[
  {
    "path": ".clang-format",
    "content": "#http://clang.llvm.org/docs/ClangFormatStyleOptions.html\n\nBasedOnStyle: LLVM\nIndentWidth: 4\nTabWidth: 4\nUseTab: ForContinuationAndIndentation\nMaxEmptyLinesToKeep: 1\nAllowShortFunctionsOnASingleLine: Empty\nBreakBeforeBraces: Linux\nColumnLimit: 120\n"
  },
  {
    "path": ".clang-tidy",
    "content": "Checks: >\n  -*,\n  modernize-*,\n  bugprone-*,\n  concurrency-*,\n  misc-*,\n  readability-*,\n  performance-*,\n  portability-*,\n  google-*,\n  linuxkernel-*,\n  -bugprone-narrowing-conversions,\n  -bugprone-branch-clone,\n  -bugprone-reserved-identifier,\n  -bugprone-easily-swappable-parameters,\n  -bugprone-sizeof-expression,\n  -bugprone-implicit-widening-of-multiplication-result,\n  -bugprone-suspicious-memory-comparison,\n  -bugprone-not-null-terminated-result,\n  -bugprone-signal-handler,\n  -bugprone-assignment-in-if-condition,\n  -concurrency-mt-unsafe,\n  -modernize-macro-to-enum,\n  -misc-unused-parameters,\n  -misc-misplaced-widening-cast,\n  -misc-no-recursion,\n  -misc-include-cleaner,\n  -readability-magic-numbers,\n  -readability-use-anyofallof,\n  -readability-identifier-length,\n  -readability-function-cognitive-complexity,\n  -readability-named-parameter,\n  -readability-isolate-declaration,\n  -readability-else-after-return,\n  -readability-redundant-control-flow,\n  -readability-suspicious-call-argument,\n  -readability-math-missing-parentheses,\n  -google-readability-casting,\n  -google-readability-todo,\n  -performance-no-int-to-ptr,\n#   clang-analyzer-*,\n#   clang-analyzer-deadcode.DeadStores,\n#   clang-analyzer-optin.performance.Padding,\n#   -clang-analyzer-security.insecureAPI.*\n\n# Turn all the warnings from the checks above into errors.\nFormatStyle: file\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.md",
    "content": "---\r\nname: 需求建议\r\nabout: 需求建议描述\r\ntitle: ''\r\nlabels: ''\r\nassignees: ''\r\n\r\n---\r\n\r\n**需求应用场景**\r\n请描述需求应用的场景和方式。\r\n\r\n**建议的方案**\r\n实现上述场景建议的方案。\r\n\r\n**设备信息**\r\n1. 设备信息（CPU，厂家）  \r\n\r\n2. 固件信息\r\n\r\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue.md",
    "content": "---\r\nname: 问题报告\r\nabout: 问题现象描述\r\ntitle: ''\r\nlabels: ''\r\nassignees: ''\r\n\r\n---\r\n\r\n**问题现象**  \r\n简要描述问题出现的现象\r\n\r\n**运行环境**\r\n1. 固件型号  \r\n\r\n2. 运营商  \r\n\r\n3. smartdns来源以及版本\r\n\r\n4. 涉及的配置(注意去除个人相关信息)\r\n\r\n\r\n**重现步骤**\r\n1. 上游DNS配置。  \r\n\r\n2. 访问的域名。  \r\n\r\n\r\n**信息收集**  \r\n1. 将/var/log/smrtdns.log日志作为附件上传(注意去除个人相关信息)。  \r\n2. 如进程异常，请将coredump功能开启，上传coredump信息文件，同时上传配套的smartdns进程文件。  \r\n在自定义界面，开启设置->自定义设置->生成coredump配置，重现问题后提交coredump文件\r\ncoredump文件在/tmp目录下\r\n\r\n"
  },
  {
    "path": ".github/workflows/c-cpp.yml",
    "content": "name: C/C++ CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: prepare\n      run: |\n        sudo apt update\n        sudo apt install libgtest-dev dnsperf\n    - name: make\n      run: |\n        make all -j4\n        make clean\n    - name: test\n      run: |\n        make -C test test -j8\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: Publish Docker Image\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'new image tag(e.g. v1.1.0)'\n        required: true\n        default: 'latest'\n\nconcurrency: \n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n   docker:\n     runs-on: ubuntu-latest\n     steps:\n       - name: Checkout repository\n         uses: actions/checkout@v3\n       - name: Set up QEMU\n         uses: docker/setup-qemu-action@v2\n       - name: Set up Docker Buildx\n         uses: docker/setup-buildx-action@v2\n       - name: Login to DockerHub\n         uses: docker/login-action@v2\n         with:\n           username: ${{ secrets.DOCKERHUB_USERNAME }}\n           password: ${{ secrets.DOCKERHUB_TOKEN }}\n       - name: Build and push\n         uses: docker/build-push-action@v3\n         with:\n           platforms: linux/amd64,linux/arm64\n           push: true\n           tags: ${{vars.DOCKERHUB_REPO}}:${{ github.event.inputs.version }}\n"
  },
  {
    "path": ".github/workflows/webui.yml",
    "content": "name: WebUI CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - uses: actions-rust-lang/setup-rust-toolchain@v1\n      with:\n        profile: minimal\n        toolchain: stable\n    - name: test\n      run: |\n        EXTRA_CFLAGS=-fPIC make -C plugin/smartdns-ui test -j8\n"
  },
  {
    "path": ".gitignore",
    "content": ".vscode\n*.o\n*.a\n*.pem\n.DS_Store\n*.swp.\n*.a\nsystemd/smartdns.service\ntest.bin\npackage/target\npackage/*.gz\npackage/*.ipk\ntarget\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM ubuntu:latest AS smartdns-builder\nLABEL previous-stage=smartdns-builder\n\n# prepare builder\nARG OPENSSL_VER=3.5.4\nARG NODE_VERSION=20.x\nRUN apt update && \\\n    apt install -y binutils perl curl make gcc clang wget unzip ca-certificates && \\\n    update-ca-certificates && \\\n    curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION} | bash - && \\\n    apt install -y nodejs && \\\n    node --version && npm --version && \\\n    \\\n    curl https://sh.rustup.rs -sSf | sh -s -- -y && \\\n    export PATH=\"$HOME/.cargo/bin:$PATH\" && \\\n    \\\n    mkdir -p /build/openssl && \\\n    cd /build/openssl && \\\n    curl -sSL https://www.github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VER}/openssl-${OPENSSL_VER}.tar.gz | tar --strip-components=1 -zxv && \\\n    \\\n    OPENSSL_OPTIONS=\"no-argon2 no-aria no-async no-bf no-blake2 no-camellia no-cmp no-cms \" \\\n    OPENSSL_OPTIONS=\"$OPENSSL_OPTIONS no-comp no-des no-dh no-dsa no-ec2m no-engine no-gost \"\\\n    OPENSSL_OPTIONS=\"$OPENSSL_OPTIONS no-http no-idea no-legacy no-md4 no-mdc2 no-multiblock \"\\\n    OPENSSL_OPTIONS=\"$OPENSSL_OPTIONS no-nextprotoneg no-ocb no-ocsp no-rc2 no-rc4 no-rmd160 \"\\\n    OPENSSL_OPTIONS=\"$OPENSSL_OPTIONS no-scrypt no-seed no-siphash no-siv no-sm2 no-sm3 no-sm4 \"\\\n    OPENSSL_OPTIONS=\"$OPENSSL_OPTIONS no-srp no-srtp no-ts no-whirlpool no-apps no-ssl-trace \"\\\n    OPENSSL_OPTIONS=\"$OPENSSL_OPTIONS no-ssl no-ssl3 no-tests -Os\" \\\n    cd /build/openssl && \\\n    if [ \"$(uname -m)\" = \"aarch64\" ]; then \\\n        ./config --prefix=/opt/build $OPENSSL_OPTIONS -mno-outline-atomics ; \\\n    else \\ \n        ./config --prefix=/opt/build $OPENSSL_OPTIONS ; \\\n    fi && \\\n    mkdir -p /opt/build/lib /opt/build/lib64 && \\\n    make all -j8 && make install_sw && \\\n    cd / && rm -rf /build\n\n# do make\nCOPY . /build/smartdns/\nRUN cd /build/smartdns && \\\n    export CFLAGS=\"-I /opt/build/include\" && \\\n    export LDFLAGS=\"-L /opt/build/lib -L /opt/build/lib64\" && \\\n    export PATH=\"$HOME/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\" && \\\n    rm -fr /build/smartdns/package/*.tar.gz && \\\n    sh ./package/build-pkg.sh --platform linux --arch `dpkg --print-architecture` --with-ui --static && \\\n    \\\n    ( cd package && tar -xvf *.tar.gz && chmod a+x smartdns/etc/init.d/smartdns ) && \\\n    \\\n    mkdir -p /release/var/log /release/run /release/var/lib/smartdns && \\\n    cp package/smartdns/etc /release/ -a && \\\n    cp package/smartdns/usr /release/ -a && \\\n    rm -f /release/usr/local/smartdns/lib/libssl* && \\\n    rm -f /release/usr/local/smartdns/lib/libcrypto* && \\\n    cp /opt/build/lib/lib*.so* /release/usr/local/lib/smartdns/lib/ -a 2>/dev/null || true && \\\n    cp /opt/build/lib64/lib*.so* /release/usr/local/lib/smartdns/lib/ -a 2>/dev/null || true && \\\n    cd / && rm -rf /build\n\nFROM busybox:stable-musl\nCOPY --from=smartdns-builder /release/ /\nEXPOSE 53/udp 6080/tcp\nVOLUME [\"/etc/smartdns/\", \"/var/lib/smartdns/\"]\n\nCMD [\"/usr/sbin/smartdns\", \"-f\", \"-x\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nPKG_CONFIG := pkg-config\nDESTDIR :=\nPREFIX := /usr\nSBINDIR := $(PREFIX)/sbin\nSLIBDIR := $(PREFIX)/lib\nSYSCONFDIR := /etc\nRUNSTATEDIR := /run\nSYSTEMDSYSTEMUNITDIR := $(shell ${PKG_CONFIG} --variable=systemdsystemunitdir systemd)\nSMARTDNS_SYSTEMD = systemd/smartdns.service\n\nifneq ($(strip $(DESTDIR)),)\n$(shell mkdir -p $(DESTDIR) -m 0755)\noverride DESTDIR := $(realpath $(DESTDIR))\nendif\n\nPLUGINS := \nWITH_UI ?= 0\n\nifeq ($(WITH_UI), 1)\nPLUGINS += plugin/smartdns-ui\nendif\n\ndefine PLUGINS_TARGETS\n    $(foreach plugin,$(PLUGINS),$(MAKE) $(MFLAGS) DESTDIR=$(DESTDIR) -C $(plugin) $(1);)\nendef\n\n.PHONY: all clean install help SMARTDNS_BIN \nall: SMARTDNS_BIN \n\nSMARTDNS_BIN: $(SMARTDNS_SYSTEMD)\n\t$(MAKE) $(MFLAGS) -C src all\n\t$(call PLUGINS_TARGETS, all)\n\n$(SMARTDNS_SYSTEMD): systemd/smartdns.service.in\n\tcp $< $@\n\tsed -i 's|@SBINDIR@|$(SBINDIR)|' $@\n\tsed -i 's|@SYSCONFDIR@|$(SYSCONFDIR)|' $@\n\tsed -i 's|@RUNSTATEDIR@|$(RUNSTATEDIR)|' $@\n\nhelp:\n\t@echo \"Options:\"\n\t@echo \"  WITH_UI=1: Build with smartdns-ui plugin\" \n\t@echo \"  OPTIMIZE_SIZE=1: Optimize size of the smartdns-ui plugin (only for smartdns-ui)\"\n\t@echo \"  DESTDIR: Specify the installation directory prefix\"\n\nclean:\n\t$(MAKE) $(MFLAGS) -C src clean  \n\t$(RM) $(SMARTDNS_SYSTEMD)\n\t$(call PLUGINS_TARGETS, clean)\n\ninstall: SMARTDNS_BIN \n\tinstall -v -m 0640 -D -t $(DESTDIR)$(SYSCONFDIR)/default etc/default/smartdns\n\tinstall -v -m 0755 -D -t $(DESTDIR)$(SYSCONFDIR)/init.d etc/init.d/smartdns\n\tinstall -v -m 0640 -D -t $(DESTDIR)$(SYSCONFDIR)/smartdns etc/smartdns/smartdns.conf\n\tinstall -v -m 0755 -D -t $(DESTDIR)$(SBINDIR) src/smartdns\n\tinstall -v -m 0644 -D -t $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) systemd/smartdns.service\n\t$(call PLUGINS_TARGETS, install)\n\n"
  },
  {
    "path": "ReadMe.md",
    "content": "# SmartDNS\n\n**[English](ReadMe_en.md)**\n\n![SmartDNS](doc/smartdns-banner.png)\nSmartDNS 是一个运行在本地的 DNS 服务器，它接受来自本地客户端的 DNS 查询请求，然后从多个上游 DNS 服务器获取 DNS 查询结果，并将访问速度最快的结果返回给客户端，以此提高网络访问速度。\nSmartDNS 同时支持指定特定域名 IP 地址，并高性匹配，可达到过滤广告的效果; 支持DOT，DOH，DOQ，DOH3，更好的保护隐私。  \n\n与 DNSmasq 的 all-servers 不同，SmartDNS 返回的是访问速度最快的解析结果。\n\n支持树莓派、OpenWrt、华硕路由器原生固件和 Windows 系统等。\n\n## 使用指导\n\nSmartDNS官网：[https://pymumu.github.io/smartdns](https://pymumu.github.io/smartdns)\n\n## 软件效果展示\n\n### 仪表盘\n\n![SmartDNS-WebUI](doc/smartdns-webui.png)\n\n### 速度对比\n\n**阿里 DNS**  \n使用阿里 DNS 查询百度IP，并检测结果。  \n\n```shell\n$ nslookup www.baidu.com 223.5.5.5\nServer:         223.5.5.5\nAddress:        223.5.5.5#53\n\nNon-authoritative answer:\nwww.baidu.com   canonical name = www.a.shifen.com.\nName:   www.a.shifen.com\nAddress: 180.97.33.108\nName:   www.a.shifen.com\nAddress: 180.97.33.107\n\n$ ping 180.97.33.107 -c 2\nPING 180.97.33.107 (180.97.33.107) 56(84) bytes of data.\n64 bytes from 180.97.33.107: icmp_seq=1 ttl=55 time=24.3 ms\n64 bytes from 180.97.33.107: icmp_seq=2 ttl=55 time=24.2 ms\n\n--- 180.97.33.107 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1001ms\nrtt min/avg/max/mdev = 24.275/24.327/24.380/0.164 ms\npi@raspberrypi:~/code/smartdns_build $ ping 180.97.33.108 -c 2\nPING 180.97.33.108 (180.97.33.108) 56(84) bytes of data.\n64 bytes from 180.97.33.108: icmp_seq=1 ttl=55 time=31.1 ms\n64 bytes from 180.97.33.108: icmp_seq=2 ttl=55 time=31.0 ms\n\n--- 180.97.33.108 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1001ms\nrtt min/avg/max/mdev = 31.014/31.094/31.175/0.193 ms\n```\n\n**SmartDNS**  \n使用 SmartDNS 查询百度 IP，并检测结果。\n\n```shell\n$ nslookup www.baidu.com\nServer:         192.168.1.1\nAddress:        192.168.1.1#53\n\nNon-authoritative answer:\nwww.baidu.com   canonical name = www.a.shifen.com.\nName:   www.a.shifen.com\nAddress: 14.215.177.39\n\n$ ping 14.215.177.39 -c 2\nPING 14.215.177.39 (14.215.177.39) 56(84) bytes of data.\n64 bytes from 14.215.177.39: icmp_seq=1 ttl=56 time=6.31 ms\n64 bytes from 14.215.177.39: icmp_seq=2 ttl=56 time=5.95 ms\n\n--- 14.215.177.39 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1001ms\nrtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms\n```\n\n从对比看出，SmartDNS 找到了访问 `www.baidu.com` 最快的 IP 地址，比阿里 DNS 速度快了 5 倍。\n\n## 特性\n\n1. **多虚拟DNS服务器**  \n   支持多个虚拟DNS服务器，不同虚拟DNS服务器不同的端口，规则，客户端。\n\n1. **多 DNS 上游服务器**  \n   支持配置多个上游 DNS 服务器，并同时进行查询，即使其中有 DNS 服务器异常，也不会影响查询。  \n\n1. **支持每个客户端独立控制**  \n   支持基于MAC，IP地址控制客户端使用不同查询规则，可实现家长控制等功能。  \n\n1. **返回最快 IP 地址**  \n   支持从域名所属 IP 地址列表中查找到访问速度最快的 IP 地址，并返回给客户端，提高网络访问速度。\n\n1. **支持多种查询协议**  \n   支持 UDP、TCP、DOT、DOH、DOQ 和 DOH3 查询及服务，以及非 53 端口查询；支持通过socks5，HTTP代理查询;\n\n1. **特定域名 IP 地址指定**  \n   支持指定域名的 IP 地址，达到广告过滤效果、避免恶意网站的效果。\n\n1. **域名高性能后缀匹配**  \n   支持域名后缀匹配模式，简化过滤配置，过滤 20 万条记录时间 < 1ms。\n\n1. **域名分流**  \n   支持域名分流，不同类型的域名向不同的 DNS 服务器查询，支持iptable和nftable更好的分流；支持测速失败的情况下设置域名结果到对应ipset和nftset集合。\n\n1. **Windows / Linux 多平台支持**  \n   支持标准 Linux 系统（树莓派）、OpenWrt 系统各种固件和华硕路由器原生固件。同时还支持 WSL（Windows Subsystem for Linux，适用于 Linux 的 Windows 子系统）。\n\n1. **支持 IPv4、IPv6 双栈**  \n   支持 IPv4 和 IPV 6网络，支持查询 A 和 AAAA 记录，支持双栈 IP 速度优化，并支持完全禁用 IPv6 AAAA 解析。\n\n1. **支持DNS64**  \n   支持DNS64转换。\n\n1. **高性能、占用资源少**  \n   多线程异步 IO 模式，cache 缓存查询结果。\n\n1. **主流系统官方支持**  \n   主流路由系统官方软件源安装smartdns。\n\n## 架构\n\n![Architecture](https://github.com/pymumu/test/releases/download/blob/architecture.png)\n\n1. SmartDNS 接收本地网络设备的DNS 查询请求，如 PC、手机的查询请求；\n1. 然后将查询请求发送到多个上游 DNS 服务器，可支持 UDP 标准端口或非标准端口查询，以及 TCP 查询；\n1. 上游 DNS 服务器返回域名对应的服务器 IP 地址列表，SmartDNS 则会检测从本地网络访问速度最快的服务器 IP；\n1. 最后将访问速度最快的服务器 IP 返回给本地客户端。\n\n## 编译\n\n- 代码编译：\n\n  SmartDNS 提供了编译软件包的脚本（`package/build-pkg.sh`），支持编译 LuCI、Debian、OpenWrt 和 Optware 安装包。\n\n- 文档编译：\n\n  文档分支为`doc`，安装`mkdocs`工具后，执行`mkdocs build`编译。\n\n## 捐赠\n\n如果你觉得此项目对你有帮助，请捐助我们，使项目能持续发展和更加完善。\n\n### PayPal 贝宝\n\n[![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://paypal.me/PengNick/)\n\n### AliPay 支付宝\n\n![alipay](doc/alipay_donate.jpg)\n\n### WeChat Pay 微信支付\n\n![wechat](doc/wechat_donate.jpg)\n\n## 开源声明\n\nSmartDNS 基于 GPL V3 协议开源。\n"
  },
  {
    "path": "ReadMe_en.md",
    "content": "# SmartDNS\n\n![SmartDNS](doc/smartdns-banner.png)  \nSmartDNS is a local DNS server. SmartDNS accepts DNS query requests from local clients, obtains DNS query results from multiple upstream DNS servers, and returns the fastest access results to clients. supports secure DNS protocols like DoT, DoH, DoQ, DoH3, better protect privacy,  \nAvoiding DNS pollution and improving network access speed, supports high-performance ad filtering.\n\nUnlike dnsmasq's all-servers, smartdns returns the fastest access resolution.\n\nSupport Raspberry Pi, openwrt, ASUS router, Windows and other devices.  \n\n## Usage\n\nPlease visit website: [https://pymumu.github.io/smartdns](https://pymumu.github.io/smartdns/en)\n\n## Software Show\n\n### Dashboard\n\n![SmartDNS-WebUI](doc/smartdns-webui.png)\n\n### Speed Comparison\n\n**Ali DNS**  \nUse Ali DNS to query Baidu's IP and test the results.  \n\n```shell\npi@raspberrypi:~/code/smartdns_build $ nslookup www.baidu.com 223.5.5.5\nServer:         223.5.5.5\nAddress:        223.5.5.5#53\n\nNon-authoritative answer:\nwww.baidu.com   canonical name = www.a.shifen.com.\nName:   www.a.shifen.com\nAddress: 180.97.33.108\nName:   www.a.shifen.com\nAddress: 180.97.33.107\n\npi@raspberrypi:~/code/smartdns_build $ ping 180.97.33.107 -c 2\nPING 180.97.33.107 (180.97.33.107) 56(84) bytes of data.\n64 bytes from 180.97.33.107: icmp_seq=1 ttl=55 time=24.3 ms\n64 bytes from 180.97.33.107: icmp_seq=2 ttl=55 time=24.2 ms\n\n--- 180.97.33.107 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1001ms\nrtt min/avg/max/mdev = 24.275/24.327/24.380/0.164 ms\npi@raspberrypi:~/code/smartdns_build $ ping 180.97.33.108 -c 2\nPING 180.97.33.108 (180.97.33.108) 56(84) bytes of data.\n64 bytes from 180.97.33.108: icmp_seq=1 ttl=55 time=31.1 ms\n64 bytes from 180.97.33.108: icmp_seq=2 ttl=55 time=31.0 ms\n\n--- 180.97.33.108 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1001ms\nrtt min/avg/max/mdev = 31.014/31.094/31.175/0.193 ms\n```\n\n**smartdns**  \nUse SmartDNS to query Baidu IP and test the results.\n\n```shell\npi@raspberrypi:~/code/smartdns_build $ nslookup www.baidu.com\nServer:         192.168.1.1\nAddress:        192.168.1.1#53\n\nNon-authoritative answer:\nwww.baidu.com   canonical name = www.a.shifen.com.\nName:   www.a.shifen.com\nAddress: 14.215.177.39\n\npi@raspberrypi:~/code/smartdns_build $ ping 14.215.177.39 -c 2\nPING 14.215.177.39 (14.215.177.39) 56(84) bytes of data.\n64 bytes from 14.215.177.39: icmp_seq=1 ttl=56 time=6.31 ms\n64 bytes from 14.215.177.39: icmp_seq=2 ttl=56 time=5.95 ms\n\n--- 14.215.177.39 ping statistics ---\n2 packets transmitted, 2 received, 0% packet loss, time 1001ms\nrtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms\n\n```\n\nFrom the comparison, smartdns found the fastest IP address to visit www.baidu.com, so accessing Baidu's DNS is 5 times faster than Ali DNS.\n\n## Features\n\n1. **Multiple Virtual DNS server**  \n   Support multiple virtual DNS servers with different ports, rules, and clients.\n\n1. **Multiple upstream DNS servers**  \n   Support configuring multiple upstream DNS servers and query at the same time.the query will not be affected, Even if there is a DNS server exception.  \n\n1. **Support per-client query control**  \n   Support controlling clients using different query rules based on MAC and IP addresses, enabling features such as parental control.  \n\n1. **Return the fastest IP address**  \n   Support finding the fastest access IP address from the IP address list of the domain name and returning it to the client to avoid DNS pollution and improve network access speed.\n\n1. **Support for multiple query protocols**  \n   Support UDP, TCP, DOT(DNS over TLS), DOH(DNS over HTTPS), DOQ(DNS over Quic), DOH3(DNS over HTTP3) queries and service, and non-53 port queries, effectively avoiding DNS pollution and protect privacy, and support query DNS over socks5, http proxy.\n\n1. **Domain IP address specification**  \n   Support configuring IP address of specific domain to achieve the effect of advertising filtering, and avoid malicious websites.\n\n1. **Domain name high performance rule filtering**  \n   Support domain name suffix matching mode, simplify filtering configuration, filter 200,000 recording and take time <1ms.\n\n1. **Linux/Windows multi-platform support**  \n   Support standard Linux system (Raspberry Pi), openwrt system various firmware, ASUS router native firmware. Support Windows 10 WSL (Windows Subsystem for Linux).\n\n1. **Support IPV4, IPV6 dual stack**  \n   Support IPV4, IPV6 network, support query A, AAAA record, dual-stack IP selection, and filter IPV6 AAAA record.\n\n1. **DNS64**  \n   Support DNS64 translation.\n\n1. **High performance, low resource consumption**  \n   Multi-threaded asynchronous IO mode, cache cache query results.\n\n1. **DNS domain forwarding**  \n   Support DNS forwarding, ipset and nftables. Support setting the domain result to ipset and nftset set when speed check fails.\n\n## Architecture\n\n![Architecture](doc/architecture.png)\n\n1. SmartDNS receives DNS query requests from local network devices, such as PCs and mobile phone query requests.\n1. SmartDNS sends query requests to multiple upstream DNS servers, using standard UDP queries, non-standard port UDP queries, and TCP queries.\n1. The upstream DNS server returns a list of Server IP addresses corresponding to the domain name. SmartDNS detects the fastest Server IP with local network access.\n1. Return the fastest accessed Server IP to the local client.\n\n## Compile\n\nsmartdns contains scripts for compiling packages, supports compiling luci, debian, openwrt, optware installation packages, and can execute `package/build-pkg.sh` compilation.\n\n## [Donate](#donate)  \n\nIf you feel that this project is helpful to you, please donate to us so that the project can continue to develop and be more perfect.\n\n### PayPal\n\n[![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://paypal.me/PengNick/)\n\n### Alipay\n\n![alipay](doc/alipay_donate.jpg)\n\n### Wechat\n  \n![wechat](doc/wechat_donate.jpg)\n\n## Open Source License\n\nSmartdns is licensed to the public under the GPL V3 License.\n"
  },
  {
    "path": "etc/default/smartdns",
    "content": "# Default settings for smartdns server. This file is sourced by /bin/sh from\n# /etc/init.d/smartdns.\n\n# Options to pass to smartdns\nSMART_DNS_OPTS=\n"
  },
  {
    "path": "etc/init.d/smartdns",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n### BEGIN INIT INFO\n# Provides:        smartdns\n# Required-Start:  $network \n# Required-Stop:   $network \n# Default-Start:   2 3 4 5\n# Default-Stop:\n# Short-Description: Start smartdns server\n### END INIT INFO\n\nPATH=/sbin:/bin:/usr/sbin:/usr/bin\n\n. /etc/default/smartdns\nSMARTDNS=/usr/sbin/smartdns\nPIDFILE=/run/smartdns.pid\nif [ ! -d \"/run\" ]; then\n\tPIDFILE=/var/run/smartdns.pid\nfi\n\ntest -x $SMARTDNS || exit 5\n\ncase $1 in\n\tstart)\n\t\t$SMARTDNS \"$SMART_DNS_OPTS\" -R\n\t\twhile true; do\n\t\t\tif [ -e \"$PIDFILE\" ]; then\n\t\t\t\tbreak;\n\t\t\tfi\n\t\t\tsleep .5\n\t\tdone\n\t\tPID=\"$(cat $PIDFILE 2>/dev/null)\"\n\t\tif [ -z \"$PID\" ]; then\n\t\t\techo \"start smartdns server failed.\"\n\t\t\texit 1\n\t\tfi\n\t\tif [ ! -e \"/proc/$PID\" ]; then\n\t\t\techo \"start smartdns server failed.\"\n\t\t\texit 1\n\t\tfi\n\t\techo \"start smartdns server success.\"\n\t\t;;\n\tstop)\n\t\tif [ ! -f \"$PIDFILE\" ]; then\n\t\t\techo \"smartdns server is stopped.\"\n\t\t\texit 0\n\t\tfi\n\t\tPID=\"$(cat $PIDFILE 2>/dev/null)\"\n\t\tif [ ! -e \"/proc/$PID\" ] || [ -z \"$PID\" ]; then\n\t\t\techo \"smartdns server is stopped\"\n\t\t\texit 0\n\t\tfi\n\n\t\tkill -TERM \"$PID\"\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Stop smartdns server failed.\"\n\t\t\texit 1;\n\t\tfi\n\t\tLOOP=1\n\t\twhile true; do\n\t\t\tif [ ! -d \"/proc/$PID\" ]; then\n\t\t\t\tbreak;\n\t\t\tfi\n\n\t\t\tif [ $LOOP -gt 12 ]; then\n\t\t\t\tkill -9 \"$PID\"\n\t\t\t\tbreak;\n\t\t\tfi\n\t\t\tLOOP=$((LOOP+1))\n\t\t\tsleep .5\n\t\tdone\n\t\techo \"Stop smartdns server success.\"\n\t\t;;\n\trestart)\n\t\t\"$0\" stop && \"$0\" start\n\t\t;;\n\tstatus)\n\t\tPID=\"$(cat \"$PIDFILE\" 2>/dev/null)\"\n\t\tif [ ! -e \"/proc/$PID\" ] || [ -z \"$PID\" ]; then\n\t\t\techo \"smartdns server is not running.\"\n\t\t\texit 1\n\t\tfi\n\t\techo \"smartdns server is running.\"\n\t\tstatus=0\n\t\t;;\n\t*)\n\t\techo \"Usage: $0 {start|stop|restart|status}\"\n\t\texit 2\n\t\t;;\nesac\n\nexit $status\n\n"
  },
  {
    "path": "etc/smartdns/smartdns.conf",
    "content": "# dns server name, default is host name\n# server-name, \n# example:\n#   server-name smartdns\n#\n\n# whether resolv local hostname to ip address\n# resolv-hostname yes\n\n# dns server run user\n# user [username]\n# example: run as nobody\n#   user nobody\n#\n\n# Include another configuration options, if -group is specified, only include the rules to specified group.\n# conf-file [file] [-group group-name]\n# conf-file blacklist-ip.conf\n# conf-file whitelist-ip.conf -group office\n# conf-file *.conf\n\n# dns server bind ip and port, default dns server port is 53, support binding multi ip and port\n# bind udp server\n#   bind [IP]:[port][@device] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]\n# bind tcp server\n#   bind-tcp [IP]:[port][@device] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]\n# bind tls server\n#   bind-tls [IP]:[port][@device] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]\n#   bind-cert-key-file [path to file]\n#      tls private key file\n#   bind-cert-file [path to file]\n#      tls cert file\n#   bind-cert-key-pass [password]\n#      tls private key password\n# bind-https server\n#   bind-https [IP]:[port][@device] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]\n# option:\n#   -group: set domain request to use the appropriate server group.\n#   -no-rule-addr: skip address rule.\n#   -no-rule-nameserver: skip nameserver rule.\n#   -no-rule-ipset: skip ipset rule or nftset rule.\n#   -no-speed-check: do not check speed.\n#   -no-cache: skip cache.\n#   -no-rule-soa: Skip address SOA(#) rules.\n#   -no-dualstack-selection: Disable dualstack ip selection.\n#   -no-ip-alias: ignore ip alias.\n#   -force-aaaa-soa: force AAAA query return SOA.\n#   -force-https-soa: force HTTPS query return SOA.\n#   -no-serve-expired: no serve expired.\n#   -no-rules: skip all rules.\n#   -ipset ipsetname: use ipset rule.\n#   -nftset nftsetname: use nftset rule.\n#   -ddr: enable ddr.\n# example: \n#  IPV4: \n#    bind :53\n#    bind :53@eth0\n#    bind :6053 -group office -no-speed-check\n#  IPV6:\n#    bind [::]:53\n#    bind [::]:53@eth0\n#    bind-tcp [::]:53\nbind [::]:53\n\n# tcp connection idle timeout\n# tcp-idle-time [second]\n\n# dns cache size\n# cache-size [number]\n#   0: for no cache\n#   -1: auto set cache size\n# cache-size 32768\n\n# dns cache memory size\n# cache-mem-size [size]\n\n# enable persist cache when restart\n# cache-persist no\n\n# cache persist file\n# cache-file /var/cache/smartdns.cache\n\n# cache persist time\n# cache-checkpoint-time [second]\n# cache-checkpoint-time 86400\n\n# prefetch domain\n# prefetch-domain [yes|no]\n# prefetch-domain yes\n\n# cache serve expired \n# serve-expired [yes|no]\n# serve-expired yes\n\n# cache serve expired TTL\n# serve-expired-ttl [num]\n# serve-expired-ttl 86400\n\n# reply TTL value to use when replying with expired data\n# serve-expired-reply-ttl [num]\n# serve-expired-reply-ttl 3\n\n# List of hosts that supply bogus NX domain results \n# bogus-nxdomain [ip/subnet]\n\n# List of IPs that will be filtered when nameserver is configured -blacklist-ip parameter\n# blacklist-ip [ip/subnet]\n\n# List of IPs that will be accepted when nameserver is configured -whitelist-ip parameter\n# whitelist-ip [ip/subnet]\n\n# List of IPs that will be ignored\n# ignore-ip [ip/subnet]\n\n# alias of IPs\n# ip-alias [ip/subnet] [ip1[,ip2]...]\n# ip-alias 192.168.0.1/24 10.9.0.1,10.9.0.2\n\n# speed check mode\n# speed-check-mode [ping|tcp:port|tcp-syn:port|none|,]\n# example:\n#   speed-check-mode ping,tcp:80,tcp:443\n#   speed-check-mode tcp:443,ping\n#   speed-check-mode tcp-syn:80,tcp-syn:443\n#   speed-check-mode none\n\n# force AAAA query return SOA\n# force-AAAA-SOA [yes|no]\n\n# force specific qtype return soa\n# force-qtype-SOA [-,][qtypeid |...]\n# force-qtype-SOA [qtypeid|start_id-end_id|,...]\n# force-qtype-SOA 65 28 add type 65,28\n# force-qtype-SOA 65,28 add type 65,28\n# force-qtype-SOA 65-68 add type 65-68\n# force-qtype-SOA -,65-68, clear type 65-68\n# force-qtype-SOA - clear all type\n# force-qtype-SOA 65\n\n# Enable IPV4, IPV6 dual stack IP optimization selection strategy\n# dualstack-ip-selection-threshold [num] (0~1000)\n# dualstack-ip-allow-force-AAAA [yes|no]\n# dualstack-ip-selection [yes|no]\n# dualstack-ip-selection no\n\n# edns client subnet\n# edns-client-subnet [ip/subnet]\n# edns-client-subnet 192.168.1.1/24\n# edns-client-subnet 8::8/56\n\n# ttl for all resource record\n# rr-ttl: ttl for all record\n# rr-ttl-min: minimum ttl for resource record\n# rr-ttl-max: maximum ttl for resource record\n# rr-ttl-reply-max: maximum reply ttl for resource record\n# example:\n# rr-ttl 300\n# rr-ttl-min 60\n# rr-ttl-max 86400\n# rr-ttl-reply-max 60\n\n# Maximum number of IPs returned to the client|8|number of IPs, 1~16\n# example:\n# max-reply-ip-num 1\n\n# Maximum number of queries per second|0|number of queries, 0 means no limit.\n# example:\n# max-query-limit 65535\n\n# response mode\n# response-mode [first-ping|fastest-ip|fastest-response]\n\n# set log level\n# log-level: [level], level=off, fatal, error, warn, notice, info, debug\n# log-file: file path of log file.\n# log-console [yes|no]: output log to console.\n# log-syslog [yes|no]: output log to syslog.\n# log-size: size of each log file, support k,m,g\n# log-num: number of logs, 0 means disable log\nlog-level info\n\n# log-file /var/log/smartdns/smartdns.log\n# log-size 128k\n# log-num 2\n# log-file-mode [mode]: file mode of log file.\n\n# dns audit\n# audit-enable [yes|no]: enable or disable audit.\n# audit-enable yes\n# audit-SOA [yes|no]: enable or disable log soa result.\n# audit-size size of each audit file, support k,m,g\n# audit-file /var/log/smartdns/smartdns-audit.log\n# audit-console [yes|no]: output audit log to console.\n# audit-syslog [yes|no]: output audit log to syslog.\n# audit-file-mode [mode]: file mode of audit file.\n# audit-size 128k\n# audit-num 2\n\n# Support reading dnsmasq dhcp file to resolve local hostname\n# dnsmasq-lease-file /var/lib/misc/dnsmasq.leases\n\n# certificate file\n# ca-file [file]\n# ca-file /etc/ssl/certs/ca-certificates.crt\n\n# certificate path\n# ca-path [path]\n# ca-path /etc/ssl/certs\n\n# remote udp dns server list\n# server [IP]:[PORT]|URL [-blacklist-ip] [-whitelist-ip] [-check-edns] [-group [group] ...] [-exclude-default-group]\n# default port is 53\n#   -blacklist-ip: filter result with blacklist ip\n#   -whitelist-ip: filter result with whitelist ip,  result in whitelist-ip will be accepted.\n#   -check-edns: result must exist edns RR, or discard result.\n#   g|-group [group]: set server to group, use with nameserver /domain/group.\n#   e|-exclude-default-group: exclude this server from default group.\n#   p|-proxy [proxy-name]: use proxy to connect to server.\n#   b|-bootstrap-dns: set as bootstrap dns server.\n#   -set-mark: set mark on packets.\n#   -subnet [ip/subnet]: set edns client subnet.\n#   -host-ip [ip]: set dns server host ip.\n#   -interface [interface]: set dns server interface.\n#   -fallback: set as fallback dns server.\n# server 8.8.8.8 -blacklist-ip -check-edns -group g1 -group g2\n# server tls://dns.google:853 \n# server quic://dns.gooel.com:443\n# server https://dns.google/dns-query\n\n# remote tcp dns server list\n# server-tcp [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-group [group] ...] [-exclude-default-group]\n# default port is 53\n# server-tcp 8.8.8.8\n\n# remote tls dns server list\n# server-tls [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]\n#   -spki-pin: TLS spki pin to verify.\n#   -tls-host-verify: cert hostname to verify.\n#   -host-name: TLS sni hostname.\n#   k|-no-check-certificate: no check certificate.\n#   p|-proxy [proxy-name]: use proxy to connect to server.\n#   -bootstrap-dns: set as bootstrap dns server.\n# Get SPKI with this command:\n#    echo | openssl s_client -connect '[ip]:853' | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n# default port is 853\n# server-tls 8.8.8.8\n# server-tls 1.0.0.1\n\n# remote quic dns server list\n# server-quic [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]\n#   -spki-pin: TLS spki pin to verify.\n#   -tls-host-verify: cert hostname to verify.\n#   -host-name: TLS sni hostname.\n#   k|-no-check-certificate: no check certificate.\n#   p|-proxy [proxy-name]: use proxy to connect to server.\n#   -bootstrap-dns: set as bootstrap dns server.\n# Get SPKI with this command:\n#    echo | openssl s_client -quic -alpn doq -connect '[ip]:853' | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n# default port is 853\n# server-quic 223.5.5.5\n\n# remote http3 dns server list\n# server-http3 [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]\n# server-h3 [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]\n#   -spki-pin: TLS spki pin to verify.\n#   -tls-host-verify: cert hostname to verify.\n#   -host-name: TLS sni hostname.\n#   k|-no-check-certificate: no check certificate.\n#   p|-proxy [proxy-name]: use proxy to connect to server.\n#   -bootstrap-dns: set as bootstrap dns server.\n# Get SPKI with this command:\n#    echo | openssl s_client -quic -alpn doq -connect '[ip]:853' | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64\n# default port is 443\n# server-http3 https://223.5.5.5/dns-query\n# server-h3 h3://223.5.5.5/dns-query\n\n# remote https dns server list\n# server-https https://[host]:[port]/path [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]\n#   -spki-pin: TLS spki pin to verify.\n#   -tls-host-verify: cert hostname to verify.\n#   -host-name: TLS sni hostname.\n#   -http-host: http host.\n#   k|-no-check-certificate: no check certificate.\n#   p|-proxy [proxy-name]: use proxy to connect to server.\n#   -bootstrap-dns: set as bootstrap dns server.\n# default port is 443\n# server-https https://cloudflare-dns.com/dns-query\n\n# socks5 and http proxy list\n# proxy-server URL -name [proxy name]\n#   URL: socks5://[username:password@]host:port\n#        http://[username:password@]host:port\n#   -name: proxy name, use with server -proxy [proxy-name]\n# example:\n#   proxy-server socks5://user:pass@1.2.3.4:1080 -name proxy\n#   proxy-server http://user:pass@1.2.3.4:3128 -name proxy\n\n# specific nameserver to domain\n# nameserver [/domain/][group|-]\n# nameserer group, set the domain name to use the appropriate server group.\n# nameserver /www.example.com/office, Set the domain name to use the appropriate server group.\n# nameserver /www.example.com/-, ignore this domain\n\n# expand ptr record from address record\n# expand-ptr-from-address yes\n\n# specific address to domain\n# address [/domain/][ip1,ip2|-|-4|-6|#|#4|#6]\n# address #, block all A and AAAA request.\n# address #6, block all AAAA request.\n# address -6, allow all AAAA request.\n# address /www.example.com/1.2.3.4, return ip 1.2.3.4 to client\n# address /www.example.com/1.2.3.4,5.6.7.8, return multiple ip addresses\n# address /www.example.com/-, ignore address, query from upstream, suffix 4, for ipv4, 6 for ipv6, none for all\n# address /www.example.com/#, return SOA to client, suffix 4, for ipv4, 6 for ipv6, none for all\n\n# specific cname to domain\n# cname /domain/target\n\n# add srv record, support multiple srv record.\n# srv-record /domain/[target][,port][,priority][,weight]\n# srv-record /_ldap._tcp.example.com/ldapserver.example.com,389\n# srv-record /_ldap._tcp.example.com/\n\n# https-record /domain/[target=][,port=][,priority=][,alph=][,ech=][,ipv4hint=][,ipv6hint=][,noiphint]\n# https-record noipv4hint,noipv6hint,noech\n# https-record /www.example.com/ipv4hint=192.168.1.2\n\n# enable DNS64 feature\n# dns64 [ip/subnet]\n# dns64 64:ff9b::/96\n\n# enable ipset timeout by ttl feature\n# ipset-timeout [yes]\n\n# specific ipset to domain\n# ipset [/domain/][ipsetname|#4:v4setname|#6:v6setname|-|#4:-|#6:-]\n# ipset [ipsetname|#4:v4setname|#6:v6setname], set global ipset.\n# ipset /www.example.com/block, set ipset with ipset name of block. \n# ipset /www.example.com/-, ignore this domain.\n# ipset ipsetname, set global ipset.\n\n# add to ipset when ping is unreachable\n# ipset-no-speed ipsetname\n# ipset-no-speed pass\n\n# enable nftset timeout by ttl feature\n# nftset-timeout [yes|no]\n# nftset-timeout yes\n\n# add to nftset when ping is unreachable\n# nftset-no-speed [#4:ip#table#set,#6:ipv6#table#setv6]\n# nftset-no-speed #4:ip#table#set\n\n# enable nftset debug, check nftset setting result, output log when error.\n# nftset-debug [yes|no]\n# nftset-debug yes\n\n# specific nftset to domain\n# nftset [/domain/][#4:ip#table#set,#6:ipv6#table#setv6]\n# nftset [#4:ip#table#set,#6:ipv6#table#setv6] set global nftset.\n# nftset /www.example.com/ip#table#set, equivalent to 'nft add element ip table set { ... }'\n# nftset /www.example.com/-, ignore this domain\n# nftset /www.example.com/#6:-, ignore ipv6\n# nftset #6:ip#table#set, set global nftset.\n\n# set ddns domain\n# ddns-domain domain\n\n# set local domain\n# local-domain domain\n\n# lookup local network hostname or ip address from mdns\n# mdns-lookup [yes|no]\n# mdns-lookup no\n\n# set hosts file\n# hosts-file [file]\n\n# set domain rules\n# domain-rules /domain/ [-speed-check-mode [...]]\n# rules:\n#   [-c] -speed-check-mode [mode]: speed check mode\n#                             speed-check-mode [ping|tcp:port|none|,]\n#   [-a] -address [address|-]: same as address option\n#   [-n] -nameserver [group|-]: same as nameserver option\n#   [-p] -ipset [ipset|-]: same as ipset option\n#   [-t] -nftset [nftset|-]: same as nftset option\n#   [-d] -dualstack-ip-selection [yes|no]: same as dualstack-ip-selection option\n#   [-g|-group group-name]: set domain-rules to group.\n#   -no-serve-expired: ignore expired domain\n#   -delete: delete domain rule\n#   -no-ip-alias: ignore ip alias\n#   -no-cache: ignore cache\n\n# collection of domains \n# the domain-set can be used with /domain/ for address, nameserver, ipset, etc.\n# domain-set -name [set-name] -type list -file [/path/to/file]\n#   [-n] -name [set name]: domain set name\n#   [-t] -type [list]: domain set type, list only now\n#   [-f] -file [path/to/set]: file path of domain set\n# \n# example:\n# domain-set -name domain-list -type list -file /etc/smartdns/domain-list.conf\n# address /domain-set:domain-list/1.2.3.4\n# nameserver /domain-set:domain-list/server-group\n# ipset /domain-set:domain-list/ipset\n# domain-rules /domain-set:domain-list/ -speed-check-mode ping\n\n# set ip rules\n# ip-rules ip-cidrs [-ip-alias [...]]\n# rules:\n#   [-c] -ip-alias [ip1,ip2]: same as ip-alias option\n#   [-a] -whitelist-ip: same as whitelist-ip option\n#   [-n] -blacklist-ip: same as blacklist-ip option\n#   [-p] -bogus-nxdomain: same as bogus-nxdomain option\n#   [-t] -ignore-ip: same as ignore-ip option\n\n# collection of IPs \n# the ip-set can be used with /ip-cidr/ for ip-alias, ignore-ip, etc.\n# ip-set -name [set-name] -type list -file [/path/to/file]\n#   [-n] -name [set name]: ip set name\n#   [-t] -type [list]: ip set type, list only now\n#   [-f] -file [path/to/set]: file path of ip set\n# \n# example:\n# ip-set -name ip-list -file /etc/smartdns/ip-list.conf\n# bogus-nxdomain ip-set:ip-list\n# ip-alias ip-set:ip-list 1.2.3.4\n# ip-alias ip-set:ip-list ip-set:ip-map-list\n\n# set client rules\n# client-rules [ip-cidr|mac|ip-set] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]\n# client-rules option is same as bind option, please see bind option for detail.\n\n# set group rules\n# group-begin [group-name] [-inherit parent-group|none|default]\n# group-match [-g|group group-name] [-domain domain] [-client-ip [ip-cidr|mac|ip-set]]\n# group-end\n\n# data directory\n# data-dir [path]\n# data-dir /var/lib/smartdns\n\n# load plugin\n# plugin [path/to/file] [args]\n\n# web ui plugin\n# plugin smartdns_ui.so\n# smartdns-ui.www-root /usr/share/smartdns/wwwroot\n# smartdns-ui.ip http://0.0.0.0:6080\n# smartdns-ui.ip https://[::]:6080\n# smartdns-ui.token-expire 600\n# smartdns-ui.max-query-log-age 86400\n# smartdns-ui.enable-terminal yes\n# smartdns-ui.enable-cors yes\n# smartdns-ui.user admin\n# smartdns-ui.password password\n"
  },
  {
    "path": "package/build-pkg.sh",
    "content": "#!/bin/sh\n# Copyright (C) 2018-2025 Nick Peng (pymumu@gmail.com)\n\nCURR_DIR=$(cd $(dirname $0);pwd)\nWORKDIR=$CURR_DIR/target\nVER=\"`date +\"1.%Y.%m.%d-%H%M\"`\"\nCODE_DIR=\"$CURR_DIR/..\"\nIS_BUILD_SMARTDNS=1\nOUTPUTDIR=$CURR_DIR\nSMARTDNS_WEBUI_URL=\"https://github.com/pymumu/smartdns-webui/archive/refs/heads/main.zip\"\nSMARTDNS_WEBUI_SOURCE=\"$WORKDIR/smartdns-webui\"\nSMARTDNS_STATIC_DIR=\"$WORKDIR/smartdns-static\"\nSMARTDNS_WITH_LIBS=0\nMAKE_NJOBS=1\n\nexport CC\nexport STRIP\nexport WORKDIR\n\nWITH_UI=0\n\nshowhelp()\n{\n\techo \"Usage: $0 [OPTION]\"\n\techo \"Options:\"\n\techo \" --platform [luci|luci-compat|debian|openwrt|optware|linux]    build for platform. \"\n\techo \" --arch [all|armhf|arm64|x86-64|...]               build for architecture, e.g. \"\n\techo \" --cross-tool [cross-tool]                         cross compiler, e.g. mips-openwrt-linux-\"\n\techo \" --with-ui                                         build with smartdns-ui plugin.\"\n\techo \"\"\n\techo \"Advance Options:\"\n\techo \" --static                                          static link smartdns\"\n\techo \" --only-package                                    only package, not build source\"\n\techo \" --filearch [arch]                                 output file arch, default: equal --arch\"\n\techo \" --outputdir [dir]                                 output package to specific directory\"\n\techo \" \"\n\techo \"Example:\"\n\techo \" build luci:\"\n\techo \"   $0 --platform luci\"\n\techo \" build luci:\"\n\techo \"   $0 --platform luci-compat\"\n\techo \" build debian:\"\n\techo \"   $0 --platform debian --arch x86-64\"\n\techo \" build raspbian pi:\"\n\techo \"   $0 --platform debian --arch arm64 --with-ui\"\n\techo \" build optware mips:\"\n\techo \"   $0 --platform optware --arch mipsbig\"\n\techo \" build openwrt mips:\"\n\techo \"   $0 --platform openwrt --arch mips\"\n\techo \" build generic linux:\"\n\techo \"   $0 --platform linux --arch x86-64 --with-ui\"\n}\n\ninit_env()\n{\n\tif [ -z \"$CC\" ]; then\n\t\tCC=gcc\n\tfi\n\n\tMAKE_NJOBS=$(grep processor /proc/cpuinfo  | wc -l 2>/dev/null || echo 1)\n\texport MAKE_NJOBS\n\n\tmkdir -p $WORKDIR\n\tif [ $? -ne 0 ]; then\n\t\techo \"create work directory failed\"\n\t\treturn 1\n\tfi\n\n\tif [ \"$STATIC\" = \"yes\" ] && [ $WITH_UI -eq 1 ]; then\n\t\tSMARTDNS_WITH_LIBS=1\n\tfi\n\n\tcheck_cc=\"`echo \"$CC\" | grep -E \"(\\-gcc|\\-cc)\"`\"\n\tif [ ! -z \"$check_cc\" ]; then\n\t\tTARGET_ARCH=\"`$CC -dumpmachine`\"\n\t\techo \"target arch: $TARGET_ARCH\"\n\tfi\n\n\tif [ $SMARTDNS_WITH_LIBS -eq 1 ]; then\n\t\tcase \"$TARGET_ARCH\" in\n\t\t\t*arm*)\n\t\t\t\tNEED_UPDATE_ARM_CP15=1\n\t\t\t\techo \"Update arm cp15\"\n\t\t\t\t;;\n\t\t\t*)\n\t\t\t\t;;\n\t\tesac\n\n\t\tLINKER_NAME=`$CC -Xlinker -v 2>&1 | grep -oP '(?<=-dynamic-linker )[^ ]+'`\n\t\tif [ -z \"$LINKER_NAME\" ]; then\n\t\t\techo \"get linker name failed\"\n\t\t\treturn 1\n\t\tfi\n\t\tLINKER_NAME=`basename $LINKER_NAME`\n\t\tLINKER_SYSROOT=\"`$CC --print-sysroot`\"\n\t\texport BINDGEN_EXTRA_CLANG_ARGS=\"--sysroot=$LINKER_SYSROOT\"\n\t\techo \"linker name: $LINKER_NAME\"\n\tfi\n}\n\n\ncopy_smartdns_libs()\n{\n\tSMARTDNS_BIN=\"$CODE_DIR/src/smartdns\"\n\n\tcopy_libs_recursive $SMARTDNS_BIN\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy libs failed\"\n\t\treturn 1\n\tfi\n\n\tLIB_WEBUI_SO=\"$CODE_DIR/plugin/smartdns-ui/target/smartdns_ui.so\"\n\tcopy_libs_recursive $LIB_WEBUI_SO\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy libs failed\"\n\t\treturn 1\n\tfi\n}\n\ncopy_libs_recursive()\n{\n\tlocal lib=$1\n\tlocal lib_path=`$CC -print-file-name=$lib`\n\tif [ -z \"$lib_path\" ]; then\n\t\treturn 0\n\tfi\n\n\tif [ -e $SMARTDNS_STATIC_DIR/lib/$lib ]; then\n\t\treturn 0\n\tfi\n\n\tlocal tmp_path=\"`echo \"$lib_path\" | grep \"libc.so\"`\"\n\tif [ ! -z \"$tmp_path\" ]; then\n\t\tLIBC_PATH=\"$tmp_path\"\n\tfi\n\n\tif [ \"$lib\" != \"$SMARTDNS_BIN\" ]; then\n\t\techo \"copy $lib_path to $SMARTDNS_STATIC_DIR/lib\"\n\t\tcp $lib_path $SMARTDNS_STATIC_DIR/lib\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"copy $lib failed\"\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tlocal shared_libs=\"`objdump -p $lib_path | grep NEEDED | awk '{print $2}'`\"\n\tfor sub_lib in $shared_libs; do\n\t\tcopy_libs_recursive $sub_lib\n\t\tif [ $? -ne 0 ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\ncopy_linker()\n{\n\tLINK_PATH=`$CC -print-file-name=$LINKER_NAME`\n\tSYM_LINKER_NAME=`readlink -f $LINK_PATH`\n\n\techo \"linker: $LINK_PATH\"\n\techo \"sym linker: $SYM_LINKER_NAME\"\n\techo \"libc: $LIBC_PATH\"\n\n\tif [ \"$SYM_LINKER_NAME\" = \"$LIBC_PATH\" ]; then\n\t\tln -f -s $(basename $LIBC_PATH) $SMARTDNS_STATIC_DIR/lib/$(basename $LINKER_NAME)\n\telse\n\t\tcp $LINK_PATH $SMARTDNS_STATIC_DIR/lib -af\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"copy $lib failed\"\n\t\t\treturn 1\n\t\tfi\n\n\t\tSYM_LINKER_NAME=`readlink $SMARTDNS_STATIC_DIR/lib/$LINKER_NAME`\n\t\tif [ ! -e $SMARTDNS_STATIC_DIR/lib/$SYM_LINKER_NAME ]; then\n\t\t\tSYM_LINKER_NAME=`basename $SYM_LINKER_NAME`\n\t\t\tln -f -s $SYM_LINKER_NAME $SMARTDNS_STATIC_DIR/lib/$LINKER_NAME\n\t\tfi\n\tfi\n\n\tln -f -s ${LINKER_NAME} ${SMARTDNS_STATIC_DIR}/lib/ld-linux.so\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy $lib failed\"\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\nbuild_smartdns()\n{\n\tMAKE_WITH_UI=\"\"\n\tif [ $WITH_UI -eq 1 ]; then\n\t\tMAKE_WITH_UI=\"WITH_UI=1 OPTIMIZE_SIZE=1\"\n\tfi\n\n\tif [ \"$PLATFORM\" = \"luci\" ]; then\n\t\treturn 0\n\tfi\n\n\tmake -C $CODE_DIR clean $MAKE_ARGS\n\tif [ $SMARTDNS_WITH_LIBS -eq 1 ]; then\n\t\tLINK_LDFLAGS='-Wl,-dynamic-linker,'lib/$(echo $LINKER_NAME)' -Wl,-rpath,\\$$ORIGIN:\\$$ORIGIN/lib'\n\t\texport LDFLAGS=\"$LDFLAGS $LINK_LDFLAGS\"\n\t\techo \"LDFLAGS: $LDFLAGS\"\n\t\tRUSTFLAGS='-C link-arg=-Wl,-rpath,$$ORIGIN'\n\t\techo \"Building smartdns with specific linker...\"\n\t\tunset STATIC\n\tfi\n\n\tRUSTFLAGS=\"$RUSTFLAGS\" make -C $CODE_DIR $MAKE_WITH_UI all -j$MAKE_NJOBS VER=$VER $MAKE_ARGS\n\tif [ $? -ne 0 ]; then\n\t\techo \"make smartdns failed\"\n\t\texit 1\n\tfi\n\n\t$STRIP -d $CODE_DIR/src/smartdns >/dev/null 2>&1\n\n\trm -fr $SMARTDNS_STATIC_DIR\n\tif [ $SMARTDNS_WITH_LIBS -eq 0 ]; then\n\t\treturn 0;\n\tfi\n\n\techo \"copy smartdns binary to $SMARTDNS_STATIC_DIR\"\n\tmkdir -p $SMARTDNS_STATIC_DIR/lib\n\tif [ $? -ne 0 ]; then\n\t\techo \"create target directory failed\"\n\t\treturn 1\n\tfi\n\n\tcp $CODE_DIR/src/smartdns $SMARTDNS_STATIC_DIR/\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy smartdns binary failed\"\n\t\treturn 1\n\tfi\n\n\tcp $CURR_DIR/run-smartdns $SMARTDNS_STATIC_DIR\n\tchmod +x $SMARTDNS_STATIC_DIR/run-smartdns\n\tif [ \"$NEED_UPDATE_ARM_CP15\" = \"1\" ]; then\n\t\tsed -i 's/NEED_CHECK_ARM_CP15=0/NEED_CHECK_ARM_CP15=1/' $SMARTDNS_STATIC_DIR/run-smartdns\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"sed run-smartdns failed\"\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tcopy_smartdns_libs\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy smartdns libs failed\"\n\t\treturn 1\n\tfi\n\trm $SMARTDNS_STATIC_DIR/lib/smartdns_ui.so >/dev/null 2>&1\n\n\tcopy_linker\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy linker failed\"\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\nbuild_webpages()\n{\n\tif [ ! -f \"$WORKDIR/smartdns-webui.zip\" ]; then\n\t\techo \"smartdns-webui source not found, downloading...\"\n\t\twget -O $WORKDIR/smartdns-webui.zip $SMARTDNS_WEBUI_URL\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to download smartdns-webui source at $SMARTDNS_WEBUI_URL\"\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tif [ ! -d \"$SMARTDNS_WEBUI_SOURCE\" ]; then\n\t\techo \"smartdns-webui source not found, unzipping...\"\n\t\tunzip -q $WORKDIR/smartdns-webui.zip -d $WORKDIR\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to unzip smartdns-webui source.\"\n\t\t\treturn 1\n\t\tfi\n\t\tmv $WORKDIR/smartdns-webui-main $SMARTDNS_WEBUI_SOURCE\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to rename smartdns-webui directory.\"\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tif [ ! -d \"$SMARTDNS_WEBUI_SOURCE\" ]; then\n\t\techo \"smartdns-webui source not found.\"\n\t\treturn 1\n\tfi\n\n\tif [ ! -f \"$SMARTDNS_WEBUI_SOURCE/package.json\" ]; then\n\t\techo \"smartdns-webui source is not valid.\"\n\t\treturn 1\n\tfi\n\n\tif [ -f \"$SMARTDNS_WEBUI_SOURCE/out/index.html\" ]; then\n\t\techo \"smartdns-webui already built, skipping build.\"\n\t\treturn 0\n\tfi\n\n\techo \"Building smartdns-webui...\"\n\tnpm install --prefix $SMARTDNS_WEBUI_SOURCE\n\tif [ $? -ne 0 ]; then\n\t\techo \"Failed to install smartdns-webui dependencies.\"\n\t\treturn 1\n\tfi\n\n\tnpm run build --prefix $SMARTDNS_WEBUI_SOURCE\n\tif [ $? -ne 0 ]; then\n\t\techo \"Failed to build smartdns-webui.\"\n\t\treturn 1\n\tfi\n\n\techo \"smartdns-webui build completed.\"\n\n\treturn 0\n}\n\nbuild()\n{\n\techo \"build package for $PLATFORM\"\n\n\tif [ $IS_BUILD_SMARTDNS -eq 1 ]; then\n\t\tbuild_smartdns\n\t\tif [ $? -ne 0 ]; then\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tWITH_UI_ARGS=\"\"\n\tif [ $WITH_UI -eq 1 ] && [ \"$PLATFORM\" != \"luci\" ]; then\n\t\tbuild_webpages\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"build smartdns-ui failed\"\n\t\t\treturn 1\n\t\tfi\n\t\tWITH_UI_ARGS=\"--with-ui\"\n\tfi\n\n\tchmod +x $CODE_DIR/package/$PLATFORM/make.sh\n\t$CODE_DIR/package/$PLATFORM/make.sh -o $CURR_DIR --arch $ARCH --ver $VER --filearch $FILEARCH $WITH_UI_ARGS -o $OUTPUTDIR \n\tif [ $? -ne 0 ]; then\n\t\techo \"build package for $PLATFORM failed\"\n\t\treturn 1\n\tfi\n\n\techo \"build package for $PLATFORM success.\"\n\treturn 0\n}\n\nmain()\n{\n\tOPTS=`getopt -o o:h --long arch:,filearch:,ver:,platform:,cross-tool:,with-nftables,static,only-package,with-ui,outputdir: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ \"$#\" -le \"1\" ]; then\n\t\tshowhelp\n\t\texit 1\n\tfi\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--arch)\n\t\t\tARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--filearch)\n\t\t\tFILEARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--platform)\n\t\t\tPLATFORM=\"$2\"\n\t\t\tshift 2;;\n\t\t--cross-tool)\n\t\t\tCROSS_TOOL=\"$2\"\n\t\t\tshift 2;;\n\t\t--static)\n\t\t\texport STATIC=\"yes\"\n\t\t\tshift 1;;\n\t\t--only-package)\n\t\t\tIS_BUILD_SMARTDNS=0\n\t\t\tshift 1;;\n\t\t--outputdir)\n\t\t\tOUTPUTDIR=\"$2\"\n\t\t\tshift 2;;\n\t\t--with-ui)\n\t\t\tWITH_UI=1\n\t\t\tshift 1;;\n\t\t--ver)\n\t\t\tVER=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n\t\tesac\n\tdone\n\n\tif [ -z \"$PLATFORM\" ]; then\n\t\techo \"please input platform\"\n\t\techo \"run $0 -h for help.\"\n\t\treturn 1\n\tfi\n\t\n\tif [ \"$PLATFORM\" = \"luci\" ]; then\n\t\tARCH=\"all\"\n\tfi\n\n\tif [ -z \"$ARCH\" ]; then\n\t\techo \"please input arch.\"\n\t\techo \"run $0 -h for help.\"\n\t\treturn 1\n\tfi\n\n\tif [ -z \"$FILEARCH\" ]; then \n\t\tFILEARCH=\"$ARCH\"\n\tfi\n\n\tif [ -z \"$OUTPUTDIR\" ]; then\n\t\tOUTPUTDIR=$CURR_DIR\n\tfi\n\n\tif [ ! -z \"$CROSS_TOOL\" ]; then\n\t\tCC=\"${CROSS_TOOL}gcc\"\n\t\tSTRIP=\"${CROSS_TOOL}strip\"\n\tfi\n\n\tif [ -z \"$CC\" ]; then\n\t\tCC=\"gcc\"\n\tfi\n\n\tif [ -z \"$STRIP\" ]; then\n\t\tif [ ! -z \"`echo $CC | grep '\\-gcc'`\" ]; then\n\t\t\tSTRIP=\"`echo \"$CC\" | sed 's/-gcc\\$/-strip/g'`\"\n\t\telse\n\t\t\tSTRIP=\"strip\"\n\t\tfi\n\tfi\n\n\tif [ ! -e \"`which $CC`\" ]; then\n\t\techo \"Cannot find compiler $CC\"\n\t\treturn 1\n\tfi\n\n\tinit_env\n\n\tbuild\n}\n\nmain $@\nexit $?\n"
  },
  {
    "path": "package/copy-smartdns.sh",
    "content": "#!/bin/sh\n\nCURR_DIR=$(cd $(dirname $0);pwd)\nWORKDIR=$CURR_DIR/target\nCODE_DIR=\"$CURR_DIR/..\"\nSMARTDNS_STATIC_DIR=\"$WORKDIR/smartdns-static\"\n\nmain() {\n\tTARGET_DIR=$1\n\tPREFIX=$2\n\tif [ -z \"$TARGET_DIR\" ]; then\n\t\techo \"Usage: $0 <target_directory> [prefix_directory]\"\n\t\texit 1\n\tfi\n\n\tif [ ! -d \"$TARGET_DIR\" ]; then\n\t\techo \"Target directory $TARGET_DIR does not exist.\"\n\t\texit 1\n\tfi\n\n\n\tif [ ! -f \"$SMARTDNS_STATIC_DIR/smartdns\" ]; then\n\t\tcp \"$CODE_DIR/src/smartdns\" \"$TARGET_DIR$PREFIX/usr/sbin/smartdns\"\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to copy smartdns binary to $TARGET_DIR$PREFIX/usr/sbin.\"\n\t\t\treturn 1\n\t\tfi\n\n\t\tchmod +x \"$TARGET_DIR$PREFIX/usr/sbin/smartdns\"\n\n\t\treturn 0\n\tfi\n\n\tif [ ! -f \"$SMARTDNS_STATIC_DIR/smartdns\" ]; then\n\t\techo \"SmartDNS binary not found in $SMARTDNS_STATIC_DIR.\"\n\t\treturn 1\n\tfi\n\n\tmkdir -p \"$TARGET_DIR$PREFIX/usr/local/lib/smartdns\"\n\tif [ $? -ne 0 ]; then\n\t\techo \"Failed to create directory $TARGET_DIR$PREFIX/usr/local/lib/smartdns.\"\n\t\treturn 1\n\tfi\n\n\tcp $SMARTDNS_STATIC_DIR/* $TARGET_DIR$PREFIX/usr/local/lib/smartdns/ -a\n\tif [ $? -ne 0 ]; then\n\t\techo \"Failed to copy smartdns static files to $TARGET_DIR$PREFIX/usr/local/lib/smartdns.\"\n\t\treturn 1\n\tfi\n\n\tln -f -s \"$PREFIX/usr/local/lib/smartdns/run-smartdns\" \"$TARGET_DIR$PREFIX/usr/sbin/smartdns\"\n\tif [ $? -ne 0 ]; then\n\t\techo \"Failed to create symlink for smartdns in $TARGET_DIR$PREFIX/usr/sbin.\"\n\t\treturn 1\n\tfi\n\tchmod +x \"$TARGET_DIR$PREFIX/usr/local/lib/smartdns/run-smartdns\"\n\n\techo \"SmartDNS files copied successfully to $TARGET_DIR$PREFIX.\"\n\treturn 0\n}\n\nmain $@\n"
  },
  {
    "path": "package/debian/DEBIAN/changelog",
    "content": "smartdns (1:1.2022.04.05) stable; urgency=low\n\n  * Initial build\n\n -- initial release. <pymumu@gmail.com>  Mon, 9 jul 2018 21:20:28 +0800\n"
  },
  {
    "path": "package/debian/DEBIAN/compat",
    "content": "9\n"
  },
  {
    "path": "package/debian/DEBIAN/conffiles",
    "content": "/etc/smartdns/smartdns.conf\n"
  },
  {
    "path": "package/debian/DEBIAN/control",
    "content": "Source: smartdns  \nMaintainer: Nick Peng <pymumu@gmail.com>\nBuild-Depends: debhelper (>= 8.0.0)\nVersion: \nSection: net\nPackage: smartdns  \nPriority: extra  \nArchitecture: armhf\nDescription: a smartdns server\n"
  },
  {
    "path": "package/debian/DEBIAN/copyright",
    "content": "Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: smartdns\nSource: http://github.com/pymumu/smartdns\n\nFiles: *\nCopyright: 2018-2025 Nick peng\nLicense: proprietary\n"
  },
  {
    "path": "package/debian/DEBIAN/prerm",
    "content": "#!/bin/sh\n\nsystemctl stop smartdns\nsystemctl disable smartdns\n"
  },
  {
    "path": "package/debian/DEBIAN/rules",
    "content": "#!/usr/bin/make -f  \n%:  \n\tdh $@ --with systemd --builddirectory=./target/\n\nclean:\n\tmake -C ../src clean\n\nbuild:\n\tmake -C ../src\n\noverride_dh_systemd_enable:\n\tdh_systemd_enable --name=smartdns \n\noverride_dh_installinit:\n\tdh_installinit --name=smartdns \n\noverride_dh_installdeb:\n\tdh_installdeb\n\tcp ../systemd/smartdns.service ${CURDIR}/debian/\n\n\n"
  },
  {
    "path": "package/debian/make.sh",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\nCURR_DIR=$(cd $(dirname $0);pwd)\nVER=\"`date +\"1.%Y.%m.%d-%H%M\"`\"\nSMARTDNS_DIR=$CURR_DIR/../../\nSMARTDNS_CP=$SMARTDNS_DIR/package/copy-smartdns.sh\nSMARTDNS_BIN=$SMARTDNS_DIR/src/smartdns\nIS_BUILD_SMARTDNS_UI=0\n\nshowhelp()\n{\n\techo \"Usage: make [OPTION]\"\n\techo \"Options:\"\n\techo \" -o               output directory.\"\n\techo \" --arch           archtecture.\"\n\techo \" --ver            version.\"\n\techo \" --with-ui        build with smartdns-ui plugin.\"\n\techo \" -h               show this message.\"\n}\n\nbuild()\n{\n\tROOT=/tmp/smartdns-deiban\n\trm -fr $ROOT\n\tmkdir -p $ROOT\n\tcd $ROOT/\n\n\tcp $CURR_DIR/DEBIAN $ROOT/ -af\n\tCONTROL=$ROOT/DEBIAN/control\n\tmkdir $ROOT/usr/sbin -p\n\tmkdir $ROOT/etc/smartdns/ -p\n\tmkdir $ROOT/etc/default/ -p\n\tmkdir $ROOT/lib/systemd/system/ -p\n\n\n\tpkgver=$(echo ${VER}| sed 's/^1\\.//g')\n\tsed -i \"s/Version:.*/Version: ${pkgver}/\" $ROOT/DEBIAN/control\n\tsed -i \"s/Architecture:.*/Architecture: $ARCH/\" $ROOT/DEBIAN/control\n\tchmod 0755 $ROOT/DEBIAN/prerm\n\n\tcp $SMARTDNS_DIR/etc/smartdns/smartdns.conf  $ROOT/etc/smartdns/\n\tcp $SMARTDNS_DIR/etc/default/smartdns  $ROOT/etc/default/\n\tcp $SMARTDNS_DIR/systemd/smartdns.service $ROOT/lib/systemd/system/ \n\n\tif [ $IS_BUILD_SMARTDNS_UI -eq 1 ]; then\n\t\tmkdir $ROOT/usr/local/lib/smartdns -p\n\t\tmkdir $ROOT/usr/share/smartdns/wwwroot -p\n\t\tcp $SMARTDNS_DIR/plugin/smartdns-ui/target/smartdns_ui.so $ROOT/usr/local/lib/smartdns/smartdns_ui.so -a\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to copy smartdns-ui plugin.\"\n\t\t\treturn 1\n\t\tfi\n\n\t\tcp $WORKDIR/smartdns-webui/out/* $ROOT/usr/share/smartdns/wwwroot/ -a\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to copy smartdns-ui plugin.\"\n\t\t\treturn 1\n\t\tfi\n\telse\n\t\techo \"smartdns-ui plugin not found, skipping copy.\"\n\tfi\n\n\t$SMARTDNS_CP $ROOT\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy smartdns file failed.\"\n\t\treturn 1\n\tfi\n\tchmod +x $ROOT/usr/sbin/smartdns 2>/dev/null\n\n\tdpkg -b $ROOT $OUTPUTDIR/smartdns.$VER.$FILEARCH.deb\n\n\trm -fr $ROOT/\n}\n\nmain()\n{\n\tOPTS=`getopt -o o:h --long arch:,ver:,with-ui,filearch: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--arch)\n\t\t\tARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--filearch)\n\t\t\tFILEARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--with-ui)\n\t\t\tIS_BUILD_SMARTDNS_UI=1\n\t\t\tshift ;;\n\t\t--ver)\n\t\t\tVER=\"$2\"\n\t\t\tshift 2;;\n\t\t-o )\n\t\t\tOUTPUTDIR=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n\t\tesac\n\tdone\n\n\tif [ -z \"$ARCH\" ]; then\n\t\techo \"please input arch.\"\n\t\treturn 1;\n\tfi\n\n\tif [ -z \"$FILEARCH\" ]; then\n\t\tFILEARCH=$ARCH\n\tfi\n\n\tif [ -z \"$OUTPUTDIR\" ]; then\n\t\tOUTPUTDIR=$CURR_DIR;\n\tfi\n\n\tbuild\n}\n\nmain $@\nexit $?\n"
  },
  {
    "path": "package/linux/install",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nINST_DIR=$(cd $(dirname $0);pwd)\nISWSL=1 # 1 means not WSL, 0 means wsl \n\nshowhelp()\n{\n\techo \"Usage: install [OPTION]\"\n\techo \"Options:\"\n\techo \" -i               install smartdns.\"\n\techo \" -u               uninstall smartdns.\"\n\techo \" -U               upgrade install smartdns.\"\n\techo \" --prefix [dir]   prefix directory.\"\n\techo \" -h               show this message.\"\n}\n\nstart_service()\n{\n\tif [ $ISSYSTEMD -ne 0 ]; then\n\t\tchkconfig smartdns on >/dev/null 2>&1\n\t\tservice smartdns start\n\t\treturn $?\n\tfi\n\n\tsystemctl daemon-reload\n\tsystemctl enable smartdns\n\tsystemctl start smartdns\n}\n\nstop_service()\n{\n\tif [ $ISSYSTEMD -ne 0 ]; then\n\t\tservice smartdns stop\n\t\tchkconfig smartdns off >/dev/null 2>&1\n\t\treturn 0\n\tfi\n\n\tsystemctl stop smartdns\n\tsystemctl disable smartdns\n\n\treturn 0\n}\n\nclean_service()\n{\n\tif [ $ISSYSTEMD -ne 0 ]; then\n\t\treturn 0\n\tfi\n\tsystemctl daemon-reload\n}\n\nget_systemd_path()\n{\n\tservice=\"`systemctl --no-legend| grep '\\.service' | head -n 1 | awk '{print $1}' 2>/dev/null`\"\n\tSERVICE_PATH=\"`systemctl show $service | grep FragmentPath | awk -F'=' '{print $2}' 2>/dev/null`\"\n\tif [ ! -z \"$SERVICE_PATH\" ]; then\n\t\tSERVICE_PATH=\"`dirname $SERVICE_PATH 2>/dev/null`\"\n\t\tif [ -d \"$SERVICE_PATH\" ]; then\n\t\t\techo \"$SERVICE_PATH\"\n\t\t\treturn 0\n\t\tfi \n\tfi\n\n\tSERVICE_PATH=\"`pkg-config systemd --variable=systemdsystemunitdir 2>/dev/null`\"\n\tif [ ! -z \"$SERVICE_PATH\" ]; then\n\t\tif [ -d \"$SERVICE_PATH\" ]; then\n\t\t\techo \"$SERVICE_PATH\"\n\t\t\treturn 0\n\t\tfi \n\tfi\n\n\tSERVICE_PATH=\"/lib/systemd/system\"\n\tif [ -d \"$SERVICE_PATH\" ]; then\n\t\techo \"$SERVICE_PATH\"\n\t\treturn 0\n\tfi\n\n\treturn 1\n}\n\ninstall_files()\n{\n\tinstall -v -d $SMARTDNS_CONF_DIR\n\tif [ $? -ne 0 ]; then\n\t\treturn 1\n\tfi\n\n\tinstall -v -d $SMARTDNS_UI_WWWROOT\n\tif [ $? -ne 0 ]; then\n\t\treturn 1\n\tfi\n\n\tinstall -v -d $SMARTDNS_PLUIGN_DIR\n\tif [ $? -ne 0 ]; then\n\t\treturn 1\n\tfi\n\n\tinstall -v -t $SMARTDNS_PLUIGN_DIR $INST_DIR/usr/local/lib/smartdns/smartdns_ui.so\n\tif [ $? -ne 0 ]; then\n\t\techo \"smartdns-ui plugin not found, skipping copy.\"\n\tfi\n\n\tif [ -d \"$INST_DIR/usr/local/lib/smartdns\" ]; then\n\t\tcp $INST_DIR/usr/local/lib/smartdns/* $SMARTDNS_PLUIGN_DIR/ -a\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to copy smartdns library files.\"\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tif [ -d \"$INST_DIR/usr/share/smartdns/wwwroot/\" ]; then\n\t\tcp $INST_DIR/usr/share/smartdns/wwwroot/* $SMARTDNS_UI_WWWROOT/ -a\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to copy smartdns-webui files.\"\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tcp $INST_DIR/usr/sbin/smartdns $PREFIX/usr/sbin -a\n\tif [ $? -ne 0 ]; then\n\t\treturn 1\n\tfi\n\tchmod +x $PREFIX/usr/sbin/smartdns\n\n\tif [ -e \"$PREFIX$SMARTDNS_CONF_DIR/smartdns.conf\" ]; then\n\t\tcp $INST_DIR/etc/smartdns/smartdns.conf $PREFIX$SMARTDNS_CONF_DIR/smartdns.conf.pkg\n\telse \n\t\tinstall -v -m 0640 -t  $PREFIX$SMARTDNS_CONF_DIR $INST_DIR/etc/smartdns/smartdns.conf\n\t\tif [ $? -ne 0 ]; then\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tinstall -v -m 0640 -t  $PREFIX/etc/default $INST_DIR/etc/default/smartdns\n\tif [ $? -ne 0 ]; then\n\t\treturn 1\n\tfi\n\t\n\tinstall -v -m 0755 -t $SMARTDNS_INIT_DIR $INST_DIR/etc/init.d/smartdns \n\tif [ $? -ne 0 ]; then\n\t\tif [ $ISSYSTEMD -ne 0 ]; then\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tif [ $ISSYSTEMD -eq 0 ]; then\n\t\tSYSTEM_UNIT_PATH=\"`get_systemd_path`\"\n\t\tif [ -z \"$SYSTEM_UNIT_PATH\" ]; then\n\t\t\techo \"cannot find systemd path\"\n\t\t\treturn 1\n\t\tfi\n\t\tinstall -v -m 0644 -t $PREFIX$SYSTEM_UNIT_PATH $INST_DIR/systemd/smartdns.service \n\t\tif [ $? -ne 0 ]; then\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\treturn 0\n}\n\nuninstall_smartdns()\n{\n\tif [ -z \"$PREFIX\" ]; then\n\t\tstop_service 2>/dev/null\n\tfi\t\n\trmdir $PREFIX$SMARTDNS_CONF_DIR 2>/dev/null\n\trm -f $PREFIX/usr/sbin/smartdns\n\trm -f $PREFIX/etc/default/smartdns\n\trm -f $PREFIX/etc/init.d/smartdns\n\trm -fr $PREFIX/usr/share/smartdns/wwwroot\n\trmdir $PREFIX/usr/share/smartdns 2>/dev/null\n\trm -fr $PREFIX/usr/local/lib/smartdns 2>/dev/null\n\n\tif [ $ISWSL -eq 0 ]; then\n\t\tsed -i '\\#%sudo ALL=NOPASSWD: /etc/init.d/smartdns#d' /etc/sudoers 2>/dev/null\n\tfi\n\n\tif [ $ISSYSTEMD -eq 0 ]; then\n\t\tSYSTEM_UNIT_PATH=\"`get_systemd_path`\"\n\t\tif [ ! -z \"$SYSTEM_UNIT_PATH\" ]; then\n\t\t\trm -f $PREFIX$SYSTEM_UNIT_PATH/smartdns.service\n\t\tfi\n\tfi\n\n\tif [ -z \"$PREFIX\" ]; then\n\t\tclean_service\n\tfi\t\n}\n\n\ninstall_smartdns()\n{\n\tlocal ret\n\n\twhich smartdns >/dev/null 2>&1\n\tif [ $? -eq 0 ]; then\n\t\techo \"Already installed.\"\n\t\treturn 1\n\tfi\n\n\tinstall_files\n\tret=$?\n\tif [ $ret -ne 0 ]; then\n\t\tuninstall_smartdns\n\t\treturn $ret\n\tfi\n\n\tif [ -z \"$PREFIX\" ]; then\n\t\tstart_service\n\tfi\n\n\tif [ $ISWSL -eq 0 ]; then\n\t\tgrep \"%sudo ALL=NOPASSWD: /etc/init.d/smartdns\" /etc/sudoers >/dev/null 2>&1\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"%sudo ALL=NOPASSWD: /etc/init.d/smartdns\" >> /etc/sudoers\n\t\tfi\n\tfi\n\n\treturn 0\n}\n\ninit_dir()\n{\n\tlocal ID=`id -u`\n\tif [ $ID -ne 0 ]; then\n\t\techo \"Please run as root.\"\n\t\treturn 1\n\tfi\n\n\tSMARTDNS_CONF_DIR=$PREFIX/etc/smartdns\n\tSMARTDNS_INIT_DIR=$PREFIX/etc/init.d\n\tSMARTDNS_UI_WWWROOT=$PREFIX/usr/share/smartdns/wwwroot\n\tSMARTDNS_PLUIGN_DIR=$PREFIX/usr/local/lib/smartdns\n\n\twhich systemctl >/dev/null 2>&1\n\tISSYSTEMD=\"$?\"\n\t# Running under WSL (Windows Subsystem for Linux)?\n\tcat /proc/version | grep -E '[Mm]icrosoft' >/dev/null 2>&1; \n\tif [ $? -eq 0 ]; then\n\t\tISSYSTEMD=1\n\t\tISWSL=0\n\tfi\n\n\tcd $INST_DIR\n}\n\nmain()\n{\n\tACTION=\"\"\n\n\tOPTS=`getopt -o iuhU --long help,prefix: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--prefix)\n\t\t\tPREFIX=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-i )\n\t\t\tACTION=\"INSTALL\"\n\t\t\tshift ;;\n\t\t-u )\n\t\t\tACTION=\"UNINSTALL\"\n\t\t\tshift ;;\n\t\t-U )\n\t\t\tACTION=\"UPGRADE\"\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n  \t\tesac\n\tdone\n\n\tinit_dir\n\n\tif [ -z \"$ACTION\" ]; then\n\t\tshowhelp\n\t\treturn 0\n\telif [ \"$ACTION\" = \"INSTALL\" ]; then\n\t\tinstall_smartdns\n\t\treturn $?\n\telif [ \"$ACTION\" = \"UNINSTALL\" ]; then\n\t\tuninstall_smartdns\n\t\treturn 0\n\telif [ \"$ACTION\" = \"UPGRADE\" ]; then\n\t\tuninstall_smartdns\n\t\tinstall_smartdns\n\t\treturn $?\n\telse\n\t\tshowhelp\n\t\treturn 1\n\tfi\n\n}\n\nmain $@\nexit $?\n\n\n"
  },
  {
    "path": "package/linux/make.sh",
    "content": "#!/bin/sh\n\nCURR_DIR=$(cd $(dirname $0);pwd)\nVER=\"`date +\"1.%Y.%m.%d-%H%M\"`\"\nSMARTDNS_DIR=$CURR_DIR/../../\nSMARTDNS_CP=$SMARTDNS_DIR/package/copy-smartdns.sh\nSMARTDNS_BIN=$SMARTDNS_DIR/src/smartdns\nIS_BUILD_SMARTDNS_UI=0\n\nshowhelp()\n{\n\techo \"Usage: make [OPTION]\"\n\techo \"Options:\"\n\techo \" -o               output directory.\"\n\techo \" --arch           archtecture.\"\n\techo \" --ver            version.\"\n\techo \" --with-ui        build with smartdns-ui plugin.\"\n\techo \" -h               show this message.\"\n}\n\nbuild()\n{\n\tPKG_ROOT=/tmp/smartdns-linux\n\trm -fr $PKG_ROOT\n\tmkdir -p $PKG_ROOT/smartdns\n\tcd $PKG_ROOT/\n\n\t# Generic x86_64\n\tmkdir $PKG_ROOT/smartdns/usr/sbin -p\n\tmkdir $PKG_ROOT/smartdns/package -p\n\tmkdir $PKG_ROOT/smartdns/systemd -p\n\t\n\tcd $SMARTDNS_DIR\n\tcp package/windows $PKG_ROOT/smartdns/package/ -a\n\tcp etc *.md LICENSE package/linux/install $PKG_ROOT/smartdns/ -a\n\tcp systemd/smartdns.service $PKG_ROOT/smartdns/systemd\n\n\t$SMARTDNS_CP $PKG_ROOT/smartdns\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy smartdns file failed.\"\n\t\trm -fr $PKG_ROOT\n\t\treturn 1\n\tfi\n\n\tif [ $IS_BUILD_SMARTDNS_UI -eq 1 ]; then\n\t\tmkdir $PKG_ROOT/smartdns/usr/local/lib/smartdns -p\n\t\tmkdir $PKG_ROOT/smartdns/usr/share/smartdns/wwwroot -p\n\t\tcp plugin/smartdns-ui/target/smartdns_ui.so $PKG_ROOT/smartdns/usr/local/lib/smartdns/smartdns_ui.so -a\n\t\tcp $WORKDIR/smartdns-webui/out/* $PKG_ROOT/smartdns/usr/share/smartdns/wwwroot/ -a\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to copy smartdns-ui plugin.\"\n\t\t\treturn 1\n\t\tfi\n\telse\n\t\techo \"smartdns-ui plugin not found, skipping copy.\"\n\tfi\n\n\tchmod +x $PKG_ROOT/smartdns/install\n\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy smartdns file failed\"\n\t\trm -fr $PKG_ROOT\n\t\texit 1\n\tfi\n\tcd $PKG_ROOT\n\ttar  zcf $OUTPUTDIR/smartdns.$VER.$FILEARCH.tar.gz smartdns\n\tif [ $? -ne 0 ]; then\n\t\techo \"create package failed\"\n\t\trm -fr $PKG_ROOT\n\t\texit 1\n\tfi\n\tcd $CURR_DIR\n\trm -fr $PKG_ROOT\n}\n\nmain()\n{\n\tOPTS=`getopt -o o:h --long arch:,ver:,with-ui,filearch: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--arch)\n\t\t\tARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--filearch)\n\t\t\tFILEARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--with-ui)\n\t\t\tIS_BUILD_SMARTDNS_UI=1\n\t\t\tshift ;;\n\t\t--ver)\n\t\t\tVER=\"$2\"\n\t\t\tshift 2;;\n\t\t-o )\n\t\t\tOUTPUTDIR=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n  \t\tesac\n\tdone\n\n\tif [ -z \"$ARCH\" ]; then\n\t\techo \"please input arch.\"\n\t\treturn 1;\n\tfi\n\n\tif [ -z \"$FILEARCH\" ]; then\n\t\tFILEARCH=$ARCH\n\tfi\n\n\tif [ -z \"$OUTPUTDIR\" ]; then\n\t\tOUTPUTDIR=$CURR_DIR;\n\tfi\n\n\tbuild\n}\n\nmain $@\nexit $?\n"
  },
  {
    "path": "package/luci/control/control",
    "content": "Package: luci-app-smartdns\nVersion: git-18.201.27126-7bf0367-1\nDepends: libc, smartdns\nSource: feeds/luci/applications/luci-app-smartdns\nSection: luci\nArchitecture: all\nDescription:  A smartdns server\n"
  },
  {
    "path": "package/luci/control/postinst",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n[ \"${IPKG_NO_SCRIPT}\" = \"1\" ] && exit 0\n[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_postinst $0 $@\n"
  },
  {
    "path": "package/luci/control/prerm",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_prerm $0 $@\n"
  },
  {
    "path": "package/luci/debian-binary",
    "content": "2.0\n"
  },
  {
    "path": "package/luci/files/luci/i18n/smartdns.zh-cn.po",
    "content": "\nmsgid \"Additional Args for upstream dns servers\"\nmsgstr \"额外的上游 DNS 服务器参数\"\n\nmsgid \"\"\n\"Additional Flags for rules, read help on domain-rule for more information.\"\nmsgstr \"额外的规则标识，具体参考domain-rule的帮助说明。\"\n\nmsgid \"\"\n\"Additional Flags for rules, read help on ip-rule for more information.\"\nmsgstr \"额外的规则标识，具体参考ip-rule的帮助说明。\"\n\nmsgid \"Additional Rule Flag\"\nmsgstr \"额外规则标识\"\n\nmsgid \"Additional Server Args\"\nmsgstr \"额外的服务器参数\"\n\nmsgid \"Additional server args, refer to the help description of the bind option.\"\nmsgstr \"额外的服务器参数，参考bind选项的帮助说明。\"\n\nmsgid \"Advanced Settings\"\nmsgstr \"高级设置\"\n\nmsgid \"Audit Log Output Mode\"\nmsgstr \"审计日志输出模式\"\n\nmsgid \"Audit Log Size\"\nmsgstr \"审计日志大小\"\n\nmsgid \"Audit Log Number\"\nmsgstr \"审计日志数量\"\n\nmsgid \"Audit Log File\"\nmsgstr \"审计日志文件路径\"\n\nmsgid \"\"\n\"Attempts to serve old responses from cache with a TTL of 0 in the response \"\n\"without waiting for the actual resolution to finish.\"\nmsgstr \"查询性能优化，有请求时尝试回应TTL为0的过期记录，以避免查询等待。\"\n\nmsgid \"Automatically Set Dnsmasq\"\nmsgstr \"自动设置Dnsmasq\"\n\nmsgid \"Automatically set as upstream of dnsmasq when port changes.\"\nmsgstr \"端口更改时自动设为 dnsmasq 的上游。\"\n\nmsgid \"Basic Settings\"\nmsgstr \"基本设置\"\n\nmsgid \"Blacklist IP\"\nmsgstr \"黑名单\"\n\nmsgid \"Blacklist IP Rule, Decline IP addresses within the range.\"\nmsgstr \"黑名单规则，拒绝指定范围的IP地址。\"\n\nmsgid \"Bind Device\"\nmsgstr \"绑定到设备\"\n\nmsgid \"Bind Device Name\"\nmsgstr \"绑定的设备名称\"\n\nmsgid \"Bogus nxdomain\"\nmsgstr \"假冒IP\"\n\nmsgid \"Block domain\"\nmsgstr \"屏蔽域名\"\n\nmsgid \"Block domain.\"\nmsgstr \"屏蔽域名。\"\n\nmsgid \"Cache Persist\"\nmsgstr \"持久化缓存\"\n\nmsgid \"Cache Size\"\nmsgstr \"缓存大小\"\n\nmsgid \"Client Rules\"\nmsgstr \"客户端规则\"\n\nmsgid \"Client Address\"\nmsgstr \"客户端地址\"\n\nmsgid \"Client Address File\"\nmsgstr \"客户端地址文件\"\n\nmsgid \"Client Rules Settings, can achieve parental control functionality.\"\nmsgstr \"客户端规则设置，可以实现家长控制功能。\"\n\nmsgid \"Collecting data ...\"\nmsgstr \"正在收集数据...\"\n\nmsgid \"\"\n\"Configure IP blacklists that will be filtered from the results of specific \"\n\"DNS server.\"\nmsgstr \"配置需要从指定域名服务器结果过滤的IP黑名单。\"\n\nmsgid \"Configure block domain list.\"\nmsgstr \"配置屏蔽域名列表\"\n\nmsgid \"Configure domain rule list.\"\nmsgstr \"配置域名规则列表\"\n\nmsgid \"Configure forwarding domain name list.\"\nmsgstr \"配置分流域名列表\"\n\nmsgid \"Configure ip rule list.\"\nmsgstr \"配置IP规则列表\"\n\nmsgid \"Custom Settings\"\nmsgstr \"自定义设置\"\n\nmsgid \"Do not use these IP addresses.\"\nmsgstr \"忽略这些IP地址\"\n\nmsgid \"DOH Server\"\nmsgstr \"DOH服务器\"\n\nmsgid \"DOH Server Port\"\nmsgstr \"DOH服务器端口\"\n\nmsgid \"DOT Server\"\nmsgstr \"DOT服务器\"\n\nmsgid \"DOT Server Port\"\nmsgstr \"DOT服务器端口\"\n\nmsgid \"DNS Block Setting\"\nmsgstr \"域名屏蔽设置\"\n\nmsgid \"DNS Forwarding Setting\"\nmsgstr \"域名分流设置\"\n\nmsgid \"DNS Server Name\"\nmsgstr \"DNS服务器名称\"\n\nmsgid \"DNS Server group\"\nmsgstr \"服务器组\"\n\nmsgid \"DNS Server group belongs to, such as office, home.\"\nmsgstr \"设置服务器组，例如office，home\"\n\nmsgid \"DNS Server ip\"\nmsgstr \"DNS服务器IP\"\n\nmsgid \"DNS Server port\"\nmsgstr \"DNS服务器端口\"\n\nmsgid \"DNS Server type\"\nmsgstr \"协议类型\"\n\nmsgid \"DNS domain result cache size\"\nmsgstr \"缓存DNS的结果，缓存大小，配置零则不缓存。\"\n\nmsgid \"DNS64\"\nmsgstr \"DNS64\"\n\nmsgid \"DNS64 Server Settings\"\nmsgstr \"DNS64服务器配置\"\n\nmsgid \"default\"\nmsgstr \"默认\"\n\nmsgid \"Description\"\nmsgstr \"描述\"\n\nmsgid \"Dnsmasq Forwarded To Smartdns Failure\"\nmsgstr \"重定向dnsmasq到smartdns失败\"\n\nmsgid \"Do not check certificate.\"\nmsgstr \"不校验证书的合法性。\"\n\nmsgid \"Do not check speed.\"\nmsgstr \"禁用测速。\"\n\nmsgid \"Domain Address\"\nmsgstr \"域名地址\"\n\nmsgid \"Domain List\"\nmsgstr \"域名列表\"\n\nmsgid \"Domain List File\"\nmsgstr \"域名列表文件\"\n\nmsgid \"Domain Rule List\"\nmsgstr \"域名规则列表\"\n\nmsgid \"Domain Rule Name\"\nmsgstr \"域名规则名称\"\n\nmsgid \"Domain Rules\"\nmsgstr \"域名规则\"\n\nmsgid \"Domain Rules Settings\"\nmsgstr \"域名规则设置\"\n\nmsgid \"Domain TTL\"\nmsgstr \"域名TTL\"\n\nmsgid \"Domain TTL Max\"\nmsgstr \"域名TTL最大值\"\n\nmsgid \"Domain TTL Min\"\nmsgstr \"域名TTL最小值\"\n\nmsgid \"Domain prefetch\"\nmsgstr \"域名预加载\"\n\nmsgid \"Donate\"\nmsgstr \"捐助\"\n\nmsgid \"Donate to smartdns\"\nmsgstr \"捐助smartdns项目\"\n\nmsgid \"Download Files\"\nmsgstr \"下载文件\"\n\nmsgid \"Download Files Setting\"\nmsgstr \"下载文件设置\"\n\nmsgid \"\"\n\"Download domain list files for domain-rule and include config files, please \"\n\"refresh the page after download to take effect.\"\nmsgstr \"\"\n\"下载域名规则所需要的域名列表文件和smartdns配置文件，下载完成后刷新页面。\"\n\nmsgid \"Dual-stack IP Selection\"\nmsgstr \"双栈IP优选\"\n\nmsgid \"Enable\"\nmsgstr \"启用\"\n\nmsgid \"Enable Auto Update\"\nmsgstr \"启用自动更新\"\n\nmsgid \"Enable IP selection between IPV4 and IPV6\"\nmsgstr \"启用 IPV4 和 IPV6 间的 IP 优选策略。\"\n\nmsgid \"Enable IPV6 DNS Server\"\nmsgstr \"启用IPV6服务器。\"\n\nmsgid \"Enable TCP DNS Server\"\nmsgstr \"启用TCP服务器。\"\n\nmsgid \"Enable daily (weekly) auto update.\"\nmsgstr \"启用每日（每周）自动更新\"\n\nmsgid \"Enable DOH DNS Server\"\nmsgstr \"启用DOH服务器\"\n\nmsgid \"Enable DOT DNS Server\"\nmsgstr \"启用DOT服务器\"\n\nmsgid \"Update time (every day)\"\nmsgstr \"更新时间（每天）\"\n\nmsgid \"Update Time (Every Week)\"\nmsgstr \"更新时间（每周）\"\n\nmsgid \"Enable Audit Log\"\nmsgstr \"启用审计日志\"\n\nmsgid \"Every Day\"\nmsgstr \"每天\"\n\nmsgid \"Every Monday\"\nmsgstr \"每周一\"\n\nmsgid \"Every Tuesday\"\nmsgstr \"每周二\"\n\nmsgid \"Every Wednesday\"\nmsgstr \"每周三\"\n\nmsgid \"Every Thursday\"\nmsgstr \"每周四\"\n\nmsgid \"Every Friday\"\nmsgstr \"每周五\"\n\nmsgid \"Every Saturday\"\nmsgstr \"每周六\"\n\nmsgid \"Every Sunday\"\nmsgstr \"每周日\"\n\nmsgid \"Enable domain prefetch, accelerate domain response speed.\"\nmsgstr \"启用域名预加载，加速域名响应速度。\"\n\nmsgid \"Enable or disable second DNS server.\"\nmsgstr \"是否启用第二DNS服务器。\"\n\nmsgid \"Enable or disable smartdns server\"\nmsgstr \"启用或禁用SmartDNS服务\"\n\nmsgid \"Exclude DNS Server from default group.\"\nmsgstr \"从default默认服务器组中排除。\"\n\nmsgid \"Exclude Default Group\"\nmsgstr \"从默认组中排除\"\n\nmsgid \"file\"\nmsgstr \"文件\"\n\nmsgid \"Fastest IP\"\nmsgstr \"最快IP\"\n\nmsgid \"Fastest Response\"\nmsgstr \"最快响应\"\n\nmsgid \"File Name\"\nmsgstr \"文件名\"\n\nmsgid \"File Type\"\nmsgstr \"文件类型\"\n\nmsgid \"Filtering IP with blacklist\"\nmsgstr \"使用IP黑名单过滤\"\n\nmsgid \"First Ping\"\nmsgstr \"最快PING\"\n\nmsgid \"Force AAAA SOA\"\nmsgstr \"停用IPV6地址解析\"\n\nmsgid \"Force AAAA SOA.\"\nmsgstr \"停用IPV6地址解析。\"\n\nmsgid \"Force HTTPS SOA\"\nmsgstr \"停用HTTPS记录解析\"\n\nmsgid \"Force HTTPS SOA.\"\nmsgstr \"停用HTTPS记录解析。\"\n\nmsgid \"General Settings\"\nmsgstr \"常规设置\"\n\nmsgid \"Generate Coredump\"\nmsgstr \"生成coredump\"\n\nmsgid \"\"\n\"Generate Coredump file when smartdns crash, coredump file is located at /tmp/\"\n\"smartdns.xxx.core.\"\nmsgstr \"\"\n\"当smartdns异常时生成coredump文件，coredump文件在/tmp/smartdns.xxx.core.\"\n\nmsgid \"Grant access to LuCI app smartdns\"\nmsgstr \"授予访问 LuCI 应用 smartdns 的权限\"\n\nmsgid \"Hosts File\"\nmsgstr \"Hosts文件\"\n\nmsgid \"HTTP Host\"\nmsgstr \"HTTP主机\"\n\nmsgid \"IP Blacklist\"\nmsgstr \"IP黑名单\"\n\nmsgid \"IP Blacklist Filtering\"\nmsgstr \"IP黑名单过滤\"\n\nmsgid \"IPV6 Server\"\nmsgstr \"IPV6服务器\"\n\nmsgid \"IP alias\"\nmsgstr \"IP别名\"\n\nmsgid \"IP Addresses\"\nmsgstr \"IP地址\"\n\nmsgid \"IP Address Mapping, Can be used for CDN acceleration with Anycast IP, such as Cloudflare's CDN.\"\nmsgstr \"IP地址映射，可用于支持AnyCast IP的CDN加速，比如Cloudflare的CDN。\"\n\nmsgid \"Ignore IP\"\nmsgstr \"忽略IP\"\n\nmsgid \"IP Rules\"\nmsgstr \"IP规则\"\n\nmsgid \"IP Rules Settings\"\nmsgstr \"IP规则设置\"\n\nmsgid \"IP Rule List\"\nmsgstr \"IP规则列表\"\n\nmsgid \"IP Rule Name\"\nmsgstr \"IP规则名称\"\n\nmsgid \"IP Set File\"\nmsgstr \"IP集合列表文件\"\n\nmsgid \"IP addresses, CIDR format.\"\nmsgstr \"IP地址，CIDR格式。\"\n\nmsgid \"IPset Name\"\nmsgstr \"IPset名称\"\n\nmsgid \"IPset name.\"\nmsgstr \"IPset名称。\"\n\nmsgid \"\"\n\"If a client address is specified, only that client will apply this \"\n\"rule. You can enter an IP address, such as 1.2.3.4, or a MAC address, \"\n\"such as aa:bb:cc:dd:ee:ff.\"\nmsgstr \"\"\n\"如果指定了客户端，那么对应的客户端会应用相应的规则，可以输入IP地址，如：1.2.3.4，或MAC地址，如：aa:bb:cc:dd:ee:ff。\"\n\nmsgid \"If you like this software, please buy me a cup of coffee.\"\nmsgstr \"如果本软件对你有帮助，请给作者加个蛋。\"\n\nmsgid \"Include Config Files<br>/etc/smartdns/conf.d\"\nmsgstr \"包含配置文件<br>/etc/smartdns/conf.d\"\n\nmsgid \"Include hosts file.\"\nmsgstr \"包含hosts文件。\"\n\nmsgid \"\"\n\"Include other config files from /etc/smartdns/conf.d or custom path, can be \"\n\"downloaded from the download page.\"\nmsgstr \"\"\n\"包含配置文件，路径为/etc/smartdns/conf.d，或自定义配置文件路径，可以从下载页\"\n\"面配置自动下载。\"\n\nmsgid \"Ipset name, Add domain result to ipset when speed check fails.\"\nmsgstr \"IPset名称，当测速失败时，将查询到的结果添加到对应的IPSet集合中。\"\n\nmsgid \"List of files to download.\"\nmsgstr \"下载文件列表。\"\n\nmsgid \"Listen only on the specified interfaces.\"\nmsgstr \"监听在指定的设备上，避免非本地网络的DNS查询请求。\"\n\nmsgid \"Local Port\"\nmsgstr \"本地端口\"\n\nmsgid \"Log Output Mode\"\nmsgstr \"日志输出模式\"\n\nmsgid \"Log Size\"\nmsgstr \"日志大小\"\n\nmsgid \"Log Level\"\nmsgstr \"日志级别\"\n\nmsgid \"Log Number\"\nmsgstr \"日志数量\"\n\nmsgid \"Log File\"\nmsgstr \"日志文件路径\"\n\nmsgid \"mDNS Lookup\"\nmsgstr \"mDNS查询\"\n\nmsgid \"Marking Packets\"\nmsgstr \"数据包标记\"\n\nmsgid \"Maximum TTL for all domain result.\"\nmsgstr \"所有域名的最大 TTL 值。\"\n\nmsgid \"Minimum TTL for all domain result.\"\nmsgstr \"所有域名的最小 TTL 值。\"\n\nmsgid \"NFTset Name\"\nmsgstr \"NFTSet名称\"\n\nmsgid \"NFTset name format error, format: [#[4|6]:[family#table#set]]\"\nmsgstr \"NFTSet名称格式错误，格式：[#[4|6]:[family#table#set]]\"\n\nmsgid \"NFTset name, format: [#[4|6]:[family#table#set]]\"\nmsgstr \"NFTSet名称，格式：[#[4|6]:[family#table#set]]\"\n\nmsgid \"NOT RUNNING\"\nmsgstr \"未运行\"\n\nmsgid \"Name of device name listen on.\"\nmsgstr \"绑定的设备名称。\"\n\nmsgid \"\"\n\"Nftset name, Add domain result to nftset when speed check fails, format: \"\n\"[#[4|6]:[family#table#set]]\"\nmsgstr \"NFTset名称，当测速失败时，将查询到的结果添加到对应的NFTSet集合中。\"\n\nmsgid \"No\"\nmsgstr \"否\"\n\nmsgid \"No Speed IPset Name\"\nmsgstr \"无速度时IPSet名称\"\n\nmsgid \"No Speed NFTset Name\"\nmsgstr \"无速度时NFTSet名称\"\n\nmsgid \"No check certificate\"\nmsgstr \"停用证书校验\"\n\nmsgid \"None\"\nmsgstr \"无\"\n\nmsgid \"Only socks5 proxy support udp server.\"\nmsgstr \"仅SOCKS5代理支持UDP服务器。\"\n\nmsgid \"Please check the system logs and check if the configuration is valid.\"\nmsgstr \"请检查系统日志，并检查配置是否合法。\"\n\nmsgid \"Please set proxy server first.\"\nmsgstr \"请先设置代理服务器。\"\n\nmsgid \"Proxy Server\"\nmsgstr \"代理服务器\"\n\nmsgid \"Proxy Server Settings\"\nmsgstr \"代理服务器设置\"\n\nmsgid \"Proxy Server URL, format: [socks5|http]://user:pass@ip:port.\"\nmsgstr \"代理服务器地址，格式：[socks5|http]://user:pass@ip:port。\"\n\nmsgid \"\"\n\"Proxy server URL format error, format: [socks5|http]://user:pass@ip:port.\"\nmsgstr \"代理服务器地址格式错误，格式：[socks5|http]://user:pass@ip:port。\"\n\nmsgid \"Query DNS through specific dns server group, such as office, home.\"\nmsgstr \"使用指定服务器组查询，比如office, home。\"\n\nmsgid \"RUNNING\"\nmsgstr \"运行中\"\n\nmsgid \"Reply Domain TTL Max\"\nmsgstr \"回应的域名TTL最大值\"\n\nmsgid \"Reply maximum TTL for all domain result.\"\nmsgstr \"设置返回给客户端的域名TTL最大值。\"\n\nmsgid \"Report bugs\"\nmsgstr \"报告BUG\"\n\nmsgid \"Return SOA when the requested result contains a specified IP address.\"\nmsgstr \"当结果包含对应范围的IP时，返回SOA。\"\n\nmsgid \"Resolve Local Hostnames\"\nmsgstr \"解析本地主机名\"\n\nmsgid \"Resolve local hostnames by reading Dnsmasq lease file.\"\nmsgstr \"读取Dnsmasq的租约文件解析本地主机名。\"\n\nmsgid \"Resolve local network hostname via mDNS protocol.\"\nmsgstr \"使用mDNS协议解析本地网络主机名。\"\n\nmsgid \"Response Mode\"\nmsgstr \"响应模式\"\n\nmsgid \"Restart\"\nmsgstr \"重启\"\n\nmsgid \"Restart Service\"\nmsgstr \"重启服务\"\n\nmsgid \"syslog\"\nmsgstr \"系统日志\"\n\nmsgid \"Second Server Settings\"\nmsgstr \"第二DNS服务器\"\n\nmsgid \"Server certificate file path.\"\nmsgstr \"服务器证书文件路径。\"\n\nmsgid \"Server certificate key file path.\"\nmsgstr \"服务器证书私钥文件路径。\"\n\nmsgid \"Server certificate key file password.\"\nmsgstr \"服务器证书私钥文件密码。\"\n\nmsgid \"Serve expired\"\nmsgstr \"缓存过期服务\"\n\nmsgid \"Server Group\"\nmsgstr \"服务器组\"\n\nmsgid \"Server Group %s not exists\"\nmsgstr \"服务器组%s不存在\"\n\nmsgid \"Server Name\"\nmsgstr \"服务器名称\"\n\nmsgid \"Server Cert\"\nmsgstr \"服务器证书\"\n\nmsgid \"Server Cert Key\"\nmsgstr \"服务器证书私钥\"\n\nmsgid \"Server Cert Key Pass\"\nmsgstr \"服务器证书私钥密码\"\n\nmsgid \"Set Specific domain ip address.\"\nmsgstr \"设置指定域名的IP地址。\"\n\nmsgid \"Set Specific domain rule list.\"\nmsgstr \"设置指定域名的规则列表。\"\n\nmsgid \"Set Specific ip blacklist.\"\nmsgstr \"设置指定的 IP 黑名单列表。\"\n\nmsgid \"Set Specific ip rule list.\"\nmsgstr \"设置对应IP的规则。\"\n\nmsgid \"Set TLS hostname to verify.\"\nmsgstr \"设置校验TLS主机名。\"\n\nmsgid \"Set mark on packets.\"\nmsgstr \"设置数据包标记。\"\n\nmsgid \"\"\n\"Set the HTTP host used for the query. Use this parameter when the host of \"\n\"the URL address is an IP address.\"\nmsgstr \"设置查询时使用的HTTP主机，当URL地址的host是IP地址时，使用此参数。\"\n\nmsgid \"Sets the server name indication for query. '-' for disable SNI name.\"\nmsgstr \"设置服务器SNI名称，‘-’表示禁用SNI名称。\"\n\nmsgid \"Settings\"\nmsgstr \"设置\"\n\nmsgid \"Skip Address Rules\"\nmsgstr \"跳过address规则\"\n\nmsgid \"Skip Cache\"\nmsgstr \"跳过cache\"\n\nmsgid \"Skip Cache.\"\nmsgstr \"跳过cache。\"\n\nmsgid \"Skip Dualstack Selection\"\nmsgstr \"跳过双栈优选\"\n\nmsgid \"Skip Dualstack Selection.\"\nmsgstr \"跳过双栈优选。\"\n\nmsgid \"Skip IP Alias\"\nmsgstr \"跳过IP别名\"\n\nmsgid \"Skip Ipset Rule\"\nmsgstr \"跳过ipset规则\"\n\nmsgid \"Skip Nameserver Rule\"\nmsgstr \"跳过Nameserver规则\"\n\nmsgid \"Skip SOA Address Rule\"\nmsgstr \"跳过address SOA(#)规则\"\n\nmsgid \"Skip SOA address rules.\"\nmsgstr \"跳过address SOA(#)规则。\"\n\nmsgid \"Skip Speed Check\"\nmsgstr \"跳过测速\"\n\nmsgid \"Skip address rules.\"\nmsgstr \"跳过address规则。\"\n\nmsgid \"Skip ipset rules.\"\nmsgstr \"跳过ipset规则。\"\n\nmsgid \"Skip nameserver rules.\"\nmsgstr \"跳过Nameserver规则。\"\n\nmsgid \"SmartDNS\"\nmsgstr \"SmartDNS\"\n\nmsgid \"Smartdns DOH server port.\"\nmsgstr \"Smartdns DOH服务器端口号。\n\nmsgid \"Smartdns DOT server port.\"\nmsgstr \"Smartdns DOT服务器端口号。\"\n\nmsgid \"SmartDNS Server\"\nmsgstr \"SmartDNS 服务器\"\n\nmsgid \"\"\n\"SmartDNS is a local high-performance DNS server, supports finding fastest \"\n\"IP, supports ad filtering, and supports avoiding DNS poisoning.\"\nmsgstr \"SmartDNS是一个本地高性能DNS服务器，支持返回最快IP，支持广告过滤。\"\n\nmsgid \"SmartDNS official website\"\nmsgstr \"SmartDNS官方网站\"\n\nmsgid \"Smartdns local server port\"\nmsgstr \"SmartDNS本地服务端口\"\n\nmsgid \"\"\n\"Smartdns local server port, smartdns will be automatically set as main dns \"\n\"when the port is 53.\"\nmsgstr \"\"\n\"SmartDNS本地服务端口，当端口号设置为53时，smartdns将会自动配置为主dns。\"\n\nmsgid \"\"\n\"Smartdns response mode, First Ping: return the first ping IP, Fastest IP: \"\n\"return the fastest IP, Fastest Response: return the fastest DNS response.\"\nmsgstr \"\"\n\"SmartDNS响应模式，最快PING： 返回最早有ping结果的IP，速度适中；最快IP： 返回\"\n\"最快IP，查询请求可能延长； 最快响应：返回最快响应的结果，查询请求时间短。\"\n\nmsgid \"Smartdns server name\"\nmsgstr \"SmartDNS的服务器名称，默认为smartdns，留空为主机名\"\n\nmsgid \"Smartdns speed check mode.\"\nmsgstr \"SmartDNS测速模式。\"\n\nmsgid \"\"\n\"Specify an IP address to return for any host in the given domains, Queries \"\n\"in the domains are never forwarded and always replied to with the specified \"\n\"IP address which may be IPv4 or IPv6.\"\nmsgstr \"\"\n\"配置特定域名返回特定的IP地址，域名查询将不到上游服务器请求，直接返回配置的IP\"\n\"地址，可用于广告屏蔽。\"\n\nmsgid \"Speed Check Mode\"\nmsgstr \"测速模式\"\n\nmsgid \"Speed check mode is invalid.\"\nmsgstr \"测速模式无效。\"\n\nmsgid \"TCP Server\"\nmsgstr \"TCP服务器\"\n\nmsgid \"TCP port is empty\"\nmsgstr \"TCP端口号为空\"\n\nmsgid \"TLS Hostname Verify\"\nmsgstr \"校验TLS主机名\"\n\nmsgid \"TLS SNI name\"\nmsgstr \"TLS SNI名称\"\n\nmsgid \"TLS SPKI Pinning\"\nmsgstr \"TLS SPKI 指纹\"\n\nmsgid \"TTL for all domain result.\"\nmsgstr \"设置所有域名的 TTL 值。\"\n\nmsgid \"Technical Support\"\nmsgstr \"技术支持\"\n\nmsgid \"URL\"\nmsgstr \"URL\"\n\nmsgid \"URL format error, format: http:// or https://\"\nmsgstr \"URL格式错误，格式：http://或https://\"\n\nmsgid \"Update\"\nmsgstr \"更新\"\n\nmsgid \"Update Files\"\nmsgstr \"更新文件\"\n\nmsgid \"Upload client address file, same as Client Address function.\"\nmsgstr \"上传客户端地址文件，与客户端地址功能相同。\"\n\nmsgid \"Upload Config File\"\nmsgstr \"上传配置文件\"\n\nmsgid \"Upload Domain List File\"\nmsgstr \"上传域名列表文件\"\n\nmsgid \"Upload domain list file to /etc/smartdns/domain-set\"\nmsgstr \"上传域名列表文件到/etc/smartdns/domain-set\"\n\nmsgid \"\"\n\"Upload domain list file, or configure auto download from Download File \"\n\"Setting page.\"\nmsgstr \"上传域名列表文件，或在下载文件设置页面设置自动下载。\"\n\nmsgid \"Upload domain list file.\"\nmsgstr \"上传域名列表文件\"\n\nmsgid \"Upload File\"\nmsgstr \"上传文件\"\n\nmsgid \"Upload IP set file.\"\nmsgstr \"上传IP集合列表文件。\"\n\nmsgid \"Upload smartdns config file to /etc/smartdns/conf.d\"\nmsgstr \"上传配置文件到/etc/smartdns/conf.d\"\n\nmsgid \"Upstream Servers\"\nmsgstr \"上游服务器\"\n\nmsgid \"\"\n\"Upstream Servers, support UDP, TCP protocol. Please configure multiple DNS \"\n\"servers, including multiple foreign DNS servers.\"\nmsgstr \"\"\n\"上游 DNS 服务器，支持 UDP，TCP 协议。请配置多个上游 DNS 服务器，包括多个国内\"\n\"外服务器。\"\n\nmsgid \"Use Proxy\"\nmsgstr \"使用代理\"\n\nmsgid \"Use proxy to connect to upstream DNS server.\"\nmsgstr \"使用代理连接上游DNS服务器。\"\n\nmsgid \"\"\n\"Used to verify the validity of the TLS server, The value is Base64 encoded \"\n\"SPKI fingerprint, leaving blank to indicate that the validity of TLS is not \"\n\"verified.\"\nmsgstr \"\"\n\"用于校验 TLS 服务器的有效性，数值为 Base64 编码的 SPKI 指纹，留空表示不验证 \"\n\"TLS 的合法性。\"\n\nmsgid \"Whitelist IP\"\nmsgstr \"白名单\"\n\nmsgid \"Whitelist IP Rule, Accept IP addresses within the range.\"\nmsgstr \"白名单规则，接受指定范围的IP地址。\"\n\nmsgid \"Write cache to disk on exit and load on startup.\"\nmsgstr \"退出时保存cache到磁盘，启动时加载。\"\n\nmsgid \"Yes\"\nmsgstr \"是\"\n\nmsgid \"default\"\nmsgstr \"默认\"\n\nmsgid \"domain list (/etc/smartdns/domain-set)\"\nmsgstr \"域名列表（/etc/smartdns/domain-set）\"\n\nmsgid \"other file (/etc/smartdns/download)\"\nmsgstr \"其它文件（/etc/smartdns/download）\"\n\nmsgid \"https\"\nmsgstr \"https\"\n\nmsgid \"ip\"\nmsgstr \"ip\"\n\nmsgid \"ip-set file (/etc/smartdns/ip-set)\"\nmsgstr \"IP集合列表文件（/etc/smartdns/ip-set）\"\n\nmsgid \"ipset name format error, format: [#[4|6]:]ipsetname\"\nmsgstr \"IPset名称格式错误，格式：[#[4|6]:]ipsetname\"\n\nmsgid \"open website\"\nmsgstr \"打开网站\"\n\nmsgid \"port\"\nmsgstr \"端口\"\n\nmsgid \"smartdns config (/etc/smartdns/conf.d)\"\nmsgstr \"smartdns 配置文件（/etc/smartdns/conf.d）\"\n\nmsgid \"smartdns custom settings\"\nmsgstr \"smartdns 自定义设置，具体配置参数参考指导\"\n\nmsgid \"tcp\"\nmsgstr \"tcp\"\n\nmsgid \"tls\"\nmsgstr \"tls\"\n\nmsgid \"type\"\nmsgstr \"类型\"\n\nmsgid \"udp\"\nmsgstr \"udp\"\n"
  },
  {
    "path": "package/luci/files/root/usr/share/luci/menu.d/luci-app-smartdns.json",
    "content": "{\n\t\"admin/services/smartdns\": {\n\t\t\"title\": \"SmartDNS\",\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smartdns/smartdns\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-smartdns\" ],\n\t\t\t\"uci\": { \"smartdns\": true }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "package/luci/files/root/usr/share/rpcd/acl.d/luci-app-smartdns.json",
    "content": "{\n\t\"luci-app-smartdns\": {\n\t\t\"description\": \"Grant access to LuCI app smartdns\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/smartdns/*\": [ \"read\" ]\n\t\t\t},\n\t\t\t\"ubus\": {\n\t\t\t\t\"service\": [ \"list\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"smartdns\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/smartdns/*\": [ \"write\" ],\n\t\t\t\t\"/etc/init.d/smartdns restart\": [ \"exec\" ],\n\t\t\t\t\"/etc/init.d/smartdns updatefiles\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"smartdns\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "package/luci/files/root/www/luci-static/resources/view/smartdns/smartdns.js",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n'use strict';\n'require fs';\n'require uci';\n'require form';\n'require view';\n'require poll';\n'require rpc';\n'require ui';\n\nvar conf = 'smartdns';\nvar callServiceList = rpc.declare({\n\tobject: 'service',\n\tmethod: 'list',\n\tparams: ['name'],\n\texpect: { '': {} }\n});\nvar pollAdded = false;\n\nfunction getServiceStatus() {\n\treturn L.resolveDefault(callServiceList(conf), {})\n\t\t.then(function (res) {\n\t\t\tvar is_running = false;\n\t\t\ttry {\n\t\t\t\tis_running = res[conf]['instances']['smartdns']['running'];\n\t\t\t} catch (e) { }\n\t\t\treturn is_running;\n\t\t});\n}\n\nfunction smartdnsServiceStatus() {\n\treturn Promise.all([\n\t\tgetServiceStatus()\n\t]);\n}\n\nfunction smartdnsRenderStatus(res) {\n\tvar renderHTML = \"\";\n\tvar isRunning = res[0];\n\n\tvar autoSetDnsmasq = uci.get_first('smartdns', 'smartdns', 'auto_set_dnsmasq');\n\tvar smartdnsPort = uci.get_first('smartdns', 'smartdns', 'port');\n\tvar smartdnsEnable = uci.get_first('smartdns', 'smartdns', 'enabled');\n\tvar dnsmasqServer = uci.get_first('dhcp', 'dnsmasq', 'server');\n\n\tif (isRunning) {\n\t\trenderHTML += \"<span style=\\\"color:green;font-weight:bold\\\">SmartDNS - \" + _(\"RUNNING\") + \"</span>\";\n\t} else {\n\t\trenderHTML += \"<span style=\\\"color:red;font-weight:bold\\\">SmartDNS - \" + _(\"NOT RUNNING\") + \"</span>\";\n\t\tif (smartdnsEnable === '1') {\n\t\t\trenderHTML += \"<br /><span style=\\\"color:red;font-weight:bold\\\">\" + _(\"Please check the system logs and check if the configuration is valid.\");\n\t\t\trenderHTML += \"</span>\";\n\t\t}\n\t\treturn renderHTML;\n\t}\n\n\tif (autoSetDnsmasq === '1' && smartdnsPort != '53') {\n\t\tvar matchLine = \"127.0.0.1#\" + smartdnsPort;\n\n\t\tuci.unload('dhcp');\n\t\tuci.load('dhcp');\n\t\tif (dnsmasqServer == undefined || dnsmasqServer.indexOf(matchLine) < 0) {\n\t\t\trenderHTML += \"<br /><span style=\\\"color:red;font-weight:bold\\\">\" + _(\"Dnsmasq Forwarded To Smartdns Failure\") + \"</span>\";\n\t\t}\n\t}\n\n\treturn renderHTML;\n}\nreturn view.extend({\n\tload: function () {\n\t\treturn Promise.all([\n\t\t\tuci.load('dhcp'),\n\t\t\tuci.load('smartdns'),\n\t\t]);\n\t},\n\trender: function (stats) {\n\t\tvar m, s, o;\n\t\tvar ss, so;\n\t\tvar servers, download_files;\n\n\t\tm = new form.Map('smartdns', _('SmartDNS'));\n\t\tm.title = _(\"SmartDNS Server\");\n\t\tm.description = _(\"SmartDNS is a local high-performance DNS server, supports finding fastest IP, \"\n\t\t\t+ \"supports ad filtering, and supports avoiding DNS poisoning.\");\n\n\t\ts = m.section(form.NamedSection, '_status');\n\t\ts.anonymous = true;\n\t\ts.render = function (section_id) {\n\t\t\tvar renderStatus = function () {\n\t\t\t\treturn L.resolveDefault(smartdnsServiceStatus()).then(function (res) {\n\t\t\t\t\tvar view = document.getElementById(\"service_status\");\n\t\t\t\t\tif (view == null) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tview.innerHTML = smartdnsRenderStatus(res);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (pollAdded == false) {\n\t\t\t\tpoll.add(renderStatus, 1);\n\t\t\t\tpollAdded = true;\n\t\t\t}\n\n\t\t\treturn E('div', { class: 'cbi-section' }, [\n\t\t\t\tE('div', { id: 'service_status' },\n\t\t\t\t\t_('Collecting data ...'))\n\t\t\t]);\n\t\t}\n\n\t\t////////////////\n\t\t// Basic;\n\t\t////////////////\n\t\ts = m.section(form.TypedSection, \"smartdns\", _(\"Settings\"), _(\"General Settings\"));\n\t\ts.anonymous = true;\n\n\t\ts.tab(\"settings\", _(\"General Settings\"));\n\t\ts.tab(\"advanced\", _('Advanced Settings'));\n\t\ts.tab(\"seconddns\", _(\"Second Server Settings\"));\n\t\ts.tab(\"dns64\", _(\"DNS64 Server Settings\"));\n\t\ts.tab(\"files\", _(\"Download Files Setting\"), _(\"Download domain list files for domain-rule and include config files, please refresh the page after download to take effect.\"));\n\t\ts.tab(\"proxy\", _(\"Proxy Server Settings\"));\n\t\ts.tab(\"custom\", _(\"Custom Settings\"));\n\n\t\t///////////////////////////////////////\n\t\t// Basic Settings\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"settings\", form.Flag, \"enabled\", _(\"Enable\"), _(\"Enable or disable smartdns server\"));\n\t\to.rmempty = false;\n\t\to.default = o.disabled;\n\n\t\t// server name;\n\t\to = s.taboption(\"settings\", form.Value, \"server_name\", _(\"Server Name\"), _(\"Smartdns server name\"));\n\t\to.placeholder = \"server name\";\n\t\to.datatype = \"hostname\";\n\t\to.rempty = true;\n\n\t\t// Port;\n\t\to = s.taboption(\"settings\", form.Value, \"port\", _(\"Local Port\"),\n\t\t\t_(\"Smartdns local server port, smartdns will be automatically set as main dns when the port is 53.\"));\n\t\to.placeholder = 53;\n\t\to.default = 53;\n\t\to.datatype = \"port\";\n\t\to.rempty = false;\n\n\t\t// auto-conf-dnsmasq;\n\t\to = s.taboption(\"settings\", form.Flag, \"auto_set_dnsmasq\", _(\"Automatically Set Dnsmasq\"), _(\"Automatically set as upstream of dnsmasq when port changes.\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t///////////////////////////////////////\n\t\t// advanced settings;\n\t\t///////////////////////////////////////\n\t\t// Speed check mode;\n\t\to = s.taboption(\"advanced\", form.Value, \"speed_check_mode\", _(\"Speed Check Mode\"), _(\"Smartdns speed check mode.\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.value(\"\", _(\"default\"));\n\t\to.value(\"ping,tcp:80,tcp:443\");\n\t\to.value(\"ping,tcp:443,tcp:80\");\n\t\to.value(\"tcp:80,tcp:443,ping\");\n\t\to.value(\"tcp:443,tcp:80,ping\");\n\t\to.value(\"none\", _(\"None\"));\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value == \"none\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar check_mode = value.split(\",\")\n\t\t\tfor (var i = 0; i < check_mode.length; i++) {\n\t\t\t\tif (check_mode[i] == \"ping\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (check_mode[i].indexOf(\"tcp:\") == 0) {\n\t\t\t\t\tvar port = check_mode[i].split(\":\")[1];\n\t\t\t\t\tif (port == \"\") {\n\t\t\t\t\t\treturn _(\"TCP port is empty\");\n\t\t\t\t\t}\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn _(\"Speed check mode is invalid.\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// response mode;\n\t\to = s.taboption(\"advanced\", form.ListValue, \"response_mode\", _(\"Response Mode\"),\n\t\t\t_(\"Smartdns response mode, First Ping: return the first ping IP, Fastest IP: return the fastest IP, Fastest Response: return the fastest DNS response.\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.value(\"\", _(\"default\"));\n\t\to.value(\"first-ping\", _(\"First Ping\"));\n\t\to.value(\"fastest-ip\", _(\"Fastest IP\"));\n\t\to.value(\"fastest-response\", _(\"Fastest Response\"));\n\n\t\t// Enable TCP server;\n\t\to = s.taboption(\"advanced\", form.Flag, \"tcp_server\", _(\"TCP Server\"), _(\"Enable TCP DNS Server\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// Enable DOT server;\n\t\to = s.taboption(\"advanced\", form.Flag, \"tls_server\", _(\"DOT Server\"), _(\"Enable DOT DNS Server\"));\n\t\to.rmempty = false;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"advanced\", form.Value, \"tls_server_port\", _(\"DOT Server Port\"), _(\"Smartdns DOT server port.\"));\n\t\to.placeholder = 853;\n\t\to.default = 853;\n\t\to.datatype = \"port\";\n\t\to.rempty = false;\n\t\to.depends('tls_server', '1');\n\n\t\t// Enable DOH server;\n\t\to = s.taboption(\"advanced\", form.Flag, \"doh_server\", _(\"DOH Server\"), _(\"Enable DOH DNS Server\"));\n\t\to.rmempty = false;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"advanced\", form.Value, \"doh_server_port\", _(\"DOH Server Port\"), _(\"Smartdns DOH server port.\"));\n\t\to.placeholder = 843;\n\t\to.default = 843;\n\t\to.datatype = \"port\";\n\t\to.rempty = false;\n\t\to.depends('doh_server', '1');\n\n\t\to = s.taboption(\"advanced\", form.Value, \"bind_cert\", _(\"Server Cert\"), _(\"Server certificate file path.\"));\n\t\to.datatype = \"string\";\n\t\to.placeholder = \"/var/etc/smartdns/smartdns/smartdns-cert.pem\"\n\t\to.rempty = true;\n\t\to.depends('tls_server', '1');\n\t\to.depends('doh_server', '1');\n\t\n\t\to = s.taboption(\"advanced\", form.Value, \"bind_cert_key\", _(\"Server Cert Key\"), _(\"Server certificate key file path.\"));\n\t\to.datatype = \"string\";\n\t\to.placeholder = \"/var/etc/smartdns/smartdns/smartdns-key.pem\"\n\t\to.rempty = false;\n\t\to.depends('tls_server', '1');\n\t\to.depends('doh_server', '1');\n\n\t\to = s.taboption(\"advanced\", form.Value, \"bind_cert_key_pass\", _(\"Server Cert Key Pass\"), _(\"Server certificate key file password.\"));\n\t\to.datatype = \"string\";\n\t\to.rempty = false;\n\t\to.depends('tls_server', '1');\n\t\to.depends('doh_server', '1');\n\n\t\t// Support IPV6;\n\t\to = s.taboption(\"advanced\", form.Flag, \"ipv6_server\", _(\"IPV6 Server\"), _(\"Enable IPV6 DNS Server\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// bind to device;\n\t\to = s.taboption(\"advanced\", form.Flag, \"bind_device\", _(\"Bind Device\"), _(\"Listen only on the specified interfaces.\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// bind device name;\n\t\to = s.taboption(\"advanced\", form.Value, \"bind_device_name\", _(\"Bind Device Name\"), _(\"Name of device name listen on.\"));\n\t\to.placeholder = \"default\";\n\t\to.rempty = true;\n\t\to.datatype = \"string\";\n\n\t\t// Support DualStack ip selection;\n\t\to = s.taboption(\"advanced\", form.Flag, \"dualstack_ip_selection\", _(\"Dual-stack IP Selection\"),\n\t\t\t_(\"Enable IP selection between IPV4 and IPV6\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// Domain prefetch load ;\n\t\to = s.taboption(\"advanced\", form.Flag, \"prefetch_domain\", _(\"Domain prefetch\"),\n\t\t\t_(\"Enable domain prefetch, accelerate domain response speed.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// Domain Serve expired\n\t\to = s.taboption(\"advanced\", form.Flag, \"serve_expired\", _(\"Serve expired\"),\n\t\t\t_(\"Attempts to serve old responses from cache with a TTL of 0 in the response without waiting for the actual resolution to finish.\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// cache-size;\n\t\to = s.taboption(\"advanced\", form.Value, \"cache_size\", _(\"Cache Size\"), _(\"DNS domain result cache size\"));\n\t\to.rempty = true;\n\n\t\t// cache-persist;\n\t\to = s.taboption(\"advanced\", form.Flag, \"cache_persist\", _(\"Cache Persist\"), _(\"Write cache to disk on exit and load on startup.\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// resolve local hostname;\n\t\to = s.taboption(\"advanced\", form.Flag, \"resolve_local_hostnames\", _(\"Resolve Local Hostnames\"), _(\"Resolve local hostnames by reading Dnsmasq lease file.\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// resolve local network hostname via mDNS;\n\t\to = s.taboption(\"advanced\", form.Flag, \"mdns_lookup\", _(\"mDNS Lookup\"), _(\"Resolve local network hostname via mDNS protocol.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// Force AAAA SOA\n\t\to = s.taboption(\"advanced\", form.Flag, \"force_aaaa_soa\", _(\"Force AAAA SOA\"), _(\"Force AAAA SOA.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// Force HTTPS SOA\n\t\to = s.taboption(\"advanced\", form.Flag, \"force_https_soa\", _(\"Force HTTPS SOA\"), _(\"Force HTTPS SOA.\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// ipset name;\n\t\to = s.taboption(\"advanced\", form.Value, \"ipset_name\", _(\"IPset Name\"), _(\"IPset name.\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar ipset = value.split(\",\")\n\t\t\tfor (var i = 0; i < ipset.length; i++) {\n\t\t\t\tif (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"ipset name format error, format: [#[4|6]:]ipsetname\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// Ipset no speed.\n\t\to = s.taboption(\"advanced\", form.Value, \"ipset_no_speed\", _(\"No Speed IPset Name\"),\n\t\t\t_(\"Ipset name, Add domain result to ipset when speed check fails.\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar ipset = value.split(\",\")\n\t\t\tfor (var i = 0; i < ipset.length; i++) {\n\t\t\t\tif (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"ipset name format error, format: [#[4|6]:]ipsetname\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\t\t\n\t\t// NFTset name;\n\t\to = s.taboption(\"advanced\", form.Value, \"nftset_name\", _(\"NFTset Name\"), _(\"NFTset name, format: [#[4|6]:[family#table#set]]\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar nftset = value.split(\",\")\n\t\t\tfor (var i = 0; i < nftset.length; i++) {\n\t\t\t\tif (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// NFTset no speed.\n\t\to = s.taboption(\"advanced\", form.Value, \"nftset_no_speed\", _(\"No Speed NFTset Name\"),\n\t\t\t_(\"Nftset name, Add domain result to nftset when speed check fails, format: [#[4|6]:[family#table#set]]\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar nftset = value.split(\",\")\n\t\t\tfor (var i = 0; i < nftset.length; i++) {\n\t\t\t\tif (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// rr-ttl;\n\t\to = s.taboption(\"advanced\", form.Value, \"rr_ttl\", _(\"Domain TTL\"), _(\"TTL for all domain result.\"));\n\t\to.rempty = true;\n\n\t\t// rr-ttl-min;\n\t\to = s.taboption(\"advanced\", form.Value, \"rr_ttl_min\", _(\"Domain TTL Min\"),\n\t\t\t_(\"Minimum TTL for all domain result.\"));\n\t\to.rempty = true;\n\t\to.placeholder = \"600\";\n\t\to.default = 600;\n\t\to.optional = true;\n\n\t\t// rr-ttl-max;\n\t\to = s.taboption(\"advanced\", form.Value, \"rr_ttl_max\", _(\"Domain TTL Max\"),\n\t\t\t_(\"Maximum TTL for all domain result.\"));\n\t\to.rempty = true;\n\n\t\t// rr-ttl-reply-max;\n\t\to = s.taboption(\"advanced\", form.Value, \"rr_ttl_reply_max\", _(\"Reply Domain TTL Max\"),\n\t\t\t_(\"Reply maximum TTL for all domain result.\"));\n\t\to.rempty = true;\n\n\t\t// other args\n\t\to = s.taboption(\"advanced\", form.Value, \"server_flags\", _(\"Additional Server Args\"),\n\t\t\t_(\"Additional server args, refer to the help description of the bind option.\"))\n\t\to.default = \"\"\n\t\to.rempty = true\n\n\t\t// include config\n\t\tdownload_files = uci.sections('smartdns', 'download-file');\n\t\to = s.taboption(\"advanced\", form.DynamicList, \"conf_files\", _(\"Include Config Files<br>/etc/smartdns/conf.d\"),\n\t\t\t_(\"Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page.\"));\n\t\tfor (var i = 0; i < download_files.length; i++) {\n\t\t\tif (download_files[i].type == undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (download_files[i].type != 'config') {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\to.value(download_files[i].name);\n\t\t}\n\n\t\to = s.taboption(\"advanced\", form.DynamicList, \"hosts_files\", _(\"Hosts File\"), _(\"Include hosts file.\"));\n\t\to.rmempty = true;\n\t\tfor (var i = 0; i < download_files.length; i++) {\n\t\t\tif (download_files[i].type == undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (download_files[i].type != 'other') {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\to.value(download_files[i].name);\n\t\t}\n\t\n\t\t///////////////////////////////////////\n\t\t// second dns server;\n\t\t///////////////////////////////////////\n\t\t// Enable;\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_enabled\", _(\"Enable\"),\n\t\t\t_(\"Enable or disable second DNS server.\"));\n\t\to.default = o.disabled;\n\t\to.rempty = true;\n\n\t\t// Port;\n\t\to = s.taboption(\"seconddns\", form.Value, \"seconddns_port\", _(\"Local Port\"), _(\"Smartdns local server port\"));\n\t\to.placeholder = 6553;\n\t\to.default = 6553;\n\t\to.datatype = \"port\";\n\t\to.rempty = false;\n\n\t\t// Enable TCP server;\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_tcp_server\", _(\"TCP Server\"), _(\"Enable TCP DNS Server\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// dns server group;\n\t\to = s.taboption(\"seconddns\", form.Value, \"seconddns_server_group\", _(\"Server Group\"),\n\t\t\t_(\"Query DNS through specific dns server group, such as office, home.\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.datatype = \"hostname\";\n\t\to.rempty = true;\n\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_no_speed_check\", _(\"Skip Speed Check\"),\n\t\t\t_(\"Do not check speed.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// skip address rules;\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_no_rule_addr\", _(\"Skip Address Rules\"),\n\t\t\t_(\"Skip address rules.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// skip name server rules;\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_no_rule_nameserver\", _(\"Skip Nameserver Rule\"),\n\t\t\t_(\"Skip nameserver rules.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// skip ipset rules;\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_no_rule_ipset\", _(\"Skip Ipset Rule\"),\n\t\t\t_(\"Skip ipset rules.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// skip soa address rule;\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_no_rule_soa\", _(\"Skip SOA Address Rule\"),\n\t\t\t_(\"Skip SOA address rules.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_no_dualstack_selection\", _(\"Skip Dualstack Selection\"),\n\t\t\t_(\"Skip Dualstack Selection.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// skip cache;\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_no_cache\", _(\"Skip Cache\"), _(\"Skip Cache.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// Force AAAA SOA\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_force_aaaa_soa\", _(\"Force AAAA SOA\"), _(\"Force AAAA SOA.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\t\t\n\t\t// Force HTTPS SOA\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_force_https_soa\", _(\"Force HTTPS SOA\"), _(\"Force HTTPS SOA.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"seconddns\", form.Flag, \"seconddns_no_ip_alias\", _(\"Skip IP Alias\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"seconddns\", form.Value, \"seconddns_ipset_name\", _(\"IPset Name\"), _(\"IPset name.\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar ipset = value.split(\",\")\n\t\t\tfor (var i = 0; i < ipset.length; i++) {\n\t\t\t\tif (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"ipset name format error, format: [#[4|6]:]ipsetname\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\to = s.taboption(\"seconddns\", form.Value, \"seconddns_nftset_name\", _(\"NFTset Name\"), _(\"NFTset name, format: [#[4|6]:[family#table#set]]\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar nftset = value.split(\",\")\n\t\t\tfor (var i = 0; i < nftset.length; i++) {\n\t\t\t\tif (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// other args\n\t\to = s.taboption(\"seconddns\", form.Value, \"seconddns_server_flags\", _(\"Additional Server Args\"),\n\t\t\t_(\"Additional server args, refer to the help description of the bind option.\"))\n\t\to.default = \"\"\n\t\to.rempty = true\n\n\t\t///////////////////////////////////////\n\t\t// DNS64 Settings\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"dns64\", form.Value, \"dns64\", _(\"DNS64\"));\n\t\to.placeholder = \"64:ff9b::/96\";\n\t\to.datatype = \"ip6addr\";\n\t\to.rempty = true;\n\n\t\t///////////////////////////////////////\n\t\t// download Files Settings\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"files\", form.Flag, \"enable_auto_update\", _(\"Enable Auto Update\"), _(\"Enable daily (weekly) auto update.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\t\to.rempty = true;\n\n\t\to = s.taboption(\"files\", form.ListValue, \"auto_update_week_time\", _(\"Update Time (Every Week)\"));\n\t\to.value('*', _('Every Day'));\n\t\to.value('1', _('Every Monday'));\n\t\to.value('2', _('Every Tuesday'));\n\t\to.value('3', _('Every Wednesday'));\n\t\to.value('4', _('Every Thursday'));\n\t\to.value('5', _('Every Friday'));\n\t\to.value('6', _('Every Saturday'));\n\t\to.value('0', _('Every Sunday'));\n\t\to.default = \"*\";\n\t\to.depends('enable_auto_update', '1');\n\n\t\to = s.taboption('files', form.ListValue, 'auto_update_day_time', _(\"Update time (every day)\"));\n\t\tfor (var i = 0; i < 24; i++)\n\t\t\to.value(i, i + ':00');\n\t\to.default = '5';\n\t\to.depends('enable_auto_update', '1');\n\n\t\to = s.taboption(\"files\", form.FileUpload, \"upload_conf_file\", _(\"Upload Config File\"),\n\t\t\t_(\"Upload smartdns config file to /etc/smartdns/conf.d\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.root_directory = \"/etc/smartdns/conf.d\"\n\n\t\to = s.taboption(\"files\", form.FileUpload, \"upload_list_file\", _(\"Upload Domain List File\"),\n\t\t\t_(\"Upload domain list file to /etc/smartdns/domain-set\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.root_directory = \"/etc/smartdns/domain-set\"\n\n\t\to = s.taboption(\"files\", form.FileUpload, \"upload_other_file\", _(\"Upload File\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.root_directory = \"/etc/smartdns/download\"\n\n\t\to = s.taboption('files', form.DummyValue, \"_update\", _(\"Update Files\"));\n\t\to.renderWidget = function () {\n\t\t\treturn E('button', {\n\t\t\t\t'class': 'btn cbi-button cbi-button-apply',\n\t\t\t\t'id': 'btn_update',\n\t\t\t\t'click': ui.createHandlerFn(this, function () {\n\t\t\t\t\treturn fs.exec('/etc/init.d/smartdns', ['updatefiles'])\n\t\t\t\t\t\t.catch(function (e) { ui.addNotification(null, E('p', e.message), 'error') });\n\t\t\t\t})\n\t\t\t}, [_(\"Update\")]);\n\t\t}\n\n\t\to = s.taboption('files', form.SectionValue, '__files__', form.GridSection, 'download-file', _('Download Files'),\n\t\t\t_('List of files to download.'));\n\n\t\tss = o.subsection;\n\n\t\tss.addremove = true;\n\t\tss.anonymous = true;\n\t\tss.sortable = true;\n\n\t\tso = ss.option(form.Value, 'name', _('File Name'), _('File Name'));\n\t\tso.rmempty = true;\n\t\tso.datatype = 'file';\n\n\t\tso = ss.option(form.Value, 'url', _('URL'), _('URL'));\n\t\tso.rmempty = true;\n\t\tso.datatype = 'string';\n\t\tso.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (!value.match(/^(http|https|ftp|sftp):\\/\\//)) {\n\t\t\t\treturn _(\"URL format error, format: http:// or https://\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tso = ss.option(form.ListValue, \"type\", _(\"type\"), _(\"File Type\"));\n\t\tso.value(\"list\", _(\"domain list (/etc/smartdns/domain-set)\"));\n\t\tso.value(\"config\", _(\"smartdns config (/etc/smartdns/conf.d)\"));\n\t\tso.value(\"ip-set\", _(\"ip-set file (/etc/smartdns/ip-set)\"));\n\t\tso.value(\"other\", _(\"other file (/etc/smartdns/download)\"));\n\t\tso.default = \"list\";\n\t\tso.rempty = false;\n\n\t\tso = ss.option(form.Value, 'desc', _('Description'), _('Description'));\n\t\tso.rmempty = true;\n\t\tso.datatype = 'string';\n\n\t\t///////////////////////////////////////\n\t\t// Proxy server settings;\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"proxy\", form.Value, \"proxy_server\", _(\"Proxy Server\"), _(\"Proxy Server URL, format: [socks5|http]://user:pass@ip:port.\"));\n\t\to.datatype = 'string';\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (!value.match(/^(socks5|http):\\/\\//)) {\n\t\t\t\treturn _(\"Proxy server URL format error, format: [socks5|http]://user:pass@ip:port.\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t///////////////////////////////////////\n\t\t// custom settings;\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"custom\", form.TextValue, \"custom_conf\",\n\t\t\t\"\", _(\"smartdns custom settings\"));\n\t\to.rows = 20;\n\t\to.cfgvalue = function (section_id) {\n\t\t\treturn fs.trimmed('/etc/smartdns/custom.conf');\n\t\t};\n\t\to.write = function (section_id, formvalue) {\n\t\t\treturn this.cfgvalue(section_id).then(function (value) {\n\t\t\t\tif (value == formvalue) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treturn fs.write('/etc/smartdns/custom.conf', formvalue.trim().replace(/\\r\\n/g, '\\n') + '\\n');\n\t\t\t});\n\t\t};\n\n\t\to = s.taboption(\"custom\", form.Flag, \"coredump\", _(\"Generate Coredump\"),\n\t\t\t_(\"Generate Coredump file when smartdns crash, coredump file is located at /tmp/smartdns.xxx.core.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"custom\", form.ListValue, \"log_level\", _(\"Log Level\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.value(\"\", _(\"default\"));\n\t\to.value(\"debug\");\n\t\to.value(\"info\");\n\t\to.value(\"notice\");\n\t\to.value(\"warn\");\n\t\to.value(\"error\");\n\t\to.value(\"fatal\");\n\t\to.value(\"off\");\n\n\t\to = s.taboption(\"custom\", form.ListValue, \"log_output_mode\", _(\"Log Output Mode\"));\n\t\to.rmempty = true;\n\t\to.placeholder = _(\"file\");\n\t\to.value(\"file\", _(\"file\"));\n\t\to.value(\"syslog\", _(\"syslog\"));\n\t\n\t\to = s.taboption(\"custom\", form.Value, \"log_size\", _(\"Log Size\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.depends(\"log_output_mode\", \"file\");\n\n\t\to = s.taboption(\"custom\", form.Value, \"log_num\", _(\"Log Number\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.depends(\"log_output_mode\", \"file\");\n\n\t\to = s.taboption(\"custom\", form.Value, \"log_file\", _(\"Log File\"))\n\t\to.rmempty = true\n\t\to.placeholder = \"/var/log/smartdns/smartdns.log\"\n\t\to.depends(\"log_output_mode\", \"file\");\n\n\t\to = s.taboption(\"custom\", form.Flag, \"enable_audit_log\", _(\"Enable Audit Log\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\t\to.rempty = true;\n\n\t\to = s.taboption(\"custom\", form.ListValue, \"audit_log_output_mode\", _(\"Audit Log Output Mode\"));\n\t\to.rmempty = true;\n\t\to.placeholder = _(\"file\");\n\t\to.value(\"file\", _(\"file\"));\n\t\to.value(\"syslog\", _(\"syslog\"));\n\t\to.depends(\"enable_audit_log\", \"1\");\n\n\t\to = s.taboption(\"custom\", form.Value, \"audit_log_size\", _(\"Audit Log Size\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.depends({\"enable_audit_log\":\"1\", \"audit_log_output_mode\":\"file\"});\n\n\t\to = s.taboption(\"custom\", form.Value, \"audit_log_num\", _(\"Audit Log Number\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.depends({\"enable_audit_log\":\"1\", \"audit_log_output_mode\":\"file\"});\n\n\t\to = s.taboption(\"custom\", form.Value, \"audit_log_file\", _(\"Audit Log File\"))\n\t\to.rmempty = true\n\t\to.placeholder = \"/var/log/smartdns/smartdns-audit.log\"\n\t\to.depends({\"enable_audit_log\":\"1\", \"audit_log_output_mode\":\"file\"});\n\n\t\t////////////////\n\t\t// Upstream servers;\n\t\t////////////////\n\t\ts = m.section(form.GridSection, \"server\", _(\"Upstream Servers\"),\n\t\t\t_(\"Upstream Servers, support UDP, TCP protocol. Please configure multiple DNS servers, \"\n\t\t\t\t+ \"including multiple foreign DNS servers.\"));\n\t\ts.anonymous = true;\n\t\ts.addremove = true;\n\t\ts.sortable = true;\n\n\t\ts.tab('general', _('General Settings'));\n\t\ts.tab('advanced', _('Advanced Settings'));\n\n\t\t// enable flag;\n\t\to = s.taboption(\"general\", form.Flag, \"enabled\", _(\"Enable\"), _(\"Enable\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\t\to.editable = true;\n\n\t\t// name;\n\t\to = s.taboption(\"general\", form.Value, \"name\", _(\"DNS Server Name\"), _(\"DNS Server Name\"));\n\n\t\t// IP address;\n\t\to = s.taboption(\"general\", form.Value, \"ip\", _(\"ip\"), _(\"DNS Server ip\"));\n\t\to.datatype = \"or(ipaddr, string)\";\n\t\to.rmempty = false;\n\n\t\t// port;\n\t\to = s.taboption(\"general\", form.Value, \"port\", _(\"port\"), _(\"DNS Server port\"));\n\t\to.placeholder = \"default\";\n\t\to.datatype = \"port\";\n\t\to.rempty = true;\n\t\to.depends(\"type\", \"udp\");\n\t\to.depends(\"type\", \"tcp\");\n\t\to.depends(\"type\", \"tls\");\n\n\t\t// type;\n\t\to = s.taboption(\"general\", form.ListValue, \"type\", _(\"type\"), _(\"DNS Server type\"));\n\t\to.placeholder = \"udp\";\n\t\to.value(\"udp\", _(\"udp\"));\n\t\to.value(\"tcp\", _(\"tcp\"));\n\t\to.value(\"tls\", _(\"tls\"));\n\t\to.value(\"https\", _(\"https\"));\n\t\to.default = \"udp\";\n\t\to.rempty = false;\n\n\t\t// server group\n\t\to = s.taboption(\"general\", form.Value, \"server_group\", _(\"Server Group\"), _(\"DNS Server group\"))\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.datatype = \"hostname\";\n\t\to.rempty = true;\n\t\tservers = uci.sections('smartdns', 'server');\n\t\tvar groupnames = new Set();\n\t\tfor (var i = 0; i < servers.length; i++) {\n\t\t\tif (servers[i].server_group == undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tgroupnames.add(servers[i].server_group);\n\t\t}\n\n\t\tfor (const groupname of groupnames) {\n\t\t\to.value(groupname);\n\t\t}\n\n\t\t// Advanced Options\n\t\to = s.taboption(\"advanced\", form.Flag, \"exclude_default_group\", _(\"Exclude Default Group\"), _(\"Exclude DNS Server from default group.\"))\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\t\to.editable = true;\n\t\to.modalonly = true;\n\n\t\t// blacklist_ip\n\t\to = s.taboption(\"advanced\", form.Flag, \"blacklist_ip\", _(\"IP Blacklist Filtering\"),\n\t\t\t_(\"Filtering IP with blacklist\"))\n\t\to.rmempty = true\n\t\to.default = o.disabled\n\t\to.modalonly = true;\n\n\t\t// TLS host verify\n\t\to = s.taboption(\"advanced\", form.Value, \"tls_host_verify\", _(\"TLS Hostname Verify\"),\n\t\t\t_(\"Set TLS hostname to verify.\"))\n\t\to.default = \"\"\n\t\to.datatype = \"string\"\n\t\to.rempty = true\n\t\to.modalonly = true;\n\t\to.depends(\"type\", \"tls\")\n\t\to.depends(\"type\", \"https\")\n\n\t\t// certificate verify\n\t\to = s.taboption(\"advanced\", form.Flag, \"no_check_certificate\", _(\"No check certificate\"),\n\t\t\t_(\"Do not check certificate.\"))\n\t\to.rmempty = true\n\t\to.default = o.disabled\n\t\to.modalonly = true;\n\t\to.depends(\"type\", \"tls\")\n\t\to.depends(\"type\", \"https\")\n\n\t\t// SNI host name\n\t\to = s.taboption(\"advanced\", form.Value, \"host_name\", _(\"TLS SNI name\"),\n\t\t\t_(\"Sets the server name indication for query. '-' for disable SNI name.\"))\n\t\to.default = \"\"\n\t\to.datatype = \"hostname\"\n\t\to.rempty = true\n\t\to.modalonly = true;\n\t\to.depends(\"type\", \"tls\")\n\t\to.depends(\"type\", \"https\")\n\n\t\t// http host\n\t\to = s.taboption(\"advanced\", form.Value, \"http_host\", _(\"HTTP Host\"),\n\t\t\t_(\"Set the HTTP host used for the query. Use this parameter when the host of the URL address is an IP address.\"))\n\t\to.default = \"\"\n\t\to.datatype = \"hostname\"\n\t\to.rempty = true\n\t\to.modalonly = true;\n\t\to.depends(\"type\", \"https\")\n\n\t\t// SPKI pin\n\t\to = s.taboption(\"advanced\", form.Value, \"spki_pin\", _(\"TLS SPKI Pinning\"),\n\t\t\t_(\"Used to verify the validity of the TLS server, The value is Base64 encoded SPKI fingerprint, \"\n\t\t\t\t+ \"leaving blank to indicate that the validity of TLS is not verified.\"))\n\t\to.default = \"\"\n\t\to.datatype = \"string\"\n\t\to.rempty = true\n\t\to.modalonly = true;\n\t\to.depends(\"type\", \"tls\")\n\t\to.depends(\"type\", \"https\")\n\n\t\t// mark\n\t\to = s.taboption(\"advanced\", form.Value, \"set_mark\", _(\"Marking Packets\"),\n\t\t\t_(\"Set mark on packets.\"))\n\t\to.default = \"\"\n\t\to.rempty = true\n\t\to.datatype = \"uinteger\"\n\t\to.modalonly = true;\n\n\t\t// use proxy\n\t\to = s.taboption(\"advanced\", form.Flag, \"use_proxy\", _(\"Use Proxy\"),\n\t\t\t_(\"Use proxy to connect to upstream DNS server.\"))\n\t\to.default = o.disabled\n\t\to.modalonly = true;\n\t\to.optional = true;\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tvar flag = this.formvalue(section_id);\n\t\t\tif (flag == \"0\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar proxy_server = uci.sections(\"smartdns\", \"smartdns\")[0].proxy_server;\n\t\t\tvar server_type = this.section.formvalue(section_id, \"type\");\n\t\t\tif (proxy_server == \"\" || proxy_server == undefined) {\n\t\t\t\treturn _(\"Please set proxy server first.\");\n\t\t\t}\n\n\t\t\tif (server_type == \"udp\" && !proxy_server.match(/^(socks5):\\/\\//)) {\n\t\t\t\treturn _(\"Only socks5 proxy support udp server.\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// other args\n\t\to = s.taboption(\"advanced\", form.Value, \"addition_arg\", _(\"Additional Server Args\"),\n\t\t\t_(\"Additional Args for upstream dns servers\"))\n\t\to.default = \"\"\n\t\to.rempty = true\n\t\to.modalonly = true;\n\n\t\t////////////////\n\t\t// client rules;\n\t\t////////////////\n\t\ts = m.section(form.TypedSection, \"client-rule\", _(\"Client Rules\"), _(\"Client Rules Settings, can achieve parental control functionality.\"));\n\t\ts.anonymous = true;\n\t\ts.nodescriptions = true;\n\n\t\ts.tab(\"basic\", _('Basic Settings'));\n\t\ts.tab(\"advanced\", _('Advanced Settings'));\n\t\ts.tab(\"block\", _(\"DNS Block Setting\"));\n\n\t\to = s.taboption(\"basic\", form.Flag, \"enabled\", _(\"Enable\"));\n\t\to.rmempty = false;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"basic\", form.DynamicList, \"client_addr\", _(\"Client Address\"), \n\t\t_(\"If a client address is specified, only that client will apply this rule. You can enter an IP address, such as 1.2.3.4, or a MAC address, such as aa:bb:cc:dd:ee:ff.\"));\n\t\to.rempty = true\n\t\to.rmempty = true;\n\t\to.modalonly = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value.match(/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\/([0-9]|[1-2][0-9]|3[0-2]))?$/)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value.match(/^([a-fA-F0-9]*:){1,7}[a-fA-F0-9]*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value.match(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t\n\t\t\treturn _(\"Client address format error, please input ip adress or mac address.\");\n\t\t}\n\n\t\to = s.taboption(\"basic\", form.FileUpload, \"client_addr_file\", _(\"Client Address File\"),\n\t\t\t_(\"Upload client address file, same as Client Address function.\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.modalonly = true;\n\t\to.root_directory = \"/etc/smartdns/ip-set\"\n\t\t\n\t\to = s.taboption(\"basic\", form.Value, \"server_group\", _(\"Server Group\"), _(\"DNS Server group belongs to, such as office, home.\"))\n\t\to.rmempty = true\n\t\to.placeholder = \"default\"\n\t\to.datatype = \"hostname\"\n\t\to.rempty = true\n\t\tfor (const groupname of groupnames) {\n\t\t\to.value(groupname);\n\t\t}\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar val = uci.sections('smartdns', 'server');\n\t\t\tfor (var i = 0; i < val.length; i++) {\n\t\t\t\tif (value == val[i].server_group) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn _('Server Group %s not exists').format(value);\n\n\t\t}\n\n\t\t// Speed check mode;\n\t\to = s.taboption(\"advanced\", form.Value, \"speed_check_mode\", _(\"Speed Check Mode\"), _(\"Smartdns speed check mode.\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.value(\"\", _(\"default\"));\n\t\to.value(\"ping,tcp:80,tcp:443\");\n\t\to.value(\"ping,tcp:443,tcp:80\");\n\t\to.value(\"tcp:80,tcp:443,ping\");\n\t\to.value(\"tcp:443,tcp:80,ping\");\n\t\to.value(\"none\", _(\"None\"));\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value == \"none\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar check_mode = value.split(\",\")\n\t\t\tfor (var i = 0; i < check_mode.length; i++) {\n\t\t\t\tif (check_mode[i] == \"ping\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (check_mode[i].indexOf(\"tcp:\") == 0) {\n\t\t\t\t\tvar port = check_mode[i].split(\":\")[1];\n\t\t\t\t\tif (port == \"\") {\n\t\t\t\t\t\treturn _(\"TCP port is empty\");\n\t\t\t\t\t}\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn _(\"Speed check mode is invalid.\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// Support DualStack ip selection;\n\t\to = s.taboption(\"advanced\", form.Flag, \"dualstack_ip_selection\", _(\"Dual-stack IP Selection\"),\n\t\t\t_(\"Enable IP selection between IPV4 and IPV6\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// Force AAAA SOA\n\t\to = s.taboption(\"advanced\", form.Flag, \"force_aaaa_soa\", _(\"Force AAAA SOA\"), _(\"Force AAAA SOA.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// Force HTTPS SOA\n\t\to = s.taboption(\"advanced\", form.Flag, \"force_https_soa\", _(\"Force HTTPS SOA\"), _(\"Force HTTPS SOA.\"));\n\t\to.rmempty = false;\n\t\to.default = o.enabled;\n\n\t\t// ipset name;\n\t\to = s.taboption(\"advanced\", form.Value, \"ipset_name\", _(\"IPset Name\"), _(\"IPset name.\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar ipset = value.split(\",\")\n\t\t\tfor (var i = 0; i < ipset.length; i++) {\n\t\t\t\tif (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"ipset name format error, format: [#[4|6]:]ipsetname\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\t\t\n\t\t// NFTset name;\n\t\to = s.taboption(\"advanced\", form.Value, \"nftset_name\", _(\"NFTset Name\"), _(\"NFTset name, format: [#[4|6]:[family#table#set]]\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar nftset = value.split(\",\")\n\t\t\tfor (var i = 0; i < nftset.length; i++) {\n\t\t\t\tif (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// include config\n\t\tdownload_files = uci.sections('smartdns', 'download-file');\n\t\to = s.taboption(\"advanced\", form.DynamicList, \"conf_files\", _(\"Include Config Files<br>/etc/smartdns/conf.d\"),\n\t\t\t_(\"Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page.\"));\n\t\tfor (var i = 0; i < download_files.length; i++) {\n\t\t\tif (download_files[i].type == undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (download_files[i].type != 'config') {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\to.value(download_files[i].name);\n\t\t}\n\n\t\to = s.taboption(\"block\", form.FileUpload, \"block_domain_set_file\", _(\"Domain List File\"), _(\"Upload domain list file.\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.editable = true\n\t\to.root_directory = \"/etc/smartdns/domain-set\"\n\n\n\t\t////////////////\n\t\t// domain rules;\n\t\t////////////////\n\t\ts = m.section(form.TypedSection, \"domain-rule\", _(\"Domain Rules\"), _(\"Domain Rules Settings\"));\n\t\ts.anonymous = true;\n\t\ts.nodescriptions = true;\n\n\t\ts.tab(\"forwarding\", _('DNS Forwarding Setting'));\n\t\ts.tab(\"block\", _(\"DNS Block Setting\"));\n\t\ts.tab(\"domain-rule-list\", _(\"Domain Rule List\"), _(\"Set Specific domain rule list.\"));\n\t\ts.tab(\"domain-address\", _(\"Domain Address\"), _(\"Set Specific domain ip address.\"));\n\n\t\t///////////////////////////////////////\n\t\t// domain forwarding;\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"forwarding\", form.Value, \"server_group\", _(\"Server Group\"), _(\"DNS Server group belongs to, such as office, home.\"))\n\t\to.rmempty = true\n\t\to.placeholder = \"default\"\n\t\to.datatype = \"hostname\"\n\t\to.rempty = true\n\t\tfor (const groupname of groupnames) {\n\t\t\to.value(groupname);\n\t\t}\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar val = uci.sections('smartdns', 'server');\n\t\t\tfor (var i = 0; i < val.length; i++) {\n\t\t\t\tif (value == val[i].server_group) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn _('Server Group %s not exists').format(value);\n\n\t\t}\n\n\t\t// Speed check mode;\n\t\to = s.taboption(\"forwarding\", form.Value, \"speed_check_mode\", _(\"Speed Check Mode\"), _(\"Smartdns speed check mode.\"));\n\t\to.rmempty = true;\n\t\to.placeholder = \"default\";\n\t\to.value(\"\", _(\"default\"));\n\t\to.value(\"ping,tcp:80,tcp:443\");\n\t\to.value(\"ping,tcp:443,tcp:80\");\n\t\to.value(\"tcp:80,tcp:443,ping\");\n\t\to.value(\"tcp:443,tcp:80,ping\");\n\t\to.value(\"none\", _(\"None\"));\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value == \"none\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar check_mode = value.split(\",\")\n\t\t\tfor (var i = 0; i < check_mode.length; i++) {\n\t\t\t\tif (check_mode[i] == \"ping\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (check_mode[i].indexOf(\"tcp:\") == 0) {\n\t\t\t\t\tvar port = check_mode[i].split(\":\")[1];\n\t\t\t\t\tif (port == \"\") {\n\t\t\t\t\t\treturn _(\"TCP port is empty\");\n\t\t\t\t\t}\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn _(\"Speed check mode is invalid.\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// Support DualStack ip selection;\n\t\to = s.taboption(\"forwarding\", form.ListValue, \"dualstack_ip_selection\", _(\"Dual-stack IP Selection\"),\n\t\t\t_(\"Enable IP selection between IPV4 and IPV6\"));\n\t\to.rmempty = true;\n\t\to.default = \"default\";\n\t\to.modalonly = true;\n\t\to.value(\"\", _(\"default\"));\n\t\to.value(\"yes\", _(\"Yes\"));\n\t\to.value(\"no\", _(\"No\"));\n\n\t\to = s.taboption(\"forwarding\", form.Flag, \"force_aaaa_soa\", _(\"Force AAAA SOA\"), _(\"Force AAAA SOA.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"forwarding\", form.Value, \"ipset_name\", _(\"IPset Name\"), _(\"IPset name.\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar ipset = value.split(\",\")\n\t\t\tfor (var i = 0; i < ipset.length; i++) {\n\t\t\t\tif (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"ipset name format error, format: [#[4|6]:]ipsetname\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\to = s.taboption(\"forwarding\", form.Value, \"nftset_name\", _(\"NFTset Name\"), _(\"NFTset name, format: [#[4|6]:[family#table#set]]\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar nftset = value.split(\",\")\n\t\t\tfor (var i = 0; i < nftset.length; i++) {\n\t\t\t\tif (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// other args\n\t\to = s.taboption(\"forwarding\", form.Value, \"addition_flag\", _(\"Additional Rule Flag\"),\n\t\t\t_(\"Additional Flags for rules, read help on domain-rule for more information.\"))\n\t\to.default = \"\"\n\t\to.rempty = true\n\t\to.modalonly = true;\n\n\t\to = s.taboption(\"forwarding\", form.FileUpload, \"forwarding_domain_set_file\", _(\"Domain List File\"),\n\t\t\t_(\"Upload domain list file, or configure auto download from Download File Setting page.\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.editable = true\n\t\to.root_directory = \"/etc/smartdns/domain-set\"\n\n\t\to = s.taboption(\"forwarding\", form.TextValue, \"domain_forwarding_list\",\n\t\t\t_(\"Domain List\"), _(\"Configure forwarding domain name list.\"));\n\t\to.rows = 10;\n\t\to.cols = 64;\n\t\to.monospace = true;\n\t\to.cfgvalue = function (section_id) {\n\t\t\treturn fs.trimmed('/etc/smartdns/domain-forwarding.list').catch(function (e) {\n\t\t\t\treturn \"\";\n\t\t\t});\n\t\t};\n\t\to.write = function (section_id, formvalue) {\n\t\t\treturn this.cfgvalue(section_id).then(function (value) {\n\t\t\t\tif (value == formvalue) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treturn fs.write('/etc/smartdns/domain-forwarding.list', formvalue.trim().replace(/\\r\\n/g, '\\n') + '\\n');\n\t\t\t});\n\t\t};\n\n\t\t///////////////////////////////////////\n\t\t// domain block;\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"block\", form.FileUpload, \"block_domain_set_file\", _(\"Domain List File\"), _(\"Upload domain list file.\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.editable = true\n\t\to.root_directory = \"/etc/smartdns/domain-set\"\n\n\t\to = s.taboption(\"block\", form.TextValue, \"domain_block_list\",\n\t\t\t_(\"Domain List\"), _(\"Configure block domain list.\"));\n\t\to.rows = 10;\n\t\to.cols = 64;\n\t\to.cfgvalue = function (section_id) {\n\t\t\treturn fs.trimmed('/etc/smartdns/domain-block.list').catch(function (e) {\n\t\t\t\treturn \"\";\n\t\t\t});\n\t\t};\n\t\to.write = function (section_id, formvalue) {\n\t\t\treturn this.cfgvalue(section_id).then(function (value) {\n\t\t\t\tif (value == formvalue) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treturn fs.write('/etc/smartdns/domain-block.list', formvalue.trim().replace(/\\r\\n/g, '\\n') + '\\n');\n\t\t\t});\n\t\t};\n\n\t\t///////////////////////////////////////\n\t\t// domain rule list;\n\t\t///////////////////////////////////////\n\t\to = s.taboption('domain-rule-list', form.SectionValue, '__domain-rule-list__', form.GridSection, 'domain-rule-list', _('Domain Rule List'),\n\t\t\t_('Configure domain rule list.'));\n\n\t\tss = o.subsection;\n\n\t\tss.addremove = true;\n\t\tss.anonymous = true;\n\t\tss.sortable = true;\n\n\t\t// enable flag;\n\t\tso = ss.option(form.Flag, \"enabled\", _(\"Enable\"), _(\"Enable\"));\n\t\tso.rmempty = false;\n\t\tso.default = so.enabled;\n\t\tso.editable = true;\n\n\t\t// name;\n\t\tso = ss.option(form.Value, \"name\", _(\"Domain Rule Name\"), _(\"Domain Rule Name\"));\n\n\t\tso = ss.option(form.Value, \"server_group\", _(\"Server Group\"), _(\"DNS Server group belongs to, such as office, home.\"))\n\t\tso.rmempty = true\n\t\tso.placeholder = \"default\"\n\t\tso.datatype = \"hostname\"\n\t\tso.rempty = true\n\t\tfor (const groupname of groupnames) {\n\t\t\tso.value(groupname);\n\t\t}\n\t\tso.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar val = uci.sections('smartdns', 'server');\n\t\t\tfor (var i = 0; i < val.length; i++) {\n\t\t\t\tif (value == val[i].server_group) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn _('Server Group %s not exists').format(value);\n\n\t\t}\n\n\t\tso = ss.option(form.FileUpload, \"domain_list_file\", _(\"Domain List File\"),\n\t\t\t_(\"Upload domain list file, or configure auto download from Download File Setting page.\"));\n\t\tso.rmempty = true\n\t\tso.datatype = \"file\"\n\t\tso.rempty = true\n\t\tso.root_directory = \"/etc/smartdns/domain-set\"\n\n\t\tso = ss.option(form.ListValue, \"block_domain_type\", _(\"Block domain\"), _(\"Block domain.\"));\n\t\tso.rmempty = true;\n\t\tso.value(\"none\", _(\"None\"));\n\t\tso.value(\"all\", \"IPv4/IPv6\");\n\t\tso.value(\"ipv4\", \"IPv4\");\n\t\tso.value(\"ipv6\", \"IPv6\");\n\t\tso.modalonly = true;\n\n\t\t// Support DualStack ip selection;\n\t\tso = ss.option(form.ListValue, \"dualstack_ip_selection\", _(\"Dual-stack IP Selection\"),\n\t\t\t_(\"Enable IP selection between IPV4 and IPV6\"));\n\t\tso.rmempty = true;\n\t\tso.default = \"default\";\n\t\tso.modalonly = true;\n\t\tso.value(\"\", _(\"default\"));\n\t\tso.value(\"yes\", _(\"Yes\"));\n\t\tso.value(\"no\", _(\"No\"));\n\n\t\tso = ss.option(form.Value, \"speed_check_mode\", _(\"Speed Check Mode\"), _(\"Smartdns speed check mode.\"));\n\t\tso.rmempty = true;\n\t\tso.placeholder = \"default\";\n\t\tso.modalonly = true;\n\t\tso.value(\"\", _(\"default\"));\n\t\tso.value(\"ping,tcp:80,tcp:443\");\n\t\tso.value(\"ping,tcp:443,tcp:80\");\n\t\tso.value(\"tcp:80,tcp:443,ping\");\n\t\tso.value(\"tcp:443,tcp:80,ping\");\n\t\tso.value(\"none\", _(\"None\"));\n\t\tso.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value == \"none\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar check_mode = value.split(\",\")\n\t\t\tfor (var i = 0; i < check_mode.length; i++) {\n\t\t\t\tif (check_mode[i] == \"ping\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (check_mode[i].indexOf(\"tcp:\") == 0) {\n\t\t\t\t\tvar port = check_mode[i].split(\":\")[1];\n\t\t\t\t\tif (port == \"\") {\n\t\t\t\t\t\treturn _(\"TCP port is empty\");\n\t\t\t\t\t}\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn _(\"Speed check mode is invalid.\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tso = ss.option(form.Flag, \"force_aaaa_soa\", _(\"Force AAAA SOA\"), _(\"Force AAAA SOA.\"));\n\t\tso.rmempty = true;\n\t\tso.default = so.disabled;\n\t\tso.modalonly = true;\n\n\n\t\tso = ss.option(form.Value, \"ipset_name\", _(\"IPset Name\"), _(\"IPset name.\"));\n\t\tso.rmempty = true;\n\t\tso.datatype = \"hostname\";\n\t\tso.rempty = true;\n\t\tso.modalonly = true;\n\n\t\tso = ss.option(form.Value, \"nftset_name\", _(\"NFTset Name\"), _(\"NFTset name, format: [#[4|6]:[family#table#set]]\"));\n\t\tso.rmempty = true;\n\t\tso.datatype = \"string\";\n\t\tso.rempty = true;\n\t\tso.modalonly = true;\n\t\tso.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar nftset = value.split(\",\")\n\t\t\tfor (var i = 0; i < nftset.length; i++) {\n\t\t\t\tif (!nftset[i].match(/#[4|6]:[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// other args\n\t\tso = ss.option(form.Value, \"addition_flag\", _(\"Additional Rule Flag\"),\n\t\t\t_(\"Additional Flags for rules, read help on domain-rule for more information.\"))\n\t\tso.default = \"\"\n\t\tso.rempty = true\n\t\tso.modalonly = true;\n\n\t\t///////////////////////////////////////\n\t\t// domain address\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"domain-address\", form.TextValue, \"address_conf\",\n\t\t\t\"\",\n\t\t\t_(\"Specify an IP address to return for any host in the given domains, Queries in the domains are never \"\n\t\t\t\t+ \"forwarded and always replied to with the specified IP address which may be IPv4 or IPv6.\"));\n\t\to.rows = 20;\n\t\to.cfgvalue = function (section_id) {\n\t\t\treturn fs.trimmed('/etc/smartdns/address.conf');\n\t\t};\n\t\to.write = function (section_id, formvalue) {\n\t\t\treturn this.cfgvalue(section_id).then(function (value) {\n\t\t\t\tif (value == formvalue) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treturn fs.write('/etc/smartdns/address.conf', formvalue.trim().replace(/\\r\\n/g, '\\n') + '\\n');\n\t\t\t});\n\t\t};\n\n\n\t\t////////////////\n\t\t// ip rules;\n\t\t////////////////\n\t\ts = m.section(form.TypedSection, \"ip-rule\", _(\"IP Rules\"), _(\"IP Rules Settings\"));\n\t\ts.anonymous = true;\n\t\ts.nodescriptions = true;\n\n\t\ts.tab(\"ip-rule-list\", _(\"IP Rule List\"), _(\"Set Specific ip rule list.\"));\n\t\ts.tab(\"blackip-list\", _(\"IP Blacklist\"), _(\"Set Specific ip blacklist.\"));\n\n\t\t///////////////////////////////////////\n\t\t// ip rule list;\n\t\t///////////////////////////////////////\n\t\to = s.taboption('ip-rule-list', form.SectionValue, '__ip-rule-list__', form.GridSection, 'ip-rule-list', _('IP Rule List'),\n\t\t\t_('Configure ip rule list.'));\n\n\t\tss = o.subsection;\n\n\t\tss.addremove = true;\n\t\tss.anonymous = true;\n\t\tss.sortable = true;\n\n\t\t// enable flag;\n\t\tso = ss.option(form.Flag, \"enabled\", _(\"Enable\"), _(\"Enable\"));\n\t\tso.rmempty = false;\n\t\tso.default = so.enabled;\n\t\tso.editable = true;\n\n\t\t// name;\n\t\tso = ss.option(form.Value, \"name\", _(\"IP Rule Name\"), _(\"IP Rule Name\"));\n\t\tso.rmempty = true;\n\t\tso.datatype = \"string\";\n\n\t\tso = ss.option(form.FileUpload, \"ip_set_file\", _(\"IP Set File\"), _(\"Upload IP set file.\"));\n\t\tso.rmempty = true\n\t\tso.datatype = \"file\"\n\t\tso.modalonly = true;\n\t\tso.root_directory = \"/etc/smartdns/ip-set\"\n\n\t\tso = ss.option(form.DynamicList, \"ip_addr\", _(\"IP Addresses\"), _(\"IP addresses, CIDR format.\"));\n\t\tso.rmempty = true;\n\t\tso.datatype = \"ipaddr\"\n\t\tso.modalonly = true;\n\n\t\tso = ss.option(form.Flag, \"whitelist_ip\", _(\"Whitelist IP\"), _(\"Whitelist IP Rule, Accept IP addresses within the range.\"));\n\t\tso.rmempty = true;\n\t\tso.default = so.disabled;\n\t\tso.modalonly = true;\n\n\t\tso = ss.option(form.Flag, \"blacklist_ip\", _(\"Blacklist IP\"), _(\"Blacklist IP Rule, Decline IP addresses within the range.\"));\n\t\tso.rmempty = true;\n\t\tso.default = so.disabled;\n\t\tso.modalonly = true;\n\n\t\tso = ss.option(form.Flag, \"ignore_ip\", _(\"Ignore IP\"), _(\"Do not use these IP addresses.\"));\n\t\tso.rmempty = true;\n\t\tso.default = so.disabled;\n\t\tso.modalonly = true;\n\n\t\tso = ss.option(form.Flag, \"bogus_nxdomain\", _(\"Bogus nxdomain\"), _(\"Return SOA when the requested result contains a specified IP address.\"));\n\t\tso.rmempty = true;\n\t\tso.default = so.disabled;\n\t\tso.modalonly = true;\n\n\t\tso = ss.option(form.DynamicList, \"ip_alias\", _(\"IP alias\"), _(\"IP Address Mapping, Can be used for CDN acceleration with Anycast IP, such as Cloudflare's CDN.\"));\n\t\tso.rmempty = true;\n\t\tso.datatype = 'ipaddr(\"nomask\")';\n\t\tso.modalonly = true;\n\n\t\t// other args\n\t\tso = ss.option(form.Value, \"addition_flag\", _(\"Additional Rule Flag\"),\n\t\t\t_(\"Additional Flags for rules, read help on ip-rule for more information.\"))\n\t\tso.default = \"\"\n\t\tso.rempty = true\n\t\tso.modalonly = true;\n\t\t///////////////////////////////////////\n\t\t// IP Blacklist;\n\t\t///////////////////////////////////////\n\t\t// blacklist;\n\t\to = s.taboption(\"blackip-list\", form.TextValue, \"blackip_ip_conf\",\n\t\t\t\"\", _(\"Configure IP blacklists that will be filtered from the results of specific DNS server.\"));\n\t\to.rows = 20;\n\t\to.cfgvalue = function (section_id) {\n\t\t\treturn fs.trimmed('/etc/smartdns/blacklist-ip.conf');\n\t\t};\n\t\to.write = function (section_id, formvalue) {\n\t\t\treturn this.cfgvalue(section_id).then(function (value) {\n\t\t\t\tif (value == formvalue) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treturn fs.write('/etc/smartdns/blacklist-ip.conf', formvalue.trim().replace(/\\r\\n/g, '\\n') + '\\n');\n\t\t\t});\n\t\t};\n\n\t\t////////////////\n\t\t// Support\n\t\t////////////////\n\t\ts = m.section(form.TypedSection, \"smartdns\", _(\"Technical Support\"),\n\t\t\t_(\"If you like this software, please buy me a cup of coffee.\"));\n\t\ts.anonymous = true;\n\n\t\to = s.option(form.Button, \"web\");\n\t\to.title = _(\"SmartDNS official website\");\n\t\to.inputtitle = _(\"open website\");\n\t\to.inputstyle = \"apply\";\n\t\to.onclick = function () {\n\t\t\twindow.open(\"https://pymumu.github.io/smartdns\", '_blank');\n\t\t};\n\n\t\to = s.option(form.Button, \"report\");\n\t\to.title = _(\"Report bugs\");\n\t\to.inputtitle = _(\"Report bugs\");\n\t\to.inputstyle = \"apply\";\n\t\to.onclick = function () {\n\t\t\twindow.open(\"https://github.com/pymumu/smartdns/issues\", '_blank');\n\t\t};\n\n\t\to = s.option(form.Button, \"Donate\");\n\t\to.title = _(\"Donate to smartdns\");\n\t\to.inputtitle = _(\"Donate\");\n\t\to.inputstyle = \"apply\";\n\t\to.onclick = function () {\n\t\t\twindow.open(\"https://pymumu.github.io/smartdns/#donate\", '_blank');\n\t\t};\n\n\t\to = s.option(form.DummyValue, \"_restart\", _(\"Restart Service\"));\n\t\to.renderWidget = function () {\n\t\t\treturn E('button', {\n\t\t\t\t'class': 'btn cbi-button cbi-button-apply',\n\t\t\t\t'id': 'btn_restart',\n\t\t\t\t'click': ui.createHandlerFn(this, function () {\n\t\t\t\t\treturn fs.exec('/etc/init.d/smartdns', ['restart'])\n\t\t\t\t\t\t.catch(function (e) { ui.addNotification(null, E('p', e.message), 'error') });\n\t\t\t\t})\n\t\t\t}, [_(\"Restart\")]);\n\t\t}\n\t\treturn m.render();\n\t}\n});\n"
  },
  {
    "path": "package/luci/make.sh",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\nCURR_DIR=$(cd $(dirname $0);pwd)\n\nVER=\"`date +\"1.%Y.%m.%d-%H%M\"`\"\nSMARTDNS_DIR=$CURR_DIR/../../\nPO2LMO=\n\nshowhelp()\n{\n\techo \"Usage: make [OPTION]\"\n\techo \"Options:\"\n\techo \" -o               output directory.\"\n\techo \" --arch           archtecture.\"\n\techo \" --ver            version.\"\n\techo \" -h               show this message.\"\n}\n\nbuild_tool()\n{\n\tmake -C $ROOT/tool/po2lmo -j \n\tPO2LMO=\"$ROOT/tool/po2lmo/src/po2lmo\"\n\n}\n\nclean_tool()\n{\n\tmake -C $ROOT/tool/po2lmo clean\n}\n\nbuild()\n{\n\n\tROOT=/tmp/luci-app-smartdns\n\trm -fr $ROOT\n\n\tmkdir -p $ROOT\n\tcp $CURR_DIR/* $ROOT/ -af\n\tcp $CURR_DIR/../tool $ROOT/ -af\n\tcd $ROOT/\n\tbuild_tool\n\t\n\tmkdir $ROOT/root/usr/lib/lua/luci -p\n\tmkdir $ROOT/root/usr/share/rpcd/acl.d/ -p\n\tcp $ROOT/files/luci/i18n $ROOT/root/usr/lib/lua/luci/ -avf\n\n\t#Generate Language\n\t$PO2LMO $ROOT/files/luci/i18n/smartdns.zh-cn.po $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.lmo\n\trm $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.po\n\n\tcp $ROOT/files/root/* $ROOT/root/ -avf\n\tINST_SIZE=\"`du -sb $ROOT/root/ | awk '{print $1}'`\"\n\t\n\tsed -i \"s/^Architecture.*/Architecture: all/g\" $ROOT/control/control\n\tsed -i \"s/Version:.*/Version: $VER/\" $ROOT/control/control\n\n\tif [ ! -z \"$INST_SIZE\" ]; then\n\t\techo \"Installed-Size: $INST_SIZE\" >> $ROOT/control/control\n\tfi\n\n\tcd $ROOT/control\n\tchmod +x *\n\ttar zcf ../control.tar.gz ./\n\tcd $ROOT\n\n\ttar zcf $ROOT/data.tar.gz -C root .\n\ttar zcf $OUTPUTDIR/luci-app-smartdns.$VER.$FILEARCH.ipk ./control.tar.gz ./data.tar.gz ./debian-binary\n\n\twhich apk >/dev/null 2>&1\n\tif [ $? -eq 0 ]; then\n\t\tAPK_VER=\"`echo $VER | sed 's/[-]/-r/'`\"\n\t\tARCH=\"`echo $ARCH | sed 's/all/noarch/g'`\"\n\t\tapk mkpkg \\\n\t\t\t--info \"name:luci-app-smartdns\" \\\n\t\t\t--info \"version:$APK_VER\" \\\n\t\t\t--info \"description:smartdns luci\" \\\n\t\t\t--info \"arch:$ARCH\" \\\n\t\t\t--info \"license:GPL\" \\\n\t\t\t--info \"origin: https://github.com/pymumu/smartdns.git\" \\\n\t\t\t--info \"depends:libc smartdns\" \\\n\t\t\t--script \"post-install:$ROOT/control/postinst\" \\\n\t\t\t--script \"pre-deinstall:$ROOT/control/prerm\" \\\n\t\t\t--files \"$ROOT/root/\" \\\n\t\t\t--output \"$OUTPUTDIR/luci-app-smartdns.$VER.$FILEARCH.apk\"\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"build apk package failed.\"\n\t\t\trm -fr $ROOT/\n\t\t\treturn 1\n\t\tfi\n\telse\n\t\techo \"== warning: apk tool not found, skip build apk package. ==\"\n\tfi\n\n\trm -fr $ROOT/\n}\n\nmain()\n{\n\tOPTS=`getopt -o o:h --long arch:,ver:,filearch: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--arch)\n\t\t\tARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--filearch)\n\t\t\tFILEARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--ver)\n\t\t\tVER=\"$2\"\n\t\t\tshift 2;;\n\t\t-o )\n\t\t\tOUTPUTDIR=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n\t\tesac\n\tdone\n\n\tif [ -z \"$ARCH\" ]; then\n\t\techo \"please input arch.\"\n\t\treturn 1;\n\tfi\n\n\tif [ -z \"$FILEARCH\" ]; then\n\t\tFILEARCH=$ARCH\n\tfi\n\n\tif [ -z \"$OUTPUTDIR\" ]; then\n\t\tOUTPUTDIR=$CURR_DIR;\n\tfi\n\n\tbuild\n}\n\nmain $@\nexit $?\n\n\n"
  },
  {
    "path": "package/luci-compat/control/control",
    "content": "Package: luci-app-smartdns\nVersion: git-18.201.27126-7bf0367-1\nDepends: libc, smartdns\nSource: feeds/luci/applications/luci-app-smartdns\nSection: luci\nArchitecture: all\nDescription:  A smartdns server\n"
  },
  {
    "path": "package/luci-compat/control/postinst",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n[ \"${IPKG_NO_SCRIPT}\" = \"1\" ] && exit 0\n[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_postinst $0 $@\n"
  },
  {
    "path": "package/luci-compat/control/prerm",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_prerm $0 $@\n"
  },
  {
    "path": "package/luci-compat/debian-binary",
    "content": "2.0\n"
  },
  {
    "path": "package/luci-compat/files/etc/uci-defaults/50_luci-smartdns",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nuci -q batch <<-EOF >/dev/null\n\tdelete ucitrack.@smartdns[-1]\n\tadd ucitrack smartdns\n\tset ucitrack.@smartdns[-1].init=smartdns\n\tcommit ucitrack\nEOF\n\nrm -f /tmp/luci-indexcache\nexit 0\n"
  },
  {
    "path": "package/luci-compat/files/luci/controller/smartdns.lua",
    "content": "--\n-- Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n--\n-- smartdns is free software: you can redistribute it and/or modify\n-- it under the terms of the GNU General Public License as published by\n-- the Free Software Foundation, either version 3 of the License, or\n-- (at your option) any later version.\n--\n-- smartdns is distributed in the hope that it will be useful,\n-- but WITHOUT ANY WARRANTY; without even the implied warranty of\n-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n-- GNU General Public License for more details.\n--\n-- You should have received a copy of the GNU General Public License\n-- along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nmodule(\"luci.controller.smartdns\", package.seeall)\nlocal smartdns = require \"luci.model.smartdns\"\n\nfunction index()\n\tif not nixio.fs.access(\"/etc/config/smartdns\") then\n\t\treturn\n\tend\n\n\tlocal page\n\tpage = entry({\"admin\", \"services\", \"smartdns\"}, cbi(\"smartdns/smartdns\"), _(\"SmartDNS\"), 60)\n\tpage.dependent = true\n\tpage = entry({\"admin\", \"services\", \"smartdns\", \"status\"}, call(\"act_status\"))\n\tpage.leaf = true\n\tpage = entry({\"admin\", \"services\", \"smartdns\", \"upstream\"}, cbi(\"smartdns/upstream\"), nil)\n\tpage.leaf = true\nend\n\nlocal function is_running()\n\treturn luci.sys.call(\"pidof smartdns >/dev/null\") == 0\nend\n\nfunction act_status()\n\tlocal e={}\n\tlocal ipv6_server;\n\tlocal dnsmasq_server = smartdns.get_config_option(\"dhcp\", \"dnsmasq\", \"server\", {nil})[1]\n\tlocal auto_set_dnsmasq = smartdns.get_config_option(\"smartdns\", \"smartdns\", \"auto_set_dnsmasq\", nil);\n\t\n\te.auto_set_dnsmasq = auto_set_dnsmasq\n\te.dnsmasq_server = dnsmasq_server\n\te.local_port = smartdns.get_config_option(\"smartdns\", \"smartdns\", \"port\", nil);\n\tif e.local_port ~= nil and e.local_port ~= \"53\" and auto_set_dnsmasq ~= nil and auto_set_dnsmasq == \"1\" then\n\t\tlocal str;\n\t\tstr = \"127.0.0.1#\" .. e.local_port \n\t\tif dnsmasq_server ~= str then\n\t\t\te.dnsmasq_redirect_failure = 1\n\t\tend\n\tend\n\te.running = is_running()\n\tluci.http.prepare_content(\"application/json\")\n\tluci.http.write_json(e)\nend\n"
  },
  {
    "path": "package/luci-compat/files/luci/i18n/smartdns.zh-cn.po",
    "content": "\nmsgid \"Additional Args for upstream dns servers\"\nmsgstr \"额外的上游 DNS 服务器参数\"\n\nmsgid \"\"\n\"Additional Flags for rules, read help on domain-rule for more information.\"\nmsgstr \"额外的规则标识，具体参考domain-rule的帮助说明。\"\n\nmsgid \"\"\n\"Additional Flags for rules, read help on ip-rule for more information.\"\nmsgstr \"额外的规则标识，具体参考ip-rule的帮助说明。\"\n\nmsgid \"Additional Rule Flag\"\nmsgstr \"额外规则标识\"\n\nmsgid \"Additional Server Args\"\nmsgstr \"额外的服务器参数\"\n\nmsgid \"Additional server args, refer to the help description of the bind option.\"\nmsgstr \"额外的服务器参数，参考bind选项的帮助说明。\"\n\nmsgid \"Advanced Settings\"\nmsgstr \"高级设置\"\n\nmsgid \"Audit Log Output Mode\"\nmsgstr \"审计日志输出模式\"\n\nmsgid \"Audit Log Size\"\nmsgstr \"审计日志大小\"\n\nmsgid \"Audit Log Number\"\nmsgstr \"审计日志数量\"\n\nmsgid \"Audit Log File\"\nmsgstr \"审计日志文件路径\"\n\nmsgid \"\"\n\"Attempts to serve old responses from cache with a TTL of 0 in the response \"\n\"without waiting for the actual resolution to finish.\"\nmsgstr \"查询性能优化，有请求时尝试回应TTL为0的过期记录，以避免查询等待。\"\n\nmsgid \"Automatically Set Dnsmasq\"\nmsgstr \"自动设置Dnsmasq\"\n\nmsgid \"Automatically set as upstream of dnsmasq when port changes.\"\nmsgstr \"端口更改时自动设为 dnsmasq 的上游。\"\n\nmsgid \"Basic Settings\"\nmsgstr \"基本设置\"\n\nmsgid \"Bind Device\"\nmsgstr \"绑定到设备\"\n\nmsgid \"Bind Device Name\"\nmsgstr \"绑定的设备名称\"\n\nmsgid \"Bogus nxdomain\"\nmsgstr \"假冒IP\"\n\nmsgid \"Blacklist IP\"\nmsgstr \"黑名单\"\n\nmsgid \"Blacklist IP Rule, Decline IP addresses within the range.\"\nmsgstr \"黑名单规则，拒绝指定范围的IP地址。\"\n\nmsgid \"Block domain\"\nmsgstr \"屏蔽域名\"\n\nmsgid \"Block domain.\"\nmsgstr \"屏蔽域名。\"\n\nmsgid \"Cache Persist\"\nmsgstr \"持久化缓存\"\n\nmsgid \"Cache Size\"\nmsgstr \"缓存大小\"\n\nmsgid \"Client Rules\"\nmsgstr \"客户端规则\"\n\nmsgid \"Client Address\"\nmsgstr \"客户端地址\"\n\nmsgid \"Client Address File\"\nmsgstr \"客户端地址文件\"\n\nmsgid \"Client Rules Settings, can achieve parental control functionality.\"\nmsgstr \"客户端规则设置，可以实现家长控制功能。\"\n\nmsgid \"Collecting data ...\"\nmsgstr \"正在收集数据...\"\n\nmsgid \"\"\n\"Configure IP blacklists that will be filtered from the results of specific \"\n\"DNS server.\"\nmsgstr \"配置需要从指定域名服务器结果过滤的IP黑名单。\"\n\nmsgid \"Configure block domain list.\"\nmsgstr \"配置屏蔽域名列表\"\n\nmsgid \"Configure domain rule list.\"\nmsgstr \"配置域名规则列表\"\n\nmsgid \"Configure forwarding domain name list.\"\nmsgstr \"配置分流域名列表\"\n\nmsgid \"Custom Settings\"\nmsgstr \"自定义设置\"\n\nmsgid \"Do not use these IP addresses.\"\nmsgstr \"忽略这些IP地址\"\n\nmsgid \"DOH Server\"\nmsgstr \"DOH服务器\"\n\nmsgid \"DOH Server Port\"\nmsgstr \"DOH服务器端口\"\n\nmsgid \"DOT Server\"\nmsgstr \"DOT服务器\"\n\nmsgid \"DOT Server Port\"\nmsgstr \"DOT服务器端口\"\n\nmsgid \"DNS Block Setting\"\nmsgstr \"域名屏蔽设置\"\n\nmsgid \"DNS Forwarding Setting\"\nmsgstr \"域名分流设置\"\n\nmsgid \"DNS Server Name\"\nmsgstr \"DNS服务器名称\"\n\nmsgid \"DNS Server group\"\nmsgstr \"服务器组\"\n\nmsgid \"DNS Server group belongs to, such as office, home.\"\nmsgstr \"设置服务器组，例如office，home\"\n\nmsgid \"DNS Server ip\"\nmsgstr \"DNS服务器IP\"\n\nmsgid \"DNS Server port\"\nmsgstr \"DNS服务器端口\"\n\nmsgid \"DNS Server type\"\nmsgstr \"协议类型\"\n\nmsgid \"DNS domain result cache size\"\nmsgstr \"缓存DNS的结果，缓存大小，配置零则不缓存。\"\n\nmsgid \"DNS64\"\nmsgstr \"DNS64\"\n\nmsgid \"DNS64 Server Settings\"\nmsgstr \"DNS64服务器配置\"\n\nmsgid \"default\"\nmsgstr \"默认\"\n\nmsgid \"Description\"\nmsgstr \"描述\"\n\nmsgid \"Dnsmasq Forwarded To Smartdns Failure\"\nmsgstr \"重定向dnsmasq到smartdns失败\"\n\nmsgid \"Do not check certificate.\"\nmsgstr \"不校验证书的合法性。\"\n\nmsgid \"Do not check speed.\"\nmsgstr \"禁用测速。\"\n\nmsgid \"Domain Address\"\nmsgstr \"域名地址\"\n\nmsgid \"Domain List\"\nmsgstr \"域名列表\"\n\nmsgid \"Domain List File\"\nmsgstr \"域名列表文件\"\n\nmsgid \"Domain Rule List\"\nmsgstr \"域名规则列表\"\n\nmsgid \"Domain Rule Name\"\nmsgstr \"域名规则名称\"\n\nmsgid \"Domain Rules\"\nmsgstr \"域名规则\"\n\nmsgid \"Domain Rules Settings\"\nmsgstr \"域名规则设置\"\n\nmsgid \"Domain TTL\"\nmsgstr \"域名TTL\"\n\nmsgid \"Domain TTL Max\"\nmsgstr \"域名TTL最大值\"\n\nmsgid \"Domain TTL Min\"\nmsgstr \"域名TTL最小值\"\n\nmsgid \"Domain prefetch\"\nmsgstr \"域名预加载\"\n\nmsgid \"Donate\"\nmsgstr \"捐助\"\n\nmsgid \"Donate to smartdns\"\nmsgstr \"捐助smartdns项目\"\n\nmsgid \"Download Files\"\nmsgstr \"下载文件\"\n\nmsgid \"Download Files Setting\"\nmsgstr \"下载文件设置\"\n\nmsgid \"\"\n\"Download domain list files for domain-rule and include config files, please \"\n\"refresh the page after download to take effect.\"\nmsgstr \"\"\n\"下载域名规则所需要的域名列表文件和smartdns配置文件，下载完成后刷新页面。\"\n\nmsgid \"Dual-stack IP Selection\"\nmsgstr \"双栈IP优选\"\n\nmsgid \"Enable\"\nmsgstr \"启用\"\n\nmsgid \"Enable Auto Update\"\nmsgstr \"启用自动更新\"\n\nmsgid \"Enable IP selection between IPV4 and IPV6\"\nmsgstr \"启用 IPV4 和 IPV6 间的 IP 优选策略。\"\n\nmsgid \"Enable IPV6 DNS Server\"\nmsgstr \"启用IPV6服务器。\"\n\nmsgid \"Enable TCP DNS Server\"\nmsgstr \"启用TCP服务器。\"\n\nmsgid \"Enable daily(week) auto update.\"\nmsgstr \"启用每天（每周）自动更新。\"\n\nmsgid \"Enable DOH DNS Server\"\nmsgstr \"启用DOH服务器。\"\n\nmsgid \"Enable DOT DNS Server\"\nmsgstr \"启用DOT服务器。\"\n\nmsgid \"Update Time (Every Week)\"\nmsgstr \"更新时间(每周)\"\n\nmsgid \"Every Day\"\nmsgstr \"每天\"\n\nmsgid \"Every Monday\"\nmsgstr \"每周一\"\n\nmsgid \"Every Tuesday\"\nmsgstr \"每周二\"\n\nmsgid \"Every Wednesday\"\nmsgstr \"每周三\"\n\nmsgid \"Every Thursday\"\nmsgstr \"每周四\"\n\nmsgid \"Every Friday\"\nmsgstr \"每周五\"\n\nmsgid \"Every Saturday\"\nmsgstr \"每周六\"\n\nmsgid \"Every Sunday\"\nmsgstr \"每周日\"\n\nmsgid \"Update Time (Every Day)\"\nmsgstr \"更新时间(每天)\"\n\nmsgid \"Enable Audit Log\"\nmsgstr \"启用审计日志\"\n\nmsgid \"Enable domain prefetch, accelerate domain response speed.\"\nmsgstr \"启用域名预加载，加速域名响应速度。\"\n\nmsgid \"Enable or disable second DNS server.\"\nmsgstr \"是否启用第二DNS服务器。\"\n\nmsgid \"Enable or disable smartdns server\"\nmsgstr \"启用或禁用SmartDNS服务\"\n\nmsgid \"Exclude DNS Server from default group.\"\nmsgstr \"从default默认服务器组中排除。\"\n\nmsgid \"Exclude Default Group\"\nmsgstr \"从默认组中排除\"\n\nmsgid \"file\"\nmsgstr \"文件\"\n\nmsgid \"Fastest IP\"\nmsgstr \"最快IP\"\n\nmsgid \"Fastest Response\"\nmsgstr \"最快响应\"\n\nmsgid \"File Name\"\nmsgstr \"文件名\"\n\nmsgid \"File Type\"\nmsgstr \"文件类型\"\n\nmsgid \"Filtering IP with blacklist\"\nmsgstr \"使用IP黑名单过滤\"\n\nmsgid \"First Ping\"\nmsgstr \"最快PING\"\n\nmsgid \"Force AAAA SOA\"\nmsgstr \"停用IPV6地址解析\"\n\nmsgid \"Force AAAA SOA.\"\nmsgstr \"停用IPV6地址解析。\"\n\nmsgid \"Force HTTPS SOA\"\nmsgstr \"停用HTTPS记录解析\"\n\nmsgid \"Force HTTPS SOA.\"\nmsgstr \"停用HTTPS记录解析。\"\n\nmsgid \"General Settings\"\nmsgstr \"常规设置\"\n\nmsgid \"Generate Coredump\"\nmsgstr \"生成coredump\"\n\nmsgid \"\"\n\"Generate Coredump file when smartdns crash, coredump file is located at /tmp/\"\n\"smartdns.xxx.core.\"\nmsgstr \"\"\n\"当smartdns异常时生成coredump文件，coredump文件在/tmp/smartdns.xxx.core.\"\n\nmsgid \"Grant access to LuCI app smartdns\"\nmsgstr \"授予访问 LuCI 应用 smartdns 的权限\"\n\nmsgid \"Hosts File\"\nmsgstr \"Hosts文件\"\n\nmsgid \"HTTP Host\"\nmsgstr \"HTTP主机\"\n\nmsgid \"IP alias\"\nmsgstr \"IP别名\"\n\nmsgid \"IP Alias Setting\"\nmsgstr \"IP别名设置\"\n\nmsgid \"IP Blacklist\"\nmsgstr \"IP黑名单\"\n\nmsgid \"IP Blacklist Filtering\"\nmsgstr \"IP黑名单过滤\"\n\nmsgid \"IP Addresses\"\nmsgstr \"IP地址\"\n\nmsgid \"IP Address Mapping, Can be used for CDN acceleration with Anycast IP, such as Cloudflare's CDN.\"\nmsgstr \"IP地址映射，可用于支持AnyCast IP的CDN加速，比如Cloudflare的CDN。\"\n\nmsgid \"Ignore IP\"\nmsgstr \"忽略IP\"\n\nmsgid \"IP Rules\"\nmsgstr \"IP规则\"\n\nmsgid \"IP Rules Settings\"\nmsgstr \"IP规则设置\"\n\nmsgid \"IP Rule Name\"\nmsgstr \"IP规则名称\"\n\nmsgid \"IP Set File\"\nmsgstr \"IP集合列表文件\"\n\nmsgid \"IP addresses, CIDR format.\"\nmsgstr \"IP地址，CIDR格式。\"\n\nmsgid \"IPV6 Server\"\nmsgstr \"IPV6服务器\"\n\nmsgid \"IPset Name\"\nmsgstr \"IPset名称\"\n\nmsgid \"IPset name.\"\nmsgstr \"IPset名称。\"\n\nmsgid \"\"\n\"If a client address is specified, only that client will apply this \"\n\"rule. You can enter an IP address, such as 1.2.3.4, or a MAC address, \"\n\"such as aa:bb:cc:dd:ee:ff.\"\nmsgstr \"\"\n\"如果指定了客户端，那么对应的客户端会应用相应的规则，可以输入IP地址，如：1.2.3.4，或MAC地址，如：aa:bb:cc:dd:ee:ff。\"\n\nmsgid \"If you like this software, please buy me a cup of coffee.\"\nmsgstr \"如果本软件对你有帮助，请给作者加个蛋。\"\n\nmsgid \"Include Config Files<br>/etc/smartdns/conf.d\"\nmsgstr \"包含配置文件<br>/etc/smartdns/conf.d\"\n\nmsgid \"Include hosts file.\"\nmsgstr \"包含hosts文件。\"\n\nmsgid \"\"\n\"Include other config files from /etc/smartdns/conf.d or custom path, can be \"\n\"downloaded from the download page.\"\nmsgstr \"\"\n\"包含配置文件，路径为/etc/smartdns/conf.d，或自定义配置文件路径，可以从下载页\"\n\"配置自动下载。面配置自动下载。\"\n\nmsgid \"Ipset name, Add domain result to ipset when speed check fails.\"\nmsgstr \"IPset名称，当测速失败时，将查询到的结果添加到对应的IPSet集合中。\"\n\nmsgid \"List of files to download.\"\nmsgstr \"下载文件列表。\"\n\nmsgid \"Listen only on the specified interfaces.\"\nmsgstr \"监听在指定的设备上，避免非本地网络的DNS查询请求。\"\n\nmsgid \"Local Port\"\nmsgstr \"本地端口\"\n\nmsgid \"Log Output Mode\"\nmsgstr \"日志输出模式\"\n\nmsgid \"Log Size\"\nmsgstr \"日志大小\"\n\nmsgid \"Log Level\"\nmsgstr \"日志级别\"\n\nmsgid \"Log Number\"\nmsgstr \"日志数量\"\n\nmsgid \"Log File\"\nmsgstr \"日志文件路径\"\n\nmsgid \"mDNS Lookup\"\nmsgstr \"mDNS查询\"\n\nmsgid \"Marking Packets\"\nmsgstr \"数据包标记\"\n\nmsgid \"Maximum TTL for all domain result.\"\nmsgstr \"所有域名的最大 TTL 值。\"\n\nmsgid \"Minimum TTL for all domain result.\"\nmsgstr \"所有域名的最小 TTL 值。\"\n\nmsgid \"NFTset Name\"\nmsgstr \"NFTSet名称\"\n\nmsgid \"NFTset name format error, format: [#[4|6]:[family#table#set]]\"\nmsgstr \"NFTSet名称格式错误，格式：[#[4|6]:[family#table#set]]\"\n\nmsgid \"NFTset name, format: [#[4|6]:[family#table#set]]\"\nmsgstr \"NFTSet名称，格式：[#[4|6]:[family#table#set]]\"\n\nmsgid \"NOT RUNNING\"\nmsgstr \"未运行\"\n\nmsgid \"Name of device name listen on.\"\nmsgstr \"绑定的设备名称。\"\n\nmsgid \"\"\n\"Nftset name, Add domain result to nftset when speed check fails, format: \"\n\"[#[4|6]:[family#table#set]]\"\nmsgstr \"NFTset名称，当测速失败时，将查询到的结果添加到对应的NFTSet集合中。\"\n\nmsgid \"No\"\nmsgstr \"否\"\n\nmsgid \"No Speed IPset Name\"\nmsgstr \"无速度时IPSet名称\"\n\nmsgid \"No Speed NFTset Name\"\nmsgstr \"无速度时NFTSet名称\"\n\nmsgid \"No check certificate\"\nmsgstr \"停用证书校验\"\n\nmsgid \"None\"\nmsgstr \"无\"\n\nmsgid \"Only socks5 proxy support udp server.\"\nmsgstr \"仅SOCKS5代理支持UDP服务器。\"\n\nmsgid \"Please set proxy server first.\"\nmsgstr \"请先设置代理服务器。\"\n\nmsgid \"Proxy Server\"\nmsgstr \"代理服务器\"\n\nmsgid \"Proxy Server Settings\"\nmsgstr \"代理服务器设置\"\n\nmsgid \"Proxy Server URL, format: [socks5|http]://user:pass@ip:port.\"\nmsgstr \"代理服务器地址，格式：[socks5|http]://user:pass@ip:port。\"\n\nmsgid \"\"\n\"Proxy server URL format error, format: [socks5|http]://user:pass@ip:port.\"\nmsgstr \"代理服务器地址格式错误，格式：[socks5|http]://user:pass@ip:port。\"\n\nmsgid \"Query DNS through specific dns server group, such as office, home.\"\nmsgstr \"使用指定服务器组查询，比如office, home。\"\n\nmsgid \"RUNNING\"\nmsgstr \"运行中\"\n\nmsgid \"Reply Domain TTL Max\"\nmsgstr \"回应的域名TTL最大值\"\n\nmsgid \"Reply maximum TTL for all domain result.\"\nmsgstr \"设置返回给客户端的域名TTL最大值。\"\n\nmsgid \"Report bugs\"\nmsgstr \"报告BUG\"\n\nmsgid \"Return SOA when the requested result contains a specified IP address.\"\nmsgstr \"当结果包含对应范围的IP时，返回SOA。\"\n\nmsgid \"Resolve Local Hostnames\"\nmsgstr \"解析本地主机名\"\n\nmsgid \"Resolve local hostnames by reading Dnsmasq lease file.\"\nmsgstr \"读取Dnsmasq的租约文件解析本地主机名。\"\n\nmsgid \"Resolve local network hostname via mDNS protocol.\"\nmsgstr \"使用mDNS协议解析本地网络主机名。\"\n\nmsgid \"Response Mode\"\nmsgstr \"响应模式\"\n\nmsgid \"Restart\"\nmsgstr \"重启\"\n\nmsgid \"Restart Service\"\nmsgstr \"重启服务\"\n\nmsgid \"syslog\"\nmsgstr \"系统日志\"\n\nmsgid \"Second Server Settings\"\nmsgstr \"第二DNS服务器\"\n\nmsgid \"Server certificate file path.\"\nmsgstr \"服务器证书文件路径。\"\n\nmsgid \"Server certificate key file path.\"\nmsgstr \"服务器证书私钥文件路径。\"\n\nmsgid \"Server certificate key file password.\"\nmsgstr \"服务器证书私钥文件密码。\"\n\nmsgid \"Serve expired\"\nmsgstr \"缓存过期服务\"\n\nmsgid \"Server Group\"\nmsgstr \"服务器组\"\n\nmsgid \"Server Group %s not exists\"\nmsgstr \"服务器组%s不存在\"\n\nmsgid \"Server Name\"\nmsgstr \"服务器名称\"\n\nmsgid \"Server Cert\"\nmsgstr \"服务器证书\"\n\nmsgid \"Server Cert Key\"\nmsgstr \"服务器证书私钥\"\n\nmsgid \"Server Cert Key Pass\"\nmsgstr \"服务器证书私钥密码\"\n\nmsgid \"Set Specific domain ip address.\"\nmsgstr \"设置指定域名的IP地址。\"\n\nmsgid \"Set Specific domain rule list.\"\nmsgstr \"设置指定域名的规则列表。\"\n\nmsgid \"Set Specific ip blacklist.\"\nmsgstr \"设置指定的 IP 黑名单列表。\"\n\nmsgid \"Set TLS hostname to verify.\"\nmsgstr \"设置校验TLS主机名。\"\n\nmsgid \"Set mark on packets.\"\nmsgstr \"设置数据包标记。\"\n\nmsgid \"\"\n\"Set the HTTP host used for the query. Use this parameter when the host of \"\n\"the URL address is an IP address.\"\nmsgstr \"设置查询时使用的HTTP主机，当URL地址的host是IP地址时，使用此参数。\"\n\nmsgid \"Sets the server name indication for query. '-' for disable SNI name.\"\nmsgstr \"设置服务器SNI名称，‘-’表示禁用SNI名称。\"\n\nmsgid \"Settings\"\nmsgstr \"设置\"\n\nmsgid \"Skip Address Rules\"\nmsgstr \"跳过address规则\"\n\nmsgid \"Skip Cache\"\nmsgstr \"跳过cache\"\n\nmsgid \"Skip Cache.\"\nmsgstr \"跳过cache。\"\n\nmsgid \"Skip Dualstack Selection\"\nmsgstr \"跳过双栈优选\"\n\nmsgid \"Skip Dualstack Selection.\"\nmsgstr \"跳过双栈优选。\"\n\nmsgid \"Skip IP Alias\"\nmsgstr \"跳过IP别名\"\n\nmsgid \"Skip Ipset Rule\"\nmsgstr \"跳过ipset规则\"\n\nmsgid \"Skip Nameserver Rule\"\nmsgstr \"跳过Nameserver规则\"\n\nmsgid \"Skip SOA Address Rule\"\nmsgstr \"跳过address SOA(#)规则\"\n\nmsgid \"Skip SOA address rules.\"\nmsgstr \"跳过address SOA(#)规则。\"\n\nmsgid \"Skip Speed Check\"\nmsgstr \"跳过测速\"\n\nmsgid \"Skip address rules.\"\nmsgstr \"跳过address规则。\"\n\nmsgid \"Skip ipset rules.\"\nmsgstr \"跳过ipset规则。\"\n\nmsgid \"Skip nameserver rules.\"\nmsgstr \"跳过Nameserver规则。\"\n\nmsgid \"SmartDNS\"\nmsgstr \"SmartDNS\"\n\nmsgid \"Smartdns DOH server port.\"\nmsgstr \"Smartdns DOH服务器端口号。\n\nmsgid \"Smartdns DOT server port.\"\nmsgstr \"Smartdns DOT服务器端口号。\"\n\nmsgid \"SmartDNS Server\"\nmsgstr \"SmartDNS 服务器\"\n\nmsgid \"\"\n\"SmartDNS is a local high-performance DNS server, supports finding fastest \"\n\"IP, supports ad filtering, and supports avoiding DNS poisoning.\"\nmsgstr \"SmartDNS是一个本地高性能DNS服务器，支持返回最快IP，支持广告过滤。\"\n\nmsgid \"SmartDNS official website\"\nmsgstr \"SmartDNS官方网站\"\n\nmsgid \"Smartdns local server port\"\nmsgstr \"SmartDNS本地服务端口\"\n\nmsgid \"\"\n\"Smartdns local server port, smartdns will be automatically set as main dns \"\n\"when the port is 53.\"\nmsgstr \"\"\n\"SmartDNS本地服务端口，当端口号设置为53时，smartdns将会自动配置为主dns。\"\n\nmsgid \"\"\n\"Smartdns response mode, First Ping: return the first ping IP, Fastest IP: \"\n\"return the fastest IP, Fastest Response: return the fastest DNS response.\"\nmsgstr \"\"\n\"SmartDNS响应模式，最快PING： 返回最早有ping结果的IP，速度适中；最快IP： 返回\"\n\"最快IP，查询请求可能延长； 最快响应：返回最快响应的结果，查询请求时间短。\"\n\nmsgid \"Smartdns server name\"\nmsgstr \"SmartDNS的服务器名称，默认为smartdns，留空为主机名\"\n\nmsgid \"Smartdns speed check mode.\"\nmsgstr \"SmartDNS测速模式。\"\n\nmsgid \"\"\n\"Specify an IP address to return for any host in the given domains, Queries \"\n\"in the domains are never forwarded and always replied to with the specified \"\n\"IP address which may be IPv4 or IPv6.\"\nmsgstr \"\"\n\"配置特定域名返回特定的IP地址，域名查询将不到上游服务器请求，直接返回配置的IP\"\n\"地址，可用于广告屏蔽。\"\n\nmsgid \"Speed Check Mode\"\nmsgstr \"测速模式\"\n\nmsgid \"Speed check mode is invalid.\"\nmsgstr \"测速模式无效。\"\n\nmsgid \"TCP Server\"\nmsgstr \"TCP服务器\"\n\nmsgid \"TCP port is empty\"\nmsgstr \"TCP端口号为空\"\n\nmsgid \"TLS Hostname Verify\"\nmsgstr \"校验TLS主机名\"\n\nmsgid \"TLS SNI name\"\nmsgstr \"TLS SNI名称\"\n\nmsgid \"TLS SPKI Pinning\"\nmsgstr \"TLS SPKI 指纹\"\n\nmsgid \"TTL for all domain result.\"\nmsgstr \"设置所有域名的 TTL 值。\"\n\nmsgid \"Technical Support\"\nmsgstr \"技术支持\"\n\nmsgid \"URL\"\nmsgstr \"URL\"\n\nmsgid \"URL format error, format: http:// or https://\"\nmsgstr \"URL格式错误，格式：http://或https://\"\n\nmsgid \"Update\"\nmsgstr \"更新\"\n\nmsgid \"Update Files\"\nmsgstr \"更新文件\"\n\nmsgid \"Upload client address file, same as Client Address function.\"\nmsgstr \"上传客户端地址文件，与客户端地址功能相同。\"\n\nmsgid \"Upload Config File\"\nmsgstr \"上传配置文件\"\n\nmsgid \"Upload Domain List File\"\nmsgstr \"上传域名列表文件\"\n\nmsgid \"Upload domain list file to /etc/smartdns/domain-set\"\nmsgstr \"上传域名列表文件到/etc/smartdns/domain-set\"\n\nmsgid \"\"\n\"Upload domain list file, or configure auto download from Download File \"\n\"Setting page.\"\nmsgstr \"上传域名列表文件，或在下载文件设置页面设置自动下载。\"\n\nmsgid \"Upload domain list file.\"\nmsgstr \"上传域名列表文件\"\n\nmsgid \"Upload File\"\nmsgstr \"上传文件\"\n\nmsgid \"Upload IP set file.\"\nmsgstr \"上传IP集合列表文件。\"\n\nmsgid \"Upload smartdns config file to /etc/smartdns/conf.d\"\nmsgstr \"上传配置文件到/etc/smartdns/conf.d\"\n\nmsgid \"Upstream DNS Server Configuration\"\nmsgstr \"上游DNS服务器配置\"\n\nmsgid \"Upstream Servers\"\nmsgstr \"上游服务器\"\n\nmsgid \"\"\n\"Upstream Servers, support UDP, TCP protocol. Please configure multiple DNS \"\n\"servers, including multiple foreign DNS servers.\"\nmsgstr \"\"\n\"上游 DNS 服务器，支持 UDP，TCP 协议。请配置多个上游 DNS 服务器，包括多个国内\"\n\"外服务器。\"\n\nmsgid \"Use Proxy\"\nmsgstr \"使用代理\"\n\nmsgid \"Use proxy to connect to upstream DNS server.\"\nmsgstr \"使用代理连接上游DNS服务器。\"\n\nmsgid \"\"\n\"Used to verify the validity of the TLS server, The value is Base64 encoded \"\n\"SPKI fingerprint, leaving blank to indicate that the validity of TLS is not \"\n\"verified.\"\nmsgstr \"\"\n\"用于校验 TLS 服务器的有效性，数值为 Base64 编码的 SPKI 指纹，留空表示不验证 \"\n\"TLS 的合法性。\"\n\nmsgid \"Whitelist IP\"\nmsgstr \"白名单\"\n\nmsgid \"Whitelist IP Rule, Accept IP addresses within the range.\"\nmsgstr \"白名单规则，接受指定范围的IP地址。\"\n\nmsgid \"Write cache to disk on exit and load on startup.\"\nmsgstr \"退出时保存cache到磁盘，启动时加载。\"\n\nmsgid \"Yes\"\nmsgstr \"是\"\n\nmsgid \"default\"\nmsgstr \"默认\"\n\nmsgid \"domain list (/etc/smartdns/domain-set)\"\nmsgstr \"域名列表（/etc/smartdns/domain-set）\"\n\nmsgid \"other file (/etc/smartdns/download)\"\nmsgstr \"其它文件（/etc/smartdns/download）\"\n\nmsgid \"https\"\nmsgstr \"https\"\n\nmsgid \"ip\"\nmsgstr \"ip\"\n\nmsgid \"ip-set file (/etc/smartdns/ip-set)\"\nmsgstr \"IP集合列表文件（/etc/smartdns/ip-set）\"\n\nmsgid \"ipset name format error, format: [#[4|6]:]ipsetname\"\nmsgstr \"IPset名称格式错误，格式：[#[4|6]:]ipsetname\"\n\nmsgid \"open website\"\nmsgstr \"打开网站\"\n\nmsgid \"port\"\nmsgstr \"端口\"\n\nmsgid \"smartdns config (/etc/smartdns/conf.d)\"\nmsgstr \"smartdns 配置文件（/etc/smartdns/conf.d）\"\n\nmsgid \"smartdns custom settings\"\nmsgstr \"smartdns 自定义设置，具体配置参数参考指导\"\n\nmsgid \"tcp\"\nmsgstr \"tcp\"\n\nmsgid \"tls\"\nmsgstr \"tls\"\n\nmsgid \"type\"\nmsgstr \"类型\"\n\nmsgid \"udp\"\nmsgstr \"udp\"\n"
  },
  {
    "path": "package/luci-compat/files/luci/model/cbi/smartdns/smartdns.lua",
    "content": "--\n-- Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n--\n-- smartdns is free software: you can redistribute it and/or modify\n-- it under the terms of the GNU General Public License as published by\n-- the Free Software Foundation, either version 3 of the License, or\n-- (at your option) any later version.\n--\n-- smartdns is distributed in the hope that it will be useful,\n-- but WITHOUT ANY WARRANTY; without even the implied warranty of\n-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n-- GNU General Public License for more details.\n--\n-- You should have received a copy of the GNU General Public License\n-- along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nrequire (\"nixio.fs\")\nrequire (\"luci.http\")\nrequire (\"luci.dispatcher\")\nrequire (\"nixio.fs\")\n\nlocal uci = require \"luci.model.uci\".cursor()\n\nm = Map(\"smartdns\")\nm.title\t= translate(\"SmartDNS Server\")\nm.description = translate(\"SmartDNS is a local high-performance DNS server, supports finding fastest IP, supports ad filtering, and supports avoiding DNS poisoning.\")\n\nm:section(SimpleSection).template  = \"smartdns/smartdns_status\"\n\n-- Basic\ns = m:section(TypedSection, \"smartdns\", translate(\"Settings\"), translate(\"General Settings\"))\ns.anonymous = true\n\ns:tab(\"settings\", translate(\"General Settings\"))\ns:tab(\"advanced\", translate('Advanced Settings'))\ns:tab(\"seconddns\", translate(\"Second Server Settings\"))\ns:tab(\"dns64\", translate(\"DNS64 Server Settings\"))\ns:tab(\"proxy\", translate(\"Proxy Server Settings\"))\ns:tab(\"custom\", translate(\"Custom Settings\"))\n\n---- Eanble\no = s:taboption(\"settings\", Flag, \"enabled\", translate(\"Enable\"), translate(\"Enable or disable smartdns server\"))\no.default     = o.disabled\no.rempty      = false\n\n---- server name\no = s:taboption(\"settings\", Value, \"server_name\", translate(\"Server Name\"), translate(\"Smartdns server name\"))\no.placeholder = \"server name\"\no.datatype    = \"hostname\"\no.rempty      = true\n\n---- Port\no = s:taboption(\"settings\", Value, \"port\", translate(\"Local Port\"), \n    translate(\"Smartdns local server port, smartdns will be automatically set as main dns when the port is 53.\"))\no.placeholder = 53\no.default     = 53\no.datatype    = \"port\"\no.rempty      = false\n\n-- Automatically Set Dnsmasq\no = s:taboption(\"settings\", Flag, \"auto_set_dnsmasq\", translate(\"Automatically Set Dnsmasq\"), translate(\"Automatically set as upstream of dnsmasq when port changes.\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- Speed check mode;\no = s:taboption(\"advanced\", Value, \"speed_check_mode\", translate(\"Speed Check Mode\"), translate(\"Smartdns speed check mode.\"));\no.rmempty = true;\no.placeholder = \"default\";\no:value(\"\", translate(\"default\"))\no:value(\"ping,tcp:80,tcp:443\");\no:value(\"ping,tcp:443,tcp:80\");\no:value(\"tcp:80,tcp:443,ping\");\no:value(\"tcp:443,tcp:80,ping\");\no:value(\"none\", translate(\"None\"));\nfunction o.validate (section_id, value) \n    if value == \"\" then\n        return value\n    end\n\n    if value == nil then\n        return nil, translate(\"Speed check mode is invalid.\")\n    end\n\n    if value == \"none\" then\n        return value\n    end\n\n    local mode = value:split(\",\");\n    for _, v in ipairs(mode) do repeat\n        if v == \"ping\" then\n            break\n        end\n\n        if v == nil then\n            return nil, translate(\"Speed check mode is invalid.\")\n        end\n        \n        local port = v:split(\":\");\n        if \"tcp\" == port[1] then\n            if tonumber(port[2]) then\n                break\n            end\n        end\n        \n        return nil, translate(\"Speed check mode is invalid.\")\n    until true end\n\n    return value\nend\n\n---- response mode;\no = s:taboption(\"advanced\", ListValue, \"response_mode\", translate(\"Response Mode\"), \n    translate(\"Smartdns response mode, First Ping: return the first ping IP, Fastest IP: return the fastest IP, Fastest Response: return the fastest DNS response.\"))\no.rmempty     = true\no.placeholder = \"default\"\no:value(\"\", translate(\"default\"))\no:value(\"first-ping\", translate(\"First Ping\"))\no:value(\"fastest-ip\", translate(\"Fastest IP\"))\no:value(\"fastest-response\", translate(\"Fastest Response\"))\n\n---- Enable TCP server\no = s:taboption(\"advanced\", Flag, \"tcp_server\", translate(\"TCP Server\"), translate(\"Enable TCP DNS Server\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"1\"\nend\n\n---- Enable DOT server;\no = s:taboption(\"advanced\", Flag, \"tls_server\", translate(\"DOT Server\"), translate(\"Enable DOT DNS Server\"))\no.rmempty = false\no.default = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\no = s:taboption(\"advanced\", Value, \"tls_server_port\", translate(\"DOT Server Port\"), translate(\"Smartdns DOT server port.\"))\no.placeholder = 853\no.default = 853\no.datatype = \"port\"\no.rempty = false\no:depends('tls_server', '1')\n\n---- Enable DOH server;\no = s:taboption(\"advanced\", Flag, \"doh_server\", translate(\"DOH Server\"), translate(\"Enable DOH DNS Server\"))\no.rmempty = false\no.default = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\no = s:taboption(\"advanced\", Value, \"doh_server_port\", translate(\"DOH Server Port\"), translate(\"Smartdns DOH server port.\"))\no.placeholder = 843\no.default = 843\no.datatype = \"port\"\no.rempty = false\no:depends('doh_server', '1')\n\no = s:taboption(\"advanced\", Value, \"bind_cert\", translate(\"Server Cert\"), translate(\"Server certificate file path.\"))\no.datatype = \"string\"\no.placeholder = \"/var/etc/smartdns/smartdns/smartdns-cert.pem\"\no.rempty = true\no:depends('tls_server', '1')\no:depends('doh_server', '1')\n\no = s:taboption(\"advanced\", Value, \"bind_cert_key\", translate(\"Server Cert Key\"), translate(\"Server certificate key file path.\"))\no.datatype = \"string\"\no.placeholder = \"/var/etc/smartdns/smartdns/smartdns-key.pem\"\no.rempty = false\no:depends('tls_server', '1')\no:depends('doh_server', '1')\n\no = s:taboption(\"advanced\", Value, \"bind_cert_key_pass\", translate(\"Server Cert Key Pass\"), translate(\"Server certificate key file password.\"))\no.datatype = \"string\"\no.rempty = false\no:depends('tls_server', '1')\no:depends('doh_server', '1')\n\n---- Support IPV6\no = s:taboption(\"advanced\", Flag, \"ipv6_server\", translate(\"IPV6 Server\"), translate(\"Enable IPV6 DNS Server\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"1\"\nend\n\n---- bind to device;\no = s:taboption(\"advanced\", Flag, \"bind_device\", translate(\"Bind Device\"), translate(\"Listen only on the specified interfaces.\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"1\"\nend\n\n---- bind device name;\no = s:taboption(\"advanced\", Value, \"bind_device_name\", translate(\"Bind Device Name\"), translate(\"Name of device name listen on.\"))\no.placeholder = \"default\"\no.rempty      = true\no.datatype    = \"string\"\n\n---- Support DualStack ip selection\no = s:taboption(\"advanced\", Flag, \"dualstack_ip_selection\", translate(\"Dual-stack IP Selection\"), translate(\"Enable IP selection between IPV4 and IPV6\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- Domain prefetch load \no = s:taboption(\"advanced\", Flag, \"prefetch_domain\", translate(\"Domain prefetch\"), translate(\"Enable domain prefetch, accelerate domain response speed.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- Domain Serve expired\no = s:taboption(\"advanced\", Flag, \"serve_expired\", translate(\"Serve expired\"), \n\ttranslate(\"Attempts to serve old responses from cache with a TTL of 0 in the response without waiting for the actual resolution to finish.\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- cache-size\no = s:taboption(\"advanced\", Value, \"cache_size\", translate(\"Cache Size\"), translate(\"DNS domain result cache size\"))\no.rempty      = true\n\n---- cache-persist;\no = s:taboption(\"advanced\", Flag, \"cache_persist\", translate(\"Cache Persist\"), translate(\"Write cache to disk on exit and load on startup.\"))\no.rmempty      = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"1\"\nend\n\n-- resolve local hostname\no = s:taboption(\"advanced\", Flag, \"resolve_local_hostnames\", translate(\"Resolve Local Hostnames\"), translate(\"Resolve local hostnames by reading Dnsmasq lease file.\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"1\"\nend\n\n-- resolve local network hostname via mDNS\no = s:taboption(\"advanced\", Flag, \"mdns_lookup\", translate(\"mDNS Lookup\"), translate(\"Resolve local network hostname via mDNS protocol.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n-- Force AAAA SOA\no = s:taboption(\"advanced\", Flag, \"force_aaaa_soa\", translate(\"Force AAAA SOA\"), translate(\"Force AAAA SOA.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n-- Force HTTPS SOA\no = s:taboption(\"advanced\", Flag, \"force_https_soa\", translate(\"Force HTTPS SOA\"), translate(\"Force HTTPS SOA.\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"1\"\nend\n\no = s:taboption(\"advanced\", Value, \"ipset_name\", translate(\"IPset Name\"), translate(\"IPset name.\"))\no.rmempty = true\no.datatype = \"string\"\no.rempty = true\n\n---- Ipset no speed.\no = s:taboption(\"advanced\", Value, \"ipset_no_speed\", translate(\"No Speed IPset Name\"), \n    translate(\"Ipset name, Add domain result to ipset when speed check fails.\"));\no.rmempty = true;\no.datatype = \"hostname\";\no.rempty = true;\n\no = s:taboption(\"advanced\", Value, \"nftset_name\", translate(\"NFTset Name\"), translate(\"NFTset name, format: [#[4|6]:[family#table#set]]\"))\no.rmempty = true\no.datatype = \"string\"\no.rempty = true\nfunction o.validate(self, value) \n    if (value == \"\") then\n        return value\n    end\n\n    if (value:match(\"#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$\")) then\n        return value\n    end\n\n    return nil, translate(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\")\nend\n\n---- NFTset no speed.\no = s:taboption(\"advanced\", Value, \"nftset_no_speed\", translate(\"No Speed NFTset Name\"), \n    translate(\"Nftset name, Add domain result to nftset when speed check fails, format: [#[4|6]:[family#table#set]]\"));\no.rmempty    = true;\no.datatype   = \"string\";\no.rempty     = true;\nfunction o.validate(self, value) \n    if (value == \"\") then\n        return value\n    end\n\n    if (value:match(\"#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$\")) then\n        return value\n    end\n\n    return nil, translate(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\")\nend\n\n---- rr-ttl\no = s:taboption(\"advanced\", Value, \"rr_ttl\", translate(\"Domain TTL\"), translate(\"TTL for all domain result.\"))\no.rempty      = true\n\n---- rr-ttl-min\no = s:taboption(\"advanced\", Value, \"rr_ttl_min\", translate(\"Domain TTL Min\"), translate(\"Minimum TTL for all domain result.\"))\no.rempty      = true\no.placeholder = \"600\"\no.default     = 600\no.optional    = true\n\n---- rr-ttl-max\no = s:taboption(\"advanced\", Value, \"rr_ttl_max\", translate(\"Domain TTL Max\"), translate(\"Maximum TTL for all domain result.\"))\no.rempty      = true\n\n---- rr-ttl-reply-max\no = s:taboption(\"advanced\", Value, \"rr_ttl_reply_max\", translate(\"Reply Domain TTL Max\"), translate(\"Reply maximum TTL for all domain result.\"))\no.rempty      = true\n\no = s:taboption(\"advanced\", DynamicList, \"conf_files\", translate(\"Include Config Files<br>/etc/smartdns/conf.d\"),\n    translate(\"Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page.\"))\no.rmempty = true\nuci:foreach(\"smartdns\", \"download-file\", function(section)\n    local filetype = section.type\n    if (filetype ~= 'config') then\n        return\n    end\n\n    o:value(section.name);\nend)\n\no = s:taboption(\"advanced\", DynamicList, \"hosts_files\", translate(\"Hosts File\"), translate(\"Include hosts file.\"))\no.rmempty = true\nuci:foreach(\"smartdns\", \"download-file\", function(section)\n    local filetype = section.type\n    if (filetype ~= 'other') then\n        return\n    end\n\n    o:value(section.name);\nend)\n\n---- other args\no = s:taboption(\"advanced\", Value, \"server_flags\", translate(\"Additional Server Args\"), translate(\"Additional server args, refer to the help description of the bind option.\"))\no.default     = \"\"\no.rempty      = true\no.optional    = true\n\n---- second dns server\n---- Eanble\no = s:taboption(\"seconddns\", Flag, \"seconddns_enabled\", translate(\"Enable\"), translate(\"Enable or disable second DNS server.\"))\no.default     = o.disabled\no.rempty      = false\n\n---- Port\no = s:taboption(\"seconddns\", Value, \"seconddns_port\", translate(\"Local Port\"), translate(\"Smartdns local server port\"))\no.placeholder = 6553\no.default     = 6553\no.datatype    = \"port\"\no.rempty      = false\n\n---- Enable TCP server\no = s:taboption(\"seconddns\", Flag, \"seconddns_tcp_server\", translate(\"TCP Server\"), translate(\"Enable TCP DNS Server\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"1\"\nend\n\n---- dns server group\no = s:taboption(\"seconddns\", Value, \"seconddns_server_group\", translate(\"Server Group\"), translate(\"Query DNS through specific dns server group, such as office, home.\"))\no.rmempty     = true\no.placeholder = \"default\"\no.datatype    = \"hostname\"\no.rempty      = true\n\no = s:taboption(\"seconddns\", Flag, \"seconddns_no_speed_check\", translate(\"Skip Speed Check\"), translate(\"Do not check speed.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- skip address rules\no = s:taboption(\"seconddns\", Flag, \"seconddns_no_rule_addr\", translate(\"Skip Address Rules\"), translate(\"Skip address rules.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- skip name server rules\no = s:taboption(\"seconddns\", Flag, \"seconddns_no_rule_nameserver\", translate(\"Skip Nameserver Rule\"), translate(\"Skip nameserver rules.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- skip ipset rules\no = s:taboption(\"seconddns\", Flag, \"seconddns_no_rule_ipset\", translate(\"Skip Ipset Rule\"), translate(\"Skip ipset rules.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- skip soa address rule\no = s:taboption(\"seconddns\", Flag, \"seconddns_no_rule_soa\", translate(\"Skip SOA Address Rule\"), translate(\"Skip SOA address rules.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\no = s:taboption(\"seconddns\", Flag, \"seconddns_no_dualstack_selection\", translate(\"Skip Dualstack Selection\"), translate(\"Skip Dualstack Selection.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- skip cache\no = s:taboption(\"seconddns\", Flag, \"seconddns_no_cache\", translate(\"Skip Cache\"), translate(\"Skip Cache.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- Force AAAA SOA\no = s:taboption(\"seconddns\", Flag, \"seconddns_force_aaaa_soa\", translate(\"Force AAAA SOA\"), translate(\"Force AAAA SOA.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\no = s:taboption(\"seconddns\", Flag, \"seconddns_force_https_soa\", translate(\"Force HTTPS SOA\"), translate(\"Force HTTPS SOA.\"))\no.rmempty     = false\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\no = s:taboption(\"seconddns\", Flag, \"seconddns_no_ip_alias\", translate(\"Skip IP Alias\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\no = s:taboption(\"seconddns\", Value, \"seconddns_ipset_name\", translate(\"IPset Name\"), translate(\"IPset name.\"))\no.rmempty = true\no.datatype = \"string\"\no.rempty = true\n\no = s:taboption(\"seconddns\", Value, \"seconddns_nftset_name\", translate(\"NFTset Name\"), translate(\"NFTset name, format: [#[4|6]:[family#table#set]]\"))\no.rmempty = true\no.datatype = \"string\"\no.rempty = true\nfunction o.validate(self, value) \n    if (value == \"\") then\n        return value\n    end\n\n    if (value:match(\"#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$\")) then\n        return value\n    end\n\n    return nil, translate(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\")\nend\n\n---- other args\no = s:taboption(\"seconddns\", Value, \"seconddns_server_flags\", translate(\"Additional Server Args\"),  translate(\"Additional server args, refer to the help description of the bind option.\"))\no.default     = \"\"\no.rempty      = true\no.optional    = true\n\n----- Proxy server settings\no = s:taboption(\"proxy\", Value, \"proxy_server\", translate(\"Proxy Server\"), translate(\"Proxy Server URL, format: [socks5|http]://user:pass@ip:port.\"));\no.datatype = 'string';\nfunction o.validate(self, value)\n    if (value == \"\") then\n        return true\n    end\n\n    if (not value:match(\"^http://\") and not value:match(\"^socks5://\")) then\n        return nil, translate(\"Proxy server URL format error, format: [socks5|http]://user:pass@ip:port.\")\n    end\n\n    return value\nend\n\n----- dns64 server settings\no = s:taboption(\"dns64\", Value, \"dns64\", translate(\"DNS64\"));\no.placeholder = \"64:ff9b::/96\"\no.datatype = 'ip6addr'\no.rmempty = true\n\n----- custom settings\ncustom = s:taboption(\"custom\", Value, \"Custom Settings\",\n\ttranslate(\"\"), \n\ttranslate(\"smartdns custom settings\"))\n\ncustom.template = \"cbi/tvalue\"\ncustom.rows = 20\n\nfunction custom.cfgvalue(self, section)\n\treturn nixio.fs.readfile(\"/etc/smartdns/custom.conf\")\nend\n\nfunction custom.write(self, section, value)\n\tvalue = value:gsub(\"\\r\\n?\", \"\\n\")\n\tnixio.fs.writefile(\"/etc/smartdns/custom.conf\", value)\nend\n\no = s:taboption(\"custom\", Flag, \"coredump\", translate(\"Generate Coredump\"), translate(\"Generate Coredump file when smartdns crash, coredump file is located at /tmp/smartdns.xxx.core.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\no = s:taboption(\"custom\", ListValue, \"log_level\", translate(\"Log Level\"))\no.rmempty     = true\no.placeholder = \"default\"\no:value(\"\", translate(\"default\"))\no:value(\"debug\")\no:value(\"info\")\no:value(\"notice\")\no:value(\"warn\")\no:value(\"error\")\no:value(\"fatal\")\no:value(\"off\")\n\no = s:taboption(\"custom\", ListValue, \"log_output_mode\", translate(\"Log Output Mode\"));\no.rmempty = true;\no.placeholder = translate(\"file\");\no:value(\"file\", translate(\"file\"));\no:value(\"syslog\", translate(\"syslog\"));\n\no = s:taboption(\"custom\", Value, \"log_size\", translate(\"Log Size\"));\no.rmempty = true;\no.placeholder = \"default\";\no:depends(\"log_output_mode\", \"file\");\n\no = s:taboption(\"custom\", Value, \"log_num\", translate(\"Log Number\"));\no.rmempty = true;\no.placeholder = \"default\";\no:depends(\"log_output_mode\", \"file\");\n\no = s:taboption(\"custom\", Value, \"log_file\", translate(\"Log File\"))\no.rmempty = true\no.placeholder = \"/var/log/smartdns/smartdns.log\"\no:depends(\"log_output_mode\", \"file\");\n\no = s:taboption(\"custom\", Flag, \"enable_audit_log\", translate(\"Enable Audit Log\"));\no.rmempty = true;\no.default = o.disabled;\no.rempty = true;\n\no = s:taboption(\"custom\", ListValue, \"audit_log_output_mode\", translate(\"Audit Log Output Mode\"));\no.rmempty = true;\no.placeholder = translate(\"file\");\no:value(\"file\", translate(\"file\"));\no:value(\"syslog\", translate(\"syslog\"));\no:depends(\"enable_audit_log\", \"1\");\n\no = s:taboption(\"custom\", Value, \"audit_log_size\", translate(\"Audit Log Size\"));\no.rmempty = true;\no.placeholder = \"default\";\no:depends({enable_audit_log = \"1\", audit_log_output_mode = \"file\"});\n\no = s:taboption(\"custom\", Value, \"audit_log_num\", translate(\"Audit Log Number\"));\no.rmempty = true;\no.placeholder = \"default\";\no:depends({enable_audit_log = \"1\", audit_log_output_mode = \"file\"});\n\no = s:taboption(\"custom\", Value, \"audit_log_file\", translate(\"Audit Log File\"))\no.rmempty = true\no.placeholder = \"/var/log/smartdns/smartdns-audit.log\"\no:depends({enable_audit_log = \"1\", audit_log_output_mode = \"file\"});\n\n-- Upstream servers\ns = m:section(TypedSection, \"server\", translate(\"Upstream Servers\"), translate(\"Upstream Servers, support UDP, TCP protocol. \" ..\n\t\"Please configure multiple DNS servers, including multiple foreign DNS servers.\"))\n\t\ns.anonymous = true\ns.addremove = true\ns.template = \"cbi/tblsection\"\ns.extedit  = luci.dispatcher.build_url(\"admin/services/smartdns/upstream/%s\")\n\n---- enable flag\no = s:option(Flag, \"enabled\", translate(\"Enable\"), translate(\"Enable\"))\no.rmempty     = false\no.default     = o.enabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"1\"\nend\n\n---- name\ns:option(Value, \"name\", translate(\"DNS Server Name\"), translate(\"DNS Server Name\"))\n\n---- IP address\no = s:option(Value, \"ip\", translate(\"ip\"), translate(\"DNS Server ip\"))\no.datatype = \"or(ipaddr, string)\"\no.rmempty = false \n---- port\no = s:option(Value, \"port\", translate(\"port\"), translate(\"DNS Server port\"))\no.placeholder = \"default\"\no.datatype    = \"port\"\no.rempty      = true\no:depends(\"type\", \"udp\")\no:depends(\"type\", \"tcp\")\no:depends(\"type\", \"tls\")\n\n---- type\no = s:option(ListValue, \"type\", translate(\"type\"), translate(\"DNS Server type\"))\no.placeholder = \"udp\"\no:value(\"udp\", translate(\"udp\"))\no:value(\"tcp\", translate(\"tcp\"))\no:value(\"tls\", translate(\"tls\"))\no:value(\"https\", translate(\"https\"))\no.default     = \"udp\"\no.rempty      = false\n\n-- client rules;\ns = m:section(TypedSection, \"client-rule\", translate(\"Client Rules\"), translate(\"Client Rules Settings, can achieve parental control functionality.\"))\ns.anonymous = true;\ns.nodescriptions = true;\n\ns:tab(\"basic\", translate('Basic Settings'))\ns:tab(\"advanced\", translate('Advanced Settings'))\ns:tab(\"block\", translate(\"DNS Block Setting\"))\n\no = s:taboption(\"basic\", Flag, \"enabled\", translate(\"Enable\"))\no.rmempty = false;\no.default = o.disabled;\n\no = s:taboption(\"basic\", DynamicList, \"client_addr\", translate(\"Client Address\"), \ntranslate(\"If a client address is specified, only that client will apply this rule. You can enter an IP address, such as 1.2.3.4, or a MAC address, such as aa:bb:cc:dd:ee:ff.\"))\no.rempty = true\no.rmempty = true;\no.datatype = \"string\"\n\no = s:taboption(\"basic\", FileUpload, \"client_addr_file\", translate(\"Client Address File\"),\n    translate(\"Upload client address file, same as Client Address function.\"))\no.rmempty = true\no.datatype = \"file\"\no.rempty = true\no.root_directory = \"/etc/smartdns/ip-set\"\n\no = s:taboption(\"basic\", Value, \"server_group\", translate(\"Server Group\"), translate(\"DNS Server group belongs to, such as office, home.\"))\no.rmempty = true\no.placeholder = \"default\"\no.datatype = \"hostname\"\no.rempty = true\nuci:foreach(\"smartdns\", \"server\", function(section)\n    local server_group = section.server_group\n    if server_group == nil then\n        return\n    end\n    o:value(server_group);\nend)\n\nfunction o.validate (section_id, value) \n    if value == \"\" then\n        return value\n    end\n\n    if value == nil then\n        return nil, translate('Server Group not exists')\n    end\n\n    local exists = false\n    uci:foreach(\"smartdns\", \"server\", function(section)\n        local server_group = section.server_group\n        if (exists == true) then\n            return\n        end\n\n        if (value == server_group) then\n            exists = true\n        end\n    end)\n\n    if exists == false then\n        return nil, translate('Server Group not exists')\n    end\n\n    return value;\n\nend\n\n-- Speed check mode;\no = s:taboption(\"advanced\", Value, \"speed_check_mode\", translate(\"Speed Check Mode\"), translate(\"Smartdns speed check mode.\"))\no.rmempty = true;\no.placeholder = \"default\";\no:value(\"\", translate(\"default\"))\no:value(\"ping,tcp:80,tcp:443\");\no:value(\"ping,tcp:443,tcp:80\");\no:value(\"tcp:80,tcp:443,ping\");\no:value(\"tcp:443,tcp:80,ping\");\no:value(\"none\", translate(\"None\"));\nfunction o.validate (section_id, value) \n    if value == \"\" then\n        return value\n    end\n\n    if value == nil then\n        return nil, translate(\"Speed check mode is invalid.\")\n    end\n\n    if value == \"none\" then\n        return value\n    end\n\n    local mode = value:split(\",\");\n    for _, v in ipairs(mode) do repeat\n        if v == \"ping\" then\n            break\n        end\n\n        if v == nil then\n            return nil, translate(\"Speed check mode is invalid.\")\n        end\n        \n        local port = v:split(\":\");\n        if \"tcp\" == port[1] then\n            if tonumber(port[2]) then\n                break\n            end\n        end\n        \n        return nil, translate(\"Speed check mode is invalid.\")\n    until true end\n\n    return value\nend\n\n-- Support DualStack ip selection;\no = s:taboption(\"advanced\", Flag, \"dualstack_ip_selection\", translate(\"Dual-stack IP Selection\"),\n    translate(\"Enable IP selection between IPV4 and IPV6\"))\no.rmempty = false\no.default = o.enabled\n\n-- Force AAAA SOA\no = s:taboption(\"advanced\", Flag, \"force_aaaa_soa\", translate(\"Force AAAA SOA\"), translate(\"Force AAAA SOA.\"))\no.rmempty = true\no.default = o.disabled\n\n-- Force HTTPS SOA\no = s:taboption(\"advanced\", Flag, \"force_https_soa\", translate(\"Force HTTPS SOA\"), translate(\"Force HTTPS SOA.\"))\no.rmempty = false\no.default = o.enabled\n\n-- ipset name\no = s:taboption(\"advanced\", Value, \"ipset_name\", translate(\"IPset Name\"), translate(\"IPset name.\"))\no.rmempty = true\no.datatype = \"string\"\no.rempty = true\n\n-- NFTset name\no = s:taboption(\"advanced\", Value, \"nftset_name\", translate(\"NFTset Name\"), translate(\"NFTset name, format: [#[4|6]:[family#table#set]]\"))\no.rmempty = true\no.datatype = \"string\"\no.rempty = true\nfunction o.validate(self, value) \n    if (value == \"\") then\n        return value\n    end\n\n    if (value:match(\"#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$\")) then\n        return value\n    end\n\n    return nil, translate(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\")\nend\n\n-- include config\no = s:taboption(\"advanced\", DynamicList, \"conf_files\", translate(\"Include Config Files<br>/etc/smartdns/conf.d\"),\n    translate(\"Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page.\"))\no.rmempty = true\nuci:foreach(\"smartdns\", \"download-file\", function(section)\n    local filetype = section.type\n    if (filetype ~= 'config') then\n        return\n    end\n\n    o:value(section.name);\nend)\n\no = s:taboption(\"block\", FileUpload, \"block_domain_set_file\", translate(\"Domain List File\"), translate(\"Upload domain list file.\"));\no.rmempty = true\no.datatype = \"file\"\no.rempty = true\no.editable = true\no.root_directory = \"/etc/smartdns/domain-set\"\n\n---- domain rules;\ns = m:section(TypedSection, \"domain-rule\", translate(\"Domain Rules\"), translate(\"Domain Rules Settings\"))\ns.anonymous = true\ns.nodescriptions = true\n\ns:tab(\"forwarding\", translate('DNS Forwarding Setting'))\ns:tab(\"block\", translate(\"DNS Block Setting\"))\ns:tab(\"domain-address\", translate(\"Domain Address\"), translate(\"Set Specific domain ip address.\"))\ns:tab(\"ip-alias\", translate('IP Alias Setting'))\ns:tab(\"blackip-list\", translate(\"IP Blacklist\"), translate(\"Set Specific ip blacklist.\"))\n\n---- domain forwarding;\no = s:taboption(\"forwarding\", Value, \"server_group\", translate(\"Server Group\"), translate(\"DNS Server group belongs to, such as office, home.\"))\no.rmempty = true\no.placeholder = \"default\"\no.datatype = \"hostname\"\no.rempty = true\nuci:foreach(\"smartdns\", \"server\", function(section)\n    local server_group = section.server_group\n    if server_group == nil then\n        return\n    end\n    o:value(server_group);\nend)\n\nfunction o.validate (section_id, value) \n    if value == \"\" then\n        return value\n    end\n\n    if value == nil then\n        return nil, translate('Server Group not exists')\n    end\n\n    local exists = false\n    uci:foreach(\"smartdns\", \"server\", function(section)\n        local server_group = section.server_group\n        if (exists == true) then\n            return\n        end\n\n        if (value == server_group) then\n            exists = true\n        end\n    end)\n\n    if exists == false then\n        return nil, translate('Server Group not exists')\n    end\n\n    return value;\n\nend\n\no = s:taboption(\"forwarding\", Value, \"speed_check_mode\", translate(\"Speed Check Mode\"), translate(\"Smartdns speed check mode.\"))\no.rmempty = true;\no.placeholder = \"default\";\no:value(\"\", translate(\"default\"))\no:value(\"ping,tcp:80,tcp:443\");\no:value(\"ping,tcp:443,tcp:80\");\no:value(\"tcp:80,tcp:443,ping\");\no:value(\"tcp:443,tcp:80,ping\");\no:value(\"none\", translate(\"None\"));\nfunction o.validate (section_id, value) \n    if value == \"\" then\n        return value\n    end\n\n    if value == nil then\n        return nil, translate(\"Speed check mode is invalid.\")\n    end\n\n    if value == \"none\" then\n        return value\n    end\n\n    local mode = value:split(\",\");\n    for _, v in ipairs(mode) do repeat\n        if v == \"ping\" then\n            break\n        end\n\n        if v == nil then\n            return nil, translate(\"Speed check mode is invalid.\")\n        end\n        \n        local port = v:split(\":\");\n        if \"tcp\" == port[1] then\n            if tonumber(port[2]) then\n                break\n            end\n        end\n        \n        return nil, translate(\"Speed check mode is invalid.\")\n    until true end\n\n    return value\nend\n\no = s:taboption(\"forwarding\", Flag, \"force_aaaa_soa\", translate(\"Force AAAA SOA\"), translate(\"Force AAAA SOA.\"))\no.rmempty = true\no.default = o.disabled\n\no = s:taboption(\"forwarding\", Value, \"ipset_name\", translate(\"IPset Name\"), translate(\"IPset name.\"))\no.rmempty = true\no.datatype = \"hostname\"\no.rempty = true\n\no = s:taboption(\"forwarding\", Value, \"nftset_name\", translate(\"NFTset Name\"), translate(\"NFTset name, format: [#[4|6]:[family#table#set]]\"))\no.rmempty = true\no.datatype = \"string\"\no.rempty = true\nfunction o.validate(self, value) \n    if (value == \"\") then\n        return value\n    end\n\n    if (value:match(\"#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$\")) then\n        return value\n    end\n\n    return nil, translate(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\")\nend\n\n---- other args\no = s:taboption(\"forwarding\", Value, \"addition_flag\", translate(\"Additional Rule Flag\"), translate(\"Additional Flags for rules, read help on domain-rule for more information.\"))\no.default = \"\"\no.rempty = true\no.modalonly = true;\n\no = s:taboption(\"forwarding\", FileUpload, \"forwarding_domain_set_file\", translate(\"Domain List File\"),\n    translate(\"Upload domain list file, or configure auto download from Download File Setting page.\"))\no.rmempty = true\no.datatype = \"file\"\no.rempty = true\no.editable = true\no.root_directory = \"/etc/smartdns/domain-set\"\n\no = s:taboption(\"forwarding\", TextValue, \"domain_forwarding_list\",\n    translate(\"Domain List\"), translate(\"Configure forwarding domain name list.\"))\no.rows = 10\no.cols = 64\no.monospace = true\nfunction o.cfgvalue(self, section)\n\treturn nixio.fs.readfile(\"/etc/smartdns/domain-forwarding.list\")\nend\nfunction o.write(self, section, value)\n\tvalue = value:gsub(\"\\r\\n?\", \"\\n\")\n\tnixio.fs.writefile(\"/etc/smartdns/domain-forwarding.list\", value)\nend\n\n---- domain block;\no = s:taboption(\"block\", FileUpload, \"block_domain_set_file\", translate(\"Domain List File\"), translate(\"Upload domain list file.\"))\no.rmempty = true\no.datatype = \"file\"\no.rempty = true\no.editable = true\no.root_directory = \"/etc/smartdns/domain-set\"\n\no = s:taboption(\"block\", TextValue, \"domain_block_list\",\n    translate(\"Domain List\"), translate(\"Configure block domain list.\"))\no.rows = 10\no.cols = 64\nfunction o.cfgvalue(self, section)\n\treturn nixio.fs.readfile(\"/etc/smartdns/domain-block.list\")\nend\nfunction o.write(self, section, value)\n\tvalue = value:gsub(\"\\r\\n?\", \"\\n\")\n\tnixio.fs.writefile(\"/etc/smartdns/domain-block.list\", value)\nend\n\n-- Doman addresss\naddr = s:taboption(\"domain-address\", Value, \"dummy_address\",\n\ttranslate(\"\"), \n\ttranslate(\"Specify an IP address to return for any host in the given domains, Queries in the domains are never forwarded and always replied to with the specified IP address which may be IPv4 or IPv6.\"))\n\naddr.template = \"cbi/tvalue\"\naddr.rows = 20\n\nfunction addr.cfgvalue(self, section)\n\treturn nixio.fs.readfile(\"/etc/smartdns/address.conf\")\nend\n\nfunction addr.write(self, section, value)\n\tvalue = value:gsub(\"\\r\\n?\", \"\\n\")\n\tnixio.fs.writefile(\"/etc/smartdns/address.conf\", value)\nend\n\n---- ip rules;\ns = m:section(TypedSection, \"ip-rule\", translate(\"IP Rules\"), translate(\"IP Rules Settings\"))\ns.anonymous = true\ns.nodescriptions = true\n\ns:tab(\"ip-alias\", translate('IP Alias Setting'))\ns:tab(\"blackip-list\", translate(\"IP Blacklist\"), translate(\"Set Specific ip blacklist.\"))\n\n-- enable flag;\no = s:taboption(\"ip-alias\", Flag, \"enabled\", translate(\"Enable\"), translate(\"Enable\"));\no.rmempty = false;\no.default = o.enabled;\no.editable = true;\n\n-- name;\no = s:taboption(\"ip-alias\", Value, \"name\", translate(\"IP Rule Name\"), translate(\"IP Rule Name\"));\no.rmempty = true;\no.datatype = \"string\";\n\no = s:taboption(\"ip-alias\", FileUpload, \"ip_set_file\", translate(\"IP Set File\"), translate(\"Upload IP set file.\"));\no.rmempty = true\no.datatype = \"file\"\no.modalonly = true;\no.root_directory = \"/etc/smartdns/ip-set\"\n\no = s:taboption(\"ip-alias\", DynamicList, \"ip_addr\", translate(\"IP Addresses\"), translate(\"IP addresses, CIDR format.\"));\no.rmempty = true;\no.datatype = \"ipaddr\"\no.modalonly = true;\n\no = s:taboption(\"ip-alias\", Flag, \"whitelist_ip\", translate(\"Whitelist IP\"), translate(\"Whitelist IP Rule, Accept IP addresses within the range.\"));\no.rmempty = true;\no.default = o.disabled;\no.modalonly = true;\n\no = s:taboption(\"ip-alias\", Flag, \"blacklist_ip\", translate(\"Blacklist IP\"), translate(\"Blacklist IP Rule, Decline IP addresses within the range.\"));\no.rmempty = true;\no.default = o.disabled;\no.modalonly = true;\n\no = s:taboption(\"ip-alias\", Flag, \"ignore_ip\", translate(\"Ignore IP\"), translate(\"Do not use these IP addresses.\"));\no.rmempty = true;\no.default = o.disabled;\no.modalonly = true;\n\no = s:taboption(\"ip-alias\", Flag, \"bogus_nxdomain\", translate(\"Bogus nxdomain\"), translate(\"Return SOA when the requested result contains a specified IP address.\"));\no.rmempty = true;\no.default = o.disabled;\no.modalonly = true;\n\no = s:taboption(\"ip-alias\", DynamicList, \"ip_alias\", translate(\"IP alias\"), translate(\"IP Address Mapping, Can be used for CDN acceleration with Anycast IP, such as Cloudflare's CDN.\"));\no.rmempty = true;\no.datatype = 'ipaddr(\"nomask\")';\no.modalonly = true;\n\n-- other args\no = s:taboption(\"ip-alias\", Value, \"addition_flag\", translate(\"Additional Rule Flag\"), translate(\"Additional Flags for rules, read help on ip-rule for more information.\"))\no.default = \"\"\no.rempty = true\no.modalonly = true;\n\n-- IP Blacklist\naddr = s:taboption(\"blackip-list\", Value, \"dummy_blacklist_ip\", \n    translate(\"\"), \n    translate(\"Configure IP blacklists that will be filtered from the results of specific DNS server.\"))\n\naddr.template = \"cbi/tvalue\"\naddr.rows = 20\n\nfunction addr.cfgvalue(self, section)\n\treturn nixio.fs.readfile(\"/etc/smartdns/blacklist-ip.conf\")\nend\n\nfunction addr.write(self, section, value)\n\t-- value = value:gsub(\"\\r\\n?\", \"\\n\")\n\tnixio.fs.writefile(\"/etc/smartdns/blacklist-ip.conf\", value)\nend\n\ns = m:section(TypedSection, \"smartdns\", translate(\"Download Files Setting\"), translate(\"Download domain list files for domain-rule and include config files, please refresh the page after download to take effect.\"))\ns.anonymous = true\n\n---- download Files Settings\no = s:option(Flag, \"enable_auto_update\", translate(\"Enable Auto Update\"), translate(\"Enable daily(week) auto update.\"))\no.rmempty = true\no.default = o.disabled\no.rempty = true\n\no = s:option(ListValue, \"auto_update_week_time\", translate(\"Update Time (Every Week)\"))\no:value(\"*\", translate(\"Every Day\"))\no:value(\"1\", translate(\"Every Monday\"))\no:value(\"2\", translate(\"Every Tuesday\"))\no:value(\"3\", translate(\"Every Wednesday\"))\no:value(\"4\", translate(\"Every Thursday\"))\no:value(\"5\", translate(\"Every Friday\"))\no:value(\"6\", translate(\"Every Saturday\"))\no:value(\"0\", translate(\"Every Sunday\"))\no.default = \"*\"\n\no = s:option(ListValue, \"auto_update_day_time\", translate(\"Update Time (Every Day)\"))\nfor i = 0, 23 do o:value(i, i .. \":00\") end\no.default = 5\n\no = s:option(FileUpload, \"upload_conf_file\", translate(\"Upload Config File\"),\n    translate(\"Upload smartdns config file to /etc/smartdns/conf.d\"))\no.rmempty = true\no.datatype = \"file\"\no.rempty = true\no.editable = true\no.root_directory = \"/etc/smartdns/conf.d\"\n\no = s:option(FileUpload, \"upload_list_file\", translate(\"Upload Domain List File\"),\n    translate(\"Upload domain list file to /etc/smartdns/domain-set\"))\no.rmempty = true\no.datatype = \"file\"\no.rempty = true\no.editable = true\no.root_directory = \"/etc/smartdns/domain-set\"\n\no = s:option(FileUpload, \"upload_other_file\", translate(\"Upload File\"))\no.rmempty = true\no.datatype = \"file\"\no.rempty = true\no.editable = true\no.root_directory = \"/etc/smartdns/download\"\n\no = s:option(Button, \"_updateate\")\no.title = translate(\"Update Files\")\no.inputtitle = translate(\"Update Files\")\no.inputstyle = \"apply\"\no.write = function()\n\tluci.sys.call(\"/etc/init.d/smartdns updatefiles >/dev/null 2>&1\")\nend\n\ns = m:section(TypedSection, \"download-file\", translate(\"Download Files\"), translate(\"List of files to download.\"))\ns.anonymous = true\ns.addremove = true\ns.template = \"cbi/tblsection\"\n\no = s:option(Value, 'name', translate('File Name'), translate('File Name'))\no.rmempty = true\no.datatype = 'string'\n\no = s:option(Value, 'url', translate('URL'), translate('URL'))\no.rmempty = true\no.datatype = 'string'\nfunction o.validate(self, value, section)\n    if value == \"\" then\n        return nil, translate(\"URL format error, format: http:// or https://\")\n    end\n\n    if value == nil then\n        return nil, translate(\"URL format error, format: http:// or https://\")\n    end\n\n    if value.find(value, \"http://\") then\n        return value\n    end\n\n    if value.find(value, \"https://\") then\n        return value\n    end\n\n    return nil, translate(\"URL format error, format: http:// or https://\")\nend\n\no = s:option(ListValue, \"type\", translate(\"type\"), translate(\"File Type\"))\no:value(\"list\", translate(\"domain list (/etc/smartdns/domain-set)\"))\no:value(\"config\", translate(\"smartdns config (/etc/smartdns/conf.d)\"))\no:value(\"ip-set\", translate(\"ip-set file (/etc/smartdns/ip-set)\"))\no:value(\"other\", translate(\"other file (/etc/smartdns/download)\"))\no.default = \"list\"\no.rempty = false\n\no = s:option(Value, 'desc', translate('Description'), translate('Description'))\no.rmempty = true\no.datatype = 'string'\n\n-- Technical Support\ns = m:section(TypedSection, \"smartdns\", translate(\"Technical Support\"), \n\ttranslate(\"If you like this software, please buy me a cup of coffee.\"))\ns.anonymous = true\n\no = s:option(Button, \"web\")\no.title = translate(\"SmartDNS official website\")\no.inputtitle = translate(\"open website\")\no.inputstyle = \"apply\"\no.write = function()\n\tluci.http.redirect(\"https://pymumu.github.io/smartdns\")\nend\n\no = s:option(Button, \"report\")\no.title = translate(\"Report bugs\")\no.inputtitle = translate(\"Report bugs\")\no.inputstyle = \"apply\"\no.write = function()\n\tluci.http.redirect(\"https://github.com/pymumu/smartdns/issues\")\nend\n\no = s:option(Button, \"Donate\")\no.title = translate(\"Donate to smartdns\")\no.inputtitle = translate(\"Donate\")\no.inputstyle = \"apply\"\no.write = function()\n\tluci.http.redirect(\"https://pymumu.github.io/smartdns/#donate\")\nend\n\no = s:option(Button, \"Restart\")\no.title = translate(\"Restart Service\")\no.inputtitle = translate(\"Restart\")\no.inputstyle = \"apply\"\no.write = function()\n\tluci.sys.call(\"/etc/init.d/smartdns restart >/dev/null 2>&1\")\nend\n\nreturn m\n"
  },
  {
    "path": "package/luci-compat/files/luci/model/cbi/smartdns/upstream.lua",
    "content": "--\n-- Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n--\n-- smartdns is free software: you can redistribute it and/or modify\n-- it under the terms of the GNU General Public License as published by\n-- the Free Software Foundation, either version 3 of the License, or\n-- (at your option) any later version.\n--\n-- smartdns is distributed in the hope that it will be useful,\n-- but WITHOUT ANY WARRANTY; without even the implied warranty of\n-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n-- GNU General Public License for more details.\n--\n-- You should have received a copy of the GNU General Public License\n-- along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nlocal sid = arg[1]\n\nm = Map(\"smartdns\", \"%s - %s\" %{translate(\"SmartDNS Server\"), translate(\"Upstream DNS Server Configuration\")})\nm.redirect = luci.dispatcher.build_url(\"admin/services/smartdns\")\n\nif m.uci:get(\"smartdns\", sid) ~= \"server\" then\n\tluci.http.redirect(m.redirect)\n\treturn\nend\n\n-- [[ Edit Server ]]--\ns = m:section(NamedSection, sid, \"server\")\ns.anonymous = true\ns.addremove   = false\n\n---- name\ns:option(Value, \"name\", translate(\"DNS Server Name\"), translate(\"DNS Server Name\"))\n\n---- IP address\no = s:option(Value, \"ip\", translate(\"ip\"), translate(\"DNS Server ip\"))\no.datatype = \"or(host, string)\"\no.rmempty = false \n---- port\no = s:option(Value, \"port\", translate(\"port\"), translate(\"DNS Server port\"))\no.placeholder = \"default\"\no.datatype    = \"port\"\no.rempty      = true\no:depends(\"type\", \"udp\")\no:depends(\"type\", \"tcp\")\no:depends(\"type\", \"tls\")\n\n---- type\no = s:option(ListValue, \"type\", translate(\"type\"), translate(\"DNS Server type\"))\no.placeholder = \"udp\"\no:value(\"udp\", translate(\"udp\"))\no:value(\"tcp\", translate(\"tcp\"))\no:value(\"tls\", translate(\"tls\"))\no:value(\"https\", translate(\"https\"))\no.default     = \"udp\"\no.rempty      = false\n\n---- server group\no = s:option(Value, \"server_group\", translate(\"Server Group\"), translate(\"DNS Server group belongs to, such as office, home.\"))\no.rmempty     = true\no.placeholder = \"default\"\no.datatype    = \"hostname\"\no.rempty      = true\n\n---- exclude default group\no = s:option(Flag, \"exclude_default_group\", translate(\"Exclude Default Group\"), translate(\"Exclude DNS Server from default group.\"))\no.rmempty = true\no.default = o.disabled\no.editable = true\no.modalonly = true\n\n---- blacklist_ip\no = s:option(Flag, \"blacklist_ip\", translate(\"IP Blacklist Filtering\"), translate(\"Filtering IP with blacklist\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\n\n---- TLS host verify\no = s:option(Value, \"tls_host_verify\", translate(\"TLS Hostname Verify\"), translate(\"Set TLS hostname to verify.\"))\no.default     = \"\"\no.datatype    = \"string\"\no.rempty      = true\no:depends(\"type\", \"tls\")\no:depends(\"type\", \"https\")\n\n---- certificate verify\no = s:option(Flag, \"no_check_certificate\", translate(\"No check certificate\"), translate(\"Do not check certificate.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\no:depends(\"type\", \"tls\")\no:depends(\"type\", \"https\")\n\n---- SNI host name\no = s:option(Value, \"host_name\", translate(\"TLS SNI name\"), translate(\"Sets the server name indication for query.\"))\no.default     = \"\"\no.datatype    = \"hostname\"\no.rempty      = true\no:depends(\"type\", \"tls\")\no:depends(\"type\", \"https\")\n\n---- http host\no = s:option(Value, \"http_host\", translate(\"HTTP Host\"), translate(\"Set the HTTP host used for the query. Use this parameter when the host of the URL address is an IP address.\"))\no.default     = \"\"\no.datatype    = \"hostname\"\no.rempty      = true\no:depends(\"type\", \"https\")\n\n---- anti-Answer-Forgery\n-- o = s:option(Flag, \"check_edns\", translate(\"Anti Answer Forgery\"), translate(\"Anti answer forgery, if DNS does not work properly after enabling, please turn off this feature\"))\n-- o.rmempty     = false\n-- o.default     = o.disabled\n-- o:depends(\"type\", \"udp\")\n-- o.cfgvalue    = function(...)\n--     return Flag.cfgvalue(...) or \"0\"\n-- end\n\n---- SPKI pin\no = s:option(Value, \"spki_pin\", translate(\"TLS SPKI Pinning\"), translate(\"Used to verify the validity of the TLS server, The value is Base64 encoded SPKI fingerprint, leaving blank to indicate that the validity of TLS is not verified.\"))\no.default     = \"\"\no.datatype    = \"string\"\no.rempty      = true\no:depends(\"type\", \"tls\")\no:depends(\"type\", \"https\")\n\n---- mark\no = s:option(Value, \"set_mark\", translate(\"Marking Packets\"), translate(\"Set mark on packets.\"))\no.default     = \"\"\no.rempty      = true\no.datatype    = \"uinteger\"\n\n---- use proxy\no = s:option(Flag, \"use_proxy\", translate(\"Use Proxy\"), translate(\"Use proxy to connect to upstream DNS server.\"))\no.rmempty     = true\no.default     = o.disabled\no.cfgvalue    = function(...)\n    return Flag.cfgvalue(...) or \"0\"\nend\nfunction o.validate(self, value, section)\n    if value == \"1\" then\n        local proxy = m.uci:get_first(\"smartdns\", \"smartdns\", \"proxy_server\")\n        if proxy == nil or proxy == \"\" then\n            return nil, translate(\"Please set proxy server first.\")\n        end\n    end\n    return value\nend\n\n---- other args\no = s:option(Value, \"addition_arg\", translate(\"Additional Server Args\"), translate(\"Additional Args for upstream dns servers\"))\no.default     = \"\"\no.rempty      = true\n\nreturn m"
  },
  {
    "path": "package/luci-compat/files/luci/model/smartdns.lua",
    "content": "--\n-- Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n--\n-- smartdns is free software: you can redistribute it and/or modify\n-- it under the terms of the GNU General Public License as published by\n-- the Free Software Foundation, either version 3 of the License, or\n-- (at your option) any later version.\n--\n-- smartdns is distributed in the hope that it will be useful,\n-- but WITHOUT ANY WARRANTY; without even the implied warranty of\n-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n-- GNU General Public License for more details.\n--\n-- You should have received a copy of the GNU General Public License\n-- along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nrequire (\"nixio.fs\")\nrequire (\"luci.http\")\nrequire (\"luci.dispatcher\")\nrequire (\"nixio.fs\")\n\nlocal uci = require \"luci.model.uci\".cursor()\n\nmodule(\"luci.model.smartdns\", package.seeall)\n\nfunction get_config_option(module, section, option, default)\n\treturn uci:get_first(module, section, option) or default\nend\n\nreturn m\n\n"
  },
  {
    "path": "package/luci-compat/files/luci/view/smartdns/smartdns_status.htm",
    "content": "<script type=\"text/javascript\">//<![CDATA[\nXHR.poll(3, '<%=luci.dispatcher.build_url(\"admin\", \"services\", \"smartdns\", \"status\")%>', null,\n\tfunction(x, data) {\n\t\tvar tb = document.getElementById('smartdns_status');\n\t\tif (data && tb) {\n\t\t\tvar links = \"\";\n\t\t\tif (data.running) {\n\t\t\t\tlinks = '<b><font color=green>SmartDNS - <%:RUNNING%></font></b></em>';\n\t\t\t\tif (data.dnsmasq_redirect_failure == 1) {\n\t\t\t\t\tlinks += \"<br></br><b><font color=red><%:Dnsmasq Forwarded To Smartdns Failure%></font></b>\"\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlinks = '<b><font color=red>SmartDNS - <%:NOT RUNNING%></font></b>';\n\t\t\t}\n\n\t\t\ttb.innerHTML = links;\n\t\t}\n\t}\n);\n//]]>\n</script>\n<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>\n<fieldset class=\"cbi-section\">\n\t<p id=\"smartdns_status\">\n\t\t<em><%:Collecting data...%></em>\n\t</p>\n</fieldset>\n"
  },
  {
    "path": "package/luci-compat/files/usr/share/rpcd/acl.d/luci-app-smartdns.json",
    "content": "{\n\t\"luci-app-smartdns\": {\n\t\t\"description\": \"Grant access to LuCI app smartdns\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/smartdns/*\": [ \"read\" ]\n\t\t\t},\n\t\t\t\"ubus\": {\n\t\t\t\t\"service\": [ \"list\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"smartdns\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/smartdns/*\": [ \"write\" ],\n\t\t\t\t\"/etc/init.d/smartdns restart\": [ \"exec\" ],\n\t\t\t\t\"/etc/init.d/smartdns updatefiles\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"smartdns\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "package/luci-compat/make.sh",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\nCURR_DIR=$(cd $(dirname $0);pwd)\n\nVER=\"`date +\"1.%Y.%m.%d-%H%M\"`\"\nSMARTDNS_DIR=$CURR_DIR/../../\nPO2LMO=\n\nshowhelp()\n{\n\techo \"Usage: make [OPTION]\"\n\techo \"Options:\"\n\techo \" -o               output directory.\"\n\techo \" --arch           archtecture.\"\n\techo \" --ver            version.\"\n\techo \" -h               show this message.\"\n}\n\nbuild_tool()\n{\n\tmake -C $ROOT/tool/po2lmo -j \n\tPO2LMO=\"$ROOT/tool/po2lmo/src/po2lmo\"\n\n}\n\nclean_tool()\n{\n\tmake -C $ROOT/tool/po2lmo clean\n}\n\nbuild()\n{\n\n\tROOT=/tmp/luci-app-smartdns\n\trm -fr $ROOT\n\n\tmkdir -p $ROOT\n\tcp $CURR_DIR/* $ROOT/ -af\n\tcp $CURR_DIR/../tool $ROOT/ -af\n\tcd $ROOT/\n\tbuild_tool\n\tmkdir $ROOT/root/usr/lib/lua/ -p\n\tcp $ROOT/files/luci $ROOT/root/usr/lib/lua/ -af\n\tcp $ROOT/files/usr $ROOT/root/ -af\n\t\n\t#Generate Language\n\t$PO2LMO $ROOT/files/luci/i18n/smartdns.zh-cn.po $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.lmo\n\trm $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.po\n\n\tcp $ROOT/files/etc $ROOT/root/ -af\n\tINST_SIZE=\"`du -sb $ROOT/root/ | awk '{print $1}'`\"\n\t\n\tsed -i \"s/^Architecture.*/Architecture: all/g\" $ROOT/control/control\n\tsed -i \"s/Version:.*/Version: $VER/\" $ROOT/control/control\n\n\tif [ ! -z \"$INST_SIZE\" ]; then\n\t\techo \"Installed-Size: $INST_SIZE\" >> $ROOT/control/control\n\tfi\n\n\tcd $ROOT/control\n\tchmod +x *\n\ttar zcf ../control.tar.gz ./\n\tcd $ROOT\n\n\ttar zcf $ROOT/data.tar.gz -C root .\n\ttar zcf $OUTPUTDIR/luci-app-smartdns.$VER.$FILEARCH.ipk ./control.tar.gz ./data.tar.gz ./debian-binary\n\n\trm -fr $ROOT/\n}\n\nmain()\n{\n\tOPTS=`getopt -o o:h --long arch:,ver:,filearch: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--arch)\n\t\t\tARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--filearch)\n\t\t\tFILEARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--ver)\n\t\t\tVER=\"$2\"\n\t\t\tshift 2;;\n\t\t-o )\n\t\t\tOUTPUTDIR=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n\t\tesac\n\tdone\n\n\tif [ -z \"$ARCH\" ]; then\n\t\techo \"please input arch.\"\n\t\treturn 1;\n\tfi\n\n\tif [ -z \"$FILEARCH\" ]; then\n\t\tFILEARCH=$ARCH\n\tfi\n\n\tif [ -z \"$OUTPUTDIR\" ]; then\n\t\tOUTPUTDIR=$CURR_DIR;\n\tfi\n\n\tbuild\n}\n\nmain $@\nexit $?\n\n\n"
  },
  {
    "path": "package/luci-lite/control/conffiles",
    "content": "/etc/config/smartdns-lite\n"
  },
  {
    "path": "package/luci-lite/control/control",
    "content": "Package: luci-app-smartdns-lite\nVersion: git-18.201.27126-7bf0367-1\nDepends: libc, smartdns\nSource: feeds/luci/applications/luci-app-smartdns-lite\nSection: luci\nArchitecture: all\nDescription:  A smartdns server for china\n"
  },
  {
    "path": "package/luci-lite/control/postinst",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n[ \"${IPKG_NO_SCRIPT}\" = \"1\" ] && exit 0\n[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_postinst $0 $@\nret=$?\n/etc/init.d/smartdns-lite clear_rules\n/etc/init.d/smartdns-lite enable\nexit 0"
  },
  {
    "path": "package/luci-lite/control/prerm",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_prerm $0 $@\n/etc/init.d/smartdns-lite clear_rules\n/etc/init.d/smartdns-lite disable\nrm /var/etc/smartdns-lite.conf -f\nexit 0"
  },
  {
    "path": "package/luci-lite/debian-binary",
    "content": "2.0\n"
  },
  {
    "path": "package/luci-lite/files/luci/i18n/smartdns-lite.zh-cn.po",
    "content": "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n\nmsgid \"A local SmartDNS server for lite users.\"\nmsgstr \"为入门用户提供的本地SmartDNS服务器。\"\n\nmsgid \"AD Block Domain List File\"\nmsgstr \"广告屏蔽域名列表文件\"\n\nmsgid \"Basic Settings\"\nmsgstr \"基础设置\"\n\nmsgid \"Block Domain List File for Parental Control.\"\nmsgstr \"家长控制功能屏蔽的域名列表文件。\"\n\nmsgid \"Client Address\"\nmsgstr \"客户端地址\"\n\nmsgid \"Client Address File\"\nmsgstr \"客户端地址文件\"\n\nmsgid \"Client address format error, please input ip adress or mac address.\"\nmsgstr \"客户端地址格式错误，请输入IP地址或MAC地址。\"\n\nmsgid \"CloudFlare CDN IP File\"\nmsgstr \"CloudFlare CDN IP文件\"\n\nmsgid \"CloudFlare CDN IP Settings\"\nmsgstr \"CloudFlare CDN IP设置\"\n\nmsgid \"Collecting data ...\"\nmsgstr \"正在收集数据...\"\n\nmsgid \"Custom Settings\"\nmsgstr \"自定义设置\"\n\nmsgid \"DNS Server Mode\"\nmsgstr \"DNS服务器模式\"\n\nmsgid \"DNS Server Port\"\nmsgstr \"DNS服务器端口号\"\n\nmsgid \"Dnsmasq Forwarded To Smartdns Failure\"\nmsgstr \"设置smartdns为dnsmasq上游失败\"\n\nmsgid \"Dnsmasq Upstream Server\"\nmsgstr \"Dnsmasq上游服务器\"\n\nmsgid \"Domain List File\"\nmsgstr \"域名列表文件\"\n\nmsgid \"Domain Rules Settings\"\nmsgstr \"域名规则设置\"\n\nmsgid \"Enable\"\nmsgstr \"启用\"\n\nmsgid \"Enable or disable cloudflare cdn ip accelerating.\"\nmsgstr \"启用或禁用cloudflare CDN IP加速。\"\n\nmsgid \"Enable or disable domain rules.\"\nmsgstr \"启用或禁用域名规则。\"\n\nmsgid \"Enable or disable smartdns server\"\nmsgstr \"启用或禁用smartdns服务器\"\n\nmsgid \"Force AAAA SOA\"\nmsgstr \"禁止AAAA记录\"\n\nmsgid \"Force AAAA SOA.\"\nmsgstr \"强制IPV6记录返回SOA。\"\n\nmsgid \"Force HTTPS SOA\"\nmsgstr \"禁用HTTPS记录\"\n\nmsgid \"Force HTTPS SOA.\"\nmsgstr \"强制HTTPS记录返回SOA\"\n\nmsgid \"Grant access to LuCI app smartdns\"\nmsgstr \"获取访问smartdns的权限\"\n\nmsgid \"IP Address Mapping, mapping all CloudFlare CDN IPs to the specified IP, can be used to accelerate CloudFlare's CDN websites.\"\nmsgstr \"IP地址映射，将所有CloudFlare CDN IP映射到设置的IP，可用于加速CloudFlare的CDN网站。\"\n\nmsgid \"IP alias\"\nmsgstr \"IP别名\"\n\nmsgid \"IPset Name\"\nmsgstr \"IPSet名称\"\n\nmsgid \"IPset name.\"\nmsgstr \"IPSet名称。\"\n\nmsgid \"\"\n\"If a client address is specified, only that client will apply this rule. You \"\n\"can enter an IP address, such as 1.2.3.4, or a MAC address, such as aa:bb:cc:\"\n\"dd:ee:ff.\"\nmsgstr \"如果指定了客户端，那么对应的客户端会应用相应的规则，可以输入IP地址，如：1.2.3.4，或MAC地址，如：aa:bb:cc:dd:ee:ff。\"\n\nmsgid \"Invalid server address: %s\"\nmsgstr \"无效的服务器地址：%s\"\n\nmsgid \"Main DNS Server\"\nmsgstr \"主DNS服务\"\n\nmsgid \"NFTset Name\"\nmsgstr \"NFTset名称\"\n\nmsgid \"NFTset name format error, format: [#[4|6]:[family#table#set]]\"\nmsgstr \"NFTset名称格式错误，格式：[#[4|6]:[family#table#set]]\"\n\nmsgid \"NFTset name, format: [#[4|6]:[family#table#set]]\"\nmsgstr \"NFTSet名称错误，格式：[#[4|6]:[family#table#set]]\"\n\nmsgid \"NOT RUNNING\"\nmsgstr \"未运行\"\n\nmsgid \"None\"\nmsgstr \"无\"\n\nmsgid \"Parental Control Domain File\"\nmsgstr \"家长控制域名列表文件\"\n\nmsgid \"Parental Control Settings\"\nmsgstr \"家长控制设置\"\n\nmsgid \"Parental Control Upstream Server\"\nmsgstr \"家长控制上游服务器\"\n\nmsgid \"Parental control feature is only available in Main DNS mode.\"\nmsgstr \"家长控制功能仅在作为主DNS时才可用。\"\n\nmsgid \"Please check the system logs and check if the configuration is valid.\"\nmsgstr \"请检查系统日志，并确保配置正确。\"\n\nmsgid \"RUNNING\"\nmsgstr \"运行中\"\n\nmsgid \"Restart\"\nmsgstr \"重启\"\n\nmsgid \"Restart Service\"\nmsgstr \"重启服务\"\n\nmsgid \"Set the IP addresses for accelerating CloudFlare CDN.\"\nmsgstr \"设置CloudFlare cdn加速。\"\n\nmsgid \"Set the file for blocking ad domain names.\"\nmsgstr \"设置屏蔽的域名。\"\n\nmsgid \"Settings\"\nmsgstr \"设置\"\n\nmsgid \"SmartDNS\"\nmsgstr \"SmartDNS\"\n\nmsgid \"SmartDNS official website\"\nmsgstr \"SmartDNS官方网站。\"\n\nmsgid \"SmartDNS Lite\"\nmsgstr \"SmartDNS轻量版\"\n\nmsgid \"Smartdns server mode.\"\nmsgstr \"smartdns服务器模式。\"\n\nmsgid \"Smartdns server port.\"\nmsgstr \"smartdns服务器端口。\"\n\nmsgid \"Speed check mode for matching domains.\"\nmsgstr \"匹配域名的测速模式。\"\n\nmsgid \"Speed Check Mode\"\nmsgstr \"测速模式\"\n\nmsgid \"Speed check mode is invalid.\"\nmsgstr \"测速模式无效。\"\n\nmsgid \"TCP port is empty\"\nmsgstr \"TCP端口为空\"\n\nmsgid \"TPROXY Server Port\"\nmsgstr \"TPROXY服务器端口\"\n\nmsgid \"TPROXY server port used for forwarding data requests, please make sure this port has enabled TPROXY service.\"\nmsgstr \"用于转发数据请求的TPROXY服务器端口，请确保该端口已启用TPROXY服务，否则链接可能不正常。\"\n\nmsgid \"Use Internal IP Rules\"\nmsgstr \"使用内置IP规则\"\n\nmsgid \"Use internal IP rules to forward data to TPROXY service when the domain matches, avoiding the need to configure IP rules.\"\nmsgstr \"当域名匹配时，使用内置IP规则将数据转发到TPROXY服务，避免复杂的IP规则配置。\"\n\nmsgid \"Upload CloudFlare cdn ip list file, please refer to https://www.cloudflare.com/ips\"\nmsgstr \"上传CloudFlare CDN IP列表文件，请参考https://www.cloudflare.com/ips\"\n\nmsgid \"Upload domain list file for matching these rules, if not specified, the rules will be applied to all domains.\"\nmsgstr \"上传域名列表文件以匹配规则，未设置任何域名时，规则将应用到所有域名。\"\n\nmsgid \"Upstream DNS Server\"\nmsgstr \"上游DNS服务器\"\n\nmsgid \"Upstream Server\"\nmsgstr \"上游服务器\"\n\nmsgid \"Upstream server for specific domain. If not specified, the default server will be used.\"\nmsgstr \"指定对应域名的上游服务器，未指定服务器时，将使用默认的服务器。\"\n\nmsgid \"\"\n\"Upstream server with parental control feature. If not specified, the default \"\n\"server will be used.\"\nmsgstr \"启用家长控制且未配置上游服务器时，将使用默认服务器。\"\n\nmsgid \"Upstream servers, format: [udp://|tcp://|tls://|https://][ip].\"\nmsgstr \"上游服务器，格式：[udp://|tcp://|tls://|https://][ip]。\"\n\nmsgid \"default\"\nmsgstr \"默认\"\n\nmsgid \"ipset name format error, format: [#[4|6]:]ipsetname\"\nmsgstr \"ipset名称格式错误，格式为：[#[4|6]:]ipsetname\"\n\nmsgid \"open website\"\nmsgstr \"打开网站\"\n\nmsgid \"smartdns custom settings\"\nmsgstr \"smartdns自定义设置\"\n"
  },
  {
    "path": "package/luci-lite/files/root/etc/config/smartdns-lite",
    "content": "config 'smartdns-lite'\n\toption 'enabled' '0'\n\t"
  },
  {
    "path": "package/luci-lite/files/root/etc/init.d/smartdns-lite",
    "content": "#!/bin/sh /etc/rc.common\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nSTART=19\nSTOP=82\nNAME=smartdns-lite\nUSE_PROCD=1\n\nSMARTDNS_CONF_DIR=\"/etc/smartdns\"\nSMARTDNS_CONF_DOWNLOAD_DIR=\"$SMARTDNS_CONF_DIR/conf.d\"\nSMARTDNS_VAR_CONF_DIR=\"/var/etc/smartdns\"\nSMARTDNS_CONF=\"$SMARTDNS_VAR_CONF_DIR/smartdns-lite.conf\"\nCUSTOM_CONF=\"$SMARTDNS_CONF_DIR/custom.conf\"\nSMARTDNS_CONF_TMP=\"${SMARTDNS_CONF}.tmp\"\nEXTRA_COMMANDS=\"clear_rules\"\nEXTRA_HELP=\"        clear_rules      clear all rules\"\n\nconf_append()\n{\n\techo \"$1 $2\" >> $SMARTDNS_CONF_TMP\n}\n\nclient_rule_addr_append()\n{\n\tconf_append \"client-rules\" \"$1\"\n}\n\nservers_append()\n{\n\tconf_append \"server\" \"$1 $server_options\"\n}\n\nsetup_tproxy_rules()\n{\n\tlocal tproxy_port=\"$1\"\n\tlocal table_type=\"$2\"\n\n\tip rule add fwmark 1104 lookup 981\n\tip route add local 0.0.0.0/0 dev lo table 981\n\tip -6 route add local ::/0 dev lo table 981\n\n\tif [ \"$table_type\" = \"iptable\" ]; then\n\t\tiptables -t mangle -N SMARTDNS_LITE\n\t\tiptables -t mangle -A SMARTDNS_LITE -p tcp -m set --match-set smartdns dst -j TPROXY --on-ip 127.0.0.1 --on-port ${tproxy_port} --tproxy-mark 1104\n\t\tiptables -t mangle -A SMARTDNS_LITE -p udp -m set --match-set smartdns dst -j TPROXY --on-ip 127.0.0.1 --on-port ${tproxy_port} --tproxy-mark 1104\n\t\tiptables -t mangle -A SMARTDNS_LITE -j ACCEPT\n\t\tiptables -t mangle -A PREROUTING -j SMARTDNS_LITE\n\n\n\t\tip6tables -t mangle -N SMARTDNS_LITE\n\t\tip6tables -t mangle -A SMARTDNS_LITE -p tcp -m set --match-set smartdns6 dst -j TPROXY --on-ip ::1 --on-port ${tproxy_port} --tproxy-mark 1104\n\t\tip6tables -t mangle -A SMARTDNS_LITE -p udp -m set --match-set smartdns6 dst -j TPROXY --on-ip ::1 --on-port ${tproxy_port} --tproxy-mark 1104\n\t\tip6tables -t mangle -A SMARTDNS_LITE -j ACCEPT\n\t\tip6tables -t mangle -A PREROUTING -j SMARTDNS_LITE\n\telif [ \"$table_type\" = \"nftable\" ]; then\n\t\tnft add table ip smartdns_lite\n\t\tnft add set ip smartdns_lite ipv4 { type ipv4_addr\\; flags interval\\; auto-merge\\; }\n\t\tnft add chain ip smartdns_lite prerouting { type filter hook prerouting priority 0\\; }\n\t\tnft add rule ip smartdns_lite prerouting meta l4proto tcp ip daddr @ipv4 tproxy to 127.0.0.1:${tproxy_port} mark set 1104\n\t\tnft add rule ip smartdns_lite prerouting meta l4proto udp ip daddr @ipv4 tproxy to 127.0.0.1:${tproxy_port} mark set 1104\n\n\t\tnft add table ip6 smartdns_lite\n\t\tnft add set ip6 smartdns_lite ipv6 { type ipv6_addr\\; flags interval\\; auto-merge\\; }\n\t\tnft add chain ip6 smartdns_lite prerouting6 { type filter hook prerouting priority 0\\; }\n\t\tnft add rule ip6 smartdns_lite prerouting6 meta l4proto tcp ip6 daddr @ipv6 tproxy to ::1:${tproxy_port} mark set 1104\n\t\tnft add rule ip6 smartdns_lite prerouting6 meta l4proto udp ip6 daddr @ipv6 tproxy to ::1:${tproxy_port} mark set 1104\n\telse\n\t\techo \"table_type error\"\n\t\treturn 1\n\tfi\n}\n\nclear_tproxy_rules()\n{\n\tip rule del fwmark 1104 > /dev/null 2>&1\n\tip route flush table 981 > /dev/null 2>&1\n\tiptables -t mangle -D PREROUTING -j SMARTDNS_LITE > /dev/null 2>&1\n\tiptables -t mangle -F SMARTDNS_LITE > /dev/null 2>&1\n\tiptables -t mangle -X SMARTDNS_LITE > /dev/null 2>&1\n\tip6tables -t mangle -D PREROUTING -j SMARTDNS_LITE > /dev/null 2>&1\n\tip6tables -t mangle -F SMARTDNS_LITE > /dev/null 2>&1\n\tip6tables -t mangle -X SMARTDNS_LITE > /dev/null 2>&1\n\tnft delete table ip smartdns_lite > /dev/null 2>&1\n\tnft delete table ip6 smartdns_lite > /dev/null 2>&1\n}\n\nclear_rules()\n{\n\tclear_tproxy_rules\n}\n\nload_parental_control_rules()\n{\n\tlocal section=\"$1\"\n\tlocal adblock_set_name=\"$2\"\n\tlocal block_domain_set_file=\"\"\n\tlocal client_set_name=\"pc-client-address-$section\"\n\tlocal block_set_name=\"pc-block-domain-$section\"\n\tlocal server_options=\"-e\"\n\n\tconfig_get_bool pc_enabled \"$section\" \"pc_enabled\" \"0\"\n\t[ \"$pc_enabled\" != \"1\" ] && return\n\n\tconf_append \"group-begin\" \"parental-control-${section}\"\n\n\tconfig_get pc_client_addr_file \"$section\" \"pc_client_addr_file\" \"\"\n\t[ -e \"$pc_client_addr_file\" ] && {\n\t\tconf_append \"ip-set\" \"-name ${client_set_name} -file '$pc_client_addr_file'\"\n\t\tconf_append \"group-match\" \"-client-ip ip-set:${client_set_name}\"\n\t}\n\n\tconfig_list_foreach \"$section\" \"pc_client_addr\" client_rule_addr_append\n\n\tconfig_list_foreach \"$section\" \"pc_servers\" servers_append\n\n\tconfig_get pc_block_file \"$section\" \"pc_block_file\" \"\"\n\t[ -e \"$pc_block_file\" ] && {\n\t\tconf_append \"domain-set\" \"-name ${block_set_name} -file '$pc_block_file'\"\n\t\tconf_append \"domain-rules\" \"/domain-set:${block_set_name}/ -address #\"\n\t}\n\n\t[ ! -z \"$adblock_set_name\" ] && {\n\t\tconf_append \"domain-rules\" \"/domain-set:${adblock_set_name}/ -address #\"\n\t}\n\n\tconf_append \"group-end\"\n}\n\nload_domain_rules()\n{\n\tlocal section=\"$1\"\n\tlocal domain_set_args=\"\"\n\tlocal domain_set_name=\"rules-domain-set-$section\"\n\tlocal domain_rule_name=\"rules-domain-group-$section\"\n\tlocal as_group=0;\n\tlocal qtype_soa_list=\"\"\n\tlocal server_options=\"\"\n\n\tclear_tproxy_rules\n\n\tconfig_get_bool rules_enabled \"$section\" \"rules_enabled\" \"0\"\n\t[ \"$rules_enabled\" != \"1\" ] && return\n\n\tconfig_list_foreach \"$section\" \"rules_servers\" servers_append\n\n\tconfig_get rules_domain_file \"$section\" \"rules_domain_file\" \"\"\n\t[ -e \"$rules_domain_file\" ] && {\n\t\tconf_append \"group-begin\" \"${domain_rule_name}\"\n\t\tconf_append \"domain-set\" \"-name ${domain_set_name} -file '$rules_domain_file'\"\n\t\tconf_append \"group-match\" \"-domain domain-set:${domain_set_name}\"\n\t\tconf_append \"force-qtype-SOA\" \"-\"\n\t\tserver_options=\"-e\"\n\t\tas_group=\"1\"\n\t}\n\n\tconfig_get rules_speed_check_mode \"$section\" \"rules_speed_check_mode\" \"\"\n\t[ ! -z \"$rules_speed_check_mode\" ] && conf_append \"speed-check-mode\" \"$rules_speed_check_mode\"\n\n\tconfig_get rules_force_aaaa_soa \"$section\" \"rules_force_aaaa_soa\" \"0\"\n\t[ \"$rules_force_aaaa_soa\" = \"1\" ] && qtype_soa_list=\"$qtype_soa_list 28\"\n\n\tconfig_get rules_force_https_soa \"$section\" \"rules_force_https_soa\" \"1\"\n\t[ \"$rules_force_https_soa\" = \"1\" ] && qtype_soa_list=\"$qtype_soa_list 65\"\n\n\t[ ! -z \"$qtype_soa_list\" ] && conf_append \"force-qtype-SOA\" \"$qtype_soa_list\"\n\n\tconfig_get_bool use_internal_rules \"$section\" \"use_internal_rules\" \"0\"\n\n\t[ \"$use_internal_rules\" = \"1\" ] && {\n\t\tconfig_get tproxy_server_port \"$section\" \"tproxy_server_port\" \"\"\n\t\t[ ! -z \"$tproxy_server_port\" ] &&  {\n\t\t\twhich nft > /dev/null 2>&1\n\t\t\tif [ \"$?\" = \"0\" ]; then\n\t\t\t\ttable_type=\"nftable\"\n\t\t\t\tconf_append \"nftset\" \"#4:ip#smartdns_lite#ipv4\"\n\t\t\t\tconf_append \"nftset\" \"#6:ip6#smartdns_lite#ipv6\"\n\t\t\telse\n\t\t\t\tconf_append \"ipset\" \"SMARTDNS_LITE\"\n\t\t\t\ttable_type=\"iptable\"\n\t\t\tfi\n\t\t\tsetup_tproxy_rules \"$tproxy_server_port\" \"$table_type\"\n\t\t}\n\t} || {\n\t\tconfig_get ipset_name \"$section\" \"ipset_name\" \"\"\n\t\t[ -z \"$ipset_name\" ] || conf_append \"ipset\" \"$ipset_name\"\n\n\t\tconfig_get nftset_name \"$section\" \"nftset_name\" \"\"\n\t\t[ -z \"$nftset_name\" ] || conf_append \"nftset\" \"$nftset_name\"\n\t}\n\n\t[ \"$as_group\" = \"1\" ] && {\n\t\tconf_append \"group-end\"\n\t}\n}\n\ncloudflare_cdn_alias()\n{\n\tconf_append \"ip-alias\" \"$1 ip-set:$ipset_set_name\"\n}\n\nload_cloudflare_cdn_accelerate()\n{\n\tlocal section=\"$1\"\n\tlocal ipset_set_name=\"cloudflare-ip-set-$section\"\n\n\tconfig_get_bool cloudflare_enabled \"$section\" \"cloudflare_enabled\" \"0\"\n\t[ \"$cloudflare_enabled\" != \"1\" ] && return\n\n\tconfig_get cloudflare_cdn_ip_file \"$section\" \"cloudflare_cdn_ip_file\" \"\"\n\t[ ! -e \"$cloudflare_cdn_ip_file\" ] && return\n\n\tconf_append \"ip-set\" \"-name ${ipset_set_name} -file '$cloudflare_cdn_ip_file'\"\n\tconfig_list_foreach \"$section\" \"cloudflare_ip_alias\" cloudflare_cdn_alias\n}\n\nunload_service()\n{\n\t:\n}\n\nload_service()\n{\n\tlocal section=\"$1\"\n\targs=\"\"\n\tlocal device=\"\"\n\tlocal adblock_set_name=\"\"\n\tlocal auto_set_dnsmasq=\"0\"\n\n\tmkdir -p $SMARTDNS_VAR_CONF_DIR\n\trm -f $SMARTDNS_CONF_TMP\n\n\tconfig_get_bool enabled \"$section\" \"enabled\" '0'\n\t[ \"$enabled\" != \"1\" ] && {\n\t\tuci -q set smartdns.@smartdns[0].enabled=\"0\"\n\t\tuci -q del_list smartdns.@smartdns[0].conf_files=\"$SMARTDNS_CONF\"\n\t\tuci commit smartdns\n\t\tclear_tproxy_rules\n\t\t/etc/init.d/smartdns reload\n\t\treturn\n\t}\n\n\tconfig_get port \"$section\" \"port\" \"53\"\n\tconfig_get server_mode \"$section\" \"server_mode\" \"main\"\n\n\t[ \"$server_mode\" = \"main\" ] && {\n\t\tport=\"53\"\n\t}\n\n\t[ \"$server_mode\" = \"dnsmasq_upstream\" ] && {\n\t\tauto_set_dnsmasq=\"1\"\n\t}\n\t\n\tconfig_list_foreach \"$section\" \"servers\" servers_append\n\n\tconfig_get ad_block_file \"$section\" \"ad_block_file\" \"\"\n\t[ -e \"$ad_block_file\" ] && {\n\t\tadblock_set_name=\"adblock-block-$section\"\n\t\tconf_append \"domain-set\" \"-name ${adblock_set_name} -file '$ad_block_file'\"\n\t\tconf_append \"domain-rules\" \"/domain-set:${adblock_set_name}/ -address #\"\n\t}\n\n\tload_cloudflare_cdn_accelerate \"$section\"\n\n\tload_parental_control_rules \"$section\" \"$adblock_set_name\"\n\n\tload_domain_rules \"$section\"\n\n\tuci -q set smartdns.@smartdns[0].enabled=\"1\"\n\tuci -q set smartdns.@smartdns[0].port=\"$port\"\n\tuci -q set smartdns.@smartdns[0].auto_set_dnsmasq=\"$auto_set_dnsmasq\"\n\tuci -q del_list smartdns.@smartdns[0].conf_files=\"$SMARTDNS_CONF\"\n\tuci -q add_list smartdns.@smartdns[0].conf_files=\"$SMARTDNS_CONF\"\n\n\ttouch $SMARTDNS_CONF_TMP\n\tmv $SMARTDNS_CONF_TMP $SMARTDNS_CONF\n\tuci commit smartdns\n\t/etc/init.d/smartdns reload\n}\n\nservice_triggers() {\n\tprocd_add_reload_trigger smartdns-lite\n}\n\nservice_stopped()\n{\n\tconfig_load \"smartdns-lite\"\n\tconfig_foreach unload_service \"smartdns-lite\"\n}\n\nstart_service()\n{\n\tconfig_load \"smartdns-lite\"\n\tconfig_foreach load_service \"smartdns-lite\"\n}\n\nreload_service()\n{\n\tstop\n\tstart\n}\n"
  },
  {
    "path": "package/luci-lite/files/root/usr/share/luci/menu.d/luci-app-smartdns-lite.json",
    "content": "{\n\t\"admin/services/smartdns-lite\": {\n\t\t\"title\": \"SmartDNS Lite\",\n\t\t\"action\": {\n\t\t\t\"type\": \"view\",\n\t\t\t\"path\": \"smartdns-lite/smartdns-lite\"\n\t\t},\n\t\t\"depends\": {\n\t\t\t\"acl\": [ \"luci-app-smartdns-lite\" ],\n\t\t\t\"uci\": { \"smartdns-lite\": true }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "package/luci-lite/files/root/usr/share/rpcd/acl.d/luci-app-smartdns-lite.json",
    "content": "{\n\t\"luci-app-smartdns-lite\": {\n\t\t\"description\": \"Grant access to LuCI app smartdns\",\n\t\t\"read\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/smartdns/*\": [ \"read\" ]\n\t\t\t},\n\t\t\t\"ubus\": {\n\t\t\t\t\"service\": [ \"list\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"smartdns-lite\", \"smartdns\" ]\n\t\t},\n\t\t\"write\": {\n\t\t\t\"file\": {\n\t\t\t\t\"/etc/smartdns/*\": [ \"write\" ],\n\t\t\t\t\"/etc/init.d/smartdns-lite restart\": [ \"exec\" ],\n\t\t\t\t\"/etc/init.d/smartdns-lite updatefiles\": [ \"exec\" ]\n\t\t\t},\n\t\t\t\"uci\": [ \"smartdns-lite\", \"smartdns\" ]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "package/luci-lite/files/root/www/luci-static/resources/view/smartdns-lite/smartdns-lite.js",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n'use strict';\n'require fs';\n'require uci';\n'require form';\n'require view';\n'require poll';\n'require rpc';\n'require ui';\n\nvar conf = 'smartdns';\nvar callServiceList = rpc.declare({\n\tobject: 'service',\n\tmethod: 'list',\n\tparams: ['name'],\n\texpect: { '': {} }\n});\nvar pollAdded = false;\n\nfunction getServiceStatus() {\n\treturn L.resolveDefault(callServiceList(conf), {})\n\t\t.then(function (res) {\n\t\t\tvar is_running = false;\n\t\t\ttry {\n\t\t\t\tis_running = res[conf]['instances']['smartdns']['running'];\n\t\t\t} catch (e) {\n\n\t\t\t}\n\t\t\treturn is_running;\n\t\t})\n}\n\nfunction smartdnsServiceStatus() {\n\treturn Promise.all([\n\t\tgetServiceStatus()\n\t]);\n}\n\nfunction smartdnsRenderStatus(res) {\n\tvar renderHTML = \"\";\n\tvar isRunning = res[0];\n\n\tvar autoSetDnsmasq = uci.get_first('smartdns', 'smartdns', 'auto_set_dnsmasq');\n\tvar smartdnsPort = uci.get_first('smartdns', 'smartdns', 'port');\n\tvar smartdnsEnable = uci.get_first('smartdns', 'smartdns', 'enabled');\n\tvar dnsmasqServer = uci.get_first('dhcp', 'dnsmasq', 'server');\n\n\tif (isRunning) {\n\t\trenderHTML += \"<span style=\\\"color:green;font-weight:bold\\\">SmartDNS - \" + _(\"RUNNING\") + \"</span>\";\n\t} else {\n\t\trenderHTML += \"<span style=\\\"color:red;font-weight:bold\\\">SmartDNS - \" + _(\"NOT RUNNING\") + \"</span>\";\n\t\tif (smartdnsEnable === '1') {\n\t\t\trenderHTML += \"<br /><span style=\\\"color:red;font-weight:bold\\\">\" + _(\"Please check the system logs and check if the configuration is valid.\");\n\t\t\trenderHTML += \"</span>\";\n\t\t}\n\t\treturn renderHTML;\n\t}\n\n\tif (autoSetDnsmasq === '1' && smartdnsPort != '53') {\n\t\tvar matchLine = \"127.0.0.1#\" + smartdnsPort;\n\n\t\tuci.unload('dhcp');\n\t\tuci.load('dhcp');\n\t\tif (dnsmasqServer == undefined || dnsmasqServer.indexOf(matchLine) < 0) {\n\t\t\trenderHTML += \"<br /><span style=\\\"color:red;font-weight:bold\\\">\" + _(\"Dnsmasq Forwarded To Smartdns Failure\") + \"</span>\";\n\t\t}\n\t}\n\n\treturn renderHTML;\n}\n\nreturn view.extend({\n\tload: function () {\n\t\treturn Promise.all([\n\t\t\tuci.load('dhcp'),\n\t\t\tuci.load('smartdns'),\n\t\t\tuci.load('smartdns-lite'),\n\t\t]);\n\t},\n\trender: function (stats) {\n\t\tvar m, s, o;\n\n\t\tm = new form.Map('smartdns-lite', _('SmartDNS Lite'));\n\t\tm.title = _(\"SmartDNS Lite\");\n\t\tm.description = _(\"A local SmartDNS server for lite users.\");\n\n\t\ts = m.section(form.NamedSection, '_status');\n\t\ts.anonymous = true;\n\t\ts.render = function (section_id) {\n\t\t\tvar renderStatus = function () {\n\t\t\t\treturn L.resolveDefault(smartdnsServiceStatus()).then(function (res) {\n\t\t\t\t\tvar view = document.getElementById(\"service_status\");\n\t\t\t\t\tif (view == null) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tview.innerHTML = smartdnsRenderStatus(res);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (pollAdded == false) {\n\t\t\t\tpoll.add(renderStatus, 1);\n\t\t\t\tpollAdded = true;\n\t\t\t}\n\n\t\t\treturn E('div', { class: 'cbi-section' }, [\n\t\t\t\tE('div', { id: 'service_status' },\n\t\t\t\t\t_('Collecting data ...'))\n\t\t\t]);\n\t\t}\n\n\t\t////////////////\n\t\t// Basic;\n\t\t////////////////\n\t\ts = m.section(form.TypedSection, \"smartdns-lite\", _(\"Settings\"));\n\t\ts.anonymous = true;\n\n\t\ts.tab(\"settings\", _(\"Basic Settings\"));\n\t\ts.tab(\"parental\", _(\"Parental Control Settings\"));\n\t\ts.tab(\"rules\", _(\"Domain Rules Settings\"));\n\t\ts.tab(\"cloudflare\", _(\"CloudFlare CDN IP Settings\"), _(\"Set the IP addresses for accelerating CloudFlare CDN.\"));\n\t\ts.tab(\"custom\", _(\"Custom Settings\"));\n\n\t\to = s.taboption(\"settings\", form.Flag, \"enabled\", _(\"Enable\"), _(\"Enable or disable smartdns server\"));\n\t\to.rmempty = false;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"settings\", form.DynamicList, \"servers\", _(\"Upstream Server\"),\n\t\t\t_(\"Upstream servers, format: [udp://|tcp://|tls://|https://][ip].\"));\n\t\to.rempty = true\n\t\to.rmempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar values = value.split(/\\s+/);\n\t\t\tfor (var i = 0; i < values.length; i++) {\n\t\t\t\tif (!values[i].match(/^(https?|udp|tcp|tls|quic):\\/\\/[0-9a-zA-Z\\.\\[\\]:]+(\\/[^\\s]*)?$/)) {\n\t\t\t\t\treturn _('Invalid server address: %s').format(values[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t};\n\n\t\to = s.taboption(\"settings\", form.FileUpload, \"ad_block_file\", _(\"AD Block Domain List File\"),\n\t\t\t_(\"Set the file for blocking ad domain names.\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.editable = true\n\t\to.root_directory = \"/etc/smartdns/domain-set\"\n\n\t\to = s.taboption(\"settings\", form.ListValue, \"server_mode\", _(\"DNS Server Mode\"), _(\"Smartdns server mode.\"));\n\t\to.rmempty = false;\n\t\to.value(\"main\", _(\"Main DNS Server\"));\n\t\to.value(\"upstream\", _(\"Upstream DNS Server\"));\n\t\to.value(\"dnsmasq_upstream\", _(\"Dnsmasq Upstream Server\"));\n\n\t\to = s.taboption(\"settings\", form.Value, \"port\", _(\"DNS Server Port\"), _(\"Smartdns server port.\"));\n\t\to.rmempty = true\n\t\to.default = 6053;\n\t\to.datatype = \"port\";\n\t\to.depends(\"server_mode\", \"upstream\");\n\t\to.depends(\"server_mode\", \"dnsmasq_upstream\");\n\n\t\to = s.taboption(\"parental\", form.Flag, \"pc_enabled\", _(\"Enable\"), _(\"Enable or disable smartdns server\"));\n\t\to.rmempty = false;\n\t\to.default = o.disabled;\n\t\to.validate = function (section_id, value) {\n\t\t\tvar v = this.map.lookupOption('pc_enabled', section_id)[0];\n\t\t\tif (v.formvalue(section_id) == 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar server_mode = this.map.lookupOption('server_mode', section_id)[0];\n\t\t\tif (server_mode.formvalue(section_id) != \"main\") {\n\t\t\t\treturn _(\"Parental control feature is only available in Main DNS mode.\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\to = s.taboption(\"parental\", form.DynamicList, \"pc_client_addr\", _(\"Client Address\"),\n\t\t\t_(\"If a client address is specified, only that client will apply this rule. You can enter an IP address, such as 1.2.3.4, or a MAC address, such as aa:bb:cc:dd:ee:ff.\"));\n\t\to.rempty = true\n\t\to.rmempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value.match(/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\/([0-9]|[1-2][0-9]|3[0-2]))?$/)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value.match(/^([a-fA-F0-9]*:){1,7}[a-fA-F0-9]*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value.match(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn _(\"Client address format error, please input ip adress or mac address.\");\n\t\t}\n\n\t\to = s.taboption(\"parental\", form.DynamicList, \"pc_servers\", _(\"Parental Control Upstream Server\"),\n\t\t\t_(\"Upstream server with parental control feature. If not specified, the default server will be used.\"));\n\t\to.rempty = true\n\t\to.rmempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar values = value.split(/\\s+/);\n\t\t\tfor (var i = 0; i < values.length; i++) {\n\t\t\t\tif (!values[i].match(/^(https?|udp|tcp|tls|quic):\\/\\/[0-9a-zA-Z\\.\\[\\]:]+(\\/[^\\s]*)?$/)) {\n\t\t\t\t\treturn _('Invalid server address: %s').format(values[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t};\n\n\t\to = s.taboption(\"parental\", form.FileUpload, \"pc_block_file\", _(\"Parental Control Domain File\"),\n\t\t\t_(\"Block Domain List File for Parental Control.\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.editable = true\n\t\to.root_directory = \"/etc/smartdns/domain-set\"\n\n\t\to = s.taboption(\"rules\", form.Flag, \"rules_enabled\", _(\"Enable\"), _(\"Enable or disable domain rules.\"));\n\t\to.rmempty = false;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"rules\", form.FileUpload, \"rules_domain_file\", _(\"Domain List File\"),\n\t\t\t_(\"Upload domain list file for matching these rules, if not specified, the rules will be applied to all domains.\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.editable = true\n\t\to.root_directory = \"/etc/smartdns/domain-set\"\n\n\t\to = s.taboption(\"rules\", form.DynamicList, \"rules_servers\", _(\"Upstream Server\"), \n\t\t\t_(\"Upstream server for specific domain. If not specified, the default server will be used.\"));\n\t\to.rempty = true\n\t\to.rmempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar values = value.split(/\\s+/);\n\t\t\tfor (var i = 0; i < values.length; i++) {\n\t\t\t\tif (!values[i].match(/^(https?|udp|tcp|tls|quic):\\/\\/[0-9a-zA-Z\\.\\[\\]:]+(\\/[^\\s]*)?$/)) {\n\t\t\t\t\treturn _('Invalid server address: %s').format(values[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t};\n\n\t\to = s.taboption(\"rules\", form.Value, \"rules_speed_check_mode\", _(\"Speed Check Mode\"), _(\"Speed check mode for matching domains.\"));\n\t\to.rmempty = true;\n\t\to.placeholder = _(\"None\");\n\t\to.default = \"none\";\n\t\to.value(\"none\", _(\"None\"));\n\t\to.value(\"ping,tcp:80,tcp:443\");\n\t\to.value(\"ping,tcp:443,tcp:80\");\n\t\to.value(\"tcp:80,tcp:443,ping\");\n\t\to.value(\"tcp:443,tcp:80,ping\");\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (value == \"none\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar check_mode = value.split(\",\")\n\t\t\tfor (var i = 0; i < check_mode.length; i++) {\n\t\t\t\tif (check_mode[i] == \"ping\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (check_mode[i].indexOf(\"tcp:\") == 0) {\n\t\t\t\t\tvar port = check_mode[i].split(\":\")[1];\n\t\t\t\t\tif (port == \"\") {\n\t\t\t\t\t\treturn _(\"TCP port is empty\");\n\t\t\t\t\t}\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn _(\"Speed check mode is invalid.\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// Force AAAA SOA\n\t\to = s.taboption(\"rules\", form.Flag, \"rules_force_aaaa_soa\", _(\"Force AAAA SOA\"), _(\"Force AAAA SOA.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\t// Force HTTPS SOA\n\t\to = s.taboption(\"rules\", form.Flag, \"rules_force_https_soa\", _(\"Force HTTPS SOA\"), _(\"Force HTTPS SOA.\"));\n\t\to.rmempty = true;\n\t\to.default = o.enabled;\n\n\t\to = s.taboption(\"rules\", form.Flag, \"use_internal_rules\", _(\"Use Internal IP Rules\"), \n\t\t_(\"Use internal IP rules to forward data to TPROXY service when the domain matches, avoiding the need to configure IP rules.\"));\n\t\to.rmempty = true;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"rules\", form.Value, \"rules_ipset_name\", _(\"IPset Name\"), _(\"IPset name.\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar ipset = value.split(\",\")\n\t\t\tfor (var i = 0; i < ipset.length; i++) {\n\t\t\t\tif (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"ipset name format error, format: [#[4|6]:]ipsetname\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\t\to.depends(\"use_internal_rules\", \"0\");\n\n\t\to = s.taboption(\"rules\", form.Value, \"rules_nftset_name\", _(\"NFTset Name\"), _(\"NFTset name, format: [#[4|6]:[family#table#set]]\"));\n\t\to.rmempty = true;\n\t\to.datatype = \"string\";\n\t\to.rempty = true;\n\t\to.validate = function (section_id, value) {\n\t\t\tif (value == \"\") {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvar nftset = value.split(\",\")\n\t\t\tfor (var i = 0; i < nftset.length; i++) {\n\t\t\t\tif (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+#[a-zA-Z0-9\\-_]+$/)) {\n\t\t\t\t\treturn _(\"NFTset name format error, format: [#[4|6]:[family#table#set]]\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\t\to.depends(\"use_internal_rules\", \"0\");\n\n\t\to = s.taboption(\"rules\", form.Value, \"tproxy_server_port\", _(\"TPROXY Server Port\"), \n\t\t\t_(\"TPROXY server port used for forwarding data requests, please make sure this port has enabled TPROXY service.\"));\n\t\to.rmempty = false;\n\t\to.datatype = \"port\";\n\t\to.rempty = false;\n\t\to.depends(\"use_internal_rules\", \"1\");\n\n\t\to = s.taboption(\"cloudflare\", form.Flag, \"cloudflare_enabled\", _(\"Enable\"), \n\t\t\t_(\"Enable or disable cloudflare cdn ip accelerating.\"));\n\t\to.rmempty = false;\n\t\to.default = o.disabled;\n\n\t\to = s.taboption(\"cloudflare\", form.FileUpload, \"cloudflare_cdn_ip_file\", _(\"CloudFlare CDN IP File\"),\n\t\t\t_(\"Upload CloudFlare cdn ip list file, please refer to https://www.cloudflare.com/ips\"));\n\t\to.rmempty = true\n\t\to.datatype = \"file\"\n\t\to.rempty = true\n\t\to.modalonly = true;\n\t\to.root_directory = \"/etc/smartdns/ip-set\"\n\n\t\to = s.taboption(\"cloudflare\", form.DynamicList, \"cloudflare_ip_alias\", _(\"IP alias\"),\n\t\t \t_(\"IP Address Mapping, mapping all CloudFlare CDN IPs to the specified IP, can be used to accelerate CloudFlare's CDN websites.\"));\n\t\to.rmempty = true;\n\t\to.datatype = 'ipaddr(\"nomask\")';\n\t\to.modalonly = true;\n\n\t\t///////////////////////////////////////\n\t\t// custom settings;\n\t\t///////////////////////////////////////\n\t\to = s.taboption(\"custom\", form.TextValue, \"custom_conf\",\n\t\t\t\"\", _(\"smartdns custom settings\"));\n\t\to.rows = 20;\n\t\to.cfgvalue = function (section_id) {\n\t\t\treturn fs.trimmed('/etc/smartdns/custom.conf');\n\t\t};\n\t\to.write = function (section_id, formvalue) {\n\t\t\treturn this.cfgvalue(section_id).then(function (value) {\n\t\t\t\tif (value == formvalue) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treturn fs.write('/etc/smartdns/custom.conf', formvalue.trim().replace(/\\r\\n/g, '\\n') + '\\n');\n\t\t\t});\n\t\t};\n\n\t\to = s.taboption(\"custom\", form.Button, \"web\");\n\t\to.title = _(\"SmartDNS official website\");\n\t\to.inputtitle = _(\"open website\");\n\t\to.inputstyle = \"apply\";\n\t\to.onclick = function () {\n\t\t\twindow.open(\"https://pymumu.github.io/smartdns\", '_blank');\n\t\t};\n\n\t\to = s.taboption(\"custom\", form.DummyValue, \"_restart\", _(\"Restart Service\"));\n\t\to.renderWidget = function () {\n\t\t\treturn E('button', {\n\t\t\t\t'class': 'btn cbi-button cbi-button-apply',\n\t\t\t\t'id': 'btn_restart',\n\t\t\t\t'click': ui.createHandlerFn(this, function () {\n\t\t\t\t\treturn fs.exec('/etc/init.d/smartdns-lite', ['restart'])\n\t\t\t\t\t\t.catch(function (e) { ui.addNotification(null, E('p', e.message), 'error') });\n\t\t\t\t})\n\t\t\t}, [_(\"Restart\")]);\n\t\t}\n\t\treturn m.render();\n\t}\n});\n"
  },
  {
    "path": "package/luci-lite/make.sh",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\nCURR_DIR=$(cd $(dirname $0);pwd)\n\nVER=\"`date +\"1.%Y.%m.%d-%H%M\"`\"\nSMARTDNS_DIR=$CURR_DIR/../../\nPO2LMO=\n\nshowhelp()\n{\n\techo \"Usage: make [OPTION]\"\n\techo \"Options:\"\n\techo \" -o               output directory.\"\n\techo \" --arch           archtecture.\"\n\techo \" --ver            version.\"\n\techo \" -h               show this message.\"\n}\n\nbuild_tool()\n{\n\tmake -C $ROOT/tool/po2lmo -j \n\tPO2LMO=\"$ROOT/tool/po2lmo/src/po2lmo\"\n\n}\n\nclean_tool()\n{\n\tmake -C $ROOT/tool/po2lmo clean\n}\n\nbuild()\n{\n\n\tROOT=/tmp/luci-app-smartdns\n\trm -fr $ROOT\n\n\tmkdir -p $ROOT\n\tcp $CURR_DIR/* $ROOT/ -af\n\tcp $CURR_DIR/../tool $ROOT/ -af\n\tcd $ROOT/\n\tbuild_tool\n\t\n\tmkdir $ROOT/root/usr/lib/lua/luci -p\n\tmkdir $ROOT/root/usr/share/rpcd/acl.d/ -p\n\tcp $ROOT/files/luci/i18n $ROOT/root/usr/lib/lua/luci/ -avf\n\n\t#Generate Language\n\t$PO2LMO $ROOT/files/luci/i18n/smartdns-lite.zh-cn.po $ROOT/root/usr/lib/lua/luci/i18n/smartdns-lite.zh-cn.lmo\n\trm $ROOT/root/usr/lib/lua/luci/i18n/smartdns-lite.zh-cn.po\n\tchmod +x $ROOT/files/root/etc/init.d/smartdns-lite\n\n\tcp $ROOT/files/root/* $ROOT/root/ -avf\n\tINST_SIZE=\"`du -sb $ROOT/root/ | awk '{print $1}'`\"\n\t\n\tsed -i \"s/^Architecture.*/Architecture: all/g\" $ROOT/control/control\n\tsed -i \"s/Version:.*/Version: $VER/\" $ROOT/control/control\n\n\tif [ ! -z \"$INST_SIZE\" ]; then\n\t\techo \"Installed-Size: $INST_SIZE\" >> $ROOT/control/control\n\tfi\n\n\tcd $ROOT/control\n\tchmod +x *\n\ttar zcf ../control.tar.gz ./\n\tcd $ROOT\n\n\ttar zcf $ROOT/data.tar.gz -C root .\n\ttar zcf $OUTPUTDIR/luci-app-smartdns-lite.$VER.$FILEARCH.ipk ./control.tar.gz ./data.tar.gz ./debian-binary\n\n\twhich apk >/dev/null 2>&1\n\tif [ $? -eq 0 ]; then\n\t\tAPK_VER=\"`echo $VER | sed 's/[-]/-r/'`\"\n\t\tARCH=\"`echo $ARCH | sed 's/all/noarch/g'`\"\n\t\tapk mkpkg \\\n\t\t\t--info \"name:luci-app-smartdns-lite\" \\\n\t\t\t--info \"version:$APK_VER\" \\\n\t\t\t--info \"description:smartdns luci lite\" \\\n\t\t\t--info \"arch:$ARCH\" \\\n\t\t\t--info \"license:GPL\" \\\n\t\t\t--info \"origin: https://github.com/pymumu/smartdns.git\" \\\n\t\t\t--info \"depends:libc smartdns\" \\\n\t\t\t--script \"post-install:$ROOT/control/postinst\" \\\n\t\t\t--script \"pre-deinstall:$ROOT/control/prerm\" \\\n\t\t\t--files \"$ROOT/root/\" \\\n\t\t\t--output \"$OUTPUTDIR/luci-app-smartdns-lite.$VER.$FILEARCH.apk\"\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"build apk package failed.\"\n\t\t\trm -fr $ROOT/\n\t\t\treturn 1\n\t\tfi\n\telse\n\t\techo \"== warning: apk tool not found, skip build apk package. ==\"\n\tfi\n\n\trm -fr $ROOT/\n}\n\nmain()\n{\n\tOPTS=`getopt -o o:h --long arch:,ver:,filearch: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--arch)\n\t\t\tARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--filearch)\n\t\t\tFILEARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--ver)\n\t\t\tVER=\"$2\"\n\t\t\tshift 2;;\n\t\t-o )\n\t\t\tOUTPUTDIR=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n\t\tesac\n\tdone\n\n\tif [ -z \"$ARCH\" ]; then\n\t\techo \"please input arch.\"\n\t\treturn 1;\n\tfi\n\n\tif [ -z \"$FILEARCH\" ]; then\n\t\tFILEARCH=$ARCH\n\tfi\n\n\tif [ -z \"$OUTPUTDIR\" ]; then\n\t\tOUTPUTDIR=$CURR_DIR;\n\tfi\n\n\tbuild\n}\n\nmain $@\nexit $?\n\n\n"
  },
  {
    "path": "package/openwrt/Makefile",
    "content": "#\n# Copyright (c) 2018-2025 Nick Peng (pymumu@gmail.com)\n# This is free software, licensed under the GNU General Public License v3.\n#\n\ninclude $(TOPDIR)/rules.mk\n\nPKG_NAME:=smartdns\nPKG_VERSION:=1.2025.46.2\nPKG_RELEASE:=3\n\nPKG_SOURCE_PROTO:=git\nPKG_SOURCE_URL:=https://www.github.com/pymumu/smartdns.git\nPKG_SOURCE_VERSION:=64fc9f20fba0e14cb118fe7f145557971cafd858\nPKG_MIRROR_HASH:=skip\n\nSMARTDNS_WEBUI_VERSION:=1.0.0\nSMAETDNS_WEBUI_SOURCE_PROTO:=git\nSMARTDNS_WEBUI_SOURCE_URL:=https://github.com/pymumu/smartdns-webui.git\nSMARTDNS_WEBUI_SOURCE_VERSION:=35cbf4a1940f5dd32670c69bd5cc02437ad073e7\nSMARTDNS_WEBUI_FILE:=smartdns-webui-$(SMARTDNS_WEBUI_VERSION).tar.gz\n\nPKG_MAINTAINER:=Nick Peng <pymumu@gmail.com>\nPKG_LICENSE:=GPL-3.0-or-later\nPKG_LICENSE_FILES:=LICENSE\n\nPKG_BUILD_PARALLEL:=1\n\n# node compile is slow, so do not use it, download node manually.\n# PACKAGE_smartdns-ui:node/host\nPKG_BUILD_DEPENDS:=PACKAGE_smartdns-ui:rust/host \n\ninclude ../../lang/rust/rust-package.mk\ninclude $(INCLUDE_DIR)/package.mk\n\nMAKE_VARS += VER=$(PKG_VERSION) \nMAKE_PATH:=src\n\ndefine Package/smartdns/default\n  SECTION:=net\n  CATEGORY:=Network\n  SUBMENU:=IP Addresses and Names\n  URL:=https://www.github.com/pymumu/smartdns/\nendef\n\ndefine Package/smartdns\n  $(Package/smartdns/default)\n  TITLE:=smartdns server\n  DEPENDS:=+libpthread +libopenssl +libatomic\nendef\n\ndefine Package/smartdns/description\nSmartDNS is a local DNS server which accepts DNS query requests from local network clients,\ngets DNS query results from multiple upstream DNS servers concurrently, and returns the fastest IP to clients.\nUnlike dnsmasq's all-servers, smartdns returns the fastest IP, and encrypt DNS queries with DoT or DoH. \nendef\n\ndefine Package/smartdns/conffiles\n/etc/config/smartdns\n/etc/smartdns/address.conf\n/etc/smartdns/blacklist-ip.conf\n/etc/smartdns/custom.conf\n/etc/smartdns/domain-block.list\n/etc/smartdns/domain-forwarding.list\nendef\n\ndefine Package/smartdns/install\n\t$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d \n\t$(INSTALL_DIR) $(1)/etc/smartdns $(1)/etc/smartdns/domain-set $(1)/etc/smartdns/conf.d/\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/src/smartdns $(1)/usr/sbin/smartdns\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/package/openwrt/files/etc/init.d/smartdns $(1)/etc/init.d/smartdns\n\t$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/address.conf $(1)/etc/smartdns/address.conf\n\t$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/blacklist-ip.conf $(1)/etc/smartdns/blacklist-ip.conf\n\t$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/custom.conf $(1)/etc/smartdns/custom.conf\n\t$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/files/etc/config/smartdns $(1)/etc/config/smartdns\nendef\n\ndefine Package/smartdns-ui\n  $(Package/smartdns/default)\n  TITLE:=smartdns dashboard\n  DEPENDS:=+smartdns $(RUST_ARCH_DEPENDS)\nendef\n\ndefine Package/smartdns-ui/description\nA dashboard ui for smartdns server.\nendef\n\ndefine Package/smartdns-ui/conffiles\n/etc/config/smartdns\nendef\n\ndefine Package/smartdns-ui/install\n\t$(INSTALL_DIR) $(1)/usr/lib\n\t$(INSTALL_DIR) $(1)/etc/smartdns/conf.d/\n\t$(INSTALL_DIR) $(1)/usr/share/smartdns/wwwroot\n\t$(INSTALL_BIN) $(PKG_BUILD_DIR)/plugin/smartdns-ui/target/smartdns_ui.so $(1)/usr/lib/smartdns_ui.so\n\t$(CP) $(PKG_BUILD_DIR)/smartdns-webui/out/* $(1)/usr/share/smartdns/wwwroot\nendef\n\ndefine Build/Compile/smartdns-webui\n\twhich npm || (echo \"npm not found, please install npm first\" && exit 1)\n\tnpm install --prefix $(PKG_BUILD_DIR)/smartdns-webui/\n\tnpm run build --prefix $(PKG_BUILD_DIR)/smartdns-webui/\nendef\n\ndefine Build/Compile/smartdns-ui\n\tcargo install --force --locked bindgen-cli\n\tCARGO_BUILD_ARGS=\"$(if $(strip $(RUST_PKG_FEATURES)),--features \"$(strip $(RUST_PKG_FEATURES))\") --profile $(CARGO_PKG_PROFILE)\"\n\t+$(CARGO_PKG_VARS) CARGO_BUILD_ARGS=\"$(CARGO_BUILD_ARGS)\" CC=$(TARGET_CC) \\\n\tPATH=\"$$(PATH):$(CARGO_HOME)/bin\" \\\n\tmake -C $(PKG_BUILD_DIR)/plugin/smartdns-ui\nendef\n\ndefine Download/smartdns-webui\n\tFILE:=$(SMARTDNS_WEBUI_FILE)\n\tPROTO:=$(SMAETDNS_WEBUI_SOURCE_PROTO)\n\tURL:=$(SMARTDNS_WEBUI_SOURCE_URL)\n\tMIRROR_HASH:=b3f4f73b746ee169708f6504c52b33d9bbeb7c269b731bd7de4f61d0ad212d74\n\tVERSION:=$(SMARTDNS_WEBUI_SOURCE_VERSION)\n\tHASH:=$(SMARTDNS_WEBUI_HASH)\n\tSUBDIR:=smartdns-webui\nendef\n$(eval $(call Download,smartdns-webui))\n\nifdef CONFIG_PACKAGE_smartdns-ui\ndefine Build/Prepare\n\t$(call Build/Prepare/Default)\n\t$(TAR) -C $(PKG_BUILD_DIR)/ -xf $(DL_DIR)/$(SMARTDNS_WEBUI_FILE)\nendef\nendif\n\ndefine Build/Compile\n\t$(call Build/Compile/Default,smartdns)\nifdef CONFIG_PACKAGE_smartdns-ui\n\t$(call Build/Compile/smartdns-ui)\n\t$(call Build/Compile/smartdns-webui)\nendif\nendef\n\n$(eval $(call BuildPackage,smartdns))\n$(eval $(call RustBinPackage,smartdns-ui))\n$(eval $(call BuildPackage,smartdns-ui))\n\n"
  },
  {
    "path": "package/openwrt/address.conf",
    "content": "# Add domains which you want to force to an IP address here.\n# The example below send any host in example.com to a local webserver.\n# address /domain/[ip|-|-4|-6|#|#4|#6]\n# address /www.example.com/1.2.3.4, return ip 1.2.3.4 to client\n# address /www.example.com/-, ignore address, query from upstream, suffix 4, for ipv4, 6 for ipv6, none for all\n# address /www.example.com/#, return SOA to client, suffix 4, for ipv4, 6 for ipv6, none for all\n\n# specific ipset to domain\n# ipset /domain/[ipset|-]\n# ipset /www.example.com/block, set ipset with ipset name of block \n# ipset /www.example.com/-, ignore this domain\n\n# specific nameserver to domain\n# nameserver /domain/[group|-]\n# nameserver /www.example.com/office, Set the domain name to use the appropriate server group.\n# nameserver /www.example.com/-, ignore this domain\n"
  },
  {
    "path": "package/openwrt/blacklist-ip.conf",
    "content": "# Add IP blacklist which you want to filtering from some DNS server here.\n# The example below filtering ip from the result of DNS server which is configured with -blacklist-ip.\n# blacklist-ip [ip/subnet]\n# blacklist-ip 254.0.0.1/16"
  },
  {
    "path": "package/openwrt/control/conffiles",
    "content": "/etc/config/smartdns\n/etc/smartdns/address.conf\n/etc/smartdns/blacklist-ip.conf\n/etc/smartdns/custom.conf\n/etc/smartdns/domain-block.list\n/etc/smartdns/domain-forwarding.list\n"
  },
  {
    "path": "package/openwrt/control/control",
    "content": "Package: smartdns\nArchitecture: \nPriority: optional\nSection: net\nVersion: \nDepends: libc, libopenssl, libpthread\nMaintainer: pymumu\nSource: http://127.0.0.1/\nDescription: A smart dns server\nEnabled: yes\n"
  },
  {
    "path": "package/openwrt/control/postinst",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nchmod +x /usr/sbin/smartdns\nchmod +x /etc/init.d/smartdns\nmkdir -p /var/etc/smartdns/\n\n[ \"${IPKG_NO_SCRIPT}\" = \"1\" ] && exit 0\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_postinst $0 $@\nret=$?\n/etc/init.d/smartdns enable\nexit 0\n\n"
  },
  {
    "path": "package/openwrt/control/prerm",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n. ${IPKG_INSTROOT}/lib/functions.sh\ndefault_prerm $0 $@\n/etc/init.d/smartdns disable\nrm /var/etc/smartdns.conf -f\nrm /var/etc/smartdns/smartdns.conf -f\nif [ \"$1\" = \"remove\" ]; then\n    rm /var/run/smartdns.pid -f\n    rm /var/log/smartdns/ -fr\n    rm /etc/smartdns/smartdns.cache -f\n    rm /var/lib/smartdns/ -fr\nfi\nexit 0\n"
  },
  {
    "path": "package/openwrt/custom.conf",
    "content": "# Add custom settings here.\n# please read https://pymumu.github.io/smartdns/config/basic-config/\n"
  },
  {
    "path": "package/openwrt/debian-binary",
    "content": "2.0\n"
  },
  {
    "path": "package/openwrt/domain-block.list",
    "content": "# domain block list, one domain name per line.\n# example: block a.com, and b.com\n# a.com\n# b.com"
  },
  {
    "path": "package/openwrt/domain-forwarding.list",
    "content": "# domain forwarding list, one domain name per line.\n# example: forwarding a.com, and b.com\n# a.com\n# b.com"
  },
  {
    "path": "package/openwrt/files/etc/config/smartdns",
    "content": "config 'smartdns'\n\toption 'enabled' '0'\n\t\nconfig 'domain-rule'"
  },
  {
    "path": "package/openwrt/files/etc/init.d/smartdns",
    "content": "#!/bin/sh /etc/rc.common\n\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n\n# smartdns is free software under the GPLv3 (or later).\n# Distributed without any warranty; see the license for details.\n# Full license: http://www.gnu.org/licenses/\n\nSTART=19\nSTOP=82\nNAME=smartdns\nUSE_PROCD=1\nSERVICE_USE_PID=1\nSERVICE_WRITE_PID=1\nSERVICE_DAEMONIZE=1\nSERVICE_PID_FILE=\"/run/smartdns.pid\"\nif [ ! -d \"/run\" ]; then\n\tSERVICE_PID_FILE=\"/var/run/smartdns.pid\"\nfi\n\nSMARTDNS_DOWNLOAD_TMP_DIR=\"/tmp/smartdns-download\"\nSMARTDNS_DEFAULT_FORWARDING_FILE=\"/etc/smartdns/domain-forwarding.list\"\nSMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE=\"/etc/smartdns/domain-block.list\"\nSMARTDNS_CONF_DIR=\"/etc/smartdns\"\nSMARTDNS_CONF_DOWNLOAD_DIR=\"$SMARTDNS_CONF_DIR/conf.d\"\nSMARTDNS_DOWNLOAD_DIR=\"$SMARTDNS_CONF_DIR/download\"\nSMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR=\"$SMARTDNS_CONF_DIR/domain-set\"\nSMARTDNS_IP_SET_DOWNLOAD_DIR=\"$SMARTDNS_CONF_DIR/ip-set\"\nSMARTDNS_VAR_CONF_DIR=\"/var/etc/smartdns\"\nSMARTDNS_CONF=\"$SMARTDNS_VAR_CONF_DIR/smartdns.conf\"\nADDRESS_CONF=\"$SMARTDNS_CONF_DIR/address.conf\"\nBLACKLIST_IP_CONF=\"$SMARTDNS_CONF_DIR/blacklist-ip.conf\"\nCUSTOM_CONF=\"$SMARTDNS_CONF_DIR/custom.conf\"\nSMARTDNS_CONF_TMP=\"${SMARTDNS_CONF}.tmp\"\nEXTRA_COMMANDS=\"updatefiles\"\nEXTRA_HELP=\"        updatefiles      Update files\"\nCOREDUMP=\"0\"\nRESPAWN=\"1\"\nDO_RELOAD=\"0\"\n\nset_forward_dnsmasq()\n{\n\tlocal PORT=\"$1\"\n\taddr=\"127.0.0.1#$PORT\"\n\t# space in suffix is important\n\tOLD_SERVER=\"$(uci -q get dhcp.@dnsmasq[0].server) \"\n\tif echo \"$OLD_SERVER\" | grep \"^$addr \" >/dev/null 2>&1; then\n\t\treturn\n\tfi\n\n\tuci_batch=\"\"\n\tuci_batch=\"$uci_batch delete dhcp.@dnsmasq[0].server\\n\"\n\tuci_batch=\"$uci_batch add_list dhcp.@dnsmasq[0].server=\\\"$addr\\\"\\n\"\n\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].noresolv=1\\n\"\n\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].rebind_protection=0\\n\"\n\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].domainneeded=0\\n\"\n\techo -e \"$uci_batch\" | uci batch -q -\n\tuci commit dhcp\n\t/etc/init.d/dnsmasq reload\n}\n\nstop_forward_dnsmasq()\n{\n\tlocal OLD_PORT=\"$1\"\n\tlocal norestart=\"$2\"\n\taddr=\"127.0.0.1#$OLD_PORT\"\n\tOLD_SERVER=\"$(uci -q get dhcp.@dnsmasq[0].server) \"\n\tif ! echo \"$OLD_SERVER\" | grep \"^$addr \" >/dev/null 2>&1; then\n\t\treturn\n\tfi\n\t\n\tuci_batch=\"\"\n\tuci_batch=\"$uci_batch delete dhcp.@dnsmasq[0].server\\n\"\n\tuci_batch=\"$uci_batch delete dhcp.@dnsmasq[0].noresolv\\n\"\n\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].rebind_protection=1\\n\"\n\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].domainneeded=1\\n\"\n\techo -e \"$uci_batch\" | uci batch -q -\n\tuci commit dhcp\n\t[ \"$norestart\" != \"1\" ] && /etc/init.d/dnsmasq reload\n}\n\nset_main_dns()\n{\n\tlocal hostip\n\thostip=\"$(uci -q get network.lan.ipaddr | sed 's/\\/.*//g')\"\n\tdnsmasq_port=\"$(uci -q get dhcp.@dnsmasq[0].port)\"\n\t[ -z \"$dnsmasq_port\" ] && dnsmasq_port=\"53\"\n\t\n\t[ -z \"$hostip\" ] && return\n\n\tuci_batch=\"\"\n\t[ \"$dnsmasq_port\" = \"53\" ] && {\n\t\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].port=0\\n\"\n\t\tuci_batch=\"$uci_batch add_list dhcp.lan.dhcp_option=\\\"6,$hostip\\\"\\n\"\n\t}\n\n\t# for some third-party firmware\n\tredir_dns=\"$(uci -q get dhcp.@dnsmasq[0].dns_redirect)\"\n\t[ \"$redir_dns\" = \"1\" ] && {\n\t\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].dns_redirect=0\\n\"\n\t\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].old_dns_redirect=1\\n\"\n\t}\n\n\tif [ -z \"$uci_batch\" ]; then\n\t\treturn\n\tfi\n\n\techo -e \"$uci_batch\" | uci batch -q -\n\tuci commit dhcp\n\t/etc/init.d/dnsmasq reload\n}\n\nstop_main_dns()\n{\n\tlocal norestart=\"$1\"\n\thostip=\"$(uci -q get network.lan.ipaddr)\"\n\tdnsmasq_port=\"$(uci -q get dhcp.@dnsmasq[0].port)\"\n\tredir_dns=\"$(uci -q get dhcp.@dnsmasq[0].old_dns_redirect)\"\n\t[ \"$dnsmasq_port\" != \"0\" ] && return\n\n\tuci_batch=\"\"\n\t[ \"$redir_dns\" = \"1\" ] && {\n\t\tuci_batch=\"$uci_batch set dhcp.@dnsmasq[0].dns_redirect=1\\n\"\n\t\tuci_batch=\"$uci_batch delete dhcp.@dnsmasq[0].old_dns_redirect\\n\"\n\t}\n\tuci_batch=\"$uci_batch delete dhcp.@dnsmasq[0].port\\n\"\n\tuci_batch=\"$uci_batch del_list dhcp.lan.dhcp_option=\\\"6,$hostip\\\"\\n\"\n\techo -e \"$uci_batch\" | uci batch -q -\n\tuci commit dhcp\n\t[ \"$norestart\" != \"1\" ] && /etc/init.d/dnsmasq reload\n}\n\nclear_iptable()\n{\n\tlocal OLD_PORT=\"$1\"\n\tlocal ipv6_server=$2\n\n\twhich iptables >/dev/null 2>&1\n\t[ $? -ne 0 ] && return\n\n\tIPS=\"$(ifconfig | grep \"inet addr\" | grep -v \":127\" | grep \"Bcast\" | awk '{print $2}' | awk -F : '{print $2}')\"\n\tfor IP in $IPS\n\tdo\n\t\tiptables -t nat -D PREROUTING -p udp -d \"$IP\" --dport 53 -j REDIRECT --to-ports \"$OLD_PORT\" >/dev/null 2>&1\n\t\tiptables -t nat -D PREROUTING -p tcp -d \"$IP\" --dport 53 -j REDIRECT --to-ports \"$OLD_PORT\" >/dev/null 2>&1\n\tdone\n\n\t[ \"$ipv6_server\" = 0 ] && return\n\n\tIPS=\"$(ifconfig | grep \"inet6 addr\" | grep -v \" fe80::\" | grep -v \" ::1\" | grep \"Global\" | awk '{print $3}')\"\n\tfor IP in $IPS\n\tdo\n\t\tip6tables -t nat -D PREROUTING -p udp -d \"$IP\" --dport 53 -j REDIRECT --to-ports \"$OLD_PORT\" >/dev/null 2>&1\n\t\tip6tables -t nat -D PREROUTING -p tcp -d \"$IP\" --dport 53 -j REDIRECT --to-ports \"$OLD_PORT\" >/dev/null 2>&1\n\tdone\n}\n\nservice_triggers() {\n\tprocd_add_reload_trigger firewall\n\tprocd_add_reload_trigger smartdns\n}\n\nconf_append()\n{\n\techo \"$1 $2\" >> $SMARTDNS_CONF_TMP\n}\n\nget_tz()\n{\n\tSET_TZ=\"\"\n\n\t[ -e \"/etc/localtime\" ] && return\n\n\tfor tzfile in /etc/TZ /var/etc/TZ\n\tdo\n\t\t[ -e \"$tzfile\" ] || continue\n\t\ttz=\"$(cat $tzfile 2>/dev/null)\"\n\tdone\n\n\t[ -z \"$tz\" ] && return\n\n\tSET_TZ=$tz\n}\n\nload_server()\n{\n\tlocal section=\"$1\"\n\tlocal ADDITIONAL_ARGS=\"\"\n\tlocal DNS_ADDRESS=\"\"\n\tlocal IS_URI=\"0\"\n\n\tconfig_get_bool enabled \"$section\" \"enabled\" \"1\"\n\tconfig_get port \"$section\" \"port\" \"\"\n\tconfig_get type \"$section\" \"type\" \"udp\"\n\tconfig_get ip \"$section\" \"ip\" \"\"\n\tconfig_get tls_host_verify \"$section\" \"tls_host_verify\" \"\"\n\tconfig_get no_check_certificate \"$section\" \"no_check_certificate\" \"0\"\n\tconfig_get host_name \"$section\" \"host_name\" \"\"\n\tconfig_get http_host \"$section\" \"http_host\" \"\"\n\tconfig_get server_group \"$section\" \"server_group\" \"\"\n\tconfig_get_bool exclude_default_group \"$section\" \"exclude_default_group\" \"0\"\n\tconfig_get blacklist_ip \"$section\" \"blacklist_ip\" \"0\"\n\tconfig_get check_edns \"$section\" \"check_edns\" \"0\"\n\tconfig_get spki_pin \"$section\" \"spki_pin\" \"\"\n\tconfig_get addition_arg \"$section\" \"addition_arg\" \"\"\n\tconfig_get set_mark \"$section\" \"set_mark\" \"\"\n\tconfig_get_bool use_proxy \"$section\" \"use_proxy\" \"0\"\n\tconfig_get fallback \"$section\" \"fallback\" \"0\"\n\n\t[ \"$enabled\" = \"0\" ] && return\n\n\tif [ -z \"$ip\" ] || [ -z \"$type\" ]; then\n\t\treturn\n\tfi\n\n\tSERVER=\"server\"\n\tif [ \"$type\" = \"tcp\" ]; then\n\t\tSERVER=\"server-tcp\"\n\telif [ \"$type\" = \"tls\" ]; then\n\t\tSERVER=\"server-tls\"\n\telif [ \"$type\" = \"https\" ]; then\n\t\tSERVER=\"server-https\"\n\telif [ \"$type\" = \"quic\" ]; then\n\t\tSERVER=\"server-quic\"\n\telif [ \"$type\" = \"h3\" ]; then\n\t\tSERVER=\"server-h3\"\n\tfi\n\n\tif echo \"$ip\" | grep \"://\" >/dev/null 2>&1; then\n\t\tIS_URI=\"1\"\n\telif echo \"$ip\" | grep \":\" >/dev/null 2>&1; then\n\t\tif ! echo \"$ip\" | grep -q \"\\\\[\" >/dev/null 2>&1; then\n\t\t\tip=\"[$ip]\"\n\t\tfi\n\tfi\n\n\t[ -z \"$tls_host_verify\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -tls-host-verify $tls_host_verify\"\n\t[ \"$no_check_certificate\" = \"0\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -no-check-certificate\"\n\t[ -z \"$host_name\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -host-name $host_name\"\n\t[ -z \"$http_host\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -http-host $http_host\"\n\t[ -z \"$server_group\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -group $server_group\"\n\t[ \"$exclude_default_group\" = \"0\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -exclude-default-group\"\n\t[ \"$blacklist_ip\" = \"0\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -blacklist-ip\"\n\t[ \"$check_edns\" = \"0\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -check-edns\"\n\t[ -z \"$spki_pin\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -spki-pin $spki_pin\"\n\t[ -z \"$set_mark\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -set-mark $set_mark\"\n\t[ \"$use_proxy\" = \"0\" ] || ADDITIONAL_ARGS=\"$ADDITIONAL_ARGS -proxy default-proxy\"\n\t[ \"$fallback\" = \"1\" ] && addition_arg=\"$addition_arg -fallback\"\n\n\tif [ -z \"$port\" ] || [ \"$IS_URI\" = \"1\" ]; then\n\t\tDNS_ADDRESS=\"$ip\"\n\telse\n\t\tDNS_ADDRESS=\"$ip:$port\"\n\tfi\n\n\tconf_append \"$SERVER\" \"$DNS_ADDRESS $ADDITIONAL_ARGS $addition_arg\"\n}\n\nrestart_crond()\n{\n\t/etc/init.d/cron restart >/dev/null 2>&1\n}\n\ndisable_auto_update()\n{\n\tlocal no_restart=\"$1\"\n\tgrep \"/etc/init.d/smartdns updatefiles\" /etc/crontabs/root 1>/dev/null 2>&1\n\tif [ $? -ne 0 ]; then\n\t\treturn \n\tfi\n\n\tsed -i '\\@/etc/init.d/smartdns updatefiles@d' /etc/crontabs/root\n\n\tif [ \"$no_restart\" = \"1\" ]; then\n\t\treturn\n\tfi\n\n\trestart_crond\n}\n\nenable_auto_update()\n{\n\tgrep \"0 $auto_update_day_time * * $auto_update_week_time /etc/init.d/smartdns updatefiles\" /etc/crontabs/root 2>/dev/null\n\tif [ $? -eq 0 ]; then\n\t\treturn \n\tfi\n\n\tdisable_auto_update 1\n\techo \"0 $auto_update_day_time * * $auto_update_week_time /etc/init.d/smartdns updatefiles\" >> /etc/crontabs/root\n\trestart_crond\n}\n\nload_domain_rules()\n{\n\tlocal section=\"$1\"\n\tlocal domain_set_args=\"\"\n\tlocal domain_set_name=\"domain\"\n\tlocal block_domain_set_file=\"\"\n\n\tconfig_get server_group \"$section\" \"server_group\" \"\"\n\t[ ! -z \"$server_group\" ] && domain_set_args=\"$domain_set_args -nameserver $server_group\"\n\n\tconfig_get speed_check_mode \"$section\" \"speed_check_mode\" \"\"\n\t[ ! -z \"$speed_check_mode\" ] && domain_set_args=\"$domain_set_args -speed-check-mode $speed_check_mode\"\n\n\tconfig_get dualstack_ip_selection \"$section\" \"dualstack_ip_selection\" \"\"\n\t[ \"$dualstack_ip_selection\" = \"no\" ] && domain_set_args=\"$domain_set_args -dualstack-ip-selection no\"\n\t[ \"$dualstack_ip_selection\" = \"yes\" ] && domain_set_args=\"$domain_set_args -dualstack-ip-selection yes\"\n\n\tconfig_get_bool force_aaaa_soa \"$section\" \"force_aaaa_soa\" \"0\"\n\t[ \"$force_aaaa_soa\" = \"1\" ] && domain_set_args=\"$domain_set_args -address #6\"\n\n\tconfig_get ipset_name \"$section\" \"ipset_name\" \"\"\n\t[ ! -z \"$ipset_name\" ] && domain_set_args=\"$domain_set_args -ipset $ipset_name\"\n\n\tconfig_get nftset_name \"$section\" \"nftset_name\" \"\"\n\t[ ! -z \"$nftset_name\" ] && domain_set_args=\"$domain_set_args -nftset '$nftset_name'\"\n\n\tconfig_get addition_flag \"$section\" \"addition_flag\" \"\"\n\t[ ! -z \"$addition_flag\" ] && domain_set_args=\"$domain_set_args $addition_flag\"\n\n\tconfig_get forwarding_domain_set_file \"$section\" \"forwarding_domain_set_file\" \"\"\n\t[ ! -z \"$forwarding_domain_set_file\" ] && {\n\t\t[ ! -e \"$forwarding_domain_set_file\" ] && touch $forwarding_domain_set_file\n\t\tconf_append \"domain-set\" \"-name ${domain_set_name}-forwarding-file -file '$forwarding_domain_set_file'\"\n\t\tconf_append \"domain-rules\" \"/domain-set:${domain_set_name}-forwarding-file/ $domain_set_args\"\n\t}\n\n\t[ ! -z \"$domain_set_args\" ] && {\n\t\t[ ! -e \"$SMARTDNS_DEFAULT_FORWARDING_FILE\" ] && touch $SMARTDNS_DEFAULT_FORWARDING_FILE\n\t\tconf_append \"domain-set\" \"-name ${domain_set_name}-forwarding-list -file $SMARTDNS_DEFAULT_FORWARDING_FILE\"\n\t\tconf_append \"domain-rules\" \"/domain-set:${domain_set_name}-forwarding-list/ $domain_set_args\"\n\t}\n\n\tconfig_get block_domain_set_file \"$section\" \"block_domain_set_file\"\n\t[ ! -z \"$block_domain_set_file\" ] && {\n\t\t[ ! -e \"$block_domain_set_file\" ] && touch $block_domain_set_file\n\t\tconf_append \"domain-set\" \"-name ${domain_set_name}-block-file -file '$block_domain_set_file'\"\n\t\tconf_append \"domain-rules\" \"/domain-set:${domain_set_name}-block-file/ -address #\"\n\t}\n\n\t[ ! -e \"$SMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE\" ] && touch $SMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE\n\tconf_append \"domain-set\" \"-name ${domain_set_name}-block-list -file $SMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE\"\n\tconf_append \"domain-rules\" \"/domain-set:${domain_set_name}-block-list/ -address #\"\n}\n\nclient_rule_addr_append()\n{\n\tconf_append \"client-rules\" \"$1\"\n}\n\nload_client_rules()\n{\n\tlocal section=\"$1\"\n\tlocal client_set_args=\"\"\n\tlocal client_set_name=\"$section\"\n\tlocal block_domain_set_file=\"\"\n\n\tconfig_get_bool enabled \"$section\" \"enabled\" \"0\"\n\t[ \"$enabled\" != \"1\" ] && return\n\n\tconf_append \"group-begin\" \"client-group-${section}\"\n\n\tconfig_list_foreach \"$section\" \"client_addr\" client_rule_addr_append\n\n\tconfig_get client_addr_file \"$section\" \"client_addr_file\" \"\"\n\t[ ! -z \"$client_addr_file\" ] && {\n\t\t[ ! -e \"$client_addr_file\" ] && touch $client_addr_file\n\t\tconf_append \"ip-set\" \"-name client-rule-list-${client_set_name} -file '$client_addr_file'\"\n\t\tconf_append \"client-rules\" \"ip-set:client-rule-list-${client_set_name}\"\n\t}\n\n\tconfig_get server_group \"$section\" \"server_group\" \"\"\n\t[ ! -z \"$server_group\" ] && conf_append \"nameserver $server_group\"\n\n\tconfig_get speed_check_mode \"$section\" \"speed_check_mode\" \"\"\n\t[ ! -z \"$speed_check_mode\" ] && conf_append \"speed-check-mode\" \"$speed_check_mode\"\n\n\tconfig_get dualstack_ip_selection \"$section\" \"dualstack_ip_selection\" \"0\"\n\t[ \"$dualstack_ip_selection\" = \"0\" ] && conf_append \"dualstack-ip-selection\" \"no\"\n\n\tconfig_get force_aaaa_soa \"$section\" \"force_aaaa_soa\" \"0\"\n\t[ \"$force_aaaa_soa\" = \"1\" ] && qtype_soa_list=\"$qtype_soa_list 28\"\n\n\tconfig_get force_https_soa \"$section\" \"force_https_soa\" \"1\"\n\t[ \"$force_https_soa\" = \"1\" ] && qtype_soa_list=\"$qtype_soa_list 65\"\n\n\tconfig_get ipset_name \"$section\" \"ipset_name\" \"\"\n\t[ -z \"$ipset_name\" ] || conf_append \"ipset\" \"$ipset_name\"\n\n\tconfig_get nftset_name \"$section\" \"nftset_name\" \"\"\n\t[ -z \"$nftset_name\" ] || conf_append \"nftset\" \"$nftset_name\"\n\n\tconfig_list_foreach \"$section\" \"conf_files\" conf_append_conf_files\n\n\t[ ! -z \"$qtype_soa_list\" ] && {\n\t\tconf_append \"force-qtype-SOA\" \"-\"\n\t\tconf_append \"force-qtype-SOA\" \"$qtype_soa_list\"\n\t}\n\n\tconfig_get block_domain_set_file \"$section\" \"block_domain_set_file\" \"\"\n\t[ -e \"$block_domain_set_file\" ] && {\n\t\tconf_append \"domain-set\" \"-name client-block-file-${client_set_name} -file '$block_domain_set_file'\"\n\t\tconf_append \"domain-rules\" \"/domain-set:client-block-file-${client_set_name}/ -address #\"\n\t}\n\n\tconf_append \"group-end\"\n}\n\nload_domain_rule_list()\n{\n\tlocal section=\"$1\"\n\tlocal domain_set_args=\"\"\n\tlocal domain_set_name=\"$section\"\n\n\tconfig_get_bool enabled \"$section\" \"enabled\" \"0\"\n\t[ \"$enabled\" != \"1\" ] && return\n\n\tconfig_get server_group \"$section\" \"server_group\" \"\"\n\t[ ! -z \"$server_group\" ] && domain_set_args=\"$domain_set_args -nameserver $server_group\"\n\n\tconfig_get block_domain_type \"$section\" \"block_domain_type\" \"\"\n\t[ \"$block_domain_type\" = \"all\" ] && domain_set_args=\"$domain_set_args -address #\"\n\t[ \"$block_domain_type\" = \"ipv4\" ] && domain_set_args=\"$domain_set_args -address #4\"\n\t[ \"$block_domain_type\" = \"ipv6\" ] && domain_set_args=\"$domain_set_args -address #6\"\n\n\tconfig_get speed_check_mode \"$section\" \"speed_check_mode\" \"\"\n\t[ ! -z \"$speed_check_mode\" ] && domain_set_args=\"$domain_set_args -speed-check-mode $speed_check_mode\"\n\n\tconfig_get dualstack_ip_selection \"$section\" \"dualstack_ip_selection\" \"\"\n\t[ \"$dualstack_ip_selection\" = \"no\" ] && domain_set_args=\"$domain_set_args -dualstack-ip-selection no\"\n\t[ \"$dualstack_ip_selection\" = \"yes\" ] && domain_set_args=\"$domain_set_args -dualstack-ip-selection yes\"\n\n\tconfig_get_bool force_aaaa_soa \"$section\" \"force_aaaa_soa\" \"0\"\n\t[ \"$force_aaaa_soa\" = \"1\" ] && domain_set_args=\"$domain_set_args -address #6\"\n\n\tconfig_get ipset_name \"$section\" \"ipset_name\" \"\"\n\t[ ! -z \"$ipset_name\" ] && domain_set_args=\"$domain_set_args -ipset $ipset_name\"\n\n\tconfig_get nftset_name \"$section\" \"nftset_name\" \"\"\n\t[ ! -z \"$nftset_name\" ] && domain_set_args=\"$domain_set_args -nftset '$nftset_name'\"\n\n\tconfig_get domain_list_file \"$section\" \"domain_list_file\" \"\"\n\t[ -z \"$domain_list_file\" ] && return\n\n\tconfig_get addition_flag \"$section\" \"addition_flag\" \"\"\n\t[ ! -z \"$addition_flag\" ] && domain_set_args=\"$domain_set_args $addition_flag\"\n\t[ -z \"$domain_set_args\" ] && return\n\n\t[ ! -e \"$domain_list_file\" ] && touch $domain_list_file\n\tconf_append \"domain-set\" \"-name domain-rule-list-${domain_set_name} -file '$domain_list_file'\"\n\tconf_append \"domain-rules\" \"/domain-set:domain-rule-list-${domain_set_name}/ $domain_set_args\"\t\n}\n\nip_rule_addr_append()\n{\n\tconf_append \"ip-rules\" \"$1 $IP_set_args\"\n}\n\nload_IP_rule_list()\n{\n\tlocal section=\"$1\"\n\tlocal IP_set_args=\"\"\n\tlocal IP_set_name=\"$section\"\n\n\tconfig_get_bool enabled \"$section\" \"enabled\" \"0\"\n\t[ \"$enabled\" != \"1\" ] && return\n\n\tconfig_get ip_set_file \"$section\" \"ip_set_file\" \"\"\n\n\tconfig_get_bool whitelist_ip \"$section\" \"whitelist_ip\" \"0\"\n\t[ \"$whitelist_ip\" = \"1\" ] && IP_set_args=\"$IP_set_args -whitelist-ip\"\n\n\tconfig_get_bool blacklist_ip \"$section\" \"blacklist_ip\" \"0\"\n\t[ \"$blacklist_ip\" = \"1\" ] && IP_set_args=\"$IP_set_args -blacklist-ip\"\n\n\tconfig_get_bool ignore_ip \"$section\" \"ignore_ip\" \"0\"\n\t[ \"$ignore_ip\" = \"1\" ] && IP_set_args=\"$IP_set_args -ignore-ip\"\n\n\tconfig_get_bool bogus_nxdomain \"$section\" \"bogus_nxdomain\" \"0\"\n\t[ \"$bogus_nxdomain\" = \"1\" ] && IP_set_args=\"$IP_set_args -bogus-nxdomain\"\n\n\tconfig_get ip_alias \"$section\" \"ip_alias\" \"\"\n\t[ ! -z \"$ip_alias\" ] && {\n\t\tip_alias=\"$(echo \"$ip_alias\" | sed 's/ /,/g')\"\n\t\tIP_set_args=\"$IP_set_args -ip-alias $ip_alias\"\n\t}\n\n\tconfig_get addition_flag \"$section\" \"addition_flag\" \"\"\n\t[ ! -z \"$addition_flag\" ] && IP_set_args=\"$IP_set_args $addition_flag\"\n\t[ -z \"$IP_set_args\" ] && return\n\n\t[ ! -z \"$ip_set_file\" ] && [ -e \"$ip_set_file\" ] && {\n\t\tconf_append \"ip-set\" \"-name ip-rule-list-file-${section} -file '$ip_set_file'\"\n\t\tconf_append \"ip-rules\" \"ip-set:ip-rule-list-file-${section} $IP_set_args\"\n\t}\n\n\tconfig_list_foreach \"$section\" \"ip_addr\" ip_rule_addr_append\n}\n\nconf_append_bind()\n{\n\tlocal ADDR=\"\"\n\tlocal bind_type=\"$1\"\n\tlocal port=\"$2\"\n\tlocal devices=\"$3\"\n\tlocal device=\"\"\n\tlocal ipv6_server=\"$4\"\n\tlocal ARGS=\"$5\"\n\n\tif [ \"$ipv6_server\" = \"1\" ]; then\n\t\tADDR=\"[::]\"\n\telse\n\t\tADDR=\"\"\n\tfi\n\n\tdevices=$(echo \"$devices\" | sed 's/,/ /g')\n\t[ ! -z \"$devices\" ] && devices=\"$devices lo\"\n\t[ -z \"$devices\" ] && devices=\"-\"\n\n\tfor device in $devices; do\n\t\tdevice=\"@$device\"\n\t\t[ \"$device\" = \"@-\" ] && device=\"\"\n\t\tconf_append \"$bind_type\" \"$ADDR:$port$device $ARGS\"\n\tdone\n}\n\nload_second_server()\n{\n\tlocal section=\"$1\"\n\tlocal ARGS=\"\"\n\tlocal ADDR=\"\"\n\tlocal device=\"\"\n\n\tconfig_get_bool seconddns_enabled \"$section\" \"seconddns_enabled\" \"0\"\n\t[ \"$seconddns_enabled\" = \"0\" ] && return\n\n\tconfig_get seconddns_port \"$section\" \"seconddns_port\" \"6553\"\n\n\tconfig_get_bool seconddns_no_speed_check \"$section\" \"seconddns_no_speed_check\" \"0\"\n\t[ \"$seconddns_no_speed_check\" = \"1\" ] && ARGS=\"$ARGS -no-speed-check\"\n\n\tconfig_get seconddns_server_group \"$section\" \"seconddns_server_group\" \"\"\n\t[ -z \"$seconddns_server_group\" ] || ARGS=\"$ARGS -group $seconddns_server_group\"\n\n\tconfig_get_bool seconddns_no_rule_addr \"$section\" \"seconddns_no_rule_addr\" \"0\"\n\t[ \"$seconddns_no_rule_addr\" = \"1\" ] && ARGS=\"$ARGS -no-rule-addr\"\n\n\tconfig_get_bool seconddns_no_rule_nameserver \"$section\" \"seconddns_no_rule_nameserver\" \"0\"\n\t[ \"$seconddns_no_rule_nameserver\" = \"1\" ] && ARGS=\"$ARGS -no-rule-nameserver\"\n\n\tconfig_get_bool seconddns_no_rule_ipset \"$section\" \"seconddns_no_rule_ipset\" \"0\"\n\t[ \"$seconddns_no_rule_ipset\" = \"1\" ] && ARGS=\"$ARGS -no-rule-ipset\"\n\n\tconfig_get_bool seconddns_no_rule_soa \"$section\" \"seconddns_no_rule_soa\" \"0\"\n\t[ \"$seconddns_no_rule_soa\" = \"1\" ] && ARGS=\"$ARGS -no-rule-soa\"\n\n\tconfig_get_bool seconddns_no_dualstack_selection \"$section\" \"seconddns_no_dualstack_selection\" \"0\"\n\t[ \"$seconddns_no_dualstack_selection\" = \"1\" ] && ARGS=\"$ARGS -no-dualstack-selection\"\n\n\tconfig_get_bool seconddns_no_cache \"$section\" \"seconddns_no_cache\" \"0\"\n\t[ \"$seconddns_no_cache\" = \"1\" ] && ARGS=\"$ARGS -no-cache\"\n\n\tconfig_get_bool seconddns_force_aaaa_soa \"$section\" \"seconddns_force_aaaa_soa\" \"0\"\n\t[ \"$seconddns_force_aaaa_soa\" = \"1\" ] && ARGS=\"$ARGS -force-aaaa-soa\"\n\n\tconfig_get_bool seconddns_force_https_soa \"$section\" \"seconddns_force_https_soa\" \"0\"\n\t[ \"$seconddns_force_https_soa\" = \"1\" ] && ARGS=\"$ARGS -force-https-soa\"\n\n\tconfig_get_bool seconddns_no_ip_alias \"$section\" \"seconddns_no_ip_alias\" \"0\"\n\t[ \"$seconddns_no_ip_alias\" = \"1\" ] && ARGS=\"$ARGS -no-ip-alias\"\n\n\tconfig_get seconddns_ipset_name \"$section\" \"seconddns_ipset_name\" \"\"\n\t[ -z \"$seconddns_ipset_name\" ] || ARGS=\"$ARGS -ipset $seconddns_ipset_name\"\n\n\tconfig_get seconddns_nftset_name \"$section\" \"seconddns_nftset_name\" \"\"\n\t[ -z \"$seconddns_nftset_name\" ] || ARGS=\"$ARGS -nftset $seconddns_nftset_name\"\n\n\tconfig_get_bool bind_device \"$section\" \"bind_device\" \"0\"\n\tconfig_get bind_device_name \"$section\" \"bind_device_name\" \"${lan_device}\"\n\t[ ! -z \"$bind_device_name\" ] && [ \"$bind_device\" = \"1\" ] && device=\"${bind_device_name}\"\n\n\tconfig_get_bool \"seconddns_tcp_server\" \"$section\" \"seconddns_tcp_server\" \"1\"\n\tconfig_get ipv6_server \"$section\" \"ipv6_server\" \"1\"\n\n\tconfig_get seconddns_server_flags \"$section\" \"seconddns_server_flags\" \"\"\n\t[ -z \"$seconddns_server_flags\" ] || ARGS=\"$ARGS $seconddns_server_flags\"\n\n\tconf_append_bind \"bind\" \"$seconddns_port\" \"$device\" \"$ipv6_server\" \"$ARGS\"\n\t[ \"$seconddns_tcp_server\" = \"1\" ] && conf_append_bind \"bind-tcp\" \"$seconddns_port\" \"$device\" \"$ipv6_server\" \"$ARGS\"\n}\n\nconf_append_conf_files()\n{\n\tlocal conf_file=\"$1\"\n\n\tif [ \"$1\" != \"${1#/}\" ]; then\n\t\tfullpath=\"$1\"\n\telse \n\t\tfullpath=\"$SMARTDNS_CONF_DOWNLOAD_DIR/$conf_file\"\n\tfi\n\n\t[ -f \"$fullpath\" ] && {\n\t\tconf_append \"conf-file\" \"'$fullpath'\"\n\t}\n}\n\nconf_append_hosts_files()\n{\n\tlocal hosts_file=\"$1\"\n\n\tif [ \"$1\" != \"${1#/}\" ]; then\n\t\tfullpath=\"$1\"\n\telse \n\t\tfullpath=\"$SMARTDNS_DOWNLOAD_DIR/$hosts_file\"\n\tfi\n\n\t[ -f \"$fullpath\" ] && {\n\t\tconf_append \"hosts-file\" \"'$fullpath'\"\n\t}\n}\n\nload_service()\n{\n\tlocal section=\"$1\"\n\targs=\"\"\n\tlocal device=\"\"\n\tdnsmasq_lease_file=\"$(uci -q get dhcp.@dnsmasq[0].leasefile)\"\n\tdnsmasq_port=\"$(uci -q get dhcp.@dnsmasq[0].port)\"\n\tresolve_file=\"$(uci -q get dhcp.@dnsmasq[0].resolvfile)\"\n\tlan_device=\"$(uci -q get network.lan.device)\"\n\n\t[ -z \"$dnsmasq_lease_file\" ] && dnsmasq_lease_file=\"/tmp/dhcp.leases\"\n\t[ -z \"$dnsmasq_port\" ] && dnsmasq_port=\"53\"\n\t[ -z \"$resolve_file\" ] && resolve_file=\"/tmp/resolv.conf.d/resolv.conf.auto\"\n\n\tqtype_soa_list=\"\"\n\n\tmkdir -p $SMARTDNS_VAR_CONF_DIR\n\trm -f $SMARTDNS_CONF_TMP\n\n\tconfig_get_bool enabled \"$section\" \"enabled\" '0'\n\n\tconfig_get server_name \"$section\" \"server_name\" \"\"\n\t[ -z \"$server_name\" ] || conf_append \"server-name\" \"$server_name\"\n\n\tconfig_get coredump \"$section\" \"coredump\" \"0\"\n\t[ \"$coredump\" = \"1\" ] && COREDUMP=\"1\"\n\n\tconfig_get port \"$section\" \"port\" \"53\"\n\tconfig_get ipv6_server \"$section\" \"ipv6_server\" \"1\"\n\tconfig_get tcp_server \"$section\" \"tcp_server\" \"1\"\n\tconfig_get tls_server \"$section\" \"tls_server\" \"0\"\n\tconfig_get tls_server_port \"$section\" \"tls_server_port\" \"853\"\n\tconfig_get doh_server \"$section\" \"doh_server\" \"0\"\n\tconfig_get doh_server_port \"$section\" \"doh_server_port\" \"843\"\n\tconfig_get bind_cert \"$section\" \"bind_cert\" \"\"\n\tconfig_get bind_cert_key \"$section\" \"bind_cert_key\" \"\"\n\tconfig_get bind_cert_key_pass \"$section\" \"bind_cert_key_pass\" \"\"\n\tconfig_get server_flags \"$section\" \"server_flags\" \"\"\n\n\tconfig_get auto_update_week_time \"$section\" \"auto_update_week_time\" \"*\"\n\tconfig_get auto_update_day_time \"$section\" \"auto_update_day_time\" \"5\"\n\n\tconfig_get speed_check_mode \"$section\" \"speed_check_mode\" \"\"\n\t[ ! -z \"$speed_check_mode\" ] && conf_append \"speed-check-mode\" \"$speed_check_mode\"\n\n\tconfig_get dualstack_ip_selection \"$section\" \"dualstack_ip_selection\" \"0\"\n\t[ \"$dualstack_ip_selection\" = \"0\" ] && conf_append \"dualstack-ip-selection\" \"no\"\n\n\tconfig_get prefetch_domain \"$section\" \"prefetch_domain\" \"0\"\n\t[ \"$prefetch_domain\" = \"1\" ] && conf_append \"prefetch-domain\" \"yes\"\n\n\tconfig_get serve_expired \"$section\" \"serve_expired\" \"0\"\n\t[ \"$serve_expired\" = \"1\" ] && conf_append \"serve-expired\" \"yes\"\n\n\tconfig_get cache_size \"$section\" \"cache_size\" \"\"\n\t[ -z \"$cache_size\" ] || conf_append \"cache-size\" \"$cache_size\"\n\n\tconfig_get resolve_local_hostnames \"$section\" \"resolve_local_hostnames\" \"1\"\n\t[ \"$resolve_local_hostnames\" = \"1\" ] && conf_append \"dnsmasq-lease-file\" \"$dnsmasq_lease_file\"\n\n\tconfig_get force_aaaa_soa \"$section\" \"force_aaaa_soa\" \"0\"\n\t[ \"$force_aaaa_soa\" = \"1\" ] && qtype_soa_list=\"$qtype_soa_list 28\"\n\n\tconfig_get force_https_soa \"$section\" \"force_https_soa\" \"1\"\n\t[ \"$force_https_soa\" = \"1\" ] && qtype_soa_list=\"$qtype_soa_list 65\"\n\n\tconfig_get auto_set_dnsmasq \"$section\" \"auto_set_dnsmasq\" \"1\"\n\n\tconfig_get ipset_name \"$section\" \"ipset_name\" \"\"\n\t[ -z \"$ipset_name\" ] || conf_append \"ipset\" \"$ipset_name\"\n\n\tconfig_get nftset_name \"$section\" \"nftset_name\" \"\"\n\t[ -z \"$nftset_name\" ] || conf_append \"nftset\" \"$nftset_name\"\n\n\tconfig_get ipset_no_speed \"$section\" \"ipset_no_speed\" \"\"\n\t[ -z \"$ipset_no_speed\" ] || conf_append \"ipset-no-speed\" \"$ipset_no_speed\"\n\n\tconfig_get nftset_no_speed \"$section\" \"nftset_no_speed\" \"\"\n\t[ -z \"$nftset_no_speed\" ] || conf_append \"nftset-no-speed\" \"$nftset_no_speed\"\n\n\tconfig_get rr_ttl \"$section\" \"rr_ttl\" \"\"\n\t[ -z \"$rr_ttl\" ] || conf_append \"rr-ttl\" \"$rr_ttl\"\n\n\tconfig_get rr_ttl_min \"$section\" \"rr_ttl_min\" \"\"\n\t[ -z \"$rr_ttl_min\" ] || conf_append \"rr-ttl-min\" \"$rr_ttl_min\"\n\n\tconfig_get rr_ttl_max \"$section\" \"rr_ttl_max\" \"\"\n\t[ -z \"$rr_ttl_max\" ] || conf_append \"rr-ttl-max\" \"$rr_ttl_max\"\n\n\tconfig_get rr_ttl_reply_max \"$section\" \"rr_ttl_reply_max\" \"\"\n\t[ -z \"$rr_ttl_reply_max\" ] || conf_append \"rr-ttl-reply-max\" \"$rr_ttl_reply_max\"\n\n\tconfig_get log_size \"$section\" \"log_size\" \"64K\"\n\t[ -z \"$log_size\" ] || conf_append \"log-size\" \"$log_size\"\n\n\tconfig_get log_num \"$section\" \"log_num\" \"1\"\n\t[ -z \"$log_num\" ] || conf_append \"log-num\" \"$log_num\"\n\n\tconfig_get log_level \"$section\" \"log_level\" \"error\"\n\t[ -z \"$log_level\" ]|| conf_append \"log-level\" \"$log_level\"\n\n\tconfig_get log_file \"$section\" \"log_file\" \"\"\n\t[ -z \"$log_file\" ] || conf_append \"log-file\" \"$log_file\"\n\n\tconfig_get log_output_mode \"$section\" \"log_output_mode\" \"\"\n\t[ \"$log_output_mode\" = \"syslog\" ] && conf_append \"log-syslog\" \"yes\"\n\n\tconfig_get_bool enable_audit_log \"$section\" \"enable_audit_log\" \"0\"\n\t[ \"$enable_audit_log\" = \"1\" ] && conf_append \"audit-enable\" \"yes\"\n\n\tconfig_get audit_log_size \"$section\" \"audit_log_size\" \"64K\"\n\t[ -z \"$audit_log_size\" ] || conf_append \"audit-size\" \"$audit_log_size\"\n\n\tconfig_get audit_log_num \"$section\" \"audit_log_num\" \"1\"\n\t[ -z \"$audit_log_num\" ] || conf_append \"audit-num\" \"$audit_log_num\"\n\n\tconfig_get audit_log_file \"$section\" \"audit_log_file\" \"\"\n\t[ -z \"$audit_log_file\" ] || conf_append \"audit-file\" \"$audit_log_file\"\n\n\tconfig_get audit_log_output_mode \"$section\" \"audit_log_output_mode\" \"\"\n\t[ \"$audit_log_output_mode\" = \"syslog\" ] && conf_append \"audit-syslog\" \"yes\"\n\n\tconfig_get response_mode \"$section\" \"response_mode\" \"\"\n\t[ -z \"$response_mode\" ] || conf_append \"response-mode\" \"$response_mode\"\n\n\tconfig_get_bool enable_auto_update \"$section\" \"enable_auto_update\" \"0\"\n\t[ \"$enabled\" = \"1\" -a \"$enable_auto_update\" = \"1\" ] && enable_auto_update || disable_auto_update\n\n\tconfig_get_bool bind_device \"$section\" \"bind_device\" \"0\"\n\tconfig_get bind_device_name \"$section\" \"bind_device_name\" \"${lan_device}\"\n\t[ ! -z \"$bind_device_name\" ] && [ \"$bind_device\" = \"1\" ] && device=\"${bind_device_name}\"\n\n\tconfig_get cache_file \"$section\" \"cache_file\" \"$SMARTDNS_CONF_DIR/smartdns.cache\"\n\n\tconfig_get_bool cache_persist \"$section\" \"cache_persist\" \"0\"\n\t[ \"$cache_persist\" = \"1\" ] && {\n\t\tconf_append \"cache-persist\" \"yes\"\n\t\tconf_append \"cache-file\" \"$cache_file\"\n\t}\n\n\t[ \"$cache_persist\" = \"0\" ] && {\n\t\tconf_append \"cache-persist\" \"no\"\n\t\t[ -f \"$cache_file\" ] && rm -f \"$cache_file\"\n\t}\n\n\tconfig_get proxy_server \"$section\" \"proxy_server\" \"\"\n\t[ -z \"$proxy_server\" ] || conf_append \"proxy-server\" \"$proxy_server -name default-proxy\"\n\t\n\tconfig_get dns64 \"$section\" \"dns64\" \"\"\n\t[ -z \"$dns64\" ] || conf_append \"dns64\" \"$dns64\"\n\n\tconfig_get ddns_domain \"$section\" \"ddns_domain\" \"\"\n\t[ -z \"$ddns_domain\" ] || conf_append \"ddns-domain\" \"$ddns_domain\"\n\n\tconfig_get local_domain \"$section\" \"local_domain\" \"\"\n\t[ -z \"$local_domain\" ] || conf_append \"local-domain\" \"$local_domain\"\n\n\tconfig_get_bool mdns_lookup \"$section\" \"mdns_lookup\" \"0\"\n\t[ \"$mdns_lookup\" = \"1\" ] && conf_append \"mdns-lookup\" \"yes\"\n\n\tconfig_get redirect \"$section\" \"redirect\" \"\"\n\tconfig_get old_port \"$section\" \"old_port\" \"0\"\n\tconfig_get old_enabled \"$section\" \"old_enabled\" \"0\"\n\tconfig_get old_auto_set_dnsmasq \"$section\" \"old_auto_set_dnsmasq\" \"0\"\n\n\t[ -z \"$qtype_soa_list\" ] || conf_append \"force-qtype-SOA\" \"$qtype_soa_list\"\n\t[ -e \"$resolve_file\" ] && conf_append \"resolv-file\" \"$resolve_file\"\n\n\t# upgrade old configuration\n\tuci_batch=\"\"\n\tif [ \"$redirect\" = \"redirect\" ] || [ \"$redirect\" = \"dnsmasq-upstream\" ] || [ \"$redirect\" = \"none\" ]; then\n\t\t[ \"$redirect\" = \"redirect\" ] && {\n\t\t\tclear_iptable \"$port\"\n\t\t\tclear_iptable \"$old_port\"\n\t\t\tuci_batch=\"$uci_batch delete smartdns.@smartdns[0].port\\n\"\n\t\t\tport=\"53\"\n\t\t}\n\n\t\t[ \"$redirect\" = \"dnsmasq-upstream\" ] && {\n\t\t\tstop_forward_dnsmasq \"$port\"\n\t\t\tstop_forward_dnsmasq \"$old_port\"\n\t\t\tauto_set_dnsmasq=\"1\"\n\t\t\tuci_batch=\"$uci_batch set smartdns.@smartdns[0].auto_set_dnsmasq=\\\"1\\\"\\n\"\n\t\t}\n\n\t\t[ \"$redirect\" = \"none\" ] && {\n\t\t\tauto_set_dnsmasq=\"0\"\n\t\t\tuci_batch=\"$uci_batch set smartdns.@smartdns[0].auto_set_dnsmasq=\\\"0\\\"\\n\"\n\t\t}\n\t\tuci_batch=\"$uci_batch delete smartdns.@smartdns[0].redirect\\n\"\n\t\tuci_batch=\"$uci_batch delete smartdns.@smartdns[0].old_redirect\\n\"\n\tfi\n\n\tuci_batch=\"$uci_batch delete smartdns.@smartdns[0].old_port\\n\"\n\tuci_batch=\"$uci_batch delete smartdns.@smartdns[0].old_enabled\\n\"\n\tuci_batch=\"$uci_batch delete smartdns.@smartdns[0].old_auto_set_dnsmasq\\n\"\n\tuci_batch=\"$uci_batch set smartdns.@smartdns[0].old_port=\\\"$port\\\"\\n\"\n\tuci_batch=\"$uci_batch set smartdns.@smartdns[0].old_enabled=\\\"$enabled\\\"\\n\"\n\tuci_batch=\"$uci_batch set smartdns.@smartdns[0].old_auto_set_dnsmasq=\\\"$auto_set_dnsmasq\\\"\\n\"\n\techo -e \"$uci_batch\" | uci batch -q -\n\tuci commit smartdns\n\n\t# disable service\n\t[ \"$enabled\" = \"0\" ] && {\n\t\t[ \"$old_enabled\" = \"0\" ] && return 1\n\t\t[ \"$old_port\" = \"53\" ] && [ \"$old_auto_set_dnsmasq\" = \"1\" ] && stop_main_dns \"0\"\n\t\t[ \"$old_port\" != \"53\" ] && [ \"$old_auto_set_dnsmasq\" = \"1\" ] && stop_forward_dnsmasq \"$old_port\" \"0\"\n\t\tdisable_auto_update\n\t\treturn 1\n\t}\n\n\t# change port\n\t[ \"$old_port\" != \"$port\" ] && {\n\t\t[ \"$old_port\" = \"53\" ] && {\n\t\t\tno_restart_dnsmasq=\"1\"\n\t\t\t[ \"$auto_set_dnsmasq\" = \"0\" ] && no_restart_dnsmasq=\"0\"\n\t\t\t[ \"$old_auto_set_dnsmasq\" = \"1\" ] && stop_main_dns \"$no_restart_dnsmasq\"\n\t\t}\n\t\t[ \"$old_port\" != \"53\" ] && [ \"$old_auto_set_dnsmasq\" = \"1\" ] && stop_forward_dnsmasq \"$old_port\" \"1\"\n\t}\n\n\t# start service\n\t[ \"$port\" = \"53\" ] && {\n\t\t[ \"$auto_set_dnsmasq\" = \"1\" ] && set_main_dns\n\t\t[ \"$auto_set_dnsmasq\" = \"0\" ] && [ \"$old_auto_set_dnsmasq\" = \"1\" ] && stop_main_dns \"0\"\n\t}\n\t[ \"$port\" != \"53\" ] && {\n\t\t[ \"$auto_set_dnsmasq\" = \"1\" ] && set_forward_dnsmasq \"$port\"\n\t\t[ \"$auto_set_dnsmasq\" = \"0\" ] && [ \"$old_auto_set_dnsmasq\" = \"1\" ] && stop_forward_dnsmasq \"$old_port\" \"0\"\n\t}\n\n\tconf_append_bind \"bind\" \"$port\" \"$device\" \"$ipv6_server\" \"$server_flags\"\n\t[ \"$tcp_server\" = \"1\" ] && conf_append_bind \"bind-tcp\" \"$port\" \"$device\" \"$ipv6_server\" \"$server_flags\"\n\t[ \"$tls_server\" = \"1\" ] && conf_append_bind \"bind-tls\" \"$tls_server_port\" \"$device\" \"$ipv6_server\" \"$server_flags\"\n\t[ \"$doh_server\" = \"1\" ] && conf_append_bind \"bind-https\" \"$doh_server_port\" \"$device\" \"$ipv6_server\" \"$server_flags\"\n\n\t[ ! -z \"$bind_cert\" ] && conf_append \"bind-cert-file\" \"$bind_cert\"\n\t[ ! -z \"$bind_cert_key\" ] && conf_append \"bind-cert-key-file\" \"$bind_cert_key\"\n\t[ ! -z \"$bind_cert_key_pass\" ] && conf_append \"bind-cert-key-pass\" \"$bind_cert_key_pass\"\n\n\tload_second_server \"$section\"\n\n\tconfig_foreach load_server \"server\"\n\n\tconfig_list_foreach \"$section\" \"conf_files\" conf_append_conf_files\n\n\tconfig_list_foreach \"$section\" \"hosts_files\" conf_append_hosts_files\n\n\tconfig_foreach load_client_rules \"client-rule\"\n\n\tconfig_foreach load_domain_rules \"domain-rule\"\n\n\tconfig_foreach load_domain_rule_list \"domain-rule-list\"\n\n\tconfig_foreach load_IP_rule_list \"ip-rule\"\n\n\tconfig_foreach load_IP_rule_list \"ip-rule-list\"\n\n\tconfig_get_bool ui \"$section\" \"ui\" '0'\n\n\t[ \"$ui\" = \"1\" ] && {\n\t\tconfig_get ui_port \"$section\" \"ui_port\" \"6080\"\n\t\tconfig_get ui_data_dir \"$section\" \"ui_data_dir\" \"/var/lib/smartdns\"\n\t\tconfig_get ui_log_max_age \"$section\" \"ui_log_max_age\" \"30\"\n\n\t\tui_log_max_age_s=$((ui_log_max_age * 86400))\n\n\t\tconf_append \"plugin\" \"smartdns_ui.so\"\n\t\tconf_append \"smartdns-ui.www-root\" \"/usr/share/smartdns/wwwroot\"\n\t\tconf_append \"smartdns-ui.ip\" \"http://[::]:$ui_port\"\n\t\tconf_append \"data-dir\" \"$ui_data_dir\"\n\t\tconf_append \"smartdns-ui.max-query-log-age\" \"$ui_log_max_age_s\"\n\t}\n\n\t{\n\t\techo \"conf-file $ADDRESS_CONF\"\n\t\techo \"conf-file $BLACKLIST_IP_CONF\"\n\t\techo \"conf-file $CUSTOM_CONF\"\n\t} >> $SMARTDNS_CONF_TMP\n\tmv $SMARTDNS_CONF_TMP $SMARTDNS_CONF\n\n\tprocd_open_instance \"smartdns\"\n\t[ \"$COREDUMP\" = \"1\" ] && {\n\t\targs=\"$args -S\"\n\t\tprocd_set_param limits core=\"unlimited\"\n\t}\n\n\tget_tz\n\t[ -z \"$SET_TZ\" ] || procd_set_param env TZ=\"$SET_TZ\"\n\n\tprocd_set_param command /usr/sbin/smartdns -f -c $SMARTDNS_CONF $args\n\t[ \"$RESPAWN\" = \"1\" ] &&\tprocd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}\n\tprocd_set_param file \"$SMARTDNS_CONF\"\n\tprocd_set_param term_timeout 60\n\tprocd_close_instance\n}\n\nunload_service()\n{\n\tlocal section=\"$1\"\n\n\t[ \"$DO_RELOAD\" = \"1\" ] && return 0\n\n\tconfig_get_bool enabled \"$section\" \"enabled\" '0'\n\tdnsmasq_port=\"$(uci -q get dhcp.@dnsmasq[0].port)\"\n\tconfig_get port \"$section\" \"port\" \"53\"\n\tconfig_get auto_set_dnsmasq \"$section\" \"auto_set_dnsmasq\" \"0\"\n\tconfig_get old_enabled \"$section\" \"old_enabled\" \"0\"\n\tconfig_get old_port \"$section\" \"old_port\" \"0\"\n\tconfig_get old_auto_set_dnsmasq \"$section\" \"old_auto_set_dnsmasq\" \"0\"\n\t[ -z \"${dnsmasq_port}\" ] && dnsmasq_port=\"53\"\n\n\t[ \"$enabled\" = \"1\" ] && {\n\t\t[ \"$old_enabled\" = \"0\" ] && return 1\n\t\t[ \"$old_port\" = \"53\" ] && [ \"$old_auto_set_dnsmasq\" = \"1\" ] && stop_main_dns \"0\"\n\t\t[ \"$old_port\" != \"53\" ] && [ \"$old_auto_set_dnsmasq\" = \"1\" ] && stop_forward_dnsmasq \"$old_port\" \"0\"\n\t}\n}\n\ndownload_file() {\n\tlocal section=\"$1\"\n\n\tconfig_get url \"$section\" \"url\" \"\"\n\tconfig_get name \"$section\" \"name\" \"\"\n\tconfig_get filetype \"$section\" \"type\" \"\"\n\tconfig_get_bool use_proxy \"$section\" \"use_proxy\" \"0\"\n\n\t[ -z \"$url\" ] && return 0\n\t[ -z \"$name\" ] && return 0\n\t[ -z \"$filetype\" ] && return 0\n\n\techo \"download $filetype file $name from $url\"\n\t[ \"$use_proxy\" = \"1\" ] && {\n\t\tproxy=\"$(uci -q get smartdns.@smartdns[0].proxy_server)\"\n\t\t[ ! -z \"$proxy\" ] && {\n\t\t\texport http_proxy=\"$proxy\"\n\t\t\texport https_proxy=\"$proxy\"\n\t\t}\n\t}\n\twget --timeout 120 -q -O \"$SMARTDNS_DOWNLOAD_TMP_DIR/$name\" \"$url\"\n\tif [ $? -ne 0 ]; then\n\t\techo \"download file $name failed\"\n\t\treturn 1\n\tfi\n\n\techo \"download file $name success\"\n\tif [ \"$filetype\" = \"list\" ]; then\n\t\tmv \"$SMARTDNS_DOWNLOAD_TMP_DIR/$name\" \"$SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR/$name\"\t\n\telif [ \"$filetype\" = \"config\" ]; then\n\t\tmv \"$SMARTDNS_DOWNLOAD_TMP_DIR/$name\" \"$SMARTDNS_CONF_DOWNLOAD_DIR/$name\"\t\n\telif [ \"$filetype\" = \"ip-set\" ]; then\n\t\tmv \"$SMARTDNS_DOWNLOAD_TMP_DIR/$name\" \"$SMARTDNS_IP_SET_DOWNLOAD_DIR/$name\"\n\telse \n\t\tmv \"$SMARTDNS_DOWNLOAD_TMP_DIR/$name\" \"$SMARTDNS_DOWNLOAD_DIR/$name\"\n\tfi\n}\n\ncheck_and_add_entry() {\n\tlocal docommit=0\n\tuci -q get smartdns.@smartdns[0] >/dev/null\n\tif [ $? -ne 0 ]; then\n\t\tuci -q add smartdns smartdns >/dev/null\n\t\tdocommit=1\n\tfi\n\n\tuci -q get smartdns.@client-rule[0] >/dev/null\n\tif [ $? -ne 0 ]; then\n\t\tuci -q add smartdns client-rule >/dev/null\n\t\tdocommit=1\n\tfi\n\t\n\tuci -q get smartdns.@domain-rule[0] >/dev/null\n\tif [ $? -ne 0 ]; then\n\t\tuci -q add smartdns domain-rule >/dev/null\n\t\tdocommit=1\n\tfi\n\n\tuci -q get smartdns.@ip-rule[0] >/dev/null\n\tif [ $? -ne 0 ]; then\n\t\tuci -q add smartdns ip-rule >/dev/null\n\t\tdocommit=1\n\tfi\n\n\tif [ \"$docommit\" = \"1\" ]; then\n\t\tuci -q commit smartdns >/dev/null\n\tfi\n\n\tif [ ! -d \"$SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR\" ]; then\n\t\tmkdir -p \"$SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR\"\n\tfi\n\n\tif [ ! -d \"$SMARTDNS_CONF_DOWNLOAD_DIR\" ]; then\n\t\tmkdir -p \"$SMARTDNS_CONF_DOWNLOAD_DIR\"\n\tfi\n}\n\nupdatefiles() {\n\tconfig_load \"smartdns\"\n\t[ ! -d \"$SMARTDNS_DOWNLOAD_TMP_DIR\" ] && mkdir -p \"$SMARTDNS_DOWNLOAD_TMP_DIR\"\n\tconfig_foreach download_file \"download-file\"\n\trm -rf \"$SMARTDNS_DOWNLOAD_TMP_DIR\" >/dev/null 2>&1\n\treload_service\n}\n\nservice_stopped()\n{\n\tconfig_load \"smartdns\"\n\tconfig_foreach unload_service \"smartdns\"\n}\n\nstart_service()\n{\n\tcheck_and_add_entry\n\tconfig_load \"smartdns\"\n\tconfig_foreach load_service \"smartdns\"\n}\n\nreload_service()\n{\n\tDO_RELOAD=\"1\"\n\tstop\n\tstart\n\tDO_RELOAD=\"0\"\n}\n"
  },
  {
    "path": "package/openwrt/make.sh",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\nCURR_DIR=$(cd $(dirname $0);pwd)\n\nVER=\"`date +\"1.%Y.%m.%d-%H%M\"`\"\nSMARTDNS_DIR=$CURR_DIR/../../\nSMARTDNS_CP=$SMARTDNS_DIR/package/copy-smartdns.sh\nSMARTDNS_BIN=$SMARTDNS_DIR/src/smartdns\nSMARTDNS_CONF=$SMARTDNS_DIR/etc/smartdns/smartdns.conf\nADDRESS_CONF=$CURR_DIR/address.conf\nBLACKLIST_IP_CONF=$CURR_DIR/blacklist-ip.conf\nCUSTOM_CONF=$CURR_DIR/custom.conf\nDOMAIN_BLOCK_LIST=$CURR_DIR/domain-block.list\nDOMAIN_FORWARDING_LIST=$CURR_DIR/domain-forwarding.list\nIS_BUILD_SMARTDNS_UI=0\n\nshowhelp()\n{\n\techo \"Usage: make [OPTION]\"\n\techo \"Options:\"\n\techo \" -o               output directory.\"\n\techo \" --arch           archtecture.\"\n\techo \" --ver            version.\"\n\techo \" --with-ui        build with smartdns-ui plugin.\"\n\techo \" -h               show this message.\"\n}\n\nbuild()\n{\n\tROOT=/tmp/smartdns-openwrt\n\trm -fr $ROOT\n\n\tmkdir -p $ROOT\n\tcp $CURR_DIR/* $ROOT/ -af\n\tcd $ROOT/\n\tmkdir $ROOT/root/usr/sbin -p\n\tmkdir $ROOT/root/etc/init.d -p\n\tmkdir $ROOT/root/etc/smartdns/ -p\n\tmkdir $ROOT/root/etc/smartdns/domain-set/ -p \n\tmkdir $ROOT/root/etc/smartdns/ip-set/ -p \n\tmkdir $ROOT/root/etc/smartdns/conf.d/ -p \n\tmkdir $ROOT/root/etc/smartdns/download/ -p \n\n\tcp $SMARTDNS_CONF  $ROOT/root/etc/smartdns/\n\tcp $ADDRESS_CONF $ROOT/root/etc/smartdns/\n\tcp $BLACKLIST_IP_CONF $ROOT/root/etc/smartdns/\n\tcp $CUSTOM_CONF $ROOT/root/etc/smartdns/\n\tcp $DOMAIN_BLOCK_LIST $ROOT/root/etc/smartdns/\n\tcp $DOMAIN_FORWARDING_LIST $ROOT/root/etc/smartdns/\n\tcp $CURR_DIR/files/etc $ROOT/root/ -af\n\t$SMARTDNS_CP $ROOT/root\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy smartdns file failed.\"\n\t\trm -fr $ROOT/\n\t\treturn 1\n\tfi\n\n\tif [ $IS_BUILD_SMARTDNS_UI -ne 0 ]; then\n\t\tmkdir $ROOT/root/usr/lib/smartdns -p\n\t\tcp $SMARTDNS_DIR/plugin/smartdns-ui/target/smartdns_ui.so $ROOT/root/usr/lib/smartdns/\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"copy smartdns_ui.so file failed.\"\n\t\t\trm -fr $ROOT/\n\t\t\treturn 1\n\t\tfi\n\n\t\tmkdir $ROOT/root/usr/share/smartdns/wwwroot -p\n\t\tcp $WORKDIR/smartdns-webui/out/* $ROOT/root/usr/share/smartdns/wwwroot/ -a\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to copy smartdns-ui web files.\"\n\t\t\trm -fr $ROOT/\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tchmod +x $ROOT/root/etc/init.d/smartdns\n\tINST_SIZE=\"`du -sb $ROOT/root/ | awk '{print $1}'`\"\n\n\tsed -i \"s/^Architecture.*/Architecture: $ARCH/g\" $ROOT/control/control\n\tsed -i \"s/Version:.*/Version: $VER/\" $ROOT/control/control\n\tsed -i \"s/^\\(bind .*\\):53/\\1:6053/g\" $ROOT/root/etc/smartdns/smartdns.conf\n\tif [ ! -z \"$INST_SIZE\" ]; then\n\t\techo \"Installed-Size: $INST_SIZE\" >> $ROOT/control/control\n\tfi\n\n\tif [ \"$STATIC\" = \"yes\" ]; then\n\t\tsed -i \"s/Depends:.*/Depends: libc/\" $ROOT/control/control\n\tfi\n\n\tcd $ROOT/control\n\tchmod +x *\n\ttar zcf ../control.tar.gz --owner=0 --group=0 ./\n\tcd $ROOT\n\n\ttar zcf $ROOT/data.tar.gz -C root --owner=0 --group=0 .\n\ttar zcf $OUTPUTDIR/smartdns.$VER.$FILEARCH.ipk --owner=0 --group=0 ./control.tar.gz ./data.tar.gz ./debian-binary\n\n\twhich apk >/dev/null 2>&1\n\tif [ $? -eq 0 ]; then\n\t\tAPK_VER=\"`echo $VER | sed 's/[-]/-r/'`\"\n\t\tARCH=\"`echo $ARCH | sed 's/all/noarch/g'`\"\n\t\tapk mkpkg \\\n\t\t\t--info \"name:smartdns\" \\\n\t\t\t--info \"version:$APK_VER\" \\\n\t\t\t--info \"description:A smartdns Server\" \\\n\t\t\t--info \"arch:$ARCH\" \\\n\t\t\t--info \"license:GPL\" \\\n\t\t\t--info \"origin: https://github.com/pymumu/smartdns.git\" \\\n\t\t\t--info \"depends:libc libpthread\" \\\n\t\t\t--script \"post-install:$ROOT/control/postinst\" \\\n\t\t\t--script \"pre-deinstall:$ROOT/control/prerm\" \\\n\t\t\t--files \"$ROOT/root/\" \\\n\t\t\t--output \"$OUTPUTDIR/smartdns.$VER.$FILEARCH.apk\"\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"build apk package failed.\"\n\t\t\trm -fr $ROOT/\n\t\t\treturn 1\n\t\tfi\n\telse\n\t\techo \"== warning: apk tool not found, skip build apk package. ==\"\n\tfi\n\n\trm -fr $ROOT/\n}\n\nmain()\n{\n\tOPTS=`getopt -o o:h --long arch:,ver:,with-ui,filearch: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--arch)\n\t\t\tARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--filearch)\n\t\t\tFILEARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--with-ui)\n\t\t\tIS_BUILD_SMARTDNS_UI=1\n\t\t\tshift ;;\n\t\t--ver)\n\t\t\tVER=\"$2\"\n\t\t\tshift 2;;\n\t\t-o )\n\t\t\tOUTPUTDIR=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n\t\tesac\n\tdone\n\n\tif [ -z \"$ARCH\" ]; then\n\t\techo \"please input arch.\"\n\t\treturn 1;\n\tfi\n\n\tif [ -z \"$FILEARCH\" ]; then\n\t\tFILEARCH=$ARCH\n\tfi\n\n\tif [ -z \"$OUTPUTDIR\" ]; then\n\t\tOUTPUTDIR=$CURR_DIR;\n\tfi\n\n\tbuild\n}\n\nmain $@\nexit $?\n\n\n"
  },
  {
    "path": "package/optware/S50smartdns",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nSMARTDNS_BIN=/opt/usr/sbin/smartdns\nSMARTDNS_CONF=/opt/etc/smartdns/smartdns.conf\nDNSMASQ_CONF=\"/etc/dnsmasq.conf /var/etc/dnsmasq.conf /etc/storage/dnsmasq/dnsmasq.conf\"\nSMARTDNS_PID=/run/smartdns.pid\nSMARTDNS_CHECK_PID=/tmp/smartdns_delay_check.pid\nif [ ! -d \"/run\" ]; then\n\tSMARTDNS_PID=/var/run/smartdns.pid\nfi\nSMARTDNS_PORT=535\nSMARTDNS_OPT=/opt/etc/smartdns/smartdns-opt.conf\n# workmode \n# DO NOT CHANGE THIS, CHANGE MODE IN smartdns-opt.conf\n# 0: run as port only\n# 1: redirect port\n# 2: replace \nSMARTDNS_WORKMODE=\"1\"\n\nSMARTDNS_INIT_SCRIPT=\"$0\"\n\nif [ -f \"$SMARTDNS_OPT\" ]; then\n. \"$SMARTDNS_OPT\"\nfi\n\n\nset_iptable()\n{\n\tlocal redirect_tcp\n\n\tredirect_tcp=0\n\n\tgrep ^bind-tcp $SMARTDNS_CONF > /dev/null 2>&1\n\tif [ $? -eq 0 ]; then\n\t\tredirect_tcp=1;\n\tfi\n\n\tIPS=\"$(ifconfig | grep \"inet addr\" | grep -v \":127\" | grep \"Bcast\" | awk '{print $2}' | awk -F: '{print $2}')\"\n\tfor IP in $IPS\n\tdo\n\t\tif [ $redirect_tcp -eq 1 ]; then\n\t\t\tiptables -t nat -A PREROUTING -p tcp -d \"$IP\" --dport 53 -j REDIRECT --to-ports \"$SMARTDNS_PORT\" > /dev/null 2>&1\n\t\tfi\n\t\tiptables -t nat -A PREROUTING -p udp -d \"$IP\" --dport 53 -j REDIRECT --to-ports \"$SMARTDNS_PORT\" > /dev/null 2>&1\n\tdone\n\n}\n\nclear_iptable()\n{\n\tIPS=\"$(ifconfig | grep \"inet addr\" | grep -v \":127\" | grep \"Bcast\" | awk '{print $2}' | awk -F: '{print $2}')\"\n\tfor IP in $IPS\n\tdo\n\t\tiptables -t nat -D PREROUTING -p tcp -d \"$IP\" --dport 53 -j REDIRECT --to-ports \"$SMARTDNS_PORT\" > /dev/null 2>&1\n\t\tiptables -t nat -D PREROUTING -p udp -d \"$IP\" --dport 53 -j REDIRECT --to-ports \"$SMARTDNS_PORT\" > /dev/null 2>&1\n\tdone\n\t\n}\n\nget_dnsmasq_cmd()\n{\n\tCMD=\"$(ps 2>/dev/null | grep -e '[a-zA-Z]\\{0,2\\} \\{1,\\}dnsmasq' | grep -v grep 2>/dev/null)\"\n\tif [ ! -z \"$CMD\" ]; then\n\t\treturn\n\tfi\n\n\tCMD=\"$(ps 2>/dev/null | grep '/usr/sbin/dnsmasq' | grep -v grep 2>/dev/null)\"\n\tif [ ! -z \"$CMD\" ]; then\n\t\treturn\n\tfi\n\n\tCMD=\"$(ps 2>/dev/null | grep 'dnsmasq' | grep -v grep 2>/dev/null)\"\n\tif [ ! -z \"$CMD\" ]; then\n\t\treturn\n\tfi\n\n\tCMD=\"$(ps ax 2>/dev/null | grep -e '[a-zA-Z]\\{0,2\\} \\{1,\\}dnsmasq' | grep -v grep 2>/dev/null)\"\n\tif [ ! -z \"$CMD\" ]; then\n\t\treturn\n\tfi\n\n\tCMD=\"$(ps ax 2>/dev/null | grep /usr/sbin/dnsmasq | grep -v grep 2>/dev/null)\"\n\tif [ ! -z \"$CMD\" ]; then\n\t\treturn\n\tfi\n\n\tCMD=\"$(ps ax 2>/dev/null | grep 'dnsmasq' | grep -v grep 2>/dev/null)\"\n\tif [ ! -z \"$CMD\" ]; then\n\t\treturn\n\tfi\t\n}\n\nget_dnsmasq_cmdline()\n{\n\n\tlocal CMD=\"\"\n\tlocal loop=0\n\n\twhile [ $loop -lt 3 ]; do \n\t\tget_dnsmasq_cmd\n\t\tif [ ! -z \"$CMD\" ]; then\n\t\t\tbreak;\n\t\tfi\n\n\t\t$SMARTDNS_INIT_SCRIPT stop\n\t\tsleep 1\n\t\tloop=$((loop+1))\n\tdone\n\n\tif [ -z \"$CMD\" ]; then\n\t\techo \"cannot find dnsmasq\"\n\t\tservice restart_dnsmasq 2>/dev/null\n\t\treturn 1\n\tfi\n\n\t# check multiple dnsmasq\n\tlinecount=\"$(echo \"$CMD\" | wc -l)\"\n\tif [ $linecount -eq 1 ]; then\n\t\tPID=\"$(echo \"$CMD\" | awk '{print $1}')\"\n\telif [ $linecount -gt 1 ]; then\n\t\tPID1=\"$(echo \"$CMD\" | awk 'NR==1{print $1}')\"\n\t\tPID2=\"$(echo \"$CMD\" | awk 'NR==2{print $1}')\"\n\t\tPID2_PPID=\"$(grep 'PPid:' /proc/$PID2/status | awk '{print $2}' 2>/dev/null)\"\n\t\tif [ \"$PID2_PPID\" != \"$PID1\" ]; then\n\t\t\tkill -9 \"$PID2\" 2>/dev/null\n\t\tfi\n\t\tPID=$PID1\n\telse\n\t\techo \"find multiple dnsmasq, but not started by the same process\"\n\t\treturn 1\n\tfi\n\n\tif [ ! -d \"/proc/$PID\" ]; then\n\t\techo \"dnsmasq is not running\"\n\t\treturn 1\n\tfi\n\n\tCMD=\"$(echo \"$CMD\" | head -n 1)\"\n\tDNSMASQ_CMD=\"$(echo \"$CMD\" | awk '{for(i=5; i<=NF;i++)printf $i \" \"}')\"\n\n\treturn 0\n}\n\nrestart_dnsmasq()\n{\n\tif [ -z \"$DNSMASQ_CMD\" ]; then\n\t\tget_dnsmasq_cmdline\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"cannot find dnsmasq\"\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tif [ ! -z \"$PID\" ]; then\n\t\tkill -9 \"$PID\"\n\tfi\n\n\t$DNSMASQ_CMD\n\n\treturn $?\n}\n\nadd_dhcp_options6()\n{\n\tCONF_FILE=$1\n\tIPS=\"$(ifconfig | grep \"inet addr\" | grep -v \":127\" | grep \"Bcast\" | awk '{print $2}' | awk -F: '{print $2}')\"\n\tfor IP in $IPS\n\tdo\n\t\tDHCP_OPTION=\"$(grep \"dhcp-option=\" \"$CONF_FILE\" | grep \"$IP\" | head -n 1)\"\n\t\tif [ -z \"$DHCP_OPTION\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tSERVER_TAG=\"$(echo \"$DHCP_OPTION\" | awk -F= '{print $2}' | awk -F, '{print $1}')\"\n\t\tLOCAL_SERVER_IP=\"$IP\"\n\n\t\tgrep \"dhcp-option *= *$SERVER_TAG, *6 *, *$LOCAL_SERVER_IP\" \"$CONF_FILE\" 1>/dev/null 2>&1\n\t\tif [ $? -eq 0 ]; then\n\t\t\tcontinue\n\t\tfi\n\t\t\n\t\tDHCP_OPTION=\"dhcp-option=$SERVER_TAG,6,$LOCAL_SERVER_IP\"\n\t\techo \"$DHCP_OPTION\" >> \"$CONF_FILE\"\n\t\tRESTART_DNSMASQ=1\n\tdone\n\n\treturn 1\n}\n\nclear_dhcp_options6()\n{\n\tCONF_FILE=$1\n\tIPS=\"$(ifconfig | grep \"inet addr\" | grep -v \":127\" | grep \"Bcast\" | awk '{print $2}' | awk -F: '{print $2}')\"\n\tfor IP in $IPS\n\tdo\n\t\tDHCP_OPTION=\"$(grep \"dhcp-option=\" \"$CONF_FILE\" | grep \"$IP\" | head -n 1)\"\n\t\tif [ -z \"$DHCP_OPTION\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tSERVER_TAG=\"$(echo \"$DHCP_OPTION\" | awk -F= '{print $2}' | awk -F, '{print $1}')\"\n\t\tLOCAL_SERVER_IP=\"$IP\"\n\n\t\tgrep \"dhcp-option *= *$SERVER_TAG, *6 *, *$LOCAL_SERVER_IP\" \"$CONF_FILE\" 1>/dev/null 2>&1\n\t\tif [ $? -ne 0 ]; then\n\t\t\tcontinue\n\t\tfi\n\t\t\n\t\tsed -i \"/^dhcp-option *=$SERVER_TAG,6,/d\" \"$CONF_FILE\"\n\t\tRESTART_DNSMASQ=1\n\tdone\n\n\treturn 1\n}\n\nset_dnsmasq_conf()\n{\n\tlocal LOCAL_SERVER_IP=\"\"\n\tlocal SERVER_TAG=\"\"\n\tlocal CONF_FILE=$1\n\tlocal DHCP_OPTIONS=\"\"\n\n\tadd_dhcp_options6 \"$CONF_FILE\"\n\n\tgrep \"^port *=0\" \"$CONF_FILE\" > /dev/null 2>&1\n\tif [ $? -ne 0 ]; then\n\t\tsed -i \"/^port *=/d\" \"$CONF_FILE\"\n\t\techo \"port=0\" >> \"$CONF_FILE\"\n\t\tRESTART_DNSMASQ=1\n\tfi\n}\n\ndo_set_dnsmasq()\n{\n\tlocal RESTART_DNSMASQ=0\n\n\tfor conf in $DNSMASQ_CONF\n\tdo\n\t\tif [ ! -e \"$conf\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tset_dnsmasq_conf \"$conf\"\n\tdone\n\t\n\tif [ $RESTART_DNSMASQ -ne 0 ]; then\n\t\trestart_dnsmasq\t\n\tfi\n}\n\nkill_dnsmasq_delay_check_pid()\n{\n\tif [ ! -e \"$SMARTDNS_CHECK_PID\" ]; then\n\t\treturn\n\tfi\n\n\tPID=\"$(cat $SMARTDNS_CHECK_PID)\"\n\tif [ -d \"/proc/$PID\" ]; then\n\t\tkill -9 $PID\n\tfi\n\trm -f $SMARTDNS_CHECK_PID\n}\n\ndnsmasq_delay_check()\n{\n\tsleep 8\n\trm -f $SMARTDNS_CHECK_PID\n\tget_dnsmasq_cmdline\n\tif [ -z \"$DNSMASQ_CMD\" ] ; then \n\t\t$SMARTDNS_INIT_SCRIPT restart\n\t\treturn\n\tfi\n\n\tdo_set_dnsmasq\n\tpid=\"$(cat $SMARTDNS_PID |head -n 1 2>/dev/null)\"\n\tif [ -z \"$pid\" ]; then\n\t\tdo_clear_dnsmasq\t\t\n\t\t$SMARTDNS_INIT_SCRIPT start > /dev/null 2>&1 &\n\telif [ ! -d \"/proc/$pid\" ]; then\n\t\tdo_clear_dnsmasq\t\t\n\t\t$SMARTDNS_INIT_SCRIPT start > /dev/null 2>&1 &\n\tfi\n\texit 0\n}\n\nbegin_dnsmasq_delay_check()\n{\n\tDNSMASQ_CMD=\"\"\n\tkill_dnsmasq_delay_check_pid\n\tget_dnsmasq_cmdline\n\tdnsmasq_delay_check > /dev/null 2>&1 &\n\tPID=$!\n\techo $PID > $SMARTDNS_CHECK_PID\n}\n\nset_dnsmasq()\n{\n\tget_dnsmasq_cmdline\n\tdo_set_dnsmasq\n\tbegin_dnsmasq_delay_check\n}\n\nset_jffs_dnsmasq()\n{\n\tlocal RESTART_DNSMASQ=0\n\n\tif [ \"$(nvram get jffs2_scripts)\" -ne 1 ]; then\n\t\tnvram set jffs2_scripts=\"1\"\n\t\tnvram commit\n\tfi\n\n\ttouch /jffs/configs/dnsmasq.conf.add\n\n\tif [ -e \"/jffs/configs/dnsmasq.conf.add\" ]; then\n\t\tset_dnsmasq_conf \"/jffs/configs/dnsmasq.conf.add\"\n\tfi\n\n\tif [ -e \"/jffs/configs/dnsmasq.conf\" ]; then\n\t\tset_dnsmasq_conf \"/jffs/configs/dnsmasq.conf\"\n\tfi\n\n\tif [ $RESTART_DNSMASQ -ne 0 ]; then\n\t\trestart_dnsmasq\t\n\tfi\n}\n\nclear_dnsmasq_conf()\n{\n\tlocal LOCAL_SERVER_IP=\"\"\n\tlocal SERVER_TAG=\"\"\n\tlocal CONF_FILE=$1\n\t\n\tclear_dhcp_options6 \"$CONF_FILE\"\n\n\tgrep \"^port *=\" \"$CONF_FILE\" > /dev/null 2>&1\n\tif [ $? -eq 0 ]; then\n\t\tsed -i \"/^port *=/d\" \"$CONF_FILE\"\n\t\tRESTART_DNSMASQ=1\n\tfi\n}\n\ndo_clear_dnsmasq()\n{\n\tlocal RESTART_DNSMASQ=0\n\n\tfor conf in $DNSMASQ_CONF\n\tdo\n\t\tif [ ! -e \"$conf\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tclear_dnsmasq_conf \"$conf\"\n\tdone\n\n\tif [ $RESTART_DNSMASQ -ne 0 ]; then\n\t\tif [ $? -eq 0 ]; then\n\t\t\treturn\n\t\tfi\n\n\t\trestart_dnsmasq\t\n\tfi\n}\n\nclear_dnsmasq()\n{\n\tkill_dnsmasq_delay_check_pid\n\tdo_clear_dnsmasq\n}\n\nclear_jffs_dnsmasq()\n{\n\tlocal RESTART_DNSMASQ=0\n\n\tif [ -e \"/jffs/configs/dnsmasq.conf.add\" ]; then\n\t\tclear_dnsmasq_conf \"/jffs/configs/dnsmasq.conf.add\"\n\tfi\n\n\tif [ -e \"/jffs/configs/dnsmasq.conf\" ]; then\n\t\tclear_dnsmasq_conf \"/jffs/configs/dnsmasq.conf\"\n\tfi\n\n\tif [ $RESTART_DNSMASQ -ne 0 ]; then\n\t\trestart_dnsmasq\t\n\tfi\n}\n\nset_smartdns_port()\n{\n\tif [ \"$SMARTDNS_WORKMODE\" = \"0\" ]; then\n\t\treturn 0\n\telif [ \"$SMARTDNS_WORKMODE\" = \"1\" ]; then\n\t\tsed -i \"s/^\\(bind .*\\):53\\(@[^ ]*\\)\\?\\( .*\\)\\?$/\\1:$SMARTDNS_PORT\\2 \\3/g\" $SMARTDNS_CONF\n\t\tsed -i \"s/^\\(bind-tcp .*\\):53\\(@[^ ]*\\)\\?\\( .*\\)\\?$/\\1:$SMARTDNS_PORT\\2 \\3/g\" $SMARTDNS_CONF\n\telif [ \"$SMARTDNS_WORKMODE\" = \"2\" ]; then\n\t\tsed -i \"s/^\\(bind .*\\):$SMARTDNS_PORT\\(@[^ ]*\\)\\?\\( .*\\)\\?$/\\1:53\\2 \\3/g\" $SMARTDNS_CONF\n\t\tsed -i \"s/^\\(bind-tcp .*\\):$SMARTDNS_PORT\\(@[^ ]*\\)\\?\\( .*\\)\\?$/\\1:53\\2 \\3/g\" $SMARTDNS_CONF\n\telif [ \"$SMARTDNS_WORKMODE\" = \"3\" ]; then\n\t\treturn 0\n\telse\n\t\treturn 1\n\tfi\n\n\treturn 0\n}\n\nset_rule()\n{\n\tif [ \"$SMARTDNS_WORKMODE\" = \"0\" ]; then\n\t\treturn 0\n\telif [ \"$SMARTDNS_WORKMODE\" = \"1\" ]; then\n\t\tset_iptable\n\t\treturn $?\n\telif [ \"$SMARTDNS_WORKMODE\" = \"2\" ]; then\n\t\tset_dnsmasq\n\t\treturn $?\n\telif [ \"$SMARTDNS_WORKMODE\" = \"3\" ]; then\n\t\tset_jffs_dnsmasq\n\t\treturn $?\n\telse\n\t\treturn 1\n\tfi\n}\n\nclear_rule()\n{\n\tif [ \"$SMARTDNS_WORKMODE\" = \"0\" ]; then\n\t\treturn 0\n\telif [ \"$SMARTDNS_WORKMODE\" = \"1\" ]; then\n\t\tclear_iptable\n\t\treturn $?\n\telif [ \"$SMARTDNS_WORKMODE\" = \"2\" ]; then\n\t\tclear_dnsmasq\n\t\treturn $?\n\telif [ \"$SMARTDNS_WORKMODE\" = \"3\" ]; then\n\t\tclear_jffs_dnsmasq\n\t\treturn $?\n\telse\n\t\treturn 1\n\tfi\n}\n\nget_tz()\n{\n\tif [ -e \"/etc/localtime\" ]; then\n\t\treturn \n\tfi\n\t\n\tfor tzfile in /etc/TZ /var/etc/TZ\n\tdo\n\t\tif [ ! -e \"$tzfile\" ]; then\n\t\t\tcontinue\n\t\tfi\n\t\t\n\t\ttz=\"$(cat $tzfile 2>/dev/null)\"\n\tdone\n\t\n\tif [ -z \"$tz\" ]; then\n\t\treturn\t\n\tfi\n\t\n\texport TZ=$tz\n}\n\ncase \"$1\" in\n\tstart)\n\tset_rule\n\tif [ $? -ne 0 ]; then\n\t\texit 1\n\tfi\n\n\tSMARTDNS_OPTION=\"\"\n\t[ \"$SMARTDNS_CRASH_RESTART\" = \"1\" ] && SMARTDNS_OPTION=\"$SMARTDNS_OPTION -R\"\n\n\tset_smartdns_port\n\tget_tz\n\t$SMARTDNS_BIN -c \"$SMARTDNS_CONF\" -p $SMARTDNS_PID $SMARTDNS_OPTION\n\tif [ $? -ne 0 ]; then\n\t\tclear_rule\n\t\texit 1\n\tfi\n\t;;\n\tstatus)\n\tpid=\"$(cat $SMARTDNS_PID |head -n 1 2>/dev/null)\"\n\tif [ -z \"$pid\" ]; then\n\t\techo \"smartdns not running.\"\n\t\texit 0\n\tfi\n\n\tif [ -d \"/proc/$pid\" ]; then\n\t\techo \"smartdns is running\"\n\t\texit 0\n\tfi\n\techo \"smartdns not running.\"\n\texit 0\n\t;;\n\tstop)\n\tpid=\"$(cat \"$SMARTDNS_PID\" | head -n 1 2>/dev/null)\"\n\tif [ -z \"$pid\" ]; then\n\t\techo \"smartdns not running.\"\n\t\texit 0\n\tfi\n\n\tkill -15 \"$pid\" 2>/dev/null\n\tSLEEP=$(which usleep 2>/dev/null)\n\tSLEEPTIME=200000\n\tif [ -z \"$SLEEP\" ]; then\n\t\tSLEEP=\"sleep\"\n\t\tSLEEPTIME=0.2\n\tfi\n\tN=300\n\twhile [ $N -gt 0 ]\n\tdo\n\t\tpid=\"$(cat \"$SMARTDNS_PID\" | head -n 1 2>/dev/null)\"\n\t\tif [ -z \"$pid\" ]; then\n\t\t\tbreak\n\t\tfi\n\n\t\tif [ ! -d \"/proc/$pid\" ]; then\n\t\t\tbreak\n\t\tfi\n\n\t\tstat=\"$(cat /proc/${pid}/stat 2>/dev/null | awk '{print $3}' 2>/dev/null)\"\n\t\tif [ \"$stat\" = \"Z\" ]; then\n\t\t\t$SLEEP $SLEEPTIME\n\t\t\tbreak\n\t\tfi\n\n\t\t$SLEEP $SLEEPTIME 2>/dev/null\n\t\tN=$((N-1))\n\tdone\n\n\tkill -9 \"$pid\" 2>/dev/null\n\tclear_rule\n\texit 0\n\t;;\n\trestart)\n\t$0 stop\n\t$0 start\n\t;;\n\treload)\n\t;;\n\tenable)\n\tnvram set apps_state_enable=2\n\tnvram set apps_state_error=0\n\tnvram set apps_state_install=5\n\tnvram set apps_state_action=install\n\tnvram set apps_u2ec_ex=2\n\t;;\n\tfirewall-start|reload|force-reload|reconfigure)\n\t$0 restart\n\t;;\n\t*)\n\t;;\nesac\n\n"
  },
  {
    "path": "package/optware/control/conffiles",
    "content": "/opt/etc/smartdns/smartdns.conf\n/opt/etc/smartdns/smartdns-opt.conf\n"
  },
  {
    "path": "package/optware/control/control",
    "content": "Package: smartdns\nArchitecture: mipsbig\nPriority: optional\nSection: net\nVersion: 2018.7.6-1921\nMaintainer: pymumu\nSource: http://127.0.0.1/\nDescription: A smart dns server\nSuggests: \nConflicts: \nEnabled: yes\n"
  },
  {
    "path": "package/optware/control/postinst",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nchmod +x /opt/usr/sbin/smartdns\nchmod +x /opt/etc/init.d/S50smartdns\n\n\n\n\n\n\n"
  },
  {
    "path": "package/optware/control/prerm",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n/opt/etc/init.d/S50smartdns stop\n"
  },
  {
    "path": "package/optware/debian-binary",
    "content": "2.0\n"
  },
  {
    "path": "package/optware/make.sh",
    "content": "#!/bin/sh\n#\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\nCURR_DIR=$(cd $(dirname $0);pwd)\nVER=\"`date +\"1.%Y.%m.%d-%H%M\"`\"\nSMARTDNS_DIR=$CURR_DIR/../../\nSMARTDNS_CP=$SMARTDNS_DIR/package/copy-smartdns.sh\nSMARTDNS_BIN=$SMARTDNS_DIR/src/smartdns\nSMARTDNS_CONF=$SMARTDNS_DIR/etc/smartdns/smartdns.conf\nSMARTDNS_OPT=$CURR_DIR/smartdns-opt.conf\nIS_BUILD_SMARTDNS_UI=0\n\nshowhelp()\n{\n\techo \"Usage: make [OPTION]\"\n\techo \"Options:\"\n\techo \" -o               output directory.\"\n\techo \" --arch           archtecture.\"\n\techo \" --ver            version.\"\n\techo \" --with-ui        build with smartdns-ui plugin.\"\n\techo \" -h               show this message.\"\n}\n\nbuild()\n{\n\tROOT=/tmp/smartdns-optware\n\trm -fr $ROOT\n\n\tmkdir -p $ROOT\n\tcp $CURR_DIR/* $ROOT/ -af\n\tcd $ROOT/\n\tmkdir $ROOT/opt/usr/sbin -p\n\tmkdir $ROOT/opt/etc/init.d -p\n\tmkdir $ROOT/opt/etc/smartdns/ -p\n\n\tcp $SMARTDNS_CONF  $ROOT/opt/etc/smartdns/\n\tcp $SMARTDNS_OPT $ROOT/opt/etc/smartdns/\n\tcp $CURR_DIR/S50smartdns $ROOT/opt/etc/init.d/\n\t$SMARTDNS_CP $ROOT /opt\n\tif [ $? -ne 0 ]; then\n\t\techo \"copy smartdns file failed.\"\n\t\trm -fr $PKG_ROOT\n\t\treturn 1\n\tfi\n\n\tif [ $IS_BUILD_SMARTDNS_UI -ne 0 ]; then\n\t\tmkdir $ROOT/opt/usr/lib/smartdns -p\n\t\tcp $SMARTDNS_DIR/plugin/smartdns-ui/target/smartdns_ui.so $ROOT/opt/usr/lib/smartdns/\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"copy smartdns_ui.so file failed.\"\n\t\t\trm -fr $ROOT/\n\t\t\treturn 1\n\t\tfi\n\n\t\tmkdir $ROOT/opt/usr/share/smartdns/wwwroot -p\n\t\tcp $WORKDIR/smartdns-webui/out/* $ROOT/opt/usr/share/smartdns/wwwroot/ -a\n\t\tif [ $? -ne 0 ]; then\n\t\t\techo \"Failed to copy smartdns-ui web files.\"\n\t\t\trm -fr $ROOT/\n\t\t\treturn 1\n\t\tfi\n\tfi\n\n\tsed -i \"s/# *server-name smartdns/server-name smartdns/g\" $ROOT/opt/etc/smartdns/smartdns.conf\n\tsed -i \"s/^Architecture.*/Architecture: $ARCH/g\" $ROOT/control/control\n\tsed -i \"s/Version:.*/Version: $VER/\" $ROOT/control/control\n\n\tcd $ROOT/control\n\tchmod +x *\n\ttar zcf ../control.tar.gz --owner=0 --group=0 ./ \n\tcd $ROOT\n\n\ttar zcf data.tar.gz --owner=0 --group=0 opt\n\ttar zcf $OUTPUTDIR/smartdns.$VER.$FILEARCH.ipk --owner=0 --group=0 control.tar.gz data.tar.gz debian-binary\n\trm -fr $ROOT/\n}\n\nmain()\n{\n\tOPTS=`getopt -o o:h --long arch:,ver:,with-ui,filearch: \\\n\t\t-n  \"\" -- \"$@\"`\n\n\tif [ $? != 0 ] ; then echo \"Terminating...\" >&2 ; exit 1 ; fi\n\n\t# Note the quotes around `$TEMP': they are essential!\n\teval set -- \"$OPTS\"\n\n\twhile true; do\n\t\tcase \"$1\" in\n\t\t--arch)\n\t\t\tARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--filearch)\n\t\t\tFILEARCH=\"$2\"\n\t\t\tshift 2;;\n\t\t--with-ui)\n\t\t\tIS_BUILD_SMARTDNS_UI=1\n\t\t\tshift ;;\n\t\t--ver)\n\t\t\tVER=\"$2\"\n\t\t\tshift 2;;\n\t\t-o )\n\t\t\tOUTPUTDIR=\"$2\"\n\t\t\tshift 2;;\n\t\t-h | --help )\n\t\t\tshowhelp\n\t\t\treturn 0\n\t\t\tshift ;;\n\t\t-- ) shift; break ;;\n\t\t* ) break ;;\n\t\tesac\n\tdone\n\n\tif [ -z \"$ARCH\" ]; then\n\t\techo \"please input arch.\"\n\t\treturn 1;\n\tfi\n\n\tif [ -z \"$FILEARCH\" ]; then\n\t\tFILEARCH=$ARCH\n\tfi\n\n\tif [ -z \"$OUTPUTDIR\" ]; then\n\t\tOUTPUTDIR=$CURR_DIR;\n\tfi\n\n\tbuild\n}\n\nmain $@\nexit $?\n"
  },
  {
    "path": "package/optware/smartdns-opt.conf",
    "content": "# workmode \n# 0: run as port only\n# 1: redirect port\n# 2: replace \nSMARTDNS_WORKMODE=\"1\"\n\n# smartdns port\nSMARTDNS_PORT=\"535\"\n\n# restart when crash\nSMARTDNS_CRASH_RESTART=\"1\""
  },
  {
    "path": "package/redhat/smartdns.spec",
    "content": "Name:           smartdns\nVersion:        1.2020.09.08\nRelease:        2235%{?dist}\nSummary:        smartdns\n\nLicense:        GPL 3.0\nURL:            https://github.com/pymumu/smartdns\nSource0:        %{name}-%{version}.tar.gz\n\nBuildRequires:  glibc\nBuildRequires:  centos-release >= 7\nBuildRequires:  openssl-devel\nRequires:       glibc\nRequires:       openssl\nRequires:       systemd\n\n%description\nA local DNS server to obtain the fastest website IP for the best Internet experience.\n\n%prep\n%setup -q\n\n%build\ncd src\nmake %{?_smp_mflags}\n\n%install\nrm -rf $RPM_BUILD_ROOT\n\n%{__install} -D -m 755 src/smartdns $RPM_BUILD_ROOT%{_sbindir}/smartdns\n%{__install} -D -m 644 etc/smartdns/smartdns.conf $RPM_BUILD_ROOT%{_sysconfdir}/smartdns/smartdns.conf\n%{__install} -D -m 644 systemd/smartdns.service.in $RPM_BUILD_ROOT%{_unitdir}/smartdns.service\n\n\ncat > $RPM_BUILD_ROOT%{_unitdir}/smartdns.service <<EOF\n[Unit]\nDescription=smartdns\nConditionFileIsExecutable=/usr/sbin/smartdns\nAfter=syslog.target network-online.target\n\n[Service]\nType=simple\nExecStart=/usr/sbin/smartdns -c /etc/smartdns/smartdns.conf -f\nPIDFile=/run/smartdns.pid\nRestart=on-failure\nKillMode=process\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n\n%files\n%defattr(-,root,root,-)\n%{_sbindir}/smartdns\n%config(noreplace) %{_sysconfdir}/smartdns/smartdns.conf\n%{_unitdir}/smartdns.service\n\n%post\n%systemd_post %{name}.service\n\n%preun\n%systemd_preun %{name}.service\n\n%postun\n%systemd_postun_with_restart %{name}.service\n"
  },
  {
    "path": "package/run-smartdns",
    "content": "#!/bin/sh\n\nCWD=$(pwd)\nreal_path=$(readlink -f \"$0\")\nCURDIR=$(cd $(dirname $real_path); pwd)\nSMARTDNS_BIN=${CURDIR}/smartdns\nNEED_CHECK_ARM_CP15=0\nINTERPRETER=\"${CURDIR}/lib/ld-linux.so\"\n\nif [ \"$NEED_CHECK_ARM_CP15\" = \"1\" ] && [ \"$(uname -m)\" = \"aarch64\" ]; then\n    # Fix arm cp15_barrier issue when running on aarch64 with 32bit userland\n    # sysctl abi.cp15_barrier=2\n    cp15_barrier=$(cat /proc/sys/abi/cp15_barrier 2>/dev/null)\n    if [ \"$cp15_barrier\" != \"2\" ]; then\n        sh -c \"echo \"2\" > /proc/sys/abi/cp15_barrier\" 2>/dev/null;\n        if [ \"$?\" != \"0\" ]; then\n            echo \"Failed to set cp15_barrier to 2, please run 'echo 2 > /proc/sys/abi/cp15_barrier' manually\"\n            exit 1\n        else\n            echo \"Set cp15_barrier to 2\"\n        fi\n    fi\nfi\n\nif [ ! -f ${INTERPRETER} ]; then\n    echo \"smartdns dynamic loader not found: ${INTERPRETER}\"\n    exit 1\nfi\n\ncd $CURDIR\nSMARTDNS_WORKDIR=\"$CWD\" exec \"${SMARTDNS_BIN}\" $@\n"
  },
  {
    "path": "package/tool/po2lmo/Makefile",
    "content": "\nINSTALL = install\nPREFIX  = /usr/bin\nCFLAGS  = -Wall -O2\npo2lmo: src/po2lmo.o src/template_lmo.o\n\t$(CC) $(LDFLAGS) -o src/po2lmo src/po2lmo.o src/template_lmo.o\n\ninstall:\n\t$(INSTALL) -m 755 src/po2lmo $(PREFIX)\n\nclean:\n\t$(RM) src/po2lmo src/*.o\n"
  },
  {
    "path": "package/tool/po2lmo/src/po2lmo.c",
    "content": "/*\n * lmo - Lua Machine Objects - PO to LMO conversion tool\n *\n *   Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>\n *\n *  Licensed under the Apache License, Version 2.0 (the \"License\");\n *  you may not use this file except in compliance with the License.\n *  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n */\n\n#include \"template_lmo.h\"\n\nstatic void die(const char *msg)\n{\n\tfprintf(stderr, \"Error: %s\\n\", msg);\n\texit(1);\n}\n\nstatic void usage(const char *name)\n{\n\tfprintf(stderr, \"Usage: %s input.po output.lmo\\n\", name);\n\texit(1);\n}\n\nstatic void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)\n{\n\tif( fwrite(ptr, size, nmemb, stream) == 0 )\n\t\tdie(\"Failed to write stdout\");\n}\n\nstatic int extract_string(const char *src, char *dest, int len)\n{\n\tint pos = 0;\n\tint esc = 0;\n\tint off = -1;\n\n\tfor( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )\n\t{\n\t\tif( (off == -1) && (src[pos] == '\"') )\n\t\t{\n\t\t\toff = pos + 1;\n\t\t}\n\t\telse if( off >= 0 )\n\t\t{\n\t\t\tif( esc == 1 )\n\t\t\t{\n\t\t\t\tswitch (src[pos])\n\t\t\t\t{\n\t\t\t\tcase '\"':\n\t\t\t\tcase '\\\\':\n\t\t\t\t\toff++;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdest[pos-off] = src[pos];\n\t\t\t\tesc = 0;\n\t\t\t}\n\t\t\telse if( src[pos] == '\\\\' )\n\t\t\t{\n\t\t\t\tdest[pos-off] = src[pos];\n\t\t\t\tesc = 1;\n\t\t\t}\n\t\t\telse if( src[pos] != '\"' )\n\t\t\t{\n\t\t\t\tdest[pos-off] = src[pos];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdest[pos-off] = '\\0';\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn (off > -1) ? strlen(dest) : -1;\n}\n\nstatic int cmp_index(const void *a, const void *b)\n{\n\tuint32_t x = ((const lmo_entry_t *)a)->key_id;\n\tuint32_t y = ((const lmo_entry_t *)b)->key_id;\n\n\tif (x < y)\n\t\treturn -1;\n\telse if (x > y)\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic void print_uint32(uint32_t x, FILE *out)\n{\n\tuint32_t y = htonl(x);\n\tprint(&y, sizeof(uint32_t), 1, out);\n}\n\nstatic void print_index(void *array, int n, FILE *out)\n{\n\tlmo_entry_t *e;\n\n\tqsort(array, n, sizeof(*e), cmp_index);\n\n\tfor (e = array; n > 0; n--, e++)\n\t{\n\t\tprint_uint32(e->key_id, out);\n\t\tprint_uint32(e->val_id, out);\n\t\tprint_uint32(e->offset, out);\n\t\tprint_uint32(e->length, out);\n\t}\n}\n\nint main(int argc, char *argv[])\n{\n\tchar line[4096];\n\tchar key[4096];\n\tchar val[4096];\n\tchar tmp[4096];\n\tint state  = 0;\n\tint offset = 0;\n\tint length = 0;\n\tint n_entries = 0;\n\tvoid *array = NULL;\n\tlmo_entry_t *entry = NULL;\n\tuint32_t key_id, val_id;\n\n\tFILE *in;\n\tFILE *out;\n\n\tif( (argc != 3) || ((in = fopen(argv[1], \"r\")) == NULL) || ((out = fopen(argv[2], \"w\")) == NULL) )\n\t\tusage(argv[0]);\n\n\tmemset(line, 0, sizeof(key));\n\tmemset(key, 0, sizeof(val));\n\tmemset(val, 0, sizeof(val));\n\n\twhile( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )\n\t{\n\t\tif( state == 0 && strstr(line, \"msgid \\\"\") == line )\n\t\t{\n\t\t\tswitch(extract_string(line, key, sizeof(key)))\n\t\t\t{\n\t\t\t\tcase -1:\n\t\t\t\t\tdie(\"Syntax error in msgid\");\n\t\t\t\tcase 0:\n\t\t\t\t\tstate = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tstate = 2;\n\t\t\t}\n\t\t}\n\t\telse if( state == 1 || state == 2 )\n\t\t{\n\t\t\tif( strstr(line, \"msgstr \\\"\") == line || state == 2 )\n\t\t\t{\n\t\t\t\tswitch(extract_string(line, val, sizeof(val)))\n\t\t\t\t{\n\t\t\t\t\tcase -1:\n\t\t\t\t\t\tstate = 4;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tstate = 3;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tswitch(extract_string(line, tmp, sizeof(tmp)))\n\t\t\t\t{\n\t\t\t\t\tcase -1:\n\t\t\t\t\t\tstate = 2;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tstrcat(key, tmp);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if( state == 3 )\n\t\t{\n\t\t\tswitch(extract_string(line, tmp, sizeof(tmp)))\n\t\t\t{\n\t\t\t\tcase -1:\n\t\t\t\t\tstate = 4;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tstrcat(val, tmp);\n\t\t\t}\n\t\t}\n\n\t\tif( state == 4 )\n\t\t{\n\t\t\tif( strlen(key) > 0 && strlen(val) > 0 )\n\t\t\t{\n\t\t\t\tkey_id = sfh_hash(key, strlen(key));\n\t\t\t\tval_id = sfh_hash(val, strlen(val));\n\n\t\t\t\tif( key_id != val_id )\n\t\t\t\t{\n\t\t\t\t\tn_entries++;\n\t\t\t\t\tarray = realloc(array, n_entries * sizeof(lmo_entry_t));\n\t\t\t\t\tentry = (lmo_entry_t *)array + n_entries - 1;\n\n\t\t\t\t\tif (!array)\n\t\t\t\t\t\tdie(\"Out of memory\");\n\n\t\t\t\t\tentry->key_id = key_id;\n\t\t\t\t\tentry->val_id = val_id;\n\t\t\t\t\tentry->offset = offset;\n\t\t\t\t\tentry->length = strlen(val);\n\n\t\t\t\t\tlength = strlen(val) + ((4 - (strlen(val) % 4)) % 4);\n\n\t\t\t\t\tprint(val, length, 1, out);\n\t\t\t\t\toffset += length;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstate = 0;\n\t\t\tmemset(key, 0, sizeof(key));\n\t\t\tmemset(val, 0, sizeof(val));\n\t\t}\n\n\t\tmemset(line, 0, sizeof(line));\n\t}\n\n\tprint_index(array, n_entries, out);\n\n\tif( offset > 0 )\n\t{\n\t\tprint_uint32(offset, out);\n\t\tfsync(fileno(out));\n\t\tfclose(out);\n\t}\n\telse\n\t{\n\t\tfclose(out);\n\t\tunlink(argv[2]);\n\t}\n\n\tfclose(in);\n\treturn(0);\n}\n"
  },
  {
    "path": "package/tool/po2lmo/src/template_lmo.c",
    "content": "/*\n * lmo - Lua Machine Objects - Base functions\n *\n *   Copyright (C) 2009-2010 Jo-Philipp Wich <xm@subsignal.org>\n *\n *  Licensed under the Apache License, Version 2.0 (the \"License\");\n *  you may not use this file except in compliance with the License.\n *  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n */\n\n#include \"template_lmo.h\"\n\n/*\n * Hash function from http://www.azillionmonkeys.com/qed/hash.html\n * Copyright (C) 2004-2008 by Paul Hsieh\n */\n\nuint32_t sfh_hash(const char *data, int len)\n{\n\tuint32_t hash = len, tmp;\n\tint rem;\n\n\tif (len <= 0 || data == NULL) return 0;\n\n\trem = len & 3;\n\tlen >>= 2;\n\n\t/* Main loop */\n\tfor (;len > 0; len--) {\n\t\thash  += sfh_get16(data);\n\t\ttmp    = (sfh_get16(data+2) << 11) ^ hash;\n\t\thash   = (hash << 16) ^ tmp;\n\t\tdata  += 2*sizeof(uint16_t);\n\t\thash  += hash >> 11;\n\t}\n\n\t/* Handle end cases */\n\tswitch (rem) {\n\t\tcase 3: hash += sfh_get16(data);\n\t\t\thash ^= hash << 16;\n\t\t\thash ^= data[sizeof(uint16_t)] << 18;\n\t\t\thash += hash >> 11;\n\t\t\tbreak;\n\t\tcase 2: hash += sfh_get16(data);\n\t\t\thash ^= hash << 11;\n\t\t\thash += hash >> 17;\n\t\t\tbreak;\n\t\tcase 1: hash += *data;\n\t\t\thash ^= hash << 10;\n\t\t\thash += hash >> 1;\n\t}\n\n\t/* Force \"avalanching\" of final 127 bits */\n\thash ^= hash << 3;\n\thash += hash >> 5;\n\thash ^= hash << 4;\n\thash += hash >> 17;\n\thash ^= hash << 25;\n\thash += hash >> 6;\n\n\treturn hash;\n}\n\nuint32_t lmo_canon_hash(const char *str, int len)\n{\n\tchar res[4096];\n\tchar *ptr, prev;\n\tint off;\n\n\tif (!str || len >= sizeof(res))\n\t\treturn 0;\n\n\tfor (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)\n\t{\n\t\tif (isspace(*str))\n\t\t{\n\t\t\tif (!isspace(prev))\n\t\t\t\t*ptr++ = ' ';\n\t\t}\n\t\telse\n\t\t{\n\t\t\t*ptr++ = *str;\n\t\t}\n\t}\n\n\tif ((ptr > res) && isspace(*(ptr-1)))\n\t\tptr--;\n\n\treturn sfh_hash(res, ptr - res);\n}\n\nlmo_archive_t * lmo_open(const char *file)\n{\n\tint in = -1;\n\tuint32_t idx_offset = 0;\n\tstruct stat s;\n\n\tlmo_archive_t *ar = NULL;\n\n\tif (stat(file, &s) == -1)\n\t\tgoto err;\n\n\tif ((in = open(file, O_RDONLY)) == -1)\n\t\tgoto err;\n\n\tif ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)\n\t{\n\t\tmemset(ar, 0, sizeof(*ar));\n\n\t\tar->fd     = in;\n\t\tar->size = s.st_size;\n\n\t\tfcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);\n\n\t\tif ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)\n\t\t\tgoto err;\n\n\t\tidx_offset = ntohl(*((const uint32_t *)\n\t\t                     (ar->mmap + ar->size - sizeof(uint32_t))));\n\n\t\tif (idx_offset >= ar->size)\n\t\t\tgoto err;\n\n\t\tar->index  = (lmo_entry_t *)(ar->mmap + idx_offset);\n\t\tar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);\n\t\tar->end    = ar->mmap + ar->size;\n\n\t\treturn ar;\n\t}\n\nerr:\n\tif (in > -1)\n\t\tclose(in);\n\n\tif (ar != NULL)\n\t{\n\t\tif ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))\n\t\t\tmunmap(ar->mmap, ar->size);\n\n\t\tfree(ar);\n\t}\n\n\treturn NULL;\n}\n\nvoid lmo_close(lmo_archive_t *ar)\n{\n\tif (ar != NULL)\n\t{\n\t\tif ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))\n\t\t\tmunmap(ar->mmap, ar->size);\n\n\t\tclose(ar->fd);\n\t\tfree(ar);\n\n\t\tar = NULL;\n\t}\n}\n\n\nlmo_catalog_t *_lmo_catalogs = NULL;\nlmo_catalog_t *_lmo_active_catalog = NULL;\n\nint lmo_load_catalog(const char *lang, const char *dir)\n{\n\tDIR *dh = NULL;\n\tchar pattern[16];\n\tchar path[PATH_MAX];\n\tstruct dirent *de = NULL;\n\n\tlmo_archive_t *ar = NULL;\n\tlmo_catalog_t *cat = NULL;\n\n\tif (!lmo_change_catalog(lang))\n\t\treturn 0;\n\n\tif (!dir || !(dh = opendir(dir)))\n\t\tgoto err;\n\n\tif (!(cat = malloc(sizeof(*cat))))\n\t\tgoto err;\n\n\tmemset(cat, 0, sizeof(*cat));\n\n\tsnprintf(cat->lang, sizeof(cat->lang), \"%s\", lang);\n\tsnprintf(pattern, sizeof(pattern), \"*.%s.lmo\", lang);\n\n\twhile ((de = readdir(dh)) != NULL)\n\t{\n\t\tif (!fnmatch(pattern, de->d_name, 0))\n\t\t{\n\t\t\tsnprintf(path, sizeof(path), \"%s/%s\", dir, de->d_name);\n\t\t\tar = lmo_open(path);\n\n\t\t\tif (ar)\n\t\t\t{\n\t\t\t\tar->next = cat->archives;\n\t\t\t\tcat->archives = ar;\n\t\t\t}\n\t\t}\n\t}\n\n\tclosedir(dh);\n\n\tcat->next = _lmo_catalogs;\n\t_lmo_catalogs = cat;\n\n\tif (!_lmo_active_catalog)\n\t\t_lmo_active_catalog = cat;\n\n\treturn 0;\n\nerr:\n\tif (dh) closedir(dh);\n\tif (cat) free(cat);\n\n\treturn -1;\n}\n\nint lmo_change_catalog(const char *lang)\n{\n\tlmo_catalog_t *cat;\n\n\tfor (cat = _lmo_catalogs; cat; cat = cat->next)\n\t{\n\t\tif (!strncmp(cat->lang, lang, sizeof(cat->lang)))\n\t\t{\n\t\t\t_lmo_active_catalog = cat;\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\nstatic lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)\n{\n\tunsigned int m, l, r;\n\tuint32_t k;\n\n\tl = 0;\n\tr = ar->length - 1;\n\n\twhile (1)\n\t{\n\t\tm = l + ((r - l) / 2);\n\n\t\tif (r < l)\n\t\t\tbreak;\n\n\t\tk = ntohl(ar->index[m].key_id);\n\n\t\tif (k == hash)\n\t\t\treturn &ar->index[m];\n\n\t\tif (k > hash)\n\t\t{\n\t\t\tif (!m)\n\t\t\t\tbreak;\n\n\t\t\tr = m - 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tl = m + 1;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nint lmo_translate(const char *key, int keylen, char **out, int *outlen)\n{\n\tuint32_t hash;\n\tlmo_entry_t *e;\n\tlmo_archive_t *ar;\n\n\tif (!key || !_lmo_active_catalog)\n\t\treturn -2;\n\n\thash = lmo_canon_hash(key, keylen);\n\n\tfor (ar = _lmo_active_catalog->archives; ar; ar = ar->next)\n\t{\n\t\tif ((e = lmo_find_entry(ar, hash)) != NULL)\n\t\t{\n\t\t\t*out = ar->mmap + ntohl(e->offset);\n\t\t\t*outlen = ntohl(e->length);\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\nvoid lmo_close_catalog(const char *lang)\n{\n\tlmo_archive_t *ar, *next;\n\tlmo_catalog_t *cat, *prev;\n\n\tfor (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next)\n\t{\n\t\tif (!strncmp(cat->lang, lang, sizeof(cat->lang)))\n\t\t{\n\t\t\tif (prev)\n\t\t\t\tprev->next = cat->next;\n\t\t\telse\n\t\t\t\t_lmo_catalogs = cat->next;\n\n\t\t\tfor (ar = cat->archives; ar; ar = next)\n\t\t\t{\n\t\t\t\tnext = ar->next;\n\t\t\t\tlmo_close(ar);\n\t\t\t}\n\n\t\t\tfree(cat);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "package/tool/po2lmo/src/template_lmo.h",
    "content": "/*\n * lmo - Lua Machine Objects - General header\n *\n *   Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>\n *\n *  Licensed under the Apache License, Version 2.0 (the \"License\");\n *  you may not use this file except in compliance with the License.\n *  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n */\n\n#ifndef _TEMPLATE_LMO_H_\n#define _TEMPLATE_LMO_H_\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/mman.h>\n#include <arpa/inet.h>\n#include <unistd.h>\n#include <errno.h>\n#include <fnmatch.h>\n#include <dirent.h>\n#include <ctype.h>\n#include <limits.h>\n\n#if (defined(__GNUC__) && defined(__i386__))\n#define sfh_get16(d) (*((const uint16_t *) (d)))\n#else\n#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\\\n\t\t\t\t\t   +(uint32_t)(((const uint8_t *)(d))[0]) )\n#endif\n\n\nstruct lmo_entry {\n\tuint32_t key_id;\n\tuint32_t val_id;\n\tuint32_t offset;\n\tuint32_t length;\n} __attribute__((packed));\n\ntypedef struct lmo_entry lmo_entry_t;\n\n\nstruct lmo_archive {\n\tint         fd;\n\tint\t        length;\n\tuint32_t    size;\n\tlmo_entry_t *index;\n\tchar        *mmap;\n\tchar\t\t*end;\n\tstruct lmo_archive *next;\n};\n\ntypedef struct lmo_archive lmo_archive_t;\n\n\nstruct lmo_catalog {\n\tchar lang[6];\n\tstruct lmo_archive *archives;\n\tstruct lmo_catalog *next;\n};\n\ntypedef struct lmo_catalog lmo_catalog_t;\n\n\nuint32_t sfh_hash(const char *data, int len);\nuint32_t lmo_canon_hash(const char *data, int len);\n\nlmo_archive_t * lmo_open(const char *file);\nvoid lmo_close(lmo_archive_t *ar);\n\n\nextern lmo_catalog_t *_lmo_catalogs;\nextern lmo_catalog_t *_lmo_active_catalog;\n\nint lmo_load_catalog(const char *lang, const char *dir);\nint lmo_change_catalog(const char *lang);\nint lmo_translate(const char *key, int keylen, char **out, int *outlen);\nvoid lmo_close_catalog(const char *lang);\n\n#endif\n"
  },
  {
    "path": "package/windows/install.bat",
    "content": "@echo off\r\nset \"CURR_PATH=%~dp0\"\r\nset \"STARTUP_PATH=%userprofile%\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\"\r\nFOR /F %%i IN ('wsl pwd') DO @set DIR_IN_WSL=%%i\r\n\r\nwsl sudo %DIR_IN_WSL%/../../install -i\r\nIF NOT %ERRORLEVEL% == 0 (\r\n  echo Install smartdns failed.\r\n  pause\r\n  exit 1\r\n)\r\n\r\ncopy %CURR_PATH%\\wsl-run.vbs \"%STARTUP_PATH%/\"\r\nIF NOT %ERRORLEVEL% == 0 (\r\n  echo Install startup script failed.\r\n  pause\r\n  exit 1\r\n)\r\n\r\necho Install smartdns success\r\npause\r\n"
  },
  {
    "path": "package/windows/reload.bat",
    "content": "@echo off\r\nset \"CURR_PATH=%~dp0\"\r\nset \"STARTUP_PATH=%userprofile%\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\"\r\nFOR /F %%i IN ('wsl pwd') DO @set DIR_IN_WSL=%%i\r\n\r\nwsl sudo cp -avf %DIR_IN_WSL%/../../etc/smartdns/* /etc/smartdns/ \r\nIF NOT %ERRORLEVEL% == 0 (\r\n  echo copy smartdns configuration file failed.\r\n  pause\r\n  exit 1\r\n)\r\n\r\nwsl sudo /etc/init.d/smartdns restart\r\nIF NOT %ERRORLEVEL% == 0 (\r\n  echo reload smartdns failed.\r\n  pause\r\n  exit 1\r\n)\r\n\r\necho reload smartdns success\r\npause\r\n"
  },
  {
    "path": "package/windows/uninstall.bat",
    "content": "@echo off\r\nset \"CURR_PATH=%~dp0\"\r\nset \"STARTUP_PATH=%userprofile%\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\"\r\nFOR /F %%i IN ('wsl pwd') DO @set DIR_IN_WSL=%%i\r\n\r\nwsl sudo %DIR_IN_WSL%/../../install -u\r\nIF NOT %ERRORLEVEL% == 0 (\r\n  echo Uninstall smartdns failed.\r\n  pause \r\n  exit 1\r\n)\r\n\r\ndel \"%STARTUP_PATH%\\wsl-run.vbs\"\r\nIF NOT %ERRORLEVEL% == 0 (\r\n  echo Uninstall startup script failed.\r\n  pause \r\n  exit 1\r\n)\r\n\r\necho uninstall success\r\n\r\npause\r\n"
  },
  {
    "path": "package/windows/wsl-run.vbs",
    "content": "Set ws = WScript.CreateObject(\"WScript.Shell\")\r\nws.run \"wsl sudo /etc/init.d/smartdns restart\", vbhide"
  },
  {
    "path": "plugin/demo/.gitignore",
    "content": ".vscode\n*.o\n*.so\n*.swp.\n"
  },
  {
    "path": "plugin/demo/Makefile",
    "content": "\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nBIN=smartdns_demo.so \nOBJS_MAIN=$(patsubst %.c,%.o,$(wildcard *.c))\nOBJS=$(OBJS_MAIN)\n\n# cflags\nifndef CFLAGS\n ifdef DEBUG\n  CFLAGS = -g -DDEBUG\n else\n  CFLAGS = -O2\n endif\n CFLAGS +=-fPIC -Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing -funwind-tables -Wmissing-prototypes -Wshadow -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough\nendif\n\noverride CFLAGS +=-Iinclude -I../../src/include -I../../src\noverride CFLAGS += -DBASE_FILE_NAME='\"$(notdir $<)\"'\noverride CFLAGS += $(EXTRA_CFLAGS)\n\noverride LDFLAGS += -lpthread -shared\n\n.PHONY: all clean\n\nall: $(BIN)\n\n$(BIN) : $(OBJS)\n\t$(CC) $(OBJS) -o $@ $(LDFLAGS)\n\nclean:\n\t$(RM) $(OBJS) $(BIN)\n"
  },
  {
    "path": "plugin/demo/demo.c",
    "content": "#include \"demo.h\"\n#include \"smartdns/dns_server.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n#include <arpa/inet.h>\n#include <netinet/in.h>\n#include <stdio.h>\n#include <sys/socket.h>\n\nstatic int demo_server_recv(struct dns_packet *packet, unsigned char *inpacket, int inpacket_len,\n\t\t\t\t\t\t\tstruct sockaddr_storage *local, socklen_t local_len, struct sockaddr_storage *from,\n\t\t\t\t\t\t\tsocklen_t from_len)\n{\n\tchar hostname[256] = {0};\n\ttlog(TLOG_INFO, \"recv packet from %s\", get_host_by_addr(hostname, sizeof(hostname), (struct sockaddr *)from));\n\treturn 0;\n}\n\nstatic void demo_server_request_complete(struct dns_request *request)\n{\n\ttlog(TLOG_INFO, \"server complete request, request domain is %s\", dns_server_request_get_domain(request));\n}\n\nstruct smartdns_operations demo_ops = {\n\t.server_recv = demo_server_recv,\n\t.server_query_complete = demo_server_request_complete,\n};\n\nint dns_plugin_init(struct dns_plugin *plugin)\n{\n\tchar options[4096] = {0};\n\tint argc = dns_plugin_get_argc(plugin);\n\tconst char **argv = dns_plugin_get_argv(plugin);\n\n\tfor (int i = 0; i < argc; i++) {\n\t\tsnprintf(options + strlen(options), sizeof(options) - strlen(options), \"%s \", argv[i]);\n\t}\n\n\ttlog(TLOG_INFO, \"demo plugin init, options: %s\", options);\n\tsmartdns_operations_register(&demo_ops);\n\treturn 0;\n}\n\nint dns_plugin_exit(struct dns_plugin *plugin)\n{\n\ttlog(TLOG_INFO, \"demo plugin exit.\");\n\tsmartdns_operations_unregister(&demo_ops);\n\treturn 0;\n}\n\nint dns_plugin_api_version(void)\n{\n\treturn SMARTDNS_PLUGIN_API_VERSION;\n}"
  },
  {
    "path": "plugin/demo/demo.h",
    "content": "\n#ifndef SMART_DNS_PLUGIN_DEMO_H\n#define SMART_DNS_PLUGIN_DEMO_H\n\n#include \"smartdns/dns_plugin.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "plugin/smartdns-ui/.gitignore",
    "content": "/target\nCargo.lock"
  },
  {
    "path": "plugin/smartdns-ui/Cargo.toml",
    "content": "[package]\nname = \"smartdns-ui\"\nversion = \"1.0.0\"\nedition = \"2021\"\n\n[lib]\ncrate-type = [\"cdylib\", \"lib\"]\n\n[dependencies]\nctor = \"0.4.3\"\nbytes = \"1.11.1\"\nrusqlite = { version = \"0.37.0\", features = [\"bundled\"] }\nhyper = { version = \"1.8.1\", features = [\"full\"] }\nhyper-util = { version = \"0.1.20\", features = [\"full\"] }\nhyper-tungstenite = \"0.18.0\"\ntokio = { version = \"1.50.0\", features = [\"full\"] }\nserde = { version = \"1.0.228\", features = [\"derive\"] }\ntokio-rustls = { version = \"0.26.4\", default-features = false, features = [\"ring\", \"tls12\"], optional = true }\nrustls = { version = \"0.23.37\", default-features = false, features = [\"ring\", \"tls12\"] }\nrustls-pemfile = { version = \"2.2.0\", optional = true}\nserde_json = \"1.0.149\"\nhttp-body-util = \"0.1.3\"\ngetopts = \"0.2.24\"\nurl = \"2.5.8\"\njsonwebtoken = { version = \"10.3.0\", features = [\"rust_crypto\"] }\nmatchit = \"0.8.6\"\nfutures = \"0.3.32\"\nsocket2 = \"0.6.3\"\ncfg-if = \"1.0.4\"\nurlencoding = \"2.1.3\"\nchrono = \"0.4.44\"\nnix = \"0.30.1\"\ntokio-fd = \"0.3.0\"\npbkdf2 = { version = \"0.12.2\", features = [\"simple\"] }\nrand_core = { version = \"0.6\", features = [\"std\"] }\n\n[features]\nbuild-release = []\nhttps = [\"tokio-rustls\", \"rustls-pemfile\"]\ndefault = [\"https\"]\n\n[dev-dependencies]\nreqwest = {version = \"0.12.28\", features = [\"blocking\"]}\ntungstenite = \"0.23.0\"\ntokio-tungstenite = \"0.23.1\"\ntempfile = \"3.27.0\"\n\n[build-dependencies]\nbindgen = \"0.69.5\"\n\n[profile.release-optmize-size]\ninherits = \"release\"\nlto = true\nopt-level = \"s\"\nstrip = true\ncodegen-units = 1\npanic = \"abort\"\n"
  },
  {
    "path": "plugin/smartdns-ui/Makefile",
    "content": "\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nBIN=smartdns-ui\nPREFIX := /usr\nSBINDIR := $(PREFIX)/sbin\nSLIBDIR := $(PREFIX)/lib\nDESTDIR :=\nCARGO_BUILD_ENV :=\nSMARTDNS_SRC_DIR=../../src\n \nifeq ($(origin CC), environment)\n  ifeq ($(CARGO_BUILD_TARGET),)\n    ifeq ($(CARGO_BUILD_ARGS),)\n      # find default target by compiler\n      ARCH_TARGET:=$(shell $(CC) -dumpmachine)\n      override CARGO_BUILD_TARGET:=$(shell rustc --print target-list | grep $(ARCH_TARGET) | head -n 1)\n\n      ifeq ($(CARGO_BUILD_TARGET),)\n        # not found, try to find target by compiler\n        ARCH_TARGET:=$(shell $(CC) -dumpmachine | sed 's/-[^-]*-linux-/-linux-/')\n        ARCH=$(shell echo $(ARCH_TARGET) | cut -d - -f 1)\n        ABI=$(shell echo $(ARCH_TARGET) | cut -d - -f 3)\n        ifneq ($(ABI),)\n          # force set target to $(ARCH)-unknown-linux-$(ABI)\n          override CARGO_BUILD_TARGET:=$(shell rustc --print target-list | grep $(ARCH) | grep linux | grep $(ABI) | grep unknown | head -n 1)\n        endif\n      endif\n\n      ifneq ($(CARGO_BUILD_TARGET),)\n        ARCH_TARGET_PATH=$(CARGO_BUILD_TARGET)/\n        override CARGO_RUSTFLAGS=-C linker=$(CC)\n         CARGO_BUILD_ARGS +=--target=$(CARGO_BUILD_TARGET)\n      endif\n    endif\n  endif\n\n  IS_NATIVE_BUILD=$(shell [ \"$(CC)\" = \"cc\" ] || [ \"$(CC)\" = \"gcc\" ] && echo 1 || echo 0)\n  ifeq ($(IS_NATIVE_BUILD), 0)\n    # find sysroot\n    SYSROOT:=$(shell $(CC) -print-sysroot)\n    ifeq ($(SYSROOT),)\n      # if sysroot is not set, try find sysroot from compiler default include path\n      SYSROOT:=$(shell $(CC) -xc /dev/null -E -Wp,-v 2>&1 | sed -n 's,^ ,,p' | head -n 1)\n      ifneq ($(SYSROOT),)\n        # find sysroot, add sysroot to BINDGEN_EXTRA_CLANG_ARGS\n        SYSROOT:=$(SYSROOT)/..\n        override CARGO_BUILD_ENV += BINDGEN_EXTRA_CLANG_ARGS=\"--sysroot=$(SYSROOT)\"\n        CARGO_BUILD_ARGS +=--target=$(CARGO_BUILD_TARGET)\n      endif\n    endif\n  endif\nendif\n\nifneq ($(CARGO_BUILD_TARGET),)\n  ARCH_TARGET_PATH=$(CARGO_BUILD_TARGET)/\nendif\n\n# check and install bindgen\nIS_BINDGEN_INSTALL=$(shell which bindgen 2>/dev/null)\nifeq ($(IS_BINDGEN_INSTALL),)\n  $(shell cargo install --force --locked bindgen-cli)\nendif\n\nifneq ($(CARGO_BUILD_TARGET),)\n  CARGO_BUILD_TARGET_INSTALL=$(shell rustup target list | grep $(CARGO_BUILD_TARGET) | grep installed)\n  ifeq ($(CARGO_BUILD_TARGET_INSTALL),)\n    # install target\n    $(shell rustup target add $(CARGO_BUILD_TARGET))\n  endif\nendif\n\nIS_MUSL=$(shell $(CC) -v 2>&1 | grep -i musl >/dev/null 2>&1 && echo 1 || echo 0)\nifeq ($(IS_MUSL), 1)\noverride CARGO_RUSTFLAGS +=-C target-feature=-crt-static\nendif\n\nifdef DEBUG\n  CARGO_BUILD_PATH=target/$(ARCH_TARGET_PATH)debug\n  SMARTDNS_BUILD_TYPE=DEBUG=1\nelse\n  ifdef OPTIMIZE_SIZE\n    CARGO_BUILD_ARGS += --profile release-optmize-size\n\tCARGO_BUILD_PATH=target/$(ARCH_TARGET_PATH)release-optmize-size\n  else\n    CARGO_BUILD_ARGS +=--release\n  \tCARGO_BUILD_PATH=target/$(ARCH_TARGET_PATH)release\n  endif\n\n  SMARTDNS_BUILD_TYPE=\nendif\n\n.PHONY: all clean install $(BIN)\n\nall: $(BIN)\n\ntest-prepare:\n\t$(MAKE) -C $(SMARTDNS_SRC_DIR) libsmartdns-test.a\n\n$(BIN):\n\tCXXFLAGS= CFLAGS= MAKEFLAGS= RUSTFLAGS=\"$(CARGO_RUSTFLAGS) $(RUSTFLAGS)\" $(CARGO_BUILD_ENV) cargo build $(CARGO_BUILD_ARGS) --features \"build-release\"\n\tcp $(CARGO_BUILD_PATH)/libsmartdns_ui.so target/smartdns_ui.so\n\ninstall: $(BIN)\n\tinstall -v -m 0644 -D -t $(DESTDIR)$(SLIBDIR)/smartdns target/smartdns_ui.so\n\ntest: test-prepare\n\tMAKEFLAGS= cargo test\n\nclean:\n\tcargo clean\n\t$(MAKE) -C $(SMARTDNS_SRC_DIR) clean\n\trm -rf target/smartdns_ui.so\n"
  },
  {
    "path": "plugin/smartdns-ui/build.rs",
    "content": "use std::collections::HashSet;\nuse std::env;\nuse std::path::PathBuf;\n\n#[derive(Debug)]\nstruct IgnoreMacros(HashSet<String>);\n\nimpl bindgen::callbacks::ParseCallbacks for IgnoreMacros {\n    fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {\n        if self.0.contains(name) {\n            bindgen::callbacks::MacroParsingBehavior::Ignore\n        } else {\n            bindgen::callbacks::MacroParsingBehavior::Default\n        }\n    }\n}\n\nfn get_git_commit_version() {\n    let result = std::process::Command::new(\"git\")\n        .args(&[\"describe\", \"--tags\", \"--always\", \"--dirty\"])\n        .output();\n\n    let git_version = match result {\n        Ok(output) => output.stdout,\n        Err(_) => Vec::new(),\n    };\n\n    let git_version = String::from_utf8(git_version).expect(\"Invalid UTF-8 sequence\");\n    println!(\"cargo:rustc-env=GIT_VERSION={}\", git_version.trim());\n}\n\nfn link_rename_lib() {\n    /*\n    rename the output file to smartdns_ui.so\n    */\n    let release_plugin = env::var(\"RELEASE_PLUGIN\").is_ok();\n\n    if release_plugin == false {\n        // In debug mode, we don't rename the output file\n        return;\n    }\n\n    let curr_source_dir = env::var(\"CARGO_MANIFEST_DIR\").unwrap();\n    let target_dir =\n        env::var(\"CARGO_TARGET_DIR\").unwrap_or_else(|_| format!(\"{}/target\", curr_source_dir));\n    let crate_name = std::env::var(\"CARGO_PKG_NAME\").unwrap().replace(\"-\", \"_\");\n    let so_path = format!(\"{}/{}.so\", target_dir, crate_name);\n    println!(\"cargo:rustc-link-arg=-o\");\n    println!(\"cargo:rustc-link-arg={}\", so_path);\n}\n\nfn link_smartdns_lib() {\n    let curr_source_dir = env::var(\"CARGO_MANIFEST_DIR\").unwrap();\n    let smartdns_src_dir = format!(\"{}/../../src\", curr_source_dir);\n    let smartdns_inc_dir = format!(\"{}/include\", smartdns_src_dir);\n    let smartdns_lib_file = format!(\"{}/libsmartdns-test.a\", smartdns_src_dir);\n\n    let cc = env::var(\"RUSTC_LINKER\")\n        .unwrap_or_else(|_| env::var(\"CC\").unwrap_or_else(|_| \"cc\".to_string()));\n\n    let sysroot_output = std::process::Command::new(&cc)\n        .arg(\"--print-sysroot\")\n        .output();\n    let mut sysroot = None;\n    if let Ok(output) = sysroot_output {\n        if output.status.success() {\n            let path = String::from_utf8(output.stdout).unwrap();\n            sysroot = Some(path.trim().to_string());\n        }\n    }\n\n    let ignored_macros = IgnoreMacros(vec![\"IPPORT_RESERVED\".into()].into_iter().collect());\n\n    let mut bindings_builder =\n        bindgen::Builder::default().header(format!(\"{}/smartdns/smartdns.h\", smartdns_inc_dir));\n    if let Some(sysroot) = sysroot {\n        bindings_builder = bindings_builder.clang_arg(format!(\"--sysroot={}\", sysroot));\n    }\n    let bindings = bindings_builder\n        .clang_arg(format!(\"-I{}/include\", smartdns_src_dir))\n        .parse_callbacks(Box::new(ignored_macros))\n        .generate()\n        .expect(\"Unable to generate bindings\");\n\n    let out_path = PathBuf::from(env::var(\"OUT_DIR\").unwrap());\n    bindings\n        .write_to_file(out_path.join(\"smartdns_bindings.rs\"))\n        .expect(\"Couldn't write bindings!\");\n    /*\n    to run tests, please run the following command:\n    make test-prepare\n    */\n    if std::path::Path::new(&smartdns_lib_file).exists() && !cfg!(feature = \"build-release\") {\n        println!(\"cargo:rerun-if-changed={}\", smartdns_lib_file);\n        println!(\"cargo:rustc-link-lib=static=smartdns-test\");\n        println!(\"cargo:rustc-link-lib=ssl\");\n        println!(\"cargo:rustc-link-lib=crypto\");\n        println!(\"cargo:rustc-link-search=native={}\", smartdns_src_dir);\n    }\n}\n\nfn main() {\n    get_git_commit_version();\n    link_smartdns_lib();\n    link_rename_lib();\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/data_server.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse crate::data_stats::*;\nuse crate::data_upstream_server::UpstreamServerInfo;\nuse crate::db::*;\nuse crate::dns_log;\nuse crate::plugin::SmartdnsPlugin;\nuse crate::server_log::ServerAuditLog;\nuse crate::server_log::ServerAuditLogMsg;\nuse crate::server_log::ServerLog;\nuse crate::server_log::ServerLogMsg;\nuse crate::smartdns;\nuse crate::smartdns::*;\nuse crate::utils;\nuse crate::whois;\nuse crate::whois::WhoIsInfo;\n\nuse std::collections::HashMap;\nuse std::error::Error;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::Weak;\nuse std::sync::{Arc, Mutex, RwLock};\nuse tokio::sync::mpsc;\nuse tokio::task::JoinHandle;\nuse tokio::time::Duration;\nuse tokio::time::Instant;\n\npub const DEFAULT_MAX_LOG_AGE: u64 = 30 * 24 * 60 * 60;\npub const DEFAULT_MAX_LOG_AGE_MS: u64 = DEFAULT_MAX_LOG_AGE * 1000;\npub const MAX_LOG_AGE_VALUE_MIN: u64 = 600;\npub const MAX_LOG_AGE_VALUE_MAX: u64 = 365 * 24 * 60 * 60 * 10;\npub const MIN_FREE_DISK_SPACE: u64 = 1024 * 1024 * 8;\npub const DB_FILE_NAME: &str = \"smartdns.db\";\n\n#[derive(Clone)]\npub struct OverviewData {\n    pub server_name: String,\n    pub db_size: u64,\n    pub startup_timestamp: u64,\n    pub free_disk_space: u64,\n    pub is_process_suspended: bool,\n}\n\n#[derive(Clone)]\npub struct MetricsData {\n    pub total_query_count: u64,\n    pub block_query_count: u64,\n    pub request_drop_count: u64,\n    pub fail_query_count: u64,\n    pub avg_query_time: f64,\n    pub cache_hit_rate: f64,\n    pub cache_number: u64,\n    pub cache_memory_size: u64,\n    pub qps: u32,\n    pub memory_usage: u64,\n    pub is_metrics_suspended: bool,\n}\n\n#[derive(Clone)]\npub struct DataServerConfig {\n    pub db_file: String,\n    pub data_path: String,\n    pub max_log_age_ms: u64,\n}\n\nimpl DataServerConfig {\n    pub fn new() -> Self {\n        DataServerConfig {\n            data_path: Plugin::dns_conf_data_dir(),\n            db_file: Plugin::dns_conf_data_dir() + \"/\" + DB_FILE_NAME,\n            max_log_age_ms: DEFAULT_MAX_LOG_AGE_MS,\n        }\n    }\n\n    pub fn load_config(&mut self, data_server: Arc<DataServer>) -> Result<(), Box<dyn Error>> {\n        self.max_log_age_ms = utils::parse_value(\n            data_server.get_server_config(\"smartdns-ui.max-query-log-age\"),\n            MAX_LOG_AGE_VALUE_MIN,\n            MAX_LOG_AGE_VALUE_MAX,\n            DEFAULT_MAX_LOG_AGE,\n        ) * 1000;\n\n        let log_level = data_server.get_server_config(\"log-level\");\n        if let Some(log_level) = log_level {\n            let log_level = log_level.try_into();\n            match log_level {\n                Ok(log_level) => {\n                    dns_log_set_level(log_level);\n                }\n                Err(_) => {\n                    dns_log!(LogLevel::WARN, \"log level is invalid\");\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\npub struct DataServerControl {\n    data_server: Arc<DataServer>,\n    server_thread: Mutex<Option<JoinHandle<()>>>,\n    is_init: Mutex<bool>,\n    is_run: Mutex<bool>,\n    plugin: Mutex<Weak<SmartdnsPlugin>>,\n}\n\nimpl DataServerControl {\n    pub fn new() -> Self {\n        DataServerControl {\n            data_server: Arc::new(DataServer::new()),\n            server_thread: Mutex::new(None),\n            is_init: Mutex::new(false),\n            is_run: Mutex::new(false),\n            plugin: Mutex::new(Weak::new()),\n        }\n    }\n\n    pub fn get_data_server(&self) -> Arc<DataServer> {\n        Arc::clone(&self.data_server)\n    }\n\n    pub fn set_plugin(&self, plugin: Arc<SmartdnsPlugin>) {\n        *self.plugin.lock().unwrap() = Arc::downgrade(&plugin);\n    }\n\n    pub fn get_plugin(&self) -> Result<Arc<SmartdnsPlugin>, Box<dyn Error>> {\n        let plugin = match self.plugin.lock() {\n            Ok(plugin) => plugin,\n            Err(_) => return Err(\"Failed to lock plugin mutex\".into()),\n        };\n\n        if let Some(plugin) = plugin.upgrade() {\n            return Ok(plugin);\n        }\n        Err(\"Plugin is not set\".into())\n    }\n\n    pub fn init_db(&self, conf: &DataServerConfig) -> Result<(), Box<dyn Error>> {\n        let inner_clone = Arc::clone(&self.data_server);\n        let ret = inner_clone.init_server(conf);\n        if let Err(e) = ret {\n            return Err(e);\n        }\n\n        *self.is_init.lock().unwrap() = true;\n        Ok(())\n    }\n\n    pub fn start_data_server(&self) -> Result<(), Box<dyn Error>> {\n        let inner_clone = Arc::clone(&self.data_server);\n\n        if *self.is_init.lock().unwrap() == false {\n            return Err(\"data server not init\".into());\n        }\n\n        let plugin = self.get_plugin()?;\n        self.data_server.set_plugin(plugin.clone());\n        let rt = plugin.get_runtime();\n\n        let server_thread = rt.spawn(async move {\n            let ret = DataServer::data_server_loop(inner_clone).await;\n            if let Err(e) = ret {\n                dns_log!(LogLevel::ERROR, \"data server error: {}\", e);\n                Plugin::smartdns_exit(1);\n            }\n\n            dns_log!(LogLevel::DEBUG, \"data server exit.\");\n        });\n\n        *self.is_run.lock().unwrap() = true;\n        *self.server_thread.lock().unwrap() = Some(server_thread);\n        Ok(())\n    }\n\n    pub fn stop_data_server(&self) {\n        if *self.is_run.lock().unwrap() == false {\n            return;\n        }\n\n        self.data_server.stop_data_server();\n        let _server_thread = self.server_thread.lock().unwrap().take();\n        if let Some(server_thread) = _server_thread {\n            let plugin = self.get_plugin();\n            if plugin.is_err() {\n                dns_log!(\n                    LogLevel::ERROR,\n                    \"get plugin error: {}\",\n                    plugin.err().unwrap()\n                );\n                return;\n            }\n\n            let plugin = plugin.unwrap();\n            let rt = plugin.get_runtime();\n            tokio::task::block_in_place(|| {\n                if let Err(e) = rt.block_on(server_thread) {\n                    dns_log!(LogLevel::ERROR, \"http server stop error: {}\", e);\n                }\n            });\n        }\n        *self.is_run.lock().unwrap() = false;\n    }\n\n    pub fn send_request(&self, request: Box<dyn DnsRequest>) -> Result<(), Box<dyn Error>> {\n        if request.is_prefetch_request() {\n            return Ok(());\n        }\n\n        self.data_server.get_stat().add_qps_count(1);\n\n        if self.data_server.is_handle_request_disabled() {\n            return Ok(());\n        }\n\n        if let Some(tx) = self.data_server.data_tx.as_ref() {\n            let ret = tx.try_send(request);\n            if let Err(e) = ret {\n                self.data_server.get_stat().add_request_drop(1);\n                return Err(e.to_string().into());\n            }\n        }\n        Ok(())\n    }\n\n    pub fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32) {\n        self.data_server.server_log(level, msg, msg_len);\n    }\n\n    pub fn server_audit_log(&self, msg: &str, msg_len: i32) {\n        self.data_server.server_audit_log(msg, msg_len);\n    }\n}\n\nimpl Drop for DataServerControl {\n    fn drop(&mut self) {\n        self.stop_data_server();\n    }\n}\n\npub struct DataServer {\n    conf: Arc<RwLock<DataServerConfig>>,\n    notify_tx: Option<mpsc::Sender<()>>,\n    notify_rx: Mutex<Option<mpsc::Receiver<()>>>,\n    data_tx: Option<mpsc::Sender<Box<dyn DnsRequest>>>,\n    data_rx: Mutex<Option<mpsc::Receiver<Box<dyn DnsRequest>>>>,\n    db: Arc<DB>,\n    disable_handle_request: AtomicBool,\n    stat: Arc<DataStats>,\n    server_log: ServerLog,\n    server_audit_log: ServerAuditLog,\n    plugin: Mutex<Weak<SmartdnsPlugin>>,\n    whois: whois::WhoIs,\n    startup_timestamp: u64,\n    recv_in_batch: Mutex<bool>,\n    mac_cache: Mutex<HashMap<String, String>>,\n    client_pending_list: Mutex<HashMap<String, (u64, ClientData)>>,\n}\n\nimpl DataServer {\n    pub fn new() -> Self {\n        let db = Arc::new(DB::new());\n        let conf = Arc::new(RwLock::new(DataServerConfig::new()));\n        let mut plugin = DataServer {\n            conf: conf.clone(),\n            notify_tx: None,\n            notify_rx: Mutex::new(None),\n            data_tx: None,\n            data_rx: Mutex::new(None),\n            db: db.clone(),\n            stat: DataStats::new(db, conf.clone()),\n            server_log: ServerLog::new(),\n            server_audit_log: ServerAuditLog::new(),\n            plugin: Mutex::new(Weak::new()),\n            whois: whois::WhoIs::new(),\n            startup_timestamp: get_utc_time_ms(),\n            disable_handle_request: AtomicBool::new(false),\n            recv_in_batch: Mutex::new(true),\n            mac_cache: Mutex::new(HashMap::new()),\n            client_pending_list: Mutex::new(HashMap::new()),\n        };\n\n        let (tx, rx) = mpsc::channel(100);\n        plugin.notify_tx = Some(tx);\n        plugin.notify_rx = Mutex::new(Some(rx));\n\n        let (tx, rx) = mpsc::channel(1024 * 256);\n        plugin.data_tx = Some(tx);\n        plugin.data_rx = Mutex::new(Some(rx));\n\n        plugin\n    }\n\n    pub fn get_recv_in_batch(&self) -> bool {\n        *self.recv_in_batch.lock().unwrap()\n    }\n\n    pub fn set_recv_in_batch(&self, recv_in_batch: bool) {\n        *self.recv_in_batch.lock().unwrap() = recv_in_batch;\n    }\n\n    fn init_server(&self, conf: &DataServerConfig) -> Result<(), Box<dyn Error>> {\n        let mut conf_clone = self.conf.write().unwrap();\n        *conf_clone = conf.clone();\n\n        smartdns::smartdns_enable_update_neighbour(true);\n\n        if utils::is_dir_writable(&conf_clone.data_path) == false {\n            return Err(format!(\n                \"data path '{}' is not exist or writable.\",\n                conf_clone.data_path\n            )\n            .into());\n        }\n\n        conf_clone.db_file = conf_clone.data_path.clone() + \"/\" + DB_FILE_NAME;\n        dns_log!(LogLevel::INFO, \"open db: {}\", conf_clone.db_file);\n        let ret = self.db.open(&conf_clone.db_file);\n        if let Err(e) = ret {\n            return Err(e);\n        }\n\n        let ret = self.stat.init();\n        if let Err(e) = ret {\n            return Err(e);\n        }\n\n        Ok(())\n    }\n\n    pub fn set_plugin(&self, plugin: Arc<SmartdnsPlugin>) {\n        *self.plugin.lock().unwrap() = Arc::downgrade(&plugin);\n    }\n\n    pub fn get_plugin(&self) -> Result<Arc<SmartdnsPlugin>, Box<dyn Error>> {\n        let plugin = match self.plugin.lock() {\n            Ok(plugin) => plugin,\n            Err(_) => return Err(\"Failed to lock plugin mutex\".into()),\n        };\n\n        if let Some(plugin) = plugin.upgrade() {\n            return Ok(plugin);\n        }\n        Err(\"Plugin is not set\".into())\n    }\n\n    pub fn get_data_server_config(&self) -> DataServerConfig {\n        let conf = self.conf.read().unwrap();\n        conf.clone()\n    }\n\n    pub fn get_config(&self, key: &str) -> Option<String> {\n        let ret = self.db.get_config(key);\n        if let Ok(value) = ret {\n            return value;\n        }\n\n        None\n    }\n\n    pub fn get_server_config_from_file(&self, key: &str) -> Option<String> {\n        let ret = Plugin::dns_conf_plugin_config(key);\n        if let Some(value) = ret {\n            return Some(value);\n        }\n\n        None\n    }\n\n    pub fn get_server_config(&self, key: &str) -> Option<String> {\n        let ret = self.get_config(key);\n        if let Some(value) = ret {\n            return Some(value);\n        }\n\n        let ret = Plugin::dns_conf_plugin_config(key);\n        if let Some(value) = ret {\n            return Some(value);\n        }\n\n        None\n    }\n\n    pub async fn whois(&self, domain: &str) -> Result<WhoIsInfo, Box<dyn Error>> {\n        self.whois.query(domain).await\n    }\n\n    pub fn get_config_list(&self) -> Result<HashMap<String, String>, Box<dyn Error>> {\n        self.db.get_config_list()\n    }\n\n    pub fn set_config(&self, key: &str, value: &str) -> Result<(), Box<dyn Error>> {\n        self.db.set_config(key, value)\n    }\n\n    pub fn get_upstream_server_list(&self) -> Result<Vec<UpstreamServerInfo>, Box<dyn Error>> {\n        let servers = UpstreamServerInfo::get_all()?;\n        Ok(servers)\n    }\n\n    pub fn get_domain_list(\n        &self,\n        param: &DomainListGetParam,\n    ) -> Result<QueryDomainListResult, Box<dyn Error>> {\n        self.db.get_domain_list(Some(param))\n    }\n\n    pub fn get_domain_list_count(&self) -> u64 {\n        self.db.get_domain_list_count(None)\n    }\n\n    pub fn delete_domain_by_id(&self, id: u64) -> Result<u64, Box<dyn Error>> {\n        self.db.delete_domain_by_id(id)\n    }\n\n    pub fn delete_domain_before_timestamp(&self, timestamp: u64) -> Result<u64, Box<dyn Error>> {\n        self.db.delete_domain_before_timestamp(timestamp)\n    }\n\n    pub fn delete_client_by_id(&self, id: u64) -> Result<u64, Box<dyn Error>> {\n        self.db.delete_client_by_id(id)\n    }\n\n    pub fn get_client_list(\n        &self,\n        param: &ClientListGetParam,\n    ) -> Result<QueryClientListResult, Box<dyn Error>> {\n        self.db.get_client_list(Some(param))\n    }\n\n    pub fn get_top_client_top_list(\n        &self,\n        count: Option<u32>,\n    ) -> Result<Vec<ClientQueryCount>, Box<dyn Error>> {\n        self.db.get_client_top_list(count.unwrap_or(10))\n    }\n\n    pub fn get_top_domain_top_list(\n        &self,\n        count: Option<u32>,\n    ) -> Result<Vec<DomainQueryCount>, Box<dyn Error>> {\n        self.db.get_domain_top_list(count.unwrap_or(10))\n    }\n\n    pub fn get_hourly_query_count(\n        &self,\n        past_hours: Option<u32>,\n    ) -> Result<HourlyQueryCount, Box<dyn Error>> {\n        self.db.get_hourly_query_count(past_hours.unwrap_or(24))\n    }\n\n    pub fn get_daily_query_count(\n        &self,\n        past_days: Option<u32>,\n    ) -> Result<DailyQueryCount, Box<dyn Error>> {\n        self.db.get_daily_query_count(past_days.unwrap_or(30))\n    }\n\n    pub fn get_stat(&self) -> Arc<DataStats> {\n        self.stat.clone()\n    }\n\n    pub fn get_metrics(&self) -> Result<MetricsData, Box<dyn Error + Send>> {\n        let metrics = MetricsData {\n            total_query_count: self.stat.get_total_request(),\n            block_query_count: self.stat.get_total_blocked_request(),\n            request_drop_count: self.stat.get_request_drop(),\n            fail_query_count: self.stat.get_total_failed_request(),\n            avg_query_time: smartdns::Stats::get_avg_process_time(),\n            cache_hit_rate: smartdns::Stats::get_cache_hit_rate(),\n            cache_number: smartdns::Plugin::dns_cache_total_num() as u64,\n            cache_memory_size: smartdns::Stats::get_cache_memory_size(),\n            qps: self.stat.get_qps(),\n            memory_usage: self.stat.get_memory_usage(),\n            is_metrics_suspended: self.is_handle_request_disabled(),\n        };\n\n        Ok(metrics)\n    }\n\n    pub fn is_handle_request_disabled(&self) -> bool {\n        self.disable_handle_request\n            .load(std::sync::atomic::Ordering::Relaxed)\n    }\n\n    pub fn get_free_disk_space(&self) -> u64 {\n        utils::get_free_disk_space(&self.get_data_server_config().db_file)\n    }\n\n    pub fn get_overview(&self) -> Result<OverviewData, Box<dyn Error + Send>> {\n        let overview = OverviewData {\n            server_name: smartdns::smartdns_get_server_name(),\n            db_size: self.db.get_db_size(),\n            startup_timestamp: self.startup_timestamp,\n            free_disk_space: self.get_free_disk_space(),\n            is_process_suspended: self.is_handle_request_disabled(),\n        };\n\n        Ok(overview)\n    }\n\n    pub fn insert_client_by_list(&self, data: &Vec<ClientData>) -> Result<(), Box<dyn Error>> {\n        self.db.insert_client(data)\n    }\n\n    pub fn insert_domain_by_list(&self, data: &Vec<DomainData>) -> Result<(), Box<dyn Error>> {\n        self.db.insert_domain(data)\n    }\n\n    pub fn insert_domain(&self, data: &DomainData) -> Result<(), Box<dyn Error>> {\n        let list = vec![data.clone()];\n        self.stat.add_total_request(1);\n        if data.is_blocked {\n            self.stat.add_total_blocked_request(1);\n        }\n\n        if data.reply_code != 0 {\n            self.stat.add_total_failed_request(1);\n        }\n\n        self.db.insert_domain(&list)\n    }\n\n    async fn data_server_handle_dns_request(\n        this: Arc<DataServer>,\n        req_list: &Vec<Box<dyn DnsRequest>>,\n    ) {\n        let mut domain_data_list = Vec::new();\n        let mut client_data_list = Vec::new();\n        let mut blocked_num = 0;\n        let mut failed_num = 0;\n        let timestamp_now = get_utc_time_ms();\n\n        // Pass 1: populate cache from incoming requests\n        {\n            let mut mac_cache = this.mac_cache.lock().unwrap();\n            if mac_cache.len() > 10000 {\n                mac_cache.clear();\n            }\n\n            for req in req_list {\n                let mac_str = req\n                    .get_remote_mac()\n                    .iter()\n                    .map(|byte| format!(\"{:02x}\", byte))\n                    .collect::<Vec<String>>()\n                    .join(\":\");\n                if mac_str != \"00:00:00:00:00:00\" {\n                    mac_cache.insert(req.get_remote_addr(), mac_str);\n                }\n            }\n        }\n\n        for req in req_list {\n            if req.is_prefetch_request() {\n                continue;\n            }\n\n            if req.is_dualstack_request() {\n                continue;\n            }\n\n            if req.get_is_blocked() {\n                blocked_num += 1;\n            }\n\n            if req.get_rcode() != 0 {\n                failed_num += 1;\n            }\n\n            let domain_data = DomainData {\n                id: 0,\n                domain: req.get_domain(),\n                domain_type: req.get_qtype(),\n                client: req.get_remote_addr(),\n                domain_group: req.get_group_name(),\n                reply_code: req.get_rcode(),\n                timestamp: req.get_query_timestamp(),\n                query_time: req.get_query_time(),\n                ping_time: req.get_ping_time(),\n                is_blocked: req.get_is_blocked(),\n                is_cached: req.get_is_cached(),\n            };\n            dns_log!(\n                LogLevel::DEBUG,\n                \"insert domain:{}, type:{}\",\n                domain_data.domain,\n                domain_data.domain_type\n            );\n\n            domain_data_list.push(domain_data);\n\n            let client_ip = req.get_remote_addr();\n            let mut mac_str = req\n                .get_remote_mac()\n                .iter()\n                .map(|byte| format!(\"{:02x}\", byte))\n                .collect::<Vec<String>>()\n                .join(\":\");\n\n            if mac_str == \"00:00:00:00:00:00\" {\n                if let Some(cached_mac) = this.mac_cache.lock().unwrap().get(&client_ip) {\n                    mac_str = cached_mac.clone();\n                }\n            }\n\n            let mut pending = this.client_pending_list.lock().unwrap();\n            if let Some(existing) = pending.get_mut(&client_ip) {\n                if mac_str != \"00:00:00:00:00:00\" {\n                    existing.1.mac = mac_str;\n                }\n                existing.1.last_query_timestamp = timestamp_now;\n            } else {\n                let client_data = ClientData {\n                    id: 0,\n                    client_ip: client_ip.clone(),\n                    hostname: \"\".to_string(),\n                    mac: mac_str,\n                    last_query_timestamp: timestamp_now,\n                };\n                pending.insert(client_ip, (timestamp_now, client_data));\n            }\n        }\n\n        {\n            let mut pending = this.client_pending_list.lock().unwrap();\n            let mut to_remove = Vec::new();\n            for (ip, (first_seen, data)) in pending.iter() {\n                if timestamp_now - *first_seen > 3000 || data.mac != \"00:00:00:00:00:00\" {\n                    to_remove.push(ip.clone());\n                }\n            }\n            for ip in to_remove {\n                if let Some((_, data)) = pending.remove(&ip) {\n                    client_data_list.push(data);\n                }\n            }\n        }\n\n        this.stat.add_total_request(domain_data_list.len() as u64);\n        this.stat.add_total_blocked_request(blocked_num as u64);\n        this.stat.add_total_failed_request(failed_num as u64);\n\n        dns_log!(\n            LogLevel::DEBUG,\n            \"insert domain list count:{}\",\n            domain_data_list.len()\n        );\n\n        let ret = DataServer::call_blocking(this.clone(), move || {\n            let _ = match this.insert_domain_by_list(&domain_data_list) {\n                Ok(v) => v,\n                Err(e) => return Err(e.to_string()),\n            };\n\n            let ret = match this.insert_client_by_list(&client_data_list) {\n                Ok(v) => v,\n                Err(e) => return Err(e.to_string()),\n            };\n\n            Ok(ret)\n        })\n        .await;\n\n        if let Err(e) = ret {\n            dns_log!(LogLevel::ERROR, \"insert domain error: {}\", e);\n            return;\n        }\n\n        let ret = ret.unwrap();\n\n        if let Err(e) = ret {\n            dns_log!(LogLevel::ERROR, \"insert domain error: {}\", e);\n        }\n    }\n\n    pub async fn get_log_stream(&self) -> mpsc::Receiver<ServerLogMsg> {\n        return self.server_log.get_log_stream().await;\n    }\n\n    pub fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32) {\n        self.server_log.dispatch_log(level, msg, msg_len);\n    }\n\n    pub async fn get_audit_log_stream(&self) -> mpsc::Receiver<ServerAuditLogMsg> {\n        return self.server_audit_log.get_audit_log_stream().await;\n    }\n\n    pub fn server_audit_log(&self, msg: &str, msg_len: i32) {\n        self.server_audit_log.dispatch_audit_log(msg, msg_len);\n    }\n\n    fn server_check(&self) {\n        let free_disk_space = self.get_free_disk_space();\n        if free_disk_space < MIN_FREE_DISK_SPACE {\n            if self\n                .disable_handle_request\n                .fetch_or(true, std::sync::atomic::Ordering::Relaxed)\n            {\n                return;\n            }\n\n            dns_log!(\n                LogLevel::WARN,\n                \"free disk space is low, stop handle request. {}\",\n                self.disable_handle_request\n                    .load(std::sync::atomic::Ordering::Relaxed)\n            );\n        } else {\n            if !self\n                .disable_handle_request\n                .load(std::sync::atomic::Ordering::Relaxed)\n            {\n                return;\n            }\n\n            self.disable_handle_request\n                .store(false, std::sync::atomic::Ordering::Relaxed);\n            dns_log!(\n                LogLevel::INFO,\n                \"free disk space is enough, start handle request.\"\n            );\n        }\n    }\n\n    async fn data_server_loop(this: Arc<DataServer>) -> Result<(), Box<dyn Error>> {\n        let mut rx: mpsc::Receiver<()>;\n        let mut data_rx: mpsc::Receiver<Box<dyn DnsRequest>>;\n        let batch_mode = *this.recv_in_batch.lock().unwrap();\n\n        {\n            let mut _rx = this.notify_rx.lock().unwrap();\n            rx = _rx.take().unwrap();\n            let mut _rx = this.data_rx.lock().unwrap();\n            data_rx = _rx.take().unwrap();\n        }\n\n        this.stat.clone().start_worker()?;\n\n        let req_list_size = if batch_mode { 1024 * 32 } else { 1 };\n        let mut req_list: Vec<Box<dyn DnsRequest>> = Vec::with_capacity(req_list_size);\n        let batch_size = if batch_mode { 1024 * 8 } else { 1 };\n        let mut recv_buffer = Vec::with_capacity(batch_size);\n        let mut batch_timer: Option<tokio::time::Interval> = None;\n        let mut check_timer = tokio::time::interval(Duration::from_secs(60));\n        let mut client_flush_timer = tokio::time::interval(Duration::from_secs(10));\n        let is_check_timer_running = Arc::new(AtomicBool::new(false));\n\n        dns_log!(LogLevel::DEBUG, \"data server start.\");\n\n        loop {\n            tokio::select! {\n                _ = rx.recv() => {\n                    break;\n                }\n                _ = client_flush_timer.tick() => {\n                    let mut flush_list = Vec::new();\n                    let timestamp_now = get_utc_time_ms();\n                    {\n                        let mut pending = this.client_pending_list.lock().unwrap();\n                        let mut to_remove = Vec::new();\n                        for (ip, (first_seen, data)) in pending.iter() {\n                            if timestamp_now - *first_seen > 10000 || data.mac != \"00:00:00:00:00:00\" {\n                                to_remove.push(ip.clone());\n                            }\n                        }\n                        for ip in to_remove {\n                            if let Some((_, data)) = pending.remove(&ip) {\n                                flush_list.push(data);\n                            }\n                        }\n                    }\n                    if !flush_list.is_empty() {\n                        let this_clone = this.clone();\n                        let _ = DataServer::call_blocking(this.clone(), move || {\n                            let _ = this_clone.insert_client_by_list(&flush_list);\n                            Ok::<(), String>(())\n                        }).await;\n                    }\n                }\n                _ = check_timer.tick() => {\n                    if is_check_timer_running.fetch_xor(true, std::sync::atomic::Ordering::Relaxed) {\n                        continue;\n                    }\n\n                    let is_check_timer_running_clone = is_check_timer_running.clone();\n                    let this_clone = this.clone();\n                    let ret = DataServer::call_blocking(this.clone(), move || {\n                        this_clone.server_check();\n                        is_check_timer_running_clone.store(false, std::sync::atomic::Ordering::Relaxed);\n                    }).await;\n\n                    if let Err(e) = ret {\n                        dns_log!(LogLevel::WARN, \"data server check error: {}\", e);\n                    }\n                }\n                _ = async {\n                    if let Some(ref mut timer) = batch_timer {\n                        timer.tick().await;\n                    }\n                }, if batch_timer.is_some() => {\n                    batch_timer = None;\n                    DataServer::data_server_handle_dns_request(this.clone(), &req_list).await;\n                    req_list.clear();\n                }\n                count = data_rx.recv_many(&mut recv_buffer, batch_size) => {\n                    if count <= 0 {\n                        continue;\n                    }\n\n                    req_list.extend(recv_buffer.drain(0..count));\n\n                    if batch_mode {\n                        if req_list.len() >= 1 && batch_timer.is_none() {\n                            let fill = (req_list.len() as f32 / batch_size as f32)\n                                .max(0.0)\n                                .min(1.0);\n                            let delay_ms = (1000.0 - 990.0 * fill) as u64;\n\n                            batch_timer = Some(tokio::time::interval_at(\n                                Instant::now() + Duration::from_millis(delay_ms),\n                                Duration::from_secs(2),\n                            ));\n                        }\n\n                        if req_list.len() < batch_size {\n                            continue;\n                        }\n                    }\n\n                    batch_timer = None;\n                    DataServer::data_server_handle_dns_request(this.clone(), &req_list).await;\n                    req_list.clear();\n                }\n            }\n        }\n\n        {\n            let mut flush_list = Vec::new();\n            {\n                let mut pending = this.client_pending_list.lock().unwrap();\n                for (_, (_, data)) in pending.drain() {\n                    flush_list.push(data);\n                }\n            }\n            if !flush_list.is_empty() {\n                let this_clone = this.clone();\n                let _ = DataServer::call_blocking(this.clone(), move || {\n                    let _ = this_clone.insert_client_by_list(&flush_list);\n                    Ok::<(), String>(())\n                }).await;\n            }\n        }\n\n        this.stat.clone().stop_worker();\n\n        Ok(())\n    }\n\n    fn stop_data_server(&self) {\n        if let Some(tx) = self.notify_tx.as_ref().cloned() {\n            let plugin = match self.get_plugin() {\n                Ok(plugin) => plugin,\n                Err(e) => {\n                    dns_log!(LogLevel::ERROR, \"get plugin error: {}\", e);\n                    return;\n                }\n            };\n\n            let rt = plugin.get_runtime();\n            tokio::task::block_in_place(|| {\n                let _ = rt.block_on(async {\n                    let _ = tx.send(()).await;\n                });\n            });\n        }\n    }\n\n    async fn call_blocking<F, R>(\n        this: Arc<DataServer>,\n        func: F,\n    ) -> Result<R, Box<dyn std::error::Error + Send>>\n    where\n        F: FnOnce() -> R + Send + 'static,\n        R: Send + 'static,\n    {\n        let rt = this.get_plugin().unwrap().get_runtime();\n\n        let ret = rt.spawn_blocking(move || -> R {\n            return func();\n        });\n\n        let ret = ret.await;\n        if ret.is_err() {\n            return Err(Box::new(ret.err().unwrap()));\n        }\n\n        let ret = ret.unwrap();\n\n        return Ok(ret);\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/data_stats.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse std::{\n    collections::HashMap,\n    error::Error,\n    sync::{atomic::AtomicU32, RwLock},\n};\n\nuse crate::{data_server::DataServerConfig, db::*, dns_log, smartdns::*, utils};\n\nuse std::sync::{\n    atomic::{AtomicBool, Ordering},\n    Arc, Mutex,\n};\n\n#[cfg(target_has_atomic = \"64\")]\nuse std::sync::atomic::AtomicU64;\nuse std::time::Duration;\nuse tokio::sync::mpsc;\nuse tokio::time::{interval_at, Instant};\n\n#[cfg(target_has_atomic = \"64\")]\nstruct DataStatsItem {\n    total_request: AtomicU64,\n    total_blocked_request: AtomicU64,\n    total_failed_request: AtomicU64,\n    qps: AtomicU32,\n    qps_count: AtomicU32,\n    request_dropped: AtomicU64,\n}\n\n#[cfg(not(target_has_atomic = \"64\"))]\nstruct DataStatsItem {\n    total_request: Arc<Mutex<u64>>,\n    total_blocked_request: Arc<Mutex<u64>>,\n    total_failed_request: Arc<Mutex<u64>>,\n    qps: AtomicU32,\n    qps_count: AtomicU32,\n    request_dropped: Arc<Mutex<u64>>,\n}\n\nimpl DataStatsItem {\n    pub fn new() -> Self {\n        #[cfg(target_has_atomic = \"64\")]\n        let ret = DataStatsItem {\n            total_request: 0.into(),\n            total_blocked_request: 0.into(),\n            total_failed_request: 0.into(),\n            qps: 0.into(),\n            qps_count: 0.into(),\n            request_dropped: 0.into(),\n        };\n        #[cfg(not(target_has_atomic = \"64\"))]\n        let ret = DataStatsItem {\n            total_request: Arc::new(Mutex::new(0)),\n            total_blocked_request: Arc::new(Mutex::new(0)),\n            total_failed_request: Arc::new(Mutex::new(0)),\n            qps: 0.into(),\n            qps_count: 0.into(),\n            request_dropped: Arc::new(Mutex::new(0)),\n        };\n\n        return ret;\n    }\n\n    pub fn get_qps(&self) -> u32 {\n        return self.qps.load(Ordering::Relaxed);\n    }\n\n    pub fn add_qps_count(&self, count: u32) {\n        self.qps_count.fetch_add(count, Ordering::Relaxed);\n    }\n\n    pub fn update_qps(&self) {\n        let qps = self.qps_count.fetch_and(0, Ordering::Relaxed);\n        self.qps.store(qps, Ordering::Relaxed);\n    }\n\n    pub fn add_request_drop(&self, count: u64) {\n        #[cfg(target_has_atomic = \"64\")]\n        {\n            self.request_dropped.fetch_and(count, Ordering::Relaxed);\n        }\n\n        #[cfg(not(target_has_atomic = \"64\"))]\n        {\n            let mut dropped = self.request_dropped.lock().unwrap();\n            *dropped += count;\n        }\n    }\n\n    pub fn get_request_drop(&self) -> u64 {\n        #[cfg(target_has_atomic = \"64\")]\n        {\n            return self.request_dropped.load(Ordering::Relaxed);\n        }\n\n        #[cfg(not(target_has_atomic = \"64\"))]\n        {\n            let dropped = self.request_dropped.lock().unwrap();\n            return *dropped;\n        }\n    }\n\n    pub fn get_total_request(&self) -> u64 {\n        #[cfg(target_has_atomic = \"64\")]\n        {\n            return self.total_request.load(Ordering::Relaxed);\n        }\n\n        #[cfg(not(target_has_atomic = \"64\"))]\n        {\n            let total = self.total_request.lock().unwrap();\n            return *total;\n        }\n    }\n\n    pub fn add_total_request(&self, total: u64) {\n        #[cfg(target_has_atomic = \"64\")]\n        {\n            self.total_request.fetch_add(total, Ordering::Relaxed);\n        }\n\n        #[cfg(not(target_has_atomic = \"64\"))]\n        {\n            let mut total_request = self.total_request.lock().unwrap();\n            *total_request += total;\n        }\n    }\n\n    pub fn get_total_blocked_request(&self) -> u64 {\n        #[cfg(target_has_atomic = \"64\")]\n        {\n            return self.total_blocked_request.load(Ordering::Relaxed);\n        }\n\n        #[cfg(not(target_has_atomic = \"64\"))]\n        {\n            let total = self.total_blocked_request.lock().unwrap();\n            return *total;\n        }\n    }\n\n    pub fn add_total_blocked_request(&self, total: u64) {\n        #[cfg(target_has_atomic = \"64\")]\n        {\n            self.total_blocked_request\n                .fetch_add(total, Ordering::Relaxed);\n        }\n\n        #[cfg(not(target_has_atomic = \"64\"))]\n        {\n            let mut total_blocked_request = self.total_blocked_request.lock().unwrap();\n            *total_blocked_request += total;\n        }\n    }\n\n    pub fn add_total_failed_request(&self, total: u64) {\n        #[cfg(target_has_atomic = \"64\")]\n        {\n            self.total_failed_request\n                .fetch_add(total, Ordering::Relaxed);\n        }\n\n        #[cfg(not(target_has_atomic = \"64\"))]\n        {\n            let mut total_failed_request = self.total_failed_request.lock().unwrap();\n            *total_failed_request += total;\n        }\n    }\n\n    pub fn get_total_failed_request(&self) -> u64 {\n        #[cfg(target_has_atomic = \"64\")]\n        {\n            return self.total_failed_request.load(Ordering::Relaxed);\n        }\n\n        #[cfg(not(target_has_atomic = \"64\"))]\n        {\n            let total = self.total_failed_request.lock().unwrap();\n            return *total;\n        }\n    }\n\n    #[allow(dead_code)]\n    pub fn get_current_hour_total(&self) -> u64 {\n        return Stats::get_request_total();\n    }\n}\n\npub struct DataStats {\n    task: Mutex<Option<tokio::task::JoinHandle<()>>>,\n    notify_tx: Option<mpsc::Sender<()>>,\n    notify_rx: Mutex<Option<mpsc::Receiver<()>>>,\n    is_run: AtomicBool,\n    data: DataStatsItem,\n    db: Arc<crate::db::DB>,\n    conf: Arc<RwLock<DataServerConfig>>,\n    is_hourly_work_running: AtomicBool,\n}\n\nimpl DataStats {\n    pub fn new(db: Arc<crate::db::DB>, conf: Arc<RwLock<DataServerConfig>>) -> Arc<Self> {\n        let (tx, rx) = mpsc::channel(100);\n\n        Arc::new(DataStats {\n            task: Mutex::new(None),\n            notify_rx: Mutex::new(Some(rx)),\n            notify_tx: Some(tx),\n            is_run: AtomicBool::new(false),\n            data: DataStatsItem::new(),\n            db: db,\n            conf: conf,\n            is_hourly_work_running: AtomicBool::new(false),\n        })\n    }\n\n    pub fn get_qps(&self) -> u32 {\n        return self.data.get_qps();\n    }\n\n    pub fn add_qps_count(&self, count: u32) {\n        self.data.add_qps_count(count);\n    }\n\n    pub fn update_qps(&self) {\n        self.data.update_qps();\n    }\n\n    pub fn add_request_drop(&self, count: u64) {\n        self.data.add_request_drop(count);\n    }\n\n    pub fn get_request_drop(&self) -> u64 {\n        return self.data.get_request_drop();\n    }\n\n    pub fn get_total_blocked_request(&self) -> u64 {\n        return self.data.get_total_blocked_request();\n    }\n\n    pub fn add_total_blocked_request(&self, total: u64) {\n        self.data.add_total_blocked_request(total);\n    }\n\n    pub fn get_total_failed_request(&self) -> u64 {\n        return self.data.get_total_failed_request();\n    }\n\n    pub fn add_total_failed_request(&self, total: u64) {\n        self.data.add_total_failed_request(total);\n    }\n\n    pub fn get_total_request(&self) -> u64 {\n        return self.data.get_total_request();\n    }\n\n    pub fn get_current_hour_total(&self) -> u64 {\n        return self.data.get_current_hour_total();\n    }\n\n    pub fn add_total_request(&self, total: u64) {\n        self.data.add_total_request(total);\n    }\n\n    pub fn get_memory_usage(&self) -> u64 {\n        let statm_path = \"/proc/self/statm\";\n        let statm = std::fs::read_to_string(statm_path);\n        if let Err(_) = statm {\n            return 0;\n        }\n\n        let statm = statm.unwrap();\n        let statm: Vec<&str> = statm.split_whitespace().collect();\n        if statm.len() < 2 {\n            return 0;\n        }\n\n        let pages = statm[1].parse::<u64>();\n        if let Err(_) = pages {\n            return 0;\n        }\n\n        let pages = pages.unwrap();\n        let pagesizie = utils::get_page_size() as u64;\n        return pages * pagesizie;\n    }\n\n    pub fn init(self: &Arc<Self>) -> Result<(), Box<dyn Error>> {\n        dns_log!(LogLevel::DEBUG, \"init data stats\");\n        self.load_status_data()?;\n        Ok(())\n    }\n\n    pub fn load_status_data(self: &Arc<Self>) -> Result<(), Box<dyn Error>> {\n        let status_data = match self.db.get_status_data_list() {\n            Ok(data) => data,\n            Err(_) => HashMap::new(),\n        };\n\n        // load total request count\n        let mut total_count = 0 as u64;\n        let status_data_total_count = status_data.get(\"total_request\");\n        if status_data_total_count.is_some() {\n            let count = status_data_total_count.unwrap().parse::<u64>();\n            if let Ok(count) = count {\n                total_count = count;\n            } else {\n                total_count = 0;\n            }\n        }\n\n        if total_count == 0 {\n            let count = self.db.get_domain_list_count(None);\n            total_count = count;\n        }\n        self.data.add_total_request(total_count);\n\n        // load total blocked request\n        let mut total_blocked_count = 0 as u64;\n        let status_data_total_blocked_count = status_data.get(\"total_blocked_request\");\n        if status_data_total_blocked_count.is_some() {\n            let count = status_data_total_blocked_count.unwrap().parse::<u64>();\n            if let Ok(count) = count {\n                total_blocked_count = count;\n            } else {\n                total_blocked_count = 0;\n            }\n        }\n\n        if total_blocked_count == 0 {\n            let mut parm = DomainListGetParam::new();\n            parm.is_blocked = Some(true);\n            \n            let count = self.db.get_domain_list_count(Some(&parm));\n            total_blocked_count = count;\n        }\n        self.data.add_total_blocked_request(total_blocked_count);\n\n        \n        // load request drop count\n        let mut request_drop = 0 as u64;\n        let status_data_request_drop = status_data.get(\"request_drop\");\n        if status_data_request_drop.is_some() {\n            let count = status_data_request_drop.unwrap().parse::<u64>();\n            if let Ok(count) = count {\n                request_drop = count;\n            } else {\n                request_drop = 0;\n            }\n        }\n        self.data.add_request_drop(request_drop);\n\n        // load total failed request\n        let mut total_failed_count = 0 as u64;\n        let status_data_total_failed_count = status_data.get(\"total_failed_request\");\n        if status_data_total_failed_count.is_some() {\n            let count = status_data_total_failed_count.unwrap().parse::<u64>();\n            if let Ok(count) = count {\n                total_failed_count = count;\n            } else {\n                total_failed_count = 0;\n            }\n        }\n        self.data.add_total_failed_request(total_failed_count);\n        Ok(())\n    }\n\n    pub fn save_status_data(self: &Arc<Self>) -> Result<(), Box<dyn Error>> {\n        self.db.set_status_data(\n            \"total_request\",\n            self.get_total_request().to_string().as_str(),\n        )?;\n        self.db.set_status_data(\n            \"total_blocked_request\",\n            self.get_total_blocked_request().to_string().as_str(),\n        )?;\n        self.db.set_status_data(\n            \"total_failed_request\",\n            self.get_total_failed_request().to_string().as_str(),\n        )?;\n        self.db.set_status_data(\n            \"request_drop\",\n            self.get_request_drop().to_string().as_str(),\n        )?;\n\n        Ok(())\n    }\n\n    pub fn start_worker(self: &Arc<Self>) -> Result<(), Box<dyn Error>> {\n        let this = self.clone();\n        let task = tokio::spawn(async move {\n            DataStats::worker_loop(&this).await;\n        });\n\n        *(self.task.lock().unwrap()) = Some(task);\n        self.is_run.store(true, Ordering::Relaxed);\n        Ok(())\n    }\n\n    pub fn refresh(self: &Arc<Self>) {\n        let now = get_utc_time_ms();\n\n        let ret = self\n            .db\n            .delete_domain_before_timestamp(now - self.conf.read().unwrap().max_log_age_ms as u64);\n        if let Err(e) = ret {\n            if e.to_string() == \"Query returned no rows\" {\n                return;\n            }\n\n            dns_log!(\n                LogLevel::WARN,\n                \"delete domain before timestamp error: {}\",\n                e\n            );\n        }\n\n        let ret = self.db.refresh_client_top_list(now - 7 * 24 * 3600 * 1000);\n        if let Err(e) = ret {\n            dns_log!(LogLevel::WARN, \"refresh client top list error: {}\", e);\n        }\n\n        let ret = self.db.refresh_domain_top_list(now - 7 * 24 * 3600 * 1000);\n        if let Err(e) = ret {\n            dns_log!(LogLevel::WARN, \"refresh domain top list error: {}\", e);\n        }\n        let _ = self\n            .db\n            .delete_hourly_query_count_before_timestamp(30 * 24 * 3600 * 1000);\n        let _ = self\n            .db\n            .delete_daily_query_count_before_timestamp(90 * 24 * 3600 * 1000);\n    }\n\n    async fn update_stats(self: &Arc<Self>) {\n        if self\n            .is_hourly_work_running\n            .fetch_or(true, Ordering::Acquire)\n        {\n            return;\n        }\n\n        let this = self.clone();\n        tokio::task::spawn_blocking(move || {\n            this.refresh();\n            this.is_hourly_work_running.store(false, Ordering::Release);\n        });\n    }\n\n    async fn worker_loop(this: &Arc<Self>) {\n        let mut rx: mpsc::Receiver<()>;\n        {\n            let mut _rx = this.notify_rx.lock().unwrap();\n            rx = _rx.take().unwrap();\n        }\n\n        this.clone().update_stats().await;\n        let start: Instant = Instant::now() + Duration::from_secs(utils::seconds_until_next_hour());\n        let mut hour_timer = interval_at(start, Duration::from_secs(60 * 60));\n        let mut second_timer = interval_at(Instant::now(), Duration::from_secs(1));\n\n        loop {\n            tokio::select! {\n                _ = rx.recv() => {\n                    break;\n                }\n\n                _ = second_timer.tick() => {\n                    this.update_qps();\n                }\n\n                _ = hour_timer.tick() => {\n                    this.update_stats().await;\n                }\n            }\n        }\n\n        let ret = this.save_status_data();\n        if let Err(e) = ret {\n            dns_log!(LogLevel::WARN, \"save status data error: {}\", e);\n        }\n    }\n\n    pub fn stop_worker(&self) {\n        if self.is_run.load(Ordering::Relaxed) == false {\n            return;\n        }\n\n        if let Some(tx) = self.notify_tx.as_ref().cloned() {\n            let _ = tx.try_send(());\n        }\n\n        let mut task = self.task.lock().unwrap();\n        if let Some(task) = task.take() {\n            tokio::task::block_in_place(|| {\n                let _ = tokio::runtime::Handle::current().block_on(task);\n            });\n        }\n\n        self.is_run.store(false, Ordering::Relaxed);\n    }\n}\n\nimpl Drop for DataStats {\n    fn drop(&mut self) {\n        self.stop_worker();\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/data_upstream_server.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse crate::{smartdns, DnsServerType};\n\n#[derive(Debug, Clone)]\npub struct UpstreamServerInfo {\n    pub host: String,\n    pub ip: String,\n    pub port: u16,\n    pub server_type: DnsServerType,\n    pub total_query_count: u64,\n    pub total_query_success: u64,\n    pub total_query_recv_count: u64,\n    pub query_success_rate: f64,\n    pub avg_time: f64,\n    pub status: String,\n    pub security: String,\n}\n\nimpl UpstreamServerInfo {\n    pub fn get_all() -> Result<Vec<UpstreamServerInfo>, Box<dyn std::error::Error>> {\n        let mut servers = Vec::new();\n\n        smartdns::DnsUpstreamServer::get_server_list()?\n            .iter()\n            .for_each(|server| {\n                let stats = server.get_server_stats();\n                let status = if stats.get_query_total() == 0 {\n                    \"Unknown\"\n                } else if server.is_server_alive() {\n                    \"Normal\"\n                } else {\n                    \"Abnormal\"\n                };\n\n                let security_status = server.get_server_security_status();\n\n                servers.push(UpstreamServerInfo {\n                    host: server.get_host(),\n                    ip: server.get_ip(),\n                    port: server.get_port(),\n                    server_type: server.get_type(),\n                    total_query_count: stats.get_query_total(),\n                    total_query_recv_count: stats.get_query_recv(),\n                    total_query_success: stats.get_query_success(),\n                    query_success_rate: stats.get_success_rate(),\n                    avg_time: stats.get_query_avg_time(),\n                    status: status.to_string(),\n                    security: security_status.to_string(),\n                });\n            });\n        Ok(servers)\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/db.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse crate::dns_log;\nuse crate::smartdns;\nuse crate::smartdns::*;\nuse crate::utils;\nuse std::collections::HashMap;\nuse std::error::Error;\nuse std::fs;\nuse std::sync::Mutex;\nuse std::vec;\n\nuse chrono::Local;\nuse rusqlite::Transaction;\nuse rusqlite::{Connection, OpenFlags, Result};\n\npub struct DB {\n    conn: Mutex<Option<Connection>>,\n    version: i32,\n    query_plan: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct ClientData {\n    pub id: u32,\n    pub hostname: String,\n    pub client_ip: String,\n    pub mac: String,\n    pub last_query_timestamp: u64,\n}\n\n#[derive(Debug, Clone)]\npub struct ClientQueryCount {\n    pub client_ip: String,\n    pub count: u32,\n    pub timestamp_start: u64,\n    pub timestamp_end: u64,\n}\n\n#[derive(Debug, Clone)]\npub struct DomainQueryCount {\n    pub domain: String,\n    pub count: u32,\n    pub timestamp_start: u64,\n    pub timestamp_end: u64,\n}\n\n#[derive(Debug, Clone)]\npub struct HourlyQueryCountItem {\n    pub hour: String,\n    pub query_count: u32,\n}\n\n#[derive(Debug, Clone)]\npub struct HourlyQueryCount {\n    pub query_timestamp: u64,\n    pub hourly_query_count: Vec<HourlyQueryCountItem>,\n}\n\n#[derive(Debug, Clone)]\npub struct DailyQueryCountItem {\n    pub day: String,\n    pub query_count: u32,\n}\n\n#[derive(Debug, Clone)]\npub struct DailyQueryCount {\n    pub query_timestamp: u64,\n    pub daily_query_count: Vec<DailyQueryCountItem>,\n}\n\n#[derive(Debug, Clone)]\npub struct DomainData {\n    pub id: u64,\n    pub timestamp: u64,\n    pub domain: String,\n    pub domain_type: u32,\n    pub client: String,\n    pub domain_group: String,\n    pub reply_code: u16,\n    pub query_time: i32,\n    pub ping_time: f64,\n    pub is_blocked: bool,\n    pub is_cached: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct QueryDomainListResult {\n    pub domain_list: Vec<DomainData>,\n    pub total_count: u64,\n    pub step_by_cursor: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct DomainListGetParamCursor {\n    pub id: Option<u64>,\n    pub total_count: u64,\n    pub direction: String,\n}\n\n#[derive(Debug, Clone)]\npub struct QueryClientListResult {\n    pub client_list: Vec<ClientData>,\n    pub total_count: u64,\n    pub step_by_cursor: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct ClientListGetParamCursor {\n    pub id: Option<u64>,\n    pub total_count: u64,\n    pub direction: String,\n}\n\n#[derive(Debug, Clone)]\npub struct ClientListGetParam {\n    pub id: Option<u64>,\n    pub order: Option<String>,\n    pub page_num: u64,\n    pub page_size: u64,\n    pub client_ip: Option<String>,\n    pub mac: Option<String>,\n    pub hostname: Option<String>,\n    pub timestamp_before: Option<u64>,\n    pub timestamp_after: Option<u64>,\n    pub cursor: Option<ClientListGetParamCursor>,\n}\n\nimpl ClientListGetParam {\n    pub fn new() -> Self {\n        ClientListGetParam {\n            id: None,\n            page_num: 1,\n            order: None,\n            page_size: 10,\n            client_ip: None,\n            mac: None,\n            hostname: None,\n            timestamp_before: None,\n            timestamp_after: None,\n            cursor: None,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct DomainListGetParam {\n    pub id: Option<u64>,\n    pub order: Option<String>,\n    pub page_num: u64,\n    pub page_size: u64,\n    pub domain: Option<String>,\n    pub domain_filter_mode: Option<String>,\n    pub domain_type: Option<u32>,\n    pub client: Option<String>,\n    pub domain_group: Option<String>,\n    pub reply_code: Option<u16>,\n    pub timestamp_before: Option<u64>,\n    pub timestamp_after: Option<u64>,\n    pub is_blocked: Option<bool>,\n    pub is_cached: Option<bool>,\n    pub cursor: Option<DomainListGetParamCursor>,\n}\n\nimpl DomainListGetParam {\n    pub fn new() -> Self {\n        DomainListGetParam {\n            id: None,\n            page_num: 1,\n            order: None,\n            page_size: 10,\n            domain: None,\n            domain_filter_mode: None,\n            domain_type: None,\n            client: None,\n            domain_group: None,\n            reply_code: None,\n            timestamp_before: None,\n            timestamp_after: None,\n            is_blocked: None,\n            is_cached: None,\n            cursor: None,\n        }\n    }\n}\n\nimpl DB {\n    pub fn new() -> Self {\n        DB {\n            conn: Mutex::new(None),\n            version: 10000, /* x: major version, xx: minor version, xx: patch version */\n            query_plan: std::env::var(\"SMARTDNS_DEBUG_SQL\").is_ok(),\n        }\n    }\n\n    fn create_table(&self, conn: &Connection) -> Result<()> {\n        conn.execute(\n            \"CREATE TABLE IF NOT EXISTS domain (\n                id INTEGER PRIMARY KEY AUTOINCREMENT,\n                timestamp BIGINT NOT NULL,\n                domain TEXT NOT NULL,\n                domain_type INTEGER NOT NULL,\n                client TEXT NOT NULL,\n                domain_group TEXT NOT NULL,\n                reply_code INTEGER NOT NULL,\n                query_time INTEGER NOT NULL,\n                ping_time REAL NOT NULL,\n                is_blocked INTEGER DEFAULT 0,\n                is_cached INTEGER DEFAULT 0\n            )\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE INDEX IF NOT EXISTS idx_domain_timestamp ON domain (timestamp)\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE INDEX IF NOT EXISTS idx_domain_client ON domain (client)\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE TABLE IF NOT EXISTS domain_hourly_count (\n                timestamp BIGINT PRIMARY KEY,\n                count INTEGER DEFAULT 0\n            );\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE TABLE IF NOT EXISTS domain_daily_count (\n                timestamp BIGINT PRIMARY KEY,\n                count INTEGER DEFAULT 0\n            );\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE TABLE IF NOT EXISTS top_domain_list (\n                domain TEXT PRIMARY KEY,\n                count INTEGER DEFAULT 0,\n                timestamp_start BIGINT DEFAULT 0,\n                timestamp_end BIGINT DEFAULT 0\n            );\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE TABLE IF NOT EXISTS top_client_list (\n                client TEXT PRIMARY KEY,\n                count INTEGER DEFAULT 0,\n                timestamp_start BIGINT DEFAULT 0,\n                timestamp_end BIGINT DEFAULT 0\n            );\",\n            [],\n        )?;\n\n        conn.execute(\n            \"\n        CREATE TABLE IF NOT EXISTS client (\n            id INTEGER PRIMARY KEY,\n            client_ip TEXT NOT NULL,\n            mac TEXT NOT NULL,\n            hostname TEXT NOT NULL,\n            last_query_timestamp BIGINT NOT NULL,\n            UNIQUE(client_ip, mac)\n        )\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE INDEX IF NOT EXISTS idx_client_last_query_timestamp ON client (last_query_timestamp)\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE TABLE IF NOT EXISTS config (\n                key TEXT PRIMARY KEY,\n                value TEXT NOT NULL\n            )\",\n            [],\n        )?;\n\n        conn.execute(\n            \"CREATE TABLE IF NOT EXISTS status_data (\n                key TEXT PRIMARY KEY,\n                value TEXT NOT NULL\n            )\",\n            [],\n        )?;\n\n        conn.execute(\n            \"INSERT INTO schema_version (version) VALUES (?)\",\n            [self.version],\n        )?;\n\n        Ok(())\n    }\n\n    fn migrate_db(&self, _conn: &Connection) -> Result<(), Box<dyn Error>> {\n        return Err(\n            \"Currently Not Support Migrate Database, Please Backup DB File, And Restart Server.\"\n                .into(),\n        );\n    }\n\n    fn init_db(&self, conn: &Connection) -> Result<(), Box<dyn Error>> {\n        conn.execute(\n            \"CREATE TABLE IF NOT EXISTS schema_version (\n                version INTEGER PRIMARY KEY\n            )\",\n            [],\n        )?;\n\n        let current_version: i32 = conn\n            .query_row(\n                \"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\",\n                [],\n                |row| row.get(0),\n            )\n            .unwrap_or(self.version);\n\n        if current_version >= self.version {\n            self.create_table(conn)?;\n        } else {\n            self.migrate_db(conn)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn open(&self, path: &str) -> Result<(), Box<dyn Error>> {\n        let ruconn: std::result::Result<Connection, rusqlite::Error> =\n            Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE);\n        let mut conn = self.conn.lock().unwrap();\n        if let Err(_) = ruconn {\n            let ruconn = Connection::open_with_flags(\n                path,\n                OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,\n            )?;\n\n            let ret = self.init_db(&ruconn);\n            if let Err(e) = ret {\n                _ = ruconn.close();\n                fs::remove_file(path)?;\n                return Err(e);\n            }\n\n            *conn = Some(ruconn);\n        } else {\n            *conn = Some(ruconn.unwrap());\n        }\n\n        conn.as_ref()\n            .unwrap()\n            .execute(\"PRAGMA synchronous = OFF\", [])?;\n        conn.as_ref()\n            .unwrap()\n            .execute(\"PRAGMA page_size  = 4096\", [])?;\n        conn.as_ref()\n            .unwrap()\n            .execute(\"PRAGMA cache_size = -8192\", [])?;\n        conn.as_ref()\n            .unwrap()\n            .execute(\"PRAGMA temp_store = MEMORY\", [])?;\n\n        let current_auto_vacuum: i32 =\n            conn.as_ref()\n                .unwrap()\n                .query_row(\"PRAGMA auto_vacuum\", [], |row| row.get(0))?;\n        dns_log!(\n            LogLevel::DEBUG,\n            \"Current auto_vacuum: {}\",\n            current_auto_vacuum\n        );\n        if current_auto_vacuum != 2 {\n            dns_log!(LogLevel::INFO, \"Set auto_vacuum to INCREMENTAL\");\n            conn.as_ref()\n                .unwrap()\n                .execute(\"PRAGMA auto_vacuum = INCREMENTAL\", [])?;\n            conn.as_ref().unwrap().execute(\"VACUUM\", [])?;\n        }\n\n        conn.as_ref()\n            .unwrap()\n            .query_row(\"PRAGMA journal_mode = WAL\", [], |_| Ok(()))?;\n        conn.as_ref()\n            .unwrap()\n            .query_row(\"PRAGMA wal_autocheckpoint = 1000\", [], |_| Ok(()))?;\n        Ok(())\n    }\n\n    pub fn run_vacuum(&self, pages: Option<u32>) -> Result<(), Box<dyn Error>> {\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n        dns_log!(LogLevel::DEBUG, \"Start incremental vacuum\");\n\n        conn.query_row(\"PRAGMA wal_checkpoint(PASSIVE)\", [], |_| Ok(()))?;\n        let vacuum_sql = if let Some(pages) = pages {\n            format!(\"PRAGMA incremental_vacuum({})\", pages)\n        } else {\n            \"PRAGMA incremental_vacuum\".to_string()\n        };\n\n        let mut stmt = conn.prepare(vacuum_sql.as_str())?;\n        let mut _reclaimed_pages = 0;\n\n        let rows = stmt.query_map([], |_row| Ok(()));\n        if let Ok(rows) = rows {\n            for _row in rows {\n                _reclaimed_pages += 1;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn set_config(&self, key: &str, value: &str) -> Result<(), Box<dyn Error>> {\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let mut stmt =\n            conn.prepare(\"INSERT OR REPLACE INTO config (key, value) VALUES (?1, ?2)\")?;\n        let ret = stmt.execute(&[&key, &value]);\n\n        if let Err(e) = ret {\n            return Err(Box::new(e));\n        }\n\n        Ok(())\n    }\n\n    pub fn get_config_list(&self) -> Result<HashMap<String, String>, Box<dyn Error>> {\n        let mut ret = HashMap::new();\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let mut stmt = conn.prepare(\"SELECT key, value FROM config\").unwrap();\n\n        let rows = stmt.query_map([], |row| {\n            let key: String = row.get(0)?;\n            let value: String = row.get(1)?;\n            Ok((key, value))\n        });\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    ret.insert(row.0, row.1);\n                }\n            }\n        }\n\n        Ok(ret)\n    }\n\n    pub fn set_status_data(&self, key: &str, value: &str) -> Result<(), Box<dyn Error>> {\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let mut stmt =\n            conn.prepare(\"INSERT OR REPLACE INTO status_data (key, value) VALUES (?1, ?2)\")?;\n        let ret = stmt.execute(&[&key, &value]);\n\n        if let Err(e) = ret {\n            return Err(Box::new(e));\n        }\n\n        Ok(())\n    }\n\n    pub fn get_status_data_list(&self) -> Result<HashMap<String, String>, Box<dyn Error>> {\n        let mut ret = HashMap::new();\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let stmt = conn.prepare(\"SELECT key, value FROM status_data\");\n        if let Err(e) = stmt {\n            return Err(Box::new(e));\n        }\n        let mut stmt = stmt.unwrap();\n\n        let rows = stmt.query_map([], |row| {\n            let key: String = row.get(0)?;\n            let value: String = row.get(1)?;\n            Ok((key, value))\n        });\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    ret.insert(row.0, row.1);\n                }\n            }\n        }\n\n        Ok(ret)\n    }\n\n    pub fn debug_query_plan(&self, conn: &Connection, sql: String, sql_param: &Vec<String>) {\n        if !self.query_plan {\n            return;\n        }\n\n        let sqlplan = \"EXPLAIN QUERY PLAN \".to_string() + &sql;\n        let stmt = conn.prepare(sqlplan.as_str());\n        if let Err(e) = stmt {\n            dns_log!(LogLevel::DEBUG, \"query plan sql error: {}\", e);\n            return;\n        }\n\n        let mut stmt = stmt.unwrap();\n        let plan_rows = stmt.query_map(rusqlite::params_from_iter(sql_param.clone()), |row| {\n            Ok(row.get::<_, String>(3)?)\n        });\n\n        if let Err(e) = plan_rows {\n            dns_log!(LogLevel::DEBUG, \"query plan error: {}\", e);\n            return;\n        }\n\n        let plan_rows = plan_rows.unwrap();\n        dns_log!(LogLevel::NOTICE, \"sql: {}\", sql);\n        for plan in plan_rows {\n            if let Ok(plan) = plan {\n                dns_log!(LogLevel::NOTICE, \"plan: {}\", plan);\n            }\n        }\n    }\n\n    pub fn get_config(&self, key: &str) -> Result<Option<String>, Box<dyn Error>> {\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let mut stmt = conn\n            .prepare(\"SELECT value FROM config WHERE key = ?\")\n            .unwrap();\n        let rows = stmt.query_map(&[&key], |row| Ok(row.get(0)?));\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    return Ok(Some(row));\n                }\n            }\n        }\n\n        Ok(None)\n    }\n\n    pub fn update_domain_hourly_count(\n        &self,\n        tx: &Transaction<'_>,\n        hourly_count: &HashMap<u64, u32>,\n    ) -> Result<(), Box<dyn Error>> {\n        let mut stmt = tx.prepare(\n            \"INSERT INTO domain_hourly_count (timestamp, count)\n                 VALUES (\n                    ?1,\n                    ?2\n                )\n                ON CONFLICT(timestamp) DO UPDATE SET count = count + ?2;\",\n        )?;\n\n        for (k, v) in hourly_count {\n            stmt.execute(rusqlite::params![k, v])?;\n        }\n        stmt.finalize()?;\n        Ok(())\n    }\n\n    pub fn update_domain_daily_count(\n        &self,\n        tx: &Transaction<'_>,\n        daily_count: &HashMap<u64, u32>,\n    ) -> Result<(), Box<dyn Error>> {\n        let mut stmt = tx.prepare(\n            \"INSERT INTO domain_daily_count (timestamp, count)\n                 VALUES (\n                    ?1,\n                    ?2\n                )\n                ON CONFLICT(timestamp) DO UPDATE SET count = count + ?2;\",\n        )?;\n\n        for (k, v) in daily_count {\n            stmt.execute(rusqlite::params![k, v])?;\n        }\n        stmt.finalize()?;\n        Ok(())\n    }\n\n    pub fn insert_domain(&self, data: &Vec<DomainData>) -> Result<(), Box<dyn Error>> {\n        let local_offset = Local::now().offset().local_minus_utc();\n        let mut conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let mut hourly_count = HashMap::new();\n        let mut daily_count = HashMap::new();\n        let conn = conn.as_mut().unwrap();\n\n        let tx = conn.transaction()?;\n\n        let mut stmt = tx.prepare(\n            \"INSERT INTO domain \\\n            (timestamp, domain, domain_type, client, domain_group, reply_code, query_time, ping_time, is_blocked, is_cached) \\\n            VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)\")?;\n\n        for d in data {\n            let ret = stmt.execute(rusqlite::params![\n                &d.timestamp.to_string(),\n                &d.domain,\n                &d.domain_type.to_string(),\n                &d.client,\n                &d.domain_group,\n                &d.reply_code,\n                &d.query_time,\n                &d.ping_time,\n                &(d.is_blocked as i32),\n                &(d.is_cached as i32)\n            ]);\n\n            if let Err(e) = ret {\n                stmt.finalize()?;\n                tx.rollback()?;\n                return Err(Box::new(e));\n            }\n\n            let localtimestamp = d.timestamp + local_offset as u64 * 1000;\n\n            let hour_timestamp =\n                localtimestamp - localtimestamp % 3600000 - local_offset as u64 * 1000;\n            let day_timestamp =\n                localtimestamp - localtimestamp % 86400000 - local_offset as u64 * 1000;\n\n            hourly_count\n                .entry(hour_timestamp)\n                .and_modify(|v| *v += 1)\n                .or_insert(1);\n            daily_count\n                .entry(day_timestamp)\n                .and_modify(|v| *v += 1)\n                .or_insert(1);\n        }\n\n        stmt.finalize()?;\n\n        self.update_domain_hourly_count(&tx, &hourly_count)?;\n        self.update_domain_daily_count(&tx, &daily_count)?;\n\n        tx.commit()?;\n\n        Ok(())\n    }\n\n    pub fn get_db_file_path(&self) -> Option<String> {\n        let conn = self.conn.lock().unwrap();\n        if conn.is_none() {\n            return None;\n        }\n\n        let conn = conn.as_ref().unwrap();\n        conn.path().map(|v| v.to_string())\n    }\n\n    pub fn get_readonly_conn(&self) -> Option<Connection> {\n        let conn = self.conn.lock().unwrap();\n        if conn.is_none() {\n            return None;\n        }\n\n        let conn = conn.as_ref().unwrap();\n\n        let read_conn = Connection::open_with_flags(\n            conn.path().unwrap(),\n            OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,\n        );\n\n        if let Err(_) = read_conn {\n            return None;\n        }\n\n        Some(read_conn.unwrap())\n    }\n\n    /// # Returns\n    ///\n    /// A tuple containing:\n    /// - `String`: The SQL WHERE clause.\n    /// - `String`: The SQL ORDER BY clause.\n    /// - `Vec<String>`: The parameters for the SQL query.\n    pub fn get_domain_sql_where(\n        param: Option<&DomainListGetParam>,\n    ) -> Result<(String, String, Vec<String>), Box<dyn Error>> {\n        let mut is_desc_order = true;\n        let mut is_cursor_prev = false;\n        let param = match param {\n            Some(v) => v,\n            None => return Ok((String::new(), String::new(), Vec::new())),\n        };\n        let mut order_timestamp_first = true;\n        let mut cusor_with_timestamp = false;\n\n        let mut sql_where = Vec::new();\n        let mut sql_param: Vec<String> = Vec::new();\n        let mut sql_order = String::new();\n\n        if let Some(v) = &param.id {\n            sql_where.push(\"id = ?\".to_string());\n            sql_param.push(v.to_string());\n            order_timestamp_first = false;\n        }\n\n        if let Some(v) = &param.order {\n            if v.eq_ignore_ascii_case(\"asc\") {\n                is_cursor_prev = true;\n            } else if v.eq_ignore_ascii_case(\"desc\") {\n                is_cursor_prev = false;\n            } else {\n                return Err(\"order param error\".into());\n            }\n        }\n\n        if let Some(v) = &param.cursor {\n            if v.direction.eq_ignore_ascii_case(\"prev\") {\n                is_desc_order = !is_desc_order;\n            } else if v.direction.eq_ignore_ascii_case(\"next\") {\n                // do nothing\n            } else {\n                return Err(\"cursor direction param error\".into());\n            }\n        }\n\n        if let Some(v) = &param.domain {\n            if let Some(m) = &param.domain_filter_mode {\n                match m.as_str() {\n                    \"endwith\" => {\n                        sql_where.push(\"domain LIKE ?\".to_string());\n                        sql_param.push(format!(\"{}%\", v));\n                    }\n                    \"startwith\" => {\n                        sql_where.push(\"domain LIKE ?\".to_string());\n                        sql_param.push(format!(\"%{}\", v));\n                    }\n                    \"contains\" => {\n                        sql_where.push(\"domain LIKE ?\".to_string());\n                        sql_param.push(format!(\"%{}%\", v));\n                    }\n                    \"equals\" => {\n                        sql_where.push(\"domain = ?\".to_string());\n                        sql_param.push(v.to_string());\n                    }\n                    _ => return Err(\"domain_filter_mode param error\".into()),\n                }\n            } else {\n                sql_where.push(\"domain = ?\".to_string());\n                sql_param.push(v.to_string());\n                order_timestamp_first = false;\n            }\n        }\n\n        if let Some(v) = &param.domain_type {\n            sql_where.push(\"domain_type = ?\".to_string());\n            sql_param.push(v.to_string());\n            order_timestamp_first = false;\n        }\n\n        if let Some(v) = &param.client {\n            sql_where.push(\"client = ?\".to_string());\n            sql_param.push(v.clone());\n            order_timestamp_first = false;\n        }\n\n        if let Some(v) = &param.domain_group {\n            sql_where.push(\"domain_group = ?\".to_string());\n            sql_param.push(v.clone());\n            order_timestamp_first = false;\n        }\n\n        if let Some(v) = &param.reply_code {\n            sql_where.push(\"reply_code = ?\".to_string());\n            sql_param.push(v.to_string());\n            order_timestamp_first = false;\n        }\n\n        if let Some(v) = &param.timestamp_before {\n            let mut use_cursor = false;\n            if param.cursor.is_some() && (is_desc_order || is_cursor_prev) {\n                let v = param.cursor.as_ref().unwrap().id;\n                if let Some(v) = v {\n                    sql_where.push(\"id < ?\".to_string());\n                    sql_param.push(v.to_string());\n                    use_cursor = true;\n                    order_timestamp_first = false;\n                    cusor_with_timestamp = true;\n                }\n            }\n\n            if use_cursor == false {\n                sql_where.push(\"timestamp <= ?\".to_string());\n                sql_param.push(v.to_string());\n            }\n        }\n\n        if let Some(v) = &param.timestamp_after {\n            let mut use_cursor = false;\n            if param.cursor.is_some() && (!is_desc_order || is_cursor_prev) {\n                let v = param.cursor.as_ref().unwrap().id;\n                if let Some(v) = v {\n                    sql_where.push(\"id > ?\".to_string());\n                    sql_param.push(v.to_string());\n                    use_cursor = true;\n                    order_timestamp_first = false;\n                    cusor_with_timestamp = true;\n                }\n            }\n\n            if use_cursor == false {\n                sql_where.push(\"timestamp >= ?\".to_string());\n                sql_param.push(v.to_string());\n            }\n        }\n\n        if !cusor_with_timestamp {\n            if let Some(v) = &param.cursor {\n                if is_cursor_prev {\n                    if let Some(id) = &v.id {\n                        if is_desc_order {\n                            sql_where.push(\"id > ?\".to_string());\n                        } else {\n                            sql_where.push(\"id < ?\".to_string());\n                        }\n\n                        sql_param.push(id.to_string());\n                        order_timestamp_first = false;\n                    }\n                } else {\n                    if let Some(id) = &v.id {\n                        if is_desc_order {\n                            sql_where.push(\"id < ?\".to_string());\n                        } else {\n                            sql_where.push(\"id > ?\".to_string());\n                        }\n\n                        sql_param.push(id.to_string());\n                        order_timestamp_first = false;\n                    }\n                }\n            }\n        }\n\n        if let Some(v) = &param.is_blocked {\n            if *v {\n                sql_where.push(\"is_blocked = 1\".to_string());\n            } else {\n                sql_where.push(\"is_blocked = 0\".to_string());\n            }\n            order_timestamp_first = false;\n        }\n\n        if let Some(v) = &param.is_cached {\n            if *v {\n                sql_where.push(\"is_cached = 1\".to_string());\n            } else {\n                sql_where.push(\"is_cached = 0\".to_string());\n            }\n            order_timestamp_first = false;\n        }\n\n        if is_cursor_prev {\n            is_desc_order = !is_desc_order;\n        }\n\n        if is_desc_order {\n            if order_timestamp_first {\n                sql_order.push_str(\" ORDER BY timestamp DESC, id DESC\");\n            } else {\n                sql_order.push_str(\" ORDER BY id DESC, timestamp DESC\");\n            }\n        } else {\n            if order_timestamp_first {\n                sql_order.push_str(\" ORDER BY timestamp ASC, id ASC\");\n            } else {\n                sql_order.push_str(\" ORDER BY id ASC, timestamp ASC\");\n            }\n        }\n\n        let sql_where = if sql_where.is_empty() {\n            String::new()\n        } else {\n            format!(\" WHERE {}\", sql_where.join(\" AND \"))\n        };\n\n        Ok((sql_where, sql_order, sql_param))\n    }\n\n    pub fn get_domain_list_count(&self, param: Option<&DomainListGetParam>) -> u64 {\n        let conn = self.get_readonly_conn();\n        if conn.as_ref().is_none() {\n            return 0;\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let mut sql = String::new();\n        let mut sql_param = Vec::new();\n        sql.push_str(\"SELECT COUNT(*) FROM domain\");\n        if let Ok((sql_where, sql_order, mut ret_sql_param)) = Self::get_domain_sql_where(param) {\n            sql.push_str(sql_where.as_str());\n            sql.push_str(sql_order.as_str());\n            sql_param.append(&mut ret_sql_param);\n        }\n\n        let mut stmt = conn.prepare(sql.as_str()).unwrap();\n        let rows = stmt.query_map(rusqlite::params_from_iter(sql_param), |row| Ok(row.get(0)?));\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    return row;\n                }\n            }\n        }\n\n        0\n    }\n\n    pub fn delete_domain_by_id(&self, id: u64) -> Result<u64, Box<dyn Error>> {\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n\n        let ret = conn.execute(\"DELETE FROM domain WHERE id = ?\", &[&id]);\n\n        if let Err(e) = ret {\n            return Err(Box::new(e));\n        }\n\n        Ok(ret.unwrap() as u64)\n    }\n\n    pub fn delete_domain_before_timestamp(&self, timestamp: u64) -> Result<u64, Box<dyn Error>> {\n        let ret = {\n            let conn = self.conn.lock().unwrap();\n            if conn.as_ref().is_none() {\n                return Err(\"db is not open\".into());\n            }\n\n            let conn = conn.as_ref().unwrap();\n\n            let ret = conn.execute(\"DELETE FROM domain WHERE timestamp <= ?\", &[&timestamp]);\n\n            if let Err(e) = ret {\n                return Err(Box::new(e));\n            }\n\n            Ok(ret.unwrap() as u64)\n        };\n\n        self.run_vacuum(Some(50000))?;\n\n        ret\n    }\n\n    pub fn refresh_client_top_list(&self, timestamp: u64) -> Result<(), Box<dyn Error>> {\n        let mut client_count_list = Vec::new();\n        let conn = match self.get_readonly_conn() {\n            Some(v) => v,\n            None => return Err(\"db is not open\".into()),\n        };\n\n        let timestamp_now = smartdns::get_utc_time_ms();\n        let sql = \"SELECT client, COUNT(*) FROM domain WHERE timestamp >= ? GROUP BY client ORDER BY COUNT(*) DESC LIMIT 20\";\n        self.debug_query_plan(&conn, sql.to_string(), &vec![timestamp.to_string()]);\n        let mut stmt = conn.prepare(sql)?;\n        let rows = stmt.query_map([timestamp.to_string()], |row| {\n            Ok(ClientQueryCount {\n                client_ip: row.get(0)?,\n                count: row.get(1)?,\n                timestamp_start: timestamp,\n                timestamp_end: timestamp_now,\n            })\n        });\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    client_count_list.push(row);\n                }\n            }\n        }\n\n        let mut conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_mut().unwrap();\n\n        let tx = conn.transaction()?;\n        let mut stmt = tx.prepare(\"DELETE FROM top_client_list\")?;\n        stmt.execute([])?;\n        stmt.finalize()?;\n        let mut stmt =\n            tx.prepare(\"INSERT INTO top_client_list (client, count, timestamp_start, timestamp_end) VALUES ( ?1, ?2, $3, $4)\")?;\n        for client in &client_count_list {\n            stmt.execute(rusqlite::params![\n                client.client_ip,\n                client.count,\n                client.timestamp_start,\n                client.timestamp_end\n            ])?;\n            dns_log!(\n                LogLevel::DEBUG,\n                \"client: {}, count: {}, timestamp_start: {}, timestamp_end: {}\",\n                client.client_ip,\n                client.count,\n                client.timestamp_start,\n                client.timestamp_end\n            );\n        }\n        stmt.finalize()?;\n        tx.commit()?;\n\n        Ok(())\n    }\n\n    pub fn get_client_top_list(&self, count: u32) -> Result<Vec<ClientQueryCount>, Box<dyn Error>> {\n        let mut ret = Vec::new();\n        let conn = self.get_readonly_conn();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let mut stmt =\n            conn.prepare(\"SELECT client, count, timestamp_start, timestamp_end FROM top_client_list ORDER BY count DESC LIMIT ?\")?;\n        let rows = stmt.query_map([count.to_string()], |row| {\n            Ok(ClientQueryCount {\n                client_ip: row.get(0)?,\n                count: row.get(1)?,\n                timestamp_start: row.get(2)?,\n                timestamp_end: row.get(3)?,\n            })\n        });\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    ret.push(row);\n                }\n            }\n        }\n\n        Ok(ret)\n    }\n\n    pub fn delete_daily_query_count_before_timestamp(\n        &self,\n        timestamp: u64,\n    ) -> Result<u64, Box<dyn Error>> {\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n\n        let ret = conn.execute(\n            \"DELETE FROM domain_daily_count WHERE timestamp <= ?\",\n            &[&timestamp],\n        );\n\n        if let Err(e) = ret {\n            return Err(Box::new(e));\n        }\n\n        Ok(ret.unwrap() as u64)\n    }\n\n    pub fn get_daily_query_count(&self, past_days: u32) -> Result<DailyQueryCount, Box<dyn Error>> {\n        let mut ret = Vec::new();\n        let conn = self.get_readonly_conn();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let seconds = 86400 * past_days - utils::seconds_until_next_hour() as u32;\n        let mut stmt = conn.prepare(\n            \"SELECT \\\n                    strftime('%Y-%m-%d', datetime(timestamp / 1000, 'unixepoch', 'localtime')) AS date, timestamp, count \\\n                 FROM \\\n                    domain_daily_count \\\n                 WHERE \\\n                    timestamp >= strftime('%s', 'now') * 1000 - ? * 1000 \\\n                 ORDER BY \\\n                    timestamp DESC;\\\n                 \",\n        )?;\n\n        let rows = stmt.query_map([seconds.to_string()], |row| {\n            Ok(DailyQueryCountItem {\n                day: row.get(0)?,\n                query_count: row.get(2)?,\n            })\n        });\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    ret.push(row);\n                }\n            }\n        }\n\n        Ok(DailyQueryCount {\n            query_timestamp: smartdns::get_utc_time_ms(),\n            daily_query_count: ret,\n        })\n    }\n\n    pub fn delete_hourly_query_count_before_timestamp(\n        &self,\n        timestamp: u64,\n    ) -> Result<u64, Box<dyn Error>> {\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n\n        let ret = conn.execute(\n            \"DELETE FROM domain_hourly_count WHERE timestamp <= ?\",\n            &[&timestamp],\n        );\n\n        if let Err(e) = ret {\n            return Err(Box::new(e));\n        }\n\n        Ok(ret.unwrap() as u64)\n    }\n\n    pub fn get_hourly_query_count(\n        &self,\n        past_hours: u32,\n    ) -> Result<HourlyQueryCount, Box<dyn Error>> {\n        let mut ret = Vec::new();\n        let conn = self.get_readonly_conn();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let query_start = std::time::Instant::now();\n        let conn = conn.as_ref().unwrap();\n        let seconds = 3600 * past_hours - utils::seconds_until_next_hour() as u32;\n\n        let sql = \"SELECT \\\n                    strftime('%Y-%m-%d %H:00:00', datetime(timestamp / 1000, 'unixepoch', 'localtime')) AS hour, timestamp, count \\\n                 FROM \\\n                    domain_hourly_count \\\n                 WHERE \\\n                    timestamp >= strftime('%s', 'now') * 1000 - ? * 1000 \\\n                 ORDER BY \\\n                    timestamp DESC;\\\n                 \";\n        self.debug_query_plan(conn, sql.to_string(), &vec![seconds.to_string()]);\n        let mut stmt = conn.prepare(sql)?;\n\n        let rows = stmt.query_map([seconds.to_string()], |row| {\n            Ok(HourlyQueryCountItem {\n                hour: row.get(0)?,\n                query_count: row.get(2)?,\n            })\n        });\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    ret.push(row);\n                }\n            }\n        }\n\n        dns_log!(\n            LogLevel::DEBUG,\n            \"hourly_query_count time: {}ms\",\n            query_start.elapsed().as_millis()\n        );\n\n        Ok(HourlyQueryCount {\n            query_timestamp: smartdns::get_utc_time_ms(),\n            hourly_query_count: ret,\n        })\n    }\n\n    pub fn refresh_domain_top_list(&self, timestamp: u64) -> Result<(), Box<dyn Error>> {\n        let mut domain_count_list = Vec::new();\n        let conn = match self.get_readonly_conn() {\n            Some(v) => v,\n            None => return Err(\"db is not open\".into()),\n        };\n\n        let timestamp_now = smartdns::get_utc_time_ms();\n        let sql = \"SELECT domain, COUNT(*) FROM domain WHERE timestamp >= ? GROUP BY domain ORDER BY COUNT(*) DESC LIMIT 20\";\n        self.debug_query_plan(&conn, sql.to_string(), &vec![timestamp.to_string()]);\n        let mut stmt = conn.prepare(sql)?;\n        let rows = stmt.query_map([timestamp.to_string()], |row| {\n            Ok(DomainQueryCount {\n                domain: row.get(0)?,\n                count: row.get(1)?,\n                timestamp_start: timestamp,\n                timestamp_end: timestamp_now,\n            })\n        });\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    domain_count_list.push(row);\n                }\n            }\n        }\n\n        let mut conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_mut().unwrap();\n        let tx = conn.transaction()?;\n        let mut stmt = tx.prepare(\"DELETE FROM top_domain_list\")?;\n        stmt.execute([])?;\n        stmt.finalize()?;\n        let mut stmt =\n            tx.prepare(\"INSERT INTO top_domain_list (domain, count, timestamp_start, timestamp_end) VALUES ( ?1, ?2, ?3, ?4)\")?;\n        for domain in &domain_count_list {\n            stmt.execute(rusqlite::params![\n                domain.domain,\n                domain.count,\n                domain.timestamp_start,\n                domain.timestamp_end\n            ])?;\n        }\n        stmt.finalize()?;\n        tx.commit()?;\n\n        Ok(())\n    }\n\n    pub fn get_domain_top_list(&self, count: u32) -> Result<Vec<DomainQueryCount>, Box<dyn Error>> {\n        let mut ret = Vec::new();\n        let conn = self.get_readonly_conn();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n\n        let mut stmt = conn.prepare(\"SELECT domain, count, timestamp_start, timestamp_end FROM top_domain_list DESC LIMIT ?\")?;\n        let rows = stmt.query_map([count.to_string()], |row| {\n            Ok(DomainQueryCount {\n                domain: row.get(0)?,\n                count: row.get(1)?,\n                timestamp_start: row.get(2)?,\n                timestamp_end: row.get(3)?,\n            })\n        });\n\n        if let Err(e) = rows {\n            return Err(Box::new(e));\n        }\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    ret.push(row);\n                }\n            }\n        }\n\n        Ok(ret)\n    }\n\n    pub fn get_domain_list(\n        &self,\n        param: Option<&DomainListGetParam>,\n    ) -> Result<QueryDomainListResult, Box<dyn Error>> {\n        let query_start = std::time::Instant::now();\n        let mut cursor_reverse = false;\n\n        let mut ret = QueryDomainListResult {\n            domain_list: vec![],\n            total_count: 0,\n            step_by_cursor: false,\n        };\n\n        let conn = self.get_readonly_conn();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n\n        let (sql_where, sql_order, mut sql_param) = Self::get_domain_sql_where(param)?;\n\n        let mut sql = String::new();\n        sql.push_str(\"SELECT id, timestamp, domain, domain_type, client, domain_group, reply_code, query_time, ping_time, is_blocked, is_cached FROM domain\");\n\n        sql.push_str(sql_where.as_str());\n        sql.push_str(sql_order.as_str());\n\n        if let Some(p) = param {\n            let mut with_offset = true;\n            if let Some(cursor) = &p.cursor {\n                if cursor.id.is_some() {\n                    sql.push_str(\" LIMIT ?\");\n                    sql_param.push(p.page_size.to_string());\n                    with_offset = false;\n                }\n\n                if cursor.direction.eq_ignore_ascii_case(\"prev\") {\n                    cursor_reverse = true;\n                }\n            }\n\n            if with_offset {\n                sql.push_str(\" LIMIT ? OFFSET ?\");\n                sql_param.push(p.page_size.to_string());\n                sql_param.push(((p.page_num - 1) * p.page_size).to_string());\n            }\n        }\n\n        self.debug_query_plan(conn, sql.clone(), &sql_param);\n        let stmt = conn.prepare(&sql);\n\n        if let Err(e) = stmt {\n            dns_log!(LogLevel::ERROR, \"get_domain_list error: {}\", e);\n            return Err(\"get_domain_list error\".into());\n        }\n\n        let mut stmt = stmt?;\n\n        let rows = stmt.query_map(rusqlite::params_from_iter(sql_param), |row| {\n            Ok(DomainData {\n                id: row.get(0)?,\n                timestamp: row.get(1)?,\n                domain: row.get(2)?,\n                domain_type: row.get(3)?,\n                client: row.get(4)?,\n                domain_group: row.get(5)?,\n                reply_code: row.get(6)?,\n                query_time: row.get(7)?,\n                ping_time: row.get(8)?,\n                is_blocked: row.get(9)?,\n                is_cached: row.get(10)?,\n            })\n        });\n\n        if let Err(e) = rows {\n            return Err(Box::new(e));\n        }\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    ret.domain_list.push(row);\n                }\n            }\n        }\n\n        if cursor_reverse {\n            ret.domain_list.reverse();\n        }\n\n        if let Some(p) = param {\n            if let Some(v) = &p.cursor {\n                ret.total_count = v.total_count;\n                ret.step_by_cursor = true;\n            } else {\n                let total_count = self.get_domain_list_count(param);\n                ret.total_count = total_count;\n            }\n        }\n\n        dns_log!(\n            LogLevel::DEBUG,\n            \"domain_list time: {}ms\",\n            query_start.elapsed().as_millis()\n        );\n        Ok(ret)\n    }\n\n    pub fn insert_client(&self, client_data: &Vec<ClientData>) -> Result<(), Box<dyn Error>> {\n        let mut conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_mut().unwrap();\n        let tx = conn.transaction()?;\n        let mut stmt = tx.prepare(\"INSERT INTO client (id, client_ip, mac, hostname, last_query_timestamp) VALUES (\n            (SELECT MAX(rowid) FROM client) + 1,\n            ?1, ?2, ?3, ?4)\n            ON CONFLICT(client_ip, mac) DO UPDATE SET last_query_timestamp = excluded.last_query_timestamp;\n            \")?;\n        for d in client_data {\n            let ret = stmt.execute(rusqlite::params![\n                d.client_ip,\n                d.mac,\n                d.hostname,\n                d.last_query_timestamp\n            ]);\n\n            if let Err(e) = ret {\n                stmt.finalize()?;\n                tx.rollback()?;\n                return Err(Box::new(e));\n            }\n        }\n        stmt.finalize()?;\n        tx.commit()?;\n\n        Ok(())\n    }\n\n    pub fn get_client_list_count(&self, param: Option<&ClientListGetParam>) -> u64 {\n        let conn = self.get_readonly_conn();\n        if conn.as_ref().is_none() {\n            return 0;\n        }\n\n        let conn = conn.as_ref().unwrap();\n        let mut sql = String::new();\n        let mut sql_param = Vec::new();\n        sql.push_str(\"SELECT COUNT(*) FROM client\");\n        if let Ok((sql_where, sql_order, mut ret_sql_param)) = Self::get_client_sql_where(param) {\n            sql.push_str(sql_where.as_str());\n            sql.push_str(sql_order.as_str());\n            sql_param.append(&mut ret_sql_param);\n        }\n\n        let mut stmt = conn.prepare(sql.as_str()).unwrap();\n        let rows = stmt.query_map(rusqlite::params_from_iter(sql_param), |row| Ok(row.get(0)?));\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    return row;\n                }\n            }\n        }\n\n        0\n    }\n\n    fn get_client_sql_where(\n        param: Option<&ClientListGetParam>,\n    ) -> Result<(String, String, Vec<String>), Box<dyn Error>> {\n        let mut is_desc_order = true;\n        let mut is_cursor_prev = false;\n        let param = match param {\n            Some(v) => v,\n            None => return Ok((String::new(), String::new(), Vec::new())),\n        };\n        let mut order_timestamp_first = false;\n        let mut cusor_with_timestamp = false;\n\n        let mut sql_where = Vec::new();\n        let mut sql_param: Vec<String> = Vec::new();\n        let mut sql_order = String::new();\n\n        if let Some(v) = &param.id {\n            sql_where.push(\"id = ?\".to_string());\n            sql_param.push(v.to_string());\n            order_timestamp_first = false;\n        }\n\n        if let Some(v) = &param.order {\n            if v.eq_ignore_ascii_case(\"asc\") {\n                is_cursor_prev = true;\n            } else if v.eq_ignore_ascii_case(\"desc\") {\n                is_cursor_prev = false;\n            } else {\n                return Err(\"order param error\".into());\n            }\n        }\n\n        if let Some(v) = &param.cursor {\n            if v.direction.eq_ignore_ascii_case(\"prev\") {\n                is_desc_order = !is_desc_order;\n            } else if v.direction.eq_ignore_ascii_case(\"next\") {\n                // do nothing\n            } else {\n                return Err(\"cursor direction param error\".into());\n            }\n        }\n\n        if let Some(v) = &param.client_ip {\n            sql_where.push(\"client_ip = ?\".to_string());\n            sql_param.push(v.to_string());\n        }\n\n        if let Some(v) = &param.mac {\n            sql_where.push(\"mac = ?\".to_string());\n            sql_param.push(v.to_string());\n        }\n\n        if let Some(v) = &param.hostname {\n            sql_where.push(\"hostname = ?\".to_string());\n            sql_param.push(v.to_string());\n        }\n\n        if let Some(v) = &param.timestamp_before {\n            let mut use_cursor = false;\n            if param.cursor.is_some() && (is_desc_order || is_cursor_prev) {\n                let v = param.cursor.as_ref().unwrap().id;\n                if let Some(v) = v {\n                    sql_where.push(\"id < ?\".to_string());\n                    sql_param.push(v.to_string());\n                    use_cursor = true;\n                    order_timestamp_first = false;\n                    cusor_with_timestamp = true;\n                }\n            }\n\n            if use_cursor == false {\n                sql_where.push(\"last_query_timestamp <= ?\".to_string());\n                sql_param.push(v.to_string());\n            }\n        }\n\n        if let Some(v) = &param.timestamp_after {\n            let mut use_cursor = false;\n            if param.cursor.is_some() && (!is_desc_order || is_cursor_prev) {\n                let v = param.cursor.as_ref().unwrap().id;\n                if let Some(v) = v {\n                    sql_where.push(\"id > ?\".to_string());\n                    sql_param.push(v.to_string());\n                    use_cursor = true;\n                    order_timestamp_first = false;\n                    cusor_with_timestamp = true;\n                }\n            }\n\n            if use_cursor == false {\n                sql_where.push(\"last_query_timestamp >= ?\".to_string());\n                sql_param.push(v.to_string());\n            }\n        }\n\n        if !cusor_with_timestamp {\n            if let Some(v) = &param.cursor {\n                if is_cursor_prev {\n                    if let Some(id) = &v.id {\n                        if is_desc_order {\n                            sql_where.push(\"id > ?\".to_string());\n                        } else {\n                            sql_where.push(\"id < ?\".to_string());\n                        }\n\n                        sql_param.push(id.to_string());\n                        order_timestamp_first = false;\n                    }\n                } else {\n                    if let Some(id) = &v.id {\n                        if is_desc_order {\n                            sql_where.push(\"id < ?\".to_string());\n                        } else {\n                            sql_where.push(\"id > ?\".to_string());\n                        }\n\n                        sql_param.push(id.to_string());\n                        order_timestamp_first = false;\n                    }\n                }\n            }\n        }\n\n        if is_desc_order {\n            if order_timestamp_first {\n                sql_order.push_str(\" ORDER BY last_query_timestamp DESC, id DESC\");\n            } else {\n                sql_order.push_str(\" ORDER BY id DESC, last_query_timestamp DESC\");\n            }\n        } else {\n            if order_timestamp_first {\n                sql_order.push_str(\" ORDER BY last_query_timestamp ASC, id ASC\");\n            } else {\n                sql_order.push_str(\" ORDER BY id ASC, last_query_timestamp ASC\");\n            }\n        }\n\n        let sql_where = if sql_where.is_empty() {\n            String::new()\n        } else {\n            format!(\" WHERE {}\", sql_where.join(\" AND \"))\n        };\n\n        Ok((sql_where, sql_order, sql_param))\n    }\n\n    pub fn get_client_list(\n        &self,\n        param: Option<&ClientListGetParam>,\n    ) -> Result<QueryClientListResult, Box<dyn Error>> {\n        let query_start = std::time::Instant::now();\n        let mut cursor_reverse = false;\n\n        let mut ret = QueryClientListResult {\n            client_list: vec![],\n            total_count: 0,\n            step_by_cursor: false,\n        };\n\n        let conn = self.get_readonly_conn();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n\n        let (sql_where, sql_order, mut sql_param) = Self::get_client_sql_where(param)?;\n\n        let mut sql = String::new();\n        sql.push_str(\"SELECT id, client_ip, mac, hostname, last_query_timestamp FROM client\");\n\n        sql.push_str(sql_where.as_str());\n        sql.push_str(sql_order.as_str());\n\n        if let Some(p) = param {\n            let mut with_offset = true;\n            if let Some(cursor) = &p.cursor {\n                if cursor.id.is_some() {\n                    sql.push_str(\" LIMIT ?\");\n                    sql_param.push(p.page_size.to_string());\n                    with_offset = false;\n                }\n\n                if cursor.direction.eq_ignore_ascii_case(\"prev\") {\n                    cursor_reverse = true;\n                }\n            }\n\n            if with_offset {\n                sql.push_str(\" LIMIT ? OFFSET ?\");\n                sql_param.push(p.page_size.to_string());\n                sql_param.push(((p.page_num - 1) * p.page_size).to_string());\n            }\n        }\n\n        self.debug_query_plan(conn, sql.clone(), &sql_param);\n        let stmt = conn.prepare(&sql);\n        if let Err(e) = stmt {\n            dns_log!(LogLevel::ERROR, \"get_client_list error: {}\", e);\n            return Err(\"get_client_list error\".into());\n        }\n        let mut stmt = stmt?;\n\n        let rows = stmt.query_map(rusqlite::params_from_iter(sql_param), |row| {\n            Ok(ClientData {\n                id: row.get(0)?,\n                client_ip: row.get(1)?,\n                mac: row.get(2)?,\n                hostname: row.get(3)?,\n                last_query_timestamp: row.get(4)?,\n            })\n        });\n\n        if let Err(e) = rows {\n            return Err(Box::new(e));\n        }\n\n        if let Ok(rows) = rows {\n            for row in rows {\n                if let Ok(row) = row {\n                    ret.client_list.push(row);\n                }\n            }\n        }\n\n        if cursor_reverse {\n            ret.client_list.reverse();\n        }\n\n        if let Some(p) = param {\n            if let Some(v) = &p.cursor {\n                ret.total_count = v.total_count;\n                ret.step_by_cursor = true;\n            } else {\n                let total_count = self.get_client_list_count(param);\n                ret.total_count = total_count;\n            }\n        }\n\n        dns_log!(\n            LogLevel::DEBUG,\n            \"domain_list time: {}ms\",\n            query_start.elapsed().as_millis()\n        );\n        Ok(ret)\n    }\n\n    pub fn delete_client_by_id(&self, id: u64) -> Result<u64, Box<dyn Error>> {\n        let conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return Err(\"db is not open\".into());\n        }\n\n        let conn = conn.as_ref().unwrap();\n\n        let ret = conn.execute(\"DELETE FROM client WHERE id = ?\", &[&id]);\n\n        if let Err(e) = ret {\n            return Err(Box::new(e));\n        }\n\n        Ok(ret.unwrap() as u64)\n    }\n\n    pub fn get_db_size(&self) -> u64 {\n        let db_file = self.get_db_file_path();\n        let mut total_size = 0;\n        if db_file.is_none() {\n            return 0;\n        }\n\n        let db_file = db_file.unwrap();\n        let wal_file = db_file.clone() + \"-wal\";\n\n        let metadata = fs::metadata(db_file);\n        if let Err(_) = metadata {\n            return 0;\n        }\n        total_size += metadata.unwrap().len();\n\n        let wal_metadata = fs::metadata(wal_file);\n        if let Ok(wal_metadata) = wal_metadata {\n            let wal_size = wal_metadata.len();\n            total_size += wal_size;\n        }\n\n        total_size\n    }\n\n    pub fn close(&self) {\n        let mut conn = self.conn.lock().unwrap();\n        if conn.as_ref().is_none() {\n            return;\n        }\n\n        if let Some(t) = conn.take() {\n            let _ = t.close();\n        }\n    }\n}\n\nimpl Drop for DB {\n    fn drop(&mut self) {\n        self.close();\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/http_api_msg.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse crate::data_server::*;\nuse crate::data_upstream_server::UpstreamServerInfo;\nuse crate::db::*;\nuse crate::smartdns::LogLevel;\nuse crate::whois::WhoIsInfo;\nuse serde_json::json;\nuse std::collections::HashMap;\nuse std::error::Error;\n\n#[derive(Debug)]\npub struct AuthUser {\n    pub username: String,\n    pub password: String,\n}\n\n#[derive(Debug)]\npub struct TokenResponse {\n    pub token: String,\n    pub expires_in: String,\n}\n\npub fn api_msg_parse_auth(data: &str) -> Result<AuthUser, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let username = v[\"username\"].as_str();\n    if username.is_none() {\n        return Err(\"username not found\".into());\n    }\n    let password = v[\"password\"].as_str();\n    if password.is_none() {\n        return Err(\"password not found\".into());\n    }\n\n    Ok(AuthUser {\n        username: username.unwrap().to_string(),\n        password: password.unwrap().to_string(),\n    })\n}\n\npub fn api_msg_parse_auth_password_change(data: &str) -> Result<(String, String), Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let old_password = v[\"old_password\"].as_str();\n    if old_password.is_none() {\n        return Err(\"old_password not found\".into());\n    }\n    let password = v[\"password\"].as_str();\n    if password.is_none() {\n        return Err(\"password not found\".into());\n    }\n\n    Ok((\n        old_password.unwrap().to_string(),\n        password.unwrap().to_string(),\n    ))\n}\n\npub fn api_msg_gen_auth_password_change(old_password: &str, password: &str) -> String {\n    let json_str = json!({\n        \"old_password\": old_password,\n        \"password\": password,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_gen_auth_login(auth: &AuthUser) -> String {\n    let json_str = json!({\n        \"username\": auth.username,\n        \"password\": auth.password,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_count(data: &str) -> Result<i64, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let count = v[\"count\"].as_i64();\n    if count.is_none() {\n        return Err(\"count not found\".into());\n    }\n\n    Ok(count.unwrap())\n}\n\npub fn api_msg_gen_count(count: i64) -> String {\n    let json_str = json!({\n        \"count\": count,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_json_object_domain_value(\n    data: &serde_json::Value,\n) -> Result<DomainData, Box<dyn Error>> {\n    let id = data[\"id\"].as_u64();\n    if id.is_none() {\n        return Err(\"id not found\".into());\n    }\n\n    let timestamp = data[\"timestamp\"].as_u64();\n    if timestamp.is_none() {\n        return Err(\"timestamp not found\".into());\n    }\n\n    let domain = data[\"domain\"].as_str();\n    if domain.is_none() {\n        return Err(\"domain not found\".into());\n    }\n\n    let domain_type = data[\"domain_type\"].as_u64();\n    if domain_type.is_none() {\n        return Err(\"domain_type not found\".into());\n    }\n\n    let client = data[\"client\"].as_str();\n    if client.is_none() {\n        return Err(\"client not found\".into());\n    }\n\n    let domain_group = data[\"domain_group\"].as_str();\n    if domain_group.is_none() {\n        return Err(\"domain_group not found\".into());\n    }\n\n    let reply_code = data[\"reply_code\"].as_u64();\n    if reply_code.is_none() {\n        return Err(\"reply_code not found\".into());\n    }\n\n    let query_time = data[\"query_time\"].as_i64();\n    if query_time.is_none() {\n        return Err(\"query_time not found\".into());\n    }\n\n    let ping_time = data[\"ping_time\"].as_f64();\n    if ping_time.is_none() {\n        return Err(\"ping_time not found\".into());\n    }\n\n    let is_blocked = data[\"is_blocked\"].as_bool();\n    if is_blocked.is_none() {\n        return Err(\"is_blocked not found\".into());\n    }\n\n    let is_cached = data[\"is_cached\"].as_bool();\n    if is_cached.is_none() {\n        return Err(\"is_cached not found\".into());\n    }\n\n    Ok(DomainData {\n        id: id.unwrap(),\n        timestamp: timestamp.unwrap(),\n        domain: domain.unwrap().to_string(),\n        domain_type: domain_type.unwrap() as u32,\n        client: client.unwrap().to_string(),\n        domain_group: domain_group.unwrap().to_string(),\n        reply_code: reply_code.unwrap() as u16,\n        query_time: query_time.unwrap() as i32,\n        ping_time: ping_time.unwrap(),\n        is_blocked: is_blocked.unwrap(),\n        is_cached: is_cached.unwrap(),\n    })\n}\n\npub fn api_msg_parse_domain(data: &str) -> Result<DomainData, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    api_msg_parse_json_object_domain_value(&v)\n}\n\npub fn api_msg_gen_json_object_domain(domain: &DomainData) -> serde_json::Value {\n    json!({\n        \"id\": domain.id,\n        \"timestamp\": domain.timestamp,\n        \"domain\": domain.domain,\n        \"domain_type\": domain.domain_type,\n        \"client\": domain.client,\n        \"domain_group\": domain.domain_group,\n        \"reply_code\": domain.reply_code,\n        \"query_time\": domain.query_time,\n        \"ping_time\": domain.ping_time,\n        \"is_blocked\": domain.is_blocked,\n        \"is_cached\": domain.is_cached,\n    })\n}\n\npub fn api_msg_gen_domain(domain: &DomainData) -> String {\n    let json_str = api_msg_gen_json_object_domain(domain);\n    json_str.to_string()\n}\n\npub fn api_msg_parse_domain_list(data: &str) -> Result<Vec<DomainData>, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let list_count = v[\"list_count\"].as_u64();\n    if list_count.is_none() {\n        return Err(\"list_count not found\".into());\n    }\n    let list_count = list_count.unwrap();\n    let mut domain_list = Vec::new();\n    for i in 0..list_count {\n        let domain_object = &v[\"domain_list\"][i as usize];\n        let domain_data = api_msg_parse_json_object_domain_value(domain_object)?;\n        domain_list.push(domain_data);\n    }\n\n    Ok(domain_list)\n}\n\npub fn api_msg_gen_domain_list(\n    domain_list_result: &QueryDomainListResult,\n    total_page: u64,\n    total_count: u64,\n) -> String {\n    let json_str = json!({\n        \"list_count\": domain_list_result.domain_list.len(),\n        \"total_page\": total_page,\n        \"total_count\": total_count,\n        \"step_by_cursor\": domain_list_result.step_by_cursor,\n        \"domain_list\":\n        domain_list_result.domain_list\n                .iter()\n                .map(|x| {\n                    api_msg_gen_json_object_domain(x)\n                })\n                .collect::<Vec<serde_json::Value>>()\n\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_client_list(data: &str) -> Result<Vec<ClientData>, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let list_count = v[\"list_count\"].as_u64();\n    if list_count.is_none() {\n        return Err(\"list_count not found\".into());\n    }\n\n    let list_count = list_count.unwrap();\n    let mut client_list = Vec::new();\n    for i in 0..list_count {\n        let client_object = &v[\"client_list\"][i as usize];\n        let id = client_object[\"id\"].as_u64();\n        if id.is_none() {\n            return Err(\"id not found\".into());\n        }\n\n        let client_ip = client_object[\"client_ip\"].as_str();\n        if client_ip.is_none() {\n            return Err(\"client_ip not found\".into());\n        }\n\n        let mac = client_object[\"mac\"].as_str();\n        if mac.is_none() {\n            return Err(\"mac not found\".into());\n        }\n\n        let hostname = client_object[\"hostname\"].as_str();\n        if hostname.is_none() {\n            return Err(\"hostname not found\".into());\n        }\n\n        let last_query_timestamp = client_object[\"last_query_timestamp\"].as_u64();\n        if last_query_timestamp.is_none() {\n            return Err(\"last_query_timestamp not found\".into());\n        }\n\n        client_list.push(ClientData {\n            id: id.unwrap() as u32,\n            client_ip: client_ip.unwrap().to_string(),\n            mac: mac.unwrap().to_string(),\n            hostname: hostname.unwrap().to_string(),\n            last_query_timestamp: last_query_timestamp.unwrap(),\n        });\n    }\n\n    Ok(client_list)\n}\n\npub fn api_msg_gen_json_object_client(client: &ClientData) -> serde_json::Value {\n    json!({\n        \"id\": client.id,\n        \"client_ip\": client.client_ip,\n        \"mac\": client.mac,\n        \"hostname\": client.hostname,\n        \"last_query_timestamp\": client.last_query_timestamp,\n    })\n}\n\npub fn api_msg_gen_client_list(\n    client_list_result: &QueryClientListResult,\n    total_page: u64,\n    total_count: u64,\n) -> String {\n    let json_str = json!({\n        \"list_count\": client_list_result.client_list.len(),\n        \"total_page\": total_page,\n        \"total_count\": total_count,\n        \"step_by_cursor\": client_list_result.step_by_cursor,\n        \"client_list\":\n        client_list_result.client_list\n                .iter()\n                .map(|x| {\n                    api_msg_gen_json_object_client(x)\n                })\n                .collect::<Vec<serde_json::Value>>()\n\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_auth_token(token: &str, expired: &str) -> String {\n    let json_str = json!({\n        \"token\": token,\n        \"token_type\": \"Bearer\",\n        \"expires_in\": expired,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_auth_token(data: &str) -> Result<TokenResponse, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let token = v[\"token\"].as_str();\n    if token.is_none() {\n        return Err(\"token not found\".into());\n    }\n    let expired = v[\"expires_in\"].as_str();\n    if expired.is_none() {\n        return Err(\"expires_in not found\".into());\n    }\n\n    Ok(TokenResponse {\n        token: token.unwrap().to_string(),\n        expires_in: expired.unwrap().to_string(),\n    })\n}\n\npub fn api_msg_gen_cache_number(cache_number: i32) -> String {\n    let json_str = json!({\n        \"cache_number\": cache_number,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_cache_number(data: &str) -> Result<i32, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let cache_number = v[\"cache_number\"].as_i64();\n    if cache_number.is_none() {\n        return Err(\"cache_number not found\".into());\n    }\n\n    Ok(cache_number.unwrap() as i32)\n}\n\npub fn api_msg_error(msg: &str) -> String {\n    let json_str = json!({\n        \"error\": msg,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_error(data: &str) -> Result<String, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let error = v[\"error\"].as_str();\n    if error.is_none() {\n        return Err(\"error not found\".into());\n    }\n\n    Ok(error.unwrap().to_string())\n}\n\npub fn api_msg_parse_loglevel(data: &str) -> Result<LogLevel, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let loglevel = v[\"log_level\"].as_str();\n    if loglevel.is_none() {\n        return Err(\"loglevel not found\".into());\n    }\n\n    let ret = loglevel.unwrap().try_into();\n    if ret.is_err() {\n        return Err(\"log level is invalid\".into());\n    }\n\n    Ok(ret.unwrap())\n}\n\npub fn api_msg_gen_loglevel(loglevel: LogLevel) -> String {\n    let loglevel_str = loglevel.to_string();\n    let json_str = json!({\n        \"log_level\": loglevel_str,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_gen_version(smartdns_version: &str, ui_version: &str) -> String {\n    let json_str = json!({\n        \"smartdns\": smartdns_version,\n        \"smartdns_ui\": ui_version,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_gen_upstream_server_list(upstream_server_list: &Vec<UpstreamServerInfo>) -> String {\n    let json_str = json!({\n        \"upstream_server_list\":\n            upstream_server_list\n                .iter()\n                .map(|x| {\n                    let s = json!({\n                        \"host\": x.host,\n                        \"ip\": x.ip,\n                        \"port\": x.port,\n                        \"server_type\": x.server_type.to_string(),\n                        \"total_query_count\": x.total_query_count,\n                        \"total_query_success\": x.total_query_success,\n                        \"total_query_recv_count\": x.total_query_recv_count,\n                        \"query_success_rate\": x.query_success_rate,\n                        \"avg_time\": x.avg_time,\n                        \"status\": x.status,\n                        \"security\": x.security,\n                    });\n                    s\n                })\n                .collect::<Vec<serde_json::Value>>()\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_upstream_server_list(\n    data: &str,\n) -> Result<Vec<UpstreamServerInfo>, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let mut upstream_server_list = Vec::new();\n    let server_list = v[\"upstream_server_list\"].as_array();\n    if server_list.is_none() {\n        return Err(\"list_count not found\".into());\n    }\n\n    for item in server_list.unwrap() {\n        let host = item[\"host\"].as_str();\n        if host.is_none() {\n            return Err(\"host not found\".into());\n        }\n\n        let ip = item[\"ip\"].as_str();\n        if ip.is_none() {\n            return Err(\"ip not found\".into());\n        }\n\n        let port = item[\"port\"].as_u64();\n        if port.is_none() {\n            return Err(\"port not found\".into());\n        }\n\n        let server_type = item[\"server_type\"].as_str();\n        if server_type.is_none() {\n            return Err(\"server_type not found\".into());\n        }\n\n        let total_query_count = item[\"total_query_count\"].as_u64();\n        if total_query_count.is_none() {\n            return Err(\"total_query_count not found\".into());\n        }\n\n        let total_query_success = item[\"total_query_success\"].as_u64();\n        if total_query_success.is_none() {\n            return Err(\"total_query_success not found\".into());\n        }\n\n        let total_query_recv_count = item[\"total_query_recv_count\"].as_u64();\n        if total_query_recv_count.is_none() {\n            return Err(\"total_query_recv_count not found\".into());\n        }\n\n        let query_success_rate = item[\"query_success_rate\"].as_f64();\n        if query_success_rate.is_none() {\n            return Err(\"query_success_rate not found\".into());\n        }\n\n        let avg_time = item[\"avg_time\"].as_f64();\n        if avg_time.is_none() {\n            return Err(\"avg_time not found\".into());\n        }\n\n        let status = item[\"status\"].as_str();\n        if status.is_none() {\n            return Err(\"status not found\".into());\n        }\n\n        let security = item[\"security\"].as_str();\n        if security.is_none() {\n            return Err(\"security not found\".into());\n        }\n\n        upstream_server_list.push(UpstreamServerInfo {\n            host: host.unwrap().to_string(),\n            ip: ip.unwrap().to_string(),\n            port: port.unwrap() as u16,\n            server_type: server_type.unwrap().parse()?,\n            total_query_count: total_query_count.unwrap() as u64,\n            total_query_success: total_query_success.unwrap() as u64,\n            total_query_recv_count: total_query_recv_count.unwrap() as u64,\n            query_success_rate: query_success_rate.unwrap(),\n            avg_time: avg_time.unwrap(),\n            status: status.unwrap().to_string(),\n            security: security.unwrap().to_string(),\n        });\n    }\n\n    Ok(upstream_server_list)\n}\n\npub fn api_msg_parse_version(data: &str) -> Result<(String, String), Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let smartdns = v[\"smartdns\"].as_str();\n    if smartdns.is_none() {\n        return Err(\"smartdns not found\".into());\n    }\n    let ui = v[\"smartdns_ui\"].as_str();\n    if ui.is_none() {\n        return Err(\"ui not found\".into());\n    }\n\n    Ok((smartdns.unwrap().to_string(), ui.unwrap().to_string()))\n}\n\npub fn api_msg_gen_key_value(data: &HashMap<String, String>) -> String {\n    let mut json_map = serde_json::Map::new();\n\n    for (key, value) in data {\n        json_map.insert(key.clone(), serde_json::Value::String(value.clone()));\n    }\n\n    serde_json::Value::Object(json_map).to_string()\n}\n\npub fn api_msg_parse_key_value(data: &str) -> Result<HashMap<String, String>, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let mut conf_map = HashMap::new();\n\n    if let serde_json::Value::Object(map) = v {\n        for (key, value) in map {\n            if let serde_json::Value::String(value_str) = value {\n                conf_map.insert(key, value_str);\n            }\n        }\n    }\n\n    Ok(conf_map)\n}\n\npub fn api_msg_gen_top_client_list(client_list: &Vec<ClientQueryCount>) -> String {\n    let json_str = json!({\n        \"client_top_list\":\n            client_list\n                .iter()\n                .map(|x| {\n                    let s = json!({\n                        \"client_ip\": x.client_ip,\n                        \"query_count\": x.count,\n                        \"timestamp_start\": x.timestamp_start,\n                        \"timestamp_end\": x.timestamp_end,\n                    });\n                    s\n                })\n                .collect::<Vec<serde_json::Value>>()\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_top_client_list(data: &str) -> Result<Vec<ClientQueryCount>, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let mut client_list = Vec::new();\n    let top_list = v[\"client_top_list\"].as_array();\n    if top_list.is_none() {\n        return Err(\"list_count not found\".into());\n    }\n\n    for item in top_list.unwrap() {\n        let client_ip = item[\"client_ip\"].as_str();\n        if client_ip.is_none() {\n            return Err(\"client_ip not found\".into());\n        }\n\n        let query_count = item[\"query_count\"].as_u64();\n        if query_count.is_none() {\n            return Err(\"query_count not found\".into());\n        }\n\n        let timestamp_start = item[\"timestamp_start\"].as_u64();\n        if timestamp_start.is_none() {\n            return Err(\"timestamp_start not found\".into());\n        }\n\n        let timestamp_end = item[\"timestamp_end\"].as_u64();\n        if timestamp_end.is_none() {\n            return Err(\"timestamp_end not found\".into());\n        }\n\n        client_list.push(ClientQueryCount {\n            client_ip: client_ip.unwrap().to_string(),\n            count: query_count.unwrap() as u32,\n            timestamp_start: timestamp_start.unwrap(),\n            timestamp_end: timestamp_end.unwrap(),\n        });\n    }\n\n    Ok(client_list)\n}\n\npub fn api_msg_gen_top_domain_list(domain_list: &Vec<DomainQueryCount>) -> String {\n    let json_str = json!({\n        \"domain_top_list\":\n            domain_list\n                .iter()\n                .map(|x| {\n                    let s = json!({\n                        \"domain\": x.domain,\n                        \"query_count\": x.count,\n                        \"timestamp_start\": x.timestamp_start,\n                        \"timestamp_end\": x.timestamp_end,\n                    });\n                    s\n                })\n                .collect::<Vec<serde_json::Value>>()\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_top_domain_list(data: &str) -> Result<Vec<DomainQueryCount>, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let mut domain_list = Vec::new();\n    let top_list = v[\"domain_top_list\"].as_array();\n    if top_list.is_none() {\n        return Err(\"list_count not found\".into());\n    }\n\n    for item in top_list.unwrap() {\n        let domain = item[\"domain\"].as_str();\n        if domain.is_none() {\n            return Err(\"domain not found\".into());\n        }\n\n        let query_count = item[\"query_count\"].as_u64();\n        if query_count.is_none() {\n            return Err(\"query_count not found\".into());\n        }\n\n        let timestamp_start = item[\"timestamp_start\"].as_u64();\n        if timestamp_start.is_none() {\n            return Err(\"timestamp_start not found\".into());\n        }\n\n        let timestamp_end = item[\"timestamp_end\"].as_u64();\n        if timestamp_end.is_none() {\n            return Err(\"timestamp_end not found\".into());\n        }\n\n        domain_list.push(DomainQueryCount {\n            domain: domain.unwrap().to_string(),\n            count: query_count.unwrap() as u32,\n            timestamp_start: timestamp_start.unwrap(),\n            timestamp_end: timestamp_end.unwrap(),\n        });\n    }\n\n    Ok(domain_list)\n}\n\npub fn api_msg_gen_metrics_data(data: &MetricsData) -> String {\n    let json_str = json!({\n        \"total_query_count\": data.total_query_count,\n        \"block_query_count\": data.block_query_count,\n        \"request_drop_count\": data.request_drop_count,\n        \"fail_query_count\": data.fail_query_count,\n        \"avg_query_time\": data.avg_query_time,\n        \"cache_hit_rate\": data.cache_hit_rate,\n        \"cache_number\": data.cache_number,\n        \"cache_memory_size\": data.cache_memory_size,\n        \"qps\": data.qps,\n        \"memory_usage\": data.memory_usage,\n        \"is_metrics_suspended\": data.is_metrics_suspended,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_metrics_data(data: &str) -> Result<MetricsData, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let total_query_count = v[\"total_query_count\"].as_u64();\n    if total_query_count.is_none() {\n        return Err(\"total_query_count not found\".into());\n    }\n\n    let block_query_count = v[\"block_query_count\"].as_u64();\n    if block_query_count.is_none() {\n        return Err(\"block_query_count not found\".into());\n    }\n\n    let request_drop_count = v[\"request_drop_count\"].as_u64();\n    if request_drop_count.is_none() {\n        return Err(\"request_drop_count not found\".into());\n    }\n\n    let fail_query_count = v[\"fail_query_count\"].as_u64();\n    if fail_query_count.is_none() {\n        return Err(\"fail_query_count not found\".into());\n    }\n\n    let avg_query_time = v[\"avg_query_time\"].as_f64();\n    if avg_query_time.is_none() {\n        return Err(\"avg_query_time not found\".into());\n    }\n\n    let cache_hit_rate = v[\"cache_hit_rate\"].as_f64();\n    if cache_hit_rate.is_none() {\n        return Err(\"cache_hit_rate not found\".into());\n    }\n\n    let cache_number = v[\"cache_number\"].as_u64();\n    if cache_number.is_none() {\n        return Err(\"cache_number not found\".into());\n    }\n\n    let cache_memory_size = v[\"cache_memory_size\"].as_u64();\n    if cache_memory_size.is_none() {\n        return Err(\"cache_memory_size not found\".into());\n    }\n\n    let qps = v[\"qps\"].as_u64();\n    if qps.is_none() {\n        return Err(\"qps not found\".into());\n    }\n\n    let memory_usage = v[\"memory_usage\"].as_u64();\n    if memory_usage.is_none() {\n        return Err(\"memory_usage not found\".into());\n    }\n\n    let is_metrics_suspended = v[\"is_metrics_suspended\"].as_bool();\n\n    Ok(MetricsData {\n        total_query_count: total_query_count.unwrap() as u64,\n        block_query_count: block_query_count.unwrap() as u64,\n        request_drop_count: request_drop_count.unwrap() as u64,\n        fail_query_count: fail_query_count.unwrap() as u64,\n        avg_query_time: avg_query_time.unwrap(),\n        cache_hit_rate: cache_hit_rate.unwrap(),\n        cache_number: cache_number.unwrap() as u64,\n        cache_memory_size: cache_memory_size.unwrap() as u64,\n        qps: qps.unwrap() as u32,\n        memory_usage: memory_usage.unwrap() as u64,\n        is_metrics_suspended: is_metrics_suspended.unwrap_or(false),\n    })\n}\n\npub fn api_msg_gen_stats_overview(data: &OverviewData) -> String {\n    let json_str = json!({\n        \"server_name\": data.server_name,\n        \"database_size\": data.db_size,\n        \"startup_timestamp\": data.startup_timestamp,\n        \"free_disk_space\": data.free_disk_space,\n        \"is_process_suspended\": data.is_process_suspended,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_stats_overview(data: &str) -> Result<OverviewData, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n\n    let server_name = v[\"server_name\"].as_str();\n    if server_name.is_none() {\n        return Err(\"server_name not found\".into());\n    }\n\n    let db_size = v[\"database_size\"].as_u64();\n    if db_size.is_none() {\n        return Err(\"database_size not found\".into());\n    }\n\n    let startup_timestamp = v[\"startup_timestamp\"].as_u64();\n    if startup_timestamp.is_none() {\n        return Err(\"startup_timestamp not found\".into());\n    }\n\n    let free_disk_space = v[\"free_disk_space\"].as_u64();\n    if free_disk_space.is_none() {\n        return Err(\"free_disk_space not found\".into());\n    }\n\n    let is_process_suspended = v[\"is_process_suspended\"].as_bool();\n    if is_process_suspended.is_none() {\n        return Err(\"is_process_suspended not found\".into());\n    }\n\n    Ok(OverviewData {\n        server_name: server_name.unwrap().to_string(),\n        db_size: db_size.unwrap() as u64,\n        startup_timestamp: startup_timestamp.unwrap() as u64,\n        free_disk_space: free_disk_space.unwrap() as u64,\n        is_process_suspended: is_process_suspended.unwrap(),\n    })\n}\n\npub fn api_msg_gen_hourly_query_count(hourly_count: &HourlyQueryCount) -> String {\n    let json_str = json!({\n        \"query_timestamp\": hourly_count.query_timestamp,\n        \"hourly_query_count\":\n        hourly_count.hourly_query_count\n                .iter()\n                .map(|x| {\n                    let s = json!({\n                        \"hour\": x.hour,\n                        \"query_count\": x.query_count,\n                    });\n                    s\n                })\n                .collect::<Vec<serde_json::Value>>()\n    });\n    json_str.to_string()\n}\n\npub fn api_msg_parse_hourly_query_count(data: &str) -> Result<HourlyQueryCount, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let query_timestamp = v[\"query_timestamp\"].as_u64();\n    if query_timestamp.is_none() {\n        return Err(\"query_timestamp not found\".into());\n    }\n\n    let mut hourly_query_count = Vec::new();\n    let hourly_list = v[\"hourly_query_count\"].as_array();\n    if hourly_list.is_none() {\n        return Err(\"hourly_query_count not found\".into());\n    }\n\n    for item in hourly_list.unwrap() {\n        let hour = item[\"hour\"].as_str();\n        if hour.is_none() {\n            return Err(\"hour not found\".into());\n        }\n\n        let query_count = item[\"query_count\"].as_u64();\n        if query_count.is_none() {\n            return Err(\"query_count not found\".into());\n        }\n\n        hourly_query_count.push(HourlyQueryCountItem {\n            hour: hour.unwrap().to_string(),\n            query_count: query_count.unwrap() as u32,\n        });\n    }\n\n    Ok(HourlyQueryCount {\n        query_timestamp: query_timestamp.unwrap(),\n        hourly_query_count: hourly_query_count,\n    })\n}\n\npub fn api_msg_gen_request_qps(qps: u32) -> String {\n    let json_str = json!({\n        \"qps\": qps,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_gen_daily_query_count(daily_count: &DailyQueryCount) -> String {\n    let json_str = json!({\n        \"query_timestamp\": daily_count.query_timestamp,\n        \"daily_query_count\":\n        daily_count.daily_query_count\n                .iter()\n                .map(|x| {\n                    let s = json!({\n                        \"day\": x.day,\n                        \"query_count\": x.query_count,\n                    });\n                    s\n                })\n                .collect::<Vec<serde_json::Value>>()\n    });\n    json_str.to_string()\n}\n\npub fn api_msg_parse_daily_query_count(data: &str) -> Result<DailyQueryCount, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let mut daily_query_count = Vec::new();\n    let query_timestamp = v[\"query_timestamp\"].as_u64();\n    if query_timestamp.is_none() {\n        return Err(\"query_timestamp not found\".into());\n    }\n\n    let daily_list = v[\"daily_query_count\"].as_array();\n    if daily_list.is_none() {\n        return Err(\"daily_query_count not found\".into());\n    }\n\n    for item in daily_list.unwrap() {\n        let day = item[\"day\"].as_str();\n        if day.is_none() {\n            return Err(\"day not found\".into());\n        }\n\n        let query_count = item[\"query_count\"].as_u64();\n        if query_count.is_none() {\n            return Err(\"query_count not found\".into());\n        }\n\n        daily_query_count.push(DailyQueryCountItem {\n            day: day.unwrap().to_string(),\n            query_count: query_count.unwrap() as u32,\n        });\n    }\n\n    Ok(DailyQueryCount {\n        query_timestamp: query_timestamp.unwrap(),\n        daily_query_count: daily_query_count,\n    })\n}\n\npub fn api_msg_gen_whois_info(data: &WhoIsInfo) -> String {\n    let json_str = json!({\n        \"domain\": data.domain,\n        \"registrar\": data.registrar,\n        \"organization\": data.organization,\n        \"address\": data.address,\n        \"city\": data.city,\n        \"country\": data.country,\n    });\n\n    json_str.to_string()\n}\n\npub fn api_msg_parse_whois_info(data: &str) -> Result<WhoIsInfo, Box<dyn Error>> {\n    let v: serde_json::Value = serde_json::from_str(data)?;\n    let domain = v[\"domain\"].as_str();\n    if domain.is_none() {\n        return Err(\"domain not found\".into());\n    }\n\n    let registrar = v[\"registrar\"].as_str();\n    if registrar.is_none() {\n        return Err(\"registrar not found\".into());\n    }\n\n    let organization = v[\"organization\"].as_str();\n    if organization.is_none() {\n        return Err(\"organization not found\".into());\n    }\n\n    let address = v[\"address\"].as_str();\n    if address.is_none() {\n        return Err(\"address not found\".into());\n    }\n\n    let city = v[\"city\"].as_str();\n    if city.is_none() {\n        return Err(\"city not found\".into());\n    }\n\n    let country = v[\"country\"].as_str();\n    if country.is_none() {\n        return Err(\"country not found\".into());\n    }\n\n    let refer = v[\"refer\"].as_str();\n    let refer = if refer.is_none() {\n        String::new()\n    } else {\n        refer.unwrap().to_string()\n    };\n\n    Ok(WhoIsInfo {\n        refer: refer,\n        domain: domain.unwrap().to_string(),\n        registrar: registrar.unwrap().to_string(),\n        organization: organization.unwrap().to_string(),\n        address: address.unwrap().to_string(),\n        city: city.unwrap().to_string(),\n        country: country.unwrap().to_string(),\n    })\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/http_error.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse std::string::FromUtf8Error;\n\nuse crate::http_api_msg::*;\nuse bytes::Bytes;\nuse http_body_util::Full;\nuse hyper::{Response, StatusCode};\n\n#[derive(Debug)]\npub struct HttpError {\n    pub code: StatusCode,\n    pub msg: String,\n}\n\nimpl HttpError {\n    pub fn new(code: StatusCode, msg: String) -> Self {\n        HttpError {\n            code: code,\n            msg: msg.to_string(),\n        }\n    }\n\n    pub fn to_response(&self) -> Response<Full<Bytes>> {\n        let bytes = Bytes::from(api_msg_error(&self.msg));\n        let mut response = Response::new(Full::new(bytes));\n        response\n            .headers_mut()\n            .insert(\"Content-Type\", \"application/json\".parse().unwrap());\n        *response.status_mut() = self.code;\n        response\n    }\n}\n\nimpl From<hyper::Error> for HttpError {\n    fn from(err: hyper::Error) -> HttpError {\n        HttpError {\n            code: StatusCode::INTERNAL_SERVER_ERROR,\n            msg: format!(\"Hyper error: {}\", err),\n        }\n    }\n}\n\nimpl From<FromUtf8Error> for HttpError {\n    fn from(err: FromUtf8Error) -> HttpError {\n        HttpError {\n            code: StatusCode::BAD_REQUEST,\n            msg: format!(\"FromUtf8Error: {}\", err),\n        }\n    }\n}\n\nimpl From<std::io::Error> for HttpError {\n    fn from(err: std::io::Error) -> HttpError {\n        HttpError {\n            code: StatusCode::INTERNAL_SERVER_ERROR,\n            msg: format!(\"IO error: {}\", err),\n        }\n    }\n}\n\nimpl From<Box<dyn std::error::Error>> for HttpError {\n    fn from(err: Box<dyn std::error::Error>) -> HttpError {\n        HttpError {\n            code: StatusCode::INTERNAL_SERVER_ERROR,\n            msg: format!(\"Error: {}\", err),\n        }\n    }\n}\n\nimpl From<Box<dyn std::error::Error + Send>> for HttpError {\n    fn from(err: Box<dyn std::error::Error + Send>) -> HttpError {\n        HttpError {\n            code: StatusCode::INTERNAL_SERVER_ERROR,\n            msg: format!(\"Error: {}\", err),\n        }\n    }\n}"
  },
  {
    "path": "plugin/smartdns-ui/src/http_jwt.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct JwtClaims {\n    pub user: String,\n    pub ip: String,\n    pub exp: u64,\n}\n\npub struct Jwt {\n    user: String,\n    secret: String,\n    ip: String,\n    expired_in: u32,\n}\n\npub struct TokenInfo {\n    pub token: String,\n    pub expire: String,\n}\n\nimpl Jwt {\n    pub fn new(user: &str, secret: &str, ip: &str, expired_in: u32) -> Self {\n        Jwt {\n            user: user.to_string(),\n            secret: secret.to_string(),\n            ip: ip.to_string(),\n            expired_in: expired_in,\n        }\n    }\n\n    pub fn refresh_token(&self, token: &str) -> Result<TokenInfo, String> {\n        if !self.is_token_valid(token) {\n            return Err(\"token is invalid\".to_string());\n        }\n        \n        Ok(self.encode_token())\n    }\n\n    pub fn encode_token(&self) -> TokenInfo {\n        let calims = JwtClaims {\n            user: self.user.clone(),\n            ip: self.ip.clone(),\n            exp: jsonwebtoken::get_current_timestamp() + self.expired_in as u64,\n        };\n        let token = encode(\n            &Header::default(),\n            &calims,\n            &EncodingKey::from_secret(self.secret.as_ref()),\n        );\n\n        let exp = self.expired_in.to_string();\n        TokenInfo {\n            token: token.unwrap(),\n            expire: exp,\n        }\n    }\n\n    pub fn is_token_valid(&self, token: &str) -> bool {\n        let calim = self.decode_token(token);\n        if self.decode_token(token).is_err() {\n            return false;\n        }\n\n        let calim = calim.unwrap();\n\n        if calim.user != self.user || calim.ip != self.ip {\n            return false;\n        }\n\n        true\n    }\n\n    pub fn decode_token(&self, token: &str) -> Result<JwtClaims, String> {\n        let calims = decode::<JwtClaims>(\n            &token,\n            &DecodingKey::from_secret(self.secret.as_ref()),\n            &Validation::default(),\n        );\n        match calims {\n            Ok(c) => Ok(c.claims),\n            Err(e) => Err(e.to_string()),\n        }\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/http_server.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nextern crate cfg_if;\n\nuse crate::data_server::*;\nuse crate::dns_log;\nuse crate::http_api_msg::*;\nuse crate::http_jwt::*;\nuse crate::http_server_api::*;\nuse crate::plugin::SmartdnsPlugin;\nuse crate::smartdns::*;\nuse crate::utils;\n\nuse bytes::Bytes;\nuse http_body_util::Full;\nuse hyper::body;\nuse hyper::header::HeaderValue;\nuse hyper::StatusCode;\nuse hyper::{service::service_fn, Request, Response};\nuse hyper_util::rt::TokioIo;\nuse hyper_util::server::conn::auto;\nuse std::convert::Infallible;\nuse std::error::Error;\nuse std::fs::Metadata;\nuse std::net::SocketAddr;\nuse std::path::PathBuf;\nuse std::path::{Component, Path};\nuse std::sync::MutexGuard;\nuse std::sync::Weak;\nuse std::sync::{Arc, Mutex};\nuse std::time::Duration;\nuse std::time::Instant;\nuse tokio::fs::read;\nuse tokio::net::TcpListener;\nuse tokio::net::TcpStream;\nuse tokio::sync::mpsc;\nuse tokio::task::JoinHandle;\ncfg_if::cfg_if! {\n    if #[cfg(feature = \"https\")] {\n        use rustls_pemfile;\n        use std::io::BufReader;\n        use tokio_rustls::{rustls, TlsAcceptor};\n    }\n}\n\nconst HTTP_SERVER_DEFAULT_PASSWORD: &str = \"password\";\nconst HTTP_SERVER_DEFAULT_USERNAME: &str = \"admin\";\nconst HTTP_SERVER_DEFAULT_WWW_ROOT: &str = \"/usr/share/smartdns/wwwroot\";\nconst HTTP_SERVER_DEFAULT_IPV6: &str = \"http://[::]:6080\";\nconst HTTP_SERVER_DEFAULT_IP: &str = \"http://0.0.0.0:6080\";\n\n#[derive(Clone)]\npub struct HttpServerConfig {\n    pub http_ip: String,\n    pub http_root: String,\n    pub username: String,\n    pub password: String,\n    pub token_expired_time: u32,\n    pub enable_cors: bool,\n    pub enable_terminal: bool,\n}\n\nimpl HttpServerConfig {\n    pub fn new() -> Self {\n        let host_ip = if utils::is_ipv6_supported() {\n            HTTP_SERVER_DEFAULT_IPV6.to_string()\n        } else {\n            HTTP_SERVER_DEFAULT_IP.to_string()\n        };\n\n        HttpServerConfig {\n            http_ip: host_ip,\n            http_root: HTTP_SERVER_DEFAULT_WWW_ROOT.to_string(),\n            username: HTTP_SERVER_DEFAULT_USERNAME.to_string(),\n            password: utils::hash_password(HTTP_SERVER_DEFAULT_PASSWORD, Some(1000)).unwrap(),\n            token_expired_time: 600,\n            enable_cors: false,\n            enable_terminal: false,\n        }\n    }\n\n    pub fn settings_map(&self) -> std::collections::HashMap<String, String> {\n        let mut map = std::collections::HashMap::new();\n        map.insert(\"http_ip\".to_string(), self.http_ip.clone());\n        map.insert(\"username\".to_string(), self.username.clone());\n        map.insert(\n            \"token_expired_time\".to_string(),\n            self.token_expired_time.to_string(),\n        );\n        map.insert(\"enable_cors\".to_string(), self.enable_cors.to_string());\n        map.insert(\n            \"enable_terminal\".to_string(),\n            self.enable_terminal.to_string(),\n        );\n        map\n    }\n\n    pub fn load_config(&mut self, data_server: Arc<DataServer>) -> Result<(), Box<dyn Error>> {\n        if let Some(password) = data_server.get_config(\"smartdns-ui.password\") {\n            self.password = password;\n        } else {\n            if let Some(password_from_file) =\n                data_server.get_server_config_from_file(\"smartdns-ui.password\")\n            {\n                self.password =\n                    utils::hash_password(password_from_file.as_str(), Some(10000)).unwrap();\n            }\n        }\n\n        if let Some(username) = data_server.get_server_config(\"smartdns-ui.user\") {\n            self.username = username;\n        }\n\n        if let Some(enable_cors) = data_server.get_server_config(\"smartdns-ui.enable-cors\") {\n            if enable_cors.eq_ignore_ascii_case(\"yes\") || enable_cors.eq_ignore_ascii_case(\"true\") {\n                self.enable_cors = true;\n            } else {\n                self.enable_cors = false;\n            }\n        }\n\n        if let Some(enable_terminal) = data_server.get_server_config(\"smartdns-ui.enable-terminal\")\n        {\n            if enable_terminal.eq_ignore_ascii_case(\"yes\")\n                || enable_terminal.eq_ignore_ascii_case(\"true\")\n            {\n                self.enable_terminal = true;\n            } else {\n                self.enable_terminal = false;\n            }\n        }\n\n        Ok(())\n    }\n}\n\npub struct HttpServerControl {\n    http_server: Arc<HttpServer>,\n    server_thread: Mutex<Option<JoinHandle<()>>>,\n    plugin: Mutex<Weak<SmartdnsPlugin>>,\n}\n\n#[allow(dead_code)]\nimpl HttpServerControl {\n    pub fn new() -> Self {\n        HttpServerControl {\n            http_server: Arc::new(HttpServer::new()),\n            server_thread: Mutex::new(None),\n            plugin: Mutex::new(Weak::new()),\n        }\n    }\n\n    pub fn set_plugin(&self, plugin: Arc<SmartdnsPlugin>) {\n        *self.plugin.lock().unwrap() = Arc::downgrade(&plugin);\n    }\n\n    pub fn get_plugin(&self) -> Result<Arc<SmartdnsPlugin>, Box<dyn Error>> {\n        let plugin = match self.plugin.lock() {\n            Ok(plugin) => plugin,\n            Err(_) => return Err(\"Failed to lock plugin mutex\".into()),\n        };\n\n        if let Some(plugin) = plugin.upgrade() {\n            return Ok(plugin);\n        }\n        Err(\"Plugin is not set\".into())\n    }\n\n    pub fn get_http_server(&self) -> Arc<HttpServer> {\n        Arc::clone(&self.http_server)\n    }\n\n    pub fn start_http_server(&self, conf: &HttpServerConfig) -> Result<(), Box<dyn Error>> {\n        let inner_clone = Arc::clone(&self.http_server);\n        let ret = inner_clone.set_conf(conf);\n        if let Err(e) = ret {\n            return Err(e);\n        }\n\n        let plugin = self.get_plugin()?;\n        inner_clone.set_plugin(plugin.clone());\n\n        let (tx, rx) = tokio::sync::oneshot::channel::<i32>();\n        let rt = plugin.get_runtime();\n\n        let server_thread = rt.spawn(async move {\n            let ret = HttpServer::http_server_loop(inner_clone, tx).await;\n            if let Err(e) = ret {\n                dns_log!(LogLevel::ERROR, \"http server error: {}\", e);\n                Plugin::smartdns_exit(1);\n            }\n            dns_log!(LogLevel::DEBUG, \"http server exit.\");\n        });\n\n        tokio::task::block_in_place(|| {\n            let _ = rt.block_on(rx);\n        });\n\n        *self.server_thread.lock().unwrap() = Some(server_thread);\n\n        Ok(())\n    }\n\n    pub fn stop_http_server(&self) {\n        let mut server_thread = self.server_thread.lock().unwrap();\n        if server_thread.is_none() {\n            return;\n        }\n\n        self.http_server.stop_http_server();\n\n        if let Some(server_thread) = server_thread.take() {\n            let plugin = self.get_plugin();\n            if plugin.is_err() {\n                dns_log!(\n                    LogLevel::ERROR,\n                    \"get plugin error: {}\",\n                    plugin.err().unwrap()\n                );\n                return;\n            }\n            let plugin = plugin.unwrap();\n            let rt = plugin.get_runtime();\n            tokio::task::block_in_place(|| {\n                if let Err(e) = rt.block_on(server_thread) {\n                    dns_log!(LogLevel::ERROR, \"http server stop error: {}\", e);\n                }\n            });\n        }\n    }\n}\n\nimpl Drop for HttpServerControl {\n    fn drop(&mut self) {\n        self.stop_http_server();\n    }\n}\n\n#[derive(Clone)]\npub struct TokioExecutor;\n\nimpl<F> hyper::rt::Executor<F> for TokioExecutor\nwhere\n    F: std::future::Future + Send + 'static,\n    F::Output: Send + 'static,\n{\n    fn execute(&self, fut: F) {\n        tokio::task::spawn(fut);\n    }\n}\n\npub struct HttpServer {\n    conf: Mutex<HttpServerConfig>,\n    notify_tx: Option<mpsc::Sender<()>>,\n    notify_rx: Mutex<Option<mpsc::Receiver<()>>>,\n    api: API,\n    local_addr: Mutex<Option<SocketAddr>>,\n    mime_map: std::collections::HashMap<&'static str, &'static str>,\n    login_attempts: Mutex<(i32, Instant)>,\n    plugin: Mutex<Weak<SmartdnsPlugin>>,\n}\n\n#[allow(dead_code)]\nimpl HttpServer {\n    fn new() -> Self {\n        let mut plugin = HttpServer {\n            conf: Mutex::new(HttpServerConfig::new()),\n            notify_tx: None,\n            notify_rx: Mutex::new(None),\n            api: API::new(),\n            local_addr: Mutex::new(None),\n            login_attempts: Mutex::new((0, Instant::now())),\n            plugin: Mutex::new(Weak::new()),\n            mime_map: std::collections::HashMap::from([\n                /* text */\n                (\"htm\", \"text/html\"),\n                (\"html\", \"text/html\"),\n                (\"js\", \"text/javascript\"),\n                (\"css\", \"text/css\"),\n                (\"txt\", \"text/plain\"),\n                (\"conf\", \"text/plain\"),\n                (\"xml\", \"text/xml\"),\n                (\"csv\", \"text/csv\"),\n                (\"md\", \"text/markdown\"),\n                /* image */\n                (\"png\", \"image/png\"),\n                (\"gif\", \"image/gif\"),\n                (\"jpeg\", \"image/jpeg\"),\n                (\"svg\", \"image/svg+xml\"),\n                (\"ico\", \"image/x-icon\"),\n                (\"bmp\", \"image/bmp\"),\n                (\"avif\", \"image/avif\"),\n                /* video */\n                (\"mpeg\", \"video/mpeg\"),\n                (\"mp4\", \"video/mp4\"),\n                (\"webm\", \"video/webm\"),\n                /* audio */\n                (\"mp3\", \"audio/mpeg\"),\n                (\"ogg\", \"audio/ogg\"),\n                (\"wav\", \"audio/wav\"),\n                /* font */\n                (\"woff\", \"font/woff\"),\n                (\"woff2\", \"font/woff2\"),\n                (\"ttf\", \"font/ttf\"),\n                (\"otf\", \"font/otf\"),\n                /* application */\n                (\"wasm\", \"application/wasm\"),\n                (\"pdf\", \"application/pdf\"),\n                (\"json\", \"application/json\"),\n                (\"tar\", \"application/x-tar\"),\n                (\"zip\", \"application/zip\"),\n            ]),\n        };\n\n        let (tx, rx) = mpsc::channel(100);\n        plugin.notify_tx = Some(tx);\n        plugin.notify_rx = Mutex::new(Some(rx));\n\n        plugin\n    }\n\n    pub fn get_conf(&self) -> HttpServerConfig {\n        let conf = self.conf.lock().unwrap();\n        conf.clone()\n    }\n\n    pub fn get_conf_mut(&'_ self) -> MutexGuard<'_, HttpServerConfig> {\n        self.conf.lock().unwrap()\n    }\n\n    pub fn login_attempts_reset(&self) {\n        let mut attempts = self.login_attempts.lock().unwrap();\n        attempts.0 = 0;\n        attempts.1 = Instant::now();\n    }\n\n    pub fn login_attempts_check(&self) -> bool {\n        let mut attempts = self.login_attempts.lock().unwrap();\n\n        if attempts.0 == 0 {\n            attempts.1 = Instant::now();\n        }\n\n        attempts.0 += 1;\n\n        if attempts.0 > 5 {\n            let now = Instant::now();\n            let duration = now.duration_since(attempts.1);\n            if duration.as_secs() < 60 {\n                if duration.as_secs() < 30 {\n                    attempts.1 = Instant::now();\n                }\n                return false;\n            }\n\n            attempts.0 = 0;\n            attempts.1 = now;\n        }\n\n        true\n    }\n\n    pub fn get_local_addr(&self) -> Option<SocketAddr> {\n        let local_addr = self.local_addr.lock().unwrap();\n        local_addr.clone()\n    }\n\n    fn set_conf(&self, conf: &HttpServerConfig) -> Result<(), Box<dyn Error>> {\n        let mut conf_clone = self.conf.lock().unwrap();\n        *conf_clone = conf.clone();\n        dns_log!(LogLevel::INFO, \"http server URI: {}\", conf_clone.http_ip);\n        dns_log!(\n            LogLevel::INFO,\n            \"http server www root: {}\",\n            conf_clone.http_root\n        );\n        Ok(())\n    }\n\n    fn set_plugin(&self, plugin: Arc<SmartdnsPlugin>) {\n        let mut _plugin = self.plugin.lock().unwrap();\n        *_plugin = Arc::downgrade(&plugin);\n    }\n\n    fn get_plugin(&self) -> Result<Arc<SmartdnsPlugin>, Box<dyn Error>> {\n        let plugin = match self.plugin.lock() {\n            Ok(plugin) => plugin,\n            Err(_) => return Err(\"Failed to lock plugin mutex\".into()),\n        };\n\n        if let Some(plugin) = plugin.upgrade() {\n            return Ok(plugin);\n        }\n        Err(\"Plugin is not set\".into())\n    }\n\n    pub fn is_https_server(&self) -> bool {\n        let http_ip = self.get_conf().http_ip;\n        if http_ip.parse::<url::Url>().is_err() {\n            return false;\n        }\n\n        let binding = http_ip.parse::<url::Url>().unwrap();\n        let scheme = binding.scheme();\n        if scheme == \"https\" {\n            return true;\n        }\n\n        false\n    }\n\n    pub fn get_data_server(&self) -> Arc<DataServer> {\n        self.get_plugin().unwrap().get_data_server()\n    }\n\n    pub fn get_token_from_header(\n        req: &Request<body::Incoming>,\n    ) -> Result<Option<String>, Box<dyn Error>> {\n        let token: String;\n        let header_auth = req.headers().get(\"Authorization\");\n        if header_auth.is_none() {\n            let cookie = req.headers().get(\"Cookie\");\n            if cookie.is_none() {\n                return Ok(None);\n            }\n\n            let cookie = cookie.unwrap().to_str();\n            if let Err(_) = cookie {\n                return Ok(None);\n            }\n\n            let cookies = cookie.unwrap().split(';').collect::<Vec<&str>>();\n            let token_cookie = cookies.iter().find(|c| c.trim().starts_with(\"token=\"));\n            if token_cookie.is_none() {\n                return Ok(None);\n            }\n\n            let token_cookie = token_cookie.unwrap().trim().strip_prefix(\"token=\");\n            if token_cookie.is_none() {\n                return Ok(None);\n            }\n\n            let data = urlencoding::decode(token_cookie.unwrap());\n            if let Err(_) = data {\n                return Ok(None);\n            }\n\n            let data = data.unwrap();\n            token = data.to_string();\n        } else {\n            let auth = header_auth.unwrap().to_str();\n            if let Err(_) = auth {\n                return Ok(None);\n            }\n\n            token = auth.unwrap().to_string();\n        }\n\n        let token_type = \"Bearer\";\n        if !token.starts_with(token_type) {\n            return Err(\"Invalid authorization type\".into());\n        }\n\n        let token = token.strip_prefix(token_type).unwrap().trim();\n\n        Ok(Some(token.to_string()))\n    }\n\n    pub fn auth_token_is_valid(\n        &self,\n        req: &Request<body::Incoming>,\n    ) -> Result<bool, Box<dyn Error>> {\n        let token = HttpServer::get_token_from_header(req)?;\n\n        if token.is_none() {\n            return Ok(false);\n        }\n\n        let token = token.unwrap();\n        let conf = self.conf.lock().unwrap();\n        let jwt = Jwt::new(&conf.username, &conf.password, \"\", conf.token_expired_time);\n        if !jwt.is_token_valid(token.as_str()) {\n            return Ok(false);\n        }\n        Ok(true)\n    }\n\n    fn server_add_cors_header(\n        &self,\n        origin: &Option<hyper::header::HeaderValue>,\n        response: &mut Response<Full<Bytes>>,\n    ) {\n        if self.get_conf().enable_cors {\n            if let Some(origin) = origin {\n                response\n                    .headers_mut()\n                    .insert(\"Access-Control-Allow-Origin\", origin.clone());\n            } else {\n                response\n                    .headers_mut()\n                    .insert(\"Access-Control-Allow-Origin\", \"*\".parse().unwrap());\n            }\n\n            response.headers_mut().insert(\n                \"Access-Control-Allow-Methods\",\n                \"GET, POST, PUT, DELETE, OPTIONS, PATCH\".parse().unwrap(),\n            );\n\n            response.headers_mut().insert(\n                \"Access-Control-Allow-Headers\",\n                \"Content-Type, Authorization, Set-Cookie\".parse().unwrap(),\n            );\n\n            response\n                .headers_mut()\n                .insert(\"Access-Control-Allow-Credentials\", \"true\".parse().unwrap());\n\n            response\n                .headers_mut()\n                .insert(\"Access-Control-Max-Age\", \"600\".parse().unwrap());\n        }\n    }\n\n    async fn server_handle_http_api_request(\n        this: Arc<HttpServer>,\n        req: Request<body::Incoming>,\n        _path: PathBuf,\n    ) -> Result<Response<Full<Bytes>>, Box<dyn std::error::Error>> {\n        let mut origin: Option<HeaderValue> = None;\n\n        if let Some(o) = req.headers().get(\"Origin\") {\n            origin = Some(o.clone());\n        }\n\n        let error_response = |code: StatusCode, msg: &str| {\n            let bytes = Bytes::from(api_msg_error(msg));\n            let mut response = Response::new(Full::new(bytes));\n            response\n                .headers_mut()\n                .insert(\"Content-Type\", \"application/json\".parse().unwrap());\n            response\n                .headers_mut()\n                .insert(\"Cache-Control\", \"no-cache\".parse().unwrap());\n            *response.status_mut() = code;\n\n            this.server_add_cors_header(&origin, &mut response);\n            Ok(response)\n        };\n\n        dns_log!(LogLevel::DEBUG, \"api request: {:?}\", req.uri());\n\n        if req.method() == hyper::Method::OPTIONS {\n            let mut response = Response::new(Full::new(Bytes::from(\"\")));\n            response\n                .headers_mut()\n                .insert(\"Content-Type\", \"application/json\".parse().unwrap());\n            response\n                .headers_mut()\n                .insert(\"Cache-Control\", \"no-cache\".parse().unwrap());\n            this.server_add_cors_header(&origin, &mut response);\n            return Ok(response);\n        }\n\n        match this.api.get_router(req.method(), req.uri().path()) {\n            Some((router, param)) => {\n                if router.auth {\n                    let is_token_valid = this.auth_token_is_valid(&req);\n                    if let Err(e) = is_token_valid {\n                        return error_response(StatusCode::BAD_REQUEST, e.to_string().as_str());\n                    }\n\n                    if !is_token_valid.unwrap() {\n                        return error_response(StatusCode::UNAUTHORIZED, \"Please login.\");\n                    }\n                }\n\n                if router.method != req.method() {\n                    return error_response(StatusCode::METHOD_NOT_ALLOWED, \"Method Not Allowed\");\n                }\n\n                let resp = (router.handler)(this.clone(), param, req).await;\n                match resp {\n                    Ok(resp) => {\n                        let mut resp = resp;\n                        if resp.headers().get(\"Content-Type\").is_none() {\n                            resp.headers_mut()\n                                .insert(\"Content-Type\", \"application/json\".parse().unwrap());\n                        }\n\n                        if resp.headers().get(\"Cache-Control\").is_none() {\n                            resp.headers_mut()\n                                .insert(\"Cache-Control\", \"no-cache\".parse().unwrap());\n                        }\n\n                        this.server_add_cors_header(&origin, &mut resp);\n\n                        Ok(resp)\n                    }\n                    Err(e) => Ok(e.to_response()),\n                }\n            }\n            None => error_response(StatusCode::NOT_FOUND, \"API not found.\"),\n        }\n    }\n\n    pub fn get_mime_type(&self, file: &str) -> String {\n        let ext = file.split('.').last().unwrap();\n        if let Some(mime) = self.mime_map.get(ext) {\n            return mime.to_string();\n        }\n\n        \"application/octet-stream\".to_string()\n    }\n\n    async fn server_handle_http_request(\n        this: Arc<HttpServer>,\n        req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, Infallible> {\n        let path = PathBuf::from(req.uri().path());\n        let mut is_404 = false;\n        let www_root = {\n            let conf = this.conf.lock().unwrap();\n            PathBuf::from(conf.http_root.clone())\n        };\n\n        let mut path = normalize_path(path.as_path());\n        if path.starts_with(\"/\") {\n            path = path.strip_prefix(\"/\").unwrap().to_path_buf();\n        }\n\n        if path.starts_with(\"api/\") {\n            let ret = HttpServer::server_handle_http_api_request(this, req, path.clone()).await;\n            if let Err(e) = ret {\n                dns_log!(LogLevel::ERROR, \"api request error: {:?}\", e);\n                let mut response = Response::new(Full::new(Bytes::from(\"Internal Server Error\")));\n                *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;\n                return Ok(response);\n            }\n\n            let ret = ret.unwrap();\n            return Ok(ret);\n        }\n\n        dns_log!(LogLevel::DEBUG, \"page request: {:?}\", req.uri());\n        let mut filepath = www_root.join(path);\n        let uri_path = req.uri().path().to_string();\n        let mut path = uri_path.clone();\n\n        if !filepath.exists() || filepath.is_dir() {\n            let suffix = filepath.extension();\n            if suffix.is_none() && !uri_path.ends_with(\"/\") {\n                let check_filepath = filepath.with_extension(\"html\");\n                if check_filepath.exists() {\n                    filepath = check_filepath;\n                    path = format!(\"{}.html\", uri_path);\n                }\n            }\n\n            if filepath.is_dir() {\n                filepath = filepath.join(\"index.html\");\n                path = format!(\"{}/index.html\", uri_path);\n            }\n\n            if !filepath.exists() {\n                filepath = www_root.join(\"404.html\");\n                path = \"/404.html\".to_string();\n                if !filepath.exists() {\n                    filepath = www_root.join(\"index.html\");\n                    path = format!(\"/index.html\");\n                } else {\n                    is_404 = true;\n                }\n            }\n        }\n\n        let mut file_meta: Option<Metadata> = None;\n        let fn_get_etag = |meta: &Metadata| -> String {\n            let modify_time = meta.modified();\n            if let Err(_) = modify_time {\n                return \"\".to_string();\n            }\n            format!(\n                \"{:x}-{:?}\",\n                meta.len(),\n                modify_time\n                    .unwrap()\n                    .duration_since(std::time::UNIX_EPOCH)\n                    .unwrap()\n                    .as_secs()\n            )\n        };\n\n        if filepath.exists() {\n            let meta = filepath.metadata();\n            if let Ok(meta) = meta {\n                file_meta = Some(meta);\n            }\n        }\n\n        let if_none_match = req.headers().get(\"If-None-Match\");\n        if if_none_match.is_some() && file_meta.is_some() {\n            let etag = fn_get_etag(&file_meta.as_ref().unwrap());\n            if etag == if_none_match.unwrap().to_str().unwrap() {\n                let mut response = Response::new(Full::new(Bytes::from(\"\")));\n                *response.status_mut() = StatusCode::NOT_MODIFIED;\n                return Ok(response);\n            }\n        }\n\n        match read(filepath).await {\n            Ok(contents) => {\n                let bytes = Bytes::from(contents);\n                let bytes_len = bytes.len();\n                let mut response = Response::new(Full::new(bytes));\n                let header = response.headers_mut();\n                header.insert(\"Content-Length\", bytes_len.to_string().parse().unwrap());\n                header.insert(\"Content-Type\", this.get_mime_type(&path).parse().unwrap());\n                header.insert(\"Connection\", \"keep-alive\".parse().unwrap());\n                header.insert(\"Keep-Alive\", \"timeout=60, max=1000\".parse().unwrap());\n\n                if file_meta.as_ref().is_some() {\n                    let etag = fn_get_etag(&file_meta.as_ref().unwrap());\n                    header.insert(\"ETag\", etag.parse().unwrap());\n                }\n\n                if is_404 {\n                    *response.status_mut() = StatusCode::NOT_FOUND;\n                } else {\n                    *response.status_mut() = StatusCode::OK;\n                }\n\n                Ok(response)\n            }\n            Err(_) => {\n                let bytes = Bytes::from(\"Page Not Found\");\n                let mut response = Response::new(Full::new(bytes));\n                *response.status_mut() = StatusCode::NOT_FOUND;\n                Ok(response)\n            }\n        }\n    }\n\n    async fn http_server_handle_conn(this: Arc<HttpServer>, stream: TcpStream) {\n        let io = TokioIo::new(stream);\n\n        let handle_func = move |req| HttpServer::server_handle_http_request(this.clone(), req);\n\n        tokio::task::spawn(async move {\n            let conn = auto::Builder::new(TokioExecutor)\n                .serve_connection_with_upgrades(io, service_fn(handle_func))\n                .await;\n            if let Err(err) = conn {\n                dns_log!(LogLevel::DEBUG, \"Error serving connection: {:?}\", err);\n                return;\n            }\n        });\n    }\n\n    #[cfg(feature = \"https\")]\n    async fn https_server_handle_conn(\n        this: Arc<HttpServer>,\n        stream: tokio_rustls::server::TlsStream<TcpStream>,\n    ) {\n        let io = TokioIo::new(stream);\n\n        let handle_func = move |req| HttpServer::server_handle_http_request(this.clone(), req);\n\n        tokio::task::spawn(async move {\n            let conn = auto::Builder::new(TokioExecutor)\n                .serve_connection_with_upgrades(io, service_fn(handle_func))\n                .await;\n            if let Err(err) = conn {\n                dns_log!(LogLevel::DEBUG, \"Error serving connection: {:?}\", err);\n                return;\n            }\n        });\n    }\n\n    #[cfg(feature = \"https\")]\n    async fn handle_tls_accept(this: Arc<HttpServer>, acceptor: TlsAcceptor, stream: TcpStream) {\n        tokio::task::spawn(async move {\n            let acceptor_future = acceptor.accept(stream);\n            let stream_ssl_tmout =\n                tokio::time::timeout(tokio::time::Duration::from_secs(60), acceptor_future).await;\n            if let Err(e) = stream_ssl_tmout {\n                dns_log!(LogLevel::DEBUG, \"tls accept timeout. {}\", e);\n                return;\n            }\n\n            let stream_ret = stream_ssl_tmout.unwrap();\n            if let Err(e) = stream_ret {\n                dns_log!(LogLevel::DEBUG, \"tls accept error: {}\", e);\n                return;\n            }\n\n            let stream_ssl = stream_ret.unwrap();\n            HttpServer::https_server_handle_conn(this, stream_ssl).await;\n        });\n    }\n\n    async fn http_server_loop(\n        this: Arc<HttpServer>,\n        kickoff_tx: tokio::sync::oneshot::Sender<i32>,\n    ) -> Result<(), Box<dyn Error>> {\n        let addr: String;\n        let mut rx: mpsc::Receiver<()>;\n\n        {\n            let conf = this.conf.lock().unwrap();\n            addr = format!(\"{}\", conf.http_ip);\n            let mut _rx = this.notify_rx.lock().unwrap();\n            rx = _rx.take().unwrap();\n        }\n\n        let url = addr.parse::<url::Url>()?;\n\n        cfg_if::cfg_if! {\n            if #[cfg(feature = \"https\")]\n            {\n                let mut acceptor = None;\n                if url.scheme() == \"https\" {\n                    #[cfg(feature = \"https\")]\n                    let cert_info = Plugin::smartdns_get_cert()?;\n\n                    dns_log!(\n                        LogLevel::DEBUG,\n                        \"cert: {}, key: {}\",\n                        cert_info.cert,\n                        cert_info.key\n                    );\n                    let cert_chain: Result<Vec<rustls::pki_types::CertificateDer<'_>>, _> =\n                        rustls_pemfile::certs(&mut BufReader::new(std::fs::File::open(\n                            cert_info.cert,\n                        )?))\n                        .collect();\n                    let cert_chain = cert_chain.unwrap_or_else(|_| Vec::new());\n                    let key_der = rustls_pemfile::private_key(&mut BufReader::new(\n                        std::fs::File::open(cert_info.key)?,\n                    ))?\n                    .unwrap();\n\n                    let mut config = rustls::ServerConfig::builder()\n                        .with_no_client_auth()\n                        .with_single_cert(cert_chain, key_der)?;\n\n                    config.alpn_protocols = vec![b\"h2\".to_vec(), b\"http/1.1\".to_vec()];\n                    acceptor = Some(TlsAcceptor::from(Arc::new(config)));\n                }\n            } else {\n                if url.scheme() == \"https\" {\n                    return Err(\"https is not supported.\".into());\n                }\n            }\n        }\n\n        let host = url.host_str().unwrap_or(\"127.0.0.1\");\n        let port = url.port().unwrap_or(80);\n        let sock_addr = format!(\"{}:{}\", host, port).parse::<SocketAddr>()?;\n\n        let listner = TcpListener::bind(sock_addr).await?;\n        let addr = listner.local_addr()?;\n\n        *this.local_addr.lock().unwrap() = Some(addr);\n        dns_log!(LogLevel::INFO, \"http server listen at {}\", addr);\n\n        let _ = kickoff_tx.send(0);\n        loop {\n            tokio::select! {\n                _ = rx.recv() => {\n                    break;\n                }\n                res = listner.accept() => {\n                    match res {\n                        Ok((stream, _)) => {\n                            let sock_ref = socket2::SockRef::from(&stream);\n\n                            let mut ka = socket2::TcpKeepalive::new();\n                            ka = ka.with_time(Duration::from_secs(60));\n                            ka = ka.with_interval(Duration::from_secs(30));\n                            sock_ref.set_tcp_keepalive(&ka)?;\n                            sock_ref.set_nonblocking(true)?;\n                            sock_ref.tcp_nodelay()?;\n\n                            if let Err(_) = sock_ref.set_recv_buffer_size(262144) {\n                                dns_log!(LogLevel::DEBUG, \"Failed to set recv buffer size\");\n                            }\n\n                            if let Err(_) = sock_ref.set_send_buffer_size(262144) {\n                                dns_log!(LogLevel::DEBUG, \"Failed to set send buffer size\");\n                            }\n                            cfg_if::cfg_if! {\n                                if #[cfg(feature = \"https\")]\n                                {\n                                    if acceptor.is_some() {\n                                        let acceptor = acceptor.clone().unwrap().clone();\n                                        let this_clone = this.clone();\n                                        HttpServer::handle_tls_accept(this_clone, acceptor, stream).await;\n                                    } else {\n                                        HttpServer::http_server_handle_conn(this.clone(), stream).await;\n                                    }\n                                } else  {\n                                    HttpServer::http_server_handle_conn(this.clone(), stream).await;\n                                }\n                            }\n                        }\n                        Err(e) => {\n                            dns_log!(LogLevel::ERROR, \"accept error: {}\", e);\n                        }\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn stop_http_server(&self) {\n        if let Some(tx) = self.notify_tx.as_ref().cloned() {\n            let plugin = match self.get_plugin() {\n                Ok(plugin) => plugin,\n                Err(e) => {\n                    dns_log!(LogLevel::ERROR, \"get plugin error: {}\", e);\n                    return;\n                }\n            };\n            let rt = plugin.get_runtime();\n            tokio::task::block_in_place(|| {\n                let _ = rt.block_on(async {\n                    let _ = tx.send(()).await;\n                });\n            });\n        }\n    }\n}\n\npub fn normalize_path(path: &Path) -> PathBuf {\n    let mut components = path.components().peekable();\n    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {\n        components.next();\n        PathBuf::from(c.as_os_str())\n    } else {\n        PathBuf::new()\n    };\n\n    for component in components {\n        match component {\n            Component::Prefix(..) => unreachable!(),\n            Component::RootDir => {\n                ret.push(component.as_os_str());\n            }\n            Component::CurDir => {}\n            Component::ParentDir => {\n                ret.pop();\n            }\n            Component::Normal(c) => {\n                ret.push(c);\n            }\n        }\n    }\n    ret\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/http_server_api.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse crate::db::*;\nuse crate::dns_log;\nuse crate::http_api_msg::*;\nuse crate::http_error::*;\nuse crate::http_jwt::*;\nuse crate::http_server::*;\nuse crate::http_server_stream;\nuse crate::smartdns;\nuse crate::smartdns::*;\nuse crate::utils;\nuse crate::Plugin;\n\nuse bytes::Bytes;\nuse http_body_util::BodyExt;\nuse http_body_util::Full;\nuse hyper::{body, Method, Request, Response, StatusCode};\nuse matchit::Router;\nuse std::collections::HashMap;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::sync::Arc;\nuse url::form_urlencoded;\n\nconst PASSWORD_CONFIG_KEY: &str = \"smartdns-ui.password\";\nconst REST_API_PATH: &str = \"/api\";\n\ntype APIRouteFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;\ntype APIRouterFun = fn(\n    this: Arc<HttpServer>,\n    param: APIRouteParam,\n    req: Request<body::Incoming>,\n) -> APIRouteFuture<'static, Result<Response<Full<Bytes>>, HttpError>>;\ntype APIRouteParam = HashMap<String, String>;\n\npub struct APIRouter {\n    pub method: Method,\n    pub auth: bool,\n    pub handler: APIRouterFun,\n}\n\npub struct API {\n    router: Router<std::collections::HashMap<Method, APIRouter>>,\n}\n\nmacro_rules! APIRoute {\n    ( $fn:path) => {\n        |r, p, h| Box::pin($fn(r, p, h))\n    };\n}\n\n#[allow(dead_code)]\nimpl API {\n    #[rustfmt::skip]\n    pub fn new() -> Self {\n        let mut api = API {\n            router: Router::new(),\n        };\n\n        api.register(Method::PUT, \"/api/service/restart\",  true, APIRoute!(API::api_service_restart));\n        api.register(Method::PUT, \"/api/cache/flush\",  true, APIRoute!(API::api_cache_flush));\n        api.register(Method::GET, \"/api/cache/count\",  true, APIRoute!(API::api_cache_count));\n        api.register(Method::POST, \"/api/auth/login\",  false, APIRoute!(API::api_auth_login));\n        api.register(Method::POST, \"/api/auth/logout\",  false, APIRoute!(API::api_auth_logout));\n        api.register(Method::GET, \"/api/auth/check\",  true, APIRoute!(API::api_auth_check));\n        api.register(Method::PUT, \"/api/auth/password\",  false, APIRoute!(API::api_auth_change_password));\n        api.register(Method::POST, \"/api/auth/refresh\",  true, APIRoute!(API::api_auth_refresh));\n        api.register(Method::GET, \"/api/domain\",  true, APIRoute!(API::api_domain_get_list));\n        api.register(Method::DELETE, \"/api/domain\",  true, APIRoute!(API::api_domain_delete_list));\n        api.register(Method::GET, \"/api/domain/count\",  true, APIRoute!(API::api_domain_get_list_count));\n        api.register(Method::GET, \"/api/domain/{id}\",  true, APIRoute!(API::api_domain_get_by_id));\n        api.register(Method::DELETE, \"/api/domain/{id}\",  true, APIRoute!(API::api_domain_delete_by_id));\n        api.register(Method::GET, \"/api/client\", true, APIRoute!(API::api_client_get_list));\n        api.register(Method::DELETE, \"/api/client/{id}\",  true, APIRoute!(API::api_client_delete_by_id));\n        api.register(Method::GET, \"/api/log/stream\", true, APIRoute!(API::api_log_stream));\n        api.register(Method::PUT, \"/api/log/level\", true, APIRoute!(API::api_log_set_level));\n        api.register(Method::GET, \"/api/log/level\", true, APIRoute!(API::api_log_get_level));\n        api.register(Method::GET, \"/api/log/audit/stream\", true, APIRoute!(API::api_audit_log_stream));\n        api.register(Method::GET, \"/api/server/version\", false, APIRoute!(API::api_server_version));\n        api.register(Method::GET, \"/api/upstream-server\", true, APIRoute!(API::api_upstream_server_get_list));\n        api.register(Method::GET, \"/api/config/settings\", true, APIRoute!(API::api_config_get_settings));\n        api.register(Method::PUT, \"/api/config/settings\", true, APIRoute!(API::api_config_set_settings));\n        api.register(Method::GET, \"/api/stats/top/client\", true, APIRoute!(API::api_stats_get_top_client));\n        api.register(Method::GET, \"/api/stats/top/domain\", true, APIRoute!(API::api_stats_get_top_domain));\n        api.register(Method::GET, \"/api/stats/metrics\", true, APIRoute!(API::api_stats_get_metrics));\n        api.register(Method::GET, \"/api/stats/overview\", true, APIRoute!(API::api_stats_get_overview));\n        api.register(Method::GET, \"/api/stats/hourly-query-count\", true, APIRoute!(API::api_stats_get_hourly_query_count));\n        api.register(Method::GET, \"/api/stats/daily-query-count\", true, APIRoute!(API::api_stats_get_daily_query_count));\n        api.register(Method::PUT, \"/api/stats/refresh\", true, APIRoute!(API::api_stats_refresh));\n        api.register(Method::GET, \"/api/whois\", true, APIRoute!(API::api_whois));\n        api.register(Method::GET, \"/api/tool/term\", true, APIRoute!(API::api_tool_term));\n        api\n    }\n\n    pub fn register(&mut self, method: Method, path: &str, auth: bool, handler: APIRouterFun) {\n        let route_data = APIRouter {\n            method: method.clone(),\n            auth: auth,\n            handler: handler,\n        };\n\n        let mut m = self.router.at_mut(path);\n        if m.is_err() {\n            let map_new = std::collections::HashMap::new();\n            _ = self.router.insert(path, map_new);\n            m = self.router.at_mut(path);\n            if m.is_err() {\n                return;\n            }\n        }\n\n        let m = m.unwrap();\n        let mutmethod_map = m.value;\n        mutmethod_map.insert(method, route_data);\n    }\n\n    pub fn get_router(&self, method: &Method, path: &str) -> Option<(&APIRouter, APIRouteParam)> {\n        let m = self.router.at(path);\n        if m.is_err() {\n            return None;\n        }\n\n        let m = m.unwrap();\n        let method_map = m.value;\n        let route_data = method_map.get(method);\n        if route_data.is_none() {\n            return None;\n        }\n\n        let route_data = route_data.unwrap();\n        let mut param = APIRouteParam::new();\n\n        m.params.iter().for_each(|(k, v)| {\n            let v = v.to_string();\n            param.insert(k.to_string(), v);\n        });\n        Some((route_data, param))\n    }\n\n    fn get_params(req: &Request<body::Incoming>) -> HashMap<String, String> {\n        let b = req.uri().query().unwrap_or(\"\").to_string();\n        form_urlencoded::parse(b.as_ref())\n            .into_owned()\n            .collect::<HashMap<String, String>>()\n    }\n\n    fn params_parser_value<T: std::str::FromStr>(v: Option<&String>) -> Option<T> {\n        if v.is_none() {\n            return None;\n        }\n        let v = v.unwrap();\n\n        match T::from_str(&v) {\n            Ok(value) => Some(value),\n            Err(_) => None,\n        }\n    }\n\n    fn params_get_value<T: std::str::FromStr>(\n        params: &HashMap<String, String>,\n        key: &str,\n    ) -> Option<T> {\n        let v = params.get(key);\n        if v.is_none() {\n            return None;\n        }\n\n        let v = v.unwrap();\n        API::params_parser_value(Some(v))\n    }\n\n    fn params_get_value_default<T: std::str::FromStr>(\n        params: &HashMap<String, String>,\n        key: &str,\n        default: T,\n    ) -> Result<T, HttpError> {\n        let v = params.get(key);\n        if v.is_none() {\n            return Ok(default);\n        }\n        let v = v.unwrap();\n        match v.parse::<T>() {\n            Ok(v) => return Ok(v),\n            Err(_) => {\n                return Err(HttpError::new(\n                    StatusCode::BAD_REQUEST,\n                    format!(\"Invalid parameter: {}\", key),\n                ));\n            }\n        }\n    }\n\n    pub fn response_error(code: StatusCode, msg: &str) -> Result<Response<Full<Bytes>>, HttpError> {\n        let bytes = Bytes::from(api_msg_error(msg));\n        let mut response = Response::new(Full::new(bytes));\n        response\n            .headers_mut()\n            .insert(\"Content-Type\", \"application/json\".parse().unwrap());\n        *response.status_mut() = code;\n        Ok(response)\n    }\n\n    pub fn response_build(\n        code: StatusCode,\n        body: String,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let mut response = Response::new(Full::new(Bytes::from(body)));\n        response\n            .headers_mut()\n            .insert(\"Content-Type\", \"application/json\".parse().unwrap());\n        *response.status_mut() = code;\n        Ok(response)\n    }\n\n    async fn api_auth_refresh(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let is_https = this.is_https_server();\n        let token = HttpServer::get_token_from_header(&req)?;\n        let unauth_response =\n            || API::response_error(StatusCode::UNAUTHORIZED, \"Incorrect username or password.\");\n\n        if token.is_none() {\n            return unauth_response();\n        }\n\n        let token = token.unwrap();\n        let conf = this.get_conf();\n        let jtw = Jwt::new(\n            &conf.username.as_str(),\n            conf.password.as_str(),\n            \"\",\n            conf.token_expired_time,\n        );\n\n        let calim = jtw.decode_token(token.as_str());\n        if calim.is_err() {\n            return unauth_response();\n        }\n\n        let token_new = jtw.refresh_token(token.as_str());\n        if token_new.is_err() {\n            return unauth_response();\n        }\n\n        let token_new = token_new.unwrap();\n        let mut resp = API::response_build(\n            StatusCode::OK,\n            api_msg_auth_token(&token_new.token, &token_new.expire),\n        );\n\n        let cookie_token = format!(\"Bearer {}\", token_new.token);\n        let token_urlencode = urlencoding::encode(cookie_token.as_str());\n        let mut cookie = format!(\n            \"token={}; HttpOnly; Max-Age={}; Path={}\",\n            token_urlencode, token_new.expire, REST_API_PATH\n        );\n\n        if is_https && conf.enable_cors {\n            cookie.push_str(\"; SameSite=None; Secure\");\n        }\n\n        resp.as_mut()\n            .unwrap()\n            .headers_mut()\n            .insert(hyper::header::SET_COOKIE, cookie.parse().unwrap());\n\n        resp\n    }\n\n    /// Login\n    /// API: POST /api/auth/login\n    ///     body:\n    /// {\n    ///   \"username\": \"admin\"\n    ///   \"password\": \"password\"\n    /// }\n    async fn api_auth_login(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let is_https = this.is_https_server();\n        let whole_body = String::from_utf8(req.into_body().collect().await?.to_bytes().into())?;\n        let userinfo = api_msg_parse_auth(whole_body.as_str());\n        if let Err(e) = userinfo {\n            return API::response_error(StatusCode::BAD_REQUEST, e.to_string().as_str());\n        }\n\n        let conf = this.get_conf();\n        let userinfo = userinfo.unwrap();\n\n        if !this.login_attempts_check() {\n            return API::response_error(\n                StatusCode::FORBIDDEN,\n                \"Too many login attempts, please try again later.\",\n            );\n        }\n\n        if userinfo.username != conf.username\n            || utils::verify_password(userinfo.password.as_str(), conf.password.as_str()) != true\n        {\n            return API::response_error(\n                StatusCode::UNAUTHORIZED,\n                \"Incorrect username or password.\",\n            );\n        }\n\n        this.login_attempts_reset();\n\n        let jtw = Jwt::new(\n            userinfo.username.as_str(),\n            conf.password.as_str(),\n            \"\",\n            conf.token_expired_time,\n        );\n        let token = jtw.encode_token();\n        let mut resp = API::response_build(\n            StatusCode::OK,\n            api_msg_auth_token(&token.token, &token.expire),\n        );\n\n        let cookie_token = format!(\"Bearer {}\", token.token);\n        let token_urlencode = urlencoding::encode(cookie_token.as_str());\n        let mut cookie = format!(\n            \"token={}; HttpOnly; Max-Age={}; Path={}\",\n            token_urlencode, token.expire, REST_API_PATH\n        );\n\n        if is_https && conf.enable_cors {\n            cookie.push_str(\"; SameSite=None; Secure\");\n        }\n\n        resp.as_mut()\n            .unwrap()\n            .headers_mut()\n            .insert(hyper::header::SET_COOKIE, cookie.parse().unwrap());\n\n        resp\n    }\n\n    async fn api_auth_logout(\n        _this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let mut response = Response::new(Full::new(Bytes::from(\"\")));\n\n        let cookie = format!(\"token=none; HttpOnly; Max-Age=1; Path={}\", REST_API_PATH);\n\n        response\n            .headers_mut()\n            .insert(hyper::header::SET_COOKIE, cookie.parse().unwrap());\n        *response.status_mut() = StatusCode::NO_CONTENT;\n        Ok(response)\n    }\n\n    async fn api_auth_check(\n        _this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        API::response_build(StatusCode::OK, \"\".to_string())\n    }\n\n    async fn api_auth_change_password(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let unauth_response =\n            || API::response_error(StatusCode::UNAUTHORIZED, \"Incorrect username or password.\");\n        let token = HttpServer::get_token_from_header(&req)?;\n        let whole_body = String::from_utf8(req.into_body().collect().await?.to_bytes().into())?;\n        if token.is_none() {\n            return unauth_response();\n        }\n\n        let password_info = match api_msg_parse_auth_password_change(whole_body.as_str()) {\n            Ok(v) => v,\n            Err(e) => {\n                return API::response_error(StatusCode::BAD_REQUEST, e.to_string().as_str());\n            }\n        };\n\n        if !this.login_attempts_check() {\n            return API::response_error(\n                StatusCode::FORBIDDEN,\n                \"Too many login attempts, please try again later.\",\n            );\n        }\n\n        let mut conf = this.get_conf_mut();\n        if utils::verify_password(password_info.0.as_str(), conf.password.as_str()) != true {\n            return API::response_error(StatusCode::FORBIDDEN, \"Incorrect password.\");\n        }\n\n        let hashed_password = match utils::hash_password(password_info.1.as_str(), Some(10000)) {\n            Ok(v) => v,\n            Err(e) => {\n                return API::response_error(\n                    StatusCode::INTERNAL_SERVER_ERROR,\n                    e.to_string().as_str(),\n                );\n            }\n        };\n\n        let data_server = this.get_data_server();\n        conf.password = hashed_password.clone();\n        let ret = data_server.set_config(PASSWORD_CONFIG_KEY, hashed_password.as_str());\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        this.login_attempts_reset();\n        API::response_build(StatusCode::NO_CONTENT, \"\".to_string())\n    }\n\n    /// Restart the service <br>\n    /// API: PUT /api/service/restart\n    ///\n    async fn api_service_restart(\n        _this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let mut response = Response::new(Full::new(Bytes::from(\"\")));\n        response\n            .headers_mut()\n            .insert(\"Content-Type\", \"application/json\".parse().unwrap());\n        *response.status_mut() = StatusCode::NO_CONTENT;\n        Plugin::smartdns_restart();\n        Ok(response)\n    }\n\n    /// Get the number of cache <br>\n    /// API: GET /api/cache/count\n    ///\n    async fn api_cache_count(\n        _this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        API::response_build(\n            StatusCode::OK,\n            api_msg_gen_cache_number(Plugin::dns_cache_total_num()),\n        )\n    }\n\n    /// Flush the cache <br>\n    /// API: PUT /api/cache/flush\n    ///\n    async fn api_cache_flush(\n        _this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        Plugin::dns_cache_flush();\n        API::response_build(\n            StatusCode::OK,\n            api_msg_gen_cache_number(Plugin::dns_cache_total_num()),\n        )\n    }\n\n    /// Get the number of domain list <br>\n    /// API: GET /api/domain/count\n    ///\n    async fn api_domain_get_list_count(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        let count = data_server.get_domain_list_count();\n        let body = api_msg_gen_count(count as i64);\n\n        API::response_build(StatusCode::OK, body)\n    }\n\n    /// Get the domain by id <br>\n    /// API: GET /api/domain/{id}\n    async fn api_domain_get_by_id(\n        this: Arc<HttpServer>,\n        param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let id = API::params_parser_value(param.get(\"id\"));\n        if id.is_none() {\n            return API::response_error(StatusCode::BAD_REQUEST, \"Invalid parameter.\");\n        }\n\n        let id = id.unwrap();\n        let mut get_param = DomainListGetParam::new();\n        get_param.id = Some(id);\n\n        let data_server = this.get_data_server();\n        let domain_list = data_server.get_domain_list(&get_param)?;\n        if domain_list.domain_list.len() == 0 {\n            return API::response_error(StatusCode::NOT_FOUND, \"Not found\");\n        }\n        let body = api_msg_gen_domain(&domain_list.domain_list[0]);\n\n        API::response_build(StatusCode::OK, body)\n    }\n\n    /// Delete the client by id <br>\n    /// API: DELETE /api/client/{id}\n    ///  parameter: <br>\n    async fn api_client_delete_by_id(\n        this: Arc<HttpServer>,\n        param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let id = match API::params_parser_value(param.get(\"id\")) {\n            Some(v) => v,\n            None => return API::response_error(StatusCode::BAD_REQUEST, \"Invalid parameter.\"),\n        };\n\n        let data_server = this.get_data_server();\n        let ret = match data_server.delete_client_by_id(id) {\n            Ok(v) => v,\n            Err(e) => {\n                return API::response_error(\n                    StatusCode::INTERNAL_SERVER_ERROR,\n                    e.to_string().as_str(),\n                )\n            }\n        };\n\n        if ret == 0 {\n            return API::response_error(StatusCode::NOT_FOUND, \"Not found\");\n        }\n\n        API::response_build(StatusCode::NO_CONTENT, \"\".to_string())\n    }\n\n    /// Delete the domain by id <br>\n    /// API: DELETE /api/domain/{id}\n    ///\n    async fn api_domain_delete_by_id(\n        this: Arc<HttpServer>,\n        param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let id = match API::params_parser_value(param.get(\"id\")) {\n            Some(v) => v,\n            None => return API::response_error(StatusCode::BAD_REQUEST, \"Invalid parameter.\"),\n        };\n\n        let data_server = this.get_data_server();\n        let ret = match data_server.delete_domain_by_id(id) {\n            Ok(v) => v,\n            Err(e) => {\n                return API::response_error(\n                    StatusCode::INTERNAL_SERVER_ERROR,\n                    e.to_string().as_str(),\n                )\n            }\n        };\n\n        if ret == 0 {\n            return API::response_error(StatusCode::NOT_FOUND, \"Not found\");\n        }\n\n        API::response_build(StatusCode::NO_CONTENT, \"\".to_string())\n    }\n\n    /// Get the domain list <br>\n    /// API: GET /api/domain <br>\n    ///   parameter: <br>\n    ///     page_num: u32: Page number <br>\n    ///     page_size: u32: Page size <br>\n    ///     domain: String: Domain <br>\n    ///     domain_type: String: Domain type <br>\n    ///     domain_group: String: Domain group <br>\n    ///     client: String: Client <br>\n    ///     reply_code: String: Reply code <br>\n    ///\n    ///\n    async fn api_domain_get_list(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let params = API::get_params(&req);\n\n        let page_num = API::params_get_value_default(&params, \"page_num\", 1 as u64)?;\n        let page_size = API::params_get_value_default(&params, \"page_size\", 10 as u64)?;\n        if page_num == 0 || page_size == 0 {\n            return API::response_error(\n                StatusCode::BAD_REQUEST,\n                \"Invalid parameter: page_num or page_size\",\n            );\n        }\n\n        let id = API::params_get_value(&params, \"id\");\n        let domain = API::params_get_value(&params, \"domain\");\n        let domain_filter_mode = API::params_get_value(&params, \"domain_filter_mode\");\n        let domain_type = API::params_get_value(&params, \"domain_type\");\n        let domain_group = API::params_get_value(&params, \"domain_group\");\n        let client = API::params_get_value(&params, \"client\");\n        let reply_code = API::params_get_value(&params, \"reply_code\");\n        let order = API::params_get_value(&params, \"order\");\n        let is_blocked = API::params_get_value(&params, \"is_blocked\");\n        let timestamp_after = API::params_get_value(&params, \"timestamp_after\");\n        let timestamp_before = API::params_get_value(&params, \"timestamp_before\");\n        let cursor = API::params_get_value(&params, \"cursor\");\n        let cursor_direction =\n            match API::params_get_value_default(&params, \"cursor_direction\", \"next\".to_string()) {\n                Ok(v) => v,\n                Err(e) => {\n                    return Ok(e.to_response());\n                }\n            };\n        let total_count = API::params_get_value(&params, \"total_count\");\n\n        let mut param = DomainListGetParam::new();\n        param.id = id;\n        param.page_num = page_num;\n        param.page_size = page_size;\n        param.domain = domain;\n        param.domain_filter_mode = domain_filter_mode;\n        param.domain_type = domain_type;\n        param.domain_group = domain_group;\n        param.client = client;\n        param.reply_code = reply_code;\n        param.order = order;\n        param.is_blocked = is_blocked;\n        param.timestamp_after = timestamp_after;\n        param.timestamp_before = timestamp_before;\n\n        if cursor.is_some() || total_count.is_some() {\n            let param_cursor = DomainListGetParamCursor {\n                id: if cursor.is_some() { cursor } else { None },\n                total_count: if total_count.is_some() {\n                    total_count.unwrap()\n                } else {\n                    0\n                },\n                direction: cursor_direction,\n            };\n            param.cursor = Some(param_cursor);\n        }\n\n        let data_server = this.get_data_server();\n        let ret = API::call_blocking(this, move || {\n            let ret = data_server\n                .get_domain_list(&param)\n                .map_err(|e| e.to_string());\n            if let Err(e) = ret {\n                return Err(e.to_string());\n            }\n\n            let ret = ret.unwrap();\n\n            return Ok(ret);\n        })\n        .await;\n\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let ret = ret.unwrap();\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let domain_list = ret.unwrap();\n        let list_count = domain_list.total_count;\n        let mut total_page = list_count / page_size;\n        if list_count % page_size != 0 {\n            total_page += 1;\n        }\n\n        let total_count = domain_list.total_count;\n        let body = api_msg_gen_domain_list(&domain_list, total_page, total_count);\n\n        API::response_build(StatusCode::OK, body)\n    }\n\n    /// Delete the domain list before timestamp <br>\n    /// API: DELETE /api/domain <br>\n    ///   parameter: <br>\n    ///     timestamp: u64: Unix timestamp <br>\n    ///\n    async fn api_domain_delete_list(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let params = API::get_params(&req);\n        let timestamp_before = API::params_get_value(&params, \"timestamp_before\");\n        if timestamp_before.is_none() {\n            return API::response_error(StatusCode::BAD_REQUEST, \"Invalid parameter.\");\n        }\n\n        let timestamp_before = timestamp_before.unwrap();\n        let data_server = this.get_data_server();\n        let ret = data_server.delete_domain_before_timestamp(timestamp_before);\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        if *ret.as_ref().unwrap() == 0 {\n            return API::response_error(StatusCode::NOT_FOUND, \"Not found\");\n        }\n\n        let body = api_msg_gen_count(ret.unwrap() as i64);\n        API::response_build(StatusCode::OK, body)\n    }\n\n    async fn api_client_get_list(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let params = API::get_params(&req);\n\n        let page_num = API::params_get_value_default(&params, \"page_num\", 1 as u64)?;\n        let page_size = API::params_get_value_default(&params, \"page_size\", 10 as u64)?;\n        if page_num == 0 || page_size == 0 {\n            return API::response_error(\n                StatusCode::BAD_REQUEST,\n                \"Invalid parameter: page_num or page_size\",\n            );\n        }\n\n        let id = API::params_get_value(&params, \"id\");\n        let client_ip = API::params_get_value(&params, \"client_ip\");\n        let hostname = API::params_get_value(&params, \"hostname\");\n        let mac = API::params_get_value(&params, \"mac\");\n        let timestamp_after = API::params_get_value(&params, \"timestamp_after\");\n        let timestamp_before = API::params_get_value(&params, \"timestamp_before\");\n        let order = API::params_get_value(&params, \"order\");\n        let cursor = API::params_get_value(&params, \"cursor\");\n        let cursor_direction =\n            match API::params_get_value_default(&params, \"cursor_direction\", \"next\".to_string()) {\n                Ok(v) => v,\n                Err(e) => {\n                    return Ok(e.to_response());\n                }\n            };\n        let total_count = API::params_get_value(&params, \"total_count\");\n\n        let mut param = ClientListGetParam::new();\n        param.id = id;\n        param.page_num = page_num;\n        param.page_size = page_size;\n        param.client_ip = client_ip;\n        param.hostname = hostname;\n        param.mac = mac;\n        param.order = order;\n        param.timestamp_after = timestamp_after;\n        param.timestamp_before = timestamp_before;\n\n        if cursor.is_some() || total_count.is_some() {\n            let param_cursor = ClientListGetParamCursor {\n                id: if cursor.is_some() { cursor } else { None },\n                total_count: if total_count.is_some() {\n                    total_count.unwrap()\n                } else {\n                    0\n                },\n                direction: cursor_direction,\n            };\n            param.cursor = Some(param_cursor);\n        }\n\n        let data_server = this.get_data_server();\n        let ret = API::call_blocking(this, move || {\n            let ret = data_server\n                .get_client_list(&param)\n                .map_err(|e| e.to_string());\n            if let Err(e) = ret {\n                return Err(e.to_string());\n            }\n\n            let ret = ret.unwrap();\n\n            return Ok(ret);\n        })\n        .await;\n\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let ret = ret.unwrap();\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let client_list = ret.unwrap();\n        let list_count = client_list.total_count;\n        let mut total_page = list_count / page_size;\n        if list_count % page_size != 0 {\n            total_page += 1;\n        }\n\n        let total_count = client_list.total_count;\n        let body = api_msg_gen_client_list(&client_list, total_page, total_count);\n\n        API::response_build(StatusCode::OK, body)\n    }\n\n    async fn api_log_stream(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        mut req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        if hyper_tungstenite::is_upgrade_request(&req) {\n            let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None)\n                .map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;\n\n            tokio::spawn(async move {\n                if let Err(e) = http_server_stream::serve_log_stream(this, websocket).await {\n                    dns_log!(LogLevel::DEBUG, \"Error in websocket connection: {e}\");\n                }\n            });\n\n            Ok(response)\n        } else {\n            return API::response_error(StatusCode::BAD_REQUEST, \"Need websocket upgrade.\");\n        }\n    }\n\n    async fn api_audit_log_stream(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        mut req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        if hyper_tungstenite::is_upgrade_request(&req) {\n            let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None)\n                .map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;\n\n            tokio::spawn(async move {\n                if let Err(e) = http_server_stream::serve_audit_log_stream(this, websocket).await {\n                    dns_log!(LogLevel::DEBUG, \"Error in websocket connection: {e}\");\n                }\n            });\n\n            Ok(response)\n        } else {\n            return API::response_error(StatusCode::BAD_REQUEST, \"Need websocket upgrade.\");\n        }\n    }\n\n    async fn api_log_set_level(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let whole_body = String::from_utf8(_req.into_body().collect().await?.to_bytes().into())?;\n        let level = api_msg_parse_loglevel(whole_body.as_str());\n        if let Err(e) = level {\n            return API::response_error(StatusCode::BAD_REQUEST, e.to_string().as_str());\n        }\n\n        let level = level.unwrap();\n        dns_log_set_level(level);\n        let data_server = this.get_data_server();\n        _ = data_server.set_config(\"log-level\", level.to_string().as_str());\n        API::response_build(StatusCode::NO_CONTENT, \"\".to_string())\n    }\n\n    async fn api_log_get_level(\n        _this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let level = dns_log_get_level();\n        let msg = api_msg_gen_loglevel(level);\n        API::response_build(StatusCode::OK, msg)\n    }\n\n    async fn api_server_version(\n        _this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let server_version = &smartdns::smartdns_version();\n        let ui_version = &smartdns::smartdns_ui_version();\n        let msg = api_msg_gen_version(server_version, ui_version);\n        API::response_build(StatusCode::OK, msg)\n    }\n\n    async fn api_upstream_server_get_list(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        let upstream_server_list = data_server.get_upstream_server_list()?;\n        let body = api_msg_gen_upstream_server_list(&upstream_server_list);\n\n        API::response_build(StatusCode::OK, body)\n    }\n\n    async fn api_config_get_settings(\n        this: Arc<HttpServer>,\n        param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let key = API::params_get_value(&param, \"key\");\n\n        let data_server = this.get_data_server();\n        let settings = data_server.get_config_list();\n        if settings.is_err() {\n            return API::response_error(StatusCode::NOT_FOUND, \"Not found\");\n        }\n\n        let mut settings = settings.unwrap();\n        this.get_conf().settings_map().iter().for_each(|(k, v)| {\n            if settings.get(k).is_none() {\n                settings.insert(k.to_string(), v.to_string());\n            }\n        });\n        let pass = settings.get(PASSWORD_CONFIG_KEY);\n        if pass.is_some() {\n            let pass = \"********\".to_string();\n            settings.insert(PASSWORD_CONFIG_KEY.to_string(), pass);\n        }\n\n        if key.is_some() {\n            let key: String = key.unwrap();\n            let value = settings.get(key.as_str());\n            if value.is_none() {\n                return API::response_error(StatusCode::NOT_FOUND, \"Not found\");\n            }\n\n            let mut map = std::collections::HashMap::new();\n            map.insert(key, value.unwrap().clone());\n            let msg = api_msg_gen_key_value(&map);\n            return API::response_build(StatusCode::OK, msg);\n        }\n\n        let msg = api_msg_gen_key_value(&settings);\n        API::response_build(StatusCode::OK, msg)\n    }\n\n    async fn api_config_set_settings(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        let whole_body = String::from_utf8(req.into_body().collect().await?.to_bytes().into())?;\n        let settings = api_msg_parse_key_value(whole_body.as_str());\n        if let Err(e) = settings {\n            return API::response_error(StatusCode::BAD_REQUEST, e.to_string().as_str());\n        }\n\n        let settings = settings.unwrap();\n        for (key, value) in settings {\n            if key == PASSWORD_CONFIG_KEY {\n                continue;\n            }\n            let ret = data_server.set_config(key.as_str(), value.as_str());\n            if let Err(e) = ret {\n                return API::response_error(\n                    StatusCode::INTERNAL_SERVER_ERROR,\n                    e.to_string().as_str(),\n                );\n            }\n        }\n\n        API::response_build(StatusCode::NO_CONTENT, \"\".to_string())\n    }\n\n    async fn api_stats_get_top_client(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        let params = API::get_params(&_req);\n        let count = API::params_get_value(&params, \"count\");\n\n        let ret = API::call_blocking(this, move || {\n            let ret = data_server.get_top_client_top_list(count);\n            if let Err(e) = ret {\n                return Err(e.to_string());\n            }\n\n            let ret = ret.unwrap();\n\n            return Ok(ret);\n        })\n        .await;\n\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let ret = ret.unwrap();\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let body = api_msg_gen_top_client_list(&ret.unwrap());\n        API::response_build(StatusCode::OK, body)\n    }\n\n    async fn api_stats_get_top_domain(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        let params = API::get_params(&_req);\n        let count = API::params_get_value(&params, \"count\");\n\n        let ret = API::call_blocking(this, move || {\n            let ret = data_server.get_top_domain_top_list(count);\n            if let Err(e) = ret {\n                return Err(e.to_string());\n            }\n\n            let ret = ret.unwrap();\n\n            return Ok(ret);\n        })\n        .await;\n\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let ret = ret.unwrap();\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let body = api_msg_gen_top_domain_list(&ret.unwrap());\n        API::response_build(StatusCode::OK, body)\n    }\n\n    async fn api_stats_get_metrics(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        mut req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        if hyper_tungstenite::is_upgrade_request(&req) {\n            let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None)\n                .map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;\n\n            tokio::spawn(async move {\n                if let Err(e) = http_server_stream::serve_metrics(data_server, websocket).await {\n                    dns_log!(LogLevel::DEBUG, \"Error in websocket connection: {e}\");\n                }\n            });\n\n            Ok(response)\n        } else {\n            let metrics = data_server.get_metrics()?;\n            let body = api_msg_gen_metrics_data(&metrics);\n            return API::response_build(StatusCode::OK, body);\n        }\n    }\n\n    async fn api_stats_get_overview(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        let overview = data_server.get_overview()?;\n        let body = api_msg_gen_stats_overview(&overview);\n        return API::response_build(StatusCode::OK, body);\n    }\n\n    async fn api_stats_refresh(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        let ret = API::call_blocking(this, move || -> Result<(), String> {\n            data_server.get_stat().refresh();\n            Ok(())\n        })\n        .await;\n\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        API::response_build(StatusCode::NO_CONTENT, \"\".to_string())\n    }\n\n    async fn api_stats_get_daily_query_count(\n        this: Arc<HttpServer>,\n        param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let data_server = this.get_data_server();\n        let past_days = API::params_get_value(&param, \"past_days\");\n        let ret = API::call_blocking(this, move || {\n            let ret = data_server.get_daily_query_count(past_days);\n            if let Err(e) = ret {\n                return Err(e.to_string());\n            }\n\n            let ret = ret.unwrap();\n\n            return Ok(ret);\n        })\n        .await;\n\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let ret = ret.unwrap();\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let body = api_msg_gen_daily_query_count(&ret.unwrap());\n        API::response_build(StatusCode::OK, body)\n    }\n\n    async fn api_stats_get_hourly_query_count(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let params = API::get_params(&_req);\n        let past_hours = API::params_get_value(&params, \"past_hours\");\n        let data_server = this.get_data_server();\n        let ret = API::call_blocking(this, move || {\n            let ret = data_server.get_hourly_query_count(past_hours);\n            if let Err(e) = ret {\n                return Err(e.to_string());\n            }\n\n            let ret = ret.unwrap();\n\n            return Ok(ret);\n        })\n        .await;\n\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let ret = ret.unwrap();\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let body = api_msg_gen_hourly_query_count(&ret.unwrap());\n        API::response_build(StatusCode::OK, body)\n    }\n\n    async fn api_whois(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        _req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        let params = API::get_params(&_req);\n        let domain = API::params_get_value(&params, \"domain\");\n        if domain.is_none() {\n            return API::response_error(StatusCode::BAD_REQUEST, \"Invalid parameter.\");\n        }\n\n        let domain: String = domain.unwrap();\n        let data_server = this.get_data_server();\n        let ret = data_server.whois(domain.as_str()).await;\n\n        if let Err(e) = ret {\n            return API::response_error(StatusCode::INTERNAL_SERVER_ERROR, e.to_string().as_str());\n        }\n\n        let body = api_msg_gen_whois_info(&ret.unwrap());\n        API::response_build(StatusCode::OK, body)\n    }\n\n    async fn api_tool_term(\n        this: Arc<HttpServer>,\n        _param: APIRouteParam,\n        mut req: Request<body::Incoming>,\n    ) -> Result<Response<Full<Bytes>>, HttpError> {\n        if this.get_conf().enable_terminal != true {\n            return API::response_error(StatusCode::FORBIDDEN, \"Terminal is disabled.\");\n        }\n\n        if hyper_tungstenite::is_upgrade_request(&req) {\n            let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None)\n                .map_err(|e| HttpError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;\n\n            tokio::spawn(async move {\n                if let Err(e) = http_server_stream::serve_term(websocket).await {\n                    dns_log!(LogLevel::DEBUG, \"Error in websocket connection: {e}\");\n                }\n            });\n\n            Ok(response)\n        } else {\n            return API::response_error(StatusCode::BAD_REQUEST, \"Need websocket upgrade.\");\n        }\n    }\n\n    async fn call_blocking<F, R>(\n        this: Arc<HttpServer>,\n        func: F,\n    ) -> Result<R, Box<dyn std::error::Error + Send>>\n    where\n        F: FnOnce() -> R + Send + 'static,\n        R: Send + 'static,\n    {\n        let rt = this.get_data_server().get_plugin().unwrap().get_runtime();\n\n        let ret = rt.spawn_blocking(move || -> R {\n            return func();\n        });\n\n        let ret = ret.await;\n        if ret.is_err() {\n            return Err(Box::new(ret.err().unwrap()));\n        }\n\n        let ret = ret.unwrap();\n\n        return Ok(ret);\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/http_server_stream.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse futures::sink::SinkExt;\nuse futures::stream::StreamExt;\nuse hyper_tungstenite::tungstenite::protocol::frame::coding::CloseCode;\nuse hyper_tungstenite::tungstenite::protocol::CloseFrame;\nuse std::os::fd::AsRawFd;\nuse std::sync::Arc;\nuse std::time::Duration;\nuse tokio::time::{interval_at, Instant};\nuse tokio_fd::AsyncFd;\n\nuse crate::smartdns::*;\nuse hyper_tungstenite::{tungstenite, HyperWebsocket};\nuse nix::errno::Errno;\nuse nix::libc::*;\nuse tokio::io::{AsyncReadExt, AsyncWriteExt};\nuse tungstenite::Message;\n\nuse crate::data_server::DataServer;\nuse crate::dns_log;\nuse crate::http_api_msg::api_msg_gen_metrics_data;\nuse crate::http_server::HttpServer;\nuse crate::smartdns::LogLevel;\n\ntype Error = Box<dyn std::error::Error + Send + Sync + 'static>;\n\nconst LOG_CONTROL_MESSAGE_TYPE: u8 = 1;\nconst LOG_CONTROL_PAUSE: u8 = 1;\nconst LOG_CONTROL_RESUME: u8 = 2;\nconst LOG_CONTROL_LOGLEVEL: u8 = 3;\n\nstruct LogLevelGuard {\n    old_log_level: LogLevel,\n}\n\nimpl Drop for LogLevelGuard {\n    fn drop(&mut self) {\n        dns_log_set_level(self.old_log_level);\n    }\n}\nimpl LogLevelGuard {\n    fn new() -> Self {\n        let old_log_level = dns_log_get_level();\n        LogLevelGuard { old_log_level }\n    }\n}\n\npub async fn serve_log_stream(\n    http_server: Arc<HttpServer>,\n    websocket: HyperWebsocket,\n) -> Result<(), Error> {\n    let mut websocket = websocket.await?;\n    let mut is_pause = false;\n\n    let data_server = http_server.get_data_server();\n    let mut log_stream = data_server.get_log_stream().await;\n\n    let _log_guard = LogLevelGuard::new();\n\n    loop {\n        tokio::select! {\n            msg = log_stream.recv() => {\n                if is_pause {\n                    continue;\n                }\n\n                match msg {\n                    Some(msg) => {\n                        let mut binary_msg = Vec::with_capacity(2 + msg.msg.len());\n                        binary_msg.push(0);\n                        binary_msg.push(msg.level as u8);\n                        binary_msg.extend_from_slice(msg.msg.as_bytes());\n                        let msg = Message::Binary(binary_msg.into());\n                        websocket.send(msg).await?;\n                    }\n                    None => {\n                        websocket.send(Message::Close(None)).await?;\n                        break;\n                    }\n                }\n            }\n\n            msg = websocket.next() => {\n                let message = msg.ok_or(\"websocket closed\")??;\n                match message {\n                    Message::Text(_msg) => {}\n                    Message::Binary(msg) => {\n                        if msg.len() == 0 {\n                            continue;\n                        }\n\n                        let msg_type = msg[0];\n                        match msg_type {\n                            LOG_CONTROL_MESSAGE_TYPE => {\n                                if msg.len() < 2 {\n                                    continue;\n                                }\n                                let control_type = msg[1];\n                                match control_type {\n                                    LOG_CONTROL_PAUSE => {\n                                        is_pause = true;\n                                        continue;\n                                    }\n                                    LOG_CONTROL_RESUME => {\n                                        is_pause = false;\n                                        continue;\n                                    }\n                                    LOG_CONTROL_LOGLEVEL => {\n                                        if msg.len() < 6 {\n                                            continue;\n                                        }\n\n                                        let level_msg = &msg[2..2 + msg.len() - 2];\n                                        let str_log_level = std::str::from_utf8(level_msg);\n                                        if str_log_level.is_err() {\n                                            continue;\n                                        }\n\n                                        let str_log_level = str_log_level.unwrap();\n                                        if str_log_level.len() == 0 {\n                                            continue;\n                                        }\n\n                                        let str_log_level = str_log_level.to_lowercase();\n                                        let str_log_level = str_log_level.as_str();\n\n                                        let log_level = str_log_level.try_into();\n                                        if log_level.is_err() {\n                                            continue;\n                                        }\n\n                                        let log_level = log_level.unwrap();\n                                        dns_log_set_level(log_level);\n                                    }\n                                    _ => {\n                                        continue;\n                                    }\n                                }\n                            }\n                            _ => {}\n                        }\n                    }\n                    Message::Ping(_msg) => {}\n                    Message::Pong(_msg) => {}\n                    Message::Close(_msg) => {\n                        websocket.send(Message::Close(None)).await?;\n                        break;\n                    }\n                    Message::Frame(_msg) => {\n                        unreachable!();\n                    }\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\npub async fn serve_audit_log_stream(\n    http_server: Arc<HttpServer>,\n    websocket: HyperWebsocket,\n) -> Result<(), Error> {\n    let mut websocket = websocket.await?;\n    let mut is_pause = false;\n\n    let data_server = http_server.get_data_server();\n    let mut log_stream = data_server.get_audit_log_stream().await;\n\n    if dns_audit_log_enabled() == false {\n        let reason =\n            \"Audit log is not enabled, please set `audit-enable` to `yes` in smartdns config file.\";\n\n        let close_msg = CloseFrame {\n            code: CloseCode::Bad(4003),\n            reason: reason.into(),\n        };\n        websocket.send(Message::Close(Some(close_msg))).await?;\n        return Ok(());\n    }\n\n    loop {\n        tokio::select! {\n            msg = log_stream.recv() => {\n                if is_pause {\n                    continue;\n                }\n\n                match msg {\n                    Some(msg) => {\n                        let mut binary_msg = Vec::with_capacity(1 + msg.msg.len());\n                        binary_msg.push(0);\n                        binary_msg.extend_from_slice(msg.msg.as_bytes());\n                        let msg = Message::Binary(binary_msg.into());\n                        websocket.send(msg).await?;\n                    }\n                    None => {\n                        websocket.send(Message::Close(None)).await?;\n                        break;\n                    }\n                }\n            }\n\n            msg = websocket.next() => {\n                let message = msg.ok_or(\"websocket closed\")??;\n                match message {\n                    Message::Text(_msg) => {}\n                    Message::Binary(msg) => {\n                        if msg.len() == 0 {\n                            continue;\n                        }\n\n                        let msg_type = msg[0];\n                        match msg_type {\n                            LOG_CONTROL_MESSAGE_TYPE => {\n                                if msg.len() < 2 {\n                                    continue;\n                                }\n                                let control_type = msg[1];\n                                match control_type {\n                                    LOG_CONTROL_PAUSE => {\n                                        is_pause = true;\n                                        continue;\n                                    }\n                                    LOG_CONTROL_RESUME => {\n                                        is_pause = false;\n                                        continue;\n                                    }\n                                    _ => {\n                                        continue;\n                                    }\n                                }\n                            }\n                            _ => {}\n                        }\n                    }\n                    Message::Ping(_msg) => {}\n                    Message::Pong(_msg) => {}\n                    Message::Close(_msg) => {\n                        websocket.send(Message::Close(None)).await?;\n                        break;\n                    }\n                    Message::Frame(_msg) => {\n                        unreachable!();\n                    }\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\npub async fn serve_metrics(\n    data_server: Arc<DataServer>,\n    websocket: HyperWebsocket,\n) -> Result<(), Error> {\n    let mut websocket = websocket.await?;\n    let mut second_timer = interval_at(Instant::now(), Duration::from_secs(1));\n    loop {\n        tokio::select! {\n            _ = second_timer.tick() => {\n                let metrics = data_server.get_metrics();\n                match metrics {\n                    Ok(metrics) => {\n                        let data_server = api_msg_gen_metrics_data(&metrics);\n                        let msg = Message::Text(data_server.into());\n                        websocket.send(msg).await?;\n                    }\n                    Err(e) => {\n                        let msg = Message::Text(format!(\"{{\\\"error\\\": \\\"{}\\\"}}\", e).into());\n                        websocket.send(msg).await?;\n                    }\n                }\n            }\n            msg = websocket.next() => {\n                let message = msg.ok_or(\"websocket closed\")??;\n                match message {\n                    Message::Text(_msg) => {}\n                    Message::Binary(_msg) => {}\n                    Message::Ping(_msg) => {}\n                    Message::Pong(_msg) => {}\n                    Message::Close(_msg) => {\n                        websocket.send(Message::Close(None)).await?;\n                        break;\n                    }\n                    Message::Frame(_msg) => {\n                        unreachable!();\n                    }\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\nenum TermMessageType {\n    Data,\n    Err,\n    Resize,\n    Pause,\n    Resume,\n}\n\nimpl TryFrom<u8> for TermMessageType {\n    type Error = ();\n\n    fn try_from(value: u8) -> Result<Self, Self::Error> {\n        match value {\n            0 => Ok(TermMessageType::Data),\n            1 => Ok(TermMessageType::Err),\n            2 => Ok(TermMessageType::Resize),\n            3 => Ok(TermMessageType::Pause),\n            4 => Ok(TermMessageType::Resume),\n            _ => Err(()),\n        }\n    }\n}\n\n#[cfg(target_os = \"linux\")]\npub async fn serve_term(websocket: HyperWebsocket) -> Result<(), Error> {\n    type WsType =\n        hyper_tungstenite::WebSocketStream<hyper_util::rt::TokioIo<hyper::upgrade::Upgraded>>;\n    let mut websocket = websocket.await?;\n\n    let (pid, asyncfd) = unsafe {\n        let mut fd_master: std::os::fd::RawFd = 0;\n        let mut ws = winsize {\n            ws_row: 24,\n            ws_col: 80,\n            ws_xpixel: 0,\n            ws_ypixel: 0,\n        };\n        let pid = forkpty(\n            &mut fd_master,\n            std::ptr::null_mut(),\n            std::ptr::null_mut(),\n            &mut ws,\n        );\n        if pid < 0 {\n            dns_log!(LogLevel::ERROR, \"forkpty failed, error: {}\", Errno::last());\n            return Err(format!(\"forkpty failed, error: {}\", Errno::last()).into());\n        }\n\n        if pid == 0 {\n            let _ = ioctl(0, TIOCSCTTY, 1);\n            for i in 3..1024 {\n                close(i);\n            }\n            use std::ffi::CString;\n            std::env::set_var(\"TERM\", \"xterm-256color\");\n\n            let find_cmd = |cmd: &str| -> Result<String, Box<dyn std::error::Error>> {\n                let env_path = std::env::var(\"PATH\")?;\n                let paths = env_path.split(':');\n\n                for path in paths {\n                    let cmd_path = format!(\"{}/{}\", path, cmd);\n                    if std::fs::metadata(&cmd_path).is_ok() {\n                        return Ok(cmd_path);\n                    }\n                }\n\n                Err(format!(\"command not found {}\", cmd).into())\n            };\n\n            let su_path = find_cmd(\"su\");\n            let login_path = find_cmd(\"login\");\n            let mut err = ENOENT;\n\n            if su_path.is_ok() && (login_path.is_err() || geteuid() != 0) {\n                let uid = getuid();\n                let pw = getpwuid(uid);\n\n                if pw.is_null() {\n                    println!(\"getpwuid failed\");\n                    _exit(1);\n                }\n\n                let arg0 = CString::new(\"su\").unwrap();\n                let arg1 = CString::new(\"-\").unwrap();\n                let arg2 = (*pw).pw_name;\n                let login_message =\n                    format!(\"Login as {}\", std::ffi::CStr::from_ptr(arg2).to_str()?);\n                println!(\"{}\", login_message);\n\n                let cmd_path = CString::new(su_path.unwrap()).unwrap();\n                let args = [arg0.as_ptr(), arg1.as_ptr(), arg2, std::ptr::null()];\n                let ret = execv(cmd_path.as_ptr(), args.as_ptr());\n                if ret < 0 {\n                    err = Errno::last_raw();\n                }\n                println!(\"Please install `su` and add current user to sudoers\");\n            } else if login_path.is_ok() {\n                if geteuid() != 0 {\n                    println!(\"Login must be run as root, please run smartdns as root\");\n                    _exit(1);\n                }\n\n                let arg0 = CString::new(\"login\").unwrap();\n                let cmd_path = CString::new(login_path.unwrap()).unwrap();\n                let args = [arg0.as_ptr(), std::ptr::null()];\n                let ret = execv(cmd_path.as_ptr(), args.as_ptr());\n                if ret < 0 {\n                    err = Errno::last_raw();\n                }\n                println!(\"Please install `login` and run as root\");\n            } else {\n                println!(\"No su or login found, please install one of them\");\n            }\n\n            println!(\"Failed to execute `su` or `login`, code: {}\", err);\n            _exit(1);\n        }\n\n        (pid, AsyncFd::try_from(fd_master))\n    };\n\n    if let Err(e) = asyncfd {\n        if pid > 0 {\n            unsafe {\n                let _ = kill(pid, SIGKILL);\n                let _ = waitpid(pid, std::ptr::null_mut(), 0);\n            }\n        }\n        return Err(e.into());\n    }\n\n    let send_error_msg = |ws: &mut WsType, msg: &str| {\n        let mut buf = [0u8; 4096];\n        buf[0] = TermMessageType::Err as u8;\n        buf[1..msg.len() + 1].copy_from_slice(msg.as_bytes());\n        let msg = Message::Binary(buf[..msg.len() + 1].to_vec().into());\n        let _ = ws.send(msg);\n\n        let msg = Message::Close(None);\n        let _ = ws.send(msg);\n    };\n\n    let mut asyncfd = asyncfd.unwrap();\n    let mut is_pause = false;\n    loop {\n        let mut buf = [0u8; 4096];\n        let (data_type, data_buf) = buf.split_at_mut(1);\n        let data_len;\n        tokio::select! {\n            n = asyncfd.read(data_buf) => {\n                match n {\n                    Ok(n) => {\n                        if n == 0 {\n                            websocket.send(Message::Close(None)).await?;\n                            dns_log!(LogLevel::DEBUG, \"EOF\");\n                            break;\n                        }\n\n                        if is_pause {\n                            continue;\n                        }\n\n                        data_len = n + 1;\n                        data_type[0] = TermMessageType::Data as u8;\n                        let msg = Message::Binary(buf[..data_len].to_vec().into());\n                        websocket.send(msg).await?;\n                    }\n                    Err(e) => {\n                        send_error_msg(&mut websocket, e.to_string().as_str());\n                        dns_log!(LogLevel::DEBUG, \"Error: {}\", e.to_string().as_str());\n                        break;\n                    }\n                }\n            }\n            msg = websocket.next() => {\n                let message = msg.ok_or(\"websocket closed\")??;\n                match message {\n                    Message::Text(msg) => {\n                        asyncfd.write(msg.as_bytes()).await?;\n                    }\n                    Message::Binary(msg) => {\n                        if msg.len() == 0 {\n                            continue;\n                        }\n\n                        let msg_type = TermMessageType::try_from(msg[0]);\n                        if msg_type.is_err() {\n                            send_error_msg(&mut websocket, \"invalid message type\");\n                            break;\n                        }\n\n                        let msg_type = msg_type.unwrap();\n                        let msg = &msg[1..];\n\n                        match msg_type {\n                            TermMessageType::Resize => {\n                                let ws = winsize {\n                                    ws_col: u16::from_be_bytes(msg[0..2].try_into().unwrap()),\n                                    ws_row: u16::from_be_bytes(msg[2..4].try_into().unwrap()),\n                                    ws_xpixel: 0,\n                                    ws_ypixel: 0,\n                                };\n                                unsafe {\n                                    let _ = ioctl(asyncfd.as_raw_fd(), TIOCSWINSZ, &ws);\n                                }\n                            }\n                            TermMessageType::Pause => {\n                                is_pause = true;\n                            }\n                            TermMessageType::Resume => {\n                                is_pause = false;\n                            }\n                            TermMessageType::Data => {\n                                asyncfd.write(msg).await?;\n                            }\n                            _ => {\n                                continue;\n                            }\n                        }\n                    }\n                    Message::Ping(_msg) => {}\n                    Message::Pong(_msg) => {}\n                    Message::Close(_msg) => {\n                        dns_log!(LogLevel::DEBUG, \"Peer term closed\");\n                        break;\n                    }\n                    Message::Frame(_msg) => {\n                        unreachable!();\n                    }\n                }\n            }\n\n        }\n    }\n\n    unsafe {\n        let fd = asyncfd.as_raw_fd();\n        if fd > 0 {\n            let _ = close(fd);\n        }\n        let _ = kill(pid, SIGKILL);\n        let _ = waitpid(pid, std::ptr::null_mut(), 0);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/lib.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npub mod data_server;\npub mod data_stats;\npub mod data_upstream_server;\npub mod db;\npub mod http_api_msg;\npub mod http_error;\npub mod http_jwt;\npub mod http_server;\npub mod http_server_api;\npub mod http_server_stream;\npub mod plugin;\npub mod server_log;\npub mod smartdns;\npub mod utils;\npub mod whois;\n\nuse ctor::ctor;\nuse ctor::dtor;\n#[cfg(not(test))]\nuse plugin::*;\nuse smartdns::*;\n\n#[cfg(not(test))]\nfn lib_init_ops() {\n    let ops: Box<dyn SmartdnsOperations> = Box::new(SmartdnsPluginImpl::new());\n    unsafe {\n        let plugin_addr = std::ptr::addr_of_mut!(PLUGIN);\n        (*plugin_addr).set_operation(ops);\n    }\n}\n\n#[cfg(not(test))]\nfn lib_deinit_ops() {\n    unsafe {\n        let plugin_addr = std::ptr::addr_of_mut!(PLUGIN);\n        (*plugin_addr).clear_operation();\n    }\n}\n\n#[cfg(test)]\nfn lib_init_smartdns_lib() {\n    smartdns::dns_log_set_level(LogLevel::DEBUG);\n}\n\n#[ctor]\nfn lib_init() {\n    #[cfg(not(test))]\n    lib_init_ops();\n\n    #[cfg(test)]\n    lib_init_smartdns_lib();\n}\n\n#[dtor]\nfn lib_deinit() {\n    #[cfg(not(test))]\n    lib_deinit_ops();\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/plugin.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse crate::data_server::*;\nuse crate::dns_log;\nuse crate::http_server::*;\nuse crate::smartdns::*;\n\nuse getopts::Options;\nuse std::error::Error;\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse tokio::runtime::Builder;\nuse tokio::runtime::Runtime;\n\npub struct SmartdnsPlugin {\n    http_server_ctl: Arc<HttpServerControl>,\n    http_conf: Mutex<HttpServerConfig>,\n\n    data_server_ctl: Arc<DataServerControl>,\n    data_conf: Mutex<DataServerConfig>,\n\n    runtime: Arc<Runtime>,\n\n    is_start: Mutex<bool>,\n}\n\nimpl SmartdnsPlugin {\n    pub fn new() -> Arc<Self> {\n        let rt = Builder::new_multi_thread()\n            .enable_all()\n            .thread_name(\"smartdns-ui\")\n            .thread_keep_alive(tokio::time::Duration::from_secs(30))\n            .build()\n            .unwrap();\n        let plugin = Arc::new(SmartdnsPlugin {\n            http_server_ctl: Arc::new(HttpServerControl::new()),\n            http_conf: Mutex::new(HttpServerConfig::new()),\n\n            data_server_ctl: Arc::new(DataServerControl::new()),\n            data_conf: Mutex::new(DataServerConfig::new()),\n            runtime: Arc::new(rt),\n\n            is_start: Mutex::new(false),\n        });\n\n        plugin.http_server_ctl.set_plugin(plugin.clone());\n        plugin.data_server_ctl.set_plugin(plugin.clone());\n\n        plugin\n    }\n\n    pub fn get_runtime(&self) -> Arc<Runtime> {\n        self.runtime.clone()\n    }\n\n    pub fn get_http_server(&self) -> Arc<HttpServer> {\n        self.http_server_ctl.get_http_server()\n    }\n\n    pub fn get_data_server(&self) -> Arc<DataServer> {\n        self.data_server_ctl.get_data_server()\n    }\n\n    fn parser_args(&self, args: &Vec<String>) -> Result<(), Box<dyn Error>> {\n        let mut opts = Options::new();\n        opts.optopt(\"i\", \"ip\", \"http ip\", \"IP\");\n        opts.optopt(\"r\", \"www-root\", \"http www root\", \"PATH\");\n        opts.optopt(\"\", \"data-dir\", \"http data dir\", \"PATH\");\n        opts.optopt(\"\", \"token-expire\", \"http token expire time\", \"TIME\");\n        if args.len() <= 0 {\n            return Ok(());\n        }\n\n        let matches = match opts.parse(&args[1..]) {\n            Ok(m) => m,\n            Err(f) => {\n                return Err(Box::new(f));\n            }\n        };\n\n        let mut http_conf = self.http_conf.lock().unwrap();\n        let mut data_conf = self.data_conf.lock().unwrap();\n\n        let www_root = Plugin::dns_conf_plugin_config(\"smartdns-ui.www-root\");\n        if let Some(www_root) = www_root {\n            http_conf.http_root = smartdns_conf_get_conf_fullpath(&www_root);\n        }\n\n        let ip = Plugin::dns_conf_plugin_config(\"smartdns-ui.ip\");\n        if let Some(ip) = ip {\n            http_conf.http_ip = ip;\n        }\n\n        if let Some(ip) = matches.opt_str(\"i\") {\n            http_conf.http_ip = ip;\n        }\n\n        if let Some(root) = matches.opt_str(\"r\") {\n            http_conf.http_root = root;\n        }\n\n        let mut token_expire = Plugin::dns_conf_plugin_config(\"smartdns-ui.token-expire\");\n        if token_expire.is_none() {\n            token_expire = matches.opt_str(\"token-expire\");\n        }\n        if let Some(token_expire) = token_expire {\n            let v = token_expire.parse::<u32>();\n            if let Err(e) = v {\n                dns_log!(\n                    LogLevel::ERROR,\n                    \"parse token expire time error: {}\",\n                    e.to_string()\n                );\n                return Err(Box::new(e));\n            }\n            http_conf.token_expired_time = v.unwrap();\n        }\n\n        if let Some(data_dir) = matches.opt_str(\"data-dir\") {\n            data_conf.data_path = data_dir;\n        }\n\n        Ok(())\n    }\n\n    pub fn load_config(&self) -> Result<(), Box<dyn Error>> {\n        let data_server = self.get_data_server();\n        self.data_conf\n            .lock()\n            .unwrap()\n            .load_config(data_server.clone())?;\n        self.http_conf\n            .lock()\n            .unwrap()\n            .load_config(data_server.clone())?;\n        Ok(())\n    }\n\n    pub fn start(&self, args: &Vec<String>) -> Result<(), Box<dyn Error>> {\n        dns_log!(LogLevel::INFO, \"start smartdns-ui server.\");\n        let mut is_start = self.is_start.lock().unwrap();\n        *is_start = true;\n\n        self.parser_args(args)?;\n        self.load_config()?;\n        self.data_server_ctl\n            .init_db(&self.data_conf.lock().unwrap())?;\n        self.load_config()?;\n        self.data_server_ctl.start_data_server()?;\n        let http_conf = self.http_conf.lock().unwrap().clone();\n        self.http_server_ctl.start_http_server(&http_conf)?;\n\n        Ok(())\n    }\n\n    pub fn stop(&self) {\n        let mut is_start = self.is_start.lock().unwrap();\n        if !*is_start {\n            return;\n        }\n        *is_start = false;\n        \n        dns_log!(LogLevel::INFO, \"stop smartdns-ui server.\");\n        self.http_server_ctl.stop_http_server();\n        self.data_server_ctl.stop_data_server();\n    }\n\n    pub fn query_complete(&self, request: Box<dyn DnsRequest>) -> Result<(), Box<dyn Error>> {\n        let ret = self.data_server_ctl.send_request(request);\n        if let Err(e) = ret {\n            dns_log!(LogLevel::DEBUG, \"send request error: {}\", e.to_string());\n            return Err(e);\n        }\n\n        Ok(())\n    }\n\n    pub fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32) {\n        self.data_server_ctl.server_log(level, msg, msg_len);\n    }\n\n    pub fn server_audit_log(&self, msg: &str, msg_len: i32) {\n        self.data_server_ctl.server_audit_log(msg, msg_len);\n    }\n}\n\nimpl Drop for SmartdnsPlugin {\n    fn drop(&mut self) {\n        self.stop();\n    }\n}\n\npub struct SmartdnsPluginImpl {\n    plugin: Arc<SmartdnsPlugin>,\n}\n\nimpl SmartdnsPluginImpl {\n    pub fn new() -> Self {\n        SmartdnsPluginImpl {\n            plugin: SmartdnsPlugin::new(),\n        }\n    }\n}\n\nimpl Drop for SmartdnsPluginImpl {\n    fn drop(&mut self) {\n        self.plugin.stop();\n    }\n}\n\nimpl SmartdnsOperations for SmartdnsPluginImpl {\n    fn server_query_complete(&self, request: Box<dyn DnsRequest>) {\n        let _ = self.plugin.query_complete(request);\n    }\n\n    fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32) {\n        self.plugin.server_log(level, msg, msg_len);\n    }\n\n    fn server_audit_log(&self, msg: &str, msg_len: i32) {\n        self.plugin.server_audit_log(msg, msg_len);\n    }\n\n    fn server_init(&mut self, args: &Vec<String>) -> Result<(), Box<dyn Error>> {\n        self.plugin.start(args)\n    }\n\n    fn server_exit(&mut self) {\n        self.plugin.stop();\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/server_log.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse tokio::sync::{mpsc, RwLock};\n\nuse crate::LogLevel;\n\n#[derive(Clone)]\npub struct ServerLogMsg {\n    pub level: LogLevel,\n    pub msg: String,\n    pub len: i32,\n}\n\npub struct ServerLog {\n    streams: RwLock<Vec<mpsc::Sender<ServerLogMsg>>>,\n}\n\nimpl ServerLog {\n    pub fn new() -> Self {\n        ServerLog {\n            streams: RwLock::new(Vec::new()),\n        }\n    }\n\n    pub async fn get_log_stream(&self) -> mpsc::Receiver<ServerLogMsg> {\n        let (tx, rx) = mpsc::channel(4096);\n        self.streams.write().await.push(tx);\n        rx\n    }\n\n    pub fn dispatch_log(&self, level: LogLevel, msg: &str, len: i32) {\n        let mut remove_list = Vec::new();\n\n        {\n            let streams = self.streams.blocking_read();\n            if streams.len() == 0 {\n                return;\n            }\n\n            let msg = ServerLogMsg {\n                level,\n                msg: msg.to_string(),\n                len,\n            };\n\n            for (i, stream) in streams.iter().enumerate() {\n                let ret = stream.try_send(msg.clone());\n                if let Err(e) = ret {\n                    match e {\n                        mpsc::error::TrySendError::Full(_) => {}\n                        mpsc::error::TrySendError::Closed(_) => {\n                            remove_list.push(i);\n                        }\n                    }\n                }\n            }\n        }\n\n        if remove_list.len() > 0 {\n            let mut streams = self.streams.blocking_write();\n            for i in remove_list.iter().rev() {\n                streams.remove(*i);\n            }\n        }\n    }\n}\n\n\n#[derive(Clone)]\npub struct ServerAuditLogMsg {\n    pub msg: String,\n    pub len: i32,\n}\n\npub struct ServerAuditLog {\n    streams: RwLock<Vec<mpsc::Sender<ServerAuditLogMsg>>>,\n}\n\nimpl ServerAuditLog {\n    pub fn new() -> Self {\n        ServerAuditLog {\n            streams: RwLock::new(Vec::new()),\n        }\n    }\n\n    pub async fn get_audit_log_stream(&self) -> mpsc::Receiver<ServerAuditLogMsg> {\n        let (tx, rx) = mpsc::channel(4096);\n        self.streams.write().await.push(tx);\n        rx\n    }\n\n    pub fn dispatch_audit_log(&self, msg: &str, len: i32) {\n        let mut remove_list = Vec::new();\n\n        {\n            let streams = self.streams.blocking_read();\n            if streams.len() == 0 {\n                return;\n            }\n\n            let msg = ServerAuditLogMsg {\n                msg: msg.to_string(),\n                len,\n            };\n\n            for (i, stream) in streams.iter().enumerate() {\n                let ret = stream.try_send(msg.clone());\n                if let Err(e) = ret {\n                    match e {\n                        mpsc::error::TrySendError::Full(_) => {}\n                        mpsc::error::TrySendError::Closed(_) => {\n                            remove_list.push(i);\n                        }\n                    }\n                }\n            }\n        }\n\n        if remove_list.len() > 0 {\n            let mut streams = self.streams.blocking_write();\n            for i in remove_list.iter().rev() {\n                streams.remove(*i);\n            }\n        }\n    }\n}"
  },
  {
    "path": "plugin/smartdns-ui/src/smartdns.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#![allow(non_upper_case_globals)]\n#![allow(non_camel_case_types)]\n#![allow(non_snake_case)]\n#![allow(dead_code)]\n#![allow(unused_imports)]\n#![allow(improper_ctypes)]\npub mod smartdns_c {\n    include!(concat!(env!(\"OUT_DIR\"), \"/smartdns_bindings.rs\"));\n}\n\nuse std::error::Error;\nuse std::ffi::CString;\nuse std::fmt;\nuse std::os::raw::*;\n\n#[repr(C)]\n#[derive(Copy, Clone, Debug, PartialEq)]\n#[allow(dead_code)]\npub enum LogLevel {\n    DEBUG = 0,\n    INFO = 1,\n    NOTICE = 2,\n    WARN = 3,\n    ERROR = 4,\n    FATAL = 5,\n}\n\nimpl From<LogLevel> for u32 {\n    fn from(level: LogLevel) -> u32 {\n        level as u32\n    }\n}\n\nimpl ToString for LogLevel {\n    fn to_string(&self) -> String {\n        match self {\n            LogLevel::DEBUG => \"debug\".to_string(),\n            LogLevel::INFO => \"info\".to_string(),\n            LogLevel::NOTICE => \"notice\".to_string(),\n            LogLevel::WARN => \"warn\".to_string(),\n            LogLevel::ERROR => \"error\".to_string(),\n            LogLevel::FATAL => \"fatal\".to_string(),\n        }\n    }\n}\n\nimpl TryFrom<u32> for LogLevel {\n    type Error = ();\n\n    fn try_from(value: u32) -> Result<Self, Self::Error> {\n        match value {\n            0 => Ok(LogLevel::DEBUG),\n            1 => Ok(LogLevel::INFO),\n            2 => Ok(LogLevel::NOTICE),\n            3 => Ok(LogLevel::WARN),\n            4 => Ok(LogLevel::ERROR),\n            5 => Ok(LogLevel::FATAL),\n            _ => Err(()),\n        }\n    }\n}\n\nimpl TryFrom<&str> for LogLevel {\n    type Error = ();\n\n    fn try_from(value: &str) -> Result<Self, Self::Error> {\n        match value.to_lowercase().as_str() {\n            \"debug\" => Ok(LogLevel::DEBUG),\n            \"info\" => Ok(LogLevel::INFO),\n            \"notice\" => Ok(LogLevel::NOTICE),\n            \"warn\" => Ok(LogLevel::WARN),\n            \"error\" => Ok(LogLevel::ERROR),\n            \"fatal\" => Ok(LogLevel::FATAL),\n            _ => Err(()),\n        }\n    }\n}\n\nimpl TryFrom<String> for LogLevel {\n    type Error = ();\n    fn try_from(value: String) -> Result<Self, Self::Error> {\n        LogLevel::try_from(value.as_str())\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum DnsServerType {\n    SERVER_UDP,\n    SERVER_TCP,\n    SERVER_TLS,\n    SERVER_HTTPS,\n    SERVER_QUIC,\n    SERVER_HTTP3,\n    SERVER_MDNS,\n    SERVER_UNKNOWN,\n}\n\nimpl From<u32> for DnsServerType {\n    fn from(t: u32) -> DnsServerType {\n        match t {\n            0 => DnsServerType::SERVER_UDP,\n            1 => DnsServerType::SERVER_TCP,\n            2 => DnsServerType::SERVER_TLS,\n            3 => DnsServerType::SERVER_HTTPS,\n            4 => DnsServerType::SERVER_QUIC,\n            5 => DnsServerType::SERVER_HTTP3,\n            6 => DnsServerType::SERVER_MDNS,\n            _ => DnsServerType::SERVER_UNKNOWN,\n        }\n    }\n}\n\nimpl std::str::FromStr for DnsServerType {\n    type Err = String;\n\n    fn from_str(t: &str) -> Result<DnsServerType, String> {\n        match t {\n            \"udp\" => Ok(DnsServerType::SERVER_UDP),\n            \"tcp\" => Ok(DnsServerType::SERVER_TCP),\n            \"tls\" => Ok(DnsServerType::SERVER_TLS),\n            \"https\" => Ok(DnsServerType::SERVER_HTTPS),\n            \"quic\" => Ok(DnsServerType::SERVER_QUIC),\n            \"http3\" => Ok(DnsServerType::SERVER_HTTP3),\n            \"mdns\" => Ok(DnsServerType::SERVER_MDNS),\n            _ => Err(\"unknown\".to_string()),\n        }\n    }\n}\n\nimpl ToString for DnsServerType {\n    fn to_string(&self) -> String {\n        match self {\n            DnsServerType::SERVER_UDP => \"udp\".to_string(),\n            DnsServerType::SERVER_TCP => \"tcp\".to_string(),\n            DnsServerType::SERVER_TLS => \"tls\".to_string(),\n            DnsServerType::SERVER_HTTPS => \"https\".to_string(),\n            DnsServerType::SERVER_QUIC => \"quic\".to_string(),\n            DnsServerType::SERVER_HTTP3 => \"http3\".to_string(),\n            DnsServerType::SERVER_MDNS => \"mdns\".to_string(),\n            DnsServerType::SERVER_UNKNOWN => \"unknown\".to_string(),\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum DnsServerTrustStatus {\n    TRUST_UNKNOW,\n    TRUST_NOT_APPLICABLE,\n    TRUST_VERIFY_FAILED,\n    TRUST_INSECURE,\n    TRUST_SECURE,\n}\n\nimpl From<u32> for DnsServerTrustStatus {\n    fn from(t: u32) -> DnsServerTrustStatus {\n        match t {\n            0 => DnsServerTrustStatus::TRUST_UNKNOW,\n            1 => DnsServerTrustStatus::TRUST_NOT_APPLICABLE,\n            2 => DnsServerTrustStatus::TRUST_VERIFY_FAILED,\n            3 => DnsServerTrustStatus::TRUST_INSECURE,\n            4 => DnsServerTrustStatus::TRUST_SECURE,\n            _ => DnsServerTrustStatus::TRUST_UNKNOW,\n        }\n    }\n}\n\nimpl ToString for DnsServerTrustStatus {\n    fn to_string(&self) -> String {\n        match self {\n            DnsServerTrustStatus::TRUST_UNKNOW => \"Unknown\".to_string(),\n            DnsServerTrustStatus::TRUST_NOT_APPLICABLE => \"Not Applicable\".to_string(),\n            DnsServerTrustStatus::TRUST_VERIFY_FAILED => \"Verify Failed\".to_string(),\n            DnsServerTrustStatus::TRUST_INSECURE => \"Insecure\".to_string(),\n            DnsServerTrustStatus::TRUST_SECURE => \"Secure\".to_string(),\n        }\n    }\n}\n\n#[macro_export]\nmacro_rules! dns_log {\n    ($level:expr, $($arg:tt)*) => {\n        if $crate::smartdns::dns_can_log($level) {\n            $crate::smartdns::dns_log_out($level, file!(), line!(), &format!($($arg)*));\n        }\n    };\n}\npub fn dns_can_log(level: LogLevel) -> bool {\n    unsafe { smartdns_c::smartdns_plugin_can_log(level as u32) != 0 }\n}\n\npub fn dns_log_set_level(level: LogLevel) {\n    unsafe {\n        smartdns_c::smartdns_plugin_log_setlevel(level as u32);\n    }\n}\n\npub fn dns_log_get_level() -> LogLevel {\n    unsafe {\n        let leve = smartdns_c::smartdns_plugin_log_getlevel();\n        LogLevel::try_from(leve as u32).unwrap()\n    }\n}\n\npub fn dns_audit_log_enabled() -> bool {\n    unsafe { smartdns_c::smartdns_plugin_is_audit_enabled() != 0 }\n}\n\npub fn dns_log_out(level: LogLevel, file: &str, line: u32, message: &str) {\n    let filename_only = std::path::Path::new(file)\n        .file_name()\n        .and_then(|s| s.to_str())\n        .unwrap();\n    let file_cstring = CString::new(filename_only).expect(\"Failed to convert to CString\");\n    let message_cstring = CString::new(message).expect(\"Failed to convert to CString\");\n\n    unsafe {\n        smartdns_c::smartdns_plugin_log(\n            level as u32,\n            file_cstring.as_ptr(),\n            line as i32,\n            std::ptr::null(),\n            message_cstring.as_ptr(),\n        );\n    }\n}\n\npub fn smartdns_version() -> String {\n    unsafe {\n        let version = smartdns_c::smartdns_version();\n        std::ffi::CStr::from_ptr(version)\n            .to_string_lossy()\n            .into_owned()\n    }\n}\n\npub fn smartdns_ui_version() -> String {\n    let mut ver = env!(\"CARGO_PKG_VERSION\").to_string();\n\n    if env!(\"GIT_VERSION\").is_empty() {\n        return ver;\n    }\n\n    ver.push_str(\" (\");\n    ver.push_str(env!(\"GIT_VERSION\"));\n    ver.push_str(\")\");\n\n    ver\n}\n\npub fn smartdns_get_server_name() -> String {\n    unsafe {\n        let mut buffer = [0u8; 4096];\n        smartdns_c::dns_server_get_server_name(\n            buffer.as_mut_ptr() as *mut c_char,\n            buffer.len() as i32,\n        );\n        let srv_name = std::ffi::CStr::from_ptr(buffer.as_ptr() as *const c_char)\n            .to_string_lossy()\n            .into_owned();\n\n        srv_name\n    }\n}\n\npub fn smartdns_server_run(file: &str) -> Result<(), Box<dyn Error>> {\n    let file = CString::new(file).expect(\"Failed to convert to CString\");\n    let ret: i32;\n    unsafe {\n        ret = smartdns_c::smartdns_server_run(file.as_ptr());\n    };\n\n    if ret != 0 {\n        return Err(\"smartdns server run error\".into());\n    }\n\n    Ok(())\n}\n\npub fn smartdns_enable_update_neighbour(enable: bool) {\n    unsafe {\n        if enable {\n            smartdns_c::dns_server_enable_update_neighbor_cache(1);\n        } else {\n            smartdns_c::dns_server_enable_update_neighbor_cache(0);\n        }\n    }\n}\n\npub fn smartdns_conf_get_conf_fullpath(path: &str) -> String {\n    let path = CString::new(path).expect(\"Failed to convert to CString\");\n    unsafe {\n        let mut buffer = [0u8; 4096];\n        smartdns_c::conf_get_conf_fullpath(\n            path.as_ptr(),\n            buffer.as_mut_ptr() as *mut c_char,\n            buffer.len().try_into().unwrap(),\n        );\n        let conf_fullpath = std::ffi::CStr::from_ptr(buffer.as_ptr() as *const c_char)\n            .to_string_lossy()\n            .into_owned();\n\n        conf_fullpath\n    }\n}\n\npub fn smartdns_server_stop() {\n    unsafe {\n        smartdns_c::smartdns_server_stop();\n    }\n}\n\npub fn get_utc_time_ms() -> u64 {\n    unsafe { smartdns_c::get_utc_time_ms() }\n}\n\nstatic SMARTDNS_OPS: smartdns_c::smartdns_operations = smartdns_c::smartdns_operations {\n    server_recv: None,\n    server_query_complete: Some(dns_request_complete),\n    server_log: Some(dns_server_log),\n    server_audit_log: Some(dns_server_audit_log),\n};\n\n#[no_mangle]\nextern \"C\" fn dns_request_complete(request: *mut smartdns_c::dns_request) {\n    unsafe {\n        let plugin_addr = std::ptr::addr_of_mut!(PLUGIN);\n        let ops = (*plugin_addr).ops.as_ref();\n        if let None = ops {\n            return;\n        }\n\n        let ops = ops.unwrap();\n        let req = DnsRequest_C::new(request);\n        ops.server_query_complete(Box::new(req));\n    }\n}\n\n#[no_mangle]\nextern \"C\" fn dns_server_log(\n    level: smartdns_c::smartdns_log_level,\n    msg: *const c_char,\n    msg_len: i32,\n) {\n    unsafe {\n        let plugin_addr = std::ptr::addr_of_mut!(PLUGIN);\n        let ops = (*plugin_addr).ops.as_ref();\n        if let None = ops {\n            return;\n        }\n\n        let raw_msg = std::slice::from_raw_parts(msg as *const u8, msg_len as usize + 1);\n        let msg = std::ffi::CStr::from_bytes_with_nul_unchecked(raw_msg)\n            .to_string_lossy()\n            .into_owned();\n        let level = LogLevel::try_from(level as u32).unwrap();\n\n        let ops = ops.unwrap();\n        ops.server_log(level, msg.as_str(), msg_len as i32);\n    }\n}\n\n#[no_mangle]\nextern \"C\" fn dns_server_audit_log(msg: *const c_char, msg_len: i32) {\n    unsafe {\n        let plugin_addr = std::ptr::addr_of_mut!(PLUGIN);\n        let ops = (*plugin_addr).ops.as_ref();\n        if let None = ops {\n            return;\n        }\n\n        let raw_msg = std::slice::from_raw_parts(msg as *const u8, msg_len as usize + 1);\n        let msg = std::ffi::CStr::from_bytes_with_nul_unchecked(raw_msg)\n            .to_string_lossy()\n            .into_owned();\n\n        let ops = ops.unwrap();\n        ops.server_audit_log(msg.as_str(), msg_len as i32);\n    }\n}\n\n#[no_mangle]\nextern \"C\" fn dns_plugin_init(plugin: *mut smartdns_c::dns_plugin) -> i32 {\n    unsafe {\n        let plugin_addr = std::ptr::addr_of_mut!(PLUGIN);\n        (*plugin_addr).parser_args(plugin).unwrap();\n        smartdns_c::smartdns_operations_register(&SMARTDNS_OPS);\n        let ret = (*plugin_addr)\n            .ops\n            .as_mut()\n            .unwrap()\n            .server_init((*plugin_addr).get_args());\n        if let Err(e) = ret {\n            dns_log!(LogLevel::ERROR, \"{}\", e.to_string());\n            dns_log!(LogLevel::ERROR, \"server init failed.\");\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n#[no_mangle]\nextern \"C\" fn dns_plugin_exit(_plugin: *mut smartdns_c::dns_plugin) -> i32 {\n    unsafe {\n        let plugin_addr = std::ptr::addr_of_mut!(PLUGIN);\n        smartdns_c::smartdns_operations_unregister(&SMARTDNS_OPS);\n        (*plugin_addr).ops.as_mut().unwrap().server_exit();\n    }\n    return 0;\n}\n\n#[no_mangle]\nextern \"C\" fn dns_plugin_api_version() -> u32 {\n    smartdns_c::SMARTDNS_PLUGIN_API_VERSION\n}\n\npub trait DnsRequest: Send + Sync {\n    fn get_group_name(&self) -> String;\n    fn get_domain(&self) -> String;\n    fn get_qtype(&self) -> u32;\n    fn get_qclass(&self) -> i32;\n    fn get_id(&self) -> u16;\n    fn get_rcode(&self) -> u16;\n    fn get_query_time(&self) -> i32;\n    fn get_query_timestamp(&self) -> u64;\n    fn get_ping_time(&self) -> f64;\n    fn get_is_blocked(&self) -> bool;\n    fn get_is_cached(&self) -> bool;\n    fn get_remote_mac(&self) -> [u8; 6];\n    fn get_remote_addr(&self) -> String;\n    fn get_local_addr(&self) -> String;\n    fn is_prefetch_request(&self) -> bool;\n    fn is_dualstack_request(&self) -> bool;\n}\n\npub struct DnsRequest_C {\n    request: *mut smartdns_c::dns_request,\n}\n\n#[allow(dead_code)]\nimpl DnsRequest_C {\n    fn new(request: *mut smartdns_c::dns_request) -> DnsRequest_C {\n        unsafe {\n            smartdns_c::dns_server_request_get(request);\n        }\n\n        DnsRequest_C { request }\n    }\n\n    fn put_ref(&mut self) {\n        unsafe {\n            smartdns_c::dns_server_request_put(self.request);\n            self.request = std::ptr::null_mut();\n        }\n    }\n}\n\n#[allow(dead_code)]\nimpl DnsRequest for DnsRequest_C {\n    fn get_group_name(&self) -> String {\n        unsafe {\n            let group_name = smartdns_c::dns_server_request_get_group_name(self.request);\n            std::ffi::CStr::from_ptr(group_name)\n                .to_string_lossy()\n                .into_owned()\n        }\n    }\n\n    fn get_domain(&self) -> String {\n        unsafe {\n            let domain = smartdns_c::dns_server_request_get_domain(self.request);\n            std::ffi::CStr::from_ptr(domain)\n                .to_string_lossy()\n                .into_owned()\n        }\n    }\n\n    fn get_qtype(&self) -> u32 {\n        unsafe { smartdns_c::dns_server_request_get_qtype(self.request) as u32 }\n    }\n\n    fn get_qclass(&self) -> i32 {\n        unsafe { smartdns_c::dns_server_request_get_qclass(self.request) }\n    }\n\n    fn get_id(&self) -> u16 {\n        unsafe { smartdns_c::dns_server_request_get_id(self.request) as u16 }\n    }\n\n    fn get_rcode(&self) -> u16 {\n        unsafe { smartdns_c::dns_server_request_get_rcode(self.request) as u16 }\n    }\n\n    fn get_query_time(&self) -> i32 {\n        unsafe { smartdns_c::dns_server_request_get_query_time(self.request) }\n    }\n\n    fn get_query_timestamp(&self) -> u64 {\n        unsafe { smartdns_c::dns_server_request_get_query_timestamp(self.request) }\n    }\n\n    fn get_ping_time(&self) -> f64 {\n        let v = unsafe { smartdns_c::dns_server_request_get_ping_time(self.request) };\n        let mut ping_time = v as f64;\n        ping_time = (ping_time * 10.0).round() / 10.0;\n        ping_time\n    }\n\n    fn get_is_blocked(&self) -> bool {\n        unsafe { smartdns_c::dns_server_request_is_blocked(self.request) != 0 }\n    }\n\n    fn get_is_cached(&self) -> bool {\n        unsafe { smartdns_c::dns_server_request_is_cached(self.request) != 0 }\n    }\n\n    fn get_remote_mac(&self) -> [u8; 6] {\n        unsafe {\n            let _mac_ptr = smartdns_c::dns_server_request_get_remote_mac(self.request);\n            if _mac_ptr.is_null() {\n                return [0u8; 6];\n            }\n\n            let mac = std::slice::from_raw_parts(_mac_ptr, 6);\n            return mac.try_into().unwrap();\n        }\n    }\n\n    fn get_remote_addr(&self) -> String {\n        unsafe {\n            let addr = smartdns_c::dns_server_request_get_remote_addr(self.request);\n            if addr.is_null() {\n                return \"API\".to_string();\n            }\n            let mut buf = [0u8; 1024];\n            let retstr = smartdns_c::get_host_by_addr(\n                buf.as_mut_ptr() as *mut c_char,\n                buf.len() as i32,\n                addr as *const smartdns_c::sockaddr,\n            );\n            if retstr.is_null() {\n                return String::new();\n            }\n\n            let addr = std::ffi::CStr::from_ptr(retstr)\n                .to_string_lossy()\n                .into_owned();\n            addr\n        }\n    }\n\n    fn get_local_addr(&self) -> String {\n        unsafe {\n            let addr = smartdns_c::dns_server_request_get_local_addr(self.request);\n            let mut buf = [0u8; 1024];\n            let retstr = smartdns_c::get_host_by_addr(\n                buf.as_mut_ptr() as *mut c_char,\n                buf.len() as i32,\n                addr as *const smartdns_c::sockaddr,\n            );\n            if retstr.is_null() {\n                return String::new();\n            }\n\n            let addr = std::ffi::CStr::from_ptr(retstr)\n                .to_string_lossy()\n                .into_owned();\n            addr\n        }\n    }\n\n    fn is_prefetch_request(&self) -> bool {\n        unsafe { smartdns_c::dns_server_request_is_prefetch(self.request) != 0 }\n    }\n\n    fn is_dualstack_request(&self) -> bool {\n        unsafe { smartdns_c::dns_server_request_is_dualstack(self.request) != 0 }\n    }\n}\n\nimpl Drop for DnsRequest_C {\n    fn drop(&mut self) {\n        self.put_ref();\n    }\n}\n\nimpl Clone for DnsRequest_C {\n    fn clone(&self) -> Self {\n        unsafe {\n            smartdns_c::dns_server_request_get(self.request);\n        }\n\n        DnsRequest_C {\n            request: self.request,\n        }\n    }\n}\n\nunsafe impl Send for DnsRequest_C {}\nunsafe impl Sync for DnsRequest_C {}\n\npub struct DnsServerStats {\n    stats: *mut smartdns_c::dns_server_stats,\n    server_info: *mut smartdns_c::dns_server_info,\n}\n\nimpl DnsServerStats {\n    fn new(\n        stats: *mut smartdns_c::dns_server_stats,\n        server_info: *mut smartdns_c::dns_server_info,\n    ) -> Self {\n        unsafe { smartdns_c::dns_client_server_info_get(server_info) };\n        DnsServerStats { stats, server_info }\n    }\n\n    pub fn get_query_total(&self) -> u64 {\n        unsafe { smartdns_c::dns_stats_server_stats_total_get(self.stats) }\n    }\n\n    pub fn get_query_success(&self) -> u64 {\n        unsafe { smartdns_c::dns_stats_server_stats_success_get(self.stats) }\n    }\n\n    pub fn get_query_recv(&self) -> u64 {\n        unsafe { smartdns_c::dns_stats_server_stats_recv_get(self.stats) }\n    }\n\n    pub fn get_success_rate(&self) -> f64 {\n        let total = self.get_query_total();\n        let success = self.get_query_success();\n        let mut success_rate: f64 = 0.0;\n        if total == 0 {\n            return success_rate;\n        }\n\n        success_rate = success as f64 / total as f64 * 100.0;\n        success_rate = (success_rate * 10.0).round() / 10.0;\n        success_rate\n    }\n\n    pub fn get_query_avg_time(&self) -> f64 {\n        let v = unsafe { smartdns_c::dns_stats_server_stats_avg_time_get(self.stats) };\n        let mut avg_time = v as f64;\n        avg_time = (avg_time * 10.0).round() / 10.0;\n        avg_time\n    }\n}\n\nimpl Drop for DnsServerStats {\n    fn drop(&mut self) {\n        unsafe {\n            if !self.server_info.is_null() {\n                smartdns_c::dns_client_server_info_release(self.server_info);\n            }\n        }\n    }\n}\n\npub struct DnsUpstreamServer {\n    server_info: *mut smartdns_c::dns_server_info,\n}\n\nimpl DnsUpstreamServer {\n    fn new(server_info: *mut smartdns_c::dns_server_info) -> Self {\n        unsafe {\n            smartdns_c::dns_client_server_info_get(server_info);\n        }\n\n        DnsUpstreamServer { server_info }\n    }\n\n    pub fn get_server_num() -> i32 {\n        unsafe { smartdns_c::dns_server_num() }\n    }\n\n    pub fn dns_server_alive_num() -> i32 {\n        unsafe { smartdns_c::dns_server_alive_num() }\n    }\n\n    pub fn get_server_stats(&self) -> DnsServerStats {\n        let stats = unsafe { smartdns_c::dns_client_get_server_stats(self.server_info) };\n        DnsServerStats::new(stats, self.server_info)\n    }\n\n    pub fn is_server_alive(&self) -> bool {\n        unsafe { smartdns_c::dns_client_server_is_alive(self.server_info) != 0 }\n    }\n\n    pub fn get_server_list() -> Result<Vec<DnsUpstreamServer>, String> {\n        let mut servers = Vec::new();\n        let server_num = DnsUpstreamServer::get_server_num();\n\n        unsafe {\n            let mut server_info: Vec<*mut smartdns_c::dns_server_info> =\n                Vec::with_capacity(server_num as usize);\n            let ret =\n                smartdns_c::dns_client_get_server_info_lists(server_info.as_mut_ptr(), server_num);\n            if ret < 0 {\n                return Err((\"get server info failed.\").to_string());\n            }\n\n            if ret > server_num {\n                return Err((\"get server info failed.\").to_string());\n            }\n            server_info.set_len(ret as usize);\n\n            for i in 0..ret {\n                let server_info = server_info[i as usize];\n                servers.push(DnsUpstreamServer::new(server_info));\n                smartdns_c::dns_client_server_info_release(server_info);\n            }\n        }\n\n        Ok(servers)\n    }\n\n    pub fn get_ip(&self) -> String {\n        unsafe {\n            let ip = smartdns_c::dns_client_get_server_ip(self.server_info);\n            std::ffi::CStr::from_ptr(ip).to_string_lossy().into_owned()\n        }\n    }\n\n    pub fn get_host(&self) -> String {\n        unsafe {\n            let host = smartdns_c::dns_client_get_server_host(self.server_info);\n            std::ffi::CStr::from_ptr(host)\n                .to_string_lossy()\n                .into_owned()\n        }\n    }\n\n    pub fn get_port(&self) -> u16 {\n        unsafe { smartdns_c::dns_client_get_server_port(self.server_info) as u16 }\n    }\n\n    pub fn get_type(&self) -> DnsServerType {\n        unsafe {\n            let t = smartdns_c::dns_client_get_server_type(self.server_info)\n                as smartdns_c::dns_server_type_t;\n            DnsServerType::from(t)\n        }\n    }\n\n    pub fn get_server_security_status(&self) -> DnsServerTrustStatus {\n        unsafe {\n            let t = smartdns_c::dns_client_get_server_security_status(self.server_info)\n                as smartdns_c::dns_server_security_status;\n            DnsServerTrustStatus::from(t)\n        }\n    }\n\n    pub fn get_groups(&self) -> Vec<String> {\n        let groups = Vec::new();\n        groups\n    }\n}\n\nimpl Drop for DnsUpstreamServer {\n    fn drop(&mut self) {\n        unsafe {\n            smartdns_c::dns_client_server_info_release(self.server_info);\n        }\n    }\n}\n\nimpl Clone for DnsUpstreamServer {\n    fn clone(&self) -> Self {\n        unsafe {\n            smartdns_c::dns_client_server_info_get(self.server_info);\n        }\n\n        DnsUpstreamServer {\n            server_info: self.server_info,\n        }\n    }\n}\n\nunsafe impl Send for DnsUpstreamServer {}\n\npub trait SmartdnsOperations {\n    fn server_query_complete(&self, request: Box<dyn DnsRequest>);\n    fn server_log(&self, level: LogLevel, msg: &str, msg_len: i32);\n    fn server_audit_log(&self, msg: &str, msg_len: i32);\n    fn server_init(&mut self, args: &Vec<String>) -> Result<(), Box<dyn Error>>;\n    fn server_exit(&mut self);\n}\n\npub static mut PLUGIN: Plugin = Plugin {\n    args: Vec::new(),\n    ops: None,\n};\n\npub struct Plugin {\n    args: Vec<String>,\n    ops: Option<Box<dyn SmartdnsOperations>>,\n}\n\npub struct SmartdnsCert {\n    pub key: String,\n    pub cert: String,\n    pub password: String,\n}\n\n#[allow(dead_code)]\nimpl Plugin {\n    pub fn get_args(&self) -> &Vec<String> {\n        &self.args\n    }\n\n    pub fn set_operation(&mut self, ops: Box<dyn SmartdnsOperations>) {\n        self.ops = Some(ops);\n    }\n\n    pub fn clear_operation(&mut self) {\n        self.ops = None;\n    }\n\n    pub fn smartdns_exit(status: i32) {\n        unsafe {\n            smartdns_c::smartdns_exit(status);\n        }\n    }\n\n    pub fn smartdns_restart() {\n        unsafe {\n            smartdns_c::smartdns_restart();\n        }\n    }\n\n    pub fn smartdns_get_cert() -> Result<SmartdnsCert, String> {\n        unsafe {\n            let mut key = [0u8; 4096];\n            let mut cert = [0u8; 4096];\n            let ret = smartdns_c::smartdns_get_cert(\n                key.as_mut_ptr() as *mut c_char,\n                cert.as_mut_ptr() as *mut c_char,\n            );\n            if ret != 0 {\n                return Err(\"get cert error\".to_string());\n            }\n\n            let key = std::ffi::CStr::from_ptr(key.as_ptr() as *const c_char)\n                .to_string_lossy()\n                .into_owned();\n            let cert = std::ffi::CStr::from_ptr(cert.as_ptr() as *const c_char)\n                .to_string_lossy()\n                .into_owned();\n            Ok(SmartdnsCert {\n                key,\n                cert,\n                password: \"\".to_string(),\n            })\n        }\n    }\n\n    pub fn dns_cache_flush() {\n        unsafe {\n            smartdns_c::dns_cache_flush();\n        }\n    }\n\n    pub fn dns_cache_total_num() -> i32 {\n        unsafe { smartdns_c::dns_cache_total_num() }\n    }\n\n    #[allow(dead_code)]\n    pub fn dns_conf_cache_dir() -> String {\n        unsafe {\n            let cache_dir = smartdns_c::dns_conf_get_cache_dir();\n            std::ffi::CStr::from_ptr(cache_dir)\n                .to_string_lossy()\n                .into_owned()\n        }\n    }\n\n    #[allow(dead_code)]\n    pub fn dns_conf_data_dir() -> String {\n        unsafe {\n            let data_dir = smartdns_c::dns_conf_get_data_dir();\n            std::ffi::CStr::from_ptr(data_dir)\n                .to_string_lossy()\n                .into_owned()\n        }\n    }\n\n    #[allow(dead_code)]\n    pub fn dns_conf_plugin_config(key: &str) -> Option<String> {\n        let key = CString::new(key).expect(\"Failed to convert to CString\");\n        unsafe {\n            let value = smartdns_c::smartdns_plugin_get_config(key.as_ptr());\n            if value.is_null() {\n                return None;\n            }\n\n            Some(\n                std::ffi::CStr::from_ptr(value)\n                    .to_string_lossy()\n                    .into_owned(),\n            )\n        }\n    }\n\n    #[allow(dead_code)]\n    pub fn dns_conf_plugin_config_default(key: &str, default_val: &String) -> String {\n        let v = Plugin::dns_conf_plugin_config(key);\n        if let Some(v) = v {\n            return v;\n        }\n\n        default_val.clone()\n    }\n\n    #[allow(dead_code)]\n    pub fn dns_conf_plugin_clear_all_config() {\n        unsafe {\n            smartdns_c::smartdns_plugin_clear_all_config();\n        }\n    }\n\n    fn parser_args(&mut self, plugin: *mut smartdns_c::dns_plugin) -> Result<(), String> {\n        let argc = unsafe { smartdns_c::dns_plugin_get_argc(plugin) };\n        let args: Vec<String> = unsafe {\n            let argv = smartdns_c::dns_plugin_get_argv(plugin);\n            let mut args = Vec::new();\n            for i in 0..argc {\n                let arg = std::ffi::CStr::from_ptr(*argv.offset(i as isize))\n                    .to_string_lossy()\n                    .into_owned();\n                args.push(arg);\n            }\n            args\n        };\n\n        self.args = args;\n        Ok(())\n    }\n}\n\npub struct Stats {}\n\nimpl Stats {\n    pub fn get_avg_process_time() -> f64 {\n        unsafe {\n            let v = smartdns_c::dns_stats_avg_time_get();\n            let mut process_time = v as f64;\n            process_time = (process_time * 10.0).round() / 10.0;\n            process_time\n        }\n    }\n\n    pub fn get_request_total() -> u64 {\n        unsafe { smartdns_c::dns_stats_request_total_get() }\n    }\n\n    pub fn get_request_success() -> u64 {\n        unsafe { smartdns_c::dns_stats_request_success_get() }\n    }\n\n    pub fn get_request_from_client() -> u64 {\n        unsafe { smartdns_c::dns_stats_request_from_client_get() }\n    }\n\n    pub fn get_request_blocked() -> u64 {\n        unsafe { smartdns_c::dns_stats_request_blocked_get() }\n    }\n\n    pub fn get_cache_hit() -> u64 {\n        unsafe { smartdns_c::dns_stats_cache_hit_get() }\n    }\n\n    pub fn get_cache_memsize() -> u64 {\n        unsafe { smartdns_c::dns_cache_total_memsize() as u64 }\n    }\n\n    pub fn get_cache_hit_rate() -> f64 {\n        unsafe {\n            let v = smartdns_c::dns_stats_cache_hit_rate_get() as f64;\n            let mut cache_hit_rate = v as f64;\n            cache_hit_rate = (cache_hit_rate * 10.0).round() / 10.0;\n            cache_hit_rate\n        }\n    }\n\n    pub fn get_cache_memory_size() -> u64 {\n        unsafe { smartdns_c::dns_cache_total_memsize() as u64 }\n    }\n}\n\nimpl fmt::Display for Stats {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"Stats\")\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_dns_log() {\n        dns_log!(LogLevel::DEBUG, \"test log\");\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/utils.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse nix::libc;\nuse pbkdf2::{\n    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},\n    Pbkdf2,\n};\n\npub fn parse_value<T>(value: Option<String>, min: T, max: T, default: T) -> T\nwhere\n    T: PartialOrd + std::str::FromStr,\n{\n    if value.is_none() {\n        return default;\n    }\n\n    let value = value.unwrap().parse::<T>();\n    if let Err(_) = value {\n        return default;\n    }\n\n    let mut value = value.unwrap_or_else(|_| default);\n\n    if value < min {\n        value = min;\n    }\n\n    if value > max {\n        value = max;\n    }\n\n    value\n}\n\npub fn seconds_until_next_hour() -> u64 {\n    let now = chrono::Local::now();\n    let minutes = chrono::Timelike::minute(&now);\n    let seconds = chrono::Timelike::second(&now);\n    let remaining_seconds = 3600 - (minutes * 60 + seconds) as u64;\n    remaining_seconds\n}\n\npub fn get_free_disk_space(path: &str) -> u64 {\n    let path = std::ffi::CString::new(path).unwrap();\n    let mut statvfs: libc::statvfs = unsafe { std::mem::zeroed() };\n    let ret = unsafe { libc::statvfs(path.as_ptr(), &mut statvfs) };\n    if ret != 0 {\n        return 0;\n    }\n    statvfs.f_bsize as u64 * statvfs.f_bavail as u64\n}\n\npub fn hash_password(password: &str, round: Option<u32>) -> Result<String, Box<dyn std::error::Error>> {\n    let salt = SaltString::generate(&mut OsRng);\n    let mut parm = pbkdf2::Params::default();\n    parm.rounds = round.unwrap_or(10000);\n    let password_hash = Pbkdf2\n        .hash_password_customized(password.as_bytes(), None, None, parm, &salt)\n        .map_err(|e| e.to_string())?\n        .to_string();\n    Ok(password_hash)\n}\n\npub fn verify_password(password: &str, password_hash: &str) -> bool {\n    let parsed_hash = match PasswordHash::new(&password_hash) {\n        Ok(h) => h,\n        Err(_) => return false,\n    };\n\n    Pbkdf2\n        .verify_password(password.as_bytes(), &parsed_hash)\n        .is_ok()\n}\n\npub fn get_page_size() -> usize {\n    // Use libc to get the system page size\n    unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }\n}\n\npub fn is_dir_writable(path: &str) -> bool {\n    let path = std::ffi::CString::new(path).unwrap();\n    unsafe { libc::access(path.as_ptr(), libc::W_OK) == 0 }\n}\n\npub fn is_ipv6_supported() -> bool {\n    let sock = unsafe { libc::socket(libc::AF_INET6, libc::SOCK_STREAM, 0) };\n    if sock < 0 {\n        return false;\n    }\n    unsafe { libc::close(sock) };\n    true\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/src/whois.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse std::collections::HashMap;\nuse std::error::Error;\nuse std::sync::Mutex;\nuse tokio::io::{AsyncReadExt, AsyncWriteExt};\nuse tokio::net::TcpStream;\n\nuse crate::dns_log;\nuse crate::smartdns::LogLevel;\n\n#[derive(Debug, Clone)]\npub struct WhoIsInfo {\n    pub domain: String,\n    pub organization: String,\n    pub address: String,\n    pub city: String,\n    pub country: String,\n    pub registrar: String,\n    pub refer: String,\n}\n\npub struct WhoIs {\n    root_server: String,\n    server_lists: Mutex<HashMap<String, String>>,\n}\n\nimpl WhoIs {\n    pub fn new() -> Self {\n        WhoIs {\n            root_server: \"whois.iana.org\".to_string(),\n            server_lists: Mutex::new(HashMap::new()),\n        }\n    }\n\n    pub fn parser_whois(&self, whois: &str, whois_domain: &mut String) -> WhoIsInfo {\n        let mut info = WhoIsInfo {\n            domain: String::new(),\n            registrar: String::new(),\n            organization: String::new(),\n            address: String::new(),\n            city: String::new(),\n            country: String::new(),\n            refer: String::new(),\n        };\n\n        for line in whois.lines() {\n            let line = line.trim();\n            if let Some(value) = line.strip_prefix(\"Organization:\") {\n                info.organization = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrar:\") {\n                info.registrar = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrant:\") {\n                info.organization = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrant Name:\") {\n                info.organization = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrant Organization:\") {\n                info.organization = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrant Street:\") {\n                info.address = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrant City:\") {\n                info.city = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrant Country:\") {\n                info.country = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrant Country Code:\") {\n                info.country = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"Registrar WHOIS Server:\") {\n                info.refer = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"refer:\") {\n                info.refer = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"whois:\") {\n                info.refer = value.trim().to_string();\n            } else if let Some(value) = line.strip_prefix(\"domain:\") {\n                *whois_domain = value.trim().to_string();\n            }\n        }\n        info\n    }\n\n    pub async fn query_from_server(domain: &str, server: &str) -> Result<String, Box<dyn Error>> {\n        let stream = TcpStream::connect(server);\n        let stream_timeout =\n            tokio::time::timeout(tokio::time::Duration::from_secs(20), stream).await;\n        if let Err(_) = stream_timeout {\n            return Err(\"Connect whois server timeout.\".into());\n        }\n\n        let mut stream = stream_timeout.unwrap()?;\n        let query = format!(\"{}\\r\\n\", domain);\n        stream.write_all(query.as_bytes()).await?;\n        let mut output = String::new();\n        let read_timeout = tokio::time::timeout(\n            tokio::time::Duration::from_secs(10),\n            stream.read_to_string(&mut output),\n        )\n        .await;\n\n        if let Err(_) = read_timeout {\n            return Err(\"Read whois server timeout.\".into());\n        }\n        stream.shutdown().await?;\n        Ok(output)\n    }\n\n    pub fn add_server(&self, domain: &str, server: &str) {\n        let mut server_lists = self.server_lists.lock().unwrap();\n        server_lists.insert(domain.to_string().to_lowercase(), server.to_string());\n    }\n\n    pub fn get_server(&self, domain: &str) -> Option<String> {\n        let server_lists = self.server_lists.lock().unwrap();\n        if let Some(server) = server_lists.get(domain) {\n            return Some(server.clone());\n        }\n        None\n    }\n\n    pub fn get_server_by_domain(&self, domain: &str) -> String {\n        let mut domain_tmp = domain.to_string().to_lowercase();\n        loop {\n            let server = self.get_server(domain_tmp.as_str());\n            if let Some(s) = server {\n                return s;\n            }\n\n            if let Some(index) = domain_tmp.find('.') {\n                domain_tmp = domain_tmp[index + 1..].to_string();\n            } else {\n                return self.root_server.clone();\n            }\n        }\n    }\n\n    pub async fn query(&self, domain: &str) -> Result<WhoIsInfo, Box<dyn Error>> {\n        let parts: Vec<&str> = domain.split('.').collect();\n        let mut domainlist = Vec::new();\n        if parts.len() < 2 {\n            domainlist.push(domain.to_string());\n        } else if parts.len() < 3 {\n            let v = format!(\"{}.{}\", parts[parts.len() - 2], parts[parts.len() - 1]);\n            domainlist.push(v);\n        } else {\n            let v = format!(\"{}.{}\", parts[parts.len() - 2], parts[parts.len() - 1]);\n            domainlist.push(v);\n            let v = format!(\n                \"{}.{}.{}\",\n                parts[parts.len() - 3],\n                parts[parts.len() - 2],\n                parts[parts.len() - 1]\n            );\n            domainlist.push(v);\n        };\n\n        let mut last_whoisinfo = WhoIsInfo {\n            domain: domain.to_string(),\n            organization: String::new(),\n            address: String::new(),\n            city: String::new(),\n            country: String::new(),\n            registrar: String::new(),\n            refer: String::new(),\n        };\n\n        for main_domain in domainlist {\n            let mut base_domain = main_domain.as_str();\n            if let Some(v) = main_domain.find(\".\") {\n                if v > 0 {\n                    base_domain = main_domain.split_at(v + 1).1;\n                }\n            }\n\n            let mut server = self.get_server_by_domain(base_domain);\n            loop {\n                let mut whois_domain = String::new();\n\n                if server.is_empty() {\n                    break;\n                }\n                let connect_host = format!(\"{}:43\", server);\n                dns_log!(\n                    LogLevel::DEBUG,\n                    \"Query whois server: {}, domain:{}\",\n                    connect_host,\n                    main_domain\n                );\n                let whois = WhoIs::query_from_server(main_domain.as_str(), &connect_host).await;\n                if let Err(e) = whois {\n                    if last_whoisinfo.registrar.len() > 0 {\n                        return Ok(last_whoisinfo);\n                    }\n                    return Err(e);\n                }\n\n                let whois = whois.unwrap();\n                let mut info = self.parser_whois(&whois, &mut whois_domain);\n\n                info.domain = domain.to_string();\n                if info.organization.len() > 0 {\n                    return Ok(info);\n                }\n\n                if info.registrar.len() > 0 {\n                    last_whoisinfo = info.clone();\n                }\n                if server == info.refer {\n                    break;\n                }\n\n                server = info.refer;\n                if whois_domain.len() > 0 {\n                    self.add_server(whois_domain.as_str(), server.as_str());\n                }\n            }\n        }\n\n        if last_whoisinfo.registrar.len() > 0 {\n            return Ok(last_whoisinfo);\n        }\n\n        Err(\"Cannot find whois.\".into())\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/tests/common/client.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse http::uri;\nuse reqwest;\nuse smartdns_ui::http_api_msg;\nuse std::{error::Error, net::TcpStream};\nuse tungstenite::*;\n\npub struct TestClient {\n    url: String,\n    token: Option<http_api_msg::TokenResponse>,\n    client: reqwest::blocking::Client,\n    no_auth_header: bool,\n}\n\nimpl TestClient {\n    pub fn new(url: &String) -> Self {\n        let client = TestClient {\n            url: url.clone(),\n            token: None,\n            client: reqwest::blocking::ClientBuilder::new()\n                .danger_accept_invalid_certs(true)\n                .build()\n                .unwrap(),\n            no_auth_header: false,\n        };\n\n        client\n    }\n\n    #[allow(dead_code)]\n    pub fn set_with_auth_header(&mut self, with_auth_header: bool) {\n        self.no_auth_header = with_auth_header;\n    }\n\n    #[allow(dead_code)]\n    pub fn login(&mut self, username: &str, password: &str) -> Result<String, Box<dyn Error>> {\n        let url = self.url.clone() + \"/api/auth/login\";\n        let body = http_api_msg::api_msg_gen_auth_login(&http_api_msg::AuthUser {\n            username: username.to_string(),\n            password: password.to_string(),\n        });\n        let resp = self.client.post(&url).body(body).send()?;\n\n        if resp.status().as_u16() != 200 {\n            return Err(resp.text()?.into());\n        }\n\n        let text = resp.text()?;\n\n        let token = http_api_msg::api_msg_parse_auth_token(&text)?;\n        self.token = Some(token);\n        Ok(text)\n    }\n\n    #[allow(dead_code)]\n    pub fn logout(&mut self) -> Result<String, Box<dyn Error>> {\n        let url = self.url.clone() + \"/api/auth/logout\";\n        let resp = self.client.post(&url).send()?;\n        let text = resp.text()?;\n        self.token = None;\n        Ok(text)\n    }\n\n    fn prep_request(\n        &self,\n        method: reqwest::Method,\n        path: &str,\n    ) -> Result<reqwest::blocking::RequestBuilder, Box<dyn Error>> {\n        let url = self.url.clone() + path;\n        let mut req = self.client.request(method, url);\n        if let Some(token) = &self.token {\n            if self.token.is_some() && !self.no_auth_header {\n                req = req.header(\"Authorization\", format!(\"Bearer {}\", token.token));\n            }\n        }\n        Ok(req)\n    }\n\n    pub fn get(&self, path: &str) -> Result<(i32, String), Box<dyn Error>> {\n        let req = self.prep_request(reqwest::Method::GET, path)?;\n        let resp = req.send()?;\n        let status = resp.status().as_u16();\n        let text = resp.text()?;\n        Ok((status as i32, text))\n    }\n\n    #[allow(dead_code)]\n    pub fn delete(&self, path: &str) -> Result<(i32, String), Box<dyn Error>> {\n        let req = self.prep_request(reqwest::Method::DELETE, path)?;\n        let resp = req.send()?;\n        let status = resp.status().as_u16();\n        let text = resp.text()?;\n        Ok((status as i32, text))\n    }\n\n    #[allow(dead_code)]\n    pub fn put(&self, path: &str, body: &str) -> Result<(i32, String), Box<dyn Error>> {\n        let req = self.prep_request(reqwest::Method::PUT, path)?;\n        let resp = req.body(body.to_string()).send()?;\n        let status = resp.status().as_u16();\n        let text = resp.text()?;\n        Ok((status as i32, text))\n    }\n\n    #[allow(dead_code)]\n    pub fn post(&self, path: &str, body: &str) -> Result<(i32, String), Box<dyn Error>> {\n        let req = self.prep_request(reqwest::Method::POST, path)?;\n        let resp = req.body(body.to_string()).send()?;\n        let status = resp.status().as_u16();\n        let text = resp.text()?;\n        Ok((status as i32, text))\n    }\n\n    #[allow(dead_code)]\n    pub fn websocket(\n        &self,\n        path: &str,\n    ) -> Result<WebSocket<stream::MaybeTlsStream<TcpStream>>, Box<dyn Error>> {\n        let url = self.url.clone() + path;\n        let uri: http::Uri = url.parse()?;\n        let mut parts = uri.into_parts();\n        parts.scheme = Some(\"ws\".parse().unwrap());\n        let uri = uri::Uri::from_parts(parts).unwrap();\n        let mut request_builder = tungstenite::ClientRequestBuilder::new(uri);\n\n        if let Some(token) = &self.token {\n            if self.token.is_some() {\n                request_builder =\n                    request_builder.with_header(\"Authorization\", format!(\"Bearer {}\", token.token));\n            }\n        }\n\n        request_builder = request_builder\n            .with_header(\"Upgrade\", \"websocket\")\n            .with_header(\"Sec-WebSocket-Version\", \"13\")\n            .with_header(\"Connection\", \"keep-alive, Upgrade\");\n\n        let ret = tungstenite::connect(request_builder);\n        if let Err(e) = ret {\n            println!(\"websocket connect error: {:?}\", e.to_string());\n            return Err(Box::new(e));\n        }\n        let (socket, _) = ret.unwrap();\n        Ok(socket)\n    }\n}\n\nimpl Drop for TestClient {\n    fn drop(&mut self) {}\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/tests/common/mod.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\nmod server;\nmod client;\n\npub use server::*;\npub use client::*;"
  },
  {
    "path": "plugin/smartdns-ui/tests/common/server.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nuse smartdns_ui::data_server::DataServer;\nuse smartdns_ui::db::*;\nuse smartdns_ui::dns_log;\nuse smartdns_ui::plugin::*;\nuse smartdns_ui::smartdns::*;\nuse std::io::Write;\nuse std::sync::Arc;\nuse tempfile::TempDir;\n\nstatic INSTANCE_LOCK: std::sync::RwLock<()> = std::sync::RwLock::new(());\n\npub struct InstanceLockGuard<'a> {\n    _read_guard: Option<std::sync::RwLockReadGuard<'a, ()>>,\n    _write_guard: Option<std::sync::RwLockWriteGuard<'a, ()>>,\n}\n\nimpl<'a> InstanceLockGuard<'a> {\n    pub fn new_read_guard() -> Self {\n        Self {\n            _read_guard: Some(INSTANCE_LOCK.read().unwrap()),\n            _write_guard: None,\n        }\n    }\n\n    pub fn new_write_guard() -> Self {\n        Self {\n            _read_guard: None,\n            _write_guard: Some(INSTANCE_LOCK.write().unwrap()),\n        }\n    }\n}\n\npub struct TestDnsRequest {\n    pub domain: String,\n    pub group_name: String,\n    pub qtype: u32,\n    pub qclass: i32,\n    pub id: u16,\n    pub rcode: u16,\n    pub query_time: i32,\n    pub query_timestamp: u64,\n    pub ping_time: f64,\n    pub is_blocked: bool,\n    pub is_cached: bool,\n    pub remote_mac: [u8; 6],\n    pub remote_addr: String,\n    pub local_addr: String,\n    pub prefetch_request: bool,\n    pub dualstack_request: bool,\n    pub drop_callback: Option<Box<dyn Fn() + Send + Sync>>,\n}\n\n#[allow(dead_code)]\nimpl TestDnsRequest {\n    pub fn new() -> Self {\n        TestDnsRequest {\n            domain: \"\".to_string(),\n            group_name: \"default\".to_string(),\n            qtype: 1,\n            qclass: 1,\n            id: 0,\n            rcode: 2,\n            query_time: 0,\n            query_timestamp: get_utc_time_ms(),\n            ping_time: -0.1 as f64,\n            is_blocked: false,\n            is_cached: false,\n            remote_mac: [0; 6],\n            remote_addr: \"127.0.0.1\".to_string(),\n            local_addr: \"127.0.0.1\".to_string(),\n            prefetch_request: false,\n            dualstack_request: false,\n            drop_callback: None,\n        }\n    }\n}\n\n#[allow(dead_code)]\nimpl DnsRequest for TestDnsRequest {\n    fn get_group_name(&self) -> String {\n        self.group_name.clone()\n    }\n\n    fn get_domain(&self) -> String {\n        self.domain.clone()\n    }\n\n    fn get_qtype(&self) -> u32 {\n        self.qtype\n    }\n\n    fn get_qclass(&self) -> i32 {\n        self.qclass\n    }\n\n    fn get_id(&self) -> u16 {\n        self.id\n    }\n\n    fn get_rcode(&self) -> u16 {\n        self.rcode\n    }\n\n    fn get_query_time(&self) -> i32 {\n        self.query_time\n    }\n\n    fn get_query_timestamp(&self) -> u64 {\n        self.query_timestamp\n    }\n\n    fn get_ping_time(&self) -> f64 {\n        self.ping_time\n    }\n\n    fn get_is_blocked(&self) -> bool {\n        self.is_blocked\n    }\n\n    fn get_is_cached(&self) -> bool {\n        self.is_cached\n    }\n\n    fn get_remote_mac(&self) -> [u8; 6] {\n        self.remote_mac\n    }\n\n    fn get_remote_addr(&self) -> String {\n        self.remote_addr.clone()\n    }\n\n    fn get_local_addr(&self) -> String {\n        self.local_addr.clone()\n    }\n\n    fn is_prefetch_request(&self) -> bool {\n        self.prefetch_request\n    }\n\n    fn is_dualstack_request(&self) -> bool {\n        self.dualstack_request\n    }\n}\n\nimpl Drop for TestDnsRequest {\n    fn drop(&mut self) {\n        if let Some(f) = &self.drop_callback {\n            f();\n        }\n    }\n}\n\nunsafe impl Send for TestDnsRequest {}\nunsafe impl Sync for TestDnsRequest {}\n\n#[allow(dead_code)]\nstruct TestSmartDnsConfigItem {\n    pub key: String,\n    pub value: String,\n}\n\npub struct TestSmartDnsServer {\n    confs: Vec<TestSmartDnsConfigItem>,\n    is_started: bool,\n    workdir: String,\n    thread: Option<std::thread::JoinHandle<()>>,\n}\n\nimpl TestSmartDnsServer {\n    pub fn new() -> Self {\n        let mut server = TestSmartDnsServer {\n            confs: Vec::new(),\n            is_started: false,\n            workdir: \"/tmp/smartdns-test.conf\".to_string(),\n            thread: None,\n        };\n\n        server.add_conf(\"bind\", \":66603\");\n        server.add_conf(\"log-level\", \"debug\");\n        server.add_conf(\"log-num\", \"0\");\n        server.add_conf(\"cache-persist\", \"no\");\n\n        server\n    }\n\n    pub fn set_workdir(&mut self, workdir: &str) {\n        self.workdir = workdir.to_string();\n    }\n\n    pub fn add_conf(&mut self, key: &str, value: &str) {\n        self.confs.push(TestSmartDnsConfigItem {\n            key: key.to_string(),\n            value: value.to_string(),\n        });\n    }\n\n    fn gen_conf_file(&self) -> std::io::Result<String> {\n        let file = self.workdir.clone() + \"/smartdns.conf\";\n        let mut f = std::fs::File::create(&file)?;\n        for conf in self.confs.iter() {\n            f.write_all(format!(\"{} {}\\n\", conf.key, conf.value).as_bytes())?;\n        }\n        Ok(file)\n    }\n\n    pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {\n        let conf_file = self.gen_conf_file()?;\n        let t = std::thread::spawn(move || {\n            dns_log!(LogLevel::ERROR, \"smartdns server run start...\");\n            smartdns_ui::smartdns::smartdns_server_run(&conf_file).unwrap();\n            dns_log!(LogLevel::ERROR, \"smartdns server run exit...\");\n        });\n        self.thread = Some(t);\n        self.is_started = true;\n        dns_log!(LogLevel::ERROR, \"smartdns_server_run\");\n        Ok(())\n    }\n\n    pub fn stop(&mut self) {\n        if !self.is_started {\n            return;\n        }\n        self.is_started = false;\n        smartdns_ui::smartdns::smartdns_server_stop();\n        if self.thread.is_none() {\n            return;\n        }\n        let _ = self.thread.take().unwrap().join();\n    }\n}\n\nimpl Drop for TestSmartDnsServer {\n    fn drop(&mut self) {\n        self.stop();\n    }\n}\n\npub struct TestServer {\n    dns_server: TestSmartDnsServer,\n    dns_server_enable: bool,\n    plugin: Arc<SmartdnsPlugin>,\n    args: Vec<String>,\n    workdir: String,\n    temp_dir: TempDir,\n    www_root: String,\n    is_started: bool,\n    ip: String,\n    is_https: bool,\n    log_level: LogLevel,\n    old_log_level: LogLevel,\n    one_instance: bool,\n    instance_lock_guard: Option<InstanceLockGuard<'static>>,\n}\n\nimpl TestServer {\n    pub fn new() -> Self {\n        let mut server = TestServer {\n            dns_server: TestSmartDnsServer::new(),\n            dns_server_enable: false,\n            plugin: SmartdnsPlugin::new(),\n            args: Vec::new(),\n            workdir: String::new(),\n            temp_dir: TempDir::with_prefix(\"smartdns-ui-\").unwrap(),\n            www_root: String::new(),\n            is_started: false,\n            ip: \"http://127.0.0.1:0\".to_string(),\n            is_https: false,\n            log_level: LogLevel::INFO,\n            old_log_level: LogLevel::INFO,\n            one_instance: false,\n            instance_lock_guard: None,\n        };\n\n        server.workdir = server.temp_dir.path().to_str().unwrap().to_string();\n        server.dns_server.set_workdir(&server.workdir);\n        server.get_data_server().set_recv_in_batch(false);\n        server\n    }\n\n    fn setup_default_args(&mut self) {\n        self.args.insert(0, \"--ip\".to_string());\n        self.args.insert(1, self.ip.clone());\n\n        self.args.insert(0, \"--data-dir\".to_string());\n        self.args.insert(1, self.workdir.clone());\n\n        self.args.insert(0, \"--www-root\".to_string());\n        self.www_root = self.workdir.clone() + \"/www\";\n        self.args.insert(1, self.www_root.clone());\n\n        self.args.insert(0, \"smartdns-ui\".to_string());\n        dns_log!(LogLevel::INFO, \"workdir: {}\", self.workdir);\n    }\n\n    #[allow(dead_code)]\n    pub fn get_url(&self, path: &str) -> String {\n        self.ip.clone() + path\n    }\n\n    pub fn get_host(&self) -> String {\n        self.ip.clone()\n    }\n\n    #[allow(dead_code)]\n    pub fn get_www_root(&self) -> &String {\n        &self.www_root\n    }\n\n    fn create_workdir(&self) -> std::io::Result<()> {\n        std::fs::create_dir_all(&self.workdir)?;\n        std::fs::create_dir_all(&self.www_root)?;\n        Ok(())\n    }\n\n    fn remove_workdir(&self) -> std::io::Result<()> {\n        let r = std::fs::remove_dir_all(&self.workdir);\n        return r;\n    }\n\n    #[allow(dead_code)]\n    pub fn add_mock_server_conf(&mut self, key: &str, value: &str) {\n        self.dns_server.add_conf(key, value);\n    }\n\n    #[allow(dead_code)]\n    pub fn enable_mock_server(&mut self) {\n        self.dns_server_enable = true;\n        self.set_one_instance(true);\n    }\n\n    #[allow(dead_code)]\n    pub fn add_args(&mut self, args: Vec<String>) {\n        for arg in args.iter() {\n            self.args.push(arg.clone());\n        }\n    }\n\n    #[allow(dead_code)]\n    pub fn send_test_dnsrequest(\n        &mut self,\n        mut request: TestDnsRequest,\n    ) -> Result<(), Box<dyn std::error::Error>> {\n        let batch_mode = self.get_data_server().get_recv_in_batch();\n        let (tx, rx) = std::sync::mpsc::channel();\n        let request_drop_callback = move || {\n            tx.send(()).unwrap();\n        };\n\n        if batch_mode == false {\n            request.drop_callback = Some(Box::new(request_drop_callback));\n        }\n\n        let ret = self.plugin.query_complete(Box::new(request));\n        if let Err(e) = ret {\n            dns_log!(LogLevel::ERROR, \"send_test_dnsrequest error: {:?}\", e);\n            return Err(e);\n        }\n\n        if batch_mode == false {\n            rx.recv().unwrap();\n        }\n        Ok(())\n    }\n\n    #[allow(dead_code)]\n    pub fn new_mock_domain_record(&self) -> DomainData {\n        DomainData {\n            id: 0,\n            timestamp: smartdns_ui::smartdns::get_utc_time_ms(),\n            domain: \"example.com\".to_string(),\n            domain_type: 1,\n            client: \"127.0.0.1\".to_string(),\n            domain_group: \"default\".to_string(),\n            reply_code: 0,\n            query_time: 0,\n            ping_time: -0.1 as f64,\n            is_blocked: false,\n            is_cached: false,\n        }\n    }\n\n    #[allow(dead_code)]\n    pub fn get_data_server(&self) -> Arc<DataServer> {\n        self.plugin.get_data_server()\n    }\n\n    #[allow(dead_code)]\n    pub fn add_domain_record(\n        &mut self,\n        record: &DomainData,\n    ) -> Result<(), Box<dyn std::error::Error>> {\n        self.plugin.get_data_server().insert_domain(record)\n    }\n\n    pub fn set_log_level(&mut self, level: LogLevel) {\n        self.log_level = level;\n    }\n\n    fn init_server(&mut self) -> Result<(), Box<dyn std::error::Error>> {\n        self.create_workdir()?;\n\n        self.old_log_level = smartdns_ui::smartdns::dns_log_get_level();\n        smartdns_ui::smartdns::dns_log_set_level(self.log_level);\n\n        Ok(())\n    }\n\n    #[allow(dead_code)]\n    pub fn set_https(&mut self, enable: bool) {\n        self.is_https = enable;\n        if enable {\n            self.ip = \"https://127.0.0.1:0\".to_string();\n        } else {\n            self.ip = \"http://127.0.0.1:0\".to_string();\n        }\n    }\n\n    pub fn set_one_instance(&mut self, one_instance: bool) {\n        self.one_instance = one_instance;\n    }\n\n    pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {\n        if self.one_instance {\n            self.instance_lock_guard = Some(InstanceLockGuard::new_write_guard());\n            if self.dns_server_enable {\n                let ret = self.dns_server.start();\n                if let Err(e) = ret {\n                    dns_log!(LogLevel::ERROR, \"start dns server failed: {:?}\", e);\n                    return Err(e);\n                }\n            }\n        } else {\n            self.instance_lock_guard = Some(InstanceLockGuard::new_read_guard());\n        }\n\n        self.setup_default_args();\n\n        dns_log!(LogLevel::INFO, \"TestServer start\");\n        let ret = self.init_server();\n        if let Err(e) = ret {\n            dns_log!(LogLevel::ERROR, \"init server failed: {:?}\", e);\n            return Err(e);\n        }\n\n        let result = self.plugin.start(&self.args);\n        if let Err(e) = result {\n            dns_log!(LogLevel::ERROR, \"start error: {:?}\", e);\n            return Err(e);\n        }\n\n        let addr = self.plugin.get_http_server().get_local_addr();\n        if addr.is_none() {\n            return Err(Box::new(std::io::Error::new(\n                std::io::ErrorKind::Other,\n                \"get local addr failed\",\n            )));\n        }\n\n        let addr = addr.unwrap();\n        if self.is_https {\n            self.ip = format!(\"https://{}:{}\", addr.ip(), addr.port());\n        } else {\n            self.ip = format!(\"http://{}:{}\", addr.ip(), addr.port());\n        }\n        self.is_started = true;\n        Ok(())\n    }\n\n    pub fn stop(&mut self) {\n        if !self.is_started {\n            return;\n        }\n        dns_log!(LogLevel::INFO, \"TestServer stop\");\n        self.plugin.stop();\n        self.is_started = false;\n        self.one_instance = false;\n        smartdns_ui::smartdns::dns_log_set_level(self.old_log_level);\n        self.dns_server.stop();\n        self.instance_lock_guard = None;\n    }\n}\n\nimpl Drop for TestServer {\n    fn drop(&mut self) {\n        self.stop();\n        let _ = self.remove_workdir();\n    }\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/tests/httpserver_test.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nmod common;\n\nuse smartdns_ui::smartdns::LogLevel;\nuse std::{fs::File, io::Write};\n\n#[test]\nfn test_http_server_indexhtml() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n    let www_root = server.get_www_root();\n\n    let mut index_html_file = File::create(www_root.clone() + \"/index.html\").unwrap();\n    let content = \"Hello, world!\";\n    index_html_file.write_all(content.as_bytes()).unwrap();\n\n    let client = common::TestClient::new(&server.get_host());\n    let c = client.get(\"/\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    assert_eq!(body, content);\n}\n\n#[test]\nfn test_http_server_somehtml() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n    let www_root = server.get_www_root();\n\n    let mut index_html_file = File::create(www_root.clone() + \"/index.html\").unwrap();\n    let content = \"Hello, world!\";\n    index_html_file.write_all(content.as_bytes()).unwrap();\n\n    let mut some_html_file = File::create(www_root.clone() + \"/some.html\").unwrap();\n    let some_content = \"Some index file!\";\n    some_html_file.write_all(some_content.as_bytes()).unwrap();\n\n    let client = common::TestClient::new(&server.get_host());\n    let c = client.get(\"/some.html\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    assert_eq!(body, some_content);\n}\n\n#[test]\nfn test_http_server_redirect_indexhtml() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n    let www_root = server.get_www_root();\n\n    let mut index_html_file = File::create(www_root.clone() + \"/index.html\").unwrap();\n    let content = \"Hello, world!\";\n    index_html_file.write_all(content.as_bytes()).unwrap();\n\n    let client = common::TestClient::new(&server.get_host());\n    let c = client.get(\"/some.html\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    assert_eq!(body, content);\n}\n\n#[test]\nfn test_http_server_404() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let client = common::TestClient::new(&server.get_host());\n    let c = client.get(\"/index.html\");\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 404);\n}\n"
  },
  {
    "path": "plugin/smartdns-ui/tests/restapi_test.rs",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nmod common;\n\nuse common::TestDnsRequest;\nuse nix::libc::c_char;\nuse smartdns_ui::{http_api_msg, http_jwt::JwtClaims, smartdns::LogLevel};\nuse std::ffi::CString;\n\n#[test]\nfn test_rest_api_login() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let body = res.unwrap();\n    println!(\"res: {}\", body);\n\n    let result = http_api_msg::api_msg_parse_auth_token(&body);\n    assert!(result.is_ok());\n    let token = result.unwrap();\n    assert!(!token.token.is_empty());\n    let calims = jsonwebtoken::dangerous::insecure_decode::<JwtClaims>(&token.token);\n    println!(\"calims: {:?}\", calims);\n    assert_eq!(token.expires_in, \"600\");\n    assert!(calims.is_ok());\n    let calims = calims.unwrap();\n    let calims = calims.claims;\n    assert_eq!(calims.user, \"admin\");\n}\n\n#[test]\nfn test_rest_api_logout() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    client.set_with_auth_header(false);\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.get(\"/api/cache/count\");\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 200);\n\n    let ret = client.logout();\n    assert!(ret.is_ok());\n\n    let c = client.get(\"/api/cache/count\");\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 401);\n}\n\n#[test]\nfn test_rest_api_login_incorrect() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"wrongpassword\");\n    assert!(!res.is_ok());\n    let body = res.err().unwrap().to_string();\n    println!(\"res: {}\", body);\n\n    let result = http_api_msg::api_msg_parse_error(&body);\n    assert!(result.is_ok());\n    assert_eq!(result.unwrap(), \"Incorrect username or password.\");\n}\n\n#[test]\nfn test_rest_api_change_password() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let password_msg =\n        http_api_msg::api_msg_gen_auth_password_change(\"wrong_oldpassword\", \"newpassword\");\n    let c = client.put(\"/api/auth/password\", password_msg.as_str());\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 403);\n\n    let password_msg = http_api_msg::api_msg_gen_auth_password_change(\"password\", \"newpassword\");\n    let c = client.put(\"/api/auth/password\", password_msg.as_str());\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 204);\n\n    let res = client.login(\"admin\", \"password\");\n    assert!(!res.is_ok());\n}\n\n#[test]\nfn test_rest_api_cache_count() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let c = client.get(\"/api/cache/count\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let count = http_api_msg::api_msg_parse_cache_number(&body);\n    assert!(count.is_ok());\n    assert_eq!(count.unwrap(), 0);\n}\n\n#[test]\nfn test_rest_api_auth_refresh() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let c = client.post(\"/api/auth/refresh\", \"\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let token = http_api_msg::api_msg_parse_auth_token(&body);\n    assert!(token.is_ok());\n    let token = token.unwrap();\n    assert!(!token.token.is_empty());\n    assert_eq!(token.expires_in, \"600\");\n    println!(\"token: {:?}\", token);\n}\n\n#[test]\nfn test_rest_api_auth_check() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let c = client.get(\"/api/auth/check\");\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 401);\n\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let c = client.get(\"/api/auth/check\");\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 200);\n}\n\n#[test]\nfn test_rest_api_no_permission() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let client = common::TestClient::new(&server.get_host());\n    let c = client.get(\"/api/cache/count\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 401);\n    println!(\"body: {}\", body);\n    let error_msg = http_api_msg::api_msg_parse_error(&body);\n    assert!(error_msg.is_ok());\n    assert_eq!(error_msg.unwrap(), \"Please login.\");\n}\n\n#[test]\nfn test_rest_api_404() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let c = client.post(\"/api/404\", \"\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 404);\n    let error_msg = http_api_msg::api_msg_parse_error(&body);\n    assert!(error_msg.is_ok());\n    assert_eq!(error_msg.unwrap(), \"API not found.\");\n}\n\n#[test]\nfn test_rest_api_log_stream() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let socket = client.websocket(\"/api/log/stream\");\n    assert!(socket.is_ok());\n    let mut socket = socket.unwrap();\n\n    _ = socket.send(tungstenite::Message::Text(\"aaaa\".to_string()));\n    _ = socket.close(None);\n}\n\n#[test]\nfn test_rest_api_log_level() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    server.set_one_instance(true);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.get(\"/api/log/level\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let log_level = http_api_msg::api_msg_parse_loglevel(&body);\n    assert!(log_level.is_ok());\n    assert_eq!(log_level.unwrap(), LogLevel::DEBUG);\n\n    let level_msg = http_api_msg::api_msg_gen_loglevel(LogLevel::ERROR);\n    let c = client.put(\"/api/log/level\", level_msg.as_str());\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 204);\n    println!(\"body: {}\", body);\n\n    assert_eq!(smartdns_ui::smartdns::dns_log_get_level(), LogLevel::ERROR);\n}\n\n#[test]\nfn test_rest_api_get_domain() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    for i in 0..1024 {\n        let mut request = TestDnsRequest::new();\n        request.domain = format!(\"{}.com\", i);\n        request.id = i as u16;\n        assert!(server.send_test_dnsrequest(request).is_ok());\n    }\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.get(\"/api/domain/count\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let count = http_api_msg::api_msg_parse_count(&body);\n    assert!(count.is_ok());\n    assert_eq!(count.unwrap(), 1024);\n\n    let c = client.get(\"/api/domain?page_num=11&page_size=10&order=asc\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let result = http_api_msg::api_msg_parse_domain_list(&body);\n    assert!(result.is_ok());\n    let result = result.unwrap();\n    assert_eq!(result.len(), 10);\n    assert_eq!(result[0].id, 101);\n    assert_eq!(result[0].domain, \"100.com\");\n}\n\n#[test]\nfn test_rest_api_audit_log_stream() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let socket = client.websocket(\"/api/log/audit/stream\");\n    assert!(socket.is_ok());\n    let mut socket = socket.unwrap();\n\n    _ = socket.send(tungstenite::Message::Text(\"aaaa\".to_string()));\n    _ = socket.close(None);\n}\n\n\n#[test]\nfn test_rest_api_get_by_id() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    for i in 0..1024 {\n        let mut request = TestDnsRequest::new();\n        request.domain = format!(\"{}.com\", i);\n        request.id = i as u16;\n        assert!(server.send_test_dnsrequest(request).is_ok());\n    }\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.get(\"/api/domain/1000\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let result = http_api_msg::api_msg_parse_domain(&body);\n    assert!(result.is_ok());\n    let result = result.unwrap();\n    assert_eq!(result.id, 1000);\n    assert_eq!(result.domain, \"999.com\");\n}\n\n#[test]\nfn test_rest_api_delete_domain_by_id() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    for i in 0..1024 {\n        let mut request = TestDnsRequest::new();\n        request.domain = format!(\"{}.com\", i);\n        request.id = i as u16;\n        assert!(server.send_test_dnsrequest(request).is_ok());\n    }\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.delete(\"/api/domain/1000\");\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 204);\n\n    let c = client.get(\"/api/domain/1000\");\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 404);\n\n    let c = client.get(\"/api/domain/count\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let count = http_api_msg::api_msg_parse_count(&body);\n    assert!(count.is_ok());\n    assert_eq!(count.unwrap(), 1023);\n}\n\n#[test]\nfn test_rest_api_server_version() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let client = common::TestClient::new(&server.get_host());\n\n    let c = client.get(\"/api/server/version\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let version = http_api_msg::api_msg_parse_version(&body);\n    assert!(version.is_ok());\n    let version = version.unwrap();\n    assert_eq!(version.0, smartdns_ui::smartdns::smartdns_version());\n    if env!(\"GIT_VERSION\").is_empty() {\n        assert_eq!(version.1, env!(\"CARGO_PKG_VERSION\"));\n        return;\n    }\n    let check_version = std::format!(\"{} ({})\", env!(\"CARGO_PKG_VERSION\"), env!(\"GIT_VERSION\"));\n    assert_eq!(version.1, check_version);\n}\n\n#[test]\nfn test_rest_api_https_server() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    server.enable_mock_server();\n    server.set_https(true);\n    assert!(server.start().is_ok());\n\n    let client = common::TestClient::new(&server.get_host());\n\n    let c = client.get(\"/api/server/version\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let version = http_api_msg::api_msg_parse_version(&body);\n    assert!(version.is_ok());\n    let version = version.unwrap();\n    assert_eq!(version.0, smartdns_ui::smartdns::smartdns_version());\n    if env!(\"GIT_VERSION\").is_empty() {\n        assert_eq!(version.1, env!(\"CARGO_PKG_VERSION\"));\n        return;\n    }\n    let check_version = std::format!(\"{} ({})\", env!(\"CARGO_PKG_VERSION\"), env!(\"GIT_VERSION\"));\n    assert_eq!(version.1, check_version);\n}\n\n#[test]\nfn test_rest_api_settings() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.get(\"/api/config/settings\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let settings = http_api_msg::api_msg_parse_key_value(&body);\n    assert!(settings.is_ok());\n\n    let mut settings = std::collections::HashMap::new();\n    settings.insert(\"key1\".to_string(), \"value1\".to_string());\n    settings.insert(\"key2\".to_string(), \"value2\".to_string());\n    let body = http_api_msg::api_msg_gen_key_value(&settings);\n    let c = client.put(\"/api/config/settings\", body.as_str());\n    assert!(c.is_ok());\n    let (code, _) = c.unwrap();\n    assert_eq!(code, 204);\n\n    let c = client.get(\"/api/config/settings\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let settings = http_api_msg::api_msg_parse_key_value(&body);\n    assert!(settings.is_ok());\n    let settings = settings.unwrap();\n    assert_eq!(settings.len(), 7);\n    assert_eq!(settings[\"key1\"], \"value1\");\n}\n\n#[test]\nfn test_rest_api_get_client() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::INFO);\n    assert!(server.start().is_ok());\n\n    for i in 0..1024 {\n        let mut request = TestDnsRequest::new();\n        request.domain = format!(\"{}.com\", i);\n        request.remote_addr = format!(\"client-{}\", i);\n        request.remote_mac = [1, 2, 3, 4, 5, i as u8];\n        request.id = i as u16;\n        assert!(server.send_test_dnsrequest(request).is_ok());\n    }\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.get(\"/api/client?page_size=4096\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let list = http_api_msg::api_msg_parse_client_list(&body);\n    assert!(list.is_ok());\n    let list = list.unwrap();\n    assert_eq!(list.len(), 1024);\n}\n\n#[test]\nfn test_rest_api_stats_top() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    for i in 0..1024 {\n        let mut request = TestDnsRequest::new();\n        if i < 512 {\n            request.domain = format!(\"a.com\");\n            request.remote_addr = format!(\"192.168.1.1\");\n        } else if i < 512 + 256 + 128 {\n            request.domain = format!(\"b.com\");\n            request.remote_addr = format!(\"192.168.1.2\");\n        } else {\n            request.domain = format!(\"c.com\");\n            request.remote_addr = format!(\"192.168.1.3\");\n        }\n        assert!(server.send_test_dnsrequest(request).is_ok());\n    }\n\n    server.get_data_server().get_stat().refresh();\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.get(\"/api/stats/top/client\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let list = http_api_msg::api_msg_parse_top_client_list(&body);\n    assert!(list.is_ok());\n    let list = list.unwrap();\n    assert_eq!(list.len(), 3);\n    assert_eq!(list[0].client_ip, \"192.168.1.1\");\n    assert_eq!(list[0].count, 512);\n    assert_eq!(list[2].client_ip, \"192.168.1.3\");\n    assert_eq!(list[2].count, 128);\n\n    let c = client.get(\"/api/stats/top/domain\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let list = http_api_msg::api_msg_parse_top_domain_list(&body);\n    assert!(list.is_ok());\n    let list = list.unwrap();\n    assert_eq!(list.len(), 3);\n    assert_eq!(list[0].domain, \"a.com\");\n    assert_eq!(list[0].count, 512);\n    assert_eq!(list[2].domain, \"c.com\");\n    assert_eq!(list[2].count, 128);\n}\n\n#[test]\nfn test_rest_api_stats_overview() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    server.enable_mock_server();\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n    let server_name;\n\n    unsafe {\n        smartdns_ui::smartdns::smartdns_c::dns_stats\n            .avg_time\n            .avg_time = 22.0 as f32;\n        smartdns_ui::smartdns::smartdns_c::dns_stats\n            .request\n            .blocked_count = 10;\n        smartdns_ui::smartdns::smartdns_c::dns_stats.request.total = 15;\n\n        server_name = smartdns_ui::smartdns::smartdns_get_server_name();\n    }\n\n    let c = client.get(\"/api/stats/overview\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let overview = http_api_msg::api_msg_parse_stats_overview(&body);\n    assert!(overview.is_ok());\n    let overview = overview.unwrap();\n    assert_eq!(overview.db_size > 0, true);\n    assert_eq!(overview.server_name, server_name);\n}\n\n#[test]\nfn test_rest_api_stats_metrics() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    server.enable_mock_server();\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    unsafe {\n        smartdns_ui::smartdns::smartdns_c::dns_stats\n            .avg_time\n            .avg_time = 22.0 as f32;\n        smartdns_ui::smartdns::smartdns_c::dns_stats\n            .request\n            .blocked_count = 10;\n        smartdns_ui::smartdns::smartdns_c::dns_stats.request.total = 15;\n    }\n\n    for i in 0..1024 {\n        let mut request = TestDnsRequest::new();\n        request.domain = format!(\"{}.com\", i);\n        request.remote_addr = format!(\"client-{}\", i);\n        request.is_blocked = i % 2 == 0;\n        assert!(server.send_test_dnsrequest(request).is_ok());\n    }\n\n    let c = client.get(\"/api/stats/metrics\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let metrics = http_api_msg::api_msg_parse_metrics_data(&body);\n    assert!(metrics.is_ok());\n    let metrics = metrics.unwrap();\n    assert_eq!(metrics.avg_query_time, 22.0 as f64);\n    assert_eq!(metrics.cache_hit_rate, 0 as f64);\n    assert_eq!(metrics.total_query_count, 1024);\n    assert_eq!(metrics.block_query_count, 1024 / 2);\n}\n\n#[test]\nfn test_rest_api_get_hourly_query_count() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    assert!(server.start().is_ok());\n\n    for i in 0..1024 {\n        let mut request = TestDnsRequest::new();\n        request.domain = format!(\"{}.com\", i);\n        request.remote_addr = format!(\"client-{}\", i);\n        assert!(server.send_test_dnsrequest(request).is_ok());\n    }\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    let c = client.get(\"/api/stats/hourly-query-count\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let list = http_api_msg::api_msg_parse_hourly_query_count(&body);\n    assert!(list.is_ok());\n    let list = list.unwrap();\n    assert_eq!(list.hourly_query_count.len(), 1);\n    assert_eq!(list.hourly_query_count[0].query_count, 1024);\n}\n\n#[test]\nfn test_rest_api_server_status() {\n    let mut server = common::TestServer::new();\n    server.set_log_level(LogLevel::DEBUG);\n    server.enable_mock_server();\n    assert!(server.start().is_ok());\n\n    let mut client = common::TestClient::new(&server.get_host());\n    let res = client.login(\"admin\", \"password\");\n    assert!(res.is_ok());\n\n    unsafe {\n        let server_type = smartdns_ui::smartdns::smartdns_c::dns_server_type_t_DNS_SERVER_UDP;\n        let mut flags: smartdns_ui::smartdns::smartdns_c::client_dns_server_flags =\n            std::mem::zeroed();\n        let ip = CString::new(\"1.2.3.4\").expect(\"CString::new failed\");\n        let port = 3353;\n        smartdns_ui::smartdns::smartdns_c::dns_client_add_server(\n            ip.as_ptr() as *const c_char,\n            port,\n            server_type,\n            &mut flags,\n        );\n    }\n\n    let c = client.get(\"/api/upstream-server\");\n    assert!(c.is_ok());\n    let (code, body) = c.unwrap();\n    assert_eq!(code, 200);\n    let server_list = http_api_msg::api_msg_parse_upstream_server_list(&body);\n    assert!(server_list.is_ok());\n    let server_list = server_list.unwrap();\n    assert!(server_list.len() > 0);\n    let exists = server_list.iter().any(|server| server.ip == \"1.2.3.4\");\n    assert!(exists);\n}\n"
  },
  {
    "path": "src/.gitignore",
    "content": ".vscode\n.o\n.DS_Store\n.swp.\nsmartdns\n!smartdns/\n"
  },
  {
    "path": "src/Makefile",
    "content": "# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nBIN=smartdns\nSMARTDNS_LIB=libsmartdns.a\nSMARTDNS_TEST_LIB=libsmartdns-test.a\n# libs without main.o\nOBJS=$(filter-out main.o, $(patsubst %.c,%.o,$(wildcard *.c)) $(patsubst %.c,%.o,$(wildcard */*.c)))\n# libs without lib/%.o\nLINT_OBJS=$(filter-out lib/%.o, $(OBJS))\nTEST_OBJS=$(patsubst %.o,%_test.o,$(OBJS))\nMAIN_OBJ = main.o\n\nifeq ($(filter -j,$(MAKEFLAGS)),)\nMAKEFLAGS += -j$(shell nproc)\nendif\n\n# cflags\nifndef CFLAGS\n ifdef DEBUG\n  CFLAGS = -g -DDEBUG\n else\n  CFLAGS = -O2\n endif\n CFLAGS +=-Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing -funwind-tables -Wmissing-prototypes -Wshadow -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough\nendif\n\nHASH := \\#\nHAS_UNWIND := $(shell printf '$(HASH)include <unwind.h>\\nvoid main() { _Unwind_Backtrace(0, 0);}' | $(CC) -x c - -o /dev/null >/dev/null 2>&1 && echo -n 1 || echo -n 0)\nifeq ($(HAS_UNWIND), 1)\noverride CFLAGS += -DHAVE_UNWIND_BACKTRACE\nendif\n\noverride CFLAGS +=-Iinclude\noverride CFLAGS += -DBASE_FILE_NAME='\"$(notdir $<)\"'\noverride CFLAGS += $(EXTRA_CFLAGS)\n\nifdef VER\n override CFLAGS += -DSMARTDNS_VERION='\"$(VER)\"'\nendif\n\nHAS_GIT := $(shell command -v git 2> /dev/null)\nifndef NO_GIT_VER\n ifdef HAS_GIT\n  IS_GIT_REPO := $(shell git rev-parse --is-inside-work-tree 2>/dev/null)\n  ifdef IS_GIT_REPO\n   override COMMIT_VERION = $(shell git describe --tags --always --dirty)\n  endif\n endif\nendif\n\nifdef COMMIT_VERION\n override CFLAGS += -DCOMMIT_VERION='\"$(shell git describe --tags --always --dirty)\"'\nendif\n\nCXXFLAGS=-O2 -g -Wall -std=c++11 \noverride CXXFLAGS +=-Iinclude\n\nifeq ($(STATIC), yes)\nSTATIC = 1\nendif\n\nifdef STATIC\n override CFLAGS += -DBUILD_STATIC\n override LDFLAGS += -lssl -lcrypto -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -ldl -lm -static -rdynamic\nelse\n override LDFLAGS += -lssl -lcrypto -lpthread -ldl -lm -rdynamic\nendif\n\nUSE_ATOMIC := $(shell printf '$(HASH)include <stdint.h>\\nvoid main() { uint64_t value=0;__atomic_add_fetch(&value, 1, __ATOMIC_SEQ_CST);__atomic_load_n(&value,__ATOMIC_SEQ_CST);}' | $(CC) -x c - -o /dev/null >/dev/null 2>&1 && echo -n 1 || echo -n 0)\nifeq ($(USE_ATOMIC), 0)\n override CFLAGS += -DUSE_ATOMIC\n override LDFLAGS += -latomic \nendif\n\noverride LDFLAGS += $(EXTRA_LDFLAGS)\n\n.PHONY: all clean\n\nall: $(BIN)\n\n%_test.o: %.c\n\t$(CC) -DTEST $(CFLAGS) -c $< -o $@\n\n$(BIN) : $(MAIN_OBJ) $(SMARTDNS_LIB)\n\t$(CC) $^ -o $@ $(LDFLAGS)\n\n$(SMARTDNS_TEST_LIB): $(TEST_OBJS)\n\t$(AR) rcs $@ $^\n\n$(SMARTDNS_LIB): $(OBJS)\n\t$(AR) rcs $@ $^\n\nclang-tidy-parallel:\n\t@echo \"Running clang-tidy with $(shell nproc) parallel jobs...\"\n\t@printf '%s\\n' $(LINT_OBJS:.o=.c) | xargs -P $(shell nproc) -I {} clang-tidy {} -- $(CFLAGS)\n\nclang-tidy:\n\tclang-tidy -p=. $(LINT_OBJS:.o=.c) -- $(CFLAGS)\n\nclean:\n\t$(RM) $(OBJS) $(BIN) $(SMARTDNS_LIB) $(MAIN_OBJ) $(SMARTDNS_TEST_LIB) $(TEST_OBJS)\n\n# lint specific files\n# make lint-files FILES=\"dns.c proxy.c\"\nlint-files:\n\t@echo \"Specify FILES variable to lint specific files, e.g., make lint-files FILES='src/dns.c src/proxy.c'\"\n\tclang-tidy -p=. $(FILES) -- $(CFLAGS)\n\n"
  },
  {
    "path": "src/dns.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n#include \"smartdns/dns.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/tlog.h\"\n\n#include <netinet/in.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n#include <strings.h>\n\n#define QR_MASK 0x8000\n#define OPCODE_MASK 0x7800\n#define AA_MASK 0x0400\n#define TC_MASK 0x0200\n#define RD_MASK 0x0100\n#define RA_MASK 0x0080\n#define Z_MASK 0x0040\n#define AD_MASK 0x0020\n#define CD_MASK 0x0010\n#define RCODE_MASK 0x000F\n#define DNS_RR_END (0XFFFF)\n\n#define UNUSED(expr)                                                                                                   \\\n\tdo {                                                                                                               \\\n\t\t(void)(expr);                                                                                                  \\\n\t} while (0)\n\n#define member_size(type, member) sizeof(((type *)0)->member)\n\nstatic int is_aligned(void *ptr, int aligment)\n{\n\treturn ((uintptr_t)(ptr)) % aligment == 0;\n}\n\n/* read short and move pointer */\nstatic unsigned short _dns_read_short(unsigned char **buffer)\n{\n\tunsigned short value = 0;\n\n\tif (is_aligned(*buffer, 2)) {\n\t\tvalue = *((unsigned short *)(*buffer));\n\t} else {\n\t\tmemcpy(&value, *buffer, 2);\n\t}\n\n\t*buffer += 2;\n\treturn ntohs(value);\n}\n\n/* write char and move pointer */\nstatic __attribute__((unused)) void _dns_write_char(unsigned char **buffer, unsigned char value)\n{\n\t**buffer = value;\n\t*buffer += 1;\n}\n\n/* read char and move pointer */\nstatic unsigned char _dns_read_char(unsigned char **buffer)\n{\n\tunsigned char value = **buffer;\n\t*buffer += 1;\n\treturn value;\n}\n\n/* write short and move pointer */\nstatic void _dns_write_short(unsigned char **buffer, unsigned short value)\n{\n\tvalue = htons(value);\n\n\tif (is_aligned(*buffer, 2)) {\n\t\t*((unsigned short *)(*buffer)) = value;\n\t} else {\n\t\tmemcpy(*buffer, &value, 2);\n\t}\n\n\t*buffer += 2;\n}\n\n/* write short and move pointer */\nstatic void _dns_write_shortptr(unsigned char **buffer, void *ptrvalue)\n{\n\tunsigned short value;\n\n\tif ((uintptr_t)ptrvalue % 2 == 0) {\n\t\tvalue = *(unsigned short *)ptrvalue;\n\t} else {\n\t\tmemcpy(&value, ptrvalue, 2);\n\t}\n\n\tvalue = htons(value);\n\n\tif (is_aligned(*buffer, 2)) {\n\t\t*((unsigned short *)(*buffer)) = value;\n\t} else {\n\t\tmemcpy(*buffer, &value, 2);\n\t}\n\n\t*buffer += 2;\n}\n\n/* write int and move pointer */\nstatic void _dns_write_int(unsigned char **buffer, unsigned int value)\n{\n\tvalue = htonl(value);\n\tif (is_aligned(*buffer, 4)) {\n\t\t*((unsigned int *)(*buffer)) = value;\n\t} else {\n\t\tmemcpy(*buffer, &value, 4);\n\t}\n\t*buffer += 4;\n}\n\n/* write int and move pointer */\nstatic void _dns_write_intptr(unsigned char **buffer, void *ptrvalue)\n{\n\tunsigned int value;\n\n\tif ((uintptr_t)ptrvalue % 4 == 0) {\n\t\tvalue = *(unsigned int *)ptrvalue;\n\t} else {\n\t\tmemcpy(&value, ptrvalue, 4);\n\t}\n\n\tvalue = htonl(value);\n\tif (is_aligned(*buffer, 4)) {\n\t\t*((unsigned int *)(*buffer)) = value;\n\t} else {\n\t\tmemcpy(*buffer, &value, 4);\n\t}\n\t*buffer += 4;\n}\n\n/* read int and move pointer */\nstatic unsigned int _dns_read_int(unsigned char **buffer)\n{\n\tunsigned int value = 0;\n\n\tif (is_aligned(*buffer, 4)) {\n\t\tvalue = *((unsigned int *)(*buffer));\n\t} else {\n\t\tmemcpy(&value, *buffer, 4);\n\t}\n\n\t*buffer += 4;\n\treturn ntohl(value);\n}\n\nstatic inline int _dns_left_len(struct dns_context *context)\n{\n\treturn context->maxsize - (context->ptr - context->data);\n}\n\nstatic int _dns_get_domain_from_packet(unsigned char *packet, int packet_size, unsigned char **domain_ptr, char *output,\n\t\t\t\t\t\t\t\t\t   int size)\n{\n\tint output_len = 0;\n\tint copy_len = 0;\n\tint len = 0;\n\tunsigned char *ptr = *domain_ptr;\n\tint is_compressed = 0;\n\tint ptr_jump = 0;\n\n\t/*[len]string[len]string...[0]0 */\n\twhile (1) {\n\t\tif (ptr >= packet + packet_size || ptr < packet || output_len >= size - 1 || ptr_jump > 32) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tlen = *ptr;\n\t\tif (len == 0) {\n\t\t\t*output = 0;\n\t\t\tptr++;\n\t\t\tbreak;\n\t\t}\n\n\t\t/* compressed domain */\n\t\tif (len >= 0xC0) {\n\t\t\tif ((ptr + 2) > (packet + packet_size)) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\t/*\n\t\t\t0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n\t\t\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t\t\t| 1  1|                OFFSET                   |\n\t\t\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t\t\t*/\n\t\t\t/* read offset */\n\t\t\tlen = _dns_read_short(&ptr) & 0x3FFF;\n\t\t\tif (is_compressed == 0) {\n\t\t\t\t*domain_ptr = ptr;\n\t\t\t}\n\n\t\t\tptr = packet + len;\n\t\t\tif (ptr > packet + packet_size) {\n\t\t\t\ttlog(TLOG_DEBUG, \"length is not enough %u:%ld, %p, %p\", packet_size, (long)(ptr - packet), *domain_ptr,\n\t\t\t\t\t packet);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tis_compressed = 1;\n\t\t\tptr_jump++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tptr_jump = 0;\n\n\t\t/* change [len] to '.' */\n\t\tif (output_len > 0) {\n\t\t\t*output = '.';\n\t\t\toutput++;\n\t\t\toutput_len += 1;\n\t\t}\n\n\t\tif (ptr > packet + packet_size) {\n\t\t\ttlog(TLOG_DEBUG, \"length is not enough %u:%ld, %p, %p\", packet_size, (long)(ptr - packet), *domain_ptr,\n\t\t\t\t packet);\n\t\t\treturn -1;\n\t\t}\n\n\t\tptr++;\n\t\tif (output_len < size - 1) {\n\t\t\t/* copy sub string */\n\t\t\tcopy_len = (len < size - output_len) ? len : size - 1 - output_len;\n\t\t\tif ((ptr + copy_len) > (packet + packet_size)) {\n\t\t\t\ttlog(TLOG_DEBUG, \"length is not enough %u:%ld, %p, %p\", packet_size, (long)(ptr - packet), *domain_ptr,\n\t\t\t\t\t packet);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tmemcpy(output, ptr, copy_len);\n\t\t}\n\n\t\tptr += len;\n\t\toutput += len;\n\t\toutput_len += len;\n\t}\n\n\tif (is_compressed == 0) {\n\t\t*domain_ptr = ptr;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_decode_domain(struct dns_context *context, char *output, int size)\n{\n\treturn _dns_get_domain_from_packet(context->data, context->maxsize, &(context->ptr), output, size);\n}\n\nstatic unsigned int dict_hash(const char *s)\n{\n\tunsigned int hashval = 0;\n\tfor (hashval = 0; *s != '\\0'; s++) {\n\t\thashval = *s + 31 * hashval;\n\t}\n\treturn hashval;\n}\n\nstatic int _dns_add_domain_dict(struct dns_context *context, unsigned int hash, int pos)\n{\n\tstruct dns_packet_dict *dict = context->namedict;\n\n\tif (dict->dict_count >= DNS_PACKET_DICT_SIZE) {\n\t\treturn -1;\n\t}\n\n\tif (hash == 0) {\n\t\treturn -1;\n\t}\n\n\tif (pos >= context->maxsize) {\n\t\treturn -1;\n\t}\n\n\tint index = dict->dict_count;\n\tdict->names[index].hash = hash;\n\tdict->names[index].pos = pos;\n\tdict->dict_count++;\n\n\treturn 0;\n}\n\nstatic int _dns_get_domain_offset(struct dns_context *context, const char *domain)\n{\n\tint i = 0;\n\n\tchar domain_check[DNS_MAX_CNAME_LEN];\n\tstruct dns_packet_dict *dict = context->namedict;\n\n\tif (*domain == '\\0') {\n\t\treturn -1;\n\t}\n\n\tunsigned int hash = dict_hash(domain);\n\tfor (i = 0; i < dict->dict_count; i++) {\n\t\tif (dict->names[i].hash != hash) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tunsigned char *domain_check_ptr = dict->names[i].pos + context->data;\n\t\tif (_dns_get_domain_from_packet(context->data, context->maxsize, &domain_check_ptr, domain_check,\n\t\t\t\t\t\t\t\t\t\tDNS_MAX_CNAME_LEN) != 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\treturn dict->names[i].pos;\n\t}\n\n\t_dns_add_domain_dict(context, hash, context->ptr - 1 - context->data);\n\treturn -1;\n}\n\nstatic int _dns_encode_domain(struct dns_context *context, const char *domain)\n{\n\tint num = 0;\n\tint total_len = 0;\n\tunsigned char *ptr_num = context->ptr++;\n\tint dict_offset = 0;\n\n\tdict_offset = _dns_get_domain_offset(context, domain);\n\ttotal_len++;\n\n\t/*[len]string[len]string...[0]0 */\n\twhile (_dns_left_len(context) > 1 && *domain != 0) {\n\t\ttotal_len++;\n\t\tif (dict_offset >= 0) {\n\t\t\tint offset = 0xc000 | dict_offset;\n\t\t\tif (_dns_left_len(context) < 2) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t_dns_write_short(&ptr_num, offset);\n\t\t\tcontext->ptr++;\n\t\t\tptr_num = NULL;\n\t\t\treturn total_len;\n\t\t}\n\n\t\tif (*domain == '.') {\n\t\t\t*ptr_num = num;\n\t\t\tnum = 0;\n\t\t\tptr_num = context->ptr;\n\t\t\tdomain++;\n\t\t\tcontext->ptr++;\n\t\t\tdict_offset = _dns_get_domain_offset(context, domain);\n\t\t\tcontinue;\n\t\t}\n\t\t*context->ptr = *domain;\n\t\tnum++;\n\t\tcontext->ptr++;\n\t\tdomain++;\n\t}\n\n\tif (_dns_left_len(context) < 1) {\n\t\treturn -1;\n\t}\n\n\t*ptr_num = num;\n\n\tif (total_len > 1) {\n\t\t/* if domain is '\\0', [domain] is '\\0' */\n\t\t*(context->ptr) = 0;\n\t\ttotal_len++;\n\t\tcontext->ptr++;\n\t}\n\n\tif (_dns_left_len(context) <= 0) {\n\t\treturn -1;\n\t}\n\n\treturn total_len;\n}\n\n/* iterator get rrs begin */\nstruct dns_rrs *dns_get_rrs_start(struct dns_packet *packet, dns_rr_type type, int *count)\n{\n\tunsigned short start = 0;\n\tstruct dns_head *head = &packet->head;\n\n\t/* get rrs count by rrs type */\n\tswitch (type) {\n\tcase DNS_RRS_QD:\n\t\t*count = head->qdcount;\n\t\tstart = packet->questions;\n\t\tbreak;\n\tcase DNS_RRS_AN:\n\t\t*count = head->ancount;\n\t\tstart = packet->answers;\n\t\tbreak;\n\tcase DNS_RRS_NS:\n\t\t*count = head->nscount;\n\t\tstart = packet->nameservers;\n\t\tbreak;\n\tcase DNS_RRS_NR:\n\t\t*count = head->nrcount;\n\t\tstart = packet->additional;\n\t\tbreak;\n\tcase DNS_RRS_OPT:\n\t\t*count = packet->optcount;\n\t\tstart = packet->optional;\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t\tbreak;\n\t}\n\n\t/* if not resource record, return null */\n\tif (start == DNS_RR_END) {\n\t\treturn NULL;\n\t}\n\n\t/* return rrs data start address */\n\treturn (struct dns_rrs *)(packet->data + start);\n}\n\n/* iterator next rrs */\nstruct dns_rrs *dns_get_rrs_next(struct dns_packet *packet, struct dns_rrs *rrs)\n{\n\tif (rrs->next == DNS_RR_END) {\n\t\treturn NULL;\n\t}\n\n\treturn (struct dns_rrs *)(packet->data + rrs->next);\n}\n\nstatic void _dns_init_context_by_rrs(struct dns_rrs *rrs, struct dns_context *context)\n{\n\tcontext->packet = rrs->packet;\n\tcontext->data = rrs->packet->data;\n\tcontext->ptr = rrs->data;\n\tcontext->namedict = &rrs->packet->namedict;\n\tcontext->maxsize = rrs->data - rrs->packet->data + rrs->len;\n}\n\n/* iterator add rrs begin */\nstatic int _dns_add_rrs_start(struct dns_packet *packet, struct dns_context *context)\n{\n\tstruct dns_rrs *rrs = NULL;\n\tunsigned char *end = packet->data + packet->len;\n\n\tif ((packet->len + (int)sizeof(*rrs)) >= packet->size) {\n\t\treturn -1;\n\t}\n\trrs = (struct dns_rrs *)end;\n\n\tcontext->ptr = rrs->data;\n\tcontext->packet = packet;\n\tcontext->maxsize = packet->size - sizeof(*packet);\n\tcontext->data = packet->data;\n\tcontext->namedict = &packet->namedict;\n\n\treturn 0;\n}\n\n/* iterator add rrs end */\nstatic int _dns_rr_add_end(struct dns_packet *packet, int type, dns_type_t rtype, int len)\n{\n\tstruct dns_rrs *rrs = NULL;\n\tstruct dns_rrs *rrs_next = NULL;\n\tstruct dns_head *head = &packet->head;\n\tunsigned char *end = packet->data + packet->len;\n\tunsigned short *count = NULL;\n\tunsigned short *start = NULL;\n\n\trrs = (struct dns_rrs *)end;\n\tif (packet->len + len > packet->size - (int)sizeof(*packet) - (int)sizeof(*rrs)) {\n\t\treturn -1;\n\t}\n\n\tswitch (type) {\n\tcase DNS_RRS_QD:\n\t\tcount = &head->qdcount;\n\t\tstart = &packet->questions;\n\t\tbreak;\n\tcase DNS_RRS_AN:\n\t\tcount = &head->ancount;\n\t\tstart = &packet->answers;\n\t\tbreak;\n\tcase DNS_RRS_NS:\n\t\tcount = &head->nscount;\n\t\tstart = &packet->nameservers;\n\t\tbreak;\n\tcase DNS_RRS_NR:\n\t\tcount = &head->nrcount;\n\t\tstart = &packet->additional;\n\t\tbreak;\n\tcase DNS_RRS_OPT:\n\t\tcount = &packet->optcount;\n\t\tstart = &packet->optional;\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t\tbreak;\n\t}\n\n\t/* add data to end of dns_packet, and set previous rrs point to this rrs */\n\tif (*start != DNS_RR_END) {\n\t\trrs_next = (struct dns_rrs *)(packet->data + *start);\n\t\twhile (rrs_next->next != DNS_RR_END) {\n\t\t\trrs_next = (struct dns_rrs *)(packet->data + rrs_next->next);\n\t\t}\n\t\trrs_next->next = packet->len;\n\t} else {\n\t\t*start = packet->len;\n\t}\n\n\t/* update rrs head info */\n\trrs->packet = packet;\n\trrs->len = len;\n\trrs->type = rtype;\n\trrs->next = DNS_RR_END;\n\n\t/* update total data length */\n\t*count += 1;\n\tpacket->len += len + sizeof(*rrs);\n\treturn 0;\n}\n\nstatic int _dns_add_qr_head(struct dns_context *context, const char *domain, int qtype, int qclass)\n{\n\tint ret = _dns_encode_domain(context, domain);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < 4) {\n\t\treturn -1;\n\t}\n\n\t_dns_write_short(&context->ptr, qtype);\n\t_dns_write_short(&context->ptr, qclass);\n\n\treturn ret + 4;\n}\n\nstatic int _dns_get_qr_head(struct dns_context *context, char *domain, int maxsize, int *qtype, int *qclass)\n{\n\tint ret = 0;\n\n\tif (domain == NULL || context == NULL) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_decode_domain(context, domain, maxsize);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < 4) {\n\t\treturn -1;\n\t}\n\n\t*qtype = _dns_read_short(&context->ptr);\n\t*qclass = _dns_read_short(&context->ptr);\n\n\treturn 0;\n}\n\nstatic int _dns_add_rr_head(struct dns_context *context, const char *domain, int qtype, int qclass, int ttl, int rr_len)\n{\n\tint len = 0;\n\n\t/* resource record head */\n\t/* |domain          |\n\t * |qtype  | qclass |\n\t * |       ttl      |\n\t * | rrlen | rrdata |\n\t */\n\tlen = _dns_add_qr_head(context, domain, qtype, qclass);\n\tif (len < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < 6) {\n\t\treturn -1;\n\t}\n\n\t_dns_write_int(&context->ptr, ttl);\n\t_dns_write_short(&context->ptr, rr_len);\n\n\treturn len + 6;\n}\n\nstatic int _dns_get_rr_head(struct dns_context *context, char *domain, int maxsize, int *qtype, int *qclass, int *ttl,\n\t\t\t\t\t\t\tint *rr_len)\n{\n\tint len = 0;\n\n\t/* resource record head */\n\t/* |domain          |\n\t * |qtype  | qclass |\n\t * |       ttl      |\n\t * | rrlen | rrdata |\n\t */\n\tlen = _dns_get_qr_head(context, domain, maxsize, qtype, qclass);\n\n\tif (_dns_left_len(context) < 6) {\n\t\treturn -1;\n\t}\n\n\t*ttl = _dns_read_int(&context->ptr);\n\t*rr_len = _dns_read_short(&context->ptr);\n\n\treturn len;\n}\n\nstruct dns_rr_nested *dns_add_rr_nested_start(struct dns_rr_nested *rr_nested_buffer, struct dns_packet *packet,\n\t\t\t\t\t\t\t\t\t\t\t  dns_rr_type type, dns_type_t rtype, const char *domain, int ttl)\n{\n\tint len = 0;\n\tmemset(rr_nested_buffer, 0, sizeof(*rr_nested_buffer));\n\trr_nested_buffer->type = type;\n\trr_nested_buffer->rtype = rtype;\n\tint ret = 0;\n\n\t/* resource record */\n\t/* |domain          |\n\t * |qtype  | qclass |\n\t * |       ttl      |\n\t * | rrlen | rrdata |\n\t */\n\tret = _dns_add_rrs_start(packet, &rr_nested_buffer->context);\n\tif (ret < 0) {\n\t\treturn NULL;\n\t}\n\trr_nested_buffer->rr_start = rr_nested_buffer->context.ptr;\n\n\t/* add rr head */\n\tlen = _dns_add_rr_head(&rr_nested_buffer->context, domain, rtype, DNS_C_IN, ttl, 0);\n\tif (len < 0) {\n\t\treturn NULL;\n\t}\n\trr_nested_buffer->rr_len_ptr = rr_nested_buffer->context.ptr - 2;\n\trr_nested_buffer->rr_head_len = len;\n\n\treturn rr_nested_buffer;\n}\n\nint dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, const void *data, int data_len)\n{\n\tif (rr_nested == NULL || data == NULL || data_len <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(&rr_nested->context) < data_len) {\n\t\treturn -1;\n\t}\n\n\tif (is_aligned(rr_nested->context.ptr, 2) == 0) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(rr_nested->context.ptr, data, data_len);\n\trr_nested->context.ptr += data_len;\n\n\tif (is_aligned(rr_nested->context.ptr, 2) == 0) {\n\t\tif (_dns_left_len(&rr_nested->context) < 1) {\n\t\t\treturn -1;\n\t\t}\n\t\t_dns_write_char(&rr_nested->context.ptr, 0);\n\t}\n\n\treturn 0;\n}\n\nint dns_add_rr_nested_end(struct dns_rr_nested *rr_nested)\n{\n\tif (rr_nested == NULL || rr_nested->rr_start == NULL) {\n\t\treturn -1;\n\t}\n\n\tint len = rr_nested->context.ptr - rr_nested->rr_start;\n\tunsigned char *ptr = rr_nested->rr_len_ptr;\n\tif (ptr == NULL || _dns_left_len(&rr_nested->context) < 2) {\n\t\treturn -1;\n\t}\n\n\t/* NO SVC keys, reset ptr */\n\tif (len <= 14) {\n\t\trr_nested->context.ptr = rr_nested->rr_start;\n\t\treturn 0;\n\t}\n\n\t_dns_write_short(&ptr, len - rr_nested->rr_head_len);\n\n\treturn _dns_rr_add_end(rr_nested->context.packet, rr_nested->type, rr_nested->rtype, len);\n}\n\nvoid *dns_get_rr_nested_start(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *ttl, int *rr_len)\n{\n\tstruct dns_context data_context;\n\tint qclass = 0;\n\tint ret = 0;\n\n\t_dns_init_context_by_rrs(rrs, &data_context);\n\tret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, qtype, &qclass, ttl, rr_len);\n\tif (ret < 0) {\n\t\treturn NULL;\n\t}\n\n\tif (qclass != DNS_C_IN) {\n\t\treturn NULL;\n\t}\n\n\tif (*rr_len < 2) {\n\t\treturn NULL;\n\t}\n\n\treturn data_context.ptr;\n}\n\nvoid *dns_get_rr_nested_next(struct dns_rrs *rrs, void *rr_nested, int rr_nested_len)\n{\n\tvoid *end = rrs->data + rrs->len;\n\tvoid *p = rr_nested + rr_nested_len;\n\n\tif (is_aligned(p, 2) == 0) {\n\t\tp++;\n\t}\n\n\tif (p == end) {\n\t\treturn NULL;\n\t} else if (p > end) {\n\t\treturn NULL;\n\t}\n\n\treturn p;\n}\n\nstatic int _dns_add_RAW(struct dns_packet *packet, dns_rr_type rrtype, dns_type_t rtype, const char *domain, int ttl,\n\t\t\t\t\t\tconst void *raw, int raw_len)\n{\n\tint len = 0;\n\tstruct dns_context context;\n\tint ret = 0;\n\n\tif (raw_len < 0) {\n\t\treturn -1;\n\t}\n\n\t/* resource record */\n\t/* |domain          |\n\t * |qtype  | qclass |\n\t * |       ttl      |\n\t * | rrlen | rrdata |\n\t */\n\tret = _dns_add_rrs_start(packet, &context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\t/* add rr head */\n\tlen = _dns_add_rr_head(&context, domain, rtype, DNS_C_IN, ttl, raw_len);\n\tif (len < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(&context) < raw_len) {\n\t\treturn -1;\n\t}\n\n\t/* add rr data */\n\tmemcpy(context.ptr, raw, raw_len);\n\tcontext.ptr += raw_len;\n\tlen += raw_len;\n\n\treturn _dns_rr_add_end(packet, rrtype, rtype, len);\n}\n\nstatic int _dns_get_RAW(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, void *raw, int *raw_len)\n{\n\tint qtype = 0;\n\tint qclass = 0;\n\tint rr_len = 0;\n\tint ret = 0;\n\tstruct dns_context context;\n\n\t/* resource record head */\n\t/* |domain          |\n\t * |qtype  | qclass |\n\t * |       ttl      |\n\t * | rrlen | rrdata |\n\t */\n\t_dns_init_context_by_rrs(rrs, &context);\n\n\t/* get rr head */\n\tret = _dns_get_rr_head(&context, domain, maxsize, &qtype, &qclass, ttl, &rr_len);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (qtype != rrs->type || rr_len > *raw_len) {\n\t\treturn -1;\n\t}\n\n\t/* get rr data */\n\tmemcpy(raw, context.ptr, rr_len);\n\tcontext.ptr += rr_len;\n\t*raw_len = rr_len;\n\n\treturn 0;\n}\n\nstatic int _dns_add_opt_RAW(struct dns_packet *packet, dns_opt_code_t opt_rrtype, void *raw, int raw_len)\n{\n\tunsigned char opt_data[DNS_MAX_OPT_LEN];\n\tstruct dns_opt *opt = (struct dns_opt *)opt_data;\n\tint len = 0;\n\n\topt->code = opt_rrtype;\n\topt->length = sizeof(unsigned short);\n\n\tmemcpy(opt->data, raw, raw_len);\n\tlen += raw_len;\n\tlen += sizeof(*opt);\n\n\treturn _dns_add_RAW(packet, DNS_RRS_OPT, (dns_type_t)opt_rrtype, \"\", 0, opt_data, len);\n}\n\nstatic int _dns_get_opt_RAW(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, struct dns_opt *dns_opt,\n\t\t\t\t\t\t\tint *dns_optlen)\n{\n\t*dns_optlen = DNS_MAX_OPT_LEN;\n\n\treturn _dns_get_RAW(rrs, domain, maxsize, ttl, dns_opt, dns_optlen);\n}\n\nstatic int __attribute__((unused)) _dns_add_OPT(struct dns_packet *packet, dns_rr_type type, unsigned short opt_code,\n\t\t\t\t\t\t\t\t\t\t\t\tunsigned short opt_len, struct dns_opt *opt)\n{\n\tint ret = 0;\n\tint len = 0;\n\tstruct dns_context context;\n\tint total_len = sizeof(*opt) + opt->length;\n\tint ttl = 0;\n\n\t/*\n\t+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n  0: |                          OPTION-CODE                          |\n\t +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n  2: |                         OPTION-LENGTH                         |\n\t +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n  4: |                                                               |\n\t /                          OPTION-DATA                          /\n\t /                                                               /\n\t +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n\t*/\n\tret = _dns_add_rrs_start(packet, &context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(&context) < total_len) {\n\t\treturn -1;\n\t}\n\n\tttl = (opt_code << 16) | opt_len;\n\n\t/* add rr head */\n\tlen = _dns_add_rr_head(&context, \"\", type, DNS_C_IN, ttl, total_len);\n\tif (len < 0) {\n\t\treturn -1;\n\t}\n\n\t/* add rr data */\n\tmemcpy(context.ptr, opt, total_len);\n\tcontext.ptr += total_len;\n\tlen = context.ptr - context.data - packet->len;\n\n\treturn _dns_rr_add_end(packet, type, DNS_T_OPT, len);\n}\n\nstatic int __attribute__((unused)) _dns_get_OPT(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len,\n\t\t\t\t\t\t\t\t\t\t\t\tstruct dns_opt *opt, int *opt_maxlen)\n{\n\tint qtype = 0;\n\tint qclass = 0;\n\tint rr_len = 0;\n\tint ret = 0;\n\tstruct dns_context context;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tint maxsize = DNS_MAX_CNAME_LEN;\n\tint ttl = 0;\n\n\t_dns_init_context_by_rrs(rrs, &context);\n\n\t/* get rr head */\n\tret = _dns_get_rr_head(&context, domain, maxsize, &qtype, &qclass, &ttl, &rr_len);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (qtype != rrs->type || rr_len > *opt_len) {\n\t\treturn -1;\n\t}\n\n\t/* get rr data */\n\t*opt_code = ttl >> 16;\n\t*opt_len = ttl & 0xFFFF;\n\tmemcpy(opt, context.ptr, rr_len);\n\tcontext.ptr += rr_len;\n\t*opt_maxlen = rr_len;\n\n\treturn 0;\n}\n\nint dns_add_CNAME(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname)\n{\n\tint rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1;\n\treturn _dns_add_RAW(packet, type, DNS_T_CNAME, domain, ttl, cname, rr_len);\n}\n\nint dns_get_CNAME(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size)\n{\n\tint len = cname_size;\n\treturn _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &len);\n}\n\nint dns_add_A(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl,\n\t\t\t  unsigned char addr[DNS_RR_A_LEN])\n{\n\treturn _dns_add_RAW(packet, type, DNS_T_A, domain, ttl, addr, DNS_RR_A_LEN);\n}\n\nint dns_get_A(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsigned char addr[DNS_RR_A_LEN])\n{\n\tint len = DNS_RR_A_LEN;\n\treturn _dns_get_RAW(rrs, domain, maxsize, ttl, addr, &len);\n}\n\nint dns_add_PTR(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname)\n{\n\tint rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1;\n\treturn _dns_add_RAW(packet, type, DNS_T_PTR, domain, ttl, cname, rr_len);\n}\n\nint dns_get_PTR(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size)\n{\n\tint len = cname_size;\n\treturn _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &len);\n}\n\nint dns_add_TXT(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *text)\n{\n\tint rr_len = strnlen(text, DNS_MAX_CNAME_LEN);\n\tchar data[DNS_MAX_CNAME_LEN];\n\n\tif (rr_len > DNS_MAX_CNAME_LEN - 2) {\n\t\treturn -1;\n\t}\n\n\tdata[0] = rr_len;\n\trr_len++;\n\tmemcpy(data + 1, text, rr_len);\n\tdata[rr_len] = 0;\n\treturn _dns_add_RAW(packet, type, DNS_T_TXT, domain, ttl, data, rr_len);\n}\n\nint dns_get_TXT(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *text, int txt_size)\n{\n\treturn -1;\n}\n\nint dns_add_NS(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, const char *cname)\n{\n\tint rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1;\n\treturn _dns_add_RAW(packet, type, DNS_T_NS, domain, ttl, cname, rr_len);\n}\n\nint dns_get_NS(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, char *cname, int cname_size)\n{\n\tint len = cname_size;\n\treturn _dns_get_RAW(rrs, domain, maxsize, ttl, cname, &len);\n}\n\nint dns_add_AAAA(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl,\n\t\t\t\t unsigned char addr[DNS_RR_AAAA_LEN])\n{\n\treturn _dns_add_RAW(packet, type, DNS_T_AAAA, domain, ttl, addr, DNS_RR_AAAA_LEN);\n}\n\nint dns_get_AAAA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsigned char addr[DNS_RR_AAAA_LEN])\n{\n\tint len = DNS_RR_AAAA_LEN;\n\treturn _dns_get_RAW(rrs, domain, maxsize, ttl, addr, &len);\n}\n\nint dns_add_SOA(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, struct dns_soa *soa)\n{\n\t/* SOA */\n\t/*| mname        |\n\t *| rname        |\n\t *| serial       |\n\t *| refresh      |\n\t *| retry        |\n\t *| expire       |\n\t *| minimum      |\n\t */\n\tunsigned char data[sizeof(*soa)];\n\tunsigned char *ptr = data;\n\tint len = 0;\n\n\tif (soa == NULL || domain == NULL || packet == NULL) {\n\t\treturn -1;\n\t}\n\n\tsafe_strncpy((char *)ptr, soa->mname, DNS_MAX_CNAME_LEN);\n\tptr += strnlen(soa->mname, DNS_MAX_CNAME_LEN - 1) + 1;\n\tsafe_strncpy((char *)ptr, soa->rname, DNS_MAX_CNAME_LEN);\n\tptr += strnlen(soa->rname, DNS_MAX_CNAME_LEN - 1) + 1;\n\tmemcpy(ptr, &soa->serial, sizeof(unsigned int));\n\tptr += 4;\n\tmemcpy(ptr, &soa->refresh, sizeof(unsigned int));\n\tptr += 4;\n\tmemcpy(ptr, &soa->retry, sizeof(unsigned int));\n\tptr += 4;\n\tmemcpy(ptr, &soa->expire, sizeof(unsigned int));\n\tptr += 4;\n\tmemcpy(ptr, &soa->minimum, sizeof(unsigned int));\n\tptr += 4;\n\tlen = ptr - data;\n\n\treturn _dns_add_RAW(packet, type, DNS_T_SOA, domain, ttl, data, len);\n}\n\nint dns_get_SOA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, struct dns_soa *soa)\n{\n\tunsigned char data[sizeof(*soa)];\n\tunsigned char *ptr = data;\n\tint len = sizeof(data);\n\n\t/* SOA */\n\t/*| mname        |\n\t *| rname        |\n\t *| serial       |\n\t *| refresh      |\n\t *| retry        |\n\t *| expire       |\n\t *| minimum      |\n\t */\n\tif (_dns_get_RAW(rrs, domain, maxsize, ttl, data, &len) != 0) {\n\t\treturn -1;\n\t}\n\n\tsafe_strncpy(soa->mname, (char *)ptr, DNS_MAX_CNAME_LEN - 1);\n\tptr += strnlen(soa->mname, DNS_MAX_CNAME_LEN - 1) + 1;\n\tif (ptr - data >= len) {\n\t\treturn -1;\n\t}\n\tsafe_strncpy(soa->rname, (char *)ptr, DNS_MAX_CNAME_LEN - 1);\n\tptr += strnlen(soa->rname, DNS_MAX_CNAME_LEN - 1) + 1;\n\tif (ptr - data + 20 > len) {\n\t\treturn -1;\n\t}\n\tmemcpy(&soa->serial, ptr, 4);\n\tptr += 4;\n\tmemcpy(&soa->refresh, ptr, 4);\n\tptr += 4;\n\tmemcpy(&soa->retry, ptr, 4);\n\tptr += 4;\n\tmemcpy(&soa->expire, ptr, 4);\n\tptr += 4;\n\tmemcpy(&soa->minimum, ptr, 4);\n\n\treturn 0;\n}\n\nint dns_set_OPT_payload_size(struct dns_packet *packet, int payload_size)\n{\n\tif (payload_size < 512) {\n\t\tpayload_size = 512;\n\t}\n\n\tpacket->payloadsize = payload_size;\n\treturn 0;\n}\n\nint dns_get_OPT_payload_size(struct dns_packet *packet)\n{\n\treturn packet->payloadsize;\n}\n\nint dns_set_OPT_option(struct dns_packet *packet, unsigned int option)\n{\n\tpacket->opt_option = option;\n\treturn 0;\n}\n\nunsigned int dns_get_OPT_option(struct dns_packet *packet)\n{\n\treturn packet->opt_option;\n}\n\nint dns_add_OPT_ECS(struct dns_packet *packet, struct dns_opt_ecs *ecs)\n{\n\tunsigned char opt_data[DNS_MAX_OPT_LEN];\n\tstruct dns_opt *opt = (struct dns_opt *)opt_data;\n\tint len = 0;\n\n\t/* ecs size 4 + bit of address*/\n\tlen = 4;\n\tlen += (ecs->source_prefix / 8);\n\tlen += (ecs->source_prefix % 8 > 0) ? 1 : 0;\n\n\topt->length = len;\n\topt->code = DNS_OPT_T_ECS;\n\tmemcpy(opt->data, ecs, len);\n\tlen += sizeof(*opt);\n\n\treturn _dns_add_RAW(packet, DNS_RRS_OPT, (dns_type_t)DNS_OPT_T_ECS, \"\", 0, opt_data, len);\n}\n\nint dns_get_OPT_ECS(struct dns_rrs *rrs, struct dns_opt_ecs *ecs)\n{\n\tunsigned char opt_data[DNS_MAX_OPT_LEN];\n\tchar domain[DNS_MAX_CNAME_LEN] = {0};\n\tstruct dns_opt *opt = (struct dns_opt *)opt_data;\n\tint len = DNS_MAX_OPT_LEN;\n\tint ttl = 0;\n\n\tif (_dns_get_RAW(rrs, domain, DNS_MAX_CNAME_LEN, &ttl, opt_data, &len) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (len < (int)sizeof(*opt)) {\n\t\treturn -1;\n\t}\n\n\tif (opt->code != DNS_OPT_T_ECS) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(ecs, opt->data, opt->length);\n\n\treturn 0;\n}\n\nint dns_add_OPT_TCP_KEEPALIVE(struct dns_packet *packet, unsigned short timeout)\n{\n\tunsigned short timeout_net = htons(timeout);\n\tint data_len = 0;\n\n\tif (timeout > 0) {\n\t\tdata_len = sizeof(timeout);\n\t}\n\n\treturn _dns_add_opt_RAW(packet, DNS_OPT_T_TCP_KEEPALIVE, &timeout_net, data_len);\n}\n\nint dns_get_OPT_TCP_KEEPALIVE(struct dns_rrs *rrs, unsigned short *timeout)\n{\n\tunsigned char opt_data[DNS_MAX_OPT_LEN];\n\tchar domain[DNS_MAX_CNAME_LEN] = {0};\n\tstruct dns_opt *opt = (struct dns_opt *)opt_data;\n\tint len = DNS_MAX_OPT_LEN;\n\tint ttl = 0;\n\tunsigned char *data = NULL;\n\n\tif (_dns_get_opt_RAW(rrs, domain, DNS_MAX_CNAME_LEN, &ttl, opt, &len) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (len < (int)sizeof(*opt)) {\n\t\treturn -1;\n\t}\n\n\tif (opt->code != DNS_OPT_T_TCP_KEEPALIVE) {\n\t\treturn -1;\n\t}\n\n\tif (opt->length == 0) {\n\t\t*timeout = 0;\n\t\treturn 0;\n\t}\n\n\tif (opt->length != sizeof(unsigned short)) {\n\t\treturn -1;\n\t}\n\n\tdata = opt->data;\n\n\t*timeout = _dns_read_short(&data);\n\n\treturn 0;\n}\n\nint dns_add_SRV(struct dns_packet *packet, dns_rr_type type, const char *domain, int ttl, int priority, int weight,\n\t\t\t\tint port, const char *target)\n{\n\tunsigned char data[DNS_MAX_CNAME_LEN];\n\tunsigned char *data_ptr = data;\n\n\tint target_len = 0;\n\tif (target == NULL) {\n\t\ttarget = \"\";\n\t}\n\n\ttarget_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1;\n\tmemcpy(data_ptr, &priority, sizeof(unsigned short));\n\tdata_ptr += sizeof(unsigned short);\n\tmemcpy(data_ptr, &weight, sizeof(unsigned short));\n\tdata_ptr += sizeof(unsigned short);\n\tmemcpy(data_ptr, &port, sizeof(unsigned short));\n\tdata_ptr += sizeof(unsigned short);\n\tif (data_ptr - data + target_len >= DNS_MAX_CNAME_LEN) {\n\t\treturn -1;\n\t}\n\n\tsafe_strncpy((char *)data_ptr, target, target_len);\n\tdata_ptr += target_len;\n\n\treturn _dns_add_RAW(packet, type, DNS_T_SRV, domain, ttl, data, data_ptr - data);\n}\n\nint dns_get_SRV(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsigned short *priority,\n\t\t\t\tunsigned short *weight, unsigned short *port, char *target, int target_size)\n{\n\tunsigned char data[DNS_MAX_CNAME_LEN];\n\tunsigned char *ptr = data;\n\tint len = sizeof(data);\n\n\tif (_dns_get_RAW(rrs, domain, maxsize, ttl, data, &len) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (len < 6) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(priority, ptr, sizeof(unsigned short));\n\tptr += sizeof(unsigned short);\n\tmemcpy(weight, ptr, sizeof(unsigned short));\n\tptr += sizeof(unsigned short);\n\tmemcpy(port, ptr, sizeof(unsigned short));\n\tptr += sizeof(unsigned short);\n\tsafe_strncpy(target, (char *)ptr, target_size);\n\n\treturn 0;\n}\n\nstatic int _dns_add_SVCB_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,\n\t\t\t\t\t\t\t\t\t dns_type_t rtype, const char *domain, int ttl, int priority, const char *target)\n{\n\tsvcparam_buffer = dns_add_rr_nested_start(svcparam_buffer, packet, type, rtype, domain, ttl);\n\tif (svcparam_buffer == NULL) {\n\t\treturn -1;\n\t}\n\n\tint target_len = 0;\n\tif (target == NULL || target[0] == '.') {\n\t\ttarget = \"\";\n\t}\n\n\ttarget_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1;\n\tif (_dns_left_len(&svcparam_buffer->context) < 2 + target_len) {\n\t\treturn -1;\n\t}\n\n\t/* add rr data */\n\t_dns_write_short(&svcparam_buffer->context.ptr, priority);\n\tsafe_strncpy((char *)svcparam_buffer->context.ptr, target, target_len);\n\tsvcparam_buffer->context.ptr += target_len;\n\n\tif (is_aligned(svcparam_buffer->context.ptr, 2) != 1) {\n\t\tif (_dns_left_len(&svcparam_buffer->context) < 1) {\n\t\t\treturn -1;\n\t\t}\n\n\t\t_dns_write_char(&svcparam_buffer->context.ptr, 0);\n\t}\n\n\treturn 0;\n}\n\nint dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,\n\t\t\t\t\t\tconst char *domain, int ttl, int priority, const char *target)\n{\n\treturn _dns_add_SVCB_HTTPS_start(svcparam_buffer, packet, type, DNS_T_HTTPS, domain, ttl, priority, target);\n}\n\nint dns_add_SVCB_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,\n\t\t\t\t\t   const char *domain, int ttl, int priority, const char *target)\n{\n\treturn _dns_add_SVCB_HTTPS_start(svcparam_buffer, packet, type, DNS_T_SVCB, domain, ttl, priority, target);\n}\n\nint dns_SVCB_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len)\n{\n\tif (_dns_left_len(&svcparam->context) < 2 + len) {\n\t\treturn -1;\n\t}\n\n\tif (dns_add_rr_nested_memcpy(svcparam, &key, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (dns_add_rr_nested_memcpy(svcparam, &len, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (dns_add_rr_nested_memcpy(svcparam, value, len) != 0) {\n\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nint dns_SVCB_add_port(struct dns_rr_nested *svcparam, unsigned short port)\n{\n\tif (_dns_left_len(&svcparam->context) < 6) {\n\t\treturn -1;\n\t}\n\n\tunsigned short value = DNS_HTTPS_T_PORT;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tvalue = 2;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tvalue = htons(port);\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint dns_SVCB_add_alpn(struct dns_rr_nested *svcparam, const unsigned char *alpn, int alpn_len)\n{\n\tif (_dns_left_len(&svcparam->context) < 2 + 2 + alpn_len) {\n\t\treturn -1;\n\t}\n\n\tunsigned short value = DNS_HTTPS_T_ALPN;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tvalue = alpn_len;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (dns_add_rr_nested_memcpy(svcparam, alpn, alpn_len) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint dns_SVCB_add_no_default_alpn(struct dns_rr_nested *svcparam)\n{\n\tif (_dns_left_len(&svcparam->context) < 4) {\n\t\treturn -1;\n\t}\n\n\tunsigned short value = DNS_HTTPS_T_NO_DEFAULT_ALPN;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tvalue = 0; /* no data for no-default-alpn */\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint dns_SVCB_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len)\n{\n\tif (_dns_left_len(&svcparam->context) < 2 + 2 + ech_len) {\n\t\treturn -1;\n\t}\n\n\tunsigned short value = DNS_HTTPS_T_ECH;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tvalue = ech_len;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (dns_add_rr_nested_memcpy(svcparam, ech, ech_len) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint dns_SVCB_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)\n{\n\tif (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_A_LEN) {\n\t\treturn -1;\n\t}\n\n\tunsigned short value = DNS_HTTPS_T_IPV4HINT;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tvalue = addr_num * DNS_RR_A_LEN;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tfor (int i = 0; i < addr_num; i++) {\n\t\tif (dns_add_rr_nested_memcpy(svcparam, addr[i], DNS_RR_A_LEN) != 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint dns_SVCB_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)\n{\n\tif (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_AAAA_LEN) {\n\t\treturn -1;\n\t}\n\n\tunsigned short value = DNS_HTTPS_T_IPV6HINT;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tvalue = addr_num * DNS_RR_AAAA_LEN;\n\tif (dns_add_rr_nested_memcpy(svcparam, &value, 2) != 0) {\n\t\treturn -1;\n\t}\n\n\tfor (int i = 0; i < addr_num; i++) {\n\t\tif (dns_add_rr_nested_memcpy(svcparam, addr[i], DNS_RR_AAAA_LEN) != 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint dns_add_SVCB_end(struct dns_rr_nested *svcparam)\n{\n\treturn dns_add_rr_nested_end(svcparam);\n}\n\n/* Backward compatibility wrapper */\nint dns_add_HTTPS_end(struct dns_rr_nested *svcparam)\n{\n\treturn dns_add_SVCB_end(svcparam);\n}\n\n/* Backward compatibility wrappers for dns_HTTPS_add_* functions */\nint dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len)\n{\n\treturn dns_SVCB_add_raw(svcparam, key, value, len);\n}\n\nint dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len)\n{\n\treturn dns_SVCB_add_alpn(svcparam, (const unsigned char *)alpn, alpn_len);\n}\n\nint dns_HTTPS_add_no_default_alpn(struct dns_rr_nested *svcparam)\n{\n\treturn dns_SVCB_add_no_default_alpn(svcparam);\n}\n\nint dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port)\n{\n\treturn dns_SVCB_add_port(svcparam, port);\n}\n\nint dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)\n{\n\treturn dns_SVCB_add_ipv4hint(svcparam, addr, addr_num);\n}\n\nint dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len)\n{\n\treturn dns_SVCB_add_ech(svcparam, ech, ech_len);\n}\n\nint dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)\n{\n\treturn dns_SVCB_add_ipv6hint(svcparam, addr, addr_num);\n}\n\nint dns_svcparm_start(struct dns_rrs *rrs, struct dns_svcparam **https_param, char *domain, int maxsize,\n\t\t\t\t\t\t\t\tint *ttl, int *priority, char *target, int target_size)\n{\n\tint qtype = 0;\n\tunsigned char *data = NULL;\n\tint rr_len = 0;\n\n\tdata = dns_get_rr_nested_start(rrs, domain, maxsize, &qtype, ttl, &rr_len);\n\tif (data == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (qtype != DNS_T_HTTPS && qtype != DNS_T_SVCB) {\n\t\treturn -1;\n\t}\n\n\tif (rr_len < 2) {\n\t\treturn -1;\n\t}\n\n\t*priority = _dns_read_short(&data);\n\trr_len -= 2;\n\tif (rr_len <= 0) {\n\t\treturn -1;\n\t}\n\n\tint len = strnlen((char *)data, rr_len);\n\tsafe_strncpy(target, (char *)data, target_size);\n\tdata += len + 1;\n\trr_len -= len + 1;\n\tif (rr_len < 0) {\n\t\treturn -1;\n\t}\n\n\t/* PADDING */\n\tif (is_aligned(data, 2) != 1) {\n\t\tdata++;\n\t\trr_len--;\n\t}\n\n\tif (rr_len == 0) {\n\t\t*https_param = NULL;\n\t\treturn 0;\n\t}\n\n\t*https_param = (struct dns_svcparam *)data;\n\n\treturn 0;\n}\n\nstruct dns_svcparam *dns_svcparm_next(struct dns_rrs *rrs, struct dns_svcparam *param)\n{\n\treturn dns_get_rr_nested_next(rrs, param, sizeof(struct dns_svcparam) + param->len);\n}\n\n/*\n * Format:\n * |DNS_NAME\\0(string)|qtype(short)|qclass(short)|\n */\nint dns_add_domain(struct dns_packet *packet, const char *domain, int qtype, int qclass)\n{\n\tint len = 0;\n\tint ret = 0;\n\tstruct dns_context context;\n\n\tret = _dns_add_rrs_start(packet, &context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tlen = _dns_add_qr_head(&context, domain, qtype, qclass);\n\tif (len < 0) {\n\t\treturn -1;\n\t}\n\n\treturn _dns_rr_add_end(packet, DNS_RRS_QD, DNS_T_CNAME, len);\n}\n\nint dns_get_domain(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *qclass)\n{\n\tstruct dns_context context;\n\n\t_dns_init_context_by_rrs(rrs, &context);\n\treturn _dns_get_qr_head(&context, domain, maxsize, qtype, qclass);\n}\n\nstatic int _dns_decode_head(struct dns_context *context)\n{\n\tunsigned int fields = 0;\n\tint len = 12;\n\tstruct dns_head *head = &context->packet->head;\n\n\tif (_dns_left_len(context) < len) {\n\t\treturn -1;\n\t}\n\n\t/*\n\t0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                      ID                       |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                    QDCOUNT                    |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                    ANCOUNT                    |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                    NSCOUNT                    |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                    ARCOUNT                    |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t*/\n\n\thead->id = _dns_read_short(&context->ptr);\n\tfields = _dns_read_short(&context->ptr);\n\thead->qr = (fields & QR_MASK) >> 15;\n\thead->opcode = (fields & OPCODE_MASK) >> 11;\n\thead->aa = (fields & AA_MASK) >> 10;\n\thead->tc = (fields & TC_MASK) >> 9;\n\thead->rd = (fields & RD_MASK) >> 8;\n\thead->ra = (fields & RA_MASK) >> 7;\n\thead->z = (fields & Z_MASK) >> 6;\n\thead->ad = (fields & AD_MASK) >> 5;\n\thead->cd = (fields & CD_MASK) >> 4;\n\thead->rcode = (fields & RCODE_MASK) >> 0;\n\thead->qdcount = _dns_read_short(&context->ptr);\n\thead->ancount = _dns_read_short(&context->ptr);\n\thead->nscount = _dns_read_short(&context->ptr);\n\thead->nrcount = _dns_read_short(&context->ptr);\n\n\treturn 0;\n}\n\nstatic int _dns_encode_head(struct dns_context *context)\n{\n\tint len = 12;\n\tstruct dns_head *head = &context->packet->head;\n\n\tif (_dns_left_len(context) < len) {\n\t\treturn -1;\n\t}\n\n\t_dns_write_short(&context->ptr, head->id);\n\n\tint fields = 0;\n\tfields |= (head->qr << 15) & QR_MASK;\n\tfields |= (head->opcode << 11) & OPCODE_MASK;\n\tfields |= (head->aa << 10) & AA_MASK;\n\tfields |= (head->tc << 9) & TC_MASK;\n\tfields |= (head->rd << 8) & RD_MASK;\n\tfields |= (head->ra << 7) & RA_MASK;\n\tfields |= (head->z << 6) & Z_MASK;\n\tfields |= (head->ad << 5) & AD_MASK;\n\tfields |= (head->cd << 4) & CD_MASK;\n\tfields |= (head->rcode << 0) & RCODE_MASK;\n\t_dns_write_short(&context->ptr, fields);\n\n\t_dns_write_short(&context->ptr, head->qdcount);\n\t_dns_write_short(&context->ptr, head->ancount);\n\t_dns_write_short(&context->ptr, head->nscount);\n\t_dns_write_short(&context->ptr, head->nrcount);\n\treturn len;\n}\n\nstatic int _dns_encode_head_count(struct dns_context *context)\n{\n\tint len = 12;\n\tstruct dns_head *head = &context->packet->head;\n\tunsigned char *ptr = context->data;\n\n\tptr += 4;\n\t_dns_write_short(&ptr, head->qdcount);\n\t_dns_write_short(&ptr, head->ancount);\n\t_dns_write_short(&ptr, head->nscount);\n\t_dns_write_short(&ptr, head->nrcount);\n\treturn len;\n}\n\nstatic int _dns_decode_qr_head(struct dns_context *context, char *domain, int domain_size, int *qtype, int *qclass)\n{\n\tint ret = 0;\n\t/*\n\t0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                                               |\n\t/                                               /\n\t/                      NAME                     /\n\t|                                               |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                      TYPE                     |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                     CLASS                     |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t*/\n\tret = _dns_decode_domain(context, domain, domain_size);\n\tif (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"decode domain failed.\");\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < 4) {\n\t\ttlog(TLOG_DEBUG, \"left length is not enough, %s.\", domain);\n\t\treturn -1;\n\t}\n\n\t*qtype = _dns_read_short(&context->ptr);\n\t*qclass = _dns_read_short(&context->ptr);\n\n\treturn 0;\n}\n\nstatic int _dns_encode_qr_head(struct dns_context *context, char *domain, int qtype, int qclass)\n{\n\tint ret = 0;\n\tret = _dns_encode_domain(context, domain);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < 4) {\n\t\treturn -1;\n\t}\n\n\t_dns_write_short(&context->ptr, qtype);\n\t_dns_write_short(&context->ptr, qclass);\n\n\treturn 0;\n}\n\nstatic int _dns_decode_rr_head(struct dns_context *context, char *domain, int domain_size, int *qtype, int *qclass,\n\t\t\t\t\t\t\t   int *ttl, int *rr_len)\n{\n\tint len = 0;\n\n\tlen = _dns_decode_qr_head(context, domain, domain_size, qtype, qclass);\n\tif (len < 0) {\n\t\ttlog(TLOG_DEBUG, \"decode qr head failed.\");\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < 6) {\n\t\ttlog(TLOG_DEBUG, \"left length is not enough.\");\n\t\treturn -1;\n\t}\n\n\t*ttl = _dns_read_int(&context->ptr);\n\t*rr_len = _dns_read_short(&context->ptr);\n\n\tif (*rr_len < 0 || *ttl < 0) {\n\t\ttlog(TLOG_DEBUG, \"rr len or ttl is invalid.\");\n\t\treturn -1;\n\t}\n\n\tif (*rr_len > _dns_left_len(context)) {\n\t\ttlog(TLOG_DEBUG, \"rr len exceeds remaining buffer.\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_encode_rr_head(struct dns_context *context, char *domain, int qtype, int qclass, int ttl, int rr_len,\n\t\t\t\t\t\t\t   unsigned char **rr_len_ptr)\n{\n\tint ret = 0;\n\tret = _dns_encode_qr_head(context, domain, qtype, qclass);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < 6) {\n\t\treturn -1;\n\t}\n\n\t_dns_write_int(&context->ptr, ttl);\n\tif (rr_len_ptr) {\n\t\t*rr_len_ptr = context->ptr;\n\t}\n\t_dns_write_short(&context->ptr, rr_len);\n\n\treturn 0;\n}\n\nstatic int _dns_encode_raw(struct dns_context *context, struct dns_rrs *rrs)\n{\n\tint ret = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tint ttl = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tint rr_len = 0;\n\tunsigned char *rr_len_ptr = NULL;\n\tstruct dns_context data_context;\n\t/*\n\t0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                                               |\n\t/                                               /\n\t/                      NAME                     /\n\t|                                               |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                      TYPE                     |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                     CLASS                     |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                      TTL                      |\n\t|                                               |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t|                   RDLENGTH                    |\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|\n\t/                     RDATA                     /\n\t/                                               /\n\t+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n\t*/\n\t_dns_init_context_by_rrs(rrs, &data_context);\n\tret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < rr_len) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(context->ptr, data_context.ptr, rr_len);\n\tcontext->ptr += rr_len;\n\tdata_context.ptr += rr_len;\n\n\treturn 0;\n}\n\nstatic int _dns_decode_raw(struct dns_context *context, unsigned char *raw, int len)\n{\n\tif (_dns_left_len(context) < len || len < 0) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(raw, context->ptr, len);\n\tcontext->ptr += len;\n\treturn 0;\n}\n\nstatic int _dns_decode_CNAME(struct dns_context *context, char *cname, int cname_size)\n{\n\tint ret = 0;\n\tret = _dns_decode_domain(context, cname, cname_size);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_encode_CNAME(struct dns_context *context, struct dns_rrs *rrs)\n{\n\tint ret = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tint ttl = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tint rr_len = 0;\n\tunsigned char *rr_len_ptr = NULL;\n\tstruct dns_context data_context;\n\n\t_dns_init_context_by_rrs(rrs, &data_context);\n\tret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\t/* when code domain, len must plus 1, because of length at the beginning */\n\trr_len = 1;\n\tret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_encode_domain(context, (char *)data_context.ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\trr_len += ret;\n\tdata_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1;\n\n\tif (rr_len > rrs->len) {\n\t\treturn -1;\n\t}\n\t_dns_write_short(&rr_len_ptr, ret);\n\n\treturn 0;\n}\n\nstatic int _dns_decode_SRV(struct dns_context *context, unsigned short *priority, unsigned short *weight,\n\t\t\t\t\t\t   unsigned short *port, char *target, int target_size)\n{\n\tint ret = 0;\n\n\tif (_dns_left_len(context) < 6) {\n\t\treturn -1;\n\t}\n\n\t*priority = _dns_read_short(&context->ptr);\n\t*weight = _dns_read_short(&context->ptr);\n\t*port = _dns_read_short(&context->ptr);\n\n\tret = _dns_decode_domain(context, target, target_size);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nstatic int _dns_decode_SOA(struct dns_context *context, struct dns_soa *soa)\n{\n\tint ret = 0;\n\tret = _dns_decode_domain(context, soa->mname, DNS_MAX_CNAME_LEN - 1);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_decode_domain(context, soa->rname, DNS_MAX_CNAME_LEN - 1);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < 20) {\n\t\treturn -1;\n\t}\n\n\tsoa->serial = _dns_read_int(&context->ptr);\n\tsoa->refresh = _dns_read_int(&context->ptr);\n\tsoa->retry = _dns_read_int(&context->ptr);\n\tsoa->expire = _dns_read_int(&context->ptr);\n\tsoa->minimum = _dns_read_int(&context->ptr);\n\n\treturn 0;\n}\n\nstatic int _dns_encode_SOA(struct dns_context *context, struct dns_rrs *rrs)\n{\n\tint ret = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tint ttl = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tint rr_len = 0;\n\tunsigned char *rr_len_ptr = NULL;\n\tstruct dns_context data_context;\n\n\t_dns_init_context_by_rrs(rrs, &data_context);\n\tret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\trr_len = 0;\n\t/* mname */\n\tret = _dns_encode_domain(context, (char *)data_context.ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\trr_len += ret;\n\tdata_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1;\n\n\t/* rname */\n\tret = _dns_encode_domain(context, (char *)data_context.ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\trr_len += ret;\n\tdata_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1;\n\tif (rr_len > rrs->len) {\n\t\treturn -1;\n\t}\n\n\trr_len += 20;\n\t_dns_write_short(&rr_len_ptr, rr_len);\n\tif (_dns_left_len(context) < 20) {\n\t\treturn -1;\n\t}\n\n\t_dns_write_intptr(&context->ptr, data_context.ptr);\n\tdata_context.ptr += 4;\n\t_dns_write_intptr(&context->ptr, data_context.ptr);\n\tdata_context.ptr += 4;\n\t_dns_write_intptr(&context->ptr, data_context.ptr);\n\tdata_context.ptr += 4;\n\t_dns_write_intptr(&context->ptr, data_context.ptr);\n\tdata_context.ptr += 4;\n\t_dns_write_intptr(&context->ptr, data_context.ptr);\n\tdata_context.ptr += 4;\n\n\treturn 0;\n}\n\nstatic int _dns_encode_SRV(struct dns_context *context, struct dns_rrs *rrs)\n{\n\tint ret = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tint ttl = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tint rr_len = 0;\n\tunsigned char *rr_len_ptr = NULL;\n\tstruct dns_context data_context;\n\n\t_dns_init_context_by_rrs(rrs, &data_context);\n\tret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, rr_len, &rr_len_ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\trr_len = 0;\n\n\tif (_dns_left_len(context) < 6) {\n\t\treturn -1;\n\t}\n\n\t_dns_write_shortptr(&context->ptr, data_context.ptr);\n\tdata_context.ptr += 2;\n\t_dns_write_shortptr(&context->ptr, data_context.ptr);\n\tdata_context.ptr += 2;\n\t_dns_write_shortptr(&context->ptr, data_context.ptr);\n\tdata_context.ptr += 2;\n\trr_len += 6;\n\n\tret = _dns_encode_domain(context, (char *)data_context.ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\trr_len += ret;\n\tdata_context.ptr += strnlen((char *)(data_context.ptr), DNS_MAX_CNAME_LEN) + 1;\n\n\t_dns_write_short(&rr_len_ptr, rr_len);\n\n\treturn 0;\n}\n\nstatic int _dns_decode_opt_ecs(struct dns_context *context, struct dns_opt_ecs *ecs, int opt_len)\n{\n\tint len = 0;\n\tif (opt_len < 4) {\n\t\treturn -1;\n\t}\n\n\tecs->family = _dns_read_short(&context->ptr);\n\tecs->source_prefix = _dns_read_char(&context->ptr);\n\tecs->scope_prefix = _dns_read_char(&context->ptr);\n\tlen = (ecs->source_prefix / 8);\n\tlen += (ecs->source_prefix % 8 > 0) ? 1 : 0;\n\n\tif (_dns_left_len(context) < len || len > (int)sizeof(ecs->addr)) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(ecs->addr, context->ptr, len);\n\tcontext->ptr += len;\n\n\ttlog(TLOG_DEBUG, \"ECS: family:%d, source_prefix:%d, scope_prefix:%d, len:%d\", ecs->family, ecs->source_prefix,\n\t\t ecs->scope_prefix, len);\n\ttlog(TLOG_DEBUG, \"%d.%d.%d.%d\", ecs->addr[0], ecs->addr[1], ecs->addr[2], ecs->addr[3]);\n\n\treturn 0;\n}\n\nstatic int _dns_decode_opt_cookie(struct dns_context *context, struct dns_opt_cookie *cookie, int opt_len)\n{\n\tif (opt_len < (int)member_size(struct dns_opt_cookie, client_cookie)) {\n\t\treturn -1;\n\t}\n\n\tint len = 8;\n\tmemcpy(cookie->client_cookie, context->ptr, len);\n\tcontext->ptr += len;\n\n\topt_len -= len;\n\tif (opt_len <= 0) {\n\t\tcookie->server_cookie_len = 0;\n\t\treturn 0;\n\t}\n\n\tif (opt_len < 8 || opt_len > (int)member_size(struct dns_opt_cookie, server_cookie)) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(cookie->server_cookie, context->ptr, opt_len);\n\tcookie->server_cookie_len = opt_len;\n\tcontext->ptr += opt_len;\n\n\ttlog(TLOG_DEBUG, \"OPT COOKIE\");\n\treturn 0;\n}\n\nstatic int _dns_decode_opt_tcp_keepalive(struct dns_context *context, unsigned short *timeout, int opt_len)\n{\n\tif (opt_len == 0) {\n\t\t*timeout = 0;\n\t\treturn 0;\n\t}\n\n\tif (opt_len < (int)sizeof(unsigned short)) {\n\t\treturn -1;\n\t}\n\n\t*timeout = _dns_read_short(&context->ptr);\n\n\ttlog(TLOG_DEBUG, \"OPT TCP KEEPALIVE %u\", *timeout);\n\treturn 0;\n}\n\nstatic int _dns_encode_OPT(struct dns_context *context, struct dns_rrs *rrs)\n{\n\tint ret = 0;\n\tint opt_code = 0;\n\tint qclass = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tstruct dns_context data_context;\n\tint rr_len = 0;\n\tint ttl = 0;\n\tstruct dns_opt *dns_opt = NULL;\n\n\t_dns_init_context_by_rrs(rrs, &data_context);\n\tret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &opt_code, &qclass, &ttl, &rr_len);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (rr_len < (int)sizeof(*dns_opt)) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < (rr_len)) {\n\t\treturn -1;\n\t}\n\n\tdns_opt = (struct dns_opt *)data_context.ptr;\n\t_dns_write_short(&context->ptr, dns_opt->code);\n\t_dns_write_short(&context->ptr, dns_opt->length);\n\n\tif (_dns_left_len(context) < dns_opt->length) {\n\t\treturn -1;\n\t}\n\n\tswitch (dns_opt->code) {\n\tcase DNS_OPT_T_ECS: {\n\t\tstruct dns_opt_ecs *ecs = (struct dns_opt_ecs *)&(dns_opt->data);\n\t\t_dns_write_short(&context->ptr, ecs->family);\n\t\t_dns_write_char(&context->ptr, ecs->source_prefix);\n\t\t_dns_write_char(&context->ptr, ecs->scope_prefix);\n\t\tmemcpy(context->ptr, ecs->addr, dns_opt->length - 4);\n\t\tcontext->ptr += dns_opt->length - 4;\n\t} break;\n\tdefault:\n\t\tmemcpy(context->ptr, dns_opt->data, dns_opt->length);\n\t\tcontext->ptr += dns_opt->length;\n\t\tbreak;\n\t}\n\treturn 0;\n}\n\nstatic int _dns_get_opts_data_len(struct dns_packet *packet, struct dns_rrs *rrs, int count)\n{\n\tint i = 0;\n\tint len = 0;\n\tint opt_code = 0;\n\tint qclass = 0;\n\tint ttl = 0;\n\tint ret = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tstruct dns_context data_context;\n\tint rr_len = 0;\n\n\tfor (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\t_dns_init_context_by_rrs(rrs, &data_context);\n\t\tret = _dns_get_rr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &opt_code, &qclass, &ttl, &rr_len);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tlen += rr_len;\n\t}\n\n\treturn len;\n}\n\nstatic int _dns_encode_opts(struct dns_packet *packet, struct dns_context *context, struct dns_rrs *rrs, int count)\n{\n\tint i = 0;\n\tint len = 0;\n\tint ret = 0;\n\tunsigned int rcode = packet->opt_option;\n\tint rr_len = 0;\n\tint payloadsize = packet->payloadsize;\n\tunsigned char *rr_len_ptr = NULL;\n\n\trr_len = _dns_get_opts_data_len(packet, rrs, count);\n\tif (rr_len < 0) {\n\t\treturn -1;\n\t}\n\n\tif (payloadsize < DNS_DEFAULT_PACKET_SIZE) {\n\t\tpayloadsize = DNS_DEFAULT_PACKET_SIZE;\n\t}\n\n\tret = _dns_encode_rr_head(context, \"\", DNS_T_OPT, payloadsize, rcode, rr_len, &rr_len_ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < rr_len) {\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\tlen = _dns_encode_OPT(context, rrs);\n\t\tif (len < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsigned int ttl, int rr_len)\n{\n\tunsigned short opt_code = 0;\n\tunsigned short opt_len = 0;\n\tunsigned short errcode = (ttl >> 16) & 0xFFFF;\n\tunsigned short ever = (ttl) & 0xFFFF;\n\tunsigned char *start = context->ptr;\n\tstruct dns_packet *packet = context->packet;\n\tint ret = 0;\n\n\tUNUSED(ever);\n\n\t/*\n\t\t Field Name   Field Type     Description\n\t ------------------------------------------------------\n\t NAME         domain name    empty (root domain)\n\t TYPE         u_int16_t      OPT\n\t CLASS        u_int16_t      sender's UDP payload size\n\t TTL          u_int32_t      extended RCODE and flags\n\t RDLEN        u_int16_t      describes RDATA\n\t RDATA        octet stream   {attribute,value} pairs\n\n\t\t\t\t\t +0 (MSB)                            +1 (LSB)\n\t +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n  0: |                          OPTION-CODE                          |\n\t +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n  2: |                         OPTION-LENGTH                         |\n\t +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n  4: |                                                               |\n\t /                          OPTION-DATA                          /\n\t /                                                               /\n\t +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n\n\tTTL\n\t\t\t\t +0 (MSB)                            +1 (LSB)\n\t  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n   0: |         EXTENDED-RCODE        |            VERSION            |\n\t  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n   2: |                               Z                               |\n\t  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n\t*/\n\n\tif (rr_len < 0) {\n\t\ttlog(TLOG_DEBUG, \"opt len is invalid.\");\n\t\treturn -1;\n\t}\n\n\tif (errcode != 0) {\n\t\ttlog(TLOG_DEBUG, \"extend rcode invalid, %d\", errcode);\n\t\treturn 0;\n\t}\n\n\twhile (context->ptr - start < rr_len) {\n\t\tif (_dns_left_len(context) < 4) {\n\t\t\ttlog(TLOG_DEBUG, \"data length is invalid, %d:%d\", _dns_left_len(context),\n\t\t\t\t (int)(context->ptr - context->data));\n\t\t\treturn -1;\n\t\t}\n\t\topt_code = _dns_read_short(&context->ptr);\n\t\topt_len = _dns_read_short(&context->ptr);\n\n\t\tif (_dns_left_len(context) < opt_len) {\n\t\t\ttlog(TLOG_DEBUG, \"read opt data failed, opt_code = %d, opt_len = %d\", opt_code, opt_len);\n\t\t\treturn -1;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"opt type %d\", opt_code);\n\t\tswitch (opt_code) {\n\t\tcase DNS_OPT_T_ECS: {\n\t\t\tstruct dns_opt_ecs ecs;\n\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\tret = _dns_decode_opt_ecs(context, &ecs, opt_len);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"decode ecs failed.\");\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tret = dns_add_OPT_ECS(packet, &ecs);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add ecs failed.\");\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t} break;\n\t\tcase DNS_OPT_T_COOKIE: {\n\t\t\tstruct dns_opt_cookie cookie;\n\t\t\tret = _dns_decode_opt_cookie(context, &cookie, opt_len);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"decode cookie failed.\");\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t} break;\n\t\tcase DNS_OPT_T_TCP_KEEPALIVE: {\n\t\t\tunsigned short timeout = 0;\n\t\t\tret = _dns_decode_opt_tcp_keepalive(context, &timeout, opt_len);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"decode tcp keepalive failed.\");\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tret = dns_add_OPT_TCP_KEEPALIVE(packet, timeout);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add tcp keepalive failed.\");\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t} break;\n\t\tcase DNS_OPT_T_PADDING:\n\t\t\tcontext->ptr += opt_len;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcontext->ptr += opt_len;\n\t\t\ttlog(TLOG_DEBUG, \"DNS opt type = %d not supported\", opt_code);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_encode_svcparam(struct dns_context *context, struct dns_rrs *rrs)\n{\n\tint ret = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tchar target[DNS_MAX_CNAME_LEN] = {0};\n\tunsigned char *rr_len_ptr = NULL;\n\tunsigned char *start = NULL;\n\tunsigned char *rr_start = NULL;\n\tint ttl = 0;\n\tint priority = 0;\n\tstruct dns_svcparam *param = NULL;\n\n\tret =\n\t\tdns_svcparm_start(rrs, &param, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);\n\tif (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"get https param failed.\");\n\t\treturn 0;\n\t}\n\n\tqtype = rrs->type;\n\tqclass = DNS_C_IN;\n\n\tret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, 0, &rr_len_ptr);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\trr_start = context->ptr;\n\tif (_dns_left_len(context) < 2) {\n\t\ttlog(TLOG_ERROR, \"left len is invalid.\");\n\t\treturn -1;\n\t}\n\n\t_dns_write_short(&context->ptr, priority);\n\tret = _dns_encode_domain(context, target);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tstart = context->ptr;\n\tfor (; param != NULL; param = dns_svcparm_next(rrs, param)) {\n\t\tif (context->ptr - start > rrs->len || _dns_left_len(context) <= 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (param->len + 4 > _dns_left_len(context)) {\n\t\t\treturn -1;\n\t\t}\n\n\t\t_dns_write_short(&context->ptr, param->key);\n\t\t_dns_write_short(&context->ptr, param->len);\n\t\tswitch (param->key) {\n\t\tcase DNS_HTTPS_T_MANDATORY:\n\t\tcase DNS_HTTPS_T_NO_DEFAULT_ALPN:\n\t\tcase DNS_HTTPS_T_ALPN:\n\t\tcase DNS_HTTPS_T_PORT:\n\t\tcase DNS_HTTPS_T_IPV4HINT:\n\t\tcase DNS_HTTPS_T_ECH:\n\t\tcase DNS_HTTPS_T_IPV6HINT: {\n\t\t\tmemcpy(context->ptr, param->value, param->len);\n\t\t\tcontext->ptr += param->len;\n\t\t} break;\n\t\tdefault:\n\t\t\t/* skip unknown key */\n\t\t\tcontext->ptr -= 4;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (_dns_left_len(context) < 2) {\n\t\treturn -1;\n\t}\n\n\t_dns_write_short(&rr_len_ptr, context->ptr - rr_start);\n\n\treturn 0;\n}\n\nstatic int _dns_decode_SVCB_HTTPS(struct dns_context *context, const char *domain, dns_rr_type type, dns_type_t qtype,\n\t\t\t\t\t\t\t unsigned int ttl, int rr_len)\n{\n\tunsigned char *start = context->ptr;\n\n\tstruct dns_packet *packet = context->packet;\n\tint ret = 0;\n\tunsigned short priority;\n\tunsigned short key;\n\tunsigned short value_len;\n\tunsigned char *value = NULL;\n\tchar target[DNS_MAX_CNAME_LEN] = {0};\n\tstruct dns_rr_nested param;\n\n\tif (rr_len < 2) {\n\t\ttlog(TLOG_DEBUG, \"https len is invalid.\");\n\t\treturn -1;\n\t}\n\n\tif (_dns_left_len(context) < rr_len) {\n\t\ttlog(TLOG_DEBUG, \"https data length exceeds buffer.\");\n\t\treturn -1;\n\t}\n\n\tpriority = _dns_read_short(&context->ptr);\n\tret = _dns_decode_domain(context, target, sizeof(target));\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\t/* Use unified function with qtype parameter */\n\t_dns_add_SVCB_HTTPS_start(&param, packet, DNS_RRS_AN, qtype, domain, ttl, priority, target);\n\n\twhile (context->ptr - start < rr_len) {\n\t\tif (_dns_left_len(context) < 4) {\n\t\t\ttlog(TLOG_WARN, \"data length is invalid, %d:%d\", _dns_left_len(context),\n\t\t\t\t (int)(context->ptr - context->data));\n\t\t\treturn -1;\n\t\t}\n\t\tkey = _dns_read_short(&context->ptr);\n\t\tvalue_len = _dns_read_short(&context->ptr);\n\t\tvalue = context->ptr;\n\n\t\tif (_dns_left_len(context) < value_len) {\n\t\t\ttlog(TLOG_ERROR, \"read https data failed, svcParam key = %d, https_len = %d\", key, value_len);\n\t\t\treturn -1;\n\t\t}\n\n\t\tswitch (key) {\n\t\tcase DNS_HTTPS_T_MANDATORY:\n\t\tcase DNS_HTTPS_T_ALPN:\n\t\tcase DNS_HTTPS_T_NO_DEFAULT_ALPN:\n\t\tcase DNS_HTTPS_T_PORT:\n\t\tcase DNS_HTTPS_T_IPV4HINT:\n\t\tcase DNS_HTTPS_T_ECH:\n\t\tcase DNS_HTTPS_T_IPV6HINT: {\n\t\t\tdns_SVCB_add_raw(&param, key, value, value_len);\n\t\t} break;\n\t\tdefault:\n\t\t\ttlog(TLOG_DEBUG, \"DNS HTTPS key = %d not supported\", key);\n\t\t\tbreak;\n\t\t}\n\n\t\tcontext->ptr += value_len;\n\t}\n\n\t/* Use unified end function */\n\tdns_add_SVCB_end(&param);\n\n\treturn 0;\n}\n\nstatic int _dns_decode_qd(struct dns_context *context)\n{\n\tstruct dns_packet *packet = context->packet;\n\tint len = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\n\tlen = _dns_decode_qr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass);\n\tif (len < 0) {\n\t\treturn -1;\n\t}\n\n\tlen = dns_add_domain(packet, domain, qtype, qclass);\n\tif (len < 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_decode_an(struct dns_context *context, dns_rr_type type)\n{\n\tint ret = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tint ttl = 0;\n\tint rr_len = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tstruct dns_packet *packet = context->packet;\n\tunsigned char *start = NULL;\n\n\t/* decode rr head */\n\tret = _dns_decode_rr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len);\n\tif (ret < 0 || qclass < 0) {\n\t\ttlog(TLOG_DEBUG, \"decode head failed.\");\n\t\treturn -1;\n\t}\n\tstart = context->ptr;\n\n\t/* decode answer */\n\tswitch (qtype) {\n\tcase DNS_T_A: {\n\t\tunsigned char addr[DNS_RR_A_LEN];\n\t\tret = _dns_decode_raw(context, addr, sizeof(addr));\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode A failed, %s, len: %d:%d\", domain, (int)(context->ptr - context->data),\n\t\t\t\t _dns_left_len(context));\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = dns_add_A(packet, type, domain, ttl, addr);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"add A failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tcase DNS_T_CNAME: {\n\t\tchar cname[DNS_MAX_CNAME_LEN];\n\t\tret = _dns_decode_CNAME(context, cname, DNS_MAX_CNAME_LEN);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode CNAME failed, %s, len: %d:%d\", domain, (int)(context->ptr - context->data),\n\t\t\t\t _dns_left_len(context));\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = dns_add_CNAME(packet, type, domain, ttl, cname);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"add CNAME failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tcase DNS_T_SOA: {\n\t\tstruct dns_soa soa;\n\t\tret = _dns_decode_SOA(context, &soa);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode SOA failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = dns_add_SOA(packet, type, domain, ttl, &soa);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"add SOA failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tcase DNS_T_NS: {\n\t\tchar ns[DNS_MAX_CNAME_LEN];\n\t\tret = _dns_decode_CNAME(context, ns, DNS_MAX_CNAME_LEN);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode NS failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = dns_add_NS(packet, type, domain, ttl, ns);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"add NS failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tcase DNS_T_PTR: {\n\t\tchar name[DNS_MAX_CNAME_LEN];\n\t\tret = _dns_decode_CNAME(context, name, DNS_MAX_CNAME_LEN);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode PTR failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = dns_add_PTR(packet, type, domain, ttl, name);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"add PTR failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tcase DNS_T_AAAA: {\n\t\tunsigned char addr[DNS_RR_AAAA_LEN];\n\t\tret = _dns_decode_raw(context, addr, sizeof(addr));\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode AAAA failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = dns_add_AAAA(packet, type, domain, ttl, addr);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"add AAAA failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tcase DNS_T_SRV: {\n\t\tunsigned short priority = 0;\n\t\tunsigned short weight = 0;\n\t\tunsigned short port = 0;\n\t\tchar target[DNS_MAX_CNAME_LEN];\n\n\t\tret = _dns_decode_SRV(context, &priority, &weight, &port, target, DNS_MAX_CNAME_LEN);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode SRV failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = dns_add_SRV(packet, type, domain, ttl, priority, weight, port, target);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"add SRV failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tcase DNS_T_OPT: {\n\t\tunsigned char *opt_start = context->ptr;\n\t\tret = _dns_decode_opt(context, type, ttl, rr_len);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode opt failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (context->ptr - opt_start != rr_len) {\n\t\t\ttlog(TLOG_DEBUG, \"opt length mismatch, %s\\n\", domain);\n\t\t\treturn -1;\n\t\t}\n\t\tdns_set_OPT_option(packet, ttl);\n\t\tdns_set_OPT_payload_size(packet, qclass);\n\t} break;\n\tcase DNS_T_HTTPS:\n\tcase DNS_T_SVCB: {\n\t\tunsigned char *https_start = context->ptr;\n\t\tret = _dns_decode_SVCB_HTTPS(context, domain, type, qtype, ttl, rr_len);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode HTTPS failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (context->ptr - https_start != rr_len) {\n\t\t\ttlog(TLOG_DEBUG, \"opt length mismatch, %s\\n\", domain);\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tdefault: {\n\t\tunsigned char raw_data[1024];\n\t\tif (_dns_left_len(context) < rr_len || rr_len >= (int)sizeof(raw_data)) {\n\t\t\ttlog(TLOG_DEBUG, \"length mismatch\\n\");\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = _dns_decode_raw(context, raw_data, rr_len);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode A failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = _dns_add_RAW(packet, type, qtype, domain, ttl, raw_data, rr_len);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"add raw failed, %s\", domain);\n\t\t\treturn -1;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"DNS type = %d not supported\", qtype);\n\t\tbreak;\n\t}\n\t}\n\n\tif (context->ptr - start != rr_len) {\n\t\ttlog(TLOG_DEBUG, \"length mismatch, %s, %ld:%d\", domain, (long)(context->ptr - start), rr_len);\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_encode_qd(struct dns_context *context, struct dns_rrs *rrs)\n{\n\tint ret = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tstruct dns_context data_context;\n\n\t_dns_init_context_by_rrs(rrs, &data_context);\n\tret = _dns_get_qr_head(&data_context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_encode_qr_head(context, domain, qtype, qclass);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tif (domain[0] == '-') {\n\t\t/* for google and cloudflare */\n\t\tunsigned char *ptr = context->ptr - 7;\n\t\tmemcpy(ptr, \"\\xC0\\x12\", 2);\n\t\tptr += 2;\n\t\t_dns_write_short(&ptr, qtype);\n\t\t_dns_write_short(&ptr, qclass);\n\t\tcontext->ptr = ptr;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_encode_an(struct dns_context *context, struct dns_rrs *rrs)\n{\n\tint ret = 0;\n\tswitch (rrs->type) {\n\tcase DNS_T_A:\n\tcase DNS_T_AAAA: {\n\t\tret = _dns_encode_raw(context, rrs);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t} break;\n\tcase DNS_T_CNAME:\n\tcase DNS_T_PTR:\n\t\tret = _dns_encode_CNAME(context, rrs);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tbreak;\n\tcase DNS_T_SOA:\n\t\tret = _dns_encode_SOA(context, rrs);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tbreak;\n\tcase DNS_T_SRV:\n\t\tret = _dns_encode_SRV(context, rrs);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tbreak;\n\tcase DNS_T_HTTPS:\n\tcase DNS_T_SVCB:\n\t\tret = _dns_encode_svcparam(context, rrs);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tret = _dns_encode_raw(context, rrs);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tbreak;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_decode_body(struct dns_context *context)\n{\n\tstruct dns_packet *packet = context->packet;\n\tstruct dns_head *head = &packet->head;\n\tint i = 0;\n\tint ret = 0;\n\tint count = 0;\n\n\tcount = head->qdcount;\n\thead->qdcount = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = _dns_decode_qd(context);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode qd failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tcount = head->ancount;\n\thead->ancount = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = _dns_decode_an(context, DNS_RRS_AN);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode an failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tcount = head->nscount;\n\thead->nscount = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = _dns_decode_an(context, DNS_RRS_NS);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode ns failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tcount = head->nrcount;\n\thead->nrcount = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = _dns_decode_an(context, DNS_RRS_NR);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"decode nr failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_encode_body(struct dns_context *context)\n{\n\tstruct dns_packet *packet = context->packet;\n\tstruct dns_head *head = &packet->head;\n\tint i = 0;\n\tint len = 0;\n\tstruct dns_rrs *rrs = NULL;\n\tint count = 0;\n\n\trrs = dns_get_rrs_start(packet, DNS_RRS_QD, &count);\n\thead->qdcount = count;\n\tfor (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\tlen = _dns_encode_qd(context, rrs);\n\t\tif (len < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\trrs = dns_get_rrs_start(packet, DNS_RRS_AN, &count);\n\thead->ancount = count;\n\tfor (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\tlen = _dns_encode_an(context, rrs);\n\t\tif (len < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\trrs = dns_get_rrs_start(packet, DNS_RRS_NS, &count);\n\thead->nscount = count;\n\tfor (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\tlen = _dns_encode_an(context, rrs);\n\t\tif (len < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\trrs = dns_get_rrs_start(packet, DNS_RRS_NR, &count);\n\thead->nrcount = count;\n\tfor (i = 0; i < count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\tlen = _dns_encode_an(context, rrs);\n\t\tif (len < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\trrs = dns_get_rrs_start(packet, DNS_RRS_OPT, &count);\n\tif (count > 0 || packet->payloadsize > 0) {\n\t\tlen = _dns_encode_opts(packet, context, rrs, count);\n\t\tif (len < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\thead->nrcount++;\n\t}\n\n\treturn 0;\n}\n\nint dns_packet_init(struct dns_packet *packet, int size, struct dns_head *head)\n{\n\tstruct dns_head *init_head = &packet->head;\n\tif (size < (int)sizeof(*packet)) {\n\t\treturn -1;\n\t}\n\n\tmemset(packet, 0, size);\n\tpacket->size = size;\n\tinit_head->id = head->id;\n\tinit_head->qr = head->qr;\n\tinit_head->opcode = head->opcode;\n\tinit_head->aa = head->aa;\n\tinit_head->tc = head->tc;\n\tinit_head->rd = head->rd;\n\tinit_head->ra = head->ra;\n\tinit_head->z = head->z;\n\tinit_head->ad = head->ad;\n\tinit_head->cd = head->cd;\n\tinit_head->rcode = head->rcode;\n\tpacket->questions = DNS_RR_END;\n\tpacket->answers = DNS_RR_END;\n\tpacket->nameservers = DNS_RR_END;\n\tpacket->additional = DNS_RR_END;\n\tpacket->optional = DNS_RR_END;\n\tpacket->optcount = 0;\n\tpacket->payloadsize = 0;\n\n\treturn 0;\n}\n\nint dns_decode_head_only(struct dns_packet *packet, int maxsize, unsigned char *data, int size)\n{\n\tstruct dns_head *head = &packet->head;\n\tstruct dns_context context;\n\tint ret = 0;\n\n\tmemset(&context, 0, sizeof(context));\n\tmemset(packet, 0, sizeof(*packet));\n\n\tcontext.data = data;\n\tcontext.packet = packet;\n\tcontext.ptr = data;\n\tcontext.maxsize = size;\n\tcontext.namedict = &packet->namedict;\n\n\tret = dns_packet_init(packet, maxsize, head);\n\tif (ret != 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_decode_head(&context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tpacket->size = context.ptr - context.data + sizeof(*packet);\n\n\treturn 0;\n}\n\nint dns_decode(struct dns_packet *packet, int maxsize, unsigned char *data, int size)\n{\n\tstruct dns_head *head = &packet->head;\n\tstruct dns_context context;\n\tint ret = 0;\n\n\tmemset(&context, 0, sizeof(context));\n\tmemset(packet, 0, sizeof(*packet));\n\n\tcontext.data = data;\n\tcontext.packet = packet;\n\tcontext.ptr = data;\n\tcontext.maxsize = size;\n\tcontext.namedict = &packet->namedict;\n\n\tret = dns_packet_init(packet, maxsize, head);\n\tif (ret != 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_decode_head(&context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_decode_body(&context);\n\tif (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"decode body failed.\\n\");\n\t\treturn -1;\n\t}\n\n\tpacket->size = context.ptr - context.data + sizeof(*packet);\n\n\treturn 0;\n}\n\nint dns_encode(unsigned char *data, int size, struct dns_packet *packet)\n{\n\tint ret = 0;\n\tstruct dns_context context;\n\tstruct dns_packet_dict namedict;\n\n\tmemset(&context, 0, sizeof(context));\n\tmemset(&namedict, 0, sizeof(namedict));\n\tcontext.data = data;\n\tcontext.packet = packet;\n\tcontext.ptr = data;\n\tcontext.maxsize = size;\n\tcontext.namedict = &namedict;\n\n\tret = _dns_encode_head(&context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_encode_body(&context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_encode_head_count(&context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\treturn context.ptr - context.data;\n}\n\nstatic int _dns_update_domain(struct dns_context *context, const char *domain)\n{\n\tint len = 0;\n\tint ptr_jump = 0;\n\tsize_t output_len = 0;\n\tunsigned char *ptr = context->ptr;\n\tunsigned char *packet = context->data;\n\tint packet_size = context->maxsize;\n\tsize_t domain_len = strlen(domain);\n\tsize_t processed_len = 0;\n\n\twhile (1) {\n\t\tif (ptr >= packet + packet_size || ptr < packet || ptr_jump > 32 || processed_len > domain_len + 1) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tlen = *ptr;\n\t\tif (len == 0) {\n\t\t\tptr++;\n\t\t\tprocessed_len++;\n\t\t\tbreak;\n\t\t}\n\n\t\t/* compressed domain */\n\t\tif (len >= 0xC0) {\n\t\t\tif ((ptr + 2) > (packet + packet_size)) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t/* read offset */\n\t\t\tlen = _dns_read_short(&ptr) & 0x3FFF;\n\t\t\tptr = packet + len;\n\t\t\tif (ptr > packet + packet_size) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tptr_jump++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tptr_jump = 0;\n\n\t\tif (output_len > 0) {\n\t\t\toutput_len += 1;\n\t\t}\n\n\t\tif (ptr > packet + packet_size) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tptr++;\n\t\t/* update domain */\n\t\tmemcpy(ptr, domain + processed_len, len);\n\t\tptr += len;\n\t\tprocessed_len += len + 1;\n\t\toutput_len += len;\n\t}\n\n\tif (output_len != domain_len) {\n\t\ttlog(TLOG_DEBUG, \"update domain failed, length mismatch. output_len: %zu, domain_len: %zu\", output_len,\n\t\t\t domain_len);\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_update_rr_domain(struct dns_context *context, unsigned char *rr_start, const char *domain,\n\t\t\t\t\t\t\t\t struct dns_update_param *param)\n{\n\tconst char *query_domain = param->query_domain;\n\tunsigned char *old_context_ptr = context->ptr;\n\n\tif (param->query_domain == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (strncasecmp(domain, query_domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\treturn 0;\n\t}\n\n\tcontext->ptr = rr_start;\n\tif (_dns_update_domain(context, query_domain) != 0) {\n\t\ttlog(TLOG_DEBUG, \"update domain failed, %s\", domain);\n\t\tcontext->ptr = old_context_ptr;\n\t\treturn -1;\n\t}\n\n\tcontext->ptr = old_context_ptr;\n\n\treturn 0;\n}\n\nstatic int _dns_update_qd(struct dns_context *context, dns_rr_type type, struct dns_update_param *param)\n{\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tint qtype = 0;\n\tint qclass = 0;\n\tint len = 0;\n\tunsigned char *rr_start = context->ptr;\n\n\tlen = _dns_decode_qr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass);\n\tif (len < 0) {\n\t\ttlog(TLOG_DEBUG, \"update qd failed.\");\n\t\treturn -1;\n\t}\n\n\tint ret = _dns_update_rr_domain(context, rr_start, domain, param);\n\tif (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"domain not match, %s\", domain);\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_update_an(struct dns_context *context, dns_rr_type type, struct dns_update_param *param)\n{\n\tint ret = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tint ttl = 0;\n\tint rr_len = 0;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tunsigned char *start = NULL;\n\tunsigned char *rr_start = context->ptr;\n\n\t/* decode rr head */\n\tret = _dns_decode_rr_head(context, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass, &ttl, &rr_len);\n\tif (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"decode head failed.\");\n\t\treturn -1;\n\t}\n\n\tret = _dns_update_rr_domain(context, rr_start, domain, param);\n\tif (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"domain not match, %s\", domain);\n\t\treturn -1;\n\t}\n\n\tstart = context->ptr;\n\tswitch (qtype) {\n\tcase DNS_T_OPT:\n\t\tbreak;\n\tdefault: {\n\t\tunsigned char *ttl_ptr = start - sizeof(int) - sizeof(short);\n\t\tif (param->ip_ttl < 0) {\n\t\t\tbreak;\n\t\t}\n\t\t_dns_write_int(&ttl_ptr, param->ip_ttl);\n\t} break;\n\t}\n\tcontext->ptr += rr_len;\n\tif (context->ptr - start != rr_len) {\n\t\ttlog(TLOG_ERROR, \"length mismatch , %s, %ld:%d\", domain, (long)(context->ptr - start), rr_len);\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_update_body(struct dns_context *context, struct dns_update_param *param)\n{\n\tstruct dns_packet *packet = context->packet;\n\tstruct dns_head *head = &packet->head;\n\tint i = 0;\n\tint ret = 0;\n\tint count = 0;\n\n\tcount = head->qdcount;\n\thead->qdcount = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = _dns_update_qd(context, DNS_RRS_QD, param);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"update qd failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tcount = head->ancount;\n\thead->ancount = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = _dns_update_an(context, DNS_RRS_AN, param);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"update an failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tcount = head->nscount;\n\thead->nscount = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = _dns_update_an(context, DNS_RRS_NS, param);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"update ns failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tcount = head->nrcount;\n\thead->nrcount = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tret = _dns_update_an(context, DNS_RRS_NR, param);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_DEBUG, \"update nr failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_update_id(unsigned char *data, int size, struct dns_update_param *param)\n{\n\tunsigned char *ptr = data;\n\t_dns_write_short(&ptr, param->id);\n\treturn 0;\n}\n\nint dns_packet_update(unsigned char *data, int size, struct dns_update_param *param)\n{\n\tstruct dns_packet packet;\n\tint maxsize = sizeof(packet);\n\tstruct dns_head *head = &packet.head;\n\tstruct dns_context context;\n\tint ret = 0;\n\n\tmemset(&context, 0, sizeof(context));\n\tmemset(&packet, 0, sizeof(packet));\n\n\tcontext.data = data;\n\tcontext.packet = &packet;\n\tcontext.ptr = data;\n\tcontext.maxsize = size;\n\n\tret = dns_packet_init(&packet, maxsize, head);\n\tif (ret != 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_decode_head(&context);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_update_id(data, size, param);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tret = _dns_update_body(&context, param);\n\tif (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"decode body failed.\\n\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_cache.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/dns_cache.h\"\n#include \"smartdns/dns_stats.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/timer.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n#include <errno.h>\n#include <fcntl.h>\n#include <pthread.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/types.h>\n\n#define DNS_CACHE_MAX_HITNUM 6000\n#define DNS_CACHE_HITNUM_STEP 3\n#define DNS_CACHE_HITNUM_STEP_MAX 6\n#define DNS_CACHE_READ_TIMEOUT 60\n#define DNS_CACHE_FAIL_TIMEOUT (60 * 5)\n#define EXPIRED_DOMAIN_PREFETCH_TIME (3600 * 8)\n\nstruct dns_cache_head {\n\tstruct hash_table cache_hash;\n\tstruct list_head cache_list;\n\tatomic_t num;\n\tatomic_t mem_size;\n\tint size;\n\tlong max_mem_size;\n\tpthread_mutex_t lock;\n\tdns_cache_callback timeout_callback;\n};\n\ntypedef int (*dns_cache_read_callback)(struct dns_cache_record *cache_record, struct dns_cache_data *cache_data);\n\nstatic int is_cache_init;\nstatic struct dns_cache_head dns_cache_head;\n\nint dns_cache_init(int size, int mem_size, dns_cache_callback timeout_callback)\n{\n\tint bits = 0;\n\tpthread_mutexattr_t mta;\n\tif (is_cache_init == 1) {\n\t\treturn -1;\n\t}\n\n\tINIT_LIST_HEAD(&dns_cache_head.cache_list);\n\n\tbits = ilog2(size) - 1;\n\tif (bits >= 20) {\n\t\tbits = 20;\n\t} else if (bits < 12) {\n\t\tbits = 12;\n\t}\n\n\thash_table_init(dns_cache_head.cache_hash, bits);\n\tatomic_set(&dns_cache_head.num, 0);\n\tatomic_set(&dns_cache_head.mem_size, 0);\n\tdns_cache_head.size = size;\n\tdns_cache_head.max_mem_size = mem_size;\n\tif (mem_size > 0) {\n\t\tdns_cache_head.size = INT32_MAX;\n\t}\n\tdns_cache_head.timeout_callback = timeout_callback;\n\tpthread_mutexattr_init(&mta);\n\tpthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE);\n\tpthread_mutex_init(&dns_cache_head.lock, &mta);\n\tpthread_mutexattr_destroy(&mta);\n\n\tis_cache_init = 1;\n\treturn 0;\n}\n\nstatic struct dns_cache *_dns_cache_first(void)\n{\n\treturn list_first_entry_or_null(&dns_cache_head.cache_list, struct dns_cache, list);\n}\n\nstatic void _dns_cache_delete(struct dns_cache *dns_cache)\n{\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\thash_del(&dns_cache->node);\n\tlist_del_init(&dns_cache->list);\n\tif (dns_timer_del(&dns_cache->timer)) {\n\t\ttlog(TLOG_DEBUG, \"dns cache timer is still pending when delete dns cache.\");\n\t}\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\tatomic_dec(&dns_cache_head.num);\n\tatomic_sub(sizeof(*dns_cache), &dns_cache_head.mem_size);\n\tif (dns_cache->cache_data) {\n\t\tdns_cache_data_put(dns_cache->cache_data);\n\t}\n\n\tdns_cache->cache_data = NULL;\n\tfree(dns_cache);\n}\n\nvoid dns_cache_get(struct dns_cache *dns_cache)\n{\n\tif (atomic_inc_return(&dns_cache->ref) == 1) {\n\t\tBUG(\"BUG: dns_cache is invalid.\");\n\t\treturn;\n\t}\n}\n\nvoid dns_cache_release(struct dns_cache *dns_cache)\n{\n\tint refcnt = 0;\n\tif (dns_cache == NULL) {\n\t\treturn;\n\t}\n\n\trefcnt = atomic_dec_return(&dns_cache->ref);\n\tif (refcnt > 0) {\n\t\treturn;\n\t} else if (refcnt < 0) {\n\t\tBUG(\"dns_cache refcnt is invalid: %d\", refcnt);\n\t\treturn;\n\t}\n\n\t_dns_cache_delete(dns_cache);\n}\n\nstatic void _dns_cache_remove(struct dns_cache *dns_cache)\n{\n\t/*\n\t * If the list is empty, it means the cache has been removed by other threads.\n\t * The reference count has been decreased by the other thread.\n\t * We should simply return to avoid double free.\n\t */\n\tif (list_empty(&dns_cache->list)) {\n\t\treturn;\n\t}\n\n\thash_del(&dns_cache->node);\n\tlist_del_init(&dns_cache->list);\n\tif (dns_timer_del(&dns_cache->timer)) {\n\t\tdns_cache_release(dns_cache);\n\t}\n\tdns_cache_release(dns_cache);\n}\n\nuint32_t dns_cache_get_query_flag(struct dns_cache *dns_cache)\n{\n\treturn dns_cache->info.query_flag;\n}\n\nconst char *dns_cache_get_dns_group_name(struct dns_cache *dns_cache)\n{\n\treturn dns_cache->info.dns_group_name;\n}\n\nstruct dns_cache_data *dns_cache_new_data_packet(void *packet, size_t packet_len)\n{\n\tstruct dns_cache_packet *cache_packet = NULL;\n\tsize_t data_size = 0;\n\tif (packet == NULL || packet_len <= 0) {\n\t\treturn NULL;\n\t}\n\n\tdata_size = sizeof(*cache_packet) + packet_len;\n\tcache_packet = zalloc(1, data_size);\n\tif (cache_packet == NULL) {\n\t\treturn NULL;\n\t}\n\n\tmemcpy(cache_packet->data, packet, packet_len);\n\n\tcache_packet->head.size = packet_len;\n\tcache_packet->head.magic = MAGIC_CACHE_DATA;\n\tatomic_set(&cache_packet->head.ref, 1);\n\tatomic_add(data_size, &dns_cache_head.mem_size);\n\n\treturn (struct dns_cache_data *)cache_packet;\n}\n\nstatic void dns_cache_expired(struct tw_base *base, struct tw_timer_list *timer, void *data, unsigned long timestamp)\n{\n\tstruct dns_cache *dns_cache = data;\n\tint mod_ret = 0;\n\n\tif (dns_cache_head.timeout_callback) {\n\t\tdns_cache_tmout_action_t tmout_act = dns_cache_head.timeout_callback(dns_cache);\n\t\tswitch (tmout_act) {\n\t\tcase DNS_CACHE_TMOUT_ACTION_OK:\n\t\t\tbreak;\n\t\tcase DNS_CACHE_TMOUT_ACTION_UPDATE:\n\t\t\tmod_ret = dns_timer_mod(&dns_cache->timer, dns_cache->info.timeout);\n\t\t\tgoto out;\n\t\tcase DNS_CACHE_TMOUT_ACTION_DEL:\n\t\t\tdns_cache_delete(dns_cache);\n\t\t\tgoto out;\n\t\tcase DNS_CACHE_TMOUT_ACTION_RETRY:\n\t\t\tmod_ret = dns_timer_mod(&dns_cache->timer, DNS_CACHE_FAIL_TIMEOUT);\n\t\t\tgoto out;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tdns_cache_release(dns_cache);\n\treturn;\nout:\n\tif (mod_ret == 0) {\n\t\tdns_cache_release(dns_cache);\n\t}\n}\n\nstatic struct dns_cache *_dns_cache_lookup(struct dns_cache_key *cache_key)\n{\n\tuint32_t key = 0;\n\tstruct dns_cache *dns_cache = NULL;\n\tstruct dns_cache *dns_cache_ret = NULL;\n\ttime_t now = 0;\n\n\tkey = hash_string_case(cache_key->domain);\n\tkey = jhash(&cache_key->qtype, sizeof(cache_key->qtype), key);\n\tkey = hash_string_initval(cache_key->dns_group_name, key);\n\tkey = jhash(&cache_key->query_flag, sizeof(cache_key->query_flag), key);\n\n\ttime(&now);\n\t/* find cache */\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\thash_table_for_each_possible(dns_cache_head.cache_hash, dns_cache, node, key)\n\t{\n\t\tif (dns_cache->info.qtype != cache_key->qtype) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncasecmp(cache_key->domain, dns_cache->info.domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(cache_key->dns_group_name, dns_cache->info.dns_group_name, DNS_GROUP_NAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (cache_key->query_flag != dns_cache->info.query_flag) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tdns_cache_ret = dns_cache;\n\t\tbreak;\n\t}\n\n\tif (dns_cache_ret) {\n\t\tdns_cache_get(dns_cache_ret);\n\t}\n\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\n\treturn dns_cache_ret;\n}\n\nstruct dns_cache *dns_cache_lookup(struct dns_cache_key *cache_key)\n{\n\tstruct dns_cache *dns_cache_ret = NULL;\n\n\tif (dns_cache_head.size <= 0) {\n\t\treturn NULL;\n\t}\n\n\tstats_inc(&dns_stats.cache.check_count);\n\tdns_cache_ret = _dns_cache_lookup(cache_key);\n\n\tif (dns_cache_ret) {\n\t\tstats_inc(&dns_stats.cache.hit_count);\n\t}\n\n\treturn dns_cache_ret;\n}\n\nstatic int _dns_cache_replace(struct dns_cache_key *cache_key, int rcode, int ttl, int speed, int timeout,\n\t\t\t\t\t\t\t  int update_time, struct dns_cache_data *cache_data)\n{\n\tstruct dns_cache *dns_cache = NULL;\n\tstruct dns_cache_data *old_cache_data = NULL;\n\n\tif (dns_cache_head.size <= 0) {\n\t\treturn 0;\n\t}\n\n\t/* lookup existing cache */\n\tdns_cache = _dns_cache_lookup(cache_key);\n\tif (dns_cache == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (ttl < DNS_CACHE_TTL_MIN) {\n\t\tttl = DNS_CACHE_TTL_MIN;\n\t}\n\n\t/* update cache data */\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\tdns_cache->info.rcode = rcode;\n\tdns_cache->info.qtype = cache_key->qtype;\n\tdns_cache->info.query_flag = cache_key->query_flag;\n\tdns_cache->info.ttl = ttl;\n\tdns_cache->info.speed = speed;\n\tdns_cache->info.timeout = timeout;\n\tdns_cache->info.is_visited = 1;\n\tif (cache_data) {\n\t\told_cache_data = dns_cache->cache_data;\n\t\tdns_cache->cache_data = cache_data;\n\t}\n\n\tif (update_time) {\n\t\ttime(&dns_cache->info.insert_time);\n\t}\n\ttime(&dns_cache->info.replace_time);\n\tlist_del(&dns_cache->list);\n\tlist_add_tail(&dns_cache->list, &dns_cache_head.cache_list);\n\tif (dns_timer_mod(&dns_cache->timer, timeout) == 0) {\n\t\tdns_cache_get(dns_cache);\n\t\tdns_cache->timer.expires = timeout;\n\t\tdns_timer_add(&dns_cache->timer);\n\t}\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\n\tif (old_cache_data) {\n\t\tdns_cache_data_put(old_cache_data);\n\t}\n\tdns_cache_release(dns_cache);\n\treturn 0;\n}\n\nint dns_cache_replace(struct dns_cache_key *cache_key, int rcode, int ttl, int speed, int timeout, int update_time,\n\t\t\t\t\t  struct dns_cache_data *cache_data)\n{\n\treturn _dns_cache_replace(cache_key, rcode, ttl, speed, timeout, update_time, cache_data);\n}\n\nstatic void _dns_cache_remove_by_domain(struct dns_cache_key *cache_key)\n{\n\tuint32_t key = 0;\n\tstruct dns_cache *dns_cache = NULL;\n\n\tkey = hash_string_case(cache_key->domain);\n\tkey = jhash(&cache_key->qtype, sizeof(cache_key->qtype), key);\n\tkey = hash_string_initval(cache_key->dns_group_name, key);\n\tkey = jhash(&cache_key->query_flag, sizeof(cache_key->query_flag), key);\n\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\thash_table_for_each_possible(dns_cache_head.cache_hash, dns_cache, node, key)\n\t{\n\t\tif (dns_cache->info.qtype != cache_key->qtype) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (dns_cache->info.query_flag != cache_key->query_flag) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncasecmp(cache_key->domain, dns_cache->info.domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(dns_cache->info.dns_group_name, cache_key->dns_group_name, DNS_GROUP_NAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_dns_cache_remove(dns_cache);\n\t\tbreak;\n\t}\n\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n}\n\nstatic int _dns_cache_insert(struct dns_cache_info *info, struct dns_cache_data *cache_data, struct list_head *head,\n\t\t\t\t\t\t\t int timeout)\n{\n\tuint32_t key = 0;\n\tstruct dns_cache *dns_cache = NULL;\n\tint loop_count = 0;\n\n\tif (cache_data == NULL || info == NULL) {\n\t\tgoto errout;\n\t}\n\n\t/* if cache already exists, free */\n\tstruct dns_cache_key cache_key;\n\tcache_key.qtype = info->qtype;\n\tcache_key.query_flag = info->query_flag;\n\tcache_key.domain = info->domain;\n\tcache_key.dns_group_name = info->dns_group_name;\n\t_dns_cache_remove_by_domain(&cache_key);\n\n\tdns_cache = zalloc(1, sizeof(*dns_cache));\n\tif (dns_cache == NULL) {\n\t\tgoto errout;\n\t}\n\tkey = hash_string_case(info->domain);\n\tkey = jhash(&info->qtype, sizeof(info->qtype), key);\n\tkey = hash_string_initval(info->dns_group_name, key);\n\tkey = jhash(&info->query_flag, sizeof(info->query_flag), key);\n\tatomic_set(&dns_cache->ref, 1);\n\tmemcpy(&dns_cache->info, info, sizeof(*info));\n\tdns_cache->cache_data = cache_data;\n\tdns_cache->timer.function = dns_cache_expired;\n\tdns_cache->timer.expires = timeout;\n\tdns_cache->timer.data = dns_cache;\n\tINIT_LIST_HEAD(&dns_cache->check_list);\n\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\tatomic_inc(&dns_cache_head.num);\n\n\t/* Release extra cache, remove oldest cache record */\n\tdo {\n\t\tint need_remove = 0;\n\n\t\tif (dns_cache_head.max_mem_size > 0 && atomic_read(&dns_cache_head.mem_size) > dns_cache_head.max_mem_size) {\n\t\t\tneed_remove = 1;\n\t\t}\n\n\t\tif (atomic_read(&dns_cache_head.num) > dns_cache_head.size) {\n\t\t\tneed_remove = 1;\n\t\t}\n\n\t\tif (need_remove == 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\tstruct dns_cache *del_cache = _dns_cache_first();\n\t\tif (del_cache == NULL) {\n\t\t\tbreak;\n\t\t}\n\n\t\t_dns_cache_remove(del_cache);\n\t} while (loop_count++ < 32);\n\n\thash_table_add(dns_cache_head.cache_hash, &dns_cache->node, key);\n\tlist_add_tail(&dns_cache->list, head);\n\tatomic_add(sizeof(*dns_cache), &dns_cache_head.mem_size);\n\n\tdns_cache_get(dns_cache);\n\tdns_timer_add(&dns_cache->timer);\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\n\treturn 0;\nerrout:\n\tif (dns_cache) {\n\t\tdns_cache_release(dns_cache);\n\t}\n\n\treturn -1;\n}\n\nint dns_cache_insert(struct dns_cache_key *cache_key, int rcode, int ttl, int speed, int timeout,\n\t\t\t\t\t struct dns_cache_data *cache_data)\n{\n\tstruct dns_cache_info info;\n\n\tif (cache_data == NULL || cache_key == NULL || cache_key->dns_group_name == NULL || cache_key->domain == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (dns_cache_head.size <= 0) {\n\t\tdns_cache_data_put(cache_data);\n\t\treturn 0;\n\t}\n\n\tif (ttl < DNS_CACHE_TTL_MIN) {\n\t\tttl = DNS_CACHE_TTL_MIN;\n\t}\n\n\tmemset(&info, 0, sizeof(info));\n\tinfo.hitnum = 3;\n\tsafe_strncpy(info.domain, cache_key->domain, DNS_MAX_CNAME_LEN);\n\tinfo.qtype = cache_key->qtype;\n\tsafe_strncpy(info.dns_group_name, cache_key->dns_group_name, DNS_GROUP_NAME_LEN);\n\tinfo.query_flag = cache_key->query_flag;\n\tinfo.ttl = ttl;\n\tinfo.hitnum_update_add = DNS_CACHE_HITNUM_STEP;\n\tinfo.speed = speed;\n\tinfo.timeout = timeout;\n\tinfo.is_visited = 1;\n\tinfo.rcode = rcode;\n\ttime(&info.insert_time);\n\ttime(&info.replace_time);\n\n\treturn _dns_cache_insert(&info, cache_data, &dns_cache_head.cache_list, timeout);\n}\n\nint dns_cache_update_timer(struct dns_cache_key *key, int timeout)\n{\n\tstruct dns_cache *dns_cache = _dns_cache_lookup(key);\n\tif (dns_cache == NULL) {\n\t\treturn -1;\n\t}\n\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\tif (dns_timer_mod(&dns_cache->timer, timeout) == 0) {\n\t\tdns_cache_get(dns_cache);\n\t\tdns_cache->timer.expires = timeout;\n\t\tdns_timer_add(&dns_cache->timer);\n\t}\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\n\tdns_cache_release(dns_cache);\n\n\treturn 0;\n}\n\nint dns_cache_get_ttl(struct dns_cache *dns_cache)\n{\n\ttime_t now = 0;\n\tint ttl = 0;\n\ttime(&now);\n\n\tttl = dns_cache->info.insert_time + dns_cache->info.ttl - now;\n\tif (ttl < 0) {\n\t\treturn 0;\n\t}\n\n\treturn ttl;\n}\n\nstruct dns_cache_data *dns_cache_get_data(struct dns_cache *dns_cache)\n{\n\tstruct dns_cache_data *cache_data;\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\tif (dns_cache->cache_data == NULL) {\n\t\tpthread_mutex_unlock(&dns_cache_head.lock);\n\t\treturn NULL;\n\t}\n\n\tdns_cache_data_get(dns_cache->cache_data);\n\tcache_data = dns_cache->cache_data;\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\treturn cache_data;\n}\n\nvoid dns_cache_data_get(struct dns_cache_data *cache_data)\n{\n\tif (atomic_inc_return(&cache_data->head.ref) == 1) {\n\t\ttlog(TLOG_ERROR, \"BUG: dns_cache data is invalid.\");\n\t\treturn;\n\t}\n\n\treturn;\n}\n\nvoid dns_cache_flush(void)\n{\n\tstruct dns_cache *dns_cache = NULL;\n\tstruct dns_cache *tmp = NULL;\n\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\tlist_for_each_entry_safe(dns_cache, tmp, &dns_cache_head.cache_list, list)\n\t{\n\t\t_dns_cache_remove(dns_cache);\n\t}\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n}\n\nvoid dns_cache_data_put(struct dns_cache_data *cache_data)\n{\n\tif (cache_data == NULL) {\n\t\treturn;\n\t}\n\n\tif (!atomic_dec_and_test(&cache_data->head.ref)) {\n\t\treturn;\n\t}\n\n\tatomic_sub(cache_data->head.size + sizeof(*cache_data), &dns_cache_head.mem_size);\n\tfree(cache_data);\n}\n\nint dns_cache_is_visited(struct dns_cache *dns_cache)\n{\n\treturn dns_cache->info.is_visited;\n}\n\nint dns_cache_total_num(void)\n{\n\treturn atomic_read(&dns_cache_head.num);\n}\n\nlong dns_cache_total_memsize(void)\n{\n\treturn atomic_read(&dns_cache_head.mem_size);\n}\n\nvoid dns_cache_delete(struct dns_cache *dns_cache)\n{\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\t_dns_cache_remove(dns_cache);\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n}\n\nint dns_cache_hitnum_dec_get(struct dns_cache *dns_cache)\n{\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\tdns_cache->info.hitnum--;\n\tif (dns_cache->info.hitnum_update_add > DNS_CACHE_HITNUM_STEP) {\n\t\tdns_cache->info.hitnum_update_add--;\n\t}\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\n\treturn dns_cache->info.hitnum;\n}\n\nvoid dns_cache_update(struct dns_cache *dns_cache)\n{\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\tif (!list_empty(&dns_cache->list)) {\n\t\tlist_del_init(&dns_cache->list);\n\t\tlist_add_tail(&dns_cache->list, &dns_cache_head.cache_list);\n\t\tdns_cache->info.hitnum += dns_cache->info.hitnum_update_add;\n\t\tif (dns_cache->info.hitnum > DNS_CACHE_MAX_HITNUM) {\n\t\t\tdns_cache->info.hitnum = DNS_CACHE_MAX_HITNUM;\n\t\t}\n\n\t\tif (dns_cache->info.hitnum_update_add < DNS_CACHE_HITNUM_STEP_MAX) {\n\t\t\tdns_cache->info.hitnum_update_add++;\n\t\t}\n\t\tdns_cache->info.is_visited = 1;\n\t}\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n}\n\nstatic int _dns_cache_read_to_cache(struct dns_cache_record *cache_record, struct dns_cache_data *cache_data)\n{\n\tstruct list_head *head = NULL;\n\thead = &dns_cache_head.cache_list;\n\tstruct dns_cache_info *info = &cache_record->info;\n\tint expired_time = 0;\n\n\ttime_t now = time(NULL);\n\tif (now < info->replace_time) {\n\t\tinfo->replace_time = now;\n\t}\n\n\tint passed_time = now - info->replace_time;\n\tint timeout = info->timeout - passed_time;\n\n\tstruct dns_conf_group *rule_group = dns_server_get_rule_group(info->dns_group_name);\n\n\tif (rule_group->dns_prefetch) {\n\t\tif (rule_group->dns_serve_expired) {\n\t\t\texpired_time = rule_group->dns_serve_expired_prefetch_time;\n\t\t\tif (expired_time == 0) {\n\t\t\t\texpired_time = rule_group->dns_serve_expired_ttl / 2;\n\t\t\t\tif (expired_time == 0 || expired_time > EXPIRED_DOMAIN_PREFETCH_TIME) {\n\t\t\t\t\texpired_time = EXPIRED_DOMAIN_PREFETCH_TIME;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\ttimeout -= 3;\n\t\t}\n\t}\n\n\tif ((timeout > expired_time + info->ttl) && expired_time > 0) {\n\t\ttimeout = expired_time + info->ttl;\n\t}\n\n\tif (timeout < DNS_CACHE_READ_TIMEOUT * 2) {\n\t\ttimeout = DNS_CACHE_READ_TIMEOUT + (rand() % DNS_CACHE_READ_TIMEOUT);\n\t}\n\n\tdns_cache_data_get(cache_data);\n\tif (_dns_cache_insert(&cache_record->info, cache_data, head, timeout) != 0) {\n\t\ttlog(TLOG_ERROR, \"insert cache data failed.\");\n\t\tdns_cache_data_put(cache_data);\n\t\tcache_data = NULL;\n\t\tgoto errout;\n\t}\n\n\tdaemon_keepalive();\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstatic int _dns_cache_read_record(int fd, uint32_t cache_number, dns_cache_read_callback callback)\n{\n\tunsigned int i = 0;\n\tssize_t ret = 0;\n\tint data_size = 0;\n\tstruct dns_cache_record cache_record;\n\tstruct dns_cache_data_head data_head;\n\tstruct dns_cache_data *cache_data = NULL;\n\n\tfor (i = 0; i < cache_number; i++) {\n\t\tret = read(fd, &cache_record, sizeof(cache_record));\n\t\tif (ret != sizeof(cache_record)) {\n\t\t\ttlog(TLOG_ERROR, \"read cache failed, %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (cache_record.magic != MAGIC_RECORD) {\n\t\t\ttlog(TLOG_ERROR, \"magic is invalid.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tret = read(fd, &data_head, sizeof(data_head));\n\t\tif (ret != sizeof(data_head)) {\n\t\t\ttlog(TLOG_ERROR, \"read data head failed, %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (data_head.magic != MAGIC_CACHE_DATA) {\n\t\t\ttlog(TLOG_ERROR, \"data magic is invalid.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (data_head.size > 1024 * 8) {\n\t\t\ttlog(TLOG_ERROR, \"data may invalid, skip load cache.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tdata_size = data_head.size + sizeof(data_head);\n\t\tcache_data = zalloc(1, data_size);\n\t\tif (cache_data == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"malloc cache data failed %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\tmemcpy(&cache_data->head, &data_head, sizeof(data_head));\n\t\tatomic_set(&cache_data->head.ref, 1);\n\t\tret = read(fd, cache_data->data, data_head.size);\n\t\tif (ret != data_head.size) {\n\t\t\ttlog(TLOG_ERROR, \"read cache data failed, %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\t/* set cache unvisited, so that when refreshing ipset/nftset, reload ipset list by restarting smartdns */\n\t\tcache_record.info.is_visited = 0;\n\t\tcache_record.info.domain[DNS_MAX_CNAME_LEN - 1] = '\\0';\n\t\tcache_record.info.dns_group_name[DNS_GROUP_NAME_LEN - 1] = '\\0';\n\t\tatomic_add(data_size, &dns_cache_head.mem_size);\n\t\tret = callback(&cache_record, cache_data);\n\t\tdns_cache_data_put(cache_data);\n\t\tcache_data = NULL;\n\t\tif (ret != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\tif (cache_data) {\n\t\tdns_cache_data_put(cache_data);\n\t}\n\treturn -1;\n}\n\nstatic int _dns_cache_file_read(const char *file, dns_cache_read_callback callback)\n{\n\tint fd = -1;\n\tssize_t ret = 0;\n\toff_t filesize = 0;\n\n\tfd = open(file, O_RDONLY);\n\tif (fd < 0) {\n\t\treturn 0;\n\t}\n\n\tfilesize = lseek(fd, 0, SEEK_END);\n\tlseek(fd, 0, SEEK_SET);\n\tposix_fadvise(fd, 0, filesize, POSIX_FADV_WILLNEED | POSIX_FADV_SEQUENTIAL);\n\n\tstruct dns_cache_file cache_file;\n\tret = read(fd, &cache_file, sizeof(cache_file));\n\tif (ret != sizeof(cache_file)) {\n\t\ttlog(TLOG_ERROR, \"read cache head failed.\");\n\t\tgoto errout;\n\t}\n\n\tif (cache_file.magic != MAGIC_NUMBER) {\n\t\ttlog(TLOG_ERROR, \"cache file is invalid.\");\n\t\tgoto errout;\n\t}\n\n\tif (strncmp(cache_file.version, dns_cache_file_version(), DNS_CACHE_VERSION_LEN) != 0) {\n\t\ttlog(TLOG_WARN, \"cache version is different, skip load cache.\");\n\t\tgoto errout;\n\t}\n\n\ttlog(TLOG_INFO, \"load cache file %s, total %d records\", file, cache_file.cache_number);\n\tif (_dns_cache_read_record(fd, cache_file.cache_number, callback) != 0) {\n\t\tgoto errout;\n\t}\n\n\tclose(fd);\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\treturn -1;\n}\n\nint dns_cache_load(const char *file)\n{\n\treturn _dns_cache_file_read(file, _dns_cache_read_to_cache);\n}\n\nstatic int _dns_cache_write_record(int fd, uint32_t *cache_number, struct list_head *head)\n{\n\tstruct dns_cache *dns_cache = NULL;\n\tstruct dns_cache *tmp = NULL;\n\tstruct dns_cache_record cache_record;\n\n\tmemset(&cache_record, 0, sizeof(cache_record));\n\n\tpthread_mutex_lock(&dns_cache_head.lock);\n\tlist_for_each_entry_safe(dns_cache, tmp, head, list)\n\t{\n\t\tstruct dns_cache_data *cache_data = dns_cache->cache_data;\n\t\tif (cache_data == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tcache_record.magic = MAGIC_RECORD;\n\t\tmemcpy(&cache_record.info, &dns_cache->info, sizeof(struct dns_cache_info));\n\t\tssize_t ret = write(fd, &cache_record, sizeof(cache_record));\n\t\tif (ret != sizeof(cache_record)) {\n\t\t\ttlog(TLOG_ERROR, \"write cache failed, %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\tret = write(fd, cache_data, sizeof(*cache_data) + cache_data->head.size);\n\t\tif (ret != (int)sizeof(*cache_data) + cache_data->head.size) {\n\t\t\ttlog(TLOG_ERROR, \"write cache data failed, %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\t(*cache_number)++;\n\t}\n\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\treturn 0;\n\nerrout:\n\tpthread_mutex_unlock(&dns_cache_head.lock);\n\treturn -1;\n}\n\nstatic int _dns_cache_write_records(int fd, uint32_t *cache_number)\n{\n\tif (_dns_cache_write_record(fd, cache_number, &dns_cache_head.cache_list) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint dns_cache_save(const char *file, int check_lock)\n{\n\tint fd = -1;\n\tuint32_t cache_number = 0;\n\ttlog(TLOG_DEBUG, \"write cache file %s\", file);\n\n\t/* check lock */\n\tif (check_lock == 1) {\n\t\tif (pthread_mutex_trylock(&dns_cache_head.lock) != 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tpthread_mutex_unlock(&dns_cache_head.lock);\n\t}\n\n\tfd = open(file, O_TRUNC | O_CREAT | O_WRONLY, 0640);\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"create file %s failed, %s\", file, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tstruct dns_cache_file cache_file;\n\tmemset(&cache_file, 0, sizeof(cache_file));\n\tcache_file.magic = MAGIC_NUMBER;\n\tsafe_strncpy(cache_file.version, dns_cache_file_version(), DNS_CACHE_VERSION_LEN);\n\tcache_file.cache_number = 0;\n\n\tif (lseek(fd, sizeof(cache_file), SEEK_SET) < 0) {\n\t\ttlog(TLOG_ERROR, \"seek file %s failed, %s\", file, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (_dns_cache_write_records(fd, &cache_number) != 0) {\n\t\ttlog(TLOG_ERROR, \"write record to file %s failed.\", file);\n\t\tgoto errout;\n\t}\n\n\tif (lseek(fd, 0, SEEK_SET) < 0) {\n\t\ttlog(TLOG_ERROR, \"seek file %s failed, %s\", file, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tcache_file.cache_number = cache_number;\n\tif (write(fd, &cache_file, sizeof(cache_file)) != sizeof(cache_file)) {\n\t\ttlog(TLOG_ERROR, \"write file head %s failed, %s, %d\", file, strerror(errno), fd);\n\t\tgoto errout;\n\t}\n\n\ttlog(TLOG_DEBUG, \"wrote total %d records.\", cache_number);\n\n\tclose(fd);\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\treturn -1;\n}\n\nstatic int _dns_cache_print(struct dns_cache_record *cache_record, struct dns_cache_data *cache_data)\n{\n\tchar req_result[1024] = {0};\n\tint left_len = sizeof(req_result);\n\tchar *ip_msg = req_result;\n\tlong i, j;\n\n\tif (cache_record->info.qtype == DNS_T_A || cache_record->info.qtype == DNS_T_AAAA) {\n\t\tchar buff[DNS_PACKSIZE];\n\t\tstruct dns_packet *packet = (struct dns_packet *)buff;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\tint rr_count = 0;\n\t\tint ttl = 0;\n\t\tint ip_num = 0;\n\t\tint total_len = 0;\n\t\tint len = 0;\n\t\tint has_result = 0;\n\t\tchar req_host[MAX_IP_LEN];\n\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\n\t\tif (dns_decode(packet, DNS_PACKSIZE, cache_data->data, cache_data->head.size) == 0) {\n\t\t\ttotal_len = snprintf(ip_msg, left_len, \", result: \");\n\t\t\tfor (j = 1; j < DNS_RRS_OPT && packet; j++) {\n\t\t\t\trrs = dns_get_rrs_start(packet, j, &rr_count);\n\t\t\t\tfor (i = 0; i < rr_count && rrs && left_len > 0; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\t\t\t\tswitch (rrs->type) {\n\t\t\t\t\tcase DNS_T_A: {\n\t\t\t\t\t\tunsigned char ipv4_addr[4];\n\t\t\t\t\t\tif (dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv4_addr) != 0) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst char *fmt = \"%d.%d.%d.%d\";\n\t\t\t\t\t\tif (ip_num > 0) {\n\t\t\t\t\t\t\tfmt = \", %d.%d.%d.%d\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlen = snprintf(ip_msg + total_len, left_len, fmt, ipv4_addr[0], ipv4_addr[1], ipv4_addr[2],\n\t\t\t\t\t\t\t\t\t   ipv4_addr[3]);\n\t\t\t\t\t\tip_num++;\n\t\t\t\t\t\thas_result = 1;\n\t\t\t\t\t} break;\n\t\t\t\t\tcase DNS_T_AAAA: {\n\t\t\t\t\t\tunsigned char ipv6_addr[16];\n\t\t\t\t\t\tif (dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv6_addr) != 0) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst char *fmt = \"%s\";\n\t\t\t\t\t\tif (ip_num > 0) {\n\t\t\t\t\t\t\tfmt = \", %s\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\treq_host[0] = '\\0';\n\t\t\t\t\t\tinet_ntop(AF_INET6, ipv6_addr, req_host, sizeof(req_host));\n\t\t\t\t\t\tlen = snprintf(ip_msg + total_len, left_len, fmt, req_host);\n\t\t\t\t\t\tip_num++;\n\t\t\t\t\t\thas_result = 1;\n\t\t\t\t\t} break;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (len < 0 || len >= left_len) {\n\t\t\t\t\t\tleft_len = 0;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tleft_len -= len;\n\t\t\t\t\ttotal_len += len;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (has_result == 0) {\n\t\t\treq_result[0] = '\\0';\n\t\t}\n\t}\n\n\tprintf(\"domain: %s, qtype: %d, rcode: %d, ttl: %d, speed: %.1fms%s\\n\", cache_record->info.domain,\n\t\t   cache_record->info.qtype, cache_record->info.rcode, cache_record->info.ttl,\n\t\t   (float)cache_record->info.speed / 10, ip_msg);\n\treturn 0;\n}\n\nint dns_cache_print(const char *file)\n{\n\tif (access(file, F_OK) != 0) {\n\t\ttlog(TLOG_ERROR, \"cache file %s not exist.\", file);\n\t\treturn -1;\n\t}\n\n\treturn _dns_cache_file_read(file, _dns_cache_print);\n}\n\nvoid dns_cache_destroy(void)\n{\n\tif (is_cache_init == 0) {\n\t\treturn;\n\t}\n\n\tdns_cache_flush();\n\n\tpthread_mutex_destroy(&dns_cache_head.lock);\n\thash_table_free(dns_cache_head.cache_hash, free);\n\n\tis_cache_init = 0;\n}\n\nconst char *dns_cache_file_version(void)\n{\n\tconst char *version = \"cache ver 1.3\";\n\treturn version;\n}\n"
  },
  {
    "path": "src/dns_client/client_http2.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"client_http2.h\"\n#include \"client_socket.h\"\n#include \"client_tls.h\"\n#include \"conn_stream.h\"\n#include \"server_info.h\"\n\n#include \"smartdns/http2.h\"\n\n#include <string.h>\n\n/* BIO read callback for HTTP/2 */\nstatic int _http2_bio_read(void *private_data, uint8_t *buf, int len)\n{\n\tstruct dns_server_info *server_info = (struct dns_server_info *)private_data;\n\treturn _dns_client_socket_ssl_recv(server_info, buf, len);\n}\n\n/* BIO write callback for HTTP/2 */\nstatic int _http2_bio_write(void *private_data, const uint8_t *buf, int len)\n{\n\tstruct dns_server_info *server_info = (struct dns_server_info *)private_data;\n\treturn _dns_client_socket_ssl_send(server_info, buf, len);\n}\n\n/* Helper function to send buffered data from a conn_stream via HTTP/2 */\nstatic int _dns_client_send_http2_stream(struct dns_server_info *server_info, struct dns_conn_stream *conn_stream,\n\t\t\t\t\t\t\t\t\t\t void *data, unsigned short len)\n{\n\tstruct http2_ctx *http2_ctx = NULL;\n\tstruct http2_stream *http2_stream = NULL;\n\tstruct client_dns_server_flag_https *https_flag = &server_info->flags.https;\n\tchar content_length[32];\n\n\tpthread_mutex_lock(&server_info->lock);\n\thttp2_ctx = server_info->http2_ctx;\n\tif (http2_ctx == NULL) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\treturn -1;\n\t}\n\t/* Get reference to prevent it from being freed while we use it */\n\thttp2_ctx_get(http2_ctx);\n\tpthread_mutex_unlock(&server_info->lock);\n\n\t/* Create HTTP/2 stream */\n\thttp2_stream = http2_stream_new(http2_ctx);\n\tif (http2_stream == NULL) {\n\t\tif (errno != ENOSPC) {\n\t\t\ttlog(TLOG_WARN, \"create http2 stream failed\");\n\t\t}\n\t\thttp2_ctx_put(http2_ctx);\n\t\treturn -1;\n\t}\n\n\t/* Set request headers */\n\tsnprintf(content_length, sizeof(content_length), \"%d\", len);\n\tstruct http2_header_pair headers[] = {{\"content-type\", \"application/dns-message\"},\n\t\t\t\t\t\t\t\t\t\t  {\"accept\", \"application/dns-message\"},\n\t\t\t\t\t\t\t\t\t\t  {\"content-length\", content_length},\n\t\t\t\t\t\t\t\t\t\t  {NULL, NULL}};\n\n\tif (http2_stream_set_request(http2_stream, \"POST\", https_flag->path, NULL, headers) < 0) {\n\t\tgoto errout;\n\t}\n\n\t/* Write request body */\n\tif (http2_stream_write_body(http2_stream, (const uint8_t *)data, len, 1) < 0) {\n\t\tgoto errout;\n\t}\n\n\tpthread_mutex_lock(&server_info->lock);\n\tconn_stream->http2_stream = http2_stream;\n\tpthread_mutex_unlock(&server_info->lock);\n\thttp2_stream_set_ex_data(http2_stream, conn_stream);\n\thttp2_ctx_put(http2_ctx);\n\treturn 0;\n\nerrout:\n\thttp2_stream_close(http2_stream);\n\thttp2_ctx_put(http2_ctx);\n\treturn -1;\n}\n\n/* Helper function to release a conn_stream and its references on error */\nstatic void _dns_client_release_stream_on_error(struct dns_server_info *server_info, struct dns_conn_stream *stream)\n{\n\tif (!stream) {\n\t\treturn;\n\t}\n\n\tpthread_mutex_lock(&server_info->lock);\n\n\t/* Remove from server list and release reference */\n\tif (!list_empty(&stream->server_list)) {\n\t\tlist_del_init(&stream->server_list);\n\t\tstream->server_info = NULL;\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\n\tpthread_mutex_unlock(&server_info->lock);\n\n\t/* Release the initial reference from creation */\n\t_dns_client_conn_stream_put(stream);\n}\n\n/* Helper function to flush pending HTTP/2 writes */\nstatic void _dns_client_flush_http2_writes(struct http2_ctx *http2_ctx)\n{\n\tint loop = 0;\n\n\twhile (http2_ctx_want_write(http2_ctx) && loop++ < 10) {\n\t\tif (http2_ctx_poll(http2_ctx, NULL, 0, NULL) < 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/* Helper function to send all buffered HTTP/2 requests */\nstatic void _dns_client_send_buffered_http2_requests(struct dns_server_info *server_info)\n{\n\tstruct dns_conn_stream *conn_stream = NULL;\n\tstruct dns_conn_stream *tmp = NULL;\n\n\twhile (1) {\n\t\tstruct dns_conn_stream *target_stream = NULL;\n\n\t\tpthread_mutex_lock(&server_info->lock);\n\t\tlist_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, server_list)\n\t\t{\n\t\t\tif (conn_stream->http2_stream != NULL || conn_stream->send_buff.len <= 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttarget_stream = conn_stream;\n\t\t\t_dns_client_conn_stream_get(target_stream);\n\t\t\tbreak;\n\t\t}\n\t\tpthread_mutex_unlock(&server_info->lock);\n\n\t\tif (target_stream == NULL) {\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Send buffered request using helper function */\n\t\tif (_dns_client_send_http2_stream(server_info, target_stream, target_stream->send_buff.data,\n\t\t\t\t\t\t\t\t\t\t  target_stream->send_buff.len) == 0) {\n\t\t\t/* Clear buffer as it's now in HTTP/2 stream buffer */\n\t\t\ttarget_stream->send_buff.len = 0;\n\t\t\t_dns_client_conn_stream_put(target_stream);\n\t\t} else {\n\t\t\t/* Send failed, remove from buffer and clean up */\n\t\t\t_dns_client_release_stream_on_error(server_info, target_stream);\n\t\t}\n\t}\n}\n\n/* Helper function to buffer data for HTTP/2 when connection is not ready */\nstatic int _dns_client_http2_pending_data(struct dns_conn_stream *stream, struct dns_server_info *server_info,\n\t\t\t\t\t\t\t\t\t\t  struct dns_query_struct *query, void *packet, int len)\n{\n\tstruct epoll_event event;\n\t\n\t/* Validate input parameters */\n\tif (len <= 0 || len > DNS_IN_PACKSIZE - 128) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\t\n\tif (DNS_TCP_BUFFER - stream->send_buff.len < len) {\n\t\terrno = ENOMEM;\n\t\treturn -1;\n\t}\n\n\tif (client.epoll_fd <= 0) {\n\t\terrno = ECONNRESET;\n\t\tgoto errout;\n\t}\n\n\tmemcpy(stream->send_buff.data + stream->send_buff.len, packet, len);\n\tstream->send_buff.len += len;\n\n\tpthread_mutex_lock(&server_info->lock);\n\tif (server_info->fd <= 0) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\terrno = ECONNRESET;\n\t\tgoto errout;\n\t}\n\n\tstream->server_info = server_info;\n\tif (list_empty(&stream->server_list)) {\n\t\t_dns_client_conn_stream_get(stream);\n\t\tlist_add_tail(&stream->server_list, &server_info->conn_stream_list);\n\t}\n\n\tif (list_empty(&stream->query_list)) {\n\t\t_dns_client_conn_stream_get(stream);\n\t\tpthread_mutex_lock(&query->lock);\n\t\tstream->query = query;\n\t\tlist_add_tail(&stream->query_list, &query->conn_stream_list);\n\t\tpthread_mutex_unlock(&query->lock);\n\t}\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLOUT;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\tgoto errout_put;\n\t}\n\tpthread_mutex_unlock(&server_info->lock);\n\n\treturn 0;\nerrout_put:\n\t/* Clean up stream on error */\n\tpthread_mutex_lock(&server_info->lock);\n\tif (!list_empty(&stream->server_list)) {\n\t\tlist_del_init(&stream->server_list);\n\t\tstream->server_info = NULL;\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\tif (!list_empty(&stream->query_list)) {\n\t\tif (stream->query) {\n\t\t\tpthread_mutex_lock(&stream->query->lock);\n\t\t\tlist_del_init(&stream->query_list);\n\t\t\tpthread_mutex_unlock(&stream->query->lock);\n\t\t\tstream->query = NULL;\n\t\t}\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\tpthread_mutex_unlock(&server_info->lock);\nerrout:\n\treturn -1;\n}\n\nint _dns_client_send_http2(struct dns_server_info *server_info, struct dns_query_struct *query, void *packet,\n\t\t\t\t\t\t   unsigned short len)\n{\n\tstruct dns_conn_stream *stream = NULL;\n\tstruct http2_ctx *http2_ctx = NULL;\n\tint ret = -1;\n\n\tif (len > DNS_IN_PACKSIZE - 128) {\n\t\ttlog(TLOG_ERROR, \"packet size is invalid.\");\n\t\tret = -1;\n\t\tgoto out;\n\t}\n\n\t/* Create connection stream for this request */\n\tstream = _dns_client_conn_stream_new();\n\tif (stream == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc memory failed for http2 stream.\");\n\t\treturn -1;\n\t}\n\tstream->type = DNS_SERVER_HTTPS;\n\n\t/* If not connected, buffer the data and return */\n\tif (server_info->status != DNS_SERVER_STATUS_CONNECTED) {\n\t\tret = _dns_client_http2_pending_data(stream, server_info, query, packet, len);\n\t\tgoto out;\n\t}\n\n\t/* If connected but context not ready, buffer it too (will be flushed in process_http2) */\n\tif (server_info->http2_ctx == NULL) {\n\t\tret = _dns_client_http2_pending_data(stream, server_info, query, packet, len);\n\t\tgoto out;\n\t}\n\n\t/* Send the request via HTTP/2 */\n\tret = _dns_client_send_http2_stream(server_info, stream, packet, len);\n\tif (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"send http2 stream failed.\");\n\t\t/* Fall back to buffering the data */\n\t\tret = _dns_client_http2_pending_data(stream, server_info, query, packet, len);\n\t\tgoto out;\n\t}\n\n\t/* Now add stream to lists since HTTP/2 stream was successfully created */\n\tpthread_mutex_lock(&server_info->lock);\n\t_dns_client_conn_stream_get(stream);\n\tstream->server_info = server_info;\n\tlist_add_tail(&stream->server_list, &server_info->conn_stream_list);\n\n\t_dns_client_conn_stream_get(stream);\n\tpthread_mutex_lock(&query->lock);\n\tstream->query = query;\n\tlist_add_tail(&stream->query_list, &query->conn_stream_list);\n\tpthread_mutex_unlock(&query->lock);\n\tpthread_mutex_unlock(&server_info->lock);\n\n\t/* Flush data immediately */\n\tint loop = 0;\n\twhile (http2_ctx_want_write(http2_ctx) && loop++ < 10) {\n\t\tif (http2_ctx_poll(http2_ctx, NULL, 0, NULL) < 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Check if there's pending write data, if so add EPOLLOUT event */\n\tif (http2_ctx_want_write(http2_ctx)) {\n\t\tstruct epoll_event event;\n\t\tmemset(&event, 0, sizeof(event));\n\t\tevent.events = EPOLLIN | EPOLLOUT;\n\t\tevent.data.ptr = server_info;\n\t\tif (server_info->fd > 0) {\n\t\t\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\t\t\t/* Continue anyway, data will be sent on next EPOLLIN */\n\t\t\t}\n\t\t}\n\t}\n\n\tret = 0;\nout:\n\tif (stream) {\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_client_http2_init_ctx(struct dns_server_info *server_info)\n{\n\tstruct http2_ctx *http2_ctx = server_info->http2_ctx;\n\tstruct client_dns_server_flag_https *https_flag = &server_info->flags.https;\n\tint ret = 0;\n\n\tif (http2_ctx != NULL) {\n\t\treturn 0;\n\t}\n\n\tpthread_mutex_lock(&server_info->lock);\n\tif (server_info->http2_ctx == NULL) {\n\t\thttp2_ctx = http2_ctx_client_new(https_flag->httphost, _http2_bio_read, _http2_bio_write, server_info, NULL);\n\t\tif (http2_ctx == NULL) {\n\t\t\tpthread_mutex_unlock(&server_info->lock);\n\t\t\ttlog(TLOG_ERROR, \"init http2 context failed.\");\n\t\t\treturn -1;\n\t\t}\n\t\tserver_info->http2_ctx = http2_ctx;\n\t\t/* server_info now owns the context (refcount=1 from _new) */\n\t\tpthread_mutex_unlock(&server_info->lock);\n\n\t\t/* Perform HTTP/2 handshake */\n\t\tret = http2_ctx_handshake(http2_ctx);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_ERROR, \"http2 handshake failed.\");\n\t\t\treturn -1;\n\t\t}\n\t} else {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_client_http2_process_write(struct dns_server_info *server_info)\n{\n\tstruct http2_ctx *http2_ctx = NULL;\n\tint epoll_events = EPOLLIN;\n\n\t/* Send buffered requests */\n\t_dns_client_send_buffered_http2_requests(server_info);\n\n\tpthread_mutex_lock(&server_info->lock);\n\thttp2_ctx = server_info->http2_ctx;\n\tif (http2_ctx == NULL) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\treturn 0;\n\t}\n\thttp2_ctx_get(http2_ctx);\n\tpthread_mutex_unlock(&server_info->lock);\n\n\t/* Flush pending writes */\n\t_dns_client_flush_http2_writes(http2_ctx);\n\n\t/* Update epoll events based on write status */\n\tif (http2_ctx_want_write(http2_ctx)) {\n\t\tepoll_events |= EPOLLOUT;\n\t}\n\n\tif (server_info->fd > 0) {\n\t\tstruct epoll_event mod_event;\n\t\tmemset(&mod_event, 0, sizeof(mod_event));\n\t\tmod_event.events = epoll_events;\n\t\tmod_event.data.ptr = server_info;\n\t\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &mod_event) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\t\thttp2_ctx_put(http2_ctx);\n\t\t\treturn -1;\n\t\t}\n\t}\n\thttp2_ctx_put(http2_ctx);\n\treturn 0;\n}\n\nstatic int _dns_client_http2_process_stream_one(struct dns_server_info *server_info,\n\t\t\t\t\t\t\t\t\t\t\t\tstruct dns_conn_stream *conn_stream)\n{\n\tstruct http2_stream *http2_stream = NULL;\n\tuint8_t response_body[DNS_IN_PACKSIZE];\n\tint response_len = 0;\n\tint ret = 0;\n\n\tif (conn_stream == NULL) {\n\t\treturn 1;\n\t}\n\n\thttp2_stream = conn_stream->http2_stream;\n\tif (http2_stream == NULL || conn_stream->query == NULL) {\n\t\treturn 1;\n\t}\n\n\t/* Check HTTP status code first */\n\tint status = http2_stream_get_status(http2_stream);\n\tif (status > 0 && status != 200) {\n\t\ttlog(TLOG_WARN, \"http2 server query from %s:%d failed, server return http code: %d\", server_info->ip,\n\t\t\t server_info->port, status);\n\t\tserver_info->prohibit = 1;\n\t\treturn 1;\n\t}\n\n\t/* Read response body */\n\tresponse_len = http2_stream_read_body(http2_stream, response_body, sizeof(response_body));\n\tif (response_len <= 0) {\n\t\t/* Error or no data - check if stream has ended */\n\t\tgoto out;\n\t}\n\n\t/* Process DNS response */\n\tret = _dns_client_recv(server_info, response_body, response_len, &server_info->addr, server_info->ai_addrlen);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"process dns response failed\");\n\t}\n\nout:\n\tif (http2_stream_is_end(http2_stream)) {\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_client_http2_process_read(struct dns_server_info *server_info)\n{\n\tstruct http2_ctx *http2_ctx = NULL;\n\tstruct http2_poll_item poll_items[128];\n\tint poll_count = 0;\n\tint loop_count = 0;\n\tconst int MAX_LOOP_COUNT = 512;\n\tstruct dns_conn_stream *conn_stream = NULL;\n\tint ret = 0;\n\tint i = 0;\n\n\tpthread_mutex_lock(&server_info->lock);\n\thttp2_ctx = server_info->http2_ctx;\n\tif (http2_ctx == NULL) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\treturn 0;\n\t}\n\thttp2_ctx_get(http2_ctx);\n\tpthread_mutex_unlock(&server_info->lock);\n\n\t/* Ensure handshake is complete before polling */\n\tret = http2_ctx_handshake(http2_ctx);\n\tif (ret == 0) {\n\t\t/* Handshake in progress, need more data */\n\t\thttp2_ctx_put(http2_ctx);\n\t\treturn 0;\n\t} else if (ret < 0) {\n\t\ttlog(TLOG_DEBUG, \"http2 handshake failed.\");\n\t\thttp2_ctx_put(http2_ctx);\n\t\treturn -1;\n\t}\n\n\t/* Poll and process streams until no more ready */\n\twhile (loop_count++ < MAX_LOOP_COUNT) {\n\t\t/* Poll for stream readiness */\n\t\tret = http2_ctx_poll_readable(http2_ctx, poll_items, 128, &poll_count);\n\t\tif (ret < 0) {\n\t\t\tif (ret != HTTP2_ERR_EOF) {\n\t\t\t\ttlog(TLOG_DEBUG, \"http2 poll failed, ret=%d\", ret);\n\t\t\t}\n\t\t\thttp2_ctx_put(http2_ctx);\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (poll_count == 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Process each ready stream */\n\t\tfor (i = 0; i < poll_count; i++) {\n\t\t\tstruct http2_stream *stream = poll_items[i].stream;\n\t\t\tif (stream == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconn_stream = (struct dns_conn_stream *)http2_stream_get_ex_data(stream);\n\t\t\tif (conn_stream == NULL) {\n\t\t\t\thttp2_stream_put(stream);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Get reference to conn_stream to prevent it from being freed while we use it */\n\t\t\t_dns_client_conn_stream_get(conn_stream);\n\t\t\tif (poll_items[i].readable) {\n\t\t\t\tint stream_ended = _dns_client_http2_process_stream_one(server_info, conn_stream);\n\t\t\t\tif (stream_ended) {\n\t\t\t\t\tint need_put = 0;\n\t\t\t\t\tpthread_mutex_lock(&server_info->lock);\n\t\t\t\t\tif (!list_empty(&conn_stream->server_list)) {\n\t\t\t\t\t\tlist_del_init(&conn_stream->server_list);\n\t\t\t\t\t\tconn_stream->server_info = NULL;\n\t\t\t\t\t\tneed_put = 1;\n\t\t\t\t\t}\n\t\t\t\t\tpthread_mutex_unlock(&server_info->lock);\n\n\t\t\t\t\tif (need_put) {\n\t\t\t\t\t\t_dns_client_conn_stream_put(conn_stream);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* Release our reference */\n\t\t\t_dns_client_conn_stream_put(conn_stream);\n\t\t\thttp2_stream_put(stream);\n\t\t}\n\n\t\tif (poll_count < 128) {\n\t\t\tbreak;\n\t\t}\n\t}\n\thttp2_ctx_put(http2_ctx);\n\treturn 0;\n}\n\nint _dns_client_process_http2(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now)\n{\n\tif (server_info->http2_ctx == NULL) {\n\t\tif (_dns_client_http2_init_ctx(server_info) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (event->events & EPOLLOUT) {\n\t\tif (_dns_client_http2_process_write(server_info) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/* Always process read, as write might have read data (e.g. WINDOW_UPDATE),\n\t   or there might be pending data in SSL/HTTP2 buffers */\n\tif (_dns_client_http2_process_read(server_info) < 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_client/client_http2.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_HTTP2_H\n#define _DNS_CLIENT_HTTP2_H\n\n#include \"dns_client.h\"\n#include \"server_info.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * Send DNS query over HTTP/2\n * @param server_info Server information\n * @param query DNS query structure\n * @param packet DNS query packet\n * @param len Packet length\n * @return 0 on success, -1 on error\n */\nint _dns_client_send_http2(struct dns_server_info *server_info, struct dns_query_struct *query, void *packet,\n\t\t\t\t\t\t   unsigned short len);\n\n/**\n * Process HTTP/2 for a server (handles handshake and all streams)\n * @param server_info Server information\n * @param event Epoll event\n * @param now Current time\n * @return 0 on success, -1 on error\n */\nint _dns_client_process_http2(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* _DNS_CLIENT_HTTP2_H */\n"
  },
  {
    "path": "src/dns_client/client_http3.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"client_http3.h\"\n#include \"client_quic.h\"\n#include \"conn_stream.h\"\n\n#include \"smartdns/http_parse.h\"\n\nint _dns_client_send_http3(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet,\n\t\t\t\t\t\t   unsigned short len)\n{\n#if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC)\n\tint http_len = 0;\n\tint ret = 0;\n\tunsigned char inpacket_data[DNS_IN_PACKSIZE];\n\tstruct client_dns_server_flag_https *https_flag = NULL;\n\tstruct http_head *http_head = NULL;\n\n\tif (len > sizeof(inpacket_data) - 128) {\n\t\ttlog(TLOG_ERROR, \"packet size is invalid.\");\n\t\tgoto errout;\n\t}\n\n\thttps_flag = &server_info->flags.https;\n\thttp_head = http_head_init(4096, HTTP_VERSION_3_0);\n\tif (http_head == NULL) {\n\t\ttlog(TLOG_ERROR, \"init http head failed.\");\n\t\tgoto errout;\n\t}\n\n\thttp_head_set_method(http_head, HTTP_METHOD_POST);\n\thttp_head_set_url(http_head, https_flag->path);\n\thttp_head_set_head_type(http_head, HTTP_HEAD_REQUEST);\n\thttp_head_add_fields(http_head, \":authority\", https_flag->httphost);\n\thttp_head_add_fields(http_head, \"user-agent\", \"smartdns\");\n\thttp_head_add_fields(http_head, \"content-type\", \"application/dns-message\");\n\thttp_head_add_fields(http_head, \"accept\", \"application/dns-message\");\n\thttp_head_add_fields(http_head, \"accept-encoding\", \"identity\");\n\thttp_head_set_data(http_head, packet, len);\n\n\thttp_len = http_head_serialize(http_head, inpacket_data, DNS_IN_PACKSIZE);\n\tif (http_len <= 0) {\n\t\ttlog(TLOG_ERROR, \"serialize http head failed.\");\n\t\tgoto errout;\n\t}\n\n\tret = _dns_client_send_quic_data(query, server_info, inpacket_data, http_len);\n\thttp_head_destroy(http_head);\n\treturn ret;\nerrout:\n\tif (http_head) {\n\t\thttp_head_destroy(http_head);\n\t}\n\n\treturn -1;\n#else\n\ttlog(TLOG_ERROR, \"http3 is not supported.\");\n#endif\n\treturn 0;\n}\n\n#if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC)\nint _dns_client_process_recv_http3(struct dns_server_info *server_info, struct dns_conn_stream *conn_stream)\n{\n\tint ret = 0;\n\tstruct http_head *http_head = NULL;\n\tuint8_t *pkg_data = NULL;\n\tint pkg_len = 0;\n\n\thttp_head = http_head_init(4096, HTTP_VERSION_3_0);\n\tif (http_head == NULL) {\n\t\tgoto errout;\n\t}\n\n\tret = http_head_parse(http_head, conn_stream->recv_buff.data, conn_stream->recv_buff.len);\n\tif (ret < 0) {\n\t\tif (ret == -1) {\n\t\t\tgoto out;\n\t\t} else if (ret == -3) {\n\t\t\t/* repsone is too large */\n\t\t\ttlog(TLOG_DEBUG, \"http3 response is too large.\");\n\t\t\tconn_stream->recv_buff.len = 0;\n\t\t\t_dns_client_conn_stream_put(conn_stream);\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"remote server not supported.\");\n\t\tgoto errout;\n\t}\n\n\tif (http_head_get_httpcode(http_head) == 0) {\n\t\t/* invalid http3 response */\n\t\tserver_info->prohibit = 1;\n\t\tgoto errout;\n\t}\n\n\tif (http_head_get_httpcode(http_head) != 200) {\n\t\ttlog(TLOG_WARN, \"http3 server query from %s:%d failed, server return http code : %d, %s\", server_info->ip,\n\t\t\t server_info->port, http_head_get_httpcode(http_head), http_head_get_httpcode_msg(http_head));\n\t\tserver_info->prohibit = 1;\n\t\tgoto errout;\n\t}\n\n\tpkg_data = (uint8_t *)http_head_get_data(http_head);\n\tpkg_len = http_head_get_data_len(http_head);\n\tif (pkg_data == NULL || pkg_len <= 0) {\n\t\tgoto errout;\n\t}\n\n\tif (_dns_client_recv(server_info, pkg_data, pkg_len, &server_info->addr, server_info->ai_addrlen) != 0) {\n\t\tgoto errout;\n\t}\nout:\n\thttp_head_destroy(http_head);\n\treturn 0;\nerrout:\n\n\tif (http_head) {\n\t\thttp_head_destroy(http_head);\n\t}\n\n\treturn -1;\n}\n#endif\n"
  },
  {
    "path": "src/dns_client/client_http3.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_HTTP3_H_\n#define _DNS_CLIENT_HTTP3_H_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_send_http3(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet,\n\t\t\t\t\t\t   unsigned short len);\n\nint _dns_client_process_recv_http3(struct dns_server_info *server_info, struct dns_conn_stream *conn_stream);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/client_https.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"client_https.h\"\n#include \"client_socket.h\"\n#include \"client_tls.h\"\n#include \"server_info.h\"\n\n#include \"smartdns/http_parse.h\"\n\nint _dns_client_format_https_packet(struct dns_server_info *server_info, void *packet, unsigned short len,\n\t\t\t\t\t\t\t\t\tunsigned char *outpacket, int outpacket_max)\n{\n\tint http_len = 0;\n\tstruct client_dns_server_flag_https *https_flag = NULL;\n\n\tif (len > outpacket_max - 2) {\n\t\ttlog(TLOG_ERROR, \"packet size is invalid.\");\n\t\treturn -1;\n\t}\n\n\thttps_flag = &server_info->flags.https;\n\n\thttp_len = snprintf((char *)outpacket, outpacket_max,\n\t\t\t\t\t\t\"POST %s HTTP/1.1\\r\\n\"\n\t\t\t\t\t\t\"Host: %s\\r\\n\"\n\t\t\t\t\t\t\"User-Agent: smartdns\\r\\n\"\n\t\t\t\t\t\t\"Content-Type: application/dns-message\\r\\n\"\n\t\t\t\t\t\t\"Accept: application/dns-message\\r\\n\"\n\t\t\t\t\t\t\"Content-Length: %d\\r\\n\"\n\t\t\t\t\t\t\"\\r\\n\",\n\t\t\t\t\t\thttps_flag->path, https_flag->httphost, len);\n\tif (http_len < 0 || http_len >= outpacket_max) {\n\t\ttlog(TLOG_ERROR, \"http header size is invalid.\");\n\t\treturn -1;\n\t}\n\n\tmemcpy(outpacket + http_len, packet, len);\n\thttp_len += len;\n\n\treturn http_len;\n}\n\nint _dns_client_send_http1(struct dns_server_info *server_info, void *packet, unsigned short len)\n{\n\tint send_len = 0;\n\tint http_len = 0;\n\tunsigned char inpacket_data[DNS_IN_PACKSIZE];\n\tunsigned char *inpacket = inpacket_data;\n\n\thttp_len = _dns_client_format_https_packet(server_info, packet, len, inpacket, sizeof(inpacket_data));\n\tif (http_len < 0) {\n\t\treturn -1;\n\t}\n\n\tif (server_info->status != DNS_SERVER_STATUS_CONNECTED) {\n\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket, http_len);\n\t}\n\n\tif (server_info->ssl == NULL) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tsend_len = _dns_client_socket_ssl_send(server_info, inpacket, http_len);\n\tif (send_len <= 0) {\n\t\tif (errno == EAGAIN || errno == EPIPE || server_info->ssl == NULL) {\n\t\t\t/* save data to buffer, and retry when EPOLLOUT is available */\n\t\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket, http_len);\n\t\t} else if (server_info->ssl && errno != ENOMEM) {\n\t\t\t_dns_client_shutdown_socket(server_info);\n\t\t}\n\t\treturn -1;\n\t} else if (send_len < http_len) {\n\t\t/* save remain data to buffer, and retry when EPOLLOUT is available */\n\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket + send_len, http_len - send_len);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_client/client_https.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_HTTPS_H_\n#define _DNS_CLIENT_HTTPS_H_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_send_http1(struct dns_server_info *server_info, void *packet, unsigned short len);\n\nint _dns_client_format_https_packet(struct dns_server_info *server_info, void *packet, unsigned short len,\n\t\t\t\t\t\t\t\t\tunsigned char *outpacket, int outpacket_max);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/client_mdns.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"client_mdns.h\"\n#include \"server_info.h\"\n\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <ifaddrs.h>\n#include <net/if.h>\n#include <netinet/ip.h>\n#include <netinet/tcp.h>\n#include <sys/epoll.h>\n#include <sys/ioctl.h>\n\nint _dns_client_create_socket_udp_mdns(struct dns_server_info *server_info)\n{\n\tint fd = -1;\n\tstruct epoll_event event;\n\tconst int on = 1;\n\tconst int val = 1;\n\tconst int priority = SOCKET_PRIORITY;\n\tconst int ip_tos = SOCKET_IP_TOS;\n\n\tfd = socket(server_info->ai_family, SOCK_DGRAM, 0);\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"create socket failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (set_fd_nonblock(fd, 1) != 0) {\n\t\ttlog(TLOG_ERROR, \"set socket non block failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tstruct ifreq ifr;\n\tmemset(&ifr, 0, sizeof(struct ifreq));\n\tsafe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name));\n\tioctl(fd, SIOCGIFINDEX, &ifr);\n\tif (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) {\n\t\ttlog(TLOG_ERROR, \"bind socket to device %s failed, %s\\n\", ifr.ifr_name, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tserver_info->fd = fd;\n\tserver_info->status = DNS_SERVER_STATUS_CONNECTIONLESS;\n\tserver_info->security_status = DNS_CLIENT_SERVER_SECURITY_NOT_APPLICABLE;\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed.\");\n\t\treturn -1;\n\t}\n\n\tsetsockopt(server_info->fd, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on));\n\tsetsockopt(server_info->fd, SOL_IP, IP_TTL, &val, sizeof(val));\n\tsetsockopt(server_info->fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority));\n\tsetsockopt(server_info->fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));\n\tsetsockopt(server_info->fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val));\n\tif (server_info->ai_family == AF_INET6) {\n\t\t/* for receiving ip ttl value */\n\t\tsetsockopt(server_info->fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(server_info->fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(server_info->fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(server_info->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val));\n\t}\n\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\tserver_info->fd = -1;\n\tserver_info->status = DNS_SERVER_STATUS_DISCONNECTED;\n\n\treturn -1;\n}\n\nint _dns_client_send_udp_mdns(struct dns_server_info *server_info, void *packet, int len)\n{\n\tint send_len = 0;\n\tconst struct sockaddr *addr = &server_info->addr;\n\tsocklen_t addrlen = server_info->ai_addrlen;\n\n\tif (server_info->fd <= 0) {\n\t\treturn -1;\n\t}\n\n\tsend_len = sendto(server_info->fd, packet, len, 0, addr, addrlen);\n\tif (send_len != len) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nint _dns_client_add_mdns_server(void)\n{\n\tstruct client_dns_server_flags server_flags;\n\tint ret = 0;\n\tstruct ifaddrs *ifaddr = NULL;\n\tstruct ifaddrs *ifa = NULL;\n\n\tif (dns_conf.mdns_lookup != 1) {\n\t\treturn 0;\n\t}\n\n\tmemset(&server_flags, 0, sizeof(server_flags));\n\tserver_flags.server_flag |= SERVER_FLAG_EXCLUDE_DEFAULT | DOMAIN_FLAG_IPSET_IGN | DOMAIN_FLAG_NFTSET_INET_IGN;\n\n\tif (dns_client_add_group(DNS_SERVER_GROUP_MDNS) != 0) {\n\t\ttlog(TLOG_ERROR, \"add default server group failed.\");\n\t\tgoto errout;\n\t}\n\n#ifdef TEST\n\tsafe_strncpy(server_flags.ifname, \"lo\", sizeof(server_flags.ifname));\n\tret = _dns_client_server_add(DNS_MDNS_IP, \"\", DNS_MDNS_PORT, DNS_SERVER_MDNS, &server_flags);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"add mdns server to %s failed.\", \"lo\");\n\t\tgoto errout;\n\t}\n\n\tif (dns_client_add_to_group(DNS_SERVER_GROUP_MDNS, DNS_MDNS_IP, DNS_MDNS_PORT, DNS_SERVER_MDNS, &server_flags) !=\n\t\t0) {\n\t\ttlog(TLOG_ERROR, \"add mdns server to group %s failed.\", DNS_SERVER_GROUP_MDNS);\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n#endif\n\n\tif (getifaddrs(&ifaddr) == -1) {\n\t\tgoto errout;\n\t}\n\n\tfor (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {\n\t\tconst unsigned char *addr = NULL;\n\t\tint addr_len = 0;\n\n\t\tif (ifa->ifa_addr == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (AF_INET != ifa->ifa_addr->sa_family && AF_INET6 != ifa->ifa_addr->sa_family) {\n\t\t\tcontinue;\n\t\t}\n\n\t\taddr = (const unsigned char *)&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;\n\t\taddr_len = sizeof(struct in_addr);\n\n\t\t// Skip the local interface\n\t\tif (strcmp(ifa->ifa_name, \"lo\") == 0 || strcmp(ifa->ifa_name, \"localhost\") == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (is_private_addr(addr, addr_len) == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tsafe_strncpy(server_flags.ifname, ifa->ifa_name, sizeof(server_flags.ifname));\n\t\tchar *dns_ip[] = {DNS_MDNS_IP, DNS_MDNS_IP6, 0};\n\t\tfor (int i = 0; dns_ip[i] != NULL; i++) {\n\t\t\tret = _dns_client_server_add(dns_ip[i], \"\", DNS_MDNS_PORT, DNS_SERVER_MDNS, &server_flags);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add mdns server failed for %s.\", dns_ip[i]);\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (dns_client_add_to_group(DNS_SERVER_GROUP_MDNS, dns_ip[i], DNS_MDNS_PORT, DNS_SERVER_MDNS,\n\t\t\t\t\t\t\t\t\t\t&server_flags) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add mdns server to group failed for %s.\", dns_ip[i]);\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t}\n\t}\n\n\tfreeifaddrs(ifaddr);\n\n\treturn 0;\n\nerrout:\n\tif (ifaddr) {\n\t\tfreeifaddrs(ifaddr);\n\t}\n\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_client/client_mdns.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_MDNS_\n#define _DNS_CLIENT_MDNS_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_create_socket_udp_mdns(struct dns_server_info *server_info);\n\nint _dns_client_send_udp_mdns(struct dns_server_info *server_info, void *packet, int len);\n\nint _dns_client_add_mdns_server(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/client_quic.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/http_parse.h\"\n#include \"smartdns/util.h\"\n\n#include \"client_http3.h\"\n#include \"client_quic.h\"\n#include \"client_socket.h\"\n#include \"client_tls.h\"\n#include \"conn_stream.h\"\n#include \"server_info.h\"\n\n#include <net/if.h>\n#include <netinet/ip.h>\n#include <openssl/err.h>\n#include <openssl/rand.h>\n#include <openssl/ssl.h>\n#include <openssl/x509v3.h>\n#include <sys/epoll.h>\n#include <sys/ioctl.h>\n\n#if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC)\nstatic int _dns_client_quic_bio_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t num_msg, uint64_t flags,\n\t\t\t\t\t\t\t\t\t\t size_t *msgs_processed)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tint total_len = 0;\n\tint len = 0;\n\tstruct sockaddr_storage from;\n\tsocklen_t from_len = sizeof(from);\n\n\tserver_info = (struct dns_server_info *)BIO_get_data(bio);\n\tif (server_info == NULL) {\n\t\ttlog(TLOG_ERROR, \"server info is null, %s\", server_info->ip);\n\t\treturn 0;\n\t}\n\n\t*msgs_processed = 0;\n\tfor (size_t i = 0; i < num_msg; i++) {\n\t\tlen = proxy_conn_recvfrom(server_info->proxy, msg[i].data, msg[i].data_len, 0, (struct sockaddr *)&from,\n\t\t\t\t\t\t\t\t  &from_len);\n\t\tif (len < 0) {\n\t\t\tif (*msgs_processed == 0) {\n\t\t\t\tERR_raise(ERR_LIB_SYS, errno);\n\t\t\t\ttotal_len = 0;\n\t\t\t}\n\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (errno == EPIPE || errno == ECONNRESET) {\n\t\t\t\t/* Ignore broken pipe and connection reset errors */\n\t\t\t\ttlog(TLOG_DEBUG, \"recvmsg broken pipe or connection reset, %s\", server_info->ip);\n\t\t\t\treturn total_len;\n\t\t\t}\n\n\t\t\ttlog(TLOG_ERROR, \"recvmsg failed, %s\", strerror(errno));\n\t\t\treturn 0;\n\t\t}\n\n\t\tmsg[i].data_len = len;\n\t\ttotal_len += len;\n\t\t*msgs_processed += 1;\n\t}\n\n\treturn total_len;\n}\n\nstatic int _dns_client_quic_bio_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t num_msg, uint64_t flags,\n\t\t\t\t\t\t\t\t\t\t size_t *msgs_processed)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tint total_len = 0;\n\tint len = 0;\n\tconst struct sockaddr *addr = NULL;\n\tsocklen_t addrlen = 0;\n\n\t*msgs_processed = 0;\n\tserver_info = (struct dns_server_info *)BIO_get_data(bio);\n\tif (server_info == NULL) {\n\t\ttlog(TLOG_ERROR, \"server info is null, %s\", server_info->ip);\n\t\treturn 0;\n\t}\n\n\taddr = &server_info->addr;\n\taddrlen = server_info->ai_addrlen;\n\tfor (size_t i = 0; i < num_msg; i++) {\n\t\tlen = proxy_conn_sendto(server_info->proxy, msg[i].data, msg[i].data_len, 0, addr, addrlen);\n\t\tif (len < 0) {\n\t\t\tif (*msgs_processed == 0) {\n\t\t\t\tERR_raise(ERR_LIB_SYS, errno);\n\t\t\t\ttotal_len = 0;\n\t\t\t}\n\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (errno == EPIPE || errno == ECONNRESET) {\n\t\t\t\t/* Ignore broken pipe and connection reset errors */\n\t\t\t\ttlog(TLOG_DEBUG, \"sendmsg broken pipe or connection reset, %s\", server_info->ip);\n\t\t\t\treturn total_len;\n\t\t\t}\n\n\t\t\ttlog(TLOG_ERROR, \"sendmsg failed, %s\", strerror(errno));\n\t\t\treturn 0;\n\t\t}\n\n\t\ttotal_len += len;\n\t\t*msgs_processed += 1;\n\t}\n\n\treturn total_len;\n}\n\nstatic long _dns_client_quic_bio_ctrl(BIO *bio, int cmd, long num, void *ptr)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tlong ret = 0;\n\n\tserver_info = (struct dns_server_info *)BIO_get_data(bio);\n\tif (server_info == NULL) {\n\t\ttlog(TLOG_ERROR, \"server info is null.\");\n\t\treturn -1;\n\t}\n\n\tswitch (cmd) {\n\tcase BIO_CTRL_DGRAM_GET_MTU:\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_client_setup_quic_ssl_bio(struct dns_server_info *server_info, SSL *ssl, int fd,\n\t\t\t\t\t\t\t\t\t\t  struct proxy_conn *proxy)\n{\n\tBIO_METHOD *bio_method_alloc = NULL;\n\tBIO_METHOD *bio_method = server_info->bio_method;\n\tBIO *udp_socket_bio = NULL;\n\n\tif (ssl == NULL) {\n\t\ttlog(TLOG_ERROR, \"ssl is null, %s\", server_info->ip);\n\t\treturn -1;\n\t}\n\n\tif (proxy == NULL) {\n\t\tif (SSL_set_fd(ssl, fd) == 0) {\n\t\t\ttlog(TLOG_ERROR, \"ssl set fd failed.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tif (bio_method == NULL) {\n\t\tbio_method_alloc = BIO_meth_new(BIO_TYPE_SOURCE_SINK, \"udp-proxy\");\n\t\tif (bio_method_alloc == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"create bio method failed.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tbio_method = bio_method_alloc;\n\t\tBIO_meth_set_sendmmsg(bio_method, _dns_client_quic_bio_sendmmsg);\n\t\tBIO_meth_set_recvmmsg(bio_method, _dns_client_quic_bio_recvmmsg);\n\t\tBIO_meth_set_ctrl(bio_method, _dns_client_quic_bio_ctrl);\n\t}\n\n\tudp_socket_bio = BIO_new(bio_method);\n\tif (udp_socket_bio == NULL) {\n\t\ttlog(TLOG_ERROR, \"create udp_socket_bio failed.\");\n\t\tgoto errout;\n\t}\n\tBIO_set_data(udp_socket_bio, (void *)server_info);\n\tBIO_set_init(udp_socket_bio, 1);\n\n\tSSL_set_bio(ssl, udp_socket_bio, udp_socket_bio);\n\tserver_info->bio_method = bio_method;\n\n\treturn 0;\n\nerrout:\n\tif (bio_method_alloc) {\n\t\tBIO_meth_free(bio_method_alloc);\n\t}\n\n\tif (udp_socket_bio) {\n\t\tBIO_free(udp_socket_bio);\n\t}\n\n\treturn -1;\n}\n\n#endif\n\nint _dns_client_create_socket_quic(struct dns_server_info *server_info, const char *hostname, const char *alpn)\n{\n#if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC)\n\tint fd = -1;\n\tunsigned char alpn_data[DNS_MAX_ALPN_LEN];\n\tint32_t alpn_len = 0;\n\tstruct epoll_event event;\n\tSSL *ssl = NULL;\n\tstruct proxy_conn *proxy = NULL;\n\tint ret = -1;\n\n\tif (server_info->ssl_ctx == NULL) {\n\t\ttlog(TLOG_ERROR, \"create ssl ctx failed, %s\", server_info->ip);\n\t\tgoto errout;\n\t}\n\n\tif (server_info->proxy_name[0] != '\\0') {\n\t\tproxy = proxy_conn_new(server_info->proxy_name, server_info->ip, server_info->port, 1, 1);\n\t\tif (proxy == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"create proxy failed, %s, proxy: %s\", server_info->ip, server_info->proxy_name);\n\t\t\tgoto errout;\n\t\t}\n\t\tfd = proxy_conn_get_fd(proxy);\n\t} else {\n\t\tfd = socket(server_info->ai_family, SOCK_DGRAM, IPPROTO_UDP);\n\t}\n\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"create socket failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (set_fd_nonblock(fd, 1) != 0) {\n\t\ttlog(TLOG_ERROR, \"set socket non block failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tssl = SSL_new(server_info->ssl_ctx);\n\tif (ssl == NULL) {\n\t\ttlog(TLOG_ERROR, \"new ssl failed, %s\", server_info->ip);\n\t\tgoto errout;\n\t}\n\n\tif (server_info->so_mark >= 0) {\n\t\tunsigned int so_mark = server_info->so_mark;\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) {\n\t\t\ttlog(TLOG_DEBUG, \"set socket mark failed, %s\", strerror(errno));\n\t\t}\n\t}\n\n\tif (proxy) {\n\t\tret = proxy_conn_connect(proxy);\n\t} else {\n\t\tret = connect(fd, &server_info->addr, server_info->ai_addrlen);\n\t}\n\n\tif (ret != 0) {\n\t\tif (errno != EINPROGRESS) {\n\t\t\ttlog(TLOG_DEBUG, \"connect %s failed, %s\", server_info->ip, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tSSL_set_blocking_mode(ssl, 0);\n\tSSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE);\n\tif (_dns_client_setup_quic_ssl_bio(server_info, ssl, fd, proxy) != 0) {\n\t\ttlog(TLOG_ERROR, \"ssl set fd failed.\");\n\t\tgoto errout;\n\t}\n\n\tSSL_set_connect_state(ssl);\n\t/* reuse ssl session */\n\tif (server_info->ssl_session) {\n\t\tSSL_set_session(ssl, server_info->ssl_session);\n\t}\n\n\tSSL_set_mode(ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE);\n\tif (hostname[0] != 0) {\n\t\tSSL_set_tlsext_host_name(ssl, hostname);\n\t}\n\n\tSSL_set1_host(ssl, hostname);\n\n\tif (alpn == NULL) {\n\t\ttlog(TLOG_INFO, \"alpn is null.\");\n\t\tgoto errout;\n\t}\n\n\talpn_len = strnlen(alpn, DNS_MAX_ALPN_LEN - 1);\n\talpn_data[0] = alpn_len;\n\tmemcpy(alpn_data + 1, alpn, alpn_len);\n\talpn_len++;\n\n\tif (SSL_set_alpn_protos(ssl, alpn_data, alpn_len)) {\n\t\ttlog(TLOG_INFO, \"SSL_set_alpn_protos failed.\");\n\t\tgoto errout;\n\t}\n\n\tif (server_info->ssl) {\n\t\tSSL_free(server_info->ssl);\n\t\tserver_info->ssl = NULL;\n\t}\n\n\tserver_info->fd = fd;\n\tserver_info->ssl = ssl;\n\tserver_info->ssl_write_len = -1;\n\tserver_info->status = DNS_SERVER_STATUS_CONNECTING;\n\tserver_info->proxy = proxy;\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLOUT;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed.\");\n\t\tgoto errout;\n\t}\n\n\ttlog(TLOG_DEBUG, \"quic server %s:%d connecting.\\n\", server_info->ip, server_info->port);\n\n\treturn 0;\nerrout:\n\tif (server_info->fd > 0) {\n\t\tserver_info->fd = -1;\n\t}\n\n\tif (server_info->ssl) {\n\t\tserver_info->ssl = NULL;\n\t}\n\n\tserver_info->status = DNS_SERVER_STATUS_INIT;\n\tserver_info->proxy = NULL;\n\tserver_info->ssl_write_len = -1;\n\n\tif (fd > 0 && proxy == NULL) {\n\t\tclose(fd);\n\t}\n\n\tif (ssl) {\n\t\tSSL_free(ssl);\n\t}\n\n\tif (proxy) {\n\t\tproxy_conn_free(proxy);\n\t}\n\n\treturn -1;\n#else\n\treturn -1;\n#endif\n}\n\n#if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC)\nstatic int _dns_client_process_quic_poll(struct dns_server_info *server_info)\n{\n\tLIST_HEAD(processed_list);\n\tstatic int MAX_POLL_ITEM_COUNT = 128;\n\tSSL_POLL_ITEM poll_items[MAX_POLL_ITEM_COUNT];\n\tmemset(poll_items, 0, sizeof(poll_items));\n\tstatic const struct timeval nz_timeout = {0, 0};\n\tint poll_ret = 0;\n\tint ret = 0;\n\tstruct dns_conn_stream *conn_stream = NULL;\n\tstruct dns_conn_stream *tmp = NULL;\n\n\twhile (true) {\n\t\tint poll_item_count = 0;\n\t\tsize_t poll_process_count = 0;\n\t\tsize_t poll_retcount = 0;\n\n\t\tpthread_mutex_lock(&server_info->lock);\n\t\tlist_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, server_list)\n\t\t{\n\t\t\tif (conn_stream->quic_stream == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (poll_item_count >= MAX_POLL_ITEM_COUNT) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tpoll_items[poll_item_count].desc = SSL_as_poll_descriptor(conn_stream->quic_stream);\n\t\t\tpoll_items[poll_item_count].events = SSL_POLL_EVENT_R;\n\t\t\tpoll_items[poll_item_count].revents = 0;\n\t\t\tpoll_item_count++;\n\t\t\tlist_del_init(&conn_stream->server_list);\n\t\t\tlist_add_tail(&conn_stream->server_list, &processed_list);\n\t\t}\n\t\tpthread_mutex_unlock(&server_info->lock);\n\n\t\tif (poll_item_count <= 0) {\n\t\t\t_ssl_do_handevent(server_info);\n\t\t\tbreak;\n\t\t}\n\n\t\tret = SSL_poll(poll_items, poll_item_count, sizeof(SSL_POLL_ITEM), &nz_timeout, 0, &poll_retcount);\n\t\tif (ret <= 0) {\n\t\t\ttlog(TLOG_DEBUG, \"SSL_poll failed, %d\", ret);\n\t\t\tgoto errout;\n\t\t}\n\n\t\tfor (int i = 0; i < MAX_POLL_ITEM_COUNT && poll_process_count < poll_retcount; i++) {\n\t\t\tif (poll_items[i].revents & SSL_POLL_EVENT_R) {\n\t\t\t\tpoll_process_count++;\n\t\t\t\tconn_stream = SSL_get_ex_data(poll_items[i].desc.value.ssl, 0);\n\t\t\t\tif (conn_stream == NULL) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"conn stream is null\");\n\t\t\t\t\tSSL_free(poll_items[i].desc.value.ssl);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tint read_len = _dns_client_socket_ssl_recv_ext(server_info, poll_items[i].desc.value.ssl,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   conn_stream->recv_buff.data, DNS_TCP_BUFFER);\n\n\t\t\t\tif (read_len < 0) {\n\t\t\t\t\tif (errno == EAGAIN) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\ttlog(TLOG_ERROR, \"recv failed, %s\", strerror(errno));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconn_stream->recv_buff.len += read_len;\n\n\t\t\t\tif (conn_stream->query == NULL) {\n\t\t\t\t\tlist_del_init(&conn_stream->server_list);\n\t\t\t\t\t_dns_client_conn_stream_put(conn_stream);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (server_info->type == DNS_SERVER_HTTP3) {\n\t\t\t\t\tret = _dns_client_process_recv_http3(server_info, conn_stream);\n\t\t\t\t\tif (ret != 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t} else if (server_info->type == DNS_SERVER_QUIC) {\n\t\t\t\t\tunsigned short qid = htons(conn_stream->query->sid);\n\t\t\t\t\tint msg_len = ntohs(*((unsigned short *)(conn_stream->recv_buff.data)));\n\t\t\t\t\tif (msg_len <= 0 || msg_len >= DNS_IN_PACKSIZE) {\n\t\t\t\t\t\t/* data len is invalid */\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (msg_len > conn_stream->recv_buff.len - 2) {\n\t\t\t\t\t\terrno = EAGAIN;\n\t\t\t\t\t\t/* len is not expected, wait and recv */\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tmemcpy(conn_stream->recv_buff.data + 2, &qid, 2);\n\t\t\t\t\tif (_dns_client_recv(server_info, conn_stream->recv_buff.data + 2, conn_stream->recv_buff.len - 2,\n\t\t\t\t\t\t\t\t\t\t &server_info->addr, server_info->ai_addrlen) != 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t/* process succeed, delete from processed_list*/\n\t\t\t\tlist_del_init(&conn_stream->server_list);\n\t\t\t\t_dns_client_conn_stream_put(conn_stream);\n\t\t\t}\n\t\t}\n\t}\n\tpoll_ret = 0;\n\tgoto out;\nerrout:\n\tpoll_ret = -1;\nout:\n\tpthread_mutex_lock(&server_info->lock);\n\tif (list_empty(&processed_list)) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\treturn 0;\n\t}\n\n\tlist_splice_tail(&processed_list, &server_info->conn_stream_list);\n\tpthread_mutex_unlock(&server_info->lock);\n\n\treturn poll_ret;\n}\n\nint _dns_client_process_quic(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now)\n{\n\tif (event->events & EPOLLIN) {\n\t\t/* connection is closed, reconnect */\n\t\tif (SSL_get_shutdown(server_info->ssl) != 0) {\n\t\t\tint ret = 0;\n\t\t\t_dns_client_close_socket_ext(server_info, 1);\n\t\t\tpthread_mutex_lock(&server_info->lock);\n\t\t\tserver_info->recv_buff.len = 0;\n\t\t\tif (!list_empty(&server_info->conn_stream_list)) {\n\t\t\t\t/* still remain request data, reconnect and send*/\n\t\t\t\tret = _dns_client_create_socket(server_info);\n\t\t\t} else {\n\t\t\t\tret = 0;\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&server_info->lock);\n\t\t\ttlog(TLOG_DEBUG, \"quic server %s:%d peer close\", server_info->ip, server_info->port);\n\t\t\treturn ret;\n\t\t}\n\n\t\tif (_dns_client_process_quic_poll(server_info) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tif (event->events & EPOLLOUT) {\n\t\tint epoll_events = EPOLLIN;\n\t\tstruct dns_conn_stream *conn_stream = NULL;\n\t\tpthread_mutex_lock(&server_info->lock);\n\t\tlist_for_each_entry(conn_stream, &server_info->conn_stream_list, server_list)\n\t\t{\n\t\t\tif (conn_stream->quic_stream != NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (conn_stream->send_buff.len <= 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconn_stream->quic_stream = SSL_new_stream(server_info->ssl, 0);\n\t\t\tif (conn_stream->quic_stream == NULL) {\n\t\t\t\tpthread_mutex_unlock(&server_info->lock);\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tSSL_set_ex_data(conn_stream->quic_stream, 0, conn_stream);\n\n\t\t\tint send_len =\n\t\t\t\t_dns_client_socket_ssl_send_ext(server_info, conn_stream->quic_stream, conn_stream->send_buff.data,\n\t\t\t\t\t\t\t\t\t\t\t\tconn_stream->send_buff.len, SSL_WRITE_FLAG_CONCLUDE);\n\t\t\tif (send_len < 0) {\n\t\t\t\tif (errno == EAGAIN) {\n\t\t\t\t\tepoll_events = EPOLLIN | EPOLLOUT;\n\t\t\t\t\t_ssl_do_handevent(server_info);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (send_len < conn_stream->send_buff.len) {\n\t\t\t\tconn_stream->send_buff.len -= send_len;\n\t\t\t\tmemmove(conn_stream->send_buff.data, conn_stream->send_buff.data + send_len,\n\t\t\t\t\t\tconn_stream->send_buff.len);\n\t\t\t\tepoll_events = EPOLLIN | EPOLLOUT;\n\t\t\t} else {\n\t\t\t\tconn_stream->send_buff.len = 0;\n\t\t\t}\n\t\t}\n\t\tpthread_mutex_unlock(&server_info->lock);\n\n\t\tif (server_info->fd > 0) {\n\t\t\t/* clear epollout event */\n\t\t\tstruct epoll_event mod_event;\n\t\t\tmemset(&mod_event, 0, sizeof(mod_event));\n\t\t\tmod_event.events = epoll_events;\n\t\t\tmod_event.data.ptr = server_info;\n\t\t\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &mod_event) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\nerrout:\n\treturn -1;\n}\n#endif\n\n#if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC)\nstatic int _dns_client_quic_pending_data(struct dns_conn_stream *stream, struct dns_server_info *server_info,\n\t\t\t\t\t\t\t\t\t\t struct dns_query_struct *query, void *packet, int len)\n{\n\tstruct epoll_event event;\n\tif (DNS_TCP_BUFFER - stream->send_buff.len < len) {\n\t\terrno = ENOMEM;\n\t\treturn -1;\n\t}\n\n\tif (client.epoll_fd <= 0) {\n\t\terrno = ECONNRESET;\n\t\tgoto errout;\n\t}\n\n\tmemcpy(stream->send_buff.data + stream->send_buff.len, packet, len);\n\tstream->send_buff.len += len;\n\n\tpthread_mutex_lock(&server_info->lock);\n\tif (server_info->fd <= 0) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\terrno = ECONNRESET;\n\t\tgoto errout;\n\t}\n\n\tstream->server_info = server_info;\n\tif (list_empty(&stream->server_list)) {\n\t\tlist_add_tail(&stream->server_list, &server_info->conn_stream_list);\n\t\t_dns_client_conn_stream_get(stream);\n\t}\n\n\tif (list_empty(&stream->query_list)) {\n\t\tpthread_mutex_lock(&query->lock);\n\t\tstream->query = query;\n\t\tlist_add_tail(&stream->query_list, &query->conn_stream_list);\n\t\tpthread_mutex_unlock(&query->lock);\n\t\t_dns_client_conn_stream_get(stream);\n\t}\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLOUT;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\tgoto errout_put;\n\t}\n\tpthread_mutex_unlock(&server_info->lock);\n\treturn 0;\n\nerrout_put:\n\tpthread_mutex_lock(&server_info->lock);\n\tif (!list_empty(&stream->server_list)) {\n\t\tlist_del_init(&stream->server_list);\n\t\tstream->server_info = NULL;\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\tif (!list_empty(&stream->query_list)) {\n\t\tif (stream->query) {\n\t\t\tpthread_mutex_lock(&stream->query->lock);\n\t\t\tlist_del_init(&stream->query_list);\n\t\t\tpthread_mutex_unlock(&stream->query->lock);\n\t\t\tstream->query = NULL;\n\t\t}\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\tpthread_mutex_unlock(&server_info->lock);\n\nerrout:\n\n\treturn -1;\n}\n\nint _dns_client_send_quic_data(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet,\n\t\t\t\t\t\t\t   unsigned short len)\n{\n\tint send_len = 0;\n\tint ret = 0;\n\n\t_dns_client_conn_server_streams_free(server_info, query);\n\n\tif (server_info->ssl == NULL) {\n\t\ttlog(TLOG_DEBUG, \"ssl is invalid, server %s\", server_info->ip);\n\t\treturn -1;\n\t}\n\n\tstruct dns_conn_stream *stream = _dns_client_conn_stream_new();\n\tif (stream == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc memory failed.\");\n\t\treturn -1;\n\t}\n\tstream->type = server_info->type;\n\n\tif (server_info->status != DNS_SERVER_STATUS_CONNECTED) {\n\t\tret = _dns_client_quic_pending_data(stream, server_info, query, packet, len);\n\t\tgoto out;\n\t}\n\n\t/* run quic handevent */\n\t_ssl_do_handevent(server_info);\n\n\tSSL *quic_stream = SSL_new_stream(server_info->ssl, 0);\n\tif (quic_stream == NULL) {\n\t\tstruct epoll_event event;\n\t\t_dns_client_shutdown_socket(server_info);\n\t\tret = _dns_client_quic_pending_data(stream, server_info, query, packet, len);\n\t\tif (ret != 0) {\n\t\t\terrno = ECONNRESET;\n\t\t\tgoto out;\n\t\t}\n\n\t\t/* clear epollout event */\n\t\tmemset(&event, 0, sizeof(event));\n\t\tevent.events = EPOLLIN;\n\t\tevent.data.ptr = server_info;\n\t\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) {\n\t\t\tif (errno == ENOENT) {\n\t\t\t\tgoto out;\n\t\t\t}\n\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\t\tret = -1;\n\t\t}\n\t\tgoto out;\n\t}\n\n\tpthread_mutex_lock(&server_info->lock);\n\tstream->server_info = server_info;\n\tlist_add_tail(&stream->server_list, &server_info->conn_stream_list);\n\t_dns_client_conn_stream_get(stream);\n\n\tpthread_mutex_lock(&query->lock);\n\tstream->query = query;\n\tlist_add_tail(&stream->query_list, &query->conn_stream_list);\n\tpthread_mutex_unlock(&query->lock);\n\t_dns_client_conn_stream_get(stream);\n\tpthread_mutex_unlock(&server_info->lock);\n\n\t/* bind stream */\n\tSSL_set_ex_data(quic_stream, 0, stream);\n\tstream->quic_stream = quic_stream;\n\n\tsend_len = _dns_client_socket_ssl_send_ext(server_info, quic_stream, packet, len, SSL_WRITE_FLAG_CONCLUDE);\n\tif (send_len <= 0) {\n\t\tif (errno == EAGAIN || server_info->ssl == NULL) {\n\t\t\t/* save data to buffer, and retry when EPOLLOUT is available */\n\t\t\tret = _dns_client_quic_pending_data(stream, server_info, query, packet, len);\n\t\t\tgoto out;\n\t\t} else if (server_info->ssl && errno != ENOMEM) {\n\t\t\t_dns_client_shutdown_socket(server_info);\n\t\t}\n\t\tret = -1;\n\t\tgoto out;\n\t} else if (send_len < len) {\n\t\t/* save remain data to buffer, and retry when EPOLLOUT is available */\n\t\tret = _dns_client_quic_pending_data(stream, server_info, query, packet + send_len, len - send_len);\n\t\tgoto out;\n\t}\nout:\n\tif (stream) {\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\n\treturn ret;\n}\n#endif\n\nint _dns_client_send_quic(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet,\n\t\t\t\t\t\t  unsigned short len)\n{\n#if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC)\n\tunsigned char inpacket_data[DNS_IN_PACKSIZE];\n\tunsigned char *inpacket = inpacket_data;\n\n\tif (len > sizeof(inpacket_data) - 2) {\n\t\ttlog(TLOG_ERROR, \"packet size is invalid.\");\n\t\treturn -1;\n\t}\n\n\t/* TCP query format\n\t * | len (short) | dns query data |\n\t */\n\t*((unsigned short *)(inpacket)) = htons(len);\n\tmemcpy(inpacket + 2, packet, len);\n\tlen += 2;\n\n\t/* set query id to zero */\n\tmemset(inpacket + 2, 0, 2);\n\n\treturn _dns_client_send_quic_data(query, server_info, inpacket, len);\n#else\n\ttlog(TLOG_ERROR, \"quic is not supported.\");\n#endif\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_client/client_quic.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_QUIC_H_\n#define _DNS_CLIENT_QUIC_H_\n\n#include \"dns_client.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_create_socket_quic(struct dns_server_info *server_info, const char *hostname, const char *alpn);\n\nint _dns_client_send_quic(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet,\n\t\t\t\t\t\t  unsigned short len);\n\nint _dns_client_send_quic_data(struct dns_query_struct *query, struct dns_server_info *server_info, void *packet,\n\t\t\t\t\t\t\t   unsigned short len);\n\nint _dns_client_process_quic(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/client_socket.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client_socket.h\"\n#include \"client_http3.h\"\n#include \"client_https.h\"\n#include \"client_mdns.h\"\n#include \"client_quic.h\"\n#include \"client_tcp.h\"\n#include \"client_tls.h\"\n#include \"client_udp.h\"\n#include \"conn_stream.h\"\n\n#include <openssl/ssl.h>\n#include <sys/epoll.h>\n\nint _dns_client_create_socket(struct dns_server_info *server_info)\n{\n\tint ret = -1;\n\tpthread_mutex_lock(&server_info->lock);\n\n\tif (server_info->fd > 0) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\treturn -1;\n\t}\n\n\ttime(&server_info->last_send);\n\ttime(&server_info->last_recv);\n\n\tif (server_info->type == DNS_SERVER_UDP) {\n\t\tret = _dns_client_create_socket_udp(server_info);\n\t} else if (server_info->type == DNS_SERVER_MDNS) {\n\t\tret = _dns_client_create_socket_udp_mdns(server_info);\n\t} else if (server_info->type == DNS_SERVER_TCP) {\n\t\tret = _dns_client_create_socket_tcp(server_info);\n\t} else if (server_info->type == DNS_SERVER_TLS) {\n\t\tstruct client_dns_server_flag_tls *flag_tls = NULL;\n\t\tflag_tls = &server_info->flags.tls;\n\t\tret = _dns_client_create_socket_tls(server_info, flag_tls->hostname, flag_tls->alpn);\n\t} else if (server_info->type == DNS_SERVER_QUIC) {\n\t\tstruct client_dns_server_flag_tls *flag_tls = NULL;\n\t\tconst char *alpn = \"doq\";\n\t\tflag_tls = &server_info->flags.tls;\n\t\tif (flag_tls->alpn[0] != 0) {\n\t\t\talpn = flag_tls->alpn;\n\t\t}\n\t\tret = _dns_client_create_socket_quic(server_info, flag_tls->hostname, alpn);\n\t} else if (server_info->type == DNS_SERVER_HTTPS) {\n\t\tstruct client_dns_server_flag_https *flag_https = NULL;\n\t\tconst char *alpn = \"h2,http/1.1\";\n\t\tflag_https = &server_info->flags.https;\n\t\tif (flag_https->alpn[0] != 0) {\n\t\t\talpn = flag_https->alpn;\n\t\t}\n\t\tret = _dns_client_create_socket_tls(server_info, flag_https->hostname, alpn);\n\t} else if (server_info->type == DNS_SERVER_HTTP3) {\n\t\tstruct client_dns_server_flag_https *flag_https = NULL;\n\t\tconst char *alpn = \"h3\";\n\t\tflag_https = &server_info->flags.https;\n\t\tif (flag_https->alpn[0] != 0) {\n\t\t\talpn = flag_https->alpn;\n\t\t}\n\t\tret = _dns_client_create_socket_quic(server_info, flag_https->hostname, alpn);\n\t} else {\n\t\tret = -1;\n\t}\n\n\tpthread_mutex_unlock(&server_info->lock);\n\n\treturn ret;\n}\n\nvoid _dns_client_close_socket_ext(struct dns_server_info *server_info, int no_del_conn_list)\n{\n\tdns_server_status server_status = DNS_SERVER_STATUS_DISCONNECTED;\n\n\tpthread_mutex_lock(&server_info->lock);\n\tserver_status = server_info->status;\n\tserver_info->status = DNS_SERVER_STATUS_DISCONNECTED;\n\n\tif (server_info->ssl) {\n\t\t/* Shutdown ssl */\n\t\tif (server_status == DNS_SERVER_STATUS_CONNECTED) {\n\t\t\t_ssl_shutdown(server_info);\n\t\t}\n\n\t\tif (server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) {\n\t\t\tstruct dns_conn_stream *conn_stream = NULL;\n\t\t\tstruct dns_conn_stream *tmp = NULL;\n\n\t\t\tlist_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, server_list)\n\t\t\t{\n\t\t\t\tif (conn_stream->quic_stream) {\n#if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC)\n\t\t\t\t\tSSL_stream_reset(conn_stream->quic_stream, NULL, 0);\n#endif\n\t\t\t\t\tSSL_free(conn_stream->quic_stream);\n\t\t\t\t\tconn_stream->quic_stream = NULL;\n\t\t\t\t}\n\n\t\t\t\tif (no_del_conn_list == 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconn_stream->server_info = NULL;\n\t\t\t\tlist_del_init(&conn_stream->server_list);\n\t\t\t\t_dns_client_conn_stream_put(conn_stream);\n\t\t\t}\n\t\t} else if (server_info->type == DNS_SERVER_HTTPS) {\n\t\t\tstruct dns_conn_stream *conn_stream = NULL;\n\t\t\tstruct dns_conn_stream *tmp = NULL;\n\n\t\t\tlist_for_each_entry_safe(conn_stream, tmp, &server_info->conn_stream_list, server_list)\n\t\t\t{\n\t\t\t\tif (conn_stream->http2_stream) {\n\t\t\t\t\thttp2_stream_close(conn_stream->http2_stream);\n\t\t\t\t\tconn_stream->http2_stream = NULL;\n\t\t\t\t}\n\n\t\t\t\tif (no_del_conn_list == 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconn_stream->server_info = NULL;\n\t\t\t\tlist_del_init(&conn_stream->server_list);\n\t\t\t\t_dns_client_conn_stream_put(conn_stream);\n\t\t\t}\n\t\t}\n\n\t\tSSL_free(server_info->ssl);\n\t\tserver_info->ssl = NULL;\n\t\tserver_info->ssl_write_len = -1;\n\t}\n\n\t/* Clean up HTTP/2 context (connection-level) */\n\tif (server_info->http2_ctx) {\n\t\thttp2_ctx_put(server_info->http2_ctx);\n\t\tserver_info->http2_ctx = NULL;\n\t}\n\n\tif (server_info->bio_method) {\n\t\tBIO_meth_free(server_info->bio_method);\n\t\tserver_info->bio_method = NULL;\n\t}\n\n\tif (server_info->proxy) {\n\t\tproxy_conn_free(server_info->proxy);\n\t\tserver_info->proxy = NULL;\n\t} else if (server_info->fd > 0) {\n\t\tclose(server_info->fd);\n\t}\n\n\tif (server_info->fd > 0) {\n\t\tepoll_ctl(client.epoll_fd, EPOLL_CTL_DEL, server_info->fd, NULL);\n\t}\n\n\tserver_info->fd = -1;\n\ttlog(TLOG_DEBUG, \"server %s:%d closed.\", server_info->ip, server_info->port);\n\n\t/* update send recv time */\n\ttime(&server_info->last_send);\n\ttime(&server_info->last_recv);\n\n\tpthread_mutex_unlock(&server_info->lock);\n}\n\nvoid _dns_client_close_socket(struct dns_server_info *server_info)\n{\n\t_dns_client_close_socket_ext(server_info, 0);\n}\n\nvoid _dns_client_shutdown_socket(struct dns_server_info *server_info)\n{\n\tif (server_info->fd <= 0) {\n\t\treturn;\n\t}\n\n\tswitch (server_info->type) {\n\tcase DNS_SERVER_UDP:\n\t\tserver_info->status = DNS_SERVER_STATUS_CONNECTING;\n\t\tatomic_set(&server_info->is_alive, 0);\n\t\treturn;\n\t\tbreak;\n\tcase DNS_SERVER_TCP:\n\t\tif (server_info->fd > 0) {\n\t\t\tshutdown(server_info->fd, SHUT_RDWR);\n\t\t}\n\t\tbreak;\n\tcase DNS_SERVER_QUIC:\n\tcase DNS_SERVER_TLS:\n\tcase DNS_SERVER_HTTP3:\n\tcase DNS_SERVER_HTTPS:\n\t\tif (server_info->ssl) {\n\t\t\t/* Shutdown ssl */\n\t\t\tif (server_info->status == DNS_SERVER_STATUS_CONNECTED) {\n\t\t\t\t_ssl_shutdown(server_info);\n\t\t\t}\n\t\t\tshutdown(server_info->fd, SHUT_RDWR);\n\t\t}\n\t\tatomic_set(&server_info->is_alive, 0);\n\t\tbreak;\n\tcase DNS_SERVER_MDNS:\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n}\n\nint _dns_client_socket_send(struct dns_server_info *server_info)\n{\n\tif (server_info->type == DNS_SERVER_UDP) {\n\t\treturn -1;\n\t} else if (server_info->type == DNS_SERVER_TCP) {\n\t\treturn send(server_info->fd, server_info->send_buff.data, server_info->send_buff.len, MSG_NOSIGNAL);\n\t} else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS ||\n\t\t\t   server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) {\n\t\tint write_len = server_info->send_buff.len;\n\t\tif (server_info->ssl_write_len > 0) {\n\t\t\twrite_len = server_info->ssl_write_len;\n\t\t\tserver_info->ssl_write_len = -1;\n\t\t}\n\t\tserver_info->ssl_want_write = 0;\n\n\t\tint ret = _dns_client_socket_ssl_send(server_info, server_info->send_buff.data, write_len);\n\t\tif (ret < 0 && errno == EAGAIN) {\n\t\t\tserver_info->ssl_write_len = write_len;\n\t\t\tif (_dns_client_ssl_poll_event(server_info, SSL_ERROR_WANT_WRITE) == 0) {\n\t\t\t\terrno = EAGAIN;\n\t\t\t}\n\t\t}\n\t\treturn ret;\n\t} else if (server_info->type == DNS_SERVER_MDNS) {\n\t\treturn -1;\n\t} else {\n\t\treturn -1;\n\t}\n}\n\nint _dns_client_socket_recv(struct dns_server_info *server_info)\n{\n\tif (server_info->type == DNS_SERVER_UDP) {\n\t\treturn -1;\n\t} else if (server_info->type == DNS_SERVER_TCP) {\n\t\treturn recv(server_info->fd, server_info->recv_buff.data + server_info->recv_buff.len,\n\t\t\t\t\tDNS_TCP_BUFFER - server_info->recv_buff.len, 0);\n\t} else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS ||\n\t\t\t   server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) {\n\t\tint ret = _dns_client_socket_ssl_recv(server_info, server_info->recv_buff.data + server_info->recv_buff.len,\n\t\t\t\t\t\t\t\t\t\t\t  DNS_TCP_BUFFER - server_info->recv_buff.len);\n\t\tif (ret == -SSL_ERROR_WANT_WRITE && errno == EAGAIN) {\n\t\t\tif (_dns_client_ssl_poll_event(server_info, SSL_ERROR_WANT_WRITE) == 0) {\n\t\t\t\terrno = EAGAIN;\n\t\t\t\tserver_info->ssl_want_write = 1;\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t} else if (server_info->type == DNS_SERVER_MDNS) {\n\t\treturn -1;\n\t} else {\n\t\treturn -1;\n\t}\n}\n\nint _dns_client_copy_data_to_buffer(struct dns_server_info *server_info, void *packet, int len)\n{\n\tif (DNS_TCP_BUFFER - server_info->send_buff.len < len) {\n\t\terrno = ENOMEM;\n\t\treturn -1;\n\t}\n\n\tmemcpy(server_info->send_buff.data + server_info->send_buff.len, packet, len);\n\tserver_info->send_buff.len += len;\n\n\treturn 0;\n}\n\nint _dns_client_send_data_to_buffer(struct dns_server_info *server_info, void *packet, int len)\n{\n\tstruct epoll_event event;\n\n\tif (DNS_TCP_BUFFER - server_info->send_buff.len < len) {\n\t\terrno = ENOMEM;\n\t\treturn -1;\n\t}\n\n\tmemcpy(server_info->send_buff.data + server_info->send_buff.len, packet, len);\n\tserver_info->send_buff.len += len;\n\n\tif (server_info->fd <= 0) {\n\t\terrno = ECONNRESET;\n\t\treturn -1;\n\t}\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLOUT;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &event) != 0) {\n\t\tif (errno == ENOENT) {\n\t\t\t/* fd not found, ignore */\n\t\t\treturn 0;\n\t\t}\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_client/client_socket.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_CLIENT_SOCKET_\n#define _DNS_CLIENT_CLIENT_SOCKET_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_send_data_to_buffer(struct dns_server_info *server_info, void *packet, int len);\n\nint _dns_client_copy_data_to_buffer(struct dns_server_info *server_info, void *packet, int len);\n\nint _dns_client_socket_send(struct dns_server_info *server_info);\n\nint _dns_client_socket_recv(struct dns_server_info *server_info);\n\nint _dns_client_create_socket(struct dns_server_info *server_info);\n\nvoid _dns_client_close_socket(struct dns_server_info *server_info);\n\nvoid _dns_client_close_socket_ext(struct dns_server_info *server_info, int no_del_conn_list);\n\nvoid _dns_client_shutdown_socket(struct dns_server_info *server_info);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/client_tcp.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/http_parse.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include \"client_https.h\"\n#include \"client_socket.h\"\n#include \"client_tcp.h\"\n#include \"client_tls.h\"\n#include \"conn_stream.h\"\n#include \"server_info.h\"\n\n#include <net/if.h>\n#include <netinet/ip.h>\n#include <netinet/tcp.h>\n#include <sys/epoll.h>\n#include <sys/ioctl.h>\n\nint _dns_client_create_socket_tcp(struct dns_server_info *server_info)\n{\n\tint fd = -1;\n\tstruct epoll_event event;\n\tint yes = 1;\n\tconst int priority = SOCKET_PRIORITY;\n\tconst int ip_tos = SOCKET_IP_TOS;\n\tstruct proxy_conn *proxy = NULL;\n\tint ret = 0;\n\n\tif (server_info->proxy_name[0] != '\\0') {\n\t\tproxy = proxy_conn_new(server_info->proxy_name, server_info->ip, server_info->port, 0, 1);\n\t\tif (proxy == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"create proxy failed, %s, proxy: %s\", server_info->ip, server_info->proxy_name);\n\t\t\tgoto errout;\n\t\t}\n\t\tfd = proxy_conn_get_fd(proxy);\n\t} else {\n\t\tfd = socket(server_info->ai_family, SOCK_STREAM, 0);\n\t}\n\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"create socket failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (server_info->flags.ifname[0] != '\\0') {\n\t\tstruct ifreq ifr;\n\t\tmemset(&ifr, 0, sizeof(struct ifreq));\n\t\tsafe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name));\n\t\tioctl(fd, SIOCGIFINDEX, &ifr);\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) {\n\t\t\ttlog(TLOG_ERROR, \"bind socket to device %s failed, %s\\n\", ifr.ifr_name, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tif (set_fd_nonblock(fd, 1) != 0) {\n\t\ttlog(TLOG_ERROR, \"set socket non block failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (server_info->so_mark >= 0) {\n\t\tunsigned int so_mark = server_info->so_mark;\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) {\n\t\t\ttlog(TLOG_DEBUG, \"set socket mark failed, %s\", strerror(errno));\n\t\t}\n\t}\n\n\t/* enable tcp fast open */\n\tif (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &yes, sizeof(yes)) != 0) {\n\t\ttlog(TLOG_DEBUG, \"enable TCP fast open failed, %s\", strerror(errno));\n\t}\n\n\tsetsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));\n\tsetsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority));\n\tsetsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));\n\tsetsockopt(fd, IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof(yes));\n\tsetsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof(yes));\n\tset_sock_keepalive(fd, 30, 3, 5);\n\tif (dns_conf.dns_socket_buff_size > 0) {\n\t\tsetsockopt(fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size));\n\t\tsetsockopt(fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size));\n\t}\n\n\tif (proxy) {\n\t\tret = proxy_conn_connect(proxy);\n\t} else {\n\t\tret = connect(fd, &server_info->addr, server_info->ai_addrlen);\n\t}\n\n\tif (ret != 0) {\n\t\tif (errno != EINPROGRESS) {\n\t\t\ttlog(TLOG_DEBUG, \"connect %s failed, %s\", server_info->ip, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tserver_info->fd = fd;\n\tserver_info->status = DNS_SERVER_STATUS_CONNECTING;\n\tserver_info->security_status = DNS_CLIENT_SERVER_SECURITY_NOT_APPLICABLE;\n\tserver_info->proxy = proxy;\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLOUT;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\ttlog(TLOG_DEBUG, \"tcp server %s connecting.\\n\", server_info->ip);\n\n\treturn 0;\nerrout:\n\tif (server_info->fd > 0) {\n\t\tserver_info->fd = -1;\n\t}\n\n\tserver_info->status = DNS_SERVER_STATUS_INIT;\n\tserver_info->proxy = NULL;\n\tserver_info->security_status = DNS_CLIENT_SERVER_SECURITY_UNKNOW;\n\tserver_info->ssl_write_len = -1;\n\n\tif (fd > 0 && proxy == NULL) {\n\t\tclose(fd);\n\t}\n\n\tif (proxy) {\n\t\tproxy_conn_free(proxy);\n\t}\n\n\treturn -1;\n}\n\nstatic int _dns_client_process_tcp_buff(struct dns_server_info *server_info)\n{\n\tint len = 0;\n\tint dns_packet_len = 0;\n\tstruct http_head *http_head = NULL;\n\tunsigned char *inpacket_data = NULL;\n\tint ret = -1;\n\n\twhile (1) {\n\t\tif (server_info->type == DNS_SERVER_HTTPS) {\n\t\t\thttp_head = http_head_init(4096, HTTP_VERSION_1_1);\n\t\t\tif (http_head == NULL) {\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\tlen = http_head_parse(http_head, server_info->recv_buff.data, server_info->recv_buff.len);\n\t\t\tif (len < 0) {\n\t\t\t\tif (len == -1) {\n\t\t\t\t\tret = 0;\n\t\t\t\t\tgoto out;\n\t\t\t\t} else if (len == -3) {\n\t\t\t\t\t/* repsone is too large */\n\t\t\t\t\ttlog(TLOG_DEBUG, \"http response is too large.\");\n\t\t\t\t\tserver_info->recv_buff.len = 0;\n\t\t\t\t\tgoto out;\n\t\t\t\t}\n\n\t\t\t\ttlog(TLOG_DEBUG, \"remote server not supported.\");\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\tif (http_head_get_httpcode(http_head) != 200) {\n\t\t\t\ttlog(TLOG_WARN, \"http server query from %s:%d failed, server return http code : %d, %s\",\n\t\t\t\t\t server_info->ip, server_info->port, http_head_get_httpcode(http_head),\n\t\t\t\t\t http_head_get_httpcode_msg(http_head));\n\t\t\t\tserver_info->prohibit = 1;\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\tdns_packet_len = http_head_get_data_len(http_head);\n\t\t\tinpacket_data = (unsigned char *)http_head_get_data(http_head);\n\t\t} else {\n\t\t\t/* tcp result format\n\t\t\t * | len (short) | dns query result |\n\t\t\t */\n\t\t\tinpacket_data = server_info->recv_buff.data;\n\t\t\tlen = ntohs(*((unsigned short *)(inpacket_data)));\n\t\t\tif (len <= 0 || len >= DNS_IN_PACKSIZE) {\n\t\t\t\t/* data len is invalid */\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\tif (len > server_info->recv_buff.len - 2) {\n\t\t\t\t/* len is not expected, wait and recv */\n\t\t\t\tret = 0;\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\tinpacket_data = server_info->recv_buff.data + 2;\n\t\t\tdns_packet_len = len;\n\t\t\tlen += 2;\n\t\t}\n\n\t\tif (inpacket_data == NULL || dns_packet_len <= 0) {\n\t\t\ttlog(TLOG_WARN, \"recv tcp packet from %s, len = %d\", server_info->ip, len);\n\t\t\tgoto out;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"recv tcp packet from %s, len = %d\", server_info->ip, len);\n\t\ttime(&server_info->last_recv);\n\t\t/* process result */\n\t\tif (_dns_client_recv(server_info, inpacket_data, dns_packet_len, &server_info->addr, server_info->ai_addrlen) !=\n\t\t\t0) {\n\t\t\tgoto out;\n\t\t}\n\n\t\tif (http_head) {\n\t\t\thttp_head_destroy(http_head);\n\t\t\thttp_head = NULL;\n\t\t}\n\n\t\tserver_info->recv_buff.len -= len;\n\t\tif (server_info->recv_buff.len < 0) {\n\t\t\tBUG(\"Internal error.\");\n\t\t}\n\n\t\t/* move to next result */\n\t\tif (server_info->recv_buff.len > 0) {\n\t\t\tmemmove(server_info->recv_buff.data, server_info->recv_buff.data + len, server_info->recv_buff.len);\n\t\t} else {\n\t\t\tret = 0;\n\t\t\tgoto out;\n\t\t}\n\t}\n\n\tret = 0;\nout:\n\tif (http_head) {\n\t\thttp_head_destroy(http_head);\n\t}\n\treturn ret;\n}\n\nstatic int _dns_client_process_https_streams(struct dns_server_info *server_info)\n{\n\tstruct dns_conn_stream *stream, *tmp;\n\tint ret = 0;\n\n\t/* If negotiated HTTP/2, skip HTTP/1.1 processing */\n\tif (server_info->alpn_selected[0] != '\\0' && strncmp(server_info->alpn_selected, \"h2\", 2) == 0) {\n\t\treturn 0;\n\t}\n\n\tpthread_mutex_lock(&server_info->lock);\n\tlist_for_each_entry_safe(stream, tmp, &server_info->conn_stream_list, server_list)\n\t{\n\t\tif (stream->send_buff.len == 0) {\n\t\t\tcontinue;\n\t\t}\n\t\t/* Format raw DNS data to HTTP/1.1 */\n\t\tunsigned char http_packet[DNS_IN_PACKSIZE];\n\t\tint http_len = _dns_client_format_https_packet(server_info, stream->send_buff.data, stream->send_buff.len,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   http_packet, sizeof(http_packet));\n\t\tif (http_len < 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\t/* Send the formatted packet */\n\t\tint send_len;\n\t\tif (server_info->ssl) {\n\t\t\tsend_len = _dns_client_socket_ssl_send(server_info, http_packet, http_len);\n\t\t} else {\n\t\t\tsend_len = send(server_info->fd, http_packet, http_len, MSG_NOSIGNAL);\n\t\t}\n\t\tif (send_len < 0) {\n\t\t\tif (errno == EAGAIN) {\n\t\t\t\t/* Buffer the formatted data */\n\t\t\t\tif (_dns_client_send_data_to_buffer(server_info, http_packet, http_len) != 0) {\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\tgoto out;\n\t\t\t}\n\t\t\tgoto errout;\n\t\t}\n\t\tif (send_len < http_len) {\n\t\t\t/* Buffer remaining */\n\t\t\tif (_dns_client_send_data_to_buffer(server_info, http_packet + send_len, http_len - send_len) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tgoto out;\n\t\t}\n\t\t/* Clear stream buffer after sending */\n\t\tstream->send_buff.len = 0;\n\t\t/* Remove from list and release */\n\t\tlist_del_init(&stream->server_list);\n\t\t_dns_client_conn_stream_put(stream);\n\t}\nout:\n\tpthread_mutex_unlock(&server_info->lock);\n\treturn ret;\nerrout:\n\tret = -1;\n\tgoto out;\n}\nint _dns_client_process_tcp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now)\n{\n\tint len = 0;\n\tint ret = -1;\n\n\tif (event->events & EPOLLIN) {\n\t\t/* receive from tcp */\n\t\tlen = _dns_client_socket_recv(server_info);\n\t\tif (len < 0) {\n\t\t\t/* no data to recv, try again */\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tif (errno == ECONNRESET || errno == ENETUNREACH || errno == EHOSTUNREACH) {\n\t\t\t\ttlog(TLOG_DEBUG, \"recv failed, server %s:%d, %s\\n\", server_info->ip, server_info->port,\n\t\t\t\t\t strerror(errno));\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (errno == ETIMEDOUT || errno == ECONNREFUSED) {\n\t\t\t\ttlog(TLOG_INFO, \"recv failed, server %s:%d, %s\\n\", server_info->ip, server_info->port, strerror(errno));\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\ttlog(TLOG_WARN, \"recv failed, server %s:%d, %s\\n\", server_info->ip, server_info->port, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\t/* peer server close */\n\t\tif (len == 0) {\n\t\t\tpthread_mutex_lock(&client.server_list_lock);\n\t\t\t_dns_client_close_socket(server_info);\n\t\t\tserver_info->recv_buff.len = 0;\n\t\t\tif (server_info->send_buff.len > 0) {\n\t\t\t\t/* still remain request data, reconnect and send*/\n\t\t\t\tret = _dns_client_create_socket(server_info);\n\t\t\t} else {\n\t\t\t\tret = 0;\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&client.server_list_lock);\n\t\t\ttlog(TLOG_DEBUG, \"peer close, %s:%d\", server_info->ip, server_info->port);\n\t\t\treturn ret;\n\t\t}\n\n\t\tserver_info->recv_buff.len += len;\n\t\tif (server_info->recv_buff.len <= 2) {\n\t\t\t/* wait and recv */\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (_dns_client_process_tcp_buff(server_info) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\t/* when connected */\n\tif (event->events & EPOLLOUT) {\n\t\tif (server_info->status == DNS_SERVER_STATUS_CONNECTING) {\n\t\t\tserver_info->status = DNS_SERVER_STATUS_CONNECTED;\n\t\t\ttlog(TLOG_DEBUG, \"tcp server %s connected\", server_info->ip);\n\t\t}\n\n\t\tif (server_info->status != DNS_SERVER_STATUS_CONNECTED) {\n\t\t\tserver_info->status = DNS_SERVER_STATUS_DISCONNECTED;\n\t\t}\n\n\t\tif (server_info->send_buff.len > 0 || server_info->ssl_want_write == 1) {\n\t\t\t/* send existing send_buffer data  */\n\t\t\tlen = _dns_client_socket_send(server_info);\n\t\t\tif (len < 0) {\n\t\t\t\tif (errno == EAGAIN) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tpthread_mutex_lock(&client.server_list_lock);\n\t\t\tserver_info->send_buff.len -= len;\n\t\t\tif (server_info->send_buff.len > 0) {\n\t\t\t\tmemmove(server_info->send_buff.data, server_info->send_buff.data + len, server_info->send_buff.len);\n\t\t\t} else if (server_info->send_buff.len < 0) {\n\t\t\t\tBUG(\"Internal Error\");\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&client.server_list_lock);\n\t\t}\n\n\t\t/* Process HTTPS streams if any */\n\t\tif (server_info->type == DNS_SERVER_HTTPS && server_info->send_buff.len == 0) {\n\t\t\tif (_dns_client_process_https_streams(server_info) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t}\n\n\t\t/* still remain data, retry */\n\t\tif (server_info->send_buff.len > 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\t/* clear epollout event */\n\t\tstruct epoll_event mod_event;\n\t\tmemset(&mod_event, 0, sizeof(mod_event));\n\t\tmod_event.events = EPOLLIN;\n\t\tmod_event.data.ptr = server_info;\n\t\tif (server_info->fd > 0) {\n\t\t\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &mod_event) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n\nerrout:\n\tpthread_mutex_lock(&client.server_list_lock);\n\tserver_info->recv_buff.len = 0;\n\tserver_info->send_buff.len = 0;\n\t_dns_client_close_socket(server_info);\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\treturn -1;\n}\n\nint _dns_client_send_tcp(struct dns_server_info *server_info, void *packet, unsigned short len)\n{\n\tint send_len = 0;\n\tunsigned char inpacket_data[DNS_IN_PACKSIZE];\n\tunsigned char *inpacket = inpacket_data;\n\n\tif (len > sizeof(inpacket_data) - 2) {\n\t\ttlog(TLOG_ERROR, \"packet size is invalid.\");\n\t\treturn -1;\n\t}\n\n\t/* TCP query format\n\t * | len (short) | dns query data |\n\t */\n\t*((unsigned short *)(inpacket)) = htons(len);\n\tmemcpy(inpacket + 2, packet, len);\n\tlen += 2;\n\n\tif (server_info->status != DNS_SERVER_STATUS_CONNECTED) {\n\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket, len);\n\t}\n\n\tif (server_info->fd <= 0) {\n\t\treturn -1;\n\t}\n\n\tsend_len = send(server_info->fd, inpacket, len, MSG_NOSIGNAL);\n\tif (send_len < 0) {\n\t\tif (errno == EAGAIN) {\n\t\t\t/* save data to buffer, and retry when EPOLLOUT is available */\n\t\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket, len);\n\t\t} else if (errno == EPIPE) {\n\t\t\t_dns_client_shutdown_socket(server_info);\n\t\t}\n\t\treturn -1;\n\t} else if (send_len < len) {\n\t\t/* save remain data to buffer, and retry when EPOLLOUT is available */\n\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket + send_len, len - send_len);\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_client_check_tcp(void)\n{\n\tstruct dns_server_info *server_info = NULL;\n\ttime_t now = 0;\n\n\ttime(&now);\n\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_for_each_entry(server_info, &client.dns_server_list, list)\n\t{\n\t\tif (server_info->type == DNS_SERVER_UDP || server_info->type == DNS_SERVER_MDNS) {\n\t\t\t/* no need to check udp server */\n\t\t\tcontinue;\n\t\t}\n\n#if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC)\n\t\tif (server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) {\n\t\t\tif (server_info->ssl) {\n\t\t\t\t_ssl_do_handevent(server_info);\n\t\t\t\tif (SSL_get_shutdown(server_info->ssl) != 0) {\n\t\t\t\t\t_dns_client_close_socket_ext(server_info, 1);\n\t\t\t\t\ttlog(TLOG_DEBUG, \"quick server %s:%d shutdown.\", server_info->ip, server_info->port);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n#endif\n\n\t\tif (server_info->status == DNS_SERVER_STATUS_CONNECTING) {\n\t\t\tif (server_info->last_recv + DNS_TCP_CONNECT_TIMEOUT < now) {\n\t\t\t\ttlog(TLOG_DEBUG, \"server %s:%d connect timeout.\", server_info->ip, server_info->port);\n\t\t\t\t_dns_client_close_socket(server_info);\n\t\t\t}\n\t\t} else if (server_info->status == DNS_SERVER_STATUS_CONNECTED) {\n\t\t\tif (server_info->last_recv + DNS_TCP_IDLE_TIMEOUT < now) {\n\t\t\t\t/*disconnect if the server is not responding */\n\t\t\t\tserver_info->recv_buff.len = 0;\n\t\t\t\tserver_info->send_buff.len = 0;\n\t\t\t\t_dns_client_close_socket(server_info);\n\t\t\t}\n\t\t}\n\t}\n\tpthread_mutex_unlock(&client.server_list_lock);\n}\n"
  },
  {
    "path": "src/dns_client/client_tcp.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_TCP_H_\n#define _DNS_CLIENT_TCP_H_\n\n#include \"dns_client.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_create_socket_tcp(struct dns_server_info *server_info);\n\nint _dns_client_process_tcp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now);\n\nint _dns_client_send_tcp(struct dns_server_info *server_info, void *packet, unsigned short len);\n\nvoid _dns_client_check_tcp(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/client_tls.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"client_tls.h\"\n#include \"client_http2.h\"\n#include \"client_quic.h\"\n#include \"client_socket.h\"\n#include \"client_tcp.h\"\n#include \"conn_stream.h\"\n#include \"server_info.h\"\n\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <net/if.h>\n#include <netinet/ip.h>\n#include <netinet/tcp.h>\n#include <openssl/err.h>\n#include <openssl/rand.h>\n#include <openssl/ssl.h>\n#include <openssl/x509v3.h>\n#include <sys/epoll.h>\n#include <sys/ioctl.h>\n\nstatic ssize_t _ssl_read_ext(struct dns_server_info *server, SSL *ssl, void *buff, int num)\n{\n\tssize_t ret = 0;\n\tpthread_mutex_lock(&server->lock);\n\tif (server == NULL || buff == NULL || ssl == NULL) {\n\t\tpthread_mutex_unlock(&server->lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\tret = SSL_read(ssl, buff, num);\n\tpthread_mutex_unlock(&server->lock);\n\treturn ret;\n}\n\nstatic ssize_t _ssl_write_ext2(struct dns_server_info *server, SSL *ssl, const void *buff, int num, uint64_t flags)\n{\n\tssize_t ret = 0;\n\tsize_t written = 0;\n\tpthread_mutex_lock(&server->lock);\n\tif (server == NULL || buff == NULL || ssl == NULL) {\n\t\tpthread_mutex_unlock(&server->lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n#if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC)\n\tret = SSL_write_ex2(ssl, buff, num, flags, &written);\n#elif OPENSSL_VERSION_NUMBER >= 0x10101000L\n\tret = SSL_write_ex(ssl, buff, num, &written);\n#else\n\tret = SSL_write(ssl, buff, num);\n\twritten = ret;\n#endif\n\tpthread_mutex_unlock(&server->lock);\n\n\tif (ret <= 0) {\n\t\treturn ret;\n\t}\n\n\treturn written;\n}\n\nint _ssl_shutdown(struct dns_server_info *server)\n{\n\tint ret = 0;\n\tpthread_mutex_lock(&server->lock);\n\tif (server == NULL || server->ssl == NULL) {\n\t\tpthread_mutex_unlock(&server->lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n\tret = SSL_shutdown(server->ssl);\n\tpthread_mutex_unlock(&server->lock);\n\treturn ret;\n}\n\nstatic int _ssl_get_error_ext(struct dns_server_info *server, SSL *ssl, int ret)\n{\n\tint err = 0;\n\tpthread_mutex_lock(&server->lock);\n\tif (server == NULL || ssl == NULL) {\n\t\tpthread_mutex_unlock(&server->lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n\terr = SSL_get_error(ssl, ret);\n\tpthread_mutex_unlock(&server->lock);\n\treturn err;\n}\n\nstatic int _ssl_get_error(struct dns_server_info *server, int ret)\n{\n\treturn _ssl_get_error_ext(server, server->ssl, ret);\n}\n\nstatic int _ssl_do_handshake(struct dns_server_info *server)\n{\n\tint err = 0;\n\tpthread_mutex_lock(&server->lock);\n\tif (server == NULL || server->ssl == NULL) {\n\t\tpthread_mutex_unlock(&server->lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n\terr = SSL_do_handshake(server->ssl);\n\tpthread_mutex_unlock(&server->lock);\n\treturn err;\n}\n\nint _ssl_do_handevent(struct dns_server_info *server)\n{\n\tint err = 0;\n\tpthread_mutex_lock(&server->lock);\n\tif (server == NULL || server->ssl == NULL) {\n\t\tpthread_mutex_unlock(&server->lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n#if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC)\n\terr = SSL_handle_events(server->ssl);\n#else\n\terr = SSL_ERROR_SYSCALL;\n#endif\n\tpthread_mutex_unlock(&server->lock);\n\treturn err;\n}\n\nstatic int _ssl_session_reused(struct dns_server_info *server)\n{\n\tint err = 0;\n\tpthread_mutex_lock(&server->lock);\n\tif (server == NULL || server->ssl == NULL) {\n\t\tpthread_mutex_unlock(&server->lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n\terr = SSL_session_reused(server->ssl);\n\tpthread_mutex_unlock(&server->lock);\n\treturn err;\n}\n\nstatic SSL_SESSION *_ssl_get1_session(struct dns_server_info *server)\n{\n\tSSL_SESSION *ret = NULL;\n\tpthread_mutex_lock(&server->lock);\n\tif (server == NULL || server->ssl == NULL) {\n\t\tpthread_mutex_unlock(&server->lock);\n\t\treturn NULL;\n\t}\n\n\tret = SSL_get1_session(server->ssl);\n\tpthread_mutex_unlock(&server->lock);\n\treturn ret;\n}\n\nint dns_client_spki_decode(const char *spki, unsigned char *spki_data_out, int spki_data_out_max_len)\n{\n\tint spki_data_len = -1;\n\n\tspki_data_len = SSL_base64_decode(spki, spki_data_out, spki_data_out_max_len);\n\n\tif (spki_data_len != SHA256_DIGEST_LENGTH) {\n\t\treturn -1;\n\t}\n\n\treturn spki_data_len;\n}\n\nstatic char *_dns_client_server_get_tls_host_verify(struct dns_server_info *server_info)\n{\n\tchar *tls_host_verify = NULL;\n\n\tswitch (server_info->type) {\n\tcase DNS_SERVER_UDP: {\n\t} break;\n\tcase DNS_SERVER_HTTP3:\n\tcase DNS_SERVER_HTTPS: {\n\t\tstruct client_dns_server_flag_https *flag_https = &server_info->flags.https;\n\t\ttls_host_verify = flag_https->tls_host_verify;\n\t} break;\n\tcase DNS_SERVER_QUIC:\n\tcase DNS_SERVER_TLS: {\n\t\tstruct client_dns_server_flag_tls *flag_tls = &server_info->flags.tls;\n\t\ttls_host_verify = flag_tls->tls_host_verify;\n\t} break;\n\tcase DNS_SERVER_TCP:\n\t\tbreak;\n\tcase DNS_SERVER_MDNS:\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t\tbreak;\n\t}\n\n\tif (tls_host_verify) {\n\t\tif (tls_host_verify[0] == '\\0') {\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn tls_host_verify;\n}\n\nstatic char *_dns_client_server_get_spki(struct dns_server_info *server_info, int *spki_len)\n{\n\t*spki_len = 0;\n\tchar *spki = NULL;\n\tswitch (server_info->type) {\n\tcase DNS_SERVER_UDP: {\n\t} break;\n\tcase DNS_SERVER_HTTP3:\n\tcase DNS_SERVER_HTTPS: {\n\t\tstruct client_dns_server_flag_https *flag_https = &server_info->flags.https;\n\t\tspki = flag_https->spki;\n\t\t*spki_len = flag_https->spi_len;\n\t} break;\n\tcase DNS_SERVER_QUIC:\n\tcase DNS_SERVER_TLS: {\n\t\tstruct client_dns_server_flag_tls *flag_tls = &server_info->flags.tls;\n\t\tspki = flag_tls->spki;\n\t\t*spki_len = flag_tls->spi_len;\n\t} break;\n\tcase DNS_SERVER_TCP:\n\t\tbreak;\n\tcase DNS_SERVER_MDNS:\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t\tbreak;\n\t}\n\n\tif (*spki_len <= 0) {\n\t\treturn NULL;\n\t}\n\n\treturn spki;\n}\n\nstatic int _dns_client_set_trusted_cert(SSL_CTX *ssl_ctx)\n{\n\tchar *cafile = NULL;\n\tchar *capath = NULL;\n\tint cert_path_set = 0;\n\n\tif (ssl_ctx == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (dns_conf.ca_file[0]) {\n\t\tcafile = dns_conf.ca_file;\n\t}\n\n\tif (dns_conf.ca_path[0]) {\n\t\tcapath = dns_conf.ca_path;\n\t}\n\n\tif (cafile == NULL && capath == NULL) {\n\t\tif (SSL_CTX_set_default_verify_paths(ssl_ctx)) {\n\t\t\tcert_path_set = 1;\n\t\t}\n\n\t\tconst STACK_OF(X509_NAME) *cas = SSL_CTX_get_client_CA_list(ssl_ctx);\n\t\tif (cas && sk_X509_NAME_num(cas) == 0) {\n\t\t\tcafile = \"/etc/ssl/certs/ca-certificates.crt\";\n\t\t\tcapath = \"/etc/ssl/certs\";\n\t\t\tcert_path_set = 0;\n\t\t}\n\t}\n\n\tif (cert_path_set == 0) {\n\t\tif (SSL_CTX_load_verify_locations(ssl_ctx, cafile, capath) == 0) {\n\t\t\ttlog(TLOG_WARN, \"load certificate from %s:%s failed.\", cafile, capath);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nSSL_CTX *_ssl_ctx_get(int is_quic)\n{\n\tSSL_CTX **ssl_ctx = NULL;\n\tpthread_mutex_lock(&client.server_list_lock);\n\tif (is_quic) {\n\t\tssl_ctx = &client.ssl_quic_ctx;\n\t} else {\n\t\tssl_ctx = &client.ssl_ctx;\n\t}\n\n\tif (*ssl_ctx) {\n\t\tpthread_mutex_unlock(&client.server_list_lock);\n\t\treturn *ssl_ctx;\n\t}\n\n#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)\n#if (OPENSSL_VERSION_NUMBER >= 0x30200000L)\n\tif (is_quic) {\n\t\t*ssl_ctx = SSL_CTX_new(OSSL_QUIC_client_method());\n\t} else {\n\t\t*ssl_ctx = SSL_CTX_new(TLS_client_method());\n\t}\n#else\n\tif (is_quic) {\n\t\treturn NULL;\n\t}\n\t*ssl_ctx = SSL_CTX_new(TLS_client_method());\n#endif\n#else\n\t*ssl_ctx = SSL_CTX_new(SSLv23_client_method());\n#endif\n\n\tif (*ssl_ctx == NULL) {\n\t\ttlog(TLOG_ERROR, \"init ssl failed.\");\n\t\tgoto errout;\n\t}\n\n\tSSL_CTX_set_options(*ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);\n\tSSL_CTX_set_session_cache_mode(*ssl_ctx, SSL_SESS_CACHE_CLIENT);\n\tSSL_CTX_sess_set_cache_size(*ssl_ctx, DNS_MAX_SERVERS);\n\tif (_dns_client_set_trusted_cert(*ssl_ctx) != 0) {\n\t\tSSL_CTX_set_verify(*ssl_ctx, SSL_VERIFY_NONE, NULL);\n\t\tclient.ssl_verify_skip = 1;\n\t}\n\n\tpthread_mutex_unlock(&client.server_list_lock);\n\treturn *ssl_ctx;\nerrout:\n\tif (*ssl_ctx) {\n\t\tSSL_CTX_free(*ssl_ctx);\n\t}\n\n\t*ssl_ctx = NULL;\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\treturn NULL;\n}\n\nint _dns_client_create_socket_tls(struct dns_server_info *server_info, const char *hostname, const char *alpn)\n{\n\tint fd = -1;\n\tstruct epoll_event event;\n\tSSL *ssl = NULL;\n\tstruct proxy_conn *proxy = NULL;\n\n\tint yes = 1;\n\tconst int priority = SOCKET_PRIORITY;\n\tconst int ip_tos = SOCKET_IP_TOS;\n\tint ret = -1;\n\n\tif (server_info->ssl_ctx == NULL) {\n\t\ttlog(TLOG_ERROR, \"create ssl ctx failed, %s\", server_info->ip);\n\t\tgoto errout;\n\t}\n\n\tif (server_info->proxy_name[0] != '\\0') {\n\t\tproxy = proxy_conn_new(server_info->proxy_name, server_info->ip, server_info->port, 0, 1);\n\t\tif (proxy == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"create proxy failed, %s, proxy: %s\", server_info->ip, server_info->proxy_name);\n\t\t\tgoto errout;\n\t\t}\n\t\tfd = proxy_conn_get_fd(proxy);\n\t} else {\n\t\tfd = socket(server_info->ai_family, SOCK_STREAM, 0);\n\t}\n\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"create socket failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (server_info->flags.ifname[0] != '\\0') {\n\t\tstruct ifreq ifr;\n\t\tmemset(&ifr, 0, sizeof(struct ifreq));\n\t\tsafe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name));\n\t\tioctl(fd, SIOCGIFINDEX, &ifr);\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) {\n\t\t\ttlog(TLOG_ERROR, \"bind socket to device %s failed, %s\\n\", ifr.ifr_name, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tssl = SSL_new(server_info->ssl_ctx);\n\tif (ssl == NULL) {\n\t\ttlog(TLOG_ERROR, \"new ssl failed, %s\", server_info->ip);\n\t\tgoto errout;\n\t}\n\n\tif (set_fd_nonblock(fd, 1) != 0) {\n\t\ttlog(TLOG_ERROR, \"set socket non block failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (server_info->so_mark >= 0) {\n\t\tunsigned int so_mark = server_info->so_mark;\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) {\n\t\t\ttlog(TLOG_DEBUG, \"set socket mark failed, %s\", strerror(errno));\n\t\t}\n\t}\n\n\tif (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &yes, sizeof(yes)) != 0) {\n\t\ttlog(TLOG_DEBUG, \"enable TCP fast open failed.\");\n\t}\n\n\t// ? this cause ssl crash ?\n\tsetsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));\n\tsetsockopt(fd, IPPROTO_TCP, TCP_THIN_DUPACK, &yes, sizeof(yes));\n\tsetsockopt(fd, IPPROTO_TCP, TCP_THIN_LINEAR_TIMEOUTS, &yes, sizeof(yes));\n\tset_sock_keepalive(fd, 30, 3, 5);\n\tsetsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority));\n\tsetsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));\n\tif (dns_conf.dns_socket_buff_size > 0) {\n\t\tsetsockopt(fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size));\n\t\tsetsockopt(fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size));\n\t}\n\n\tif (proxy) {\n\t\tret = proxy_conn_connect(proxy);\n\t} else {\n\t\tret = connect(fd, &server_info->addr, server_info->ai_addrlen);\n\t}\n\n\tif (ret != 0) {\n\t\tif (errno != EINPROGRESS) {\n\t\t\ttlog(TLOG_DEBUG, \"connect %s failed, %s\", server_info->ip, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tSSL_set_connect_state(ssl);\n\tif (SSL_set_fd(ssl, fd) == 0) {\n\t\ttlog(TLOG_ERROR, \"ssl set fd failed.\");\n\t\tgoto errout;\n\t}\n\n\t/* reuse ssl session */\n\tif (server_info->ssl_session) {\n\t\tSSL_set_session(ssl, server_info->ssl_session);\n\t}\n\n\tSSL_set_mode(ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE);\n\tif (hostname && hostname[0] != 0) {\n\t\tSSL_set_tlsext_host_name(ssl, hostname);\n\t}\n\n\tif (alpn && alpn[0] != 0) {\n\t\tuint8_t alpn_data[DNS_MAX_ALPN_LEN];\n\t\tint alpn_data_len = encode_alpn_protos(alpn, alpn_data, sizeof(alpn_data));\n\n\t\tif (alpn_data_len > 0) {\n\t\t\tif (SSL_set_alpn_protos(ssl, alpn_data, alpn_data_len)) {\n\t\t\t\ttlog(TLOG_INFO, \"SSL_set_alpn_protos failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (server_info->ssl) {\n\t\tSSL_free(server_info->ssl);\n\t\tserver_info->ssl = NULL;\n\t}\n\n\tserver_info->fd = fd;\n\tserver_info->ssl = ssl;\n\tserver_info->ssl_write_len = -1;\n\tserver_info->status = DNS_SERVER_STATUS_CONNECTING;\n\tserver_info->proxy = proxy;\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLOUT;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\ttlog(TLOG_DEBUG, \"tls server %s connecting.\\n\", server_info->ip);\n\n\treturn 0;\nerrout:\n\tif (server_info->fd > 0) {\n\t\tserver_info->fd = -1;\n\t}\n\n\tif (server_info->ssl) {\n\t\tserver_info->ssl = NULL;\n\t}\n\n\tserver_info->status = DNS_SERVER_STATUS_INIT;\n\tserver_info->proxy = NULL;\n\tserver_info->ssl_write_len = -1;\n\n\tif (fd > 0 && proxy == NULL) {\n\t\tclose(fd);\n\t}\n\n\tif (ssl) {\n\t\tSSL_free(ssl);\n\t}\n\n\tif (proxy) {\n\t\tproxy_conn_free(proxy);\n\t}\n\n\treturn -1;\n}\n\nint _dns_client_socket_ssl_send_ext(struct dns_server_info *server, SSL *ssl, const void *buf, int num, uint64_t flags)\n{\n\tint ret = 0;\n\tint ssl_ret = 0;\n\tunsigned long ssl_err = 0;\n\n\tif (ssl == NULL) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tif (num < 0) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tret = _ssl_write_ext2(server, ssl, buf, num, flags);\n\tif (ret > 0) {\n\t\treturn ret;\n\t}\n\n\tssl_ret = _ssl_get_error_ext(server, ssl, ret);\n\tswitch (ssl_ret) {\n\tcase SSL_ERROR_NONE:\n\tcase SSL_ERROR_ZERO_RETURN:\n\t\treturn 0;\n\t\tbreak;\n\tcase SSL_ERROR_WANT_READ:\n\t\terrno = EAGAIN;\n\t\tret = -SSL_ERROR_WANT_READ;\n\t\tbreak;\n\tcase SSL_ERROR_WANT_WRITE:\n\t\terrno = EAGAIN;\n\t\tret = -SSL_ERROR_WANT_WRITE;\n\t\tbreak;\n\tcase SSL_ERROR_SSL: {\n\t\tchar buff[256];\n\t\tssl_err = ERR_get_error();\n\t\tint ssl_reason = ERR_GET_REASON(ssl_err);\n\t\tif (ssl_reason == SSL_R_UNINITIALIZED || ssl_reason == SSL_R_PROTOCOL_IS_SHUTDOWN ||\n\t\t\tssl_reason == SSL_R_BAD_LENGTH || ssl_reason == SSL_R_SHUTDOWN_WHILE_IN_INIT ||\n\t\t\tssl_reason == SSL_R_BAD_WRITE_RETRY) {\n\t\t\terrno = EAGAIN;\n\t\t\treturn -1;\n\t\t}\n\n\t\ttlog(TLOG_WARN, \"server %s SSL write fail error: %s\", server->ip, ERR_error_string(ssl_err, buff));\n\t\terrno = EFAULT;\n\t\tret = -1;\n\t} break;\n\tcase SSL_ERROR_SYSCALL:\n\t\ttlog(TLOG_DEBUG, \"SSL syscall failed, %s\", strerror(errno));\n\t\treturn ret;\n\tdefault:\n\t\terrno = EFAULT;\n\t\tret = -1;\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nint _dns_client_socket_ssl_recv_ext(struct dns_server_info *server, SSL *ssl, void *buf, int num)\n{\n\tssize_t ret = 0;\n\tint ssl_ret = 0;\n\tunsigned long ssl_err = 0;\n\n\tif (ssl == NULL) {\n\t\terrno = EFAULT;\n\t\treturn -1;\n\t}\n\n\tret = _ssl_read_ext(server, ssl, buf, num);\n\tif (ret > 0) {\n\t\treturn ret;\n\t}\n\n\tssl_ret = _ssl_get_error_ext(server, ssl, ret);\n\tswitch (ssl_ret) {\n\tcase SSL_ERROR_NONE:\n\tcase SSL_ERROR_ZERO_RETURN:\n\t\treturn 0;\n\t\tbreak;\n\tcase SSL_ERROR_WANT_READ:\n\t\terrno = EAGAIN;\n\t\tret = -SSL_ERROR_WANT_READ;\n\t\tbreak;\n\tcase SSL_ERROR_WANT_WRITE:\n\t\terrno = EAGAIN;\n\t\tret = -SSL_ERROR_WANT_WRITE;\n\t\tbreak;\n\tcase SSL_ERROR_SSL: {\n\t\tchar buff[256];\n\n\t\tssl_err = ERR_get_error();\n\t\tint ssl_reason = ERR_GET_REASON(ssl_err);\n\n\t\tswitch (ssl_reason) {\n\t\tcase SSL_R_UNINITIALIZED:\n\t\t\terrno = EAGAIN;\n\t\t\treturn -1;\n\t\tcase SSL_R_SHUTDOWN_WHILE_IN_INIT:\n\t\tcase SSL_R_PROTOCOL_IS_SHUTDOWN:\n#ifdef SSL_R_STREAM_FINISHED\n\t\tcase SSL_R_STREAM_FINISHED:\n#endif\n#ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING\n\t\tcase SSL_R_UNEXPECTED_EOF_WHILE_READING:\n#endif\n\t\t\treturn 0;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\n\t\ttlog(TLOG_WARN, \"server %s SSL read fail error: %s\", server->ip, ERR_error_string(ssl_err, buff));\n\t\terrno = EFAULT;\n\t\tret = -1;\n\t} break;\n\tcase SSL_ERROR_SYSCALL:\n\t\tif (errno == 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tret = -1;\n\t\treturn ret;\n\tdefault:\n\t\terrno = EFAULT;\n\t\tret = -1;\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nint _dns_client_socket_ssl_send(struct dns_server_info *server, const void *buf, int num)\n{\n\treturn _dns_client_socket_ssl_send_ext(server, server->ssl, buf, num, 0);\n}\n\nint _dns_client_socket_ssl_recv(struct dns_server_info *server, void *buf, int num)\n{\n\treturn _dns_client_socket_ssl_recv_ext(server, server->ssl, buf, num);\n}\n\nint _dns_client_ssl_poll_event(struct dns_server_info *server_info, int ssl_ret)\n{\n\tstruct epoll_event fd_event;\n\n\tmemset(&fd_event, 0, sizeof(fd_event));\n\n\tif (ssl_ret == SSL_ERROR_WANT_READ) {\n\t\tfd_event.events = EPOLLIN;\n\t} else if (ssl_ret == SSL_ERROR_WANT_WRITE) {\n\t\tfd_event.events = EPOLLOUT | EPOLLIN;\n\t} else {\n\t\tgoto errout;\n\t}\n\n\tif (server_info->fd < 0) {\n\t\tgoto errout;\n\t}\n\n\tfd_event.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &fd_event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nstatic inline int _dns_client_to_hex(int c)\n{\n\tif (c > 0x9) {\n\t\treturn 'A' + c - 0xA;\n\t}\n\n\treturn '0' + c;\n}\n\nstatic int _dns_client_tls_matchName(const char *host, const char *pattern, int size)\n{\n\tint match = -1;\n\tint i = 0;\n\tint j = 0;\n\n\twhile (i < size && host[j] != '\\0') {\n\t\tif (toupper(pattern[i]) == toupper(host[j])) {\n\t\t\ti++;\n\t\t\tj++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (pattern[i] == '*') {\n\t\t\twhile (host[j] != '.' && host[j] != '\\0') {\n\t\t\t\tj++;\n\t\t\t}\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\n\tif (i == size && host[j] == '\\0') {\n\t\tmatch = 0;\n\t}\n\n\treturn match;\n}\n\nstatic int _dns_client_tls_get_cert_CN(X509 *cert, char *cn, int max_cn_len)\n{\n\tX509_NAME *cert_name = NULL;\n\n\tcert_name = X509_get_subject_name(cert);\n\tif (cert_name == NULL) {\n\t\ttlog(TLOG_ERROR, \"get subject name failed.\");\n\t\tgoto errout;\n\t}\n\n\tif (X509_NAME_get_text_by_NID(cert_name, NID_commonName, cn, max_cn_len) == -1) {\n\t\ttlog(TLOG_ERROR, \"cannot found x509 name\");\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\n/*\n * check SAN\n * return 0: match\n * return -1: not match\n * return -2: no SAN\n */\nstatic int _dns_client_verify_SAN(struct dns_server_info *server_info, X509 *cert)\n{\n\tGENERAL_NAMES *alt_names = NULL;\n\tint i = 0;\n\tint ret = -1;\n\tchar *tls_host_verify = NULL;\n\n\t/* check tls host */\n\ttls_host_verify = _dns_client_server_get_tls_host_verify(server_info);\n\tif (tls_host_verify == NULL) {\n\t\treturn 0;\n\t}\n\n\talt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);\n\tif (alt_names == NULL) {\n\t\tret = -2;\n\t\tgoto errout;\n\t}\n\n\tif (sk_GENERAL_NAME_num(alt_names) == 0) {\n\t\tret = -2;\n\t\tgoto errout;\n\t}\n\n\t/* found subject alt name */\n\tfor (i = 0; i < sk_GENERAL_NAME_num(alt_names); i++) {\n\t\tGENERAL_NAME *name = sk_GENERAL_NAME_value(alt_names, i);\n\t\tif (name == NULL) {\n\t\t\tcontinue;\n\t\t}\n\t\tswitch (name->type) {\n\t\tcase GEN_DNS: {\n\t\t\tASN1_IA5STRING *dns = name->d.dNSName;\n\t\t\tif (dns == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttlog(TLOG_DEBUG, \"peer SAN: %s\", dns->data);\n\t\t\tif (_dns_client_tls_matchName(tls_host_verify, (char *)dns->data, dns->length) == 0) {\n\t\t\t\ttlog(TLOG_DEBUG, \"peer SAN match: %s\", dns->data);\n\t\t\t\tGENERAL_NAMES_free(alt_names);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t} break;\n\t\tcase GEN_IPADD:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\ttlog(TLOG_WARN, \"server %s SAN is invalid, expect SAN: %s\", server_info->ip, tls_host_verify);\n\treturn -1;\nerrout:\n\tserver_info->prohibit = 1;\n\tif (alt_names) {\n\t\tGENERAL_NAMES_free(alt_names);\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_client_verify_common_name(struct dns_server_info *server_info, X509 *cert)\n{\n\tchar *tls_host_verify = NULL;\n\tchar peer_CN[256];\n\n\ttls_host_verify = _dns_client_server_get_tls_host_verify(server_info);\n\tif (tls_host_verify == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (_dns_client_tls_get_cert_CN(cert, peer_CN, sizeof(peer_CN)) != 0) {\n\t\ttlog(TLOG_ERROR, \"get cert CN failed.\");\n\t\tgoto errout;\n\t}\n\n\ttlog(TLOG_DEBUG, \"peer CN: %s\", peer_CN);\n\n\t/* check tls host */\n\ttls_host_verify = _dns_client_server_get_tls_host_verify(server_info);\n\tif (tls_host_verify == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (tls_host_verify) {\n\t\tif (_dns_client_tls_matchName(tls_host_verify, peer_CN, strnlen(peer_CN, DNS_MAX_CNAME_LEN)) == 0) {\n\t\t\treturn 0;\n\t\t}\n\t}\n\nerrout:\n\n\ttlog(TLOG_WARN, \"server %s CN is invalid, expect CN: %s, Peer CN: %s\", server_info->ip, tls_host_verify, peer_CN);\n\tserver_info->prohibit = 1;\n\n\treturn -1;\n}\n\nstatic int _dns_client_tls_verify(struct dns_server_info *server_info)\n{\n\tX509 *cert = NULL;\n\tX509_PUBKEY *pubkey = NULL;\n\n\tchar cert_fingerprint[256];\n\tint i = 0;\n\tint key_len = 0;\n\tunsigned char *key_data = NULL;\n\tunsigned char *key_data_tmp = NULL;\n\tunsigned char *key_sha256 = NULL;\n\tchar *spki = NULL;\n\tint spki_len = 0;\n\tint is_secure = 0;\n\n\tif (server_info->ssl == NULL) {\n\t\treturn -1;\n\t}\n\n\tpthread_mutex_lock(&server_info->lock);\n\tcert = SSL_get_peer_certificate(server_info->ssl);\n\tif (cert == NULL) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\ttlog(TLOG_ERROR, \"get peer certificate failed.\");\n\t\treturn -1;\n\t}\n\n\tif (server_info->skip_check_cert == 0) {\n\t\tlong res = SSL_get_verify_result(server_info->ssl);\n\t\tif (res != X509_V_OK) {\n\t\t\tpthread_mutex_unlock(&server_info->lock);\n\t\t\ttlog(TLOG_WARN, \"peer server %s certificate verify failed, %s\", server_info->ip,\n\t\t\t\t X509_verify_cert_error_string(res));\n\t\t\tgoto errout;\n\t\t}\n\n\t\tis_secure = 1;\n\t}\n\tpthread_mutex_unlock(&server_info->lock);\n\n\tswitch (_dns_client_verify_SAN(server_info, cert)) {\n\tcase 0:\n\t\tbreak;\n\tcase -1:\n\t\t/* verify SAN failed. */\n\t\tgoto errout;\n\tcase -2:\n\t\tif (_dns_client_verify_common_name(server_info, cert) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\ttlog(TLOG_WARN, \"server %s SAN is invalid\", server_info->ip);\n\t\tgoto errout;\n\t}\n\n\tpubkey = X509_get_X509_PUBKEY(cert);\n\tif (pubkey == NULL) {\n\t\ttlog(TLOG_ERROR, \"get pub key failed.\");\n\t\tgoto errout;\n\t}\n\n\t/* get spki pin */\n\tkey_len = i2d_X509_PUBKEY(pubkey, NULL);\n\tif (key_len <= 0) {\n\t\ttlog(TLOG_ERROR, \"get x509 public key failed.\");\n\t\tgoto errout;\n\t}\n\n\tkey_data = OPENSSL_malloc(key_len);\n\tkey_data_tmp = key_data;\n\tif (key_data == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc memory failed.\");\n\t\tgoto errout;\n\t}\n\n\ti2d_X509_PUBKEY(pubkey, &key_data_tmp);\n\n\t/* Get the SHA256 value of SPKI */\n\tkey_sha256 = SSL_SHA256(key_data, key_len, NULL);\n\tif (key_sha256 == NULL) {\n\t\ttlog(TLOG_ERROR, \"get sha256 failed.\");\n\t\tgoto errout;\n\t}\n\n\tchar *ptr = cert_fingerprint;\n\tfor (i = 0; i < SHA256_DIGEST_LENGTH; i++) {\n\t\t*ptr = _dns_client_to_hex(key_sha256[i] >> 4 & 0xF);\n\t\tptr++;\n\t\t*ptr = _dns_client_to_hex(key_sha256[i] & 0xF);\n\t\tptr++;\n\t\t*ptr = ':';\n\t\tptr++;\n\t}\n\tptr--;\n\t*ptr = 0;\n\ttlog(TLOG_DEBUG, \"cert SPKI pin(%s): %s\", \"sha256\", cert_fingerprint);\n\n\tspki = _dns_client_server_get_spki(server_info, &spki_len);\n\tif (spki && spki_len > 0 && spki_len <= SHA256_DIGEST_LENGTH) {\n\t\t/* check SPKI */\n\t\tif (memcmp(spki, key_sha256, spki_len) != 0) {\n\t\t\ttlog(TLOG_INFO, \"server %s cert spki is invalid\", server_info->ip);\n\t\t\tgoto errout;\n\t\t} else {\n\t\t\ttlog(TLOG_DEBUG, \"server %s cert spki verify succeed\", server_info->ip);\n\t\t\tis_secure = 1;\n\t\t}\n\t}\n\n\tOPENSSL_free(key_data);\n\tX509_free(cert);\n\n\tif (is_secure) {\n\t\tserver_info->security_status = DNS_CLIENT_SERVER_SECURITY_SECURE;\n\t} else {\n\t\tserver_info->security_status = DNS_CLIENT_SERVER_SECURITY_INSECURE;\n\t}\n\n\treturn 0;\n\nerrout:\n\tif (key_data) {\n\t\tOPENSSL_free(key_data);\n\t}\n\n\tif (cert) {\n\t\tX509_free(cert);\n\t}\n\n\tserver_info->security_status = DNS_CLIENT_SERVER_SECURITY_VERIFY_FAILED;\n\n\treturn -1;\n}\n\nint _dns_client_process_tls(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now)\n{\n\tint ret = -1;\n\tstruct epoll_event fd_event;\n\tint ssl_ret = 0;\n\n\tif (unlikely(server_info->ssl == NULL)) {\n\t\ttlog(TLOG_ERROR, \"ssl is invalid, server %s\", server_info->ip);\n\t\tgoto errout;\n\t}\n\n\tif (server_info->status == DNS_SERVER_STATUS_CONNECTING) {\n\t\t/* do SSL handshake */\n\t\tret = _ssl_do_handshake(server_info);\n\t\tif (ret <= 0) {\n\t\t\tmemset(&fd_event, 0, sizeof(fd_event));\n\t\t\tssl_ret = _ssl_get_error(server_info, ret);\n\t\t\tif (_dns_client_ssl_poll_event(server_info, ssl_ret) == 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tif (ssl_ret != SSL_ERROR_SYSCALL) {\n\t\t\t\tunsigned long ssl_err = ERR_get_error();\n\t\t\t\tint ssl_reason = ERR_GET_REASON(ssl_err);\n\t\t\t\ttlog(TLOG_WARN, \"Handshake with %s failed, error no: %s(%d, %d, %d)\\n\", server_info->ip,\n\t\t\t\t\t ERR_reason_error_string(ssl_err), ret, ssl_ret, ssl_reason);\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (errno != ENETUNREACH) {\n\t\t\t\ttlog(TLOG_WARN, \"Handshake with %s failed, %s\", server_info->ip, strerror(errno));\n\t\t\t}\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"remote server %s:%d connected\\n\", server_info->ip, server_info->port);\n\t\t/* Was the stored session reused? */\n\t\tif (_ssl_session_reused(server_info)) {\n\t\t\ttlog(TLOG_DEBUG, \"reused session\");\n\t\t} else {\n\t\t\ttlog(TLOG_DEBUG, \"new session\");\n\t\t\tpthread_mutex_lock(&server_info->lock);\n\t\t\tif (server_info->ssl_session) {\n\t\t\t\t/* free session */\n\t\t\t\tSSL_SESSION_free(server_info->ssl_session);\n\t\t\t\tserver_info->ssl_session = NULL;\n\t\t\t}\n\n\t\t\tif (_dns_client_tls_verify(server_info) != 0) {\n\t\t\t\ttlog(TLOG_WARN, \"peer %s verify failed.\", server_info->ip);\n\t\t\t\tpthread_mutex_unlock(&server_info->lock);\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\t/* save ssl session for next request */\n\t\t\tserver_info->ssl_session = _ssl_get1_session(server_info);\n\t\t\tpthread_mutex_unlock(&server_info->lock);\n\t\t}\n\n\t\t/* Detect negotiated ALPN protocol */\n\t\tconst unsigned char *alpn_data = NULL;\n\t\tunsigned int alpn_len = 0;\n\t\tSSL_get0_alpn_selected(server_info->ssl, &alpn_data, &alpn_len);\n\t\tif (alpn_data && alpn_len > 0 && alpn_len < sizeof(server_info->alpn_selected)) {\n\t\t\tmemcpy(server_info->alpn_selected, alpn_data, alpn_len);\n\t\t\tserver_info->alpn_selected[alpn_len] = '\\0';\n\t\t\ttlog(TLOG_DEBUG, \"ALPN negotiated: %s\", server_info->alpn_selected);\n\t\t} else {\n\t\t\tsafe_strncpy(server_info->alpn_selected, \"http/1.1\", sizeof(server_info->alpn_selected));\n\t\t}\n\n\t\tserver_info->status = DNS_SERVER_STATUS_CONNECTED;\n\t\tmemset(&fd_event, 0, sizeof(fd_event));\n\t\tfd_event.events = EPOLLIN | EPOLLOUT;\n\t\tfd_event.data.ptr = server_info;\n\t\tif (server_info->fd > 0) {\n\t\t\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_MOD, server_info->fd, &fd_event) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t}\n\n\t\tevent->events = EPOLLOUT;\n\t}\n\n\tif (server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) {\n/* QUIC */\n#if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC)\n\t\treturn _dns_client_process_quic(server_info, event, now);\n#else\n\t\ttlog(TLOG_ERROR, \"quic/http3 is not supported.\");\n\t\tgoto errout;\n#endif\n\t}\n\n\t/* Check if HTTPS server negotiated HTTP/2 */\n\tif (server_info->type == DNS_SERVER_HTTPS && strncmp(server_info->alpn_selected, \"h2\", sizeof(\"h2\")) == 0) {\n\t\t/* HTTP/2 processing */\n\t\tif (_dns_client_process_http2(server_info, event, now) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\treturn 0;\n\t}\n\n\treturn _dns_client_process_tcp(server_info, event, now);\nerrout:\n\tpthread_mutex_lock(&server_info->lock);\n\tserver_info->recv_buff.len = 0;\n\tserver_info->send_buff.len = 0;\n\tpthread_mutex_unlock(&server_info->lock);\n\t_dns_client_close_socket(server_info);\n\n\treturn -1;\n}\n\nint _dns_client_send_tls(struct dns_server_info *server_info, void *packet, unsigned short len)\n{\n\tint send_len = 0;\n\tunsigned char inpacket_data[DNS_IN_PACKSIZE];\n\tunsigned char *inpacket = inpacket_data;\n\n\tif (len > sizeof(inpacket_data) - 2) {\n\t\ttlog(TLOG_ERROR, \"packet size is invalid.\");\n\t\treturn -1;\n\t}\n\n\t/* TCP query format\n\t * | len (short) | dns query data |\n\t */\n\t*((unsigned short *)(inpacket)) = htons(len);\n\tmemcpy(inpacket + 2, packet, len);\n\tlen += 2;\n\n\tif (server_info->status != DNS_SERVER_STATUS_CONNECTED) {\n\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket, len);\n\t}\n\n\tif (server_info->ssl == NULL) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tsend_len = _dns_client_socket_ssl_send(server_info, inpacket, len);\n\tif (send_len <= 0) {\n\t\tif (errno == EAGAIN || errno == EPIPE || server_info->ssl == NULL) {\n\t\t\t/* save data to buffer, and retry when EPOLLOUT is available */\n\t\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket, len);\n\t\t} else if (server_info->ssl && errno != ENOMEM) {\n\t\t\t_dns_client_shutdown_socket(server_info);\n\t\t}\n\t\treturn -1;\n\t} else if (send_len < len) {\n\t\t/* save remain data to buffer, and retry when EPOLLOUT is available */\n\t\treturn _dns_client_send_data_to_buffer(server_info, inpacket + send_len, len - send_len);\n\t}\n\n\treturn 0;\n}"
  },
  {
    "path": "src/dns_client/client_tls.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_TLS_H_\n#define _DNS_CLIENT_TLS_H_\n\n#include \"dns_client.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_socket_ssl_send(struct dns_server_info *server, const void *buf, int num);\n\nint _dns_client_socket_ssl_recv(struct dns_server_info *server, void *buf, int num);\n\nint _dns_client_socket_ssl_send_ext(struct dns_server_info *server, SSL *ssl, const void *buf, int num, uint64_t flags);\n\nint _dns_client_socket_ssl_recv_ext(struct dns_server_info *server, SSL *ssl, void *buf, int num);\n\nint _dns_client_create_socket_tls(struct dns_server_info *server_info, const char *hostname, const char *alpn);\n\nint _dns_client_ssl_poll_event(struct dns_server_info *server_info, int ssl_ret);\n\nint _dns_client_send_tls(struct dns_server_info *server_info, void *packet, unsigned short len);\n\nint _dns_client_process_tls(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now);\n\nSSL_CTX *_ssl_ctx_get(int is_quic);\n\nint _ssl_shutdown(struct dns_server_info *server);\n\nint _ssl_do_handevent(struct dns_server_info *server);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/client_udp.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/util.h\"\n\n#include \"client_socket.h\"\n#include \"client_udp.h\"\n#include \"server_info.h\"\n\n#include <net/if.h>\n#include <netinet/ip.h>\n#include <sys/epoll.h>\n#include <sys/ioctl.h>\n\nstatic int _dns_client_create_socket_udp_proxy(struct dns_server_info *server_info)\n{\n\tstruct proxy_conn *proxy = NULL;\n\tint fd = -1;\n\tstruct epoll_event event;\n\tint ret = -1;\n\n\tproxy = proxy_conn_new(server_info->proxy_name, server_info->ip, server_info->port, 1, 1);\n\tif (proxy == NULL) {\n\t\ttlog(TLOG_ERROR, \"create proxy failed, %s, proxy: %s\", server_info->ip, server_info->proxy_name);\n\t\tgoto errout;\n\t}\n\n\tfd = proxy_conn_get_fd(proxy);\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"get proxy fd failed, %s\", server_info->ip);\n\t\tgoto errout;\n\t}\n\n\tif (server_info->so_mark >= 0) {\n\t\tunsigned int so_mark = server_info->so_mark;\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) {\n\t\t\ttlog(TLOG_DEBUG, \"set socket mark failed, %s\", strerror(errno));\n\t\t}\n\t}\n\n\tif (server_info->flags.ifname[0] != '\\0') {\n\t\tstruct ifreq ifr;\n\t\tmemset(&ifr, 0, sizeof(struct ifreq));\n\t\tsafe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name));\n\t\tioctl(fd, SIOCGIFINDEX, &ifr);\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) {\n\t\t\ttlog(TLOG_ERROR, \"bind socket to device %s failed, %s\\n\", ifr.ifr_name, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tset_fd_nonblock(fd, 1);\n\tset_sock_keepalive(fd, 30, 3, 5);\n\tif (dns_conf.dns_socket_buff_size > 0) {\n\t\tsetsockopt(fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size));\n\t\tsetsockopt(fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size));\n\t}\n\n\tret = proxy_conn_connect(proxy);\n\tif (ret != 0) {\n\t\tif (errno != EINPROGRESS) {\n\t\t\ttlog(TLOG_DEBUG, \"connect %s failed, %s\", server_info->ip, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tserver_info->fd = fd;\n\tserver_info->status = DNS_SERVER_STATUS_CONNECTING;\n\tserver_info->proxy = proxy;\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLOUT;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\treturn 0;\nerrout:\n\tif (proxy) {\n\t\tproxy_conn_free(proxy);\n\t}\n\n\treturn -1;\n}\n\nint _dns_client_create_socket_udp(struct dns_server_info *server_info)\n{\n\tint fd = -1;\n\tstruct epoll_event event;\n\tconst int on = 1;\n\tconst int val = 255;\n\tconst int priority = SOCKET_PRIORITY;\n\tconst int ip_tos = SOCKET_IP_TOS;\n\n\tif (server_info->proxy_name[0] != '\\0') {\n\t\treturn _dns_client_create_socket_udp_proxy(server_info);\n\t}\n\n\tfd = socket(server_info->ai_family, SOCK_DGRAM, 0);\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"create socket failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (set_fd_nonblock(fd, 1) != 0) {\n\t\ttlog(TLOG_ERROR, \"set socket non block failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (server_info->flags.ifname[0] != '\\0') {\n\t\tstruct ifreq ifr;\n\t\tmemset(&ifr, 0, sizeof(struct ifreq));\n\t\tsafe_strncpy(ifr.ifr_name, server_info->flags.ifname, sizeof(ifr.ifr_name));\n\t\tioctl(fd, SIOCGIFINDEX, &ifr);\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) {\n\t\t\ttlog(TLOG_ERROR, \"bind socket to device %s failed, %s\\n\", ifr.ifr_name, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tserver_info->fd = fd;\n\tserver_info->status = DNS_SERVER_STATUS_CONNECTING;\n\tserver_info->security_status = DNS_CLIENT_SERVER_SECURITY_NOT_APPLICABLE;\n\n\tif (connect(fd, &server_info->addr, server_info->ai_addrlen) != 0) {\n\t\tif (errno != EINPROGRESS) {\n\t\t\ttlog(TLOG_DEBUG, \"connect %s failed, %s\", server_info->ip, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN;\n\tevent.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed.\");\n\t\treturn -1;\n\t}\n\n\tif (server_info->so_mark >= 0) {\n\t\tunsigned int so_mark = server_info->so_mark;\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) {\n\t\t\ttlog(TLOG_DEBUG, \"set socket mark failed, %s\", strerror(errno));\n\t\t}\n\t}\n\n\tsetsockopt(server_info->fd, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on));\n\tsetsockopt(server_info->fd, SOL_IP, IP_TTL, &val, sizeof(val));\n\tsetsockopt(server_info->fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority));\n\tsetsockopt(server_info->fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));\n\tif (server_info->ai_family == AF_INET6) {\n\t\t/* for receiving ip ttl value */\n\t\tsetsockopt(server_info->fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(server_info->fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(server_info->fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on));\n\t}\n\n\tif (dns_conf.dns_socket_buff_size > 0) {\n\t\tsetsockopt(server_info->fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size,\n\t\t\t\t   sizeof(dns_conf.dns_socket_buff_size));\n\t\tsetsockopt(server_info->fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size,\n\t\t\t\t   sizeof(dns_conf.dns_socket_buff_size));\n\t}\n\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\tserver_info->fd = -1;\n\tserver_info->status = DNS_SERVER_STATUS_DISCONNECTED;\n\n\treturn -1;\n}\n\nstatic int _dns_client_process_send_udp_buffer(struct dns_server_info *server_info, struct epoll_event *event,\n\t\t\t\t\t\t\t\t\t\t\t   unsigned long now)\n{\n\tint send_len = 0;\n\tif (server_info->send_buff.len <= 0 || server_info->status != DNS_SERVER_STATUS_CONNECTED) {\n\t\treturn 0;\n\t}\n\n\twhile (server_info->send_buff.len - send_len > 0) {\n\t\tint ret = 0;\n\t\tint packet_len = 0;\n\t\tpacket_len = *(int *)(server_info->send_buff.data + send_len);\n\t\tsend_len += sizeof(packet_len);\n\t\tif (packet_len > server_info->send_buff.len - 1) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tret = _dns_client_send_udp(server_info, server_info->send_buff.data + send_len, packet_len);\n\t\tif (ret < 0) {\n\t\t\ttlog(TLOG_ERROR, \"sendto failed, %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t\tsend_len += packet_len;\n\t}\n\n\tserver_info->send_buff.len -= send_len;\n\tif (server_info->send_buff.len < 0) {\n\t\tserver_info->send_buff.len = 0;\n\t}\n\n\treturn 0;\n\nerrout:\n\tpthread_mutex_lock(&client.server_list_lock);\n\tserver_info->recv_buff.len = 0;\n\tserver_info->send_buff.len = 0;\n\t_dns_client_close_socket(server_info);\n\tpthread_mutex_unlock(&client.server_list_lock);\n\treturn -1;\n}\n\nstatic int _dns_client_process_udp_proxy(struct dns_server_info *server_info, struct epoll_event *event,\n\t\t\t\t\t\t\t\t\t\t unsigned long now)\n{\n\tstruct sockaddr_storage from;\n\tsocklen_t from_len = sizeof(from);\n\tchar from_host[DNS_MAX_CNAME_LEN];\n\tunsigned char inpacket[DNS_IN_PACKSIZE];\n\tint len = 0;\n\tint ret = 0;\n\n\t_dns_client_process_send_udp_buffer(server_info, event, now);\n\n\tif (!(event->events & EPOLLIN)) {\n\t\treturn 0;\n\t}\n\n\tlen = proxy_conn_recvfrom(server_info->proxy, inpacket, sizeof(inpacket), 0, (struct sockaddr *)&from, &from_len);\n\tif (len < 0) {\n\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EHOSTUNREACH) {\n\t\t\ttlog(TLOG_DEBUG, \"recvfrom %s failed, %s\\n\", server_info->ip, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttlog(TLOG_ERROR, \"recvfrom %s failed, %s\\n\", server_info->ip, strerror(errno));\n\t\tgoto errout;\n\t} else if (len == 0) {\n\t\tpthread_mutex_lock(&server_info->lock);\n\t\t_dns_client_close_socket(server_info);\n\t\tserver_info->recv_buff.len = 0;\n\t\tif (server_info->send_buff.len > 0) {\n\t\t\t/* still remain request data, reconnect and send*/\n\t\t\tret = _dns_client_create_socket(server_info);\n\t\t} else {\n\t\t\tret = 0;\n\t\t}\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\ttlog(TLOG_DEBUG, \"peer close, %s:%d\", server_info->ip, server_info->port);\n\t\treturn ret;\n\t}\n\n\tint latency = get_tick_count() - server_info->send_tick;\n\ttlog(TLOG_DEBUG, \"recv udp packet from %s, len: %d, latency: %d\",\n\t\t get_host_by_addr(from_host, sizeof(from_host), (struct sockaddr *)&from), len, latency);\n\n\tif (latency < server_info->drop_packet_latency_ms) {\n\t\ttlog(TLOG_DEBUG, \"drop packet from %s, latency: %d\", from_host, latency);\n\t\treturn 0;\n\t}\n\n\tif (server_info->status == DNS_SERVER_STATUS_CONNECTING) {\n\t\tserver_info->status = DNS_SERVER_STATUS_CONNECTED;\n\t}\n\n\t/* update recv time */\n\ttime(&server_info->last_recv);\n\n\t/* processing dns packet */\n\tif (_dns_client_recv(server_info, inpacket, len, (struct sockaddr *)&from, from_len) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\nerrout:\n\tpthread_mutex_lock(&server_info->lock);\n\tserver_info->recv_buff.len = 0;\n\tserver_info->send_buff.len = 0;\n\t_dns_client_close_socket(server_info);\n\tpthread_mutex_unlock(&server_info->lock);\n\treturn -1;\n}\n\nint _dns_client_process_udp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now)\n{\n\tint len = 0;\n\tunsigned char inpacket[DNS_IN_PACKSIZE];\n\tstruct sockaddr_storage from;\n\tsocklen_t from_len = sizeof(from);\n\tchar from_host[DNS_MAX_CNAME_LEN];\n\tstruct msghdr msg;\n\tstruct iovec iov;\n\tchar ans_data[4096];\n\tint ttl = 0;\n\tstruct cmsghdr *cmsg = NULL;\n\n\tif (server_info->proxy) {\n\t\treturn _dns_client_process_udp_proxy(server_info, event, now);\n\t}\n\n\tmemset(&msg, 0, sizeof(msg));\n\tiov.iov_base = (char *)inpacket;\n\tiov.iov_len = sizeof(inpacket);\n\tmsg.msg_name = &from;\n\tmsg.msg_namelen = sizeof(from);\n\tmsg.msg_iov = &iov;\n\tmsg.msg_iovlen = 1;\n\tmsg.msg_control = ans_data;\n\tmsg.msg_controllen = sizeof(ans_data);\n\n\tlen = recvmsg(server_info->fd, &msg, MSG_DONTWAIT);\n\tif (len < 0) {\n\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tserver_info->prohibit = 1;\n\t\tif (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EHOSTUNREACH) {\n\t\t\ttlog(TLOG_DEBUG, \"recvfrom %s failed, %s\\n\", server_info->ip, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttlog(TLOG_ERROR, \"recvfrom %s failed, %s\\n\", server_info->ip, strerror(errno));\n\t\tgoto errout;\n\t}\n\tfrom_len = msg.msg_namelen;\n\n\t/* Get the TTL of the IP header */\n\tfor (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {\n\t\tif (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TTL) {\n\t\t\tif (cmsg->cmsg_len >= sizeof(int)) {\n\t\t\t\tint *ttlPtr = (int *)CMSG_DATA(cmsg);\n\t\t\t\tttl = *ttlPtr;\n\t\t\t}\n\t\t} else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) {\n\t\t\tif (cmsg->cmsg_len >= sizeof(int)) {\n\t\t\t\tint *ttlPtr = (int *)CMSG_DATA(cmsg);\n\t\t\t\tttl = *ttlPtr;\n\t\t\t}\n\t\t}\n\t}\n\n\tint from_port = from.ss_family == AF_INET ? ntohs(((struct sockaddr_in *)&from)->sin_port)\n\t\t\t\t\t\t\t\t\t\t\t  : ntohs(((struct sockaddr_in6 *)&from)->sin6_port);\n\tint latency = get_tick_count() - server_info->send_tick;\n\ttlog(TLOG_DEBUG, \"recv udp packet from %s:%d, len: %d, ttl: %d, latency: %d\",\n\t\t get_host_by_addr(from_host, sizeof(from_host), (struct sockaddr *)&from), from_port, len, ttl, latency);\n\n\t/* update recv time */\n\ttime(&server_info->last_recv);\n\n\tif (latency > 0 && latency < server_info->drop_packet_latency_ms) {\n\t\ttlog(TLOG_DEBUG, \"drop packet from %s, latency: %d\", from_host, latency);\n\t\treturn 0;\n\t}\n\n\tif (server_info->status == DNS_SERVER_STATUS_CONNECTING) {\n\t\tserver_info->status = DNS_SERVER_STATUS_CONNECTED;\n\t}\n\n\t/* processing dns packet */\n\tif (_dns_client_recv(server_info, inpacket, len, (struct sockaddr *)&from, from_len) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n\nerrout:\n\tpthread_mutex_lock(&client.server_list_lock);\n\tserver_info->recv_buff.len = 0;\n\tserver_info->send_buff.len = 0;\n\t_dns_client_close_socket(server_info);\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\treturn -1;\n}\n\nint _dns_client_send_udp(struct dns_server_info *server_info, void *packet, int len)\n{\n\tint send_len = 0;\n\tconst struct sockaddr *addr = &server_info->addr;\n\tsocklen_t addrlen = server_info->ai_addrlen;\n\tint ret = 0;\n\n\tif (server_info->fd <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (server_info->proxy) {\n\t\tif (server_info->status != DNS_SERVER_STATUS_CONNECTED) {\n\t\t\t/*set packet len*/\n\t\t\t_dns_client_copy_data_to_buffer(server_info, &len, sizeof(len));\n\t\t\treturn _dns_client_copy_data_to_buffer(server_info, packet, len);\n\t\t}\n\n\t\tsend_len = proxy_conn_sendto(server_info->proxy, packet, len, 0, addr, addrlen);\n\t\tif (send_len != len) {\n\t\t\t_dns_client_close_socket(server_info);\n\t\t\tserver_info->recv_buff.len = 0;\n\t\t\tif (server_info->send_buff.len > 0) {\n\t\t\t\t/* still remain request data, reconnect and send*/\n\t\t\t\tret = _dns_client_create_socket(server_info);\n\t\t\t} else {\n\t\t\t\tret = 0;\n\t\t\t}\n\n\t\t\tif (ret != 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t_dns_client_copy_data_to_buffer(server_info, &len, sizeof(len));\n\t\t\treturn _dns_client_copy_data_to_buffer(server_info, packet, len);\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tsend_len = sendto(server_info->fd, packet, len, 0, NULL, 0);\n\tif (send_len != len) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nvoid _dns_client_check_udp_nat(struct dns_query_struct *query)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct dns_server_group_member *group_member = NULL;\n\n\t/* For udp nat case.\n\t * when router reconnect to internet, udp port may always marked as UNREPLIED.\n\t * dns query will timeout, and cannot reconnect again,\n\t * create a new socket to communicate.\n\t */\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_for_each_entry(group_member, &query->server_group->head, list)\n\t{\n\t\tserver_info = group_member->server;\n\t\tif (server_info->type != DNS_SERVER_UDP) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (server_info->last_send - 5 > server_info->last_recv) {\n\t\t\tserver_info->recv_buff.len = 0;\n\t\t\tserver_info->send_buff.len = 0;\n\t\t\ttlog(TLOG_DEBUG, \"query server %s timeout.\", server_info->ip);\n\t\t\t_dns_client_close_socket(server_info);\n\t\t}\n\t}\n\tpthread_mutex_unlock(&client.server_list_lock);\n}\n"
  },
  {
    "path": "src/dns_client/client_udp.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_UDP_H_\n#define _DNS_CLIENT_UDP_H_\n\n#include \"dns_client.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_send_udp(struct dns_server_info *server_info, void *packet, int len);\n\nint _dns_client_create_socket_udp(struct dns_server_info *server_info);\n\nvoid _dns_client_check_udp_nat(struct dns_query_struct *query);\n\nint _dns_client_process_udp(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/conn_stream.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"conn_stream.h\"\n\n#include \"smartdns/util.h\"\n\nstruct dns_conn_stream *_dns_client_conn_stream_new(void)\n{\n\tstruct dns_conn_stream *stream = NULL;\n\n\tstream = zalloc(1, sizeof(*stream));\n\tif (stream == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc conn stream failed\");\n\t\treturn NULL;\n\t}\n\tINIT_LIST_HEAD(&stream->server_list);\n\tINIT_LIST_HEAD(&stream->query_list);\n\tstream->quic_stream = NULL;\n\tstream->http2_stream = NULL;\n\tstream->server_info = NULL;\n\tstream->query = NULL;\n\tatomic_set(&stream->refcnt, 1);\n\n\treturn stream;\n}\n\nvoid _dns_client_conn_stream_get(struct dns_conn_stream *stream)\n{\n\tif (atomic_inc_return(&stream->refcnt) <= 1) {\n\t\tBUG(\"stream ref is invalid\");\n\t}\n}\n\nvoid _dns_client_conn_stream_put(struct dns_conn_stream *stream)\n{\n\tint refcnt = atomic_dec_return(&stream->refcnt);\n\tif (refcnt) {\n\t\tif (refcnt < 0) {\n\t\t\tBUG(\"BUG: stream  %p, refcnt is %d\", stream, refcnt);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (stream->quic_stream) {\n\t\tSSL_free(stream->quic_stream);\n\t\tstream->quic_stream = NULL;\n\t}\n\n\tif (stream->http2_stream) {\n\t\tstruct http2_stream *http2_stream = stream->http2_stream;\n\t\tstream->http2_stream = NULL;\n\t\thttp2_stream_close(http2_stream);\n\t\tstream->server_info = NULL;\n\t}\n\n\tif (stream->query) {\n\t\tpthread_mutex_lock(&stream->query->lock);\n\t\tlist_del_init(&stream->query_list);\n\t\tpthread_mutex_unlock(&stream->query->lock);\n\t\tstream->query = NULL;\n\t}\n\n\tif (stream->server_info) {\n\t\tpthread_mutex_lock(&stream->server_info->lock);\n\t\tlist_del_init(&stream->server_list);\n\t\tpthread_mutex_unlock(&stream->server_info->lock);\n\t}\n\n\tfree(stream);\n}\n\nvoid _dns_client_conn_server_streams_free(struct dns_server_info *server_info, struct dns_query_struct *query)\n{\n\tstruct dns_conn_stream *stream = NULL;\n\tstruct dns_conn_stream *tmp = NULL;\n\n\tpthread_mutex_lock(&server_info->lock);\n\tlist_for_each_entry_safe(stream, tmp, &server_info->conn_stream_list, server_list)\n\t{\n\n\t\tif (stream->query != query) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlist_del_init(&stream->server_list);\n\t\tstream->server_info = NULL;\n\t\tif (stream->quic_stream) {\n#if defined(OSSL_QUIC1_VERSION) && !defined(OPENSSL_NO_QUIC)\n\t\t\tSSL_stream_reset(stream->quic_stream, NULL, 0);\n#endif\n\t\t\tSSL_free(stream->quic_stream);\n\t\t\tstream->quic_stream = NULL;\n\t\t}\n\n\t\tif (stream->http2_stream) {\n\t\t\thttp2_stream_close(stream->http2_stream);\n\t\t\tstream->http2_stream = NULL;\n\t\t}\n\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\tpthread_mutex_unlock(&server_info->lock);\n}"
  },
  {
    "path": "src/dns_client/conn_stream.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_CONN_STREAM_\n#define _DNS_CLIENT_CONN_STREAM_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_client_conn_stream_put(struct dns_conn_stream *stream);\n\nvoid _dns_client_conn_stream_get(struct dns_conn_stream *stream);\n\nstruct dns_conn_stream *_dns_client_conn_stream_new(void);\n\nvoid _dns_client_conn_server_streams_free(struct dns_server_info *server_info, struct dns_query_struct *query);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/dns_client.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/util.h\"\n\n#include \"client_http2.h\"\n#include \"client_http3.h\"\n#include \"client_https.h\"\n#include \"client_mdns.h\"\n#include \"client_quic.h\"\n#include \"client_socket.h\"\n#include \"client_tcp.h\"\n#include \"client_tls.h\"\n#include \"client_udp.h\"\n#include \"conn_stream.h\"\n#include \"dns_client.h\"\n#include \"ecs.h\"\n#include \"group.h\"\n#include \"packet.h\"\n#include \"pending_server.h\"\n#include \"proxy.h\"\n#include \"query.h\"\n#include \"server_info.h\"\n#include \"wake_event.h\"\n\nstatic int is_client_init;\nstruct dns_client client;\n\nvoid dns_client_flags_init(struct client_dns_server_flags *flags)\n{\n\tmemset(flags, 0, sizeof(*flags));\n}\n\nstatic int _dns_client_server_package_address_match(struct dns_server_info *server_info, struct sockaddr *addr,\n\t\t\t\t\t\t\t\t\t\t\t\t\tsocklen_t addr_len)\n{\n\tif (server_info->type == DNS_SERVER_MDNS) {\n\t\treturn 0;\n\t}\n\n\tif (addr_len != server_info->ai_addrlen) {\n\t\treturn -1;\n\t}\n\n\tif (memcmp(addr, &server_info->addr, addr_len) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _dns_client_recv(struct dns_server_info *server_info, unsigned char *inpacket, int inpacket_len,\n\t\t\t\t\t struct sockaddr *from, socklen_t from_len)\n{\n\tint len = 0;\n\tint i = 0;\n\tint j = 0;\n\tint qtype = 0;\n\tint qclass = 0;\n\tchar domain[DNS_MAX_CNAME_LEN] = {0};\n\tint rr_count = 0;\n\tstruct dns_rrs *rrs = NULL;\n\tunsigned char packet_buff[DNS_PACKSIZE];\n\tstruct dns_packet *packet = (struct dns_packet *)packet_buff;\n\tint ret = 0;\n\tstruct dns_query_struct *query = NULL;\n\tint request_num = 0;\n\tint has_opt = 0;\n\n\tpacket->head.tc = 0;\n\n\tif (_dns_client_server_package_address_match(server_info, from, from_len) != 0) {\n\t\ttlog(TLOG_DEBUG, \"packet from invalid server.\");\n\t\treturn -1;\n\t}\n\tstats_inc(&server_info->stats.recv_count);\n\n\t/* decode domain from udp packet */\n\tlen = dns_decode(packet, DNS_PACKSIZE, inpacket, inpacket_len);\n\tif (len != 0) {\n\t\tchar host_name[DNS_MAX_CNAME_LEN];\n\t\ttlog(TLOG_INFO, \"decode failed, packet len = %d, tc = %d, id = %d, from = %s\\n\", inpacket_len, packet->head.tc,\n\t\t\t packet->head.id, get_host_by_addr(host_name, sizeof(host_name), from));\n\t\tif (dns_conf.dns_save_fail_packet) {\n\t\t\tdns_packet_save(dns_conf.dns_save_fail_packet_dir, \"client\", host_name, inpacket, inpacket_len);\n\t\t}\n\t\treturn -1;\n\t}\n\n\t/* not answer, return error */\n\tif (packet->head.qr != DNS_OP_IQUERY) {\n\t\ttlog(TLOG_DEBUG, \"message type error.\\n\");\n\t\treturn -1;\n\t}\n\n\ttlog(TLOG_DEBUG,\n\t\t \"qdcount = %d, ancount = %d, nscount = %d, nrcount = %d, len = %d, id = %d, tc = %d, rd = %d, ra = %d, rcode \"\n\t\t \"= %d, payloadsize = %d\\n\",\n\t\t packet->head.qdcount, packet->head.ancount, packet->head.nscount, packet->head.nrcount, inpacket_len,\n\t\t packet->head.id, packet->head.tc, packet->head.rd, packet->head.ra, packet->head.rcode,\n\t\t dns_get_OPT_payload_size(packet));\n\n\t/* get question */\n\tfor (j = 0; j < DNS_RRS_END && domain[0] == '\\0'; j++) {\n\t\trrs = dns_get_rrs_start(packet, (dns_rr_type)j, &rr_count);\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\t\tdns_get_domain(rrs, domain, DNS_MAX_CNAME_LEN, &qtype, &qclass);\n\t\t\ttlog(TLOG_DEBUG, \"domain: %s qtype: %d  qclass: %d\\n\", domain, qtype, qclass);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (dns_get_OPT_payload_size(packet) > 0) {\n\t\thas_opt = 1;\n\t}\n\n\tatomic_set(&server_info->is_alive, 1);\n\tint latency = get_tick_count() - server_info->send_tick;\n\tdns_stats_server_stats_avg_time_add(&server_info->stats, latency);\n\n\t/* get query reference */\n\tquery = _dns_client_get_request(domain, qtype, packet->head.id);\n\tif (query == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (has_opt == 0 && server_info->flags.result_flag & DNSSERVER_FLAG_CHECK_EDNS) {\n\t\t_dns_client_query_release(query);\n\t\treturn 0;\n\t}\n\n\t/* avoid multiple replies */\n\tif (_dns_replied_check_add(query, server_info) != 0) {\n\t\t_dns_client_query_release(query);\n\t\treturn 0;\n\t}\n\n\trequest_num = atomic_dec_return(&query->dns_request_sent);\n\tif (request_num < 0) {\n\t\t_dns_client_query_release(query);\n\t\ttlog(TLOG_ERROR, \"send count is invalid, %d\", request_num);\n\t\treturn -1;\n\t}\n\n\t/* notify caller dns query result */\n\tif (query->callback) {\n\t\tret = query->callback(query->domain, DNS_QUERY_RESULT, server_info, packet, inpacket, inpacket_len,\n\t\t\t\t\t\t\t  query->user_ptr);\n\n\t\tif (ret == DNS_CLIENT_ACTION_RETRY || ret == DNS_CLIENT_ACTION_DROP) {\n\t\t\t/* remove this result */\n\t\t\t_dns_replied_check_remove(query, server_info);\n\t\t\tatomic_inc(&query->dns_request_sent);\n\t\t\tif (ret == DNS_CLIENT_ACTION_RETRY) {\n\t\t\t\t/*\n\t\t\t\t * retry immdiately\n\t\t\t\t * The socket needs to be re-created to avoid being limited, such as 1.1.1.1\n\t\t\t\t */\n\t\t\t\tpthread_mutex_lock(&client.server_list_lock);\n\t\t\t\t_dns_client_close_socket(server_info);\n\t\t\t\tpthread_mutex_unlock(&client.server_list_lock);\n\t\t\t\t_dns_client_retry_dns_query(query);\n\t\t\t}\n\t\t} else {\n\t\t\tif (ret == DNS_CLIENT_ACTION_OK) {\n\t\t\t\tquery->has_result = 1;\n\t\t\t} else {\n\t\t\t\ttlog(TLOG_DEBUG, \"query %s result is invalid, %d\", query->domain, ret);\n\t\t\t}\n\n\t\t\tif (request_num == 0) {\n\t\t\t\t/* if all server replied, or done, stop query, release resource */\n\t\t\t\t_dns_client_query_remove(query);\n\t\t\t}\n\t\t}\n\t}\n\n\tstats_inc(&server_info->stats.success_count);\n\t_dns_client_query_release(query);\n\treturn 0;\n}\n\nstatic int _dns_client_process(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now)\n{\n\tif (server_info->proxy) {\n\t\tint ret = _dns_proxy_handshake(server_info, event, now);\n\t\tif (ret != 0) {\n\t\t\treturn ret;\n\t\t}\n\t}\n\n\tif (server_info->type == DNS_SERVER_UDP || server_info->type == DNS_SERVER_MDNS) {\n\t\t/* receive from udp */\n\t\treturn _dns_client_process_udp(server_info, event, now);\n\t} else if (server_info->type == DNS_SERVER_TCP) {\n\t\t/* receive from tcp */\n\t\treturn _dns_client_process_tcp(server_info, event, now);\n\t} else if (server_info->type == DNS_SERVER_TLS || server_info->type == DNS_SERVER_HTTPS ||\n\t\t\t   server_info->type == DNS_SERVER_QUIC || server_info->type == DNS_SERVER_HTTP3) {\n\t\t/* receive from tls */\n\t\treturn _dns_client_process_tls(server_info, event, now);\n\t} else {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_client_send_http(struct dns_server_info *server_info, struct dns_query_struct *query, void *packet_data,\n\t\t\t\t\t\t\t\t int packet_data_len)\n{\n\t/* If ALPN is negotiated and is NOT h2, use HTTP/1.1 */\n\tif (server_info->alpn_selected[0] != '\\0' && strncmp(server_info->alpn_selected, \"h2\", 2) != 0) {\n\t\treturn _dns_client_send_http1(server_info, packet_data, packet_data_len);\n\t}\n\n\t/* Default to HTTP/2 buffering (stream-based).\n\t   If ALPN later turns out to be H1, _dns_client_process_https_streams will handle it. */\n\treturn _dns_client_send_http2(server_info, query, packet_data, packet_data_len);\n}\n\nstatic int _dns_client_check_server_prohibit(struct dns_server_info *server_info, int prohibit_time)\n{\n\tif (server_info->prohibit) {\n\t\tif (server_info->is_already_prohibit == 0) {\n\t\t\tserver_info->is_already_prohibit = 1;\n\t\t\t_dns_server_inc_prohibit_server_num(server_info);\n\t\t\ttime(&server_info->last_send);\n\t\t\ttime(&server_info->last_recv);\n\t\t\tif (server_info->type != DNS_SERVER_MDNS) {\n\t\t\t\ttlog(TLOG_INFO, \"server %s not alive, prohibit\", server_info->ip);\n\t\t\t}\n\t\t\t_dns_client_shutdown_socket(server_info);\n\t\t}\n\n\t\ttime_t now = 0;\n\t\ttime(&now);\n\t\tif ((now - prohibit_time < server_info->last_send)) {\n\t\t\treturn 1;\n\t\t}\n\t\tserver_info->prohibit = 0;\n\t\tserver_info->is_already_prohibit = 0;\n\t\t_dns_server_dec_prohibit_server_num(server_info);\n\t\tif (now - prohibit_time >= server_info->last_send) {\n\t\t\t_dns_client_close_socket(server_info);\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int _dns_client_send_one_packet(struct dns_server_info *server_info, struct dns_query_struct *query,\n\t\t\t\t\t\t\t\t\t   void *packet_data, int packet_data_len)\n{\n\tint ret = 0;\n\tint send_err = 0;\n\tint retry = 0;\n\n\tatomic_inc(&query->dns_request_sent);\n\tstats_inc(&server_info->stats.total);\n\terrno = 0;\n\tserver_info->send_tick = get_tick_count();\n\n\twhile (1) {\n\t\tswitch (server_info->type) {\n\t\tcase DNS_SERVER_UDP:\n\t\t\t/* udp query */\n\t\t\tret = _dns_client_send_udp(server_info, packet_data, packet_data_len);\n\t\t\tsend_err = errno;\n\t\t\tbreak;\n\t\tcase DNS_SERVER_TCP:\n\t\t\t/* tcp query */\n\t\t\tret = _dns_client_send_tcp(server_info, packet_data, packet_data_len);\n\t\t\tsend_err = errno;\n\t\t\tbreak;\n\t\tcase DNS_SERVER_TLS:\n\t\t\t/* tls query */\n\t\t\tret = _dns_client_send_tls(server_info, packet_data, packet_data_len);\n\t\t\tsend_err = errno;\n\t\t\tbreak;\n\t\tcase DNS_SERVER_HTTPS:\n\t\t\t/* https query - buffer raw data in stream, protocol determined later */\n\t\t\tret = _dns_client_send_http(server_info, query, packet_data, packet_data_len);\n\t\t\tsend_err = errno;\n\t\t\tbreak;\n\t\tcase DNS_SERVER_MDNS:\n\t\t\t/* mdns query */\n\t\t\tret = _dns_client_send_udp_mdns(server_info, packet_data, packet_data_len);\n\t\t\tsend_err = errno;\n\t\t\tbreak;\n\t\tcase DNS_SERVER_QUIC:\n\t\t\t/* quic query */\n\t\t\tret = _dns_client_send_quic(query, server_info, packet_data, packet_data_len);\n\t\t\tsend_err = errno;\n\t\t\tbreak;\n\t\tcase DNS_SERVER_HTTP3:\n\t\t\t/* http3 query */\n\t\t\tret = _dns_client_send_http3(query, server_info, packet_data, packet_data_len);\n\t\t\tsend_err = errno;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t/* unsupported query type */\n\t\t\tret = -1;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (ret != 0) {\n\t\t\tswitch (send_err) {\n\t\t\tcase EBADF:\n\t\t\tcase ECONNRESET:\n\t\t\tcase EPIPE:\n\t\t\tcase EDESTADDRREQ:\n\t\t\tcase EINVAL:\n\t\t\tcase EISCONN:\n\t\t\tcase ENOTCONN:\n\t\t\tcase ENOTSOCK:\n\t\t\tcase EOPNOTSUPP: {\n\t\t\t\ttlog(TLOG_DEBUG, \"send query to %s failed, %s, type: %d\", server_info->ip, strerror(send_err),\n\t\t\t\t\t server_info->type);\n\t\t\t\t_dns_client_close_socket(server_info);\n\t\t\t\tif (retry == 0) {\n\t\t\t\t\tretry = 1;\n\t\t\t\t\tif (_dns_client_create_socket(server_info) == 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tatomic_dec(&query->dns_request_sent);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\ttlog(TLOG_DEBUG, \"send query to %s failed, %s, type: %d\", server_info->ip, strerror(send_err),\n\t\t\t\t server_info->type);\n\t\t\ttime_t now = 0;\n\t\t\ttime(&now);\n\t\t\tif (now - 10 > server_info->last_recv || send_err != ENOMEM) {\n\t\t\t\tserver_info->prohibit = 1;\n\t\t\t}\n\n\t\t\tatomic_dec(&query->dns_request_sent);\n\t\t\treturn -1;\n\t\t}\n\t\tbreak;\n\t}\n\ttime(&server_info->last_send);\n\n\treturn 0;\n}\n\nint _dns_client_send_packet(struct dns_query_struct *query, void *packet, int len)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct dns_server_group_member *group_member = NULL;\n\tstruct dns_server_group_member *tmp = NULL;\n\tint ret = 0;\n\tint i = 0;\n\tint total_server = 0;\n\tint send_count = 0;\n\tvoid *packet_data = NULL;\n\tint packet_data_len = 0;\n\tunsigned char packet_data_buffer[DNS_IN_PACKSIZE];\n\tint prohibit_time = 60;\n\n\tquery->send_tick = get_tick_count();\n\n\t/* send query to all dns servers */\n\tatomic_inc(&query->dns_request_sent);\n\tfor (i = 0; i < 2; i++) {\n\t\ttotal_server = 0;\n\t\tif (i == 1) {\n\t\t\tprohibit_time = 5;\n\t\t}\n\n\t\t/* fallback group exists, use fallback group */\n\t\tif (atomic_read(&query->retry_count) == 1) {\n\t\t\tstruct dns_server_group *fallback_server_group = _dns_client_get_group(\"fallback\");\n\t\t\tif (fallback_server_group != NULL) {\n\t\t\t\tquery->server_group = fallback_server_group;\n\t\t\t}\n\t\t}\n\n\t\tpthread_mutex_lock(&client.server_list_lock);\n\t\tlist_for_each_entry_safe(group_member, tmp, &query->server_group->head, list)\n\t\t{\n\t\t\tserver_info = group_member->server;\n\n\t\t\t/* skip fallback server for first query */\n\t\t\tif (server_info->flags.fallback && atomic_read(&query->retry_count) == DNS_QUERY_RETRY && i == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (_dns_client_check_server_prohibit(server_info, prohibit_time)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttotal_server++;\n\t\t\ttlog(TLOG_DEBUG, \"send query to server %s:%d, type:%d\", server_info->ip, server_info->port,\n\t\t\t\t server_info->type);\n\t\t\tif (server_info->fd <= 0) {\n\t\t\t\tret = _dns_client_create_socket(server_info);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\tserver_info->prohibit = 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (_dns_client_setup_server_packet(server_info, query, packet, len, packet_data_buffer, &packet_data,\n\t\t\t\t\t\t\t\t\t\t\t\t&packet_data_len) != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (_dns_client_send_one_packet(server_info, query, packet_data, packet_data_len) == 0) {\n\t\t\t\tsend_count++;\n\t\t\t}\n\t\t}\n\t\tpthread_mutex_unlock(&client.server_list_lock);\n\n\t\tif (send_count > 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tint num = atomic_dec_return(&query->dns_request_sent);\n\tif (num == 0 && send_count > 0) {\n\t\t_dns_client_query_remove(query);\n\t}\n\n\tif (send_count <= 0) {\n\t\tstatic time_t lastlog = 0;\n\t\ttime_t now = 0;\n\t\ttime(&now);\n\t\tif (now - lastlog > 120) {\n\t\t\tlastlog = now;\n\t\t\ttlog(TLOG_WARN, \"send query %s to upstream server failed, total server number %d\", query->domain,\n\t\t\t\t total_server);\n\t\t}\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint dns_client_query(const char *domain, int qtype, dns_client_callback callback, void *user_ptr,\n\t\t\t\t\t const char *group_name, struct dns_query_options *options)\n{\n\tstruct dns_query_struct *query = NULL;\n\tint ret = 0;\n\tint unused __attribute__((unused));\n\n\tif (domain == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (atomic_read(&client.run) == 0) {\n\t\tgoto errout;\n\t}\n\n\tquery = zalloc(1, sizeof(*query));\n\tif (query == NULL) {\n\t\tgoto errout;\n\t}\n\n\tINIT_HLIST_NODE(&query->domain_node);\n\tINIT_LIST_HEAD(&query->dns_request_list);\n\tINIT_LIST_HEAD(&query->conn_stream_list);\n\tpthread_mutex_init(&query->lock, NULL);\n\tatomic_set(&query->refcnt, 0);\n\tatomic_set(&query->dns_request_sent, 0);\n\tatomic_set(&query->retry_count, DNS_QUERY_RETRY);\n\thash_init(query->replied_map);\n\tsafe_strncpy(query->domain, domain, DNS_MAX_CNAME_LEN);\n\tquery->user_ptr = user_ptr;\n\tquery->callback = callback;\n\tquery->qtype = qtype;\n\tquery->send_tick = 0;\n\tquery->has_result = 0;\n\tquery->server_group = _dns_client_get_dnsserver_group(group_name);\n\tif (query->server_group == NULL) {\n\t\ttlog(TLOG_ERROR, \"get dns server group %s failed.\", group_name);\n\t\tgoto errout;\n\t}\n\n\tquery->conf = dns_server_get_rule_group(options->conf_group_name);\n\tif (query->conf == NULL) {\n\t\ttlog(TLOG_ERROR, \"get dns config group %s failed.\", options->conf_group_name);\n\t\tgoto errout;\n\t}\n\n\tif (_dns_client_query_parser_options(query, options) != 0) {\n\t\ttlog(TLOG_ERROR, \"parser options for %s failed.\", domain);\n\t\tgoto errout;\n\t}\n\n\t_dns_client_query_get(query);\n\t/* add query to hashtable */\n\tif (_dns_client_add_hashmap(query) != 0) {\n\t\ttlog(TLOG_ERROR, \"add query to hash map failed.\");\n\t\tgoto errout;\n\t}\n\n\t/* send query */\n\t_dns_client_query_get(query);\n\tret = _dns_client_send_query(query);\n\tif (ret != 0) {\n\t\t_dns_client_query_release(query);\n\t\tgoto errout_del_list;\n\t}\n\n\tpthread_mutex_lock(&client.domain_map_lock);\n\tif (hash_hashed(&query->domain_node)) {\n\t\tif (list_empty(&client.dns_request_list)) {\n\t\t\t_dns_client_do_wakeup_event();\n\t\t}\n\n\t\tlist_add_tail(&query->dns_request_list, &client.dns_request_list);\n\t}\n\tpthread_mutex_unlock(&client.domain_map_lock);\n\n\ttlog(TLOG_INFO, \"request: %s, qtype: %d, id: %d, group: %s\", domain, qtype, query->sid,\n\t\t query->server_group->group_name);\n\t_dns_client_query_release(query);\n\n\treturn 0;\nerrout_del_list:\n\tquery->callback = NULL;\n\t_dns_client_query_remove(query);\n\tquery = NULL;\nerrout:\n\tif (query) {\n\t\tfree(query);\n\t}\n\treturn -1;\n}\n\nstatic void _dns_client_period_run_second(void)\n{\n\t_dns_client_check_tcp();\n\t_dns_client_check_servers();\n\t_dns_client_add_pending_servers();\n}\n\nstatic void _dns_client_period_run(unsigned int msec)\n{\n\tstruct dns_query_struct *query = NULL;\n\tstruct dns_query_struct *tmp = NULL;\n\n\tLIST_HEAD(check_list);\n\tunsigned long now = get_tick_count();\n\n\t/* get query which timed out to check list */\n\tpthread_mutex_lock(&client.domain_map_lock);\n\tlist_for_each_entry_safe(query, tmp, &client.dns_request_list, dns_request_list)\n\t{\n\t\tif ((now - DNS_QUERY_TIMEOUT >= query->send_tick) && query->send_tick > 0) {\n\t\t\tlist_add(&query->period_list, &check_list);\n\t\t\t_dns_client_query_get(query);\n\t\t}\n\t}\n\tpthread_mutex_unlock(&client.domain_map_lock);\n\n\tlist_for_each_entry_safe(query, tmp, &check_list, period_list)\n\t{\n\t\t/* free timed out query, and notify caller */\n\t\tlist_del_init(&query->period_list);\n\n\t\t/* check udp nat after retrying. */\n\t\tif (atomic_read(&query->retry_count) == 1) {\n\t\t\t_dns_client_check_udp_nat(query);\n\t\t}\n\t\t_dns_client_retry_dns_query(query);\n\t\t_dns_client_query_release(query);\n\t}\n\n\tif (msec % 10 == 0) {\n\t\t_dns_client_period_run_second();\n\t}\n}\n\nstatic void *_dns_client_work(void *arg)\n{\n\tstruct epoll_event events[DNS_MAX_EVENTS + 1];\n\tint num = 0;\n\tint i = 0;\n\tunsigned long now = {0};\n\tunsigned int msec = 0;\n\tunsigned int sleep = 100;\n\tint sleep_time = 0;\n\tunsigned long expect_time = 0;\n\tunsigned long start_time = 0;\n\n\tnow = get_tick_count();\n\tstart_time = now;\n\texpect_time = now + sleep;\n\n\twhile (atomic_read(&client.run)) {\n\t\tnow = get_tick_count();\n\n\t\tif (now >= expect_time) {\n\t\t\tunsigned long elapsed_from_start = now - start_time;\n\t\t\tunsigned int current_period = (elapsed_from_start + sleep / 2) / sleep;\n\n\t\t\tif (current_period > msec) {\n\t\t\t\tmsec = current_period;\n\t\t\t}\n\n\t\t\texpect_time = start_time + (msec + 1) * sleep;\n\t\t\t_dns_client_period_run(msec);\n\t\t\tmsec++;\n\n\t\t\t/* When client is idle, the sleep time is 1000ms, to reduce CPU usage */\n\t\t\tpthread_mutex_lock(&client.domain_map_lock);\n\t\t\tif (list_empty(&client.dns_request_list)) {\n\t\t\t\tif (msec % 10 != 0) {\n\t\t\t\t\tmsec = ((msec / 10) + 1) * 10;\n\t\t\t\t\texpect_time = start_time + msec * sleep;\n\t\t\t\t}\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&client.domain_map_lock);\n\t\t}\n\n\t\tsleep_time = (int)(expect_time - now);\n\t\tif (sleep_time < 0) {\n\t\t\tsleep_time = 0;\n\t\t}\n\n\t\tnum = epoll_wait(client.epoll_fd, events, DNS_MAX_EVENTS, sleep_time);\n\t\tif (num < 0) {\n\t\t\tusleep(100000);\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (i = 0; i < num; i++) {\n\t\t\tstruct epoll_event *event = &events[i];\n\t\t\tstruct dns_server_info *server_info = (struct dns_server_info *)event->data.ptr;\n\t\t\tif (event->data.fd == client.fd_wakeup) {\n\t\t\t\t_dns_client_clear_wakeup_event();\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (server_info == NULL) {\n\t\t\t\ttlog(TLOG_WARN, \"server info is invalid.\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t_dns_client_process(server_info, event, now);\n\t\t}\n\t}\n\n\tclose(client.epoll_fd);\n\tclient.epoll_fd = -1;\n\n\treturn NULL;\n}\n\nint dns_client_init(void)\n{\n\tpthread_attr_t attr;\n\tint epollfd = -1;\n\tint fd_wakeup = -1;\n\tint ret = 0;\n\n\tif (is_client_init == 1) {\n\t\treturn -1;\n\t}\n\n\tif (client.epoll_fd > 0) {\n\t\treturn -1;\n\t}\n\n\tmemset(&client, 0, sizeof(client));\n\tpthread_attr_init(&attr);\n\tatomic_set(&client.dns_server_num, 0);\n\tatomic_set(&client.dns_server_prohibit_num, 0);\n\tatomic_set(&client.run_period, 0);\n\n\tepollfd = epoll_create1(EPOLL_CLOEXEC);\n\tif (epollfd < 0) {\n\t\ttlog(TLOG_ERROR, \"create epoll failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tpthread_mutex_init(&client.server_list_lock, NULL);\n\tINIT_LIST_HEAD(&client.dns_server_list);\n\n\tpthread_mutex_init(&client.domain_map_lock, NULL);\n\thash_init(client.domain_map);\n\thash_init(client.group);\n\tINIT_LIST_HEAD(&client.dns_request_list);\n\n\tif (dns_client_add_group(DNS_SERVER_GROUP_DEFAULT) != 0) {\n\t\ttlog(TLOG_ERROR, \"add default server group failed.\");\n\t\tgoto errout;\n\t}\n\n\tif (_dns_client_add_mdns_server() != 0) {\n\t\ttlog(TLOG_ERROR, \"add mdns server failed.\");\n\t\tgoto errout;\n\t}\n\n\tclient.default_group = _dns_client_get_group(DNS_SERVER_GROUP_DEFAULT);\n\tclient.epoll_fd = epollfd;\n\tatomic_set(&client.run, 1);\n\n\t/* start work task */\n\tret = pthread_create(&client.tid, &attr, _dns_client_work, NULL);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"create client work thread failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tfd_wakeup = _dns_client_create_wakeup_event();\n\tif (fd_wakeup < 0) {\n\t\ttlog(TLOG_ERROR, \"create wakeup event failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tclient.fd_wakeup = fd_wakeup;\n\tis_client_init = 1;\n\n\treturn 0;\nerrout:\n\tif (client.tid) {\n\t\tvoid *retval = NULL;\n\t\tatomic_set(&client.run, 0);\n\t\tpthread_join(client.tid, &retval);\n\t\tclient.tid = 0;\n\t}\n\n\tif (epollfd > 0) {\n\t\tclose(epollfd);\n\t}\n\n\tif (fd_wakeup > 0) {\n\t\tclose(fd_wakeup);\n\t}\n\n\tpthread_mutex_destroy(&client.server_list_lock);\n\tpthread_mutex_destroy(&client.domain_map_lock);\n\n\treturn -1;\n}\n\nvoid dns_client_exit(void)\n{\n\tif (is_client_init == 0) {\n\t\treturn;\n\t}\n\n\tif (client.tid) {\n\t\tvoid *ret = NULL;\n\t\tatomic_set(&client.run, 0);\n\t\t_dns_client_do_wakeup_event();\n\t\tpthread_join(client.tid, &ret);\n\t\tclient.tid = 0;\n\t}\n\n\t/* free all resources */\n\t_dns_client_close_wakeup_event();\n\t_dns_client_remove_all_pending_servers();\n\t_dns_client_server_remove_all();\n\t_dns_client_query_remove_all();\n\t_dns_client_group_remove_all();\n\n\tpthread_mutex_destroy(&client.server_list_lock);\n\tpthread_mutex_destroy(&client.domain_map_lock);\n\tif (client.ssl_ctx) {\n\t\tSSL_CTX_free(client.ssl_ctx);\n\t\tclient.ssl_ctx = NULL;\n\t}\n\n\tif (client.ssl_quic_ctx) {\n\t\tSSL_CTX_free(client.ssl_quic_ctx);\n\t\tclient.ssl_quic_ctx = NULL;\n\t}\n\n\tis_client_init = 0;\n}\n"
  },
  {
    "path": "src/dns_client/dns_client.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_H_\n#define _DNS_CLIENT_H_\n\n#include \"smartdns/dns.h\"\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/dns_stats.h\"\n#include \"smartdns/http2.h\"\n#include \"smartdns/lib/atomic.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/lib/list.h\"\n#include \"smartdns/tlog.h\"\n\n#include <openssl/err.h>\n#include <openssl/rand.h>\n#include <openssl/ssl.h>\n#include <openssl/x509v3.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\n#define DNS_MAX_HOSTNAME 256\n#define DNS_MAX_EVENTS 256\n#define DNS_HOSTNAME_LEN 128\n#define DNS_TCP_BUFFER (32 * 1024)\n#define DNS_TCP_IDLE_TIMEOUT (60 * 10)\n#define DNS_TCP_CONNECT_TIMEOUT (5)\n#define DNS_QUERY_TIMEOUT (500)\n#define DNS_QUERY_RETRY (4)\n#define DNS_PENDING_SERVER_RETRY 60\n#define SOCKET_PRIORITY (6)\n#define SOCKET_IP_TOS (IPTOS_LOWDELAY | IPTOS_RELIABILITY)\n\n/* ECS info */\nstruct dns_client_ecs {\n\tint enable;\n\tstruct dns_opt_ecs ecs;\n};\n\n/* TCP/TLS buffer */\nstruct dns_server_buff {\n\tunsigned char data[DNS_TCP_BUFFER];\n\tint len;\n};\n\ntypedef enum dns_server_status {\n\tDNS_SERVER_STATUS_INIT = 0,\n\tDNS_SERVER_STATUS_CONNECTING,\n\tDNS_SERVER_STATUS_CONNECTIONLESS,\n\tDNS_SERVER_STATUS_CONNECTED,\n\tDNS_SERVER_STATUS_DISCONNECTED,\n} dns_server_status;\n\n/* dns server information */\nstruct dns_server_info {\n\tatomic_t refcnt;\n\tstruct list_head list;\n\tstruct list_head check_list;\n\t/* server ping handle */\n\tstruct ping_host_struct *ping_host;\n\n\tchar host[DNS_HOSTNAME_LEN];\n\tchar ip[DNS_MAX_HOSTNAME];\n\tint port;\n\tchar proxy_name[DNS_HOSTNAME_LEN];\n\t/* server type */\n\tdns_server_type_t type;\n\tlong long so_mark;\n\tint drop_packet_latency_ms;\n\n\t/* client socket */\n\tint fd;\n\tint ttl;\n\tint ttl_range;\n\tSSL *ssl;\n\tint ssl_write_len;\n\tint ssl_want_write;\n\tSSL_CTX *ssl_ctx;\n\tSSL_SESSION *ssl_session;\n\tBIO_METHOD *bio_method;\n\n\tstruct proxy_conn *proxy;\n\n\tpthread_mutex_t lock;\n\tchar skip_check_cert;\n\tdns_server_status status;\n\n\tstruct dns_server_buff send_buff;\n\tstruct dns_server_buff recv_buff;\n\n\ttime_t last_send;\n\ttime_t last_recv;\n\tunsigned long send_tick;\n\tint prohibit;\n\tatomic_t is_alive;\n\tint is_already_prohibit;\n\n\t/* server addr info */\n\tunsigned short ai_family;\n\n\tsocklen_t ai_addrlen;\n\tunion {\n\t\tstruct sockaddr_in in;\n\t\tstruct sockaddr_in6 in6;\n\t\tstruct sockaddr addr;\n\t};\n\n\tstruct client_dns_server_flags flags;\n\n\t/* ECS */\n\tstruct dns_client_ecs ecs_ipv4;\n\tstruct dns_client_ecs ecs_ipv6;\n\n\tstruct dns_server_stats stats;\n\tstruct list_head conn_stream_list;\n\n\t/* HTTP/2 context - connection level, shared across requests */\n\tstruct http2_ctx *http2_ctx;\n\tchar alpn_selected[32];\n\n\tdns_server_security_status security_status;\n};\n\nstruct dns_server_pending_group {\n\tstruct list_head list;\n\tchar group_name[DNS_GROUP_NAME_LEN];\n};\n\nstruct dns_server_pending {\n\tstruct list_head list;\n\tstruct list_head retry_list;\n\tatomic_t refcnt;\n\n\tchar host[DNS_HOSTNAME_LEN];\n\tchar ipv4[DNS_HOSTNAME_LEN];\n\tchar ipv6[DNS_HOSTNAME_LEN];\n\tunsigned int ping_time_v6;\n\tunsigned int ping_time_v4;\n\tunsigned int has_v4;\n\tunsigned int has_v6;\n\tunsigned int query_v4;\n\tunsigned int query_v6;\n\tunsigned int has_soa_v4;\n\tunsigned int has_soa_v6;\n\n\t/* server type */\n\tdns_server_type_t type;\n\tint retry_cnt;\n\n\tint port;\n\n\tstruct client_dns_server_flags flags;\n\n\tstruct list_head group_list;\n};\n\n/* upstream server group member */\nstruct dns_server_group_member {\n\tstruct list_head list;\n\tstruct dns_server_info *server;\n};\n\n/* upstream server groups */\nstruct dns_server_group {\n\tchar group_name[DNS_GROUP_NAME_LEN];\n\tstruct hlist_node node;\n\tstruct list_head head;\n};\n\n/* dns client */\nstruct dns_client {\n\tpthread_t tid;\n\tatomic_t run;\n\tint epoll_fd;\n\n\t/* dns server list */\n\tpthread_mutex_t server_list_lock;\n\tstruct list_head dns_server_list;\n\tstruct dns_server_group *default_group;\n\n\tSSL_CTX *ssl_ctx;\n\tSSL_CTX *ssl_quic_ctx;\n\tint ssl_verify_skip;\n\n\t/* query list */\n\tstruct list_head dns_request_list;\n\tatomic_t run_period;\n\tatomic_t dns_server_num;\n\tatomic_t dns_server_prohibit_num;\n\n\t/* query domain hash table, key: sid + domain */\n\tpthread_mutex_t domain_map_lock;\n\tDECLARE_HASHTABLE(domain_map, 6);\n\tDECLARE_HASHTABLE(group, 4);\n\n\tint fd_wakeup;\n};\n\n/* dns replied server info */\nstruct dns_query_replied {\n\tstruct hlist_node node;\n\tstruct dns_server_info *server;\n};\n\nstruct dns_conn_stream {\n\tatomic_t refcnt;\n\tstruct list_head query_list;\n\tstruct list_head server_list;\n\tstruct dns_server_buff send_buff;\n\tstruct dns_server_buff recv_buff;\n\n\tstruct dns_query_struct *query;\n\tstruct dns_server_info *server_info;\n\n\tSSL *quic_stream;\n\tstruct http2_stream *http2_stream;\n\tdns_server_type_t type;\n};\n\n/* query struct */\nstruct dns_query_struct {\n\tstruct list_head dns_request_list;\n\tatomic_t refcnt;\n\tstruct dns_server_group *server_group;\n\n\tstruct dns_conf_group *conf;\n\n\tstruct list_head conn_stream_list;\n\n\t/* query id, hash key sid + domain*/\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tunsigned short sid;\n\tstruct hlist_node domain_node;\n\n\tstruct list_head period_list;\n\n\t/* dns query type */\n\tint qtype;\n\n\t/* dns query number */\n\tatomic_t dns_request_sent;\n\tunsigned long send_tick;\n\n\t/* caller notification */\n\tdns_client_callback callback;\n\tvoid *user_ptr;\n\n\t/* retry count */\n\tatomic_t retry_count;\n\n\t/* has result */\n\tint has_result;\n\n\t/* ECS */\n\tstruct dns_client_ecs ecs;\n\n\t/* EDNS0_DO */\n\tint edns0_do;\n\n\t/* replied hash table */\n\tDECLARE_HASHTABLE(replied_map, 4);\n\n\tpthread_mutex_t lock;\n};\n\nextern struct dns_client client;\n\nint _dns_client_recv(struct dns_server_info *server_info, unsigned char *inpacket, int inpacket_len,\n\t\t\t\t\t struct sockaddr *from, socklen_t from_len);\n\nint _dns_client_send_packet(struct dns_query_struct *query, void *packet, int len);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/ecs.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ecs.h\"\n\n#include \"smartdns/util.h\"\n\nstatic int _dns_client_setup_ecs(char *ip, int subnet, struct dns_client_ecs *ecs)\n{\n\tstruct sockaddr_storage addr;\n\tsocklen_t addr_len = sizeof(addr);\n\tif (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) {\n\t\treturn -1;\n\t}\n\n\tswitch (addr.ss_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)&addr;\n\t\tmemcpy(&ecs->ecs.addr, &addr_in->sin_addr.s_addr, 4);\n\t\tecs->ecs.source_prefix = subnet;\n\t\tecs->ecs.scope_prefix = 0;\n\t\tecs->ecs.family = DNS_OPT_ECS_FAMILY_IPV4;\n\t\tecs->enable = 1;\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)&addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tecs->ecs.source_prefix = subnet;\n\t\t\tecs->ecs.scope_prefix = 0;\n\t\t\tecs->ecs.family = DNS_OPT_ECS_FAMILY_IPV4;\n\t\t\tecs->enable = 1;\n\t\t} else {\n\t\t\tmemcpy(&ecs->ecs.addr, addr_in6->sin6_addr.s6_addr, 16);\n\t\t\tecs->ecs.source_prefix = subnet;\n\t\t\tecs->ecs.scope_prefix = 0;\n\t\t\tecs->ecs.family = DNS_ADDR_FAMILY_IPV6;\n\t\t\tecs->enable = 1;\n\t\t}\n\t} break;\n\tdefault:\n\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nint _dns_client_server_add_ecs(struct dns_server_info *server_info, struct client_dns_server_flags *flags)\n{\n\tint ret = 0;\n\n\tif (flags == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (flags->ipv4_ecs.enable) {\n\t\tret = _dns_client_setup_ecs(flags->ipv4_ecs.ip, flags->ipv4_ecs.subnet, &server_info->ecs_ipv4);\n\t}\n\n\tif (flags->ipv6_ecs.enable) {\n\t\tret |= _dns_client_setup_ecs(flags->ipv6_ecs.ip, flags->ipv6_ecs.subnet, &server_info->ecs_ipv6);\n\t}\n\n\treturn ret;\n}\n\nint _dns_client_dns_add_ecs(struct dns_query_struct *query, struct dns_packet *packet)\n{\n\tif (query->ecs.enable == 0) {\n\t\treturn 0;\n\t}\n\n\treturn dns_add_OPT_ECS(packet, &query->ecs.ecs);\n}\n\nint _dns_client_query_setup_default_ecs(struct dns_query_struct *query)\n{\n\tstruct dns_conf_group *conf = query->conf;\n\tstruct dns_edns_client_subnet *ecs_conf = NULL;\n\n\tif (query->qtype == DNS_T_A && conf->ipv4_ecs.enable) {\n\t\tecs_conf = &conf->ipv4_ecs;\n\t} else if (query->qtype == DNS_T_AAAA && conf->ipv6_ecs.enable) {\n\t\tecs_conf = &conf->ipv6_ecs;\n\t} else {\n\t\tif (conf->ipv4_ecs.enable) {\n\t\t\tecs_conf = &conf->ipv4_ecs;\n\t\t} else if (conf->ipv6_ecs.enable) {\n\t\t\tecs_conf = &conf->ipv6_ecs;\n\t\t}\n\t}\n\n\tif (ecs_conf == NULL) {\n\t\treturn 0;\n\t}\n\n\tstruct dns_client_ecs ecs;\n\tif (_dns_client_setup_ecs(ecs_conf->ip, ecs_conf->subnet, &ecs) != 0) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(&query->ecs, &ecs, sizeof(query->ecs));\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_client/ecs.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_ECS_\n#define _DNS_CLIENT_ECS_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_query_setup_default_ecs(struct dns_query_struct *query);\n\nint _dns_client_dns_add_ecs(struct dns_query_struct *query, struct dns_packet *packet);\n\nint _dns_client_server_add_ecs(struct dns_server_info *server_info, struct client_dns_server_flags *flags);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/group.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"group.h\"\n#include \"pending_server.h\"\n#include \"server_info.h\"\n\n#include \"smartdns/util.h\"\n\n/* get server group by name */\nstruct dns_server_group *_dns_client_get_group(const char *group_name)\n{\n\tuint32_t key = 0;\n\tstruct dns_server_group *group = NULL;\n\tstruct hlist_node *tmp = NULL;\n\n\tif (group_name == NULL) {\n\t\treturn NULL;\n\t}\n\n\tkey = hash_string(group_name);\n\thash_for_each_possible_safe(client.group, group, tmp, node, key)\n\t{\n\t\tif (strncmp(group->group_name, group_name, DNS_GROUP_NAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn group;\n\t}\n\n\treturn NULL;\n}\n\n/* get server group by name */\nstruct dns_server_group *_dns_client_get_dnsserver_group(const char *group_name)\n{\n\tstruct dns_server_group *group = _dns_client_get_group(group_name);\n\n\tif (group == NULL) {\n\t\tgoto use_default;\n\t} else {\n\t\tif (list_empty(&group->head)) {\n\t\t\ttlog(TLOG_DEBUG, \"server group %s not exist, use default server group.\", group_name);\n\t\t\tgoto use_default;\n\t\t}\n\t}\n\n\treturn group;\n\nuse_default:\n\treturn client.default_group;\n}\n\n/* add server to group */\nint _dns_client_add_to_group(const char *group_name, struct dns_server_info *server_info)\n{\n\tstruct dns_server_group *group = NULL;\n\tstruct dns_server_group_member *group_member = NULL;\n\n\tgroup = _dns_client_get_group(group_name);\n\tif (group == NULL) {\n\t\ttlog(TLOG_ERROR, \"group %s not exist.\", group_name);\n\t\treturn -1;\n\t}\n\n\tgroup_member = zalloc(1, sizeof(*group_member));\n\tif (group_member == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc memory failed.\");\n\t\tgoto errout;\n\t}\n\tgroup_member->server = server_info;\n\tdns_client_server_info_get(server_info);\n\tlist_add(&group_member->list, &group->head);\n\n\treturn 0;\nerrout:\n\tif (group_member) {\n\t\tfree(group_member);\n\t}\n\n\treturn -1;\n}\n\nint dns_client_add_to_group(const char *group_name, const char *server_ip, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t\tstruct client_dns_server_flags *flags)\n{\n\treturn _dns_client_add_to_group_pending(group_name, server_ip, port, server_type, flags, 1);\n}\n\n/* free group member */\nstatic int _dns_client_remove_member(struct dns_server_group_member *group_member)\n{\n\tif (group_member == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (group_member->server) {\n\t\tdns_client_server_info_release(group_member->server);\n\t}\n\n\tlist_del_init(&group_member->list);\n\tfree(group_member);\n\n\treturn 0;\n}\n\nstatic int _dns_client_remove_from_group(struct dns_server_group *group, struct dns_server_info *server_info)\n{\n\tstruct dns_server_group_member *group_member = NULL;\n\tstruct dns_server_group_member *tmp = NULL;\n\n\tlist_for_each_entry_safe(group_member, tmp, &group->head, list)\n\t{\n\t\tif (group_member->server != server_info) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_dns_client_remove_member(group_member);\n\t}\n\n\treturn 0;\n}\n\nint _dns_client_remove_server_from_groups(struct dns_server_info *server_info)\n{\n\tstruct dns_server_group *group = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(client.group, i, tmp, group, node)\n\t{\n\t\t_dns_client_remove_from_group(group, server_info);\n\t}\n\n\treturn 0;\n}\n\nint dns_client_remove_from_group(const char *group_name, const char *server_ip, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t\t\t struct client_dns_server_flags *flags)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct dns_server_group *group = NULL;\n\n\tserver_info = _dns_client_get_server(server_ip, port, server_type, flags);\n\tif (server_info == NULL) {\n\t\treturn -1;\n\t}\n\n\tgroup = _dns_client_get_group(group_name);\n\tif (group == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn _dns_client_remove_from_group(group, server_info);\n}\n\nint dns_client_add_group(const char *group_name)\n{\n\tuint32_t key = 0;\n\tstruct dns_server_group *group = NULL;\n\n\tif (group_name == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_client_get_group(group_name) != NULL) {\n\t\treturn 0;\n\t}\n\n\tgroup = zalloc(1, sizeof(*group));\n\tif (group == NULL) {\n\t\tgoto errout;\n\t}\n\tINIT_LIST_HEAD(&group->head);\n\tsafe_strncpy(group->group_name, group_name, DNS_GROUP_NAME_LEN);\n\tkey = hash_string(group_name);\n\thash_add(client.group, &group->node, key);\n\n\treturn 0;\nerrout:\n\tif (group) {\n\t\tfree(group);\n\t\tgroup = NULL;\n\t}\n\n\treturn -1;\n}\n\nstatic int _dns_client_remove_group(struct dns_server_group *group)\n{\n\tstruct dns_server_group_member *group_member = NULL;\n\tstruct dns_server_group_member *tmp = NULL;\n\n\tif (group == NULL) {\n\t\treturn 0;\n\t}\n\n\tlist_for_each_entry_safe(group_member, tmp, &group->head, list)\n\t{\n\t\t_dns_client_remove_member(group_member);\n\t}\n\n\thash_del(&group->node);\n\tfree(group);\n\n\treturn 0;\n}\n\nint dns_client_remove_group(const char *group_name)\n{\n\tuint32_t key = 0;\n\tstruct dns_server_group *group = NULL;\n\tstruct hlist_node *tmp = NULL;\n\n\tif (group_name == NULL) {\n\t\treturn -1;\n\t}\n\n\tkey = hash_string(group_name);\n\thash_for_each_possible_safe(client.group, group, tmp, node, key)\n\t{\n\t\tif (strncmp(group->group_name, group_name, DNS_GROUP_NAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_dns_client_remove_group(group);\n\n\t\treturn 0;\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_client_group_remove_all(void)\n{\n\tstruct dns_server_group *group = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(client.group, i, tmp, group, node)\n\t{\n\t\t_dns_client_remove_group(group);\n\t}\n}"
  },
  {
    "path": "src/dns_client/group.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_GROUP_\n#define _DNS_CLIENT_GROUP_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_remove_server_from_groups(struct dns_server_info *server_info);\n\nvoid _dns_client_group_remove_all(void);\n\nstruct dns_server_group *_dns_client_get_dnsserver_group(const char *group_name);\n\nint _dns_client_add_to_group(const char *group_name, struct dns_server_info *server_info);\n\nstruct dns_server_group *_dns_client_get_group(const char *group_name);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/packet.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"packet.h\"\n\n#include \"smartdns/util.h\"\n\nint _dns_client_setup_server_packet(struct dns_server_info *server_info, struct dns_query_struct *query,\n\t\t\t\t\t\t\t\t\tvoid *default_packet, int default_packet_len, unsigned char *packet_data_buffer,\n\t\t\t\t\t\t\t\t\tvoid **packet_data, int *packet_data_len)\n{\n\tunsigned char packet_buff[DNS_PACKSIZE];\n\tstruct dns_packet *packet = (struct dns_packet *)packet_buff;\n\tstruct dns_head head;\n\tint encode_len = 0;\n\tint repack = 0;\n\tint hitchhiking = 0;\n\n\t*packet_data = default_packet;\n\t*packet_data_len = default_packet_len;\n\n\tif (server_info->ecs_ipv4.enable == true || server_info->ecs_ipv6.enable == true) {\n\t\trepack = 1;\n\t}\n\n\tif ((server_info->flags.server_flag & SERVER_FLAG_HITCHHIKING) != 0) {\n\t\thitchhiking = 1;\n\t\trepack = 1;\n\t}\n\n\tif (repack == 0) {\n\t\t/* no need to encode packet */\n\t\treturn 0;\n\t}\n\n\t/* init dns packet head */\n\tmemset(&head, 0, sizeof(head));\n\thead.id = query->sid;\n\thead.qr = DNS_QR_QUERY;\n\thead.opcode = DNS_OP_QUERY;\n\thead.aa = 0;\n\thead.rd = 1;\n\thead.ra = 0;\n\thead.ad = query->edns0_do;\n\thead.rcode = 0;\n\n\tif (dns_packet_init(packet, DNS_PACKSIZE, &head) != 0) {\n\t\ttlog(TLOG_ERROR, \"init packet failed.\");\n\t\treturn -1;\n\t}\n\n\tif (hitchhiking != 0 && dns_add_domain(packet, \"-\", query->qtype, DNS_C_IN) != 0) {\n\t\ttlog(TLOG_ERROR, \"add domain to packet failed.\");\n\t\treturn -1;\n\t}\n\n\t/* add question */\n\tif (dns_add_domain(packet, query->domain, query->qtype, DNS_C_IN) != 0) {\n\t\ttlog(TLOG_ERROR, \"add domain to packet failed.\");\n\t\treturn -1;\n\t}\n\n\tdns_set_OPT_payload_size(packet, DNS_IN_PACKSIZE);\n\tif (query->edns0_do) {\n\t\tdns_set_OPT_option(packet, DNS_OPT_FLAG_DO);\n\t}\n\n\tif (server_info->flags.tcp_keepalive > 0) {\n\t\tdns_add_OPT_TCP_KEEPALIVE(packet, server_info->flags.tcp_keepalive);\n\t}\n\n\tif ((query->qtype == DNS_T_A && server_info->ecs_ipv4.enable)) {\n\t\tdns_add_OPT_ECS(packet, &server_info->ecs_ipv4.ecs);\n\t} else if ((query->qtype == DNS_T_AAAA && server_info->ecs_ipv6.enable)) {\n\t\tdns_add_OPT_ECS(packet, &server_info->ecs_ipv6.ecs);\n\t} else if (query->qtype == DNS_T_AAAA || query->qtype == DNS_T_A || server_info->flags.subnet_all_query_types) {\n\t\tif (server_info->ecs_ipv6.enable) {\n\t\t\tdns_add_OPT_ECS(packet, &server_info->ecs_ipv6.ecs);\n\t\t} else if (server_info->ecs_ipv4.enable) {\n\t\t\tdns_add_OPT_ECS(packet, &server_info->ecs_ipv4.ecs);\n\t\t}\n\t}\n\n\t/* encode packet */\n\tencode_len = dns_encode(packet_data_buffer, DNS_IN_PACKSIZE, packet);\n\tif (encode_len <= 0) {\n\t\ttlog(TLOG_ERROR, \"encode query failed.\");\n\t\treturn -1;\n\t}\n\n\tif (encode_len > DNS_IN_PACKSIZE) {\n\t\tBUG(\"size is invalid.\");\n\t\treturn -1;\n\t}\n\n\t*packet_data = packet_data_buffer;\n\t*packet_data_len = encode_len;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_client/packet.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_PACKET_\n#define _DNS_CLIENT_PACKET_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_client_setup_server_packet(struct dns_server_info *server_info, struct dns_query_struct *query,\n\t\t\t\t\t\t\t\t\tvoid *default_packet, int default_packet_len, unsigned char *packet_data_buffer,\n\t\t\t\t\t\t\t\t\tvoid **packet_data, int *packet_data_len);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/pending_server.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"pending_server.h\"\n#include \"group.h\"\n#include \"server_info.h\"\n#include \"wake_event.h\"\n\n#include \"smartdns/dns_server.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <pthread.h>\n\nstatic LIST_HEAD(pending_servers);\nstatic pthread_mutex_t pending_server_mutex = PTHREAD_MUTEX_INITIALIZER;\nstatic int dns_client_has_bootstrap_dns = 0;\n\n/* get addr info */\nstruct addrinfo *_dns_client_getaddr(const char *host, char *port, int type, int protocol)\n{\n\tstruct addrinfo hints;\n\tstruct addrinfo *result = NULL;\n\tint ret = 0;\n\n\tmemset(&hints, 0, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = type;\n\thints.ai_protocol = protocol;\n\n\tret = getaddrinfo(host, port, &hints, &result);\n\tif (ret != 0) {\n\t\ttlog(TLOG_WARN, \"get addr info failed. %s\\n\", gai_strerror(ret));\n\t\ttlog(TLOG_WARN, \"host = %s, port = %s, type = %d, protocol = %d\", host, port, type, protocol);\n\t\tgoto errout;\n\t}\n\n\treturn result;\nerrout:\n\tif (result) {\n\t\tfreeaddrinfo(result);\n\t}\n\treturn NULL;\n}\n\nstatic int _dns_client_resolv_ip_by_host(const char *host, char *ip, int ip_len)\n{\n\tstruct addrinfo *gai = NULL;\n\tgai = _dns_client_getaddr(host, NULL, SOCK_STREAM, 0);\n\tif (gai == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (get_host_by_addr(ip, ip_len, gai->ai_addr) == NULL) {\n\t\tfreeaddrinfo(gai);\n\t\treturn -1;\n\t}\n\n\tfreeaddrinfo(gai);\n\treturn 0;\n}\n\nint _dns_client_add_to_pending_group(const char *group_name, const char *server_ip, int port,\n\t\t\t\t\t\t\t\t\t dns_server_type_t server_type, const struct client_dns_server_flags *flags)\n{\n\tstruct dns_server_pending *item = NULL;\n\tstruct dns_server_pending *tmp = NULL;\n\tstruct dns_server_pending *pending = NULL;\n\tstruct dns_server_pending_group *group = NULL;\n\n\tif (group_name == NULL || server_ip == NULL) {\n\t\tgoto errout;\n\t}\n\n\tpthread_mutex_lock(&pending_server_mutex);\n\tlist_for_each_entry_safe(item, tmp, &pending_servers, list)\n\t{\n\t\tif (memcmp(&item->flags, flags, sizeof(*flags)) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(item->host, server_ip, DNS_HOSTNAME_LEN) == 0 && item->port == port && item->type == server_type) {\n\t\t\tpending = item;\n\t\t\tbreak;\n\t\t}\n\t}\n\tpthread_mutex_unlock(&pending_server_mutex);\n\n\tif (pending == NULL) {\n\t\ttlog(TLOG_ERROR, \"cannot found server for group %s: %s, %d, %d\", group_name, server_ip, port, server_type);\n\t\tgoto errout;\n\t}\n\n\tgroup = zalloc(1, sizeof(*group));\n\tif (group == NULL) {\n\t\tgoto errout;\n\t}\n\tsafe_strncpy(group->group_name, group_name, DNS_GROUP_NAME_LEN);\n\n\tpthread_mutex_lock(&pending_server_mutex);\n\tlist_add_tail(&group->list, &pending->group_list);\n\tpthread_mutex_unlock(&pending_server_mutex);\n\n\treturn 0;\n\nerrout:\n\tif (group) {\n\t\tfree(group);\n\t}\n\treturn -1;\n}\n\nstatic void _dns_client_server_pending_get(struct dns_server_pending *pending)\n{\n\tif (atomic_inc_return(&pending->refcnt) <= 0) {\n\t\tBUG(\"pending ref is invalid\");\n\t}\n}\n\nstatic void _dns_client_server_pending_release(struct dns_server_pending *pending)\n{\n\tstruct dns_server_pending_group *group = NULL;\n\tstruct dns_server_pending_group *tmp = NULL;\n\n\tint refcnt = atomic_dec_return(&pending->refcnt);\n\n\tif (refcnt) {\n\t\tif (refcnt < 0) {\n\t\t\tBUG(\"BUG: pending refcnt is %d\", refcnt);\n\t\t}\n\t\treturn;\n\t}\n\n\tpthread_mutex_lock(&pending_server_mutex);\n\tlist_for_each_entry_safe(group, tmp, &pending->group_list, list)\n\t{\n\t\tlist_del_init(&group->list);\n\t\tfree(group);\n\t}\n\n\tlist_del_init(&pending->list);\n\tpthread_mutex_unlock(&pending_server_mutex);\n\tfree(pending);\n}\n\nstatic void _dns_client_server_pending_remove(struct dns_server_pending *pending)\n{\n\tpthread_mutex_lock(&pending_server_mutex);\n\tlist_del_init(&pending->list);\n\tpthread_mutex_unlock(&pending_server_mutex);\n\t_dns_client_server_pending_release(pending);\n}\n\nstatic int _dns_client_server_pending(const char *server_ip, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t\t\t\t  const struct client_dns_server_flags *flags)\n{\n\tstruct dns_server_pending *pending = NULL;\n\n\tpending = zalloc(1, sizeof(*pending));\n\tif (pending == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed\");\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(pending->host, server_ip, DNS_HOSTNAME_LEN);\n\tpending->port = port;\n\tpending->type = server_type;\n\tpending->ping_time_v4 = -1;\n\tpending->ping_time_v6 = -1;\n\tpending->ipv4[0] = 0;\n\tpending->ipv6[0] = 0;\n\tpending->has_v4 = 0;\n\tpending->has_v6 = 0;\n\t_dns_client_server_pending_get(pending);\n\tINIT_LIST_HEAD(&pending->group_list);\n\tINIT_LIST_HEAD(&pending->retry_list);\n\tmemcpy(&pending->flags, flags, sizeof(struct client_dns_server_flags));\n\n\tpthread_mutex_lock(&pending_server_mutex);\n\tlist_add_tail(&pending->list, &pending_servers);\n\tatomic_set(&client.run_period, 1);\n\tpthread_mutex_unlock(&pending_server_mutex);\n\n\t_dns_client_do_wakeup_event();\n\n\treturn 0;\nerrout:\n\tif (pending) {\n\t\tfree(pending);\n\t}\n\n\treturn -1;\n}\n\nint _dns_client_add_server_pending(const char *server_ip, const char *server_host, int port,\n\t\t\t\t\t\t\t\t   dns_server_type_t server_type, struct client_dns_server_flags *flags, int is_pending)\n{\n\tint ret = 0;\n\tchar server_ip_tmp[DNS_HOSTNAME_LEN] = {0};\n\n\tif (server_type >= DNS_SERVER_TYPE_END) {\n\t\ttlog(TLOG_ERROR, \"server type is invalid.\");\n\t\treturn -1;\n\t}\n\n\tif (check_is_ipaddr(server_ip) && is_pending) {\n\t\tret = _dns_client_server_pending(server_ip, port, server_type, flags);\n\t\tif (ret == 0) {\n\t\t\ttlog(TLOG_INFO, \"add pending server %s\", server_ip);\n\t\t\treturn 0;\n\t\t}\n\t} else if (check_is_ipaddr(server_ip) && is_pending == 0) {\n\t\tif (_dns_client_resolv_ip_by_host(server_ip, server_ip_tmp, sizeof(server_ip_tmp)) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"resolve %s failed.\", server_ip);\n\t\t\treturn -1;\n\t\t}\n\n\t\ttlog(TLOG_INFO, \"resolve %s to %s.\", server_ip, server_ip_tmp);\n\t\tserver_ip = server_ip_tmp;\n\t}\n\n\t/* add server */\n\tret = _dns_client_server_add(server_ip, server_host, port, server_type, flags);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\tif ((flags->server_flag & SERVER_FLAG_EXCLUDE_DEFAULT) == 0 || dns_conf_exist_bootstrap_dns) {\n\t\tdns_client_has_bootstrap_dns = 1;\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstatic int _dns_client_pending_server_resolve(const struct dns_result *result, void *user_ptr)\n{\n\tstruct dns_server_pending *pending = user_ptr;\n\tint ret = 0;\n\tint has_soa = 0;\n\n\tif (result->rtcode == DNS_RC_NXDOMAIN || result->has_soa == 1 || result->rtcode == DNS_RC_REFUSED ||\n\t\t(result->rtcode == DNS_RC_NOERROR && result->ip_num == 0 && result->ip == NULL)) {\n\t\thas_soa = 1;\n\t}\n\n\tif (result->addr_type == DNS_T_A) {\n\t\tpending->ping_time_v4 = -1;\n\t\tif (result->rtcode == DNS_RC_NOERROR && result->ip_num > 0) {\n\t\t\tpending->has_v4 = 1;\n\t\t\tpending->ping_time_v4 = result->ping_time;\n\t\t\tpending->has_soa_v4 = 0;\n\t\t\tsafe_strncpy(pending->ipv4, result->ip, DNS_HOSTNAME_LEN);\n\t\t} else if (has_soa) {\n\t\t\tpending->has_v4 = 0;\n\t\t\tpending->ping_time_v4 = -1;\n\t\t\tpending->has_soa_v4 = 1;\n\t\t}\n\t} else if (result->addr_type == DNS_T_AAAA) {\n\t\tpending->ping_time_v6 = -1;\n\t\tif (result->rtcode == DNS_RC_NOERROR && result->ip_num > 0) {\n\t\t\tpending->has_v6 = 1;\n\t\t\tpending->ping_time_v6 = result->ping_time;\n\t\t\tpending->has_soa_v6 = 0;\n\t\t\tsafe_strncpy(pending->ipv6, result->ip, DNS_HOSTNAME_LEN);\n\t\t} else if (has_soa) {\n\t\t\tpending->has_v6 = 0;\n\t\t\tpending->ping_time_v6 = -1;\n\t\t\tpending->has_soa_v6 = 1;\n\t\t}\n\t} else {\n\t\tret = -1;\n\t}\n\n\t_dns_client_server_pending_release(pending);\n\treturn ret;\n}\n\n/* add server to group */\nint _dns_client_add_to_group_pending(const char *group_name, const char *server_ip, int port,\n\t\t\t\t\t\t\t\t\t dns_server_type_t server_type, const struct client_dns_server_flags *flags,\n\t\t\t\t\t\t\t\t\t int is_pending)\n{\n\tstruct dns_server_info *server_info = NULL;\n\n\tif (group_name == NULL || server_ip == NULL) {\n\t\treturn -1;\n\t}\n\n\tserver_info = _dns_client_get_server(server_ip, port, server_type, flags);\n\tif (server_info == NULL) {\n\t\tif (is_pending == 0) {\n\t\t\ttlog(TLOG_ERROR, \"add server %s:%d to group %s failed\", server_ip, port, group_name);\n\t\t\treturn -1;\n\t\t}\n\t\treturn _dns_client_add_to_pending_group(group_name, server_ip, port, server_type, flags);\n\t}\n\n\treturn _dns_client_add_to_group(group_name, server_info);\n}\n\nstatic int _dns_client_add_pendings(struct dns_server_pending *pending, char *ip)\n{\n\tstruct dns_server_pending_group *group = NULL;\n\tstruct dns_server_pending_group *tmp = NULL;\n\tchar ip_tmp[DNS_HOSTNAME_LEN] = {0};\n\n\tif (check_is_ipaddr(ip) != 0) {\n\t\tif (_dns_client_resolv_ip_by_host(ip, ip_tmp, sizeof(ip_tmp)) != 0) {\n\t\t\ttlog(TLOG_WARN, \"resolv %s failed.\", ip);\n\t\t\treturn -1;\n\t\t}\n\n\t\ttlog(TLOG_INFO, \"resolv %s to %s.\", ip, ip_tmp);\n\n\t\tip = ip_tmp;\n\t}\n\n\tif (_dns_client_add_server_pending(ip, pending->host, pending->port, pending->type, &pending->flags, 0) != 0) {\n\t\treturn -1;\n\t}\n\n\tlist_for_each_entry_safe(group, tmp, &pending->group_list, list)\n\t{\n\t\tif (_dns_client_add_to_group_pending(group->group_name, ip, pending->port, pending->type, &pending->flags, 0) !=\n\t\t\t0) {\n\t\t\ttlog(TLOG_WARN, \"add server to group failed, skip add.\");\n\t\t}\n\n\t\tlist_del_init(&group->list);\n\t\tfree(group);\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_client_remove_all_pending_servers(void)\n{\n\tstruct dns_server_pending *pending = NULL;\n\tstruct dns_server_pending *tmp = NULL;\n\tLIST_HEAD(remove_list);\n\n\tpthread_mutex_lock(&pending_server_mutex);\n\tlist_for_each_entry_safe(pending, tmp, &pending_servers, list)\n\t{\n\t\tlist_del_init(&pending->list);\n\t\tlist_add(&pending->retry_list, &remove_list);\n\t\t_dns_client_server_pending_get(pending);\n\t}\n\tpthread_mutex_unlock(&pending_server_mutex);\n\n\tlist_for_each_entry_safe(pending, tmp, &remove_list, retry_list)\n\t{\n\t\tlist_del_init(&pending->retry_list);\n\t\t_dns_client_server_pending_remove(pending);\n\t\t_dns_client_server_pending_release(pending);\n\t}\n}\n\nvoid _dns_client_add_pending_servers(void)\n{\n#ifdef TEST\n\tconst int delay_value = 1;\n#else\n\tconst int delay_value = 3;\n#endif\n\tstruct dns_server_pending *pending = NULL;\n\tstruct dns_server_pending *tmp = NULL;\n\tstatic int delay = delay_value;\n\tLIST_HEAD(retry_list);\n\n\t/* add pending server after 3 seconds */\n\tif (++delay < delay_value) {\n\t\treturn;\n\t}\n\tdelay = 0;\n\n\tpthread_mutex_lock(&pending_server_mutex);\n\tif (list_empty(&pending_servers)) {\n\t\tatomic_set(&client.run_period, 0);\n\t} else {\n\t\tatomic_set(&client.run_period, 1);\n\t}\n\n\tlist_for_each_entry_safe(pending, tmp, &pending_servers, list)\n\t{\n\t\tlist_add(&pending->retry_list, &retry_list);\n\t\t_dns_client_server_pending_get(pending);\n\t}\n\tpthread_mutex_unlock(&pending_server_mutex);\n\n\tlist_for_each_entry_safe(pending, tmp, &retry_list, retry_list)\n\t{\n\t\t/* send dns type A, AAAA query to bootstrap DNS server */\n\t\tint add_success = 0;\n\t\tchar *dnsserver_ip = NULL;\n\n\t\t/* if has no bootstrap DNS, just call getaddrinfo to get address */\n\t\tif (dns_client_has_bootstrap_dns == 0) {\n\t\t\tlist_del_init(&pending->retry_list);\n\t\t\t_dns_client_server_pending_release(pending);\n\t\t\tpending->retry_cnt++;\n\t\t\tif (_dns_client_add_pendings(pending, pending->host) != 0) {\n\t\t\t\tpthread_mutex_unlock(&pending_server_mutex);\n\t\t\t\ttlog(TLOG_INFO, \"add pending DNS server %s from resolv.conf failed, retry %d...\", pending->host,\n\t\t\t\t\t pending->retry_cnt - 1);\n\t\t\t\tif (pending->retry_cnt - 1 > DNS_PENDING_SERVER_RETRY) {\n\t\t\t\t\ttlog(TLOG_WARN, \"add pending DNS server %s from resolv.conf failed, exit...\", pending->host);\n\t\t\t\t\texit(1);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t_dns_client_server_pending_release(pending);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (pending->query_v4 == 0) {\n\t\t\tpending->query_v4 = 1;\n\t\t\t_dns_client_server_pending_get(pending);\n\t\t\tif (dns_server_query(pending->host, DNS_T_A, NULL, _dns_client_pending_server_resolve, pending) != 0) {\n\t\t\t\t_dns_client_server_pending_release(pending);\n\t\t\t\tpending->query_v4 = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (pending->query_v6 == 0) {\n\t\t\tpending->query_v6 = 1;\n\t\t\t_dns_client_server_pending_get(pending);\n\t\t\tif (dns_server_query(pending->host, DNS_T_AAAA, NULL, _dns_client_pending_server_resolve, pending) != 0) {\n\t\t\t\t_dns_client_server_pending_release(pending);\n\t\t\t\tpending->query_v6 = 0;\n\t\t\t}\n\t\t}\n\n\t\tlist_del_init(&pending->retry_list);\n\t\t_dns_client_server_pending_release(pending);\n\n\t\t/* if both A, AAAA has query result, select fastest IP address */\n\t\tif (pending->has_v4 && pending->has_v6) {\n\t\t\tif (pending->ping_time_v4 <= pending->ping_time_v6 && pending->ipv4[0]) {\n\t\t\t\tdnsserver_ip = pending->ipv4;\n\t\t\t} else {\n\t\t\t\tdnsserver_ip = pending->ipv6;\n\t\t\t}\n\t\t} else if (pending->has_v4) {\n\t\t\tdnsserver_ip = pending->ipv4;\n\t\t} else if (pending->has_v6) {\n\t\t\tdnsserver_ip = pending->ipv6;\n\t\t}\n\n\t\tif (dnsserver_ip && dnsserver_ip[0]) {\n\t\t\tif (_dns_client_add_pendings(pending, dnsserver_ip) == 0) {\n\t\t\t\tadd_success = 1;\n\t\t\t}\n\t\t}\n\n\t\tpending->retry_cnt++;\n\t\tif (pending->retry_cnt == 1) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (dnsserver_ip == NULL && pending->has_soa_v4 && pending->has_soa_v6) {\n\t\t\ttlog(TLOG_WARN, \"add pending DNS server %s failed, no such host.\", pending->host);\n\t\t\t_dns_client_server_pending_remove(pending);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (pending->retry_cnt - 1 > DNS_PENDING_SERVER_RETRY || add_success) {\n\t\t\tif (add_success == 0) {\n\t\t\t\ttlog(TLOG_WARN, \"add pending DNS server %s failed.\", pending->host);\n\t\t\t}\n\t\t\t_dns_client_server_pending_remove(pending);\n\t\t} else {\n\t\t\ttlog(TLOG_INFO, \"add pending DNS server %s failed, retry %d...\", pending->host, pending->retry_cnt - 1);\n\t\t\tpending->query_v4 = 0;\n\t\t\tpending->query_v6 = 0;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/dns_client/pending_server.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_PENDING_SERVER_\n#define _DNS_CLIENT_PENDING_SERVER_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nstruct addrinfo *_dns_client_getaddr(const char *host, char *port, int type, int protocol);\n\nint _dns_client_add_server_pending(const char *server_ip, const char *server_host, int port,\n\t\t\t\t\t\t\t\t   dns_server_type_t server_type, struct client_dns_server_flags *flags,\n\t\t\t\t\t\t\t\t   int is_pending);\n\nint _dns_client_add_to_pending_group(const char *group_name, const char *server_ip, int port,\n\t\t\t\t\t\t\t\t\t dns_server_type_t server_type, const struct client_dns_server_flags *flags);\n\nint _dns_client_add_to_group_pending(const char *group_name, const char *server_ip, int port,\n\t\t\t\t\t\t\t\t\t dns_server_type_t server_type, const struct client_dns_server_flags *flags,\n\t\t\t\t\t\t\t\t\t int is_pending);\n\nvoid _dns_client_remove_all_pending_servers(void);\n\nvoid _dns_client_add_pending_servers(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/proxy.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/proxy.h\"\n#include \"smartdns/util.h\"\n\n#include \"proxy.h\"\n#include \"client_socket.h\"\n\n#include <sys/epoll.h>\n#include <sys/socket.h>\n\nint _dns_proxy_handshake(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now)\n{\n\tstruct epoll_event fd_event;\n\tproxy_handshake_state ret;\n\tint fd;\n\tint retval = -1;\n\tint epoll_op = EPOLL_CTL_MOD;\n\n\tpthread_mutex_lock(&server_info->lock);\n\tif (server_info->proxy == NULL) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\treturn -1;\n\t}\n\n\tret = proxy_conn_handshake(server_info->proxy);\n\tfd = server_info->fd;\n\n\tif (ret == PROXY_HANDSHAKE_OK) {\n\t\tpthread_mutex_unlock(&server_info->lock);\n\t\treturn 0;\n\t}\n\n\tif (ret == PROXY_HANDSHAKE_ERR || fd < 0) {\n\t\tgoto errout;\n\t}\n\n\tmemset(&fd_event, 0, sizeof(fd_event));\n\tif (ret == PROXY_HANDSHAKE_CONNECTED) {\n\t\tfd_event.events = EPOLLIN;\n\t\tif (server_info->type == DNS_SERVER_UDP || server_info->type == DNS_SERVER_HTTP3 ||\n\t\t\tserver_info->type == DNS_SERVER_QUIC) {\n\t\t\tepoll_ctl(client.epoll_fd, EPOLL_CTL_DEL, fd, NULL);\n\t\t\tevent->events = 0;\n\t\t\tfd = proxy_conn_get_udpfd(server_info->proxy);\n\t\t\tif (fd < 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"get udp fd failed\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tset_fd_nonblock(fd, 1);\n\t\t\tif (server_info->so_mark >= 0) {\n\t\t\t\tunsigned int so_mark = server_info->so_mark;\n\t\t\t\tif (setsockopt(fd, SOL_SOCKET, SO_MARK, &so_mark, sizeof(so_mark)) != 0) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"set socket mark failed, %s\", strerror(errno));\n\t\t\t\t}\n\t\t\t}\n\t\t\tserver_info->fd = fd;\n\t\t\tepoll_op = EPOLL_CTL_ADD;\n\n\t\t\tif (server_info->type == DNS_SERVER_UDP) {\n\t\t\t\tserver_info->status = DNS_SERVER_STATUS_CONNECTED;\n\t\t\t} else {\n\t\t\t\t/* do handshake for quic */\n\t\t\t\tserver_info->status = DNS_SERVER_STATUS_CONNECTING;\n\t\t\t\tfd_event.events |= EPOLLOUT;\n\t\t\t}\n\n\t\t} else {\n\t\t\tfd_event.events |= EPOLLOUT;\n\t\t}\n\t\tretval = 0;\n\t}\n\n\tif (ret == PROXY_HANDSHAKE_WANT_READ) {\n\t\tfd_event.events = EPOLLIN;\n\t} else if (ret == PROXY_HANDSHAKE_WANT_WRITE) {\n\t\tfd_event.events = EPOLLOUT | EPOLLIN;\n\t}\n\n\tfd_event.data.ptr = server_info;\n\tif (epoll_ctl(client.epoll_fd, epoll_op, fd, &fd_event) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tpthread_mutex_unlock(&server_info->lock);\n\treturn retval;\n\nerrout:\n\tserver_info->recv_buff.len = 0;\n\tserver_info->send_buff.len = 0;\n\tif (server_info->proxy) {\n\t\t_dns_client_close_socket(server_info);\n\t}\n\tpthread_mutex_unlock(&server_info->lock);\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_client/proxy.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_PROXY_\n#define _DNS_CLIENT_PROXY_\n\n#include \"dns_client.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_proxy_handshake(struct dns_server_info *server_info, struct epoll_event *event, unsigned long now);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/query.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/util.h\"\n\n#include \"conn_stream.h\"\n#include \"ecs.h\"\n#include \"query.h\"\n\nvoid _dns_client_query_get(struct dns_query_struct *query)\n{\n\tif (atomic_inc_return(&query->refcnt) <= 0) {\n\t\tBUG(\"query ref is invalid, domain: %s\", query->domain);\n\t}\n}\n\nvoid _dns_client_query_release(struct dns_query_struct *query)\n{\n\tint refcnt = atomic_dec_return(&query->refcnt);\n\tunsigned long bucket = 0;\n\tstruct dns_query_replied *replied_map = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tstruct dns_conn_stream *stream = NULL;\n\tstruct dns_conn_stream *stream_tmp = NULL;\n\n\tif (refcnt) {\n\t\tif (refcnt < 0) {\n\t\t\tBUG(\"BUG: refcnt is %d\", refcnt);\n\t\t}\n\t\treturn;\n\t}\n\n\t/* notify caller query end */\n\tif (query->callback) {\n\t\ttlog(TLOG_DEBUG, \"result: %s, qtype: %d, has-result: %d, id %d\", query->domain, query->qtype, query->has_result,\n\t\t\t query->sid);\n\t\tquery->callback(query->domain, DNS_QUERY_END, NULL, NULL, NULL, 0, query->user_ptr);\n\t}\n\n\tpthread_mutex_lock(&query->lock);\n\tlist_for_each_entry_safe(stream, stream_tmp, &query->conn_stream_list, query_list)\n\t{\n\t\tlist_del_init(&stream->query_list);\n\t\tstream->query = NULL;\n\t\t_dns_client_conn_stream_put(stream);\n\t}\n\tpthread_mutex_unlock(&query->lock);\n\n\tpthread_mutex_destroy(&query->lock);\n\n\t/* free resource */\n\tpthread_mutex_lock(&client.domain_map_lock);\n\tlist_del_init(&query->dns_request_list);\n\thash_del(&query->domain_node);\n\tpthread_mutex_unlock(&client.domain_map_lock);\n\n\thash_for_each_safe(query->replied_map, bucket, tmp, replied_map, node)\n\t{\n\t\thash_del(&replied_map->node);\n\t\tfree(replied_map);\n\t}\n\tmemset(query, 0, sizeof(*query));\n\tfree(query);\n}\n\nvoid _dns_client_query_remove(struct dns_query_struct *query)\n{\n\t/* remove query from period check list, and release reference*/\n\tpthread_mutex_lock(&client.domain_map_lock);\n\tlist_del_init(&query->dns_request_list);\n\thash_del(&query->domain_node);\n\tpthread_mutex_unlock(&client.domain_map_lock);\n\n\t_dns_client_query_release(query);\n}\n\nvoid _dns_client_query_remove_all(void)\n{\n\tstruct dns_query_struct *query = NULL;\n\tstruct dns_query_struct *tmp = NULL;\n\tLIST_HEAD(check_list);\n\n\tpthread_mutex_lock(&client.domain_map_lock);\n\tlist_for_each_entry_safe(query, tmp, &client.dns_request_list, dns_request_list)\n\t{\n\t\tlist_add(&query->period_list, &check_list);\n\t}\n\tpthread_mutex_unlock(&client.domain_map_lock);\n\n\tlist_for_each_entry_safe(query, tmp, &check_list, period_list)\n\t{\n\t\tlist_del_init(&query->period_list);\n\t\t_dns_client_query_remove(query);\n\t}\n}\n\nint _dns_client_send_query(struct dns_query_struct *query)\n{\n\tunsigned char packet_buff[DNS_PACKSIZE];\n\tunsigned char inpacket[DNS_IN_PACKSIZE];\n\tstruct dns_packet *packet = (struct dns_packet *)packet_buff;\n\tint encode_len = 0;\n\n\t/* init dns packet head */\n\tstruct dns_head head;\n\tmemset(&head, 0, sizeof(head));\n\thead.id = query->sid;\n\thead.qr = DNS_QR_QUERY;\n\thead.opcode = DNS_OP_QUERY;\n\thead.aa = 0;\n\thead.rd = 1;\n\thead.ra = 0;\n\thead.ad = query->edns0_do;\n\thead.rcode = 0;\n\n\tif (dns_packet_init(packet, DNS_PACKSIZE, &head) != 0) {\n\t\ttlog(TLOG_ERROR, \"init packet failed.\");\n\t\treturn -1;\n\t}\n\n\t/* add question */\n\tif (dns_add_domain(packet, query->domain, query->qtype, DNS_C_IN) != 0) {\n\t\ttlog(TLOG_ERROR, \"add domain to packet failed.\");\n\t\treturn -1;\n\t}\n\n\tdns_set_OPT_payload_size(packet, DNS_IN_PACKSIZE);\n\tif (query->edns0_do) {\n\t\tdns_set_OPT_option(packet, DNS_OPT_FLAG_DO);\n\t}\n\t/* dns_add_OPT_TCP_KEEPALIVE(packet, 1200); */\n\tif (_dns_client_dns_add_ecs(query, packet) != 0) {\n\t\ttlog(TLOG_ERROR, \"add ecs failed.\");\n\t\treturn -1;\n\t}\n\n\t/* encode packet */\n\tencode_len = dns_encode(inpacket, DNS_IN_PACKSIZE, packet);\n\tif (encode_len <= 0) {\n\t\ttlog(TLOG_ERROR, \"encode query failed.\");\n\t\treturn -1;\n\t}\n\n\tif (encode_len > DNS_IN_PACKSIZE) {\n\t\tBUG(\"size is invalid.\");\n\t\treturn -1;\n\t}\n\n\t/* send query packet */\n\treturn _dns_client_send_packet(query, inpacket, encode_len);\n}\n\nstruct dns_query_struct *_dns_client_get_request(char *domain, int qtype, unsigned short sid)\n{\n\tstruct dns_query_struct *query = NULL;\n\tstruct dns_query_struct *query_result = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tuint32_t key = 0;\n\n\t/* get query by hash key : id + domain */\n\tkey = hash_string(domain);\n\tkey = jhash(&sid, sizeof(sid), key);\n\tkey = jhash(&qtype, sizeof(qtype), key);\n\tpthread_mutex_lock(&client.domain_map_lock);\n\thash_for_each_possible_safe(client.domain_map, query, tmp, domain_node, key)\n\t{\n\t\tif (sid != query->sid) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (qtype != query->qtype) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(query->domain, domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tquery_result = query;\n\t\t_dns_client_query_get(query_result);\n\t\tbreak;\n\t}\n\tpthread_mutex_unlock(&client.domain_map_lock);\n\n\treturn query_result;\n}\n\nint _dns_replied_check_add(struct dns_query_struct *dns_query, struct dns_server_info *server)\n{\n\tuint32_t key = 0;\n\tstruct dns_query_replied *replied_map = NULL;\n\n\t/* avoid multiple replies from one server */\n\tkey = jhash((const void *)&server, sizeof(server), 0);\n\thash_for_each_possible(dns_query->replied_map, replied_map, node, key)\n\t{\n\t\t/* already replied, ignore this reply */\n\t\tif (replied_map->server == server) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treplied_map = zalloc(1, sizeof(*replied_map));\n\tif (replied_map == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed\");\n\t\treturn -1;\n\t}\n\n\t/* add address info to check hashtable */\n\treplied_map->server = server;\n\thash_add(dns_query->replied_map, &replied_map->node, key);\n\treturn 0;\n}\n\nvoid _dns_replied_check_remove(struct dns_query_struct *dns_query, struct dns_server_info *server)\n{\n\tuint32_t key = 0;\n\tstruct dns_query_replied *replied_map = NULL;\n\n\tkey = jhash((const void *)&server, sizeof(server), 0);\n\thash_for_each_possible(dns_query->replied_map, replied_map, node, key)\n\t{\n\t\tif (replied_map->server == server) {\n\t\t\thash_del(&replied_map->node);\n\t\t\tfree(replied_map);\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nint _dns_client_query_parser_options(struct dns_query_struct *query, struct dns_query_options *options)\n{\n\tif (options->enable_flag & DNS_QUEY_OPTION_ECS_IP) {\n\t\tstruct sockaddr_storage addr;\n\t\tsocklen_t addr_len = sizeof(addr);\n\t\tstruct dns_opt_ecs *ecs = NULL;\n\n\t\tecs = &query->ecs.ecs;\n\t\tgetaddr_by_host(options->ecs_ip.ip, (struct sockaddr *)&addr, &addr_len);\n\n\t\tquery->ecs.enable = 1;\n\t\tecs->source_prefix = options->ecs_ip.subnet;\n\t\tecs->scope_prefix = 0;\n\n\t\tswitch (addr.ss_family) {\n\t\tcase AF_INET: {\n\t\t\tstruct sockaddr_in *addr_in = NULL;\n\t\t\taddr_in = (struct sockaddr_in *)&addr;\n\t\t\tecs->family = DNS_OPT_ECS_FAMILY_IPV4;\n\t\t\tmemcpy(&ecs->addr, &addr_in->sin_addr.s_addr, 4);\n\t\t} break;\n\t\tcase AF_INET6: {\n\t\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\t\taddr_in6 = (struct sockaddr_in6 *)&addr;\n\t\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\t\tmemcpy(&ecs->addr, addr_in6->sin6_addr.s6_addr + 12, 4);\n\t\t\t\tecs->family = DNS_OPT_ECS_FAMILY_IPV4;\n\t\t\t} else {\n\t\t\t\tmemcpy(&ecs->addr, addr_in6->sin6_addr.s6_addr, 16);\n\t\t\t\tecs->family = DNS_OPT_ECS_FAMILY_IPV6;\n\t\t\t}\n\t\t} break;\n\t\tdefault:\n\t\t\ttlog(TLOG_WARN, \"ECS set failure.\");\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (options->enable_flag & DNS_QUEY_OPTION_ECS_DNS) {\n\t\tstruct dns_opt_ecs *ecs = &options->ecs_dns;\n\t\tif (ecs->family != DNS_OPT_ECS_FAMILY_IPV6 && ecs->family != DNS_OPT_ECS_FAMILY_IPV4) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (ecs->family == DNS_OPT_ECS_FAMILY_IPV4 && ecs->source_prefix > 32) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (ecs->family == DNS_OPT_ECS_FAMILY_IPV6 && ecs->source_prefix > 128) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tmemcpy(&query->ecs.ecs, ecs, sizeof(query->ecs.ecs));\n\t\tquery->ecs.enable = 1;\n\t}\n\n\tif (query->ecs.enable == 0) {\n\t\t_dns_client_query_setup_default_ecs(query);\n\t}\n\n\tif (options->enable_flag & DNS_QUEY_OPTION_EDNS0_DO) {\n\t\tquery->edns0_do = 1;\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_client_retry_dns_query(struct dns_query_struct *query)\n{\n\tif (atomic_dec_and_test(&query->retry_count) || (query->has_result != 0)) {\n\t\t_dns_client_query_remove(query);\n\t\tif (query->has_result == 0) {\n\t\t\ttlog(TLOG_DEBUG, \"retry query %s, type: %d, id: %d failed\", query->domain, query->qtype, query->sid);\n\t\t}\n\t} else {\n\t\ttlog(TLOG_DEBUG, \"retry query %s, type: %d, id: %d\", query->domain, query->qtype, query->sid);\n\t\t_dns_client_send_query(query);\n\t}\n}\n\nint _dns_client_add_hashmap(struct dns_query_struct *query)\n{\n\tuint32_t key = 0;\n\tstruct hlist_node *tmp = NULL;\n\tstruct dns_query_struct *query_check = NULL;\n\tint is_exists = 0;\n\tint loop = 0;\n\n\twhile (loop++ <= 32) {\n\t\tif (RAND_bytes((unsigned char *)&query->sid, sizeof(query->sid)) != 1) {\n\t\t\tquery->sid = random();\n\t\t}\n\n\t\tkey = hash_string(query->domain);\n\t\tkey = jhash(&query->sid, sizeof(query->sid), key);\n\t\tkey = jhash(&query->qtype, sizeof(query->qtype), key);\n\t\tis_exists = 0;\n\t\tpthread_mutex_lock(&client.domain_map_lock);\n\t\thash_for_each_possible_safe(client.domain_map, query_check, tmp, domain_node, key)\n\t\t{\n\t\t\tif (query->sid != query_check->sid) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (query->qtype != query_check->qtype) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (strncmp(query_check->domain, query->domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tis_exists = 1;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (is_exists == 1) {\n\t\t\tpthread_mutex_unlock(&client.domain_map_lock);\n\t\t\tcontinue;\n\t\t}\n\n\t\thash_add(client.domain_map, &query->domain_node, key);\n\t\tpthread_mutex_unlock(&client.domain_map_lock);\n\t\tbreak;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_client/query.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_QUERY_\n#define _DNS_CLIENT_QUERY_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_client_retry_dns_query(struct dns_query_struct *query);\n\nvoid _dns_client_query_remove(struct dns_query_struct *query);\n\nvoid _dns_client_query_release(struct dns_query_struct *query);\n\nvoid _dns_client_query_get(struct dns_query_struct *query);\n\nvoid _dns_client_query_remove_all(void);\n\nint _dns_client_send_query(struct dns_query_struct *query);\n\nstruct dns_query_struct *_dns_client_get_request(char *domain, int qtype, unsigned short sid);\n\nint _dns_replied_check_add(struct dns_query_struct *dns_query, struct dns_server_info *server);\nvoid _dns_replied_check_remove(struct dns_query_struct *dns_query, struct dns_server_info *server);\n\nint _dns_client_query_parser_options(struct dns_query_struct *query, struct dns_query_options *options);\n\nvoid _dns_client_retry_dns_query(struct dns_query_struct *query);\n\nint _dns_client_add_hashmap(struct dns_query_struct *query);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/server_info.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"server_info.h\"\n#include \"client_socket.h\"\n#include \"client_tls.h\"\n#include \"conn_stream.h\"\n#include \"ecs.h\"\n#include \"group.h\"\n#include \"pending_server.h\"\n\n#include \"smartdns/fast_ping.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <net/if.h>\n#include <pthread.h>\n#include <sys/epoll.h>\n\nunsigned int dns_client_server_result_flag(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn server_info->flags.result_flag;\n}\n\nconst char *dns_client_get_server_ip(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn server_info->ip;\n}\n\ndns_server_security_status dns_client_get_server_security_status(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn DNS_CLIENT_SERVER_SECURITY_UNKNOW;\n\t}\n\n\treturn server_info->security_status;\n}\n\nconst char *dns_client_get_server_host(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn server_info->host;\n}\n\nint dns_client_get_server_port(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn server_info->port;\n}\n\nstatic inline void _dns_server_inc_server_num(struct dns_server_info *server_info)\n{\n\tif (server_info->type == DNS_SERVER_MDNS) {\n\t\treturn;\n\t}\n\n\tatomic_inc(&client.dns_server_num);\n}\n\nstatic inline void _dns_server_dec_server_num(struct dns_server_info *server_info)\n{\n\tif (server_info->type == DNS_SERVER_MDNS) {\n\t\treturn;\n\t}\n\n\tatomic_dec(&client.dns_server_num);\n}\n\nvoid _dns_server_inc_prohibit_server_num(struct dns_server_info *server_info)\n{\n\tif (server_info->type == DNS_SERVER_MDNS) {\n\t\treturn;\n\t}\n\n\tatomic_inc(&client.dns_server_prohibit_num);\n}\n\nvoid _dns_server_dec_prohibit_server_num(struct dns_server_info *server_info)\n{\n\tif (server_info->type == DNS_SERVER_MDNS) {\n\t\treturn;\n\t}\n\n\tatomic_dec(&client.dns_server_prohibit_num);\n}\n\ndns_server_type_t dns_client_get_server_type(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn DNS_SERVER_TYPE_END;\n\t}\n\n\treturn server_info->type;\n}\n\nstruct dns_server_stats *dns_client_get_server_stats(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn &server_info->stats;\n}\n\nint dns_client_server_is_alive(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn atomic_read(&server_info->is_alive);\n}\n\nstatic void _dns_client_server_free(struct dns_server_info *server_info)\n{\n\tpthread_mutex_lock(&client.server_list_lock);\n\tif (!list_empty(&server_info->list)) {\n\t\tlist_del_init(&server_info->list);\n\t\t_dns_server_dec_server_num(server_info);\n\t}\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\tlist_del_init(&server_info->check_list);\n\t_dns_client_server_close(server_info);\n\tpthread_mutex_destroy(&server_info->lock);\n\tfree(server_info);\n}\n\nvoid dns_client_server_info_get(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn;\n\t}\n\n\tatomic_inc(&server_info->refcnt);\n}\n\nvoid dns_client_server_info_release(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn;\n\t}\n\n\tint refcnt = atomic_dec_return(&server_info->refcnt);\n\tif (refcnt > 0) {\n\t\treturn;\n\t}\n\n\t_dns_client_server_free(server_info);\n}\n\nstatic void _dns_client_server_info_remove(struct dns_server_info *server_info)\n{\n\tif (server_info == NULL) {\n\t\treturn;\n\t}\n\n\tpthread_mutex_lock(&client.server_list_lock);\n\tif (!list_empty(&server_info->list)) {\n\t\tlist_del_init(&server_info->list);\n\t\t_dns_server_dec_server_num(server_info);\n\t}\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\t_dns_client_server_close(server_info);\n\tdns_client_server_info_release(server_info);\n}\n\nint dns_client_get_server_info_lists(struct dns_server_info **server_info, int max_server_num)\n{\n\tstruct dns_server_info *server = NULL;\n\tstruct dns_server_info *tmp = NULL;\n\tint i = 0;\n\n\tif (server_info == NULL) {\n\t\treturn -1;\n\t}\n\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_for_each_entry_safe(server, tmp, &client.dns_server_list, list)\n\t{\n\t\tif (i >= max_server_num) {\n\t\t\tbreak;\n\t\t}\n\n\t\tserver_info[i] = server;\n\t\tdns_client_server_info_get(server_info[i]);\n\t\ti++;\n\t}\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\treturn i;\n}\n\n/* check whether server exists */\nstatic int _dns_client_server_exist(const char *server_ip, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t\t\t\tstruct client_dns_server_flags *flags)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct dns_server_info *tmp = NULL;\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list)\n\t{\n\t\tif (server_info->port != port || server_info->type != server_type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (memcmp(&server_info->flags, flags, sizeof(*flags)) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(server_info->ip, server_ip, DNS_HOSTNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tpthread_mutex_unlock(&client.server_list_lock);\n\t\treturn 0;\n\t}\n\n\tpthread_mutex_unlock(&client.server_list_lock);\n\treturn -1;\n}\n\nstatic void _dns_client_server_update_ttl(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result,\n\t\t\t\t\t\t\t\t\t\t  struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl,\n\t\t\t\t\t\t\t\t\t\t  struct timeval *tv, int error, void *userptr)\n{\n\tstruct dns_server_info *server_info = userptr;\n\tif (result != PING_RESULT_RESPONSE || server_info == NULL) {\n\t\treturn;\n\t}\n\n\tdouble rtt = tv->tv_sec * 1000.0 + tv->tv_usec / 1000.0;\n\ttlog(TLOG_DEBUG, \"from %s: seq=%d ttl=%d time=%.3f\\n\", host, seqno, ttl, rtt);\n\tserver_info->ttl = ttl;\n}\n\n/* get server control block by ip and port, type */\nstruct dns_server_info *_dns_client_get_server(const char *server_ip, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t\t\t\t\t\t   const struct client_dns_server_flags *flags)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct dns_server_info *tmp = NULL;\n\tstruct dns_server_info *server_info_return = NULL;\n\n\tif (server_ip == NULL) {\n\t\treturn NULL;\n\t}\n\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list)\n\t{\n\t\tif (server_info->port != port || server_info->type != server_type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(server_info->ip, server_ip, DNS_HOSTNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (memcmp(&server_info->flags, flags, sizeof(*flags)) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tserver_info_return = server_info;\n\t\tbreak;\n\t}\n\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\treturn server_info_return;\n}\n\n/* add dns server information */\nint _dns_client_server_add(const char *server_ip, const char *server_host, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t   struct client_dns_server_flags *flags)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct addrinfo *gai = NULL;\n\tint spki_data_len = 0;\n\tint ttl = 0;\n\tchar port_s[8];\n\tint sock_type = 0;\n\tchar skip_check_cert = 0;\n\tchar ifname[IFNAMSIZ * 2] = {0};\n\tchar default_is_alive = 0;\n\n\tswitch (server_type) {\n\tcase DNS_SERVER_UDP: {\n\t\tstruct client_dns_server_flag_udp *flag_udp = &flags->udp;\n\t\tttl = flag_udp->ttl;\n\t\tif (ttl > 255) {\n\t\t\tttl = 255;\n\t\t} else if (ttl < -32) {\n\t\t\tttl = -32;\n\t\t}\n\n\t\tsock_type = SOCK_DGRAM;\n\t} break;\n\tcase DNS_SERVER_HTTP3: {\n\t\tstruct client_dns_server_flag_https *flag_https = &flags->https;\n\t\tspki_data_len = flag_https->spi_len;\n\t\tif (flag_https->httphost[0] == 0) {\n\t\t\tif (server_host) {\n\t\t\t\tsafe_strncpy(flag_https->httphost, server_host, DNS_MAX_CNAME_LEN);\n\t\t\t} else {\n\t\t\t\tset_http_host(server_ip, port, DEFAULT_DNS_HTTPS_PORT, flag_https->httphost);\n\t\t\t}\n\t\t}\n\t\tsock_type = SOCK_DGRAM;\n\t\tskip_check_cert = flag_https->skip_check_cert;\n\t} break;\n\tcase DNS_SERVER_HTTPS: {\n\t\tstruct client_dns_server_flag_https *flag_https = &flags->https;\n\t\tspki_data_len = flag_https->spi_len;\n\t\tif (flag_https->httphost[0] == 0) {\n\t\t\tif (server_host) {\n\t\t\t\tsafe_strncpy(flag_https->httphost, server_host, DNS_MAX_CNAME_LEN);\n\t\t\t} else {\n\t\t\t\tset_http_host(server_ip, port, DEFAULT_DNS_HTTPS_PORT, flag_https->httphost);\n\t\t\t}\n\t\t}\n\t\tsock_type = SOCK_STREAM;\n\t\tskip_check_cert = flag_https->skip_check_cert;\n\t} break;\n\tcase DNS_SERVER_QUIC: {\n\t\tstruct client_dns_server_flag_tls *flag_tls = &flags->tls;\n\t\tspki_data_len = flag_tls->spi_len;\n\t\tsock_type = SOCK_DGRAM;\n\t\tskip_check_cert = flag_tls->skip_check_cert;\n\t} break;\n\tcase DNS_SERVER_TLS: {\n\t\tstruct client_dns_server_flag_tls *flag_tls = &flags->tls;\n\t\tspki_data_len = flag_tls->spi_len;\n\t\tsock_type = SOCK_STREAM;\n\t\tskip_check_cert = flag_tls->skip_check_cert;\n\t} break;\n\tcase DNS_SERVER_TCP:\n\t\tsock_type = SOCK_STREAM;\n\t\tbreak;\n\tcase DNS_SERVER_MDNS: {\n\t\tif (flags->ifname[0] == '\\0') {\n\t\t\ttlog(TLOG_ERROR, \"mdns server must set ifname.\");\n\t\t\treturn -1;\n\t\t}\n\t\tsock_type = SOCK_DGRAM;\n\t\tdefault_is_alive = 1;\n\t} break;\n\tdefault:\n\t\treturn -1;\n\t\tbreak;\n\t}\n\n\tif (spki_data_len > DNS_SERVER_SPKI_LEN) {\n\t\ttlog(TLOG_ERROR, \"spki data length is invalid.\");\n\t\treturn -1;\n\t}\n\n\t/* if server exist, return */\n\tif (_dns_client_server_exist(server_ip, port, server_type, flags) == 0) {\n\t\treturn 0;\n\t}\n\n\tsnprintf(port_s, sizeof(port_s), \"%d\", port);\n\tgai = _dns_client_getaddr(server_ip, port_s, sock_type, 0);\n\tif (gai == NULL) {\n\t\ttlog(TLOG_DEBUG, \"get address failed, %s:%d\", server_ip, port);\n\t\tgoto errout;\n\t}\n\n\tif (server_type != DNS_SERVER_UDP) {\n\t\tflags->result_flag &= (~DNSSERVER_FLAG_CHECK_TTL);\n\t}\n\n\tserver_info = zalloc(1, sizeof(*server_info));\n\tif (server_info == NULL) {\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(server_info->ip, server_ip, sizeof(server_info->ip));\n\tserver_info->port = port;\n\tserver_info->ai_family = gai->ai_family;\n\tserver_info->ai_addrlen = gai->ai_addrlen;\n\tserver_info->type = server_type;\n\tserver_info->fd = -1;\n\tserver_info->status = DNS_SERVER_STATUS_INIT;\n\tserver_info->ttl = ttl;\n\tserver_info->ttl_range = 0;\n\tserver_info->skip_check_cert = skip_check_cert;\n\tserver_info->prohibit = 0;\n\tserver_info->so_mark = flags->set_mark;\n\tserver_info->drop_packet_latency_ms = flags->drop_packet_latency_ms;\n\tserver_info->security_status = DNS_CLIENT_SERVER_SECURITY_UNKNOW;\n\n\tatomic_set(&server_info->refcnt, 0);\n\tatomic_set(&server_info->is_alive, default_is_alive);\n\tINIT_LIST_HEAD(&server_info->check_list);\n\tINIT_LIST_HEAD(&server_info->list);\n\tsafe_strncpy(server_info->proxy_name, flags->proxyname, sizeof(server_info->proxy_name));\n\tif (server_host && server_host[0]) {\n\t\tsafe_strncpy(server_info->host, server_host, sizeof(server_info->host));\n\t} else {\n\t\tsafe_strncpy(server_info->host, server_ip, sizeof(server_info->host));\n\t}\n\n\tpthread_mutexattr_t attr;\n\tpthread_mutexattr_init(&attr);\n\tpthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);\n\tpthread_mutex_init(&server_info->lock, &attr);\n\tpthread_mutexattr_destroy(&attr);\n\n\tmemcpy(&server_info->flags, flags, sizeof(server_info->flags));\n\tINIT_LIST_HEAD(&server_info->list);\n\tINIT_LIST_HEAD(&server_info->conn_stream_list);\n\n\tif (_dns_client_server_add_ecs(server_info, flags) != 0) {\n\t\ttlog(TLOG_ERROR, \"add %s ecs failed.\", server_ip);\n\t\tgoto errout;\n\t}\n\n\t/* exclude this server from default group */\n\tif ((server_info->flags.server_flag & SERVER_FLAG_EXCLUDE_DEFAULT) == 0) {\n\t\tif (_dns_client_add_to_group(DNS_SERVER_GROUP_DEFAULT, server_info) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"add server %s to default group failed.\", server_ip);\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\t/* if server type is TLS, create ssl context */\n\tif (server_type == DNS_SERVER_TLS || server_type == DNS_SERVER_HTTPS || server_type == DNS_SERVER_QUIC ||\n\t\tserver_type == DNS_SERVER_HTTP3) {\n\t\tif (server_type == DNS_SERVER_QUIC || server_type == DNS_SERVER_HTTP3) {\n\t\t\tserver_info->ssl_ctx = _ssl_ctx_get(1);\n\t\t} else {\n\t\t\tserver_info->ssl_ctx = _ssl_ctx_get(0);\n\t\t}\n\t\tif (server_info->ssl_ctx == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"init ssl failed.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (client.ssl_verify_skip) {\n\t\t\tserver_info->skip_check_cert = 1;\n\t\t}\n\t}\n\n\t/* safe address info */\n\tif (gai->ai_addrlen > sizeof(server_info->in6)) {\n\t\ttlog(TLOG_ERROR, \"addr len invalid, %d, %zd, %d\", gai->ai_addrlen, sizeof(server_info->addr),\n\t\t\t server_info->ai_family);\n\t\tgoto errout;\n\t}\n\tmemcpy(&server_info->addr, gai->ai_addr, gai->ai_addrlen);\n\n\t/* start ping task */\n\tif (server_type == DNS_SERVER_UDP) {\n\t\tif (ttl <= 0 && (server_info->flags.result_flag & DNSSERVER_FLAG_CHECK_TTL)) {\n\t\t\tserver_info->ping_host =\n\t\t\t\tfast_ping_start(PING_TYPE_DNS, server_ip, 0, 60000, 1000, _dns_client_server_update_ttl, server_info);\n\t\t\tif (server_info->ping_host == NULL) {\n\t\t\t\ttlog(TLOG_ERROR, \"start ping failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (ttl < 0) {\n\t\t\t\tserver_info->ttl_range = -ttl;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* add to list */\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_add(&server_info->list, &client.dns_server_list);\n\tdns_client_server_info_get(server_info);\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\t_dns_server_inc_server_num(server_info);\n\tfreeaddrinfo(gai);\n\n\tif (flags->ifname[0]) {\n\t\tsnprintf(ifname, sizeof(ifname), \"@%s\", flags->ifname);\n\t}\n\n\ttlog(TLOG_INFO, \"add server %s:%d%s, type: %s\", server_ip, port, ifname,\n\t\t _dns_server_get_type_string(server_info->type));\n\n\treturn 0;\nerrout:\n\tif (server_info) {\n\t\tif (server_info->ping_host) {\n\t\t\tfast_ping_stop(server_info->ping_host);\n\t\t}\n\n\t\tpthread_mutex_destroy(&server_info->lock);\n\t\tfree(server_info);\n\t}\n\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\n\treturn -1;\n}\n\nconst char *_dns_server_get_type_string(dns_server_type_t type)\n{\n\tconst char *type_str = \"\";\n\n\tswitch (type) {\n\tcase DNS_SERVER_UDP:\n\t\ttype_str = \"udp\";\n\t\tbreak;\n\tcase DNS_SERVER_TCP:\n\t\ttype_str = \"tcp\";\n\t\tbreak;\n\tcase DNS_SERVER_TLS:\n\t\ttype_str = \"tls\";\n\t\tbreak;\n\tcase DNS_SERVER_HTTPS:\n\t\ttype_str = \"https\";\n\t\tbreak;\n\tcase DNS_SERVER_MDNS:\n\t\ttype_str = \"mdns\";\n\t\tbreak;\n\tcase DNS_SERVER_HTTP3:\n\t\ttype_str = \"http3\";\n\t\tbreak;\n\tcase DNS_SERVER_QUIC:\n\t\ttype_str = \"quic\";\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn type_str;\n}\n\nvoid _dns_client_server_close(struct dns_server_info *server_info)\n{\n\t/* stop ping task */\n\tif (server_info->ping_host) {\n\t\tif (fast_ping_stop(server_info->ping_host) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"stop ping failed.\\n\");\n\t\t}\n\n\t\tserver_info->ping_host = NULL;\n\t}\n\n\t_dns_client_close_socket(server_info);\n\n\tif (server_info->ssl_session) {\n\t\tSSL_SESSION_free(server_info->ssl_session);\n\t\tserver_info->ssl_session = NULL;\n\t}\n\n\tserver_info->ssl_ctx = NULL;\n}\n\n/* remove all servers information */\nvoid _dns_client_server_remove_all(void)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct dns_server_info *tmp = NULL;\n\tLIST_HEAD(free_list);\n\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list)\n\t{\n\t\tlist_add(&server_info->check_list, &free_list);\n\t\tdns_client_server_info_get(server_info);\n\t}\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\tlist_for_each_entry_safe(server_info, tmp, &free_list, check_list)\n\t{\n\t\tlist_del_init(&server_info->check_list);\n\t\t_dns_client_server_info_remove(server_info);\n\t\tdns_client_server_info_release(server_info);\n\t}\n}\n\n/* remove single server */\nstatic int _dns_client_server_remove(const char *server_ip, int port, dns_server_type_t server_type)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct dns_server_info *tmp = NULL;\n\tLIST_HEAD(free_list);\n\n\t/* find server and remove */\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list)\n\t{\n\t\tif (server_info->port != port || server_info->type != server_type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(server_info->ip, server_ip, DNS_HOSTNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlist_add(&server_info->check_list, &free_list);\n\t\tdns_client_server_info_get(server_info);\n\t\treturn 0;\n\t}\n\tpthread_mutex_unlock(&client.server_list_lock);\n\n\tlist_for_each_entry_safe(server_info, tmp, &free_list, check_list)\n\t{\n\t\tlist_del_init(&server_info->check_list);\n\t\t_dns_client_remove_server_from_groups(server_info);\n\t\t_dns_client_server_info_remove(server_info);\n\t\tdns_client_server_info_release(server_info);\n\t}\n\n\treturn -1;\n}\n\nvoid _dns_client_check_servers(void)\n{\n\tstruct dns_server_info *server_info = NULL;\n\tstruct dns_server_info *tmp = NULL;\n\tstatic unsigned int second_count = 0;\n\n\tsecond_count++;\n\tif (second_count % 10 != 0) {\n\t\treturn;\n\t}\n\n\tpthread_mutex_lock(&client.server_list_lock);\n\tlist_for_each_entry_safe(server_info, tmp, &client.dns_server_list, list)\n\t{\n\t\tdns_stats_server_stats_avg_time_update(&server_info->stats);\n\t\tif (server_info->type != DNS_SERVER_UDP) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (server_info->last_send - 600 > server_info->last_recv) {\n\t\t\tserver_info->recv_buff.len = 0;\n\t\t\tserver_info->send_buff.len = 0;\n\t\t\ttlog(TLOG_DEBUG, \"server %s may failure.\", server_info->ip);\n\t\t\t_dns_client_close_socket(server_info);\n\t\t}\n\t}\n\tpthread_mutex_unlock(&client.server_list_lock);\n}\n\nint dns_client_add_server(const char *server_ip, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t  struct client_dns_server_flags *flags)\n{\n\treturn _dns_client_add_server_pending(server_ip, NULL, port, server_type, flags, 1);\n}\n\nint dns_client_remove_server(const char *server_ip, int port, dns_server_type_t server_type)\n{\n\treturn _dns_client_server_remove(server_ip, port, server_type);\n}\n\nint dns_server_num(void)\n{\n\treturn atomic_read(&client.dns_server_num);\n}\n\nint dns_server_alive_num(void)\n{\n\treturn atomic_read(&client.dns_server_num) - atomic_read(&client.dns_server_prohibit_num);\n}\n"
  },
  {
    "path": "src/dns_client/server_info.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_SERVER_INFO_\n#define _DNS_CLIENT_SERVER_INFO_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_server_inc_prohibit_server_num(struct dns_server_info *server_info);\n\nvoid _dns_server_dec_prohibit_server_num(struct dns_server_info *server_info);\n\nvoid _dns_client_server_close(struct dns_server_info *server_info);\n\nconst char *_dns_server_get_type_string(dns_server_type_t type);\n\nvoid _dns_client_server_remove_all(void);\n\nstruct dns_server_info *_dns_client_get_server(const char *server_ip, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t\t\t\t\t\t   const struct client_dns_server_flags *flags);\n\nint _dns_client_server_add(const char *server_ip, const char *server_host, int port, dns_server_type_t server_type,\n\t\t\t\t\t\t   struct client_dns_server_flags *flags);\n\nvoid _dns_client_check_servers(void);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_client/wake_event.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"wake_event.h\"\n\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n\nvoid _dns_client_close_wakeup_event(void)\n{\n\tif (client.fd_wakeup > 0) {\n\t\tclose(client.fd_wakeup);\n\t\tclient.fd_wakeup = -1;\n\t}\n}\n\nvoid _dns_client_clear_wakeup_event(void)\n{\n\tuint64_t val = 0;\n\tint unused __attribute__((unused));\n\n\tif (client.fd_wakeup <= 0) {\n\t\treturn;\n\t}\n\n\tunused = read(client.fd_wakeup, &val, sizeof(val));\n}\n\nvoid _dns_client_do_wakeup_event(void)\n{\n\tuint64_t val = 1;\n\tint unused __attribute__((unused));\n\tif (client.fd_wakeup <= 0) {\n\t\treturn;\n\t}\n\n\tunused = write(client.fd_wakeup, &val, sizeof(val));\n}\n\nint _dns_client_create_wakeup_event(void)\n{\n\tint fd_wakeup = -1;\n\n\tfd_wakeup = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);\n\tif (fd_wakeup < 0) {\n\t\ttlog(TLOG_ERROR, \"create eventfd failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tstruct epoll_event event;\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN;\n\tevent.data.fd = fd_wakeup;\n\tif (epoll_ctl(client.epoll_fd, EPOLL_CTL_ADD, fd_wakeup, &event) < 0) {\n\t\ttlog(TLOG_ERROR, \"add eventfd to epoll failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\treturn fd_wakeup;\n\nerrout:\n\tif (fd_wakeup > 0) {\n\t\tclose(fd_wakeup);\n\t}\n\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_client/wake_event.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CLIENT_WAKE_EVENT_\n#define _DNS_CLIENT_WAKE_EVENT_\n\n#include \"dns_client.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_client_do_wakeup_event(void);\n\nint _dns_client_create_wakeup_event(void);\n\nvoid _dns_client_close_wakeup_event(void);\n\nvoid _dns_client_clear_wakeup_event(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/address.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"address.h\"\n#include \"domain_rule.h\"\n#include \"get_domain.h\"\n#include \"ptr.h\"\n#include \"smartdns/util.h\"\n\n#define TMP_BUFF_LEN 1024\n\nint _conf_domain_rule_address(char *domain, const char *domain_address)\n{\n\tstruct dns_rule_address_IPV4 *address_ipv4 = NULL;\n\tstruct dns_rule_address_IPV6 *address_ipv6 = NULL;\n\tstruct dns_rule *address = NULL;\n\n\tchar ip[MAX_IP_LEN];\n\tint port = 0;\n\tstruct sockaddr_storage addr;\n\tsocklen_t addr_len = sizeof(addr);\n\tunsigned int flag = 0;\n\tchar *ptr = NULL;\n\tchar *field = NULL;\n\tchar tmpbuff[TMP_BUFF_LEN] = {0};\n\n\tchar ipv6_addr[DNS_MAX_REPLY_IP_NUM][DNS_RR_AAAA_LEN];\n\tint ipv6_num = 0;\n\tchar ipv4_addr[DNS_MAX_REPLY_IP_NUM][DNS_RR_A_LEN];\n\tint ipv4_num = 0;\n\n\tsafe_strncpy(tmpbuff, domain_address, sizeof(tmpbuff));\n\n\tptr = tmpbuff;\n\n\tdo {\n\t\tfield = ptr;\n\t\tif (field == NULL || *field == '\\0') {\n\t\t\tbreak;\n\t\t}\n\n\t\tptr = strstr(ptr, \",\");\n\t\tif (ptr) {\n\t\t\t*ptr = 0;\n\t\t}\n\n\t\tif (*(field) == '#') {\n\t\t\tif (strncmp(field, \"#4\", sizeof(\"#4\")) == 0) {\n\t\t\t\tflag = DOMAIN_FLAG_ADDR_IPV4_SOA;\n\t\t\t} else if (strncmp(field, \"#6\", sizeof(\"#6\")) == 0) {\n\t\t\t\tflag = DOMAIN_FLAG_ADDR_IPV6_SOA;\n\t\t\t} else if (strncmp(field, \"#\", sizeof(\"#\")) == 0) {\n\t\t\t\tflag = DOMAIN_FLAG_ADDR_SOA;\n\t\t\t} else {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\t/* add SOA rule */\n\t\t\tif (_config_domain_rule_flag_set(domain, flag, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (ptr) {\n\t\t\t\tptr++;\n\t\t\t}\n\t\t\tcontinue;\n\t\t} else if (*(field) == '-') {\n\t\t\tif (strncmp(field, \"-4\", sizeof(\"-4\")) == 0) {\n\t\t\t\tflag = DOMAIN_FLAG_ADDR_IPV4_IGN;\n\t\t\t} else if (strncmp(field, \"-6\", sizeof(\"-6\")) == 0) {\n\t\t\t\tflag = DOMAIN_FLAG_ADDR_IPV6_IGN;\n\t\t\t} else if (strncmp(field, \"-\", sizeof(\"-\")) == 0) {\n\t\t\t\tflag = DOMAIN_FLAG_ADDR_IGN;\n\t\t\t} else {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\t/* ignore rule */\n\t\t\tif (_config_domain_rule_flag_set(domain, flag, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (ptr) {\n\t\t\t\tptr++;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* set address to domain */\n\t\tif (parse_ip(field, ip, &port) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\taddr_len = sizeof(addr);\n\t\tif (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tswitch (addr.ss_family) {\n\t\tcase AF_INET: {\n\t\t\tstruct sockaddr_in *addr_in = NULL;\n\t\t\taddr_in = (struct sockaddr_in *)&addr;\n\t\t\tif (ipv4_num < DNS_MAX_REPLY_IP_NUM) {\n\t\t\t\tmemcpy(ipv4_addr[ipv4_num], &addr_in->sin_addr.s_addr, DNS_RR_A_LEN);\n\t\t\t\tipv4_num++;\n\t\t\t}\n\t\t} break;\n\t\tcase AF_INET6: {\n\t\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\t\taddr_in6 = (struct sockaddr_in6 *)&addr;\n\t\t\tif (ipv6_num < DNS_MAX_REPLY_IP_NUM) {\n\t\t\t\tmemcpy(ipv6_addr[ipv6_num], addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN);\n\t\t\t\tipv6_num++;\n\t\t\t}\n\t\t} break;\n\t\tdefault:\n\t\t\tip[0] = '\\0';\n\t\t\tbreak;\n\t\t}\n\n\t\t/* add PTR */\n\t\tif (dns_conf.expand_ptr_from_address == 1 && ip[0] != '\\0' && _conf_ptr_add(domain, ip, 0) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (ptr) {\n\t\t\tptr++;\n\t\t}\n\t} while (ptr);\n\n\tif (ipv4_num > 0) {\n\t\taddress_ipv4 = _new_dns_rule_ext(DOMAIN_RULE_ADDRESS_IPV4, ipv4_num * DNS_RR_A_LEN);\n\t\tif (address_ipv4 == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tmemcpy(address_ipv4->ipv4_addr, ipv4_addr[0], ipv4_num * DNS_RR_A_LEN);\n\t\taddress_ipv4->addr_num = ipv4_num;\n\t\taddress = (struct dns_rule *)address_ipv4;\n\n\t\tif (_config_domain_rule_add(domain, DOMAIN_RULE_ADDRESS_IPV4, address) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\t_dns_rule_put(address);\n\t}\n\n\tif (ipv6_num > 0) {\n\t\taddress_ipv6 = _new_dns_rule_ext(DOMAIN_RULE_ADDRESS_IPV6, ipv6_num * DNS_RR_AAAA_LEN);\n\t\tif (address_ipv6 == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tmemcpy(address_ipv6->ipv6_addr, ipv6_addr[0], ipv6_num * DNS_RR_AAAA_LEN);\n\t\taddress_ipv6->addr_num = ipv6_num;\n\t\taddress = (struct dns_rule *)address_ipv6;\n\n\t\tif (_config_domain_rule_add(domain, DOMAIN_RULE_ADDRESS_IPV6, address) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\t_dns_rule_put(address);\n\t}\n\n\treturn 0;\nerrout:\n\tif (address) {\n\t\t_dns_rule_put(address);\n\t}\n\n\ttlog(TLOG_ERROR, \"add address %s, %s at %s:%d failed\", domain, domain_address, conf_get_conf_file(),\n\t\t conf_get_current_lineno());\n\treturn 0;\n}\n\nint _config_address(void *data, int argc, char *argv[])\n{\n\tchar *value = argv[1];\n\tchar domain[DNS_MAX_CONF_CNAME_LEN];\n\n\tif (argc <= 1) {\n\t\tgoto errout;\n\t}\n\n\tif (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn _conf_domain_rule_address(domain, value);\nerrout:\n\ttlog(TLOG_ERROR, \"add address %s failed\", value);\n\treturn 0;\n}"
  },
  {
    "path": "src/dns_conf/address.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_ADDRESS_H_\n#define _DNS_CONF_ADDRESS_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_address(void *data, int argc, char *argv[]);\n\nint _conf_domain_rule_address(char *domain, const char *domain_address);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/bind.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"bind.h\"\n#include \"dns_conf_group.h\"\n#include \"domain_rule.h\"\n#include \"ipset.h\"\n#include \"nftset.h\"\n#include \"server_group.h\"\n#include \"smartdns/util.h\"\n\n#include <getopt.h>\n\nstatic int _config_bind_ip_parser_nftset(struct dns_bind_ip *bind_ip, unsigned int *server_flag, const char *nftsetname)\n{\n\tstruct dns_nftset_rule *nftset_rule = NULL;\n\tstruct dns_nftset_rule **bind_nftset_rule = NULL;\n\tconst struct dns_nftset_name *nftset_name = NULL;\n\tenum domain_rule type = DOMAIN_RULE_MAX;\n\n\tchar *setname = NULL;\n\tchar *tablename = NULL;\n\tchar *family = NULL;\n\tchar copied_name[DNS_MAX_NFTSET_NAMELEN + 1];\n\n\tsafe_strncpy(copied_name, nftsetname, DNS_MAX_NFTSET_NAMELEN);\n\tfor (char *tok = strtok(copied_name, \",\"); tok; tok = strtok(NULL, \",\")) {\n\t\tchar *saveptr = NULL;\n\t\tchar *tok_set = NULL;\n\n\t\tif (strncmp(tok, \"#4:\", 3U) == 0) {\n\t\t\tbind_nftset_rule = &bind_ip->nftset_ipset_rule.nftset_ip;\n\t\t\ttype = DOMAIN_RULE_NFTSET_IP;\n\t\t} else if (strncmp(tok, \"#6:\", 3U) == 0) {\n\t\t\tbind_nftset_rule = &bind_ip->nftset_ipset_rule.nftset_ip6;\n\t\t\ttype = DOMAIN_RULE_NFTSET_IP6;\n\t\t} else if (strncmp(tok, \"-\", 2U) == 0) {\n\t\t\tcontinue;\n\t\t} else {\n\t\t\treturn -1;\n\t\t}\n\n\t\ttok_set = tok + 3;\n\n\t\tif (strncmp(tok_set, \"-\", 2U) == 0) {\n\t\t\t*server_flag |= BIND_FLAG_NO_RULE_NFTSET;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfamily = strtok_r(tok_set, \"#\", &saveptr);\n\t\tif (family == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\ttablename = strtok_r(NULL, \"#\", &saveptr);\n\t\tif (tablename == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tsetname = strtok_r(NULL, \"#\", &saveptr);\n\t\tif (setname == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\t/* new nftset domain */\n\t\tnftset_name = _dns_conf_get_nftable(family, tablename, setname);\n\t\tif (nftset_name == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tnftset_rule = _new_dns_rule(type);\n\t\tif (nftset_rule == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tnftset_rule->nfttablename = nftset_name->nfttablename;\n\t\tnftset_rule->nftsetname = nftset_name->nftsetname;\n\t\tnftset_rule->familyname = nftset_name->nftfamilyname;\n\t\t/* reference is 1 here */\n\t\t*bind_nftset_rule = nftset_rule;\n\n\t\tnftset_rule = NULL;\n\t}\n\n\treturn 0;\n}\n\nstatic int _config_bind_ip_parser_ipset(struct dns_bind_ip *bind_ip, unsigned int *server_flag, const char *ipsetname)\n{\n\tstruct dns_ipset_rule **bind_ipset_rule = NULL;\n\tstruct dns_ipset_rule *ipset_rule = NULL;\n\tconst char *ipset = NULL;\n\tenum domain_rule type = DOMAIN_RULE_MAX;\n\n\tchar copied_name[DNS_MAX_NFTSET_NAMELEN + 1];\n\n\tsafe_strncpy(copied_name, ipsetname, DNS_MAX_NFTSET_NAMELEN);\n\n\tfor (char *tok = strtok(copied_name, \",\"); tok; tok = strtok(NULL, \",\")) {\n\t\tif (tok[0] == '#') {\n\t\t\tif (strncmp(tok, \"#6:\", 3U) == 0) {\n\t\t\t\tbind_ipset_rule = &bind_ip->nftset_ipset_rule.ipset_ip6;\n\t\t\t\ttype = DOMAIN_RULE_IPSET_IPV6;\n\t\t\t} else if (strncmp(tok, \"#4:\", 3U) == 0) {\n\t\t\t\tbind_ipset_rule = &bind_ip->nftset_ipset_rule.ipset_ip;\n\t\t\t\ttype = DOMAIN_RULE_IPSET_IPV4;\n\t\t\t} else {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\ttok += 3;\n\t\t} else {\n\t\t\ttype = DOMAIN_RULE_IPSET;\n\t\t\tbind_ipset_rule = &bind_ip->nftset_ipset_rule.ipset;\n\t\t}\n\n\t\tif (strncmp(tok, \"-\", 1) == 0) {\n\t\t\t*server_flag |= BIND_FLAG_NO_RULE_IPSET;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (bind_ipset_rule == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* new ipset domain */\n\t\tipset = _dns_conf_get_ipset(tok);\n\t\tif (ipset == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tipset_rule = _new_dns_rule(type);\n\t\tif (ipset_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tipset_rule->ipsetname = ipset;\n\t\t/* reference is 1 here */\n\t\t*bind_ipset_rule = ipset_rule;\n\t\tipset_rule = NULL;\n\t}\n\n\treturn 0;\nerrout:\n\tif (ipset_rule) {\n\t\t_dns_rule_put(&ipset_rule->head);\n\t}\n\n\treturn -1;\n}\n\nstatic int _bind_is_ip_valid(const char *ip)\n{\n\tstruct sockaddr_storage addr;\n\tsocklen_t addr_len = sizeof(addr);\n\tchar ip_check[MAX_IP_LEN];\n\tint port_check = -1;\n\n\tif (parse_ip(ip, ip_check, &port_check) != 0) {\n\t\tif (port_check != -1 && ip_check[0] == '\\0') {\n\t\t\treturn 0;\n\t\t}\n\t\treturn -1;\n\t}\n\n\tif (getaddr_by_host(ip_check, (struct sockaddr *)&addr, &addr_len) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _config_bind_ip(int argc, char *argv[], DNS_BIND_TYPE type)\n{\n\tint index = dns_conf.bind_ip_num;\n\tstruct dns_bind_ip *bind_ip = NULL;\n\tchar *ip = NULL;\n\tint opt = 0;\n\tint optind_last = 0;\n\tchar group_name[DNS_GROUP_NAME_LEN];\n\tconst char *group = NULL;\n\tunsigned int server_flag = 0;\n\tint i = 0;\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"group\", required_argument, NULL, 'g'}, /* add to group */\n\t\t{\"no-rule-addr\", no_argument, NULL, 'A'},   \n\t\t{\"no-rule-nameserver\", no_argument, NULL, 'N'},   \n\t\t{\"no-rule-ipset\", no_argument, NULL, 'I'},   \n\t\t{\"no-rule-sni-proxy\", no_argument, NULL, 'P'},   \n\t\t{\"no-rule-soa\", no_argument, NULL, 'O'},\n\t\t{\"no-speed-check\", no_argument, NULL, 'S'},  \n\t\t{\"no-cache\", no_argument, NULL, 'C'},  \n\t\t{\"no-dualstack-selection\", no_argument, NULL, 'D'},\n\t\t{\"no-ip-alias\", no_argument, NULL, 'a'},\n\t\t{\"force-aaaa-soa\", no_argument, NULL, 'F'},\n\t\t{\"acl\", no_argument, NULL, 251},\n\t\t{\"no-rules\", no_argument, NULL, 252},\n\t\t{\"no-serve-expired\", no_argument, NULL, 253},\n\t\t{\"force-https-soa\", no_argument, NULL, 254},\n\t\t{\"ipset\", required_argument, NULL, 255},\n\t\t{\"nftset\", required_argument, NULL, 256},\n\t\t{\"alpn\", required_argument, NULL, 257},\n\t\t{\"ddr\", no_argument, NULL, 258},\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"bind: invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tip = argv[1];\n\tif (index >= DNS_MAX_BIND_IP) {\n\t\ttlog(TLOG_WARN, \"exceeds max server number, %s\", ip);\n\t\treturn 0;\n\t}\n\n\tif (_bind_is_ip_valid(ip) != 0) {\n\t\ttlog(TLOG_ERROR, \"bind ip address invalid: %s\", ip);\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < dns_conf.bind_ip_num; i++) {\n\t\tbind_ip = &dns_conf.bind_ip[i];\n\t\tif (bind_ip->type != type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(bind_ip->ip, ip, DNS_MAX_IPLEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttlog(TLOG_WARN, \"bind server %s, type %d, already configured, skip.\", ip, type);\n\t\treturn 0;\n\t}\n\n\tbind_ip = &dns_conf.bind_ip[index];\n\tbind_ip->type = type;\n\tbind_ip->flags = 0;\n\tsafe_strncpy(bind_ip->ip, ip, DNS_MAX_IPLEN);\n\t/* get current group */\n\tif (_config_current_group()) {\n\t\tgroup = _config_current_group()->group_name;\n\t}\n\n\t/* process extra options */\n\toptind = 1;\n\toptind_last = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'g': {\n\t\t\tsafe_strncpy(group_name, optarg, DNS_GROUP_NAME_LEN);\n\t\t\tgroup = _dns_conf_get_group_name(group_name);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'A': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_ADDR;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'a': {\n\t\t\tserver_flag |= BIND_FLAG_NO_IP_ALIAS;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'N': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_NAMESERVER;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'I': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_IPSET;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'P': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_SNIPROXY;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'S': {\n\t\t\tserver_flag |= BIND_FLAG_NO_SPEED_CHECK;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'C': {\n\t\t\tserver_flag |= BIND_FLAG_NO_CACHE;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'O': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_SOA;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'D': {\n\t\t\tserver_flag |= BIND_FLAG_NO_DUALSTACK_SELECTION;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'F': {\n\t\t\tserver_flag |= BIND_FLAG_FORCE_AAAA_SOA;\n\t\t\tbreak;\n\t\t}\n\t\tcase 251: {\n\t\t\tserver_flag |= BIND_FLAG_ACL;\n\t\t\tbreak;\n\t\t}\n\t\tcase 252: {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULES;\n\t\t\tbreak;\n\t\t}\n\t\tcase 253: {\n\t\t\tserver_flag |= BIND_FLAG_NO_SERVE_EXPIRED;\n\t\t\tbreak;\n\t\t}\n\t\tcase 254: {\n\t\t\tserver_flag |= BIND_FLAG_FORCE_HTTPS_SOA;\n\t\t\tbreak;\n\t\t}\n\t\tcase 255: {\n\t\t\t_config_bind_ip_parser_ipset(bind_ip, &server_flag, optarg);\n\t\t\tserver_flag |= BIND_FLAG_NO_DUALSTACK_SELECTION;\n\t\t\tserver_flag |= BIND_FLAG_NO_PREFETCH;\n\t\t\tserver_flag |= BIND_FLAG_NO_SERVE_EXPIRED;\n\t\t\tbreak;\n\t\t}\n\t\tcase 256: {\n\t\t\t_config_bind_ip_parser_nftset(bind_ip, &server_flag, optarg);\n\t\t\tserver_flag |= BIND_FLAG_NO_DUALSTACK_SELECTION;\n\t\t\tserver_flag |= BIND_FLAG_NO_PREFETCH;\n\t\t\tserver_flag |= BIND_FLAG_NO_SERVE_EXPIRED;\n\t\t\tbreak;\n\t\t}\n\t\tcase 257: {\n\t\t\tsafe_strncpy(bind_ip->alpn, optarg, DNS_MAX_ALPN_LEN);\n\t\t\tbreak;\n\t\t}\n\t\tcase 258: {\n\t\t\tserver_flag |= BIND_FLAG_DDR;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tif (optind > optind_last) {\n\t\t\t\ttlog(TLOG_WARN, \"unknown bind option: %s at '%s:%d'.\", argv[optind - 1], conf_get_conf_file(),\n\t\t\t\t\t conf_get_current_lineno());\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toptind_last = optind;\n\t}\n\n\t/* add new server */\n\tbind_ip->flags = server_flag;\n\tbind_ip->group = group;\n\tdns_conf.bind_ip_num++;\n\tif (bind_ip->type == DNS_BIND_TYPE_TLS || bind_ip->type == DNS_BIND_TYPE_HTTPS) {\n\t\tif (bind_ip->ssl_cert_file == NULL || bind_ip->ssl_cert_key_file == NULL) {\n\t\t\tbind_ip->ssl_cert_file = dns_conf.bind_ca_file;\n\t\t\tbind_ip->ssl_cert_key_file = dns_conf.bind_ca_key_file;\n\t\t\tbind_ip->ssl_cert_key_pass = dns_conf.bind_ca_key_pass;\n\t\t\tdns_conf.need_cert = 1;\n\t\t}\n\t}\n\ttlog(TLOG_DEBUG, \"bind ip %s, type: %d, flag: %X\", ip, type, server_flag);\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nvoid dns_server_bind_destroy(void)\n{\n\tfor (int i = 0; i < dns_conf.bind_ip_num; i++) {\n\t\tstruct dns_bind_ip *bind_ip = &dns_conf.bind_ip[i];\n\n\t\tif (bind_ip->nftset_ipset_rule.ipset) {\n\t\t\t_dns_rule_put(&bind_ip->nftset_ipset_rule.ipset->head);\n\t\t}\n\n\t\tif (bind_ip->nftset_ipset_rule.ipset_ip) {\n\t\t\t_dns_rule_put(&bind_ip->nftset_ipset_rule.ipset_ip->head);\n\t\t}\n\n\t\tif (bind_ip->nftset_ipset_rule.ipset_ip6) {\n\t\t\t_dns_rule_put(&bind_ip->nftset_ipset_rule.ipset_ip6->head);\n\t\t}\n\n\t\tif (bind_ip->nftset_ipset_rule.nftset_ip) {\n\t\t\t_dns_rule_put(&bind_ip->nftset_ipset_rule.nftset_ip->head);\n\t\t}\n\n\t\tif (bind_ip->nftset_ipset_rule.nftset_ip6) {\n\t\t\t_dns_rule_put(&bind_ip->nftset_ipset_rule.nftset_ip6->head);\n\t\t}\n\t}\n\tmemset(dns_conf.bind_ip, 0, sizeof(dns_conf.bind_ip));\n\tdns_conf.bind_ip_num = 0;\n}\n\nint _config_add_default_server_if_needed(void)\n{\n\tif (dns_conf.bind_ip_num > 0) {\n\t\treturn 0;\n\t}\n\n\t/* add default server */\n\tchar *argv[] = {\"bind\", \"[::]:53\", NULL};\n\tint argc = sizeof(argv) / sizeof(char *) - 1;\n\treturn _config_bind_ip(argc, argv, DNS_BIND_TYPE_UDP);\n}\n\nint _config_bind_ip_udp(void *data, int argc, char *argv[])\n{\n\treturn _config_bind_ip(argc, argv, DNS_BIND_TYPE_UDP);\n}\n\nint _config_bind_ip_tcp(void *data, int argc, char *argv[])\n{\n\treturn _config_bind_ip(argc, argv, DNS_BIND_TYPE_TCP);\n}\n\nint _config_bind_ip_tls(void *data, int argc, char *argv[])\n{\n\treturn _config_bind_ip(argc, argv, DNS_BIND_TYPE_TLS);\n}\n\nint _config_bind_ip_https(void *data, int argc, char *argv[])\n{\n\treturn _config_bind_ip(argc, argv, DNS_BIND_TYPE_HTTPS);\n}\n"
  },
  {
    "path": "src/dns_conf/bind.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_BIND_H_\n#define _DNS_CONF_BIND_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_add_default_server_if_needed(void);\nint _config_bind_ip_udp(void *data, int argc, char *argv[]);\nint _config_bind_ip_tcp(void *data, int argc, char *argv[]);\nint _config_bind_ip_tls(void *data, int argc, char *argv[]);\nint _config_bind_ip_https(void *data, int argc, char *argv[]);\n\nvoid dns_server_bind_destroy(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/bootstrap_dns.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"bootstrap_dns.h\"\n#include \"domain_rule.h\"\n#include \"nameserver.h\"\n#include \"smartdns/util.h\"\n\nchar dns_conf_exist_bootstrap_dns;\n\nint _config_update_bootstrap_dns_rule(void)\n{\n\tstruct dns_servers *server = NULL;\n\n\tif (dns_conf_exist_bootstrap_dns == 0) {\n\t\treturn 0;\n\t}\n\n\tfor (int i = 0; i < dns_conf.server_num; i++) {\n\t\tserver = &dns_conf.servers[i];\n\t\tif (check_is_ipaddr(server->server) == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_conf_domain_rule_nameserver(server->server, \"bootstrap-dns\");\n\t}\n\n\treturn 0;\n}"
  },
  {
    "path": "src/dns_conf/bootstrap_dns.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_BOOTSTRAP_H_\n#define _DNS_CONF_BOOTSTRAP_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_update_bootstrap_dns_rule(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/client_rule.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client_rule.h\"\n#include \"dns_conf_group.h\"\n#include \"ip_rule.h\"\n#include \"server_group.h\"\n#include \"set_file.h\"\n#include \"smartdns/util.h\"\n\n#include <getopt.h>\n\nstatic radix_node_t *_create_client_rules_node(const char *addr)\n{\n\tradix_node_t *node = NULL;\n\tvoid *p = NULL;\n\tprefix_t prefix;\n\tconst char *errmsg = NULL;\n\n\tp = prefix_pton(addr, -1, &prefix, &errmsg);\n\tif (p == NULL) {\n\t\treturn NULL;\n\t}\n\n\tnode = radix_lookup(dns_conf.client_rule.rule, &prefix);\n\treturn node;\n}\n\nstatic void *_new_dns_client_rule_ext(enum client_rule client_rule, int ext_size)\n{\n\tstruct dns_client_rule *rule;\n\tint size = 0;\n\n\tif (client_rule >= CLIENT_RULE_MAX) {\n\t\treturn NULL;\n\t}\n\n\tswitch (client_rule) {\n\tcase CLIENT_RULE_FLAGS:\n\t\tsize = sizeof(struct client_rule_flags);\n\t\tbreak;\n\tcase CLIENT_RULE_GROUP:\n\t\tsize = sizeof(struct client_rule_group);\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t}\n\n\tsize += ext_size;\n\trule = zalloc(1, size);\n\tif (!rule) {\n\t\treturn NULL;\n\t}\n\trule->rule = client_rule;\n\tatomic_set(&rule->refcnt, 1);\n\treturn rule;\n}\n\nstatic void *_new_dns_client_rule(enum client_rule client_rule)\n{\n\treturn _new_dns_client_rule_ext(client_rule, 0);\n}\n\nstatic void _dns_client_rule_get(struct dns_client_rule *rule)\n{\n\tatomic_inc(&rule->refcnt);\n}\n\nstatic void _dns_client_rule_put(struct dns_client_rule *rule)\n{\n\tint refcount = atomic_dec_return(&rule->refcnt);\n\tif (refcount > 0) {\n\t\treturn;\n\t}\n\n\tfree(rule);\n}\n\nstatic int _config_client_rules_free(struct dns_client_rules *client_rules)\n{\n\tint i = 0;\n\n\tif (client_rules == NULL) {\n\t\treturn 0;\n\t}\n\n\tfor (i = 0; i < CLIENT_RULE_MAX; i++) {\n\t\tif (client_rules->rules[i] == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_dns_client_rule_put(client_rules->rules[i]);\n\t\tclient_rules->rules[i] = NULL;\n\t}\n\n\tfree(client_rules);\n\treturn 0;\n}\n\nstatic struct client_roue_group_mac *_config_client_rule_group_mac_new(uint8_t mac[6])\n{\n\tstruct client_roue_group_mac *group_mac = NULL;\n\tuint32_t key;\n\n\tgroup_mac = zalloc(1, sizeof(*group_mac));\n\tif (group_mac == NULL) {\n\t\treturn NULL;\n\t}\n\tmemcpy(group_mac->mac, mac, 6);\n\n\tkey = jhash(mac, 6, 0);\n\thash_add(dns_conf.client_rule.mac, &group_mac->node, key);\n\tdns_conf.client_rule.mac_num++;\n\n\treturn group_mac;\n}\n\nstruct client_roue_group_mac *dns_server_rule_group_mac_get(const uint8_t mac[6])\n{\n\tstruct client_roue_group_mac *group_mac = NULL;\n\tuint32_t key;\n\n\tkey = jhash(mac, 6, 0);\n\thash_for_each_possible(dns_conf.client_rule.mac, group_mac, node, key)\n\t{\n\t\tif (memcmp(group_mac->mac, mac, 6) == 0) {\n\t\t\treturn group_mac;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic struct client_roue_group_mac *_config_client_rule_group_mac_get_or_add(uint8_t mac[6])\n{\n\tstruct client_roue_group_mac *group_mac = dns_server_rule_group_mac_get(mac);\n\tif (group_mac == NULL) {\n\t\tgroup_mac = _config_client_rule_group_mac_new(mac);\n\t}\n\n\treturn group_mac;\n}\n\nstatic int _config_client_rule_add(const char *ip_cidr, enum client_rule type, void *rule);\nstatic int _config_client_rule_add_callback(const char *ip_cidr, void *priv)\n{\n\tstruct dns_set_rule_add_callback_args *args = (struct dns_set_rule_add_callback_args *)priv;\n\treturn _config_client_rule_add(ip_cidr, args->type, args->rule);\n}\n\nstatic int _config_client_rule_add(const char *ip_cidr, enum client_rule type, void *rule)\n{\n\tstruct dns_client_rules *client_rules = NULL;\n\tstruct dns_client_rules *add_client_rules = NULL;\n\tstruct client_roue_group_mac *group_mac = NULL;\n\tradix_node_t *node = NULL;\n\n\tif (ip_cidr == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (type >= CLIENT_RULE_MAX) {\n\t\tgoto errout;\n\t}\n\n\tuint8_t mac[6];\n\tint is_mac_address = 0;\n\n\tis_mac_address = parser_mac_address(ip_cidr, mac);\n\tif (is_mac_address == 0) {\n\t\tgroup_mac = _config_client_rule_group_mac_get_or_add(mac);\n\t\tif (group_mac == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"get or add mac %s failed\", ip_cidr);\n\t\t\tgoto errout;\n\t\t}\n\n\t\tclient_rules = group_mac->rules;\n\t} else {\n\t\tif (strncmp(ip_cidr, \"ip-set:\", sizeof(\"ip-set:\") - 1) == 0) {\n\t\t\tstruct dns_set_rule_add_callback_args args;\n\t\t\targs.type = type;\n\t\t\targs.rule = rule;\n\t\t\treturn _config_ip_rule_set_each(ip_cidr + sizeof(\"ip-set:\") - 1, _config_client_rule_add_callback, &args);\n\t\t}\n\n\t\t/* Get existing or create domain rule */\n\t\tnode = _create_client_rules_node(ip_cidr);\n\t\tif (node == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"create addr node failed.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tclient_rules = node->data;\n\t}\n\n\tif (client_rules == NULL) {\n\t\tadd_client_rules = zalloc(1, sizeof(*add_client_rules));\n\t\tif (add_client_rules == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\tclient_rules = add_client_rules;\n\t\tif (is_mac_address == 0) {\n\t\t\tgroup_mac->rules = client_rules;\n\t\t} else {\n\t\t\tnode->data = client_rules;\n\t\t}\n\t}\n\n\t/* add new rule to domain */\n\tif (client_rules->rules[type]) {\n\t\t_dns_client_rule_put(client_rules->rules[type]);\n\t\tclient_rules->rules[type] = NULL;\n\t}\n\n\tclient_rules->rules[type] = rule;\n\t_dns_client_rule_get(rule);\n\n\treturn 0;\nerrout:\n\tif (add_client_rules) {\n\t\tfree(add_client_rules);\n\t}\n\n\ttlog(TLOG_ERROR, \"add client %s rule failed\", ip_cidr);\n\treturn -1;\n}\n\nstatic int _config_client_rule_flag_callback(const char *ip_cidr, void *priv)\n{\n\tstruct dns_set_rule_flags_callback_args *args = (struct dns_set_rule_flags_callback_args *)priv;\n\treturn _config_client_rule_flag_set(ip_cidr, args->flags, args->is_clear_flag);\n}\n\nint _config_client_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear)\n{\n\tstruct dns_client_rules *client_rules = NULL;\n\tstruct dns_client_rules *add_client_rules = NULL;\n\tstruct client_rule_flags *client_rule_flags = NULL;\n\tstruct client_roue_group_mac *group_mac = NULL;\n\tradix_node_t *node = NULL;\n\tuint8_t mac[6];\n\tint is_mac_address = 0;\n\n\tis_mac_address = parser_mac_address(ip_cidr, mac);\n\tif (is_mac_address == 0) {\n\t\tgroup_mac = _config_client_rule_group_mac_get_or_add(mac);\n\t\tif (group_mac == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"get or add mac %s failed\", ip_cidr);\n\t\t\tgoto errout;\n\t\t}\n\n\t\tclient_rules = group_mac->rules;\n\t} else {\n\t\tif (strncmp(ip_cidr, \"ip-set:\", sizeof(\"ip-set:\") - 1) == 0) {\n\t\t\tstruct dns_set_rule_flags_callback_args args;\n\t\t\targs.flags = flag;\n\t\t\targs.is_clear_flag = is_clear;\n\t\t\treturn _config_ip_rule_set_each(ip_cidr + sizeof(\"ip-set:\") - 1, _config_client_rule_flag_callback, &args);\n\t\t}\n\n\t\t/* Get existing or create domain rule */\n\t\tnode = _create_client_rules_node(ip_cidr);\n\t\tif (node == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"create addr node failed.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tclient_rules = node->data;\n\t}\n\n\tif (client_rules == NULL) {\n\t\tadd_client_rules = zalloc(1, sizeof(*add_client_rules));\n\t\tif (add_client_rules == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\tclient_rules = add_client_rules;\n\t\tif (is_mac_address == 0) {\n\t\t\tgroup_mac->rules = client_rules;\n\t\t} else {\n\t\t\tnode->data = client_rules;\n\t\t}\n\t}\n\n\t/* add new rule to domain */\n\tif (client_rules->rules[CLIENT_RULE_FLAGS] == NULL) {\n\t\tclient_rule_flags = _new_dns_client_rule(CLIENT_RULE_FLAGS);\n\t\tclient_rule_flags->flags = 0;\n\t\tclient_rules->rules[CLIENT_RULE_FLAGS] = &client_rule_flags->head;\n\t}\n\n\tclient_rule_flags = container_of(client_rules->rules[CLIENT_RULE_FLAGS], struct client_rule_flags, head);\n\tif (is_clear == false) {\n\t\tclient_rule_flags->flags |= flag;\n\t} else {\n\t\tclient_rule_flags->flags &= ~flag;\n\t}\n\tclient_rule_flags->is_flag_set |= flag;\n\n\treturn 0;\nerrout:\n\tif (add_client_rules) {\n\t\tfree(add_client_rules);\n\t}\n\n\ttlog(TLOG_ERROR, \"set ip %s flags failed\", ip_cidr);\n\n\treturn -1;\n}\n\nstatic void _config_client_rule_iter_free_cb(radix_node_t *node, void *cbctx)\n{\n\tstruct dns_client_rules *client_rules = NULL;\n\tif (node == NULL) {\n\t\treturn;\n\t}\n\n\tif (node->data == NULL) {\n\t\treturn;\n\t}\n\n\tclient_rules = node->data;\n\t_config_client_rules_free(client_rules);\n\tnode->data = NULL;\n}\n\nint _config_client_rules(void *data, int argc, char *argv[])\n{\n\tint opt = 0;\n\tint optind_last = 0;\n\tconst char *client = argv[1];\n\tunsigned int server_flag = 0;\n\tconst char *group = NULL;\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"group\", required_argument, NULL, 'g'},\n\t\t{\"no-rule-addr\", no_argument, NULL, 'A'},   \n\t\t{\"no-rule-nameserver\", no_argument, NULL, 'N'},   \n\t\t{\"no-rule-ipset\", no_argument, NULL, 'I'},   \n\t\t{\"no-rule-sni-proxy\", no_argument, NULL, 'P'},   \n\t\t{\"no-rule-soa\", no_argument, NULL, 'O'},\n\t\t{\"no-speed-check\", no_argument, NULL, 'S'},  \n\t\t{\"no-cache\", no_argument, NULL, 'C'},  \n\t\t{\"no-dualstack-selection\", no_argument, NULL, 'D'},\n\t\t{\"no-ip-alias\", no_argument, NULL, 'a'},\n\t\t{\"force-aaaa-soa\", no_argument, NULL, 'F'},\n\t\t{\"acl\", no_argument, NULL, 251},\n\t\t{\"no-rules\", no_argument, NULL, 252},\n\t\t{\"no-serve-expired\", no_argument, NULL, 253},\n\t\t{\"force-https-soa\", no_argument, NULL, 254},\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\t/* get current group */\n\tif (_config_current_group()) {\n\t\tgroup = _config_current_group()->group_name;\n\t}\n\n\t/* process extra options */\n\toptind = 1;\n\toptind_last = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"g:\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'g': {\n\t\t\tgroup = optarg;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'A': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_ADDR;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'a': {\n\t\t\tserver_flag |= BIND_FLAG_NO_IP_ALIAS;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'N': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_NAMESERVER;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'I': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_IPSET;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'P': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_SNIPROXY;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'S': {\n\t\t\tserver_flag |= BIND_FLAG_NO_SPEED_CHECK;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'C': {\n\t\t\tserver_flag |= BIND_FLAG_NO_CACHE;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'O': {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULE_SOA;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'D': {\n\t\t\tserver_flag |= BIND_FLAG_NO_DUALSTACK_SELECTION;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'F': {\n\t\t\tserver_flag |= BIND_FLAG_FORCE_AAAA_SOA;\n\t\t\tbreak;\n\t\t}\n\t\tcase 251: {\n\t\t\tserver_flag |= BIND_FLAG_ACL;\n\t\t\tbreak;\n\t\t}\n\t\tcase 252: {\n\t\t\tserver_flag |= BIND_FLAG_NO_RULES;\n\t\t\tbreak;\n\t\t}\n\t\tcase 253: {\n\t\t\tserver_flag |= BIND_FLAG_NO_SERVE_EXPIRED;\n\t\t\tbreak;\n\t\t}\n\t\tcase 254: {\n\t\t\tserver_flag |= BIND_FLAG_FORCE_HTTPS_SOA;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tif (optind > optind_last) {\n\t\t\t\ttlog(TLOG_WARN, \"unknown client-rules option: %s at '%s:%d'.\", argv[optind - 1], conf_get_conf_file(),\n\t\t\t\t\t conf_get_current_lineno());\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toptind_last = optind;\n\t}\n\n\tif (group != NULL) {\n\t\tif (_config_client_rule_group_add(client, group) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"add group rule failed.\");\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tif (_config_client_rule_flag_set(client, server_flag, 0) != 0) {\n\t\ttlog(TLOG_ERROR, \"set client rule flags failed.\");\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstatic void _config_client_rule_destroy_mac(void)\n{\n\tstruct hlist_node *tmp = NULL;\n\tunsigned int i;\n\tstruct client_roue_group_mac *group_mac = NULL;\n\n\thash_for_each_safe(dns_conf.client_rule.mac, i, tmp, group_mac, node)\n\t{\n\t\thlist_del_init(&group_mac->node);\n\t\t_config_client_rules_free(group_mac->rules);\n\t\tfree(group_mac);\n\t}\n}\n\nvoid _config_client_rule_destroy(void)\n{\n\tDestroy_Radix(dns_conf.client_rule.rule, _config_client_rule_iter_free_cb, NULL);\n\t_config_client_rule_destroy_mac();\n}\n\nint _config_client_rule_group_add(const char *client, const char *group_name)\n{\n\tstruct client_rule_group *client_rule = NULL;\n\tconst char *group = NULL;\n\n\tclient_rule = _new_dns_client_rule(CLIENT_RULE_GROUP);\n\tif (client_rule == NULL) {\n\t\tgoto errout;\n\t}\n\n\tgroup = _dns_conf_get_group_name(group_name);\n\tif (group == NULL) {\n\t\tgoto errout;\n\t}\n\n\tclient_rule->group_name = group;\n\tif (_config_client_rule_add(client, CLIENT_RULE_GROUP, client_rule) != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_client_rule_put(&client_rule->head);\n\n\treturn 0;\nerrout:\n\tif (client_rule != NULL) {\n\t\t_dns_client_rule_put(&client_rule->head);\n\t}\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_conf/client_rule.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_CLIENT_RULE_H_\n#define _DNS_CONF_CLIENT_RULE_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_client_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear);\n\nint _config_client_rule_group_add(const char *client, const char *group_name);\n\nint _config_client_rules(void *data, int argc, char *argv[]);\n\nvoid _config_client_rule_destroy(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/client_subnet.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client_subnet.h\"\n#include \"dns_conf_group.h\"\n#include \"set_file.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\nint _conf_client_subnet(char *subnet, struct dns_edns_client_subnet *ipv4_ecs, struct dns_edns_client_subnet *ipv6_ecs)\n{\n\tchar *slash = NULL;\n\tint subnet_len = 0;\n\tstruct dns_edns_client_subnet *ecs = NULL;\n\tstruct sockaddr_storage addr;\n\tsocklen_t addr_len = sizeof(addr);\n\tchar str_subnet[128];\n\n\tif (subnet == NULL) {\n\t\treturn -1;\n\t}\n\n\tsafe_strncpy(str_subnet, subnet, sizeof(str_subnet));\n\tslash = strstr(str_subnet, \"/\");\n\tif (slash) {\n\t\t*slash = 0;\n\t\tslash++;\n\t\tsubnet_len = atoi(slash);\n\t}\n\n\tif (getaddr_by_host(str_subnet, (struct sockaddr *)&addr, &addr_len) != 0) {\n\t\tgoto errout;\n\t}\n\n\tswitch (addr.ss_family) {\n\tcase AF_INET:\n\t\tif (subnet_len < 0 || subnet_len > 32) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (subnet_len == 0) {\n\t\t\tsubnet_len = 32;\n\t\t}\n\t\tecs = ipv4_ecs;\n\t\tbreak;\n\tcase AF_INET6:\n\t\tif (subnet_len < 0 || subnet_len > 128) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (subnet_len == 0) {\n\t\t\tsubnet_len = 128;\n\t\t}\n\t\tecs = ipv6_ecs;\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t}\n\n\tif (ecs == NULL) {\n\t\treturn 0;\n\t}\n\n\tsafe_strncpy(ecs->ip, str_subnet, DNS_MAX_IPLEN);\n\tecs->subnet = subnet_len;\n\tecs->enable = 1;\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nint _conf_edns_client_subnet(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\treturn -1;\n\t}\n\n\treturn _conf_client_subnet(argv[1], &_config_current_rule_group()->ipv4_ecs,\n\t\t\t\t\t\t\t   &_config_current_rule_group()->ipv6_ecs);\n}\n"
  },
  {
    "path": "src/dns_conf/client_subnet.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_CLIENT_SUBNET_H_\n#define _DNS_CONF_CLIENT_SUBNET_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _conf_edns_client_subnet(void *data, int argc, char *argv[]);\n\nint _conf_client_subnet(char *subnet, struct dns_edns_client_subnet *ipv4_ecs, struct dns_edns_client_subnet *ipv6_ecs);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/cname.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"cname.h\"\n\n#include \"domain_rule.h\"\n#include \"set_file.h\"\n#include \"smartdns/lib/stringutil.h\"\n\nint _conf_domain_rule_cname(const char *domain, const char *cname)\n{\n\tstruct dns_cname_rule *cname_rule = NULL;\n\tenum domain_rule type = DOMAIN_RULE_CNAME;\n\n\tcname_rule = _new_dns_rule(type);\n\tif (cname_rule == NULL) {\n\t\tgoto errout;\n\t}\n\n\t/* ignore this domain */\n\tif (*cname == '-') {\n\t\tif (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_CNAME_IGN, 0) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tsafe_strncpy(cname_rule->cname, cname, DNS_MAX_CONF_CNAME_LEN);\n\n\tif (_config_domain_rule_add(domain, type, cname_rule) != 0) {\n\t\tgoto errout;\n\t}\n\t_dns_rule_put(&cname_rule->head);\n\tcname_rule = NULL;\n\n\treturn 0;\n\nerrout:\n\ttlog(TLOG_ERROR, \"add cname %s:%s failed\", domain, cname);\n\n\tif (cname_rule) {\n\t\t_dns_rule_put(&cname_rule->head);\n\t}\n\n\treturn 0;\n}\n\nint _config_cname(void *data, int argc, char *argv[])\n{\n\tchar *value = argv[1];\n\tchar domain[DNS_MAX_CONF_CNAME_LEN];\n\n\tif (argc <= 1) {\n\t\tgoto errout;\n\t}\n\n\tif (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn _conf_domain_rule_cname(domain, value);\nerrout:\n\ttlog(TLOG_ERROR, \"add cname %s:%s failed\", domain, value);\n\treturn 0;\n}"
  },
  {
    "path": "src/dns_conf/cname.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_CNAME_H_\n#define _DNS_CONF_CNAME_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_cname(void *data, int argc, char *argv[]);\n\nint _conf_domain_rule_cname(const char *domain, const char *cname);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/conf_file.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"conf_file.h\"\n#include \"dns_conf_group.h\"\n#include \"set_file.h\"\n#include \"smartdns/lib/conf.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/util.h\"\n#include \"smartdns/lib/stringutil.h\"\n\n#include <errno.h>\n#include <getopt.h>\n#include <limits.h>\n#include <stdio.h>\n\nstruct conf_file_path {\n\tstruct hlist_node node;\n\tchar file[DNS_MAX_PATH];\n};\n\nstruct hash_table conf_file_table;\n\nint conf_file_table_init(void)\n{\n\thash_table_init(conf_file_table, 8);\n\n\treturn 0;\n}\n\nstatic int conf_file_check_duplicate(const char *conf_file)\n{\n\tstruct conf_file_path *file = NULL;\n\tuint32_t key = 0;\n\n\tkey = hash_string(conf_file);\n\thash_table_for_each_possible(conf_file_table, file, node, key)\n\t{\n\t\tif (strncmp(file->file, conf_file, DNS_MAX_PATH) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tfile = zalloc(1, sizeof(*file));\n\tif (file == NULL) {\n\t\treturn -1;\n\t}\n\n\tsafe_strncpy(file->file, conf_file, DNS_MAX_PATH);\n\thash_table_add(conf_file_table, &file->node, key);\n\treturn -1;\n}\n\nstatic int conf_additional_file(const char *conf_file)\n{\n\tchar file_path[PATH_MAX];\n\tchar file_path_dir[PATH_MAX];\n\n\tif (conf_file == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (conf_file[0] != '/') {\n\t\tsafe_strncpy(file_path_dir, conf_get_conf_file(), DNS_MAX_PATH);\n\t\tdir_name(file_path_dir);\n\t\tif (strncmp(file_path_dir, conf_get_conf_file(), sizeof(file_path_dir)) == 0) {\n\t\t\tif (snprintf(file_path, DNS_MAX_PATH, \"%s\", conf_file) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t} else {\n\t\t\tif (snprintf(file_path, DNS_MAX_PATH, \"%s/%s\", file_path_dir, conf_file) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t} else {\n\t\tsafe_strncpy(file_path, conf_file, DNS_MAX_PATH);\n\t}\n\n\tif (access(file_path, R_OK) != 0) {\n\t\ttlog(TLOG_ERROR, \"config file '%s' is not readable, %s\", conf_file, strerror(errno));\n\t\treturn -1;\n\t}\n\n\tif (conf_file_check_duplicate(file_path) == 0) {\n\t\treturn 0;\n\t}\n\n\treturn load_conf(file_path, smartdns_config_item(), _conf_printf);\n}\n\nstatic int _config_additional_file_callback(const char *file, void *priv)\n{\n\treturn conf_additional_file(file);\n}\n\nint config_additional_file(void *data, int argc, char *argv[])\n{\n\tconst char *conf_pattern = NULL;\n\tint opt = 0;\n\tconst char *group_name = NULL;\n\tint ret = 0;\n\tstruct dns_conf_group_info *last_group_info;\n\n\tif (argc < 1) {\n\t\treturn -1;\n\t}\n\n\tconf_pattern = argv[1];\n\tif (conf_pattern == NULL) {\n\t\treturn -1;\n\t}\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"group\", required_argument, NULL, 'g'},\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\n\t/* process extra options */\n\toptind = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"g:\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'g': {\n\t\t\tgroup_name = optarg;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlast_group_info = _config_current_group();\n\tif (group_name != NULL) {\n\t\tret = _config_current_group_push(group_name, NULL);\n\t\tif (ret != 0) {\n\t\t\ttlog(TLOG_ERROR, \"begin group '%s' failed.\", group_name);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tret = _config_foreach_file(conf_pattern, _config_additional_file_callback, NULL);\n\tif (group_name != NULL) {\n\t\t_config_current_group_pop_to(last_group_info);\n\t}\n\n\treturn ret;\n}\n\nvoid _config_file_hash_table_destroy(void)\n{\n\tstruct conf_file_path *file = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tint i = 0;\n\n\thash_table_for_each_safe(conf_file_table, i, tmp, file, node)\n\t{\n\t\thlist_del_init(&file->node);\n\t\tfree(file);\n\t}\n\n\thash_table_free(conf_file_table, free);\n}\n"
  },
  {
    "path": "src/dns_conf/conf_file.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_CONF_FILE_H_\n#define _DNS_CONF_CONF_FILE_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint conf_file_table_init(void);\n\nint config_additional_file(void *data, int argc, char *argv[]);\n\nvoid _config_file_hash_table_destroy(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/ddns_domain.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ddns_domain.h\"\n#include \"domain_rule.h\"\n#include \"smartdns/lib/stringutil.h\"\n\nstatic char ddns_domain[DNS_MAX_CNAME_LEN] = {0};\n\nconst char *dns_conf_get_ddns_domain(void)\n{\n\treturn ddns_domain;\n}\n\nint _config_ddns_domain(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\treturn -1;\n\t}\n\n\tconst char *domain = argv[1];\n\tsafe_strncpy(ddns_domain, domain, sizeof(ddns_domain));\n\t_config_domain_rule_flag_set(domain, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0);\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_conf/ddns_domain.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_DDNS_DOMAIN_H_\n#define _DNS_CONF_DDNS_DOMAIN_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_ddns_domain(void *data, int argc, char *argv[]);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/dhcp_lease_dnsmasq.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"dhcp_lease_dnsmasq.h\"\n#include \"host_file.h\"\n#include \"ptr.h\"\n\n#include <errno.h>\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n\nstatic char dns_conf_dnsmasq_lease_file[DNS_MAX_PATH];\nstatic time_t dns_conf_dnsmasq_lease_file_time;\n\nstatic int _conf_dhcp_lease_dnsmasq_add(const char *file)\n{\n\tFILE *fp = NULL;\n\tchar line[MAX_LINE_LEN];\n\tchar ip[DNS_MAX_IPLEN];\n\tchar hostname[DNS_MAX_CNAME_LEN];\n\tint ret = 0;\n\tint line_no = 0;\n\tint filed_num = 0;\n\n\tfp = fopen(file, \"r\");\n\tif (fp == NULL) {\n\t\ttlog(TLOG_WARN, \"open file %s error, %s\", file, strerror(errno));\n\t\treturn 0;\n\t}\n\n\tline_no = 0;\n\twhile (fgets(line, MAX_LINE_LEN, fp)) {\n\t\tline_no++;\n\t\tfiled_num = sscanf(line, \"%*s %*s %63s %255s %*s\", ip, hostname);\n\t\tif (filed_num <= 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(hostname, \"*\", DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tret = _conf_host_add(hostname, ip, DNS_HOST_TYPE_DNSMASQ, 1);\n\t\tif (ret != 0) {\n\t\t\ttlog(TLOG_WARN, \"add host %s/%s at %d failed\", hostname, ip, line_no);\n\t\t}\n\n\t\tret = _conf_ptr_add(hostname, ip, 1);\n\t\tif (ret != 0) {\n\t\t\ttlog(TLOG_WARN, \"add ptr %s/%s at %d failed.\", hostname, ip, line_no);\n\t\t}\n\t}\n\n\tfclose(fp);\n\n\treturn 0;\n}\n\nint _conf_dhcp_lease_dnsmasq_file(void *data, int argc, char *argv[])\n{\n\tstruct stat statbuf;\n\n\tif (argc < 1) {\n\t\treturn -1;\n\t}\n\n\tconf_get_conf_fullpath(argv[1], dns_conf_dnsmasq_lease_file, sizeof(dns_conf_dnsmasq_lease_file));\n\tif (_conf_dhcp_lease_dnsmasq_add(dns_conf_dnsmasq_lease_file) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (stat(dns_conf_dnsmasq_lease_file, &statbuf) != 0) {\n\t\treturn 0;\n\t}\n\n\tdns_conf_dnsmasq_lease_file_time = statbuf.st_mtime;\n\treturn 0;\n}\n\nint dns_server_check_update_hosts(void)\n{\n\tstruct stat statbuf;\n\ttime_t now = 0;\n\n\tif (dns_conf_dnsmasq_lease_file[0] == '\\0') {\n\t\treturn -1;\n\t}\n\n\tif (stat(dns_conf_dnsmasq_lease_file, &statbuf) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (dns_conf_dnsmasq_lease_file_time == statbuf.st_mtime) {\n\t\treturn -1;\n\t}\n\n\ttime(&now);\n\n\tif (now - statbuf.st_mtime < 30) {\n\t\treturn -1;\n\t}\n\n\t_config_ptr_table_destroy(1);\n\t_config_host_table_destroy(1);\n\n\tif (_conf_dhcp_lease_dnsmasq_add(dns_conf_dnsmasq_lease_file) != 0) {\n\t\treturn -1;\n\t}\n\n\tdns_conf_dnsmasq_lease_file_time = statbuf.st_mtime;\n\treturn 0;\n}"
  },
  {
    "path": "src/dns_conf/dhcp_lease_dnsmasq.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_DHCP_LEASE_DNSMASQ_H_\n#define _DNS_CONF_DHCP_LEASE_DNSMASQ_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _conf_dhcp_lease_dnsmasq_file(void *data, int argc, char *argv[]);\n\nint dns_server_check_update_hosts(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/dns64.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"dns64.h\"\n#include \"dns_conf_group.h\"\n#include \"domain_rule.h\"\n\nint _config_dns64(void *data, int argc, char *argv[])\n{\n\tprefix_t prefix;\n\tchar *subnet = NULL;\n\tconst char *errmsg = NULL;\n\tvoid *p = NULL;\n\n\tif (argc <= 1) {\n\t\treturn -1;\n\t}\n\n\tsubnet = argv[1];\n\n\tif (strncmp(subnet, \"-\", 2U) == 0) {\n\t\tmemset(&_config_current_rule_group()->dns_dns64, 0, sizeof(struct dns_dns64));\n\t\treturn 0;\n\t}\n\n\tp = prefix_pton(subnet, -1, &prefix, &errmsg);\n\tif (p == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (prefix.family != AF_INET6) {\n\t\ttlog(TLOG_ERROR, \"dns64 subnet %s is not ipv6\", subnet);\n\t\tgoto errout;\n\t}\n\n\tif (prefix.bitlen <= 0 || prefix.bitlen > 96) {\n\t\ttlog(TLOG_ERROR, \"dns64 subnet %s is not valid\", subnet);\n\t\tgoto errout;\n\t}\n\n\tstruct dns_dns64 *dns64 = &(_config_current_rule_group()->dns_dns64);\n\tmemcpy(&dns64->prefix, &prefix.add.sin6.s6_addr, sizeof(dns64->prefix));\n\tdns64->prefix_len = prefix.bitlen;\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nstatic void _dns_conf_dns64_setup_ipv4only_arpa_rule(void)\n{\n\t_config_domain_rule_flag_set(DNS64_IPV4ONLY_APRA_DOMAIN, DOMAIN_FLAG_DUALSTACK_SELECT, 0);\n\t_conf_domain_rule_speed_check(DNS64_IPV4ONLY_APRA_DOMAIN, \"none\");\n\t_conf_domain_rule_response_mode(DNS64_IPV4ONLY_APRA_DOMAIN, \"fastest-response\");\n}\n\nvoid _dns_conf_dns64_post(void)\n{\n\t_dns_conf_dns64_setup_ipv4only_arpa_rule();\n}"
  },
  {
    "path": "src/dns_conf/dns64.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_DNS64_H_\n#define _DNS_CONF_DNS64_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_dns64(void *data, int argc, char *argv[]);\n\nvoid _dns_conf_dns64_post(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/dns_conf.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/lib/idna.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n\n#include \"address.h\"\n#include \"bind.h\"\n#include \"bootstrap_dns.h\"\n#include \"client_rule.h\"\n#include \"client_subnet.h\"\n#include \"cname.h\"\n#include \"conf_file.h\"\n#include \"ddns_domain.h\"\n#include \"dhcp_lease_dnsmasq.h\"\n#include \"dns64.h\"\n#include \"dns_conf_group.h\"\n#include \"domain_rule.h\"\n#include \"domain_set.h\"\n#include \"group.h\"\n#include \"host_file.h\"\n#include \"https_record.h\"\n#include \"ip_alias.h\"\n#include \"ip_rule.h\"\n#include \"ip_set.h\"\n#include \"ipset.h\"\n#include \"local_domain.h\"\n#include \"nameserver.h\"\n#include \"nftset.h\"\n#include \"plugin.h\"\n#include \"proxy_names.h\"\n#include \"proxy_server.h\"\n#include \"ptr.h\"\n#include \"qtype_soa.h\"\n#include \"server.h\"\n#include \"server_group.h\"\n#include \"smartdns_domain.h\"\n#include \"speed_check_mode.h\"\n#include \"srv_record.h\"\n\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <syslog.h>\n#include <unistd.h>\n\nstatic struct config_enum_list dns_conf_response_mode_enum[] = {\n\t{\"first-ping\", DNS_RESPONSE_MODE_FIRST_PING_IP},\n\t{\"fastest-ip\", DNS_RESPONSE_MODE_FASTEST_IP},\n\t{\"fastest-response\", DNS_RESPONSE_MODE_FASTEST_RESPONSE},\n\t{NULL, 0}};\n\nstruct dns_config dns_conf;\n\nstruct config_enum_list *response_mode_list(void)\n{\n\treturn dns_conf_response_mode_enum;\n}\n\nstatic int _config_option_parser_filepath(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\treturn -1;\n\t}\n\n\tconf_get_conf_fullpath(argv[1], data, DNS_MAX_PATH);\n\n\treturn 0;\n}\n\nstatic int _config_log_level(void *data, int argc, char *argv[])\n{\n\t/* read log level and set */\n\tchar *value = argv[1];\n\n\tif (strncasecmp(\"debug\", value, MAX_LINE_LEN) == 0) {\n\t\tdns_conf.log_level = TLOG_DEBUG;\n\t} else if (strncasecmp(\"info\", value, MAX_LINE_LEN) == 0) {\n\t\tdns_conf.log_level = TLOG_INFO;\n\t} else if (strncasecmp(\"notice\", value, MAX_LINE_LEN) == 0) {\n\t\tdns_conf.log_level = TLOG_NOTICE;\n\t} else if (strncasecmp(\"warn\", value, MAX_LINE_LEN) == 0) {\n\t\tdns_conf.log_level = TLOG_WARN;\n\t} else if (strncasecmp(\"error\", value, MAX_LINE_LEN) == 0) {\n\t\tdns_conf.log_level = TLOG_ERROR;\n\t} else if (strncasecmp(\"fatal\", value, MAX_LINE_LEN) == 0) {\n\t\tdns_conf.log_level = TLOG_FATAL;\n\t} else if (strncasecmp(\"off\", value, MAX_LINE_LEN) == 0) {\n\t\tdns_conf.log_level = TLOG_OFF;\n\t} else {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_conf_setup_mdns(void)\n{\n\tif (dns_conf.mdns_lookup != 1) {\n\t\treturn 0;\n\t}\n\n\treturn _conf_domain_rule_nameserver(DNS_SERVER_GROUP_LOCAL, DNS_SERVER_GROUP_MDNS);\n}\n\nstatic int _config_server_name(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\treturn -1;\n\t}\n\n\tutf8_to_punycode(argv[1], strlen(argv[1]), dns_conf.server_name, DNS_MAX_SERVER_NAME_LEN);\n\n\treturn 0;\n}\n\nstatic struct config_item _config_item[] = {\n\tCONF_CUSTOM(\"server-name\", _config_server_name, NULL),\n\tCONF_YESNO(\"resolv-hostname\", &dns_conf.resolv_hostname),\n\tCONF_CUSTOM(\"bind\", _config_bind_ip_udp, NULL),\n\tCONF_CUSTOM(\"bind-tcp\", _config_bind_ip_tcp, NULL),\n\tCONF_CUSTOM(\"bind-tls\", _config_bind_ip_tls, NULL),\n\tCONF_CUSTOM(\"bind-https\", _config_bind_ip_https, NULL),\n\tCONF_CUSTOM(\"bind-cert-root-key-file\", _config_option_parser_filepath, &dns_conf.bind_root_ca_key_file),\n\tCONF_INT(\"bind-cert-validity-days\", &dns_conf.bind_ca_validity_days, 0, 9999),\n\tCONF_CUSTOM(\"bind-cert-file\", _config_option_parser_filepath, &dns_conf.bind_ca_file),\n\tCONF_CUSTOM(\"bind-cert-key-file\", _config_option_parser_filepath, &dns_conf.bind_ca_key_file),\n\tCONF_STRING(\"bind-cert-key-pass\", dns_conf.bind_ca_key_pass, DNS_MAX_PATH),\n\tCONF_CUSTOM(\"server\", _config_server_udp, NULL),\n\tCONF_CUSTOM(\"server-tcp\", _config_server_tcp, NULL),\n\tCONF_CUSTOM(\"server-tls\", _config_server_tls, NULL),\n\tCONF_CUSTOM(\"server-https\", _config_server_https, NULL),\n\tCONF_CUSTOM(\"server-h3\", _config_server_http3, NULL),\n\tCONF_CUSTOM(\"server-http3\", _config_server_http3, NULL),\n\tCONF_CUSTOM(\"server-quic\", _config_server_quic, NULL),\n\tCONF_YESNO(\"mdns-lookup\", &dns_conf.mdns_lookup),\n\tCONF_YESNO(\"local-ptr-enable\", &dns_conf.local_ptr_enable),\n\tCONF_CUSTOM(\"nameserver\", _config_nameserver, NULL),\n\tCONF_YESNO(\"expand-ptr-from-address\", &dns_conf.expand_ptr_from_address),\n\tCONF_CUSTOM(\"address\", _config_address, NULL),\n\tCONF_CUSTOM(\"cname\", _config_cname, NULL),\n\tCONF_CUSTOM(\"srv-record\", _config_srv_record, NULL),\n\tCONF_CUSTOM(\"https-record\", _config_https_record, NULL),\n\tCONF_CUSTOM(\"proxy-server\", _config_proxy_server, NULL),\n\tCONF_YESNO_FUNC(\"ipset-timeout\", _dns_conf_group_yesno, group_member(ipset_nftset.ipset_timeout_enable)),\n\tCONF_CUSTOM(\"ipset\", _config_ipset, NULL),\n\tCONF_CUSTOM(\"ipset-no-speed\", _config_ipset_no_speed, NULL),\n\tCONF_YESNO_FUNC(\"nftset-timeout\", _dns_conf_group_yesno, group_member(ipset_nftset.nftset_timeout_enable)),\n\tCONF_YESNO(\"nftset-debug\", &dns_conf.nftset_debug_enable),\n\tCONF_CUSTOM(\"nftset\", _config_nftset, NULL),\n\tCONF_CUSTOM(\"nftset-no-speed\", _config_nftset_no_speed, NULL),\n\tCONF_CUSTOM(\"speed-check-mode\", _config_speed_check_mode, NULL),\n\tCONF_INT(\"tcp-idle-time\", &dns_conf.tcp_idle_time, 0, 3600),\n\tCONF_SSIZE(\"cache-size\", &dns_conf.cachesize, -1, CONF_INT_MAX),\n\tCONF_SSIZE(\"cache-mem-size\", &dns_conf.cache_max_memsize, 0, CONF_INT_MAX),\n\tCONF_CUSTOM(\"cache-file\", _config_option_parser_filepath, (char *)&dns_conf.cache_file),\n\tCONF_CUSTOM(\"data-dir\", _config_option_parser_filepath, (char *)&dns_conf.data_dir),\n\tCONF_YESNO(\"cache-persist\", &dns_conf.cache_persist),\n\tCONF_INT(\"cache-checkpoint-time\", &dns_conf.cache_checkpoint_time, 0, 3600 * 24 * 7),\n\tCONF_YESNO_FUNC(\"prefetch-domain\", _dns_conf_group_yesno, group_member(dns_prefetch)),\n\tCONF_YESNO_FUNC(\"serve-expired\", _dns_conf_group_yesno, group_member(dns_serve_expired)),\n\tCONF_INT_FUNC(\"serve-expired-ttl\", _dns_conf_group_int, group_member(dns_serve_expired_ttl), 0, CONF_INT_MAX),\n\tCONF_INT_FUNC(\"serve-expired-reply-ttl\", _dns_conf_group_int, group_member(dns_serve_expired_reply_ttl), 0,\n\t\t\t\t  CONF_INT_MAX),\n\tCONF_INT_FUNC(\"serve-expired-prefetch-time\", _dns_conf_group_int, group_member(dns_serve_expired_prefetch_time), 0,\n\t\t\t\t  CONF_INT_MAX),\n\tCONF_YESNO_FUNC(\"dualstack-ip-selection\", _dns_conf_group_yesno, group_member(dualstack_ip_selection)),\n\tCONF_YESNO_FUNC(\"dualstack-ip-allow-force-AAAA\", _dns_conf_group_yesno,\n\t\t\t\t\tgroup_member(dns_dualstack_ip_allow_force_AAAA)),\n\tCONF_INT_FUNC(\"dualstack-ip-selection-threshold\", _dns_conf_group_int,\n\t\t\t\t  group_member(dns_dualstack_ip_selection_threshold), 0, 1000),\n\tCONF_CUSTOM(\"dns64\", _config_dns64, NULL),\n\tCONF_CUSTOM(\"log-level\", _config_log_level, NULL),\n\tCONF_CUSTOM(\"log-file\", _config_option_parser_filepath, (char *)dns_conf.log_file),\n\tCONF_SIZE(\"log-size\", &dns_conf.log_size, 0, 1024 * 1024 * 1024),\n\tCONF_INT(\"log-num\", &dns_conf.log_num, 0, 1024),\n\tCONF_YESNO(\"log-color\", &dns_conf.log_color_mode),\n\tCONF_YESNO(\"log-console\", &dns_conf.log_console),\n\tCONF_YESNO(\"log-syslog\", &dns_conf.log_syslog),\n\tCONF_INT_BASE(\"log-file-mode\", &dns_conf.log_file_mode, 0, 511, 8),\n\tCONF_YESNO(\"audit-enable\", &dns_conf.audit_enable),\n\tCONF_YESNO(\"audit-SOA\", &dns_conf.audit_log_SOA),\n\tCONF_CUSTOM(\"audit-file\", _config_option_parser_filepath, (char *)&dns_conf.audit_file),\n\tCONF_INT_BASE(\"audit-file-mode\", &dns_conf.audit_file_mode, 0, 511, 8),\n\tCONF_SIZE(\"audit-size\", &dns_conf.audit_size, 0, 1024 * 1024 * 1024),\n\tCONF_INT(\"audit-num\", &dns_conf.audit_num, 0, 1024),\n\tCONF_YESNO(\"audit-console\", &dns_conf.audit_console),\n\tCONF_YESNO(\"audit-syslog\", &dns_conf.audit_syslog),\n\tCONF_YESNO(\"acl-enable\", &dns_conf.acl_enable),\n\tCONF_INT_FUNC(\"rr-ttl\", _dns_conf_group_int, group_member(dns_rr_ttl), 0, CONF_INT_MAX),\n\tCONF_INT_FUNC(\"rr-ttl-min\", _dns_conf_group_int, group_member(dns_rr_ttl_min), 0, CONF_INT_MAX),\n\tCONF_INT_FUNC(\"rr-ttl-max\", _dns_conf_group_int, group_member(dns_rr_ttl_max), 0, CONF_INT_MAX),\n\tCONF_INT_FUNC(\"rr-ttl-reply-max\", _dns_conf_group_int, group_member(dns_rr_ttl_reply_max), 0, CONF_INT_MAX),\n\tCONF_INT_FUNC(\"local-ttl\", _dns_conf_group_int, group_member(dns_local_ttl), 0, CONF_INT_MAX),\n\tCONF_INT_FUNC(\"max-reply-ip-num\", _dns_conf_group_int, group_member(dns_max_reply_ip_num), 1, CONF_INT_MAX),\n\tCONF_INT(\"max-query-limit\", &dns_conf.max_query_limit, 0, CONF_INT_MAX),\n\tCONF_ENUM_FUNC(\"response-mode\", _dns_conf_group_enum, group_member(dns_response_mode),\n\t\t\t\t   &dns_conf_response_mode_enum),\n\tCONF_YESNO_FUNC(\"force-AAAA-SOA\", _dns_conf_group_yesno, group_member(force_AAAA_SOA)),\n\tCONF_YESNO_FUNC(\"force-no-CNAME\", _dns_conf_group_yesno, group_member(dns_force_no_cname)),\n\tCONF_CUSTOM(\"force-qtype-SOA\", _config_qtype_soa, NULL),\n\tCONF_CUSTOM(\"blacklist-ip\", _config_blacklist_ip, NULL),\n\tCONF_CUSTOM(\"whitelist-ip\", _config_whitelist_ip, NULL),\n\tCONF_CUSTOM(\"ip-alias\", _config_ip_alias, NULL),\n\tCONF_CUSTOM(\"ip-rules\", _config_ip_rules, NULL),\n\tCONF_CUSTOM(\"ip-set\", _config_ip_set, NULL),\n\tCONF_CUSTOM(\"bogus-nxdomain\", _config_bogus_nxdomain, NULL),\n\tCONF_CUSTOM(\"ignore-ip\", _config_ip_ignore, NULL),\n\tCONF_CUSTOM(\"edns-client-subnet\", _conf_edns_client_subnet, NULL),\n\tCONF_CUSTOM(\"domain-rules\", _config_domain_rules, NULL),\n\tCONF_CUSTOM(\"domain-set\", _config_domain_set, NULL),\n\tCONF_CUSTOM(\"ddns-domain\", _config_ddns_domain, NULL),\n\tCONF_CUSTOM(\"local-domain\", _config_local_domain, NULL),\n\tCONF_CUSTOM(\"dnsmasq-lease-file\", _conf_dhcp_lease_dnsmasq_file, NULL),\n\tCONF_CUSTOM(\"hosts-file\", _config_hosts_file, NULL),\n\tCONF_CUSTOM(\"group-begin\", _config_group_begin, NULL),\n\tCONF_CUSTOM(\"group-end\", _config_group_end, NULL),\n\tCONF_CUSTOM(\"group-match\", _config_group_match, NULL),\n\tCONF_CUSTOM(\"client-rules\", _config_client_rules, NULL),\n\tCONF_STRING(\"ca-file\", (char *)&dns_conf.ca_file, DNS_MAX_PATH),\n\tCONF_STRING(\"ca-path\", (char *)&dns_conf.ca_path, DNS_MAX_PATH),\n\tCONF_STRING(\"user\", (char *)&dns_conf.user, sizeof(dns_conf.user)),\n\tCONF_YESNO(\"debug-save-fail-packet\", &dns_conf.dns_save_fail_packet),\n\tCONF_YESNO(\"no-pidfile\", &dns_conf.dns_no_pidfile),\n\tCONF_YESNO(\"no-daemon\", &dns_conf.dns_no_daemon),\n\tCONF_YESNO(\"restart-on-crash\", &dns_conf.dns_restart_on_crash),\n\tCONF_SIZE(\"socket-buff-size\", &dns_conf.dns_socket_buff_size, 0, 1024 * 1024 * 8),\n\tCONF_CUSTOM(\"plugin\", _config_plugin, NULL),\n\tCONF_STRING(\"resolv-file\", (char *)&dns_conf.dns_resolv_file, sizeof(dns_conf.dns_resolv_file)),\n\tCONF_STRING(\"debug-save-fail-packet-dir\", (char *)&dns_conf.dns_save_fail_packet_dir,\n\t\t\t\tsizeof(dns_conf.dns_save_fail_packet_dir)),\n\tCONF_CUSTOM(\"conf-file\", config_additional_file, NULL),\n\tCONF_END(),\n};\n\nconst struct config_item *smartdns_config_item(void)\n{\n\treturn _config_item;\n}\n\nstatic int _conf_value_handler(const char *key, const char *value)\n{\n\tif (strstr(key, \".\") == NULL) {\n\t\treturn -1;\n\t}\n\n#ifdef BUILD_STATIC\n\treturn -1;\n#endif\n\n\treturn _config_plugin_conf_add(key, value);\n}\n\nint _conf_printf(const char *key, const char *value, const char *file, int lineno, int ret)\n{\n\tswitch (ret) {\n\tcase CONF_RET_ERR:\n\tcase CONF_RET_WARN:\n\tcase CONF_RET_BADCONF:\n\t\ttlog(TLOG_WARN, \"process config failed at '%s:%d'.\", file, lineno);\n\t\treturn -1;\n\t\tbreak;\n\tcase CONF_RET_NOENT:\n\t\tif (_conf_value_handler(key, value) == 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\ttlog(TLOG_WARN, \"unsupported config at '%s:%d'.\", file, lineno);\n\t\treturn 0;\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn 0;\n}\n\nconst char *dns_conf_get_cache_dir(void)\n{\n\tif (dns_conf.cache_file[0] == '\\0') {\n\t\treturn SMARTDNS_CACHE_FILE;\n\t}\n\n\treturn dns_conf.cache_file;\n}\n\nconst char *dns_conf_get_data_dir(void)\n{\n\tif (dns_conf.data_dir[0] == '\\0') {\n\t\treturn SMARTDNS_DATA_DIR;\n\t}\n\n\treturn dns_conf.data_dir;\n}\n\nstatic int _dns_server_load_conf_init(void)\n{\n\tdns_conf.client_rule.rule = New_Radix();\n\tif (dns_conf.client_rule.rule == NULL) {\n\t\ttlog(TLOG_WARN, \"init client rule radix tree failed.\");\n\t\treturn -1;\n\t}\n\thash_init(dns_conf.client_rule.mac);\n\n\tconf_file_table_init();\n\t_config_rule_group_init();\n\t_config_ipset_init();\n\t_config_group_table_init();\n\t_config_host_table_init();\n\t_config_ptr_table_init();\n\t_config_domain_set_name_table_init();\n\t_config_ip_set_name_table_init();\n\t_config_plugin_table_init();\n\n\tif (_config_current_group_push_default() != 0) {\n\t\ttlog(TLOG_ERROR, \"init default group failed.\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nvoid dns_server_load_exit(void)\n{\n\t_config_rule_group_destroy();\n\t_config_client_rule_destroy();\n\t_config_ipset_table_destroy();\n\t_config_nftset_table_destroy();\n\t_config_group_table_destroy();\n\t_config_ptr_table_destroy(0);\n\t_config_host_table_destroy(0);\n\t_config_proxy_table_destroy();\n\t_config_plugin_table_destroy();\n\t_config_plugin_table_conf_destroy();\n\n\tdns_conf.server_num = 0;\n\tdns_server_bind_destroy();\n\n\tif (dns_conf.log_syslog == 1 || dns_conf.audit_syslog == 1) {\n\t\tcloselog();\n\t}\n\n\tmemset(&dns_conf, 0, sizeof(dns_conf));\n}\n\nstatic void _dns_conf_default_value_init(void)\n{\n\tDOMAIN_CHECK_TYPE tcp_check_type = DOMAIN_CHECK_TCP;\n\tif (dns_has_raw_cap) {\n\t\t/* use tcp-syn as default if have raw socket capability */\n\t\ttcp_check_type = DOMAIN_CHECK_TCP_SYN;\n\t}\n\n\tdns_conf.max_query_limit = DNS_MAX_QUERY_LIMIT;\n\tdns_conf.tcp_idle_time = 120;\n\tdns_conf.local_ptr_enable = 1;\n\tdns_conf.audit_size = 1024 * 1024;\n\tdns_conf.cache_checkpoint_time = DNS_DEFAULT_CHECKPOINT_TIME;\n\tdns_conf.cache_persist = 2;\n\tdns_conf.log_num = 8;\n\tdns_conf.log_size = 1024 * 128;\n\tdns_conf.log_level = TLOG_ERROR;\n\tdns_conf.log_color_mode = 1;\n\tdns_conf.audit_num = 2;\n\tdns_conf.audit_file_mode = 0640;\n\tdns_conf.audit_size = 1024 * 128;\n\tdns_conf.resolv_hostname = 1;\n\tdns_conf.cachesize = -1;\n\tdns_conf.cache_max_memsize = -1;\n\n\tdns_conf.default_check_orders.orders[0].type = DOMAIN_CHECK_ICMP;\n\tdns_conf.default_check_orders.orders[0].tcp_port = 0;\n\tdns_conf.default_check_orders.orders[1].type = tcp_check_type;\n\tdns_conf.default_check_orders.orders[1].tcp_port = 80;\n\tdns_conf.default_check_orders.orders[2].type = tcp_check_type;\n\tdns_conf.default_check_orders.orders[2].tcp_port = 443;\n\tdns_conf.default_response_mode = DNS_RESPONSE_MODE_FIRST_PING_IP;\n}\n\nstatic int _dns_conf_load_pre(void)\n{\n\t_dns_conf_default_value_init();\n\n\tif (_dns_server_load_conf_init() != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_ping_cap_check();\n\n\tsafe_strncpy(dns_conf.dns_save_fail_packet_dir, SMARTDNS_DEBUG_DIR, sizeof(dns_conf.dns_save_fail_packet_dir));\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nstatic void _dns_conf_auto_set_cache_size(void)\n{\n\tuint64_t memsize = get_system_mem_size();\n\tif (dns_conf.cachesize >= 0) {\n\t\treturn;\n\t}\n\n\tif (memsize <= 16 * 1024 * 1024) {\n\t\tdns_conf.cachesize = 4096; /* 2MB memory */\n\t} else if (memsize <= 32 * 1024 * 1024) {\n\t\tdns_conf.cachesize = 16384; /* 8MB memory*/\n\t} else if (memsize <= 64 * 1024 * 1024) {\n\t\tdns_conf.cachesize = 32768; /* 16MB memory*/\n\t} else if (memsize <= 128 * 1024 * 1024) {\n\t\tdns_conf.cachesize = 65536; /* 32MB memory*/\n\t} else if (memsize <= 256 * 1024 * 1024) {\n\t\tdns_conf.cachesize = 131072; /* 64MB memory*/\n\t} else if (memsize <= 512LL * 1024 * 1024) {\n\t\tdns_conf.cachesize = 196608; /* 96MB memory*/\n\t} else if (memsize <= 1024LL * 1024 * 1024) {\n\t\tdns_conf.cachesize = 262144; /* 128MB memory*/\n\t} else if (memsize <= 2048LL * 1024 * 1024) {\n\t\tdns_conf.cachesize = 393216; /* 192MB memory*/\n\t} else if (memsize <= 4096LL * 1024 * 1024) {\n\t\tdns_conf.cachesize = 524288; /* 256MB memory*/\n\t} else {\n\t\tdns_conf.cachesize = 1048576; /* 512MB memory*/\n\t}\n}\n\nstatic int _dns_conf_load_post(void)\n{\n\t_config_current_group_pop_to_default();\n\t_config_setup_smartdns_domain();\n\t_dns_conf_speed_check_mode_verify();\n\n\t_dns_conf_auto_set_cache_size();\n\n\t_dns_conf_setup_mdns();\n\n\tif (dns_conf.dns_resolv_file[0] == '\\0') {\n\t\tsafe_strncpy(dns_conf.dns_resolv_file, DNS_RESOLV_FILE, sizeof(dns_conf.dns_resolv_file));\n\t}\n\n\t_dns_conf_group_post();\n\n\t_dns_conf_dns64_post();\n\n\t_config_domain_set_name_table_destroy();\n\n\t_config_ip_set_name_table_destroy();\n\n\t_config_update_bootstrap_dns_rule();\n\n\t_config_add_default_server_if_needed();\n\n\t_config_file_hash_table_destroy();\n\n\t_config_current_group_pop_all();\n\n\tif (dns_conf.log_syslog == 0 && dns_conf.audit_syslog == 0) {\n\t\tcloselog();\n\t}\n\n\treturn 0;\n}\n\nint dns_server_load_conf(const char *file)\n{\n\tint ret = 0;\n\tret = _dns_conf_load_pre();\n\tif (ret != 0) {\n\t\treturn ret;\n\t}\n\n\topenlog(\"smartdns\", LOG_CONS, LOG_USER);\n\tret = load_conf(file, _config_item, _conf_printf);\n\tif (ret != 0) {\n\t\tcloselog();\n\t\treturn ret;\n\t}\n\n\tret = _dns_conf_load_post();\n\treturn ret;\n}\n"
  },
  {
    "path": "src/dns_conf/dns_conf.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_H_\n#define _DNS_CONF_H_\n\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/tlog.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nconst struct config_item *smartdns_config_item(void);\n\nint _conf_printf(const char *key, const char *value, const char *file, int lineno, int ret);\n\nstruct config_enum_list *response_mode_list(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/dns_conf_group.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"dns_conf_group.h\"\n#include \"domain_rule.h\"\n#include \"ip_rule.h\"\n#include \"server_group.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\nstruct dns_conf_group_info *dns_conf_current_group_info;\nstruct dns_conf_group_info *dns_conf_default_group_info;\nstatic LIST_HEAD(dns_conf_group_info_list);\n\nstruct dns_conf_rule dns_conf_rule;\n\nint _config_rule_group_init(void)\n{\n\thash_init(dns_conf_rule.group);\n\tdns_conf_rule.default_conf = _config_rule_group_new(\"\");\n\tif (dns_conf_rule.default_conf == NULL) {\n\t\ttlog(TLOG_WARN, \"init default domain rule failed.\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\n__attribute__((unused)) int _dns_conf_group_int(int value, int *data)\n{\n\tstruct dns_conf_group *conf_group = _config_current_rule_group();\n\tif (conf_group == NULL) {\n\t\treturn -1;\n\t}\n\n\tvoid *ptr = (char *)conf_group + (size_t)data;\n\t*(int *)ptr = value;\n\n\treturn 0;\n}\n\n__attribute__((unused)) int _dns_conf_group_int_base(int value, int *data)\n{\n\tstruct dns_conf_group *conf_group = _config_current_rule_group();\n\tif (conf_group == NULL) {\n\t\treturn -1;\n\t}\n\n\tvoid *ptr = (char *)conf_group + (size_t)data;\n\t*(int *)ptr = value;\n\n\treturn 0;\n}\n\n__attribute__((unused)) int _dns_conf_group_string(const char *value, char *data)\n{\n\tstruct dns_conf_group *conf_group = _config_current_rule_group();\n\tif (conf_group == NULL) {\n\t\treturn -1;\n\t}\n\n\tchar *ptr = (char *)conf_group + (size_t)data;\n\tsafe_strncpy(ptr, value, DNS_MAX_PATH);\n\n\treturn 0;\n}\n\n__attribute__((unused)) int _dns_conf_group_yesno(int value, int *data)\n{\n\tstruct dns_conf_group *conf_group = _config_current_rule_group();\n\tif (conf_group == NULL) {\n\t\treturn -1;\n\t}\n\n\tvoid *ptr = (char *)conf_group + (size_t)data;\n\t*(int *)ptr = value;\n\n\treturn 0;\n}\n\n__attribute__((unused)) int _dns_conf_group_size(size_t value, size_t *data)\n{\n\tstruct dns_conf_group *conf_group = _config_current_rule_group();\n\tif (conf_group == NULL) {\n\t\treturn -1;\n\t}\n\n\tvoid *ptr = (char *)conf_group + (size_t)data;\n\t*(size_t *)ptr = value;\n\n\treturn 0;\n}\n\n__attribute__((unused)) int _dns_conf_group_ssize(ssize_t value, ssize_t *data)\n{\n\tstruct dns_conf_group *conf_group = _config_current_rule_group();\n\tif (conf_group == NULL) {\n\t\treturn -1;\n\t}\n\n\tvoid *ptr = (char *)conf_group + (size_t)data;\n\t*(ssize_t *)ptr = value;\n\n\treturn 0;\n}\n\n__attribute__((unused)) int _dns_conf_group_enum(int value, int *data)\n{\n\tstruct dns_conf_group *conf_group = _config_current_rule_group();\n\tif (conf_group == NULL) {\n\t\treturn -1;\n\t}\n\n\tvoid *ptr = (char *)conf_group + (size_t)data;\n\t*(int *)ptr = value;\n\n\treturn 0;\n}\n\nstruct dns_conf_group *_config_current_rule_group(void)\n{\n\tif (dns_conf_current_group_info == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn dns_conf_current_group_info->rule;\n}\n\nstruct dns_conf_group_info *_config_current_group(void)\n{\n\treturn dns_conf_current_group_info;\n}\n\nvoid _config_set_current_group(struct dns_conf_group_info *group_info)\n{\n\tif (group_info == NULL) {\n\t\treturn;\n\t}\n\n\tdns_conf_current_group_info = group_info;\n}\n\nstruct dns_conf_group_info *_config_default_group(void)\n{\n\treturn dns_conf_default_group_info;\n}\n\nvoid _config_current_group_pop(void)\n{\n\tstruct dns_conf_group_info *group_info = NULL;\n\n\tgroup_info = list_last_entry(&dns_conf_group_info_list, struct dns_conf_group_info, list);\n\tif (group_info == NULL) {\n\t\treturn;\n\t}\n\n\tif (group_info == dns_conf_default_group_info) {\n\t\tdns_conf_current_group_info = dns_conf_default_group_info;\n\t\treturn;\n\t}\n\n\tlist_del(&group_info->list);\n\tfree(group_info);\n\n\tgroup_info = list_last_entry(&dns_conf_group_info_list, struct dns_conf_group_info, list);\n\tif (group_info == NULL) {\n\t\tdns_conf_current_group_info = NULL;\n\t\treturn;\n\t}\n\n\tdns_conf_current_group_info = group_info;\n}\n\nstatic int _config_domain_rule_iter_copy(void *data, const unsigned char *key, uint32_t key_len, void *value)\n{\n\tart_tree *dest_tree = data;\n\tstruct dns_domain_rule *old_domain_rule = NULL;\n\tstruct dns_domain_rule *new_domain_rule = NULL;\n\n\told_domain_rule = (struct dns_domain_rule *)value;\n\n\t/* Allocate new domain rule with same capacity as original */\n\tsize_t size = sizeof(struct dns_domain_rule) + old_domain_rule->capacity * sizeof(struct dns_rule *);\n\tnew_domain_rule = zalloc(1, size);\n\tif (new_domain_rule == NULL) {\n\t\treturn -1;\n\t}\n\tnew_domain_rule->capacity = old_domain_rule->capacity;\n\n\t/* Copy rules using actual capacity, not DOMAIN_RULE_MAX */\n\tfor (int i = 0; i < old_domain_rule->capacity; i++) {\n\t\tif (old_domain_rule->rules[i]) {\n\t\t\t_dns_rule_get(old_domain_rule->rules[i]);\n\t\t\tnew_domain_rule->rules[i] = old_domain_rule->rules[i];\n\t\t}\n\t}\n\n\told_domain_rule = art_insert(dest_tree, key, key_len, new_domain_rule);\n\tif (old_domain_rule) {\n\t\t_config_domain_rule_free(old_domain_rule);\n\t}\n\n\treturn 0;\n}\n\nstatic int _config_rule_group_setup_value(struct dns_conf_group_info *group_info)\n{\n\tstruct dns_conf_group *group_rule = group_info->rule;\n\tint soa_talbe_size = MAX_QTYPE_NUM / 8 + 1;\n\tuint8_t *soa_table = NULL;\n\tstruct dns_conf_group *parent_group = _config_current_rule_group();\n\n\tif (group_info->inherit_group != NULL) {\n\t\tif (strncmp(group_info->inherit_group, \"none\", sizeof(\"none\")) == 0) {\n\t\t\tparent_group = NULL;\n\t\t} else if (strncmp(group_info->inherit_group, \"parent\", sizeof(\"parent\")) == 0) {\n\t\t\tparent_group = _config_current_rule_group();\n\t\t} else if (strncmp(group_info->inherit_group, \"default\", sizeof(\"default\")) == 0) {\n\t\t\tparent_group = dns_server_get_default_rule_group();\n\t\t} else {\n\t\t\tparent_group = _config_rule_group_get(group_info->inherit_group);\n\t\t\tif (parent_group == NULL) {\n\t\t\t\ttlog(TLOG_WARN, \"inherit group %s not exist.\", group_info->inherit_group);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\tsoa_table = zalloc(1, soa_talbe_size);\n\tif (soa_table == NULL) {\n\t\ttlog(TLOG_WARN, \"malloc qtype soa table failed.\");\n\t\treturn -1;\n\t}\n\tgroup_rule->soa_table = soa_table;\n\n\tif (parent_group != NULL) {\n\t\t/* copy parent group data. */\n\t\tmemcpy(&group_rule->copy_data_section_begin, &parent_group->copy_data_section_begin,\n\t\t\t   offsetof(struct dns_conf_group, copy_data_section_end) -\n\t\t\t\t   offsetof(struct dns_conf_group, copy_data_section_begin));\n\t\tmemcpy(group_rule->soa_table, parent_group->soa_table, soa_talbe_size);\n\t\tart_iter(&parent_group->domain_rule.tree, _config_domain_rule_iter_copy, &group_rule->domain_rule.tree);\n\t\treturn 0;\n\t}\n\n\tmemcpy(&group_rule->check_orders, &dns_conf.default_check_orders, sizeof(group_rule->check_orders));\n\tgroup_rule->dualstack_ip_selection = 1;\n\tgroup_rule->dns_dualstack_ip_selection_threshold = 10;\n\tgroup_rule->dns_rr_ttl_min = 600;\n\tgroup_rule->dns_serve_expired = 1;\n\n\tif (group_rule->dns_prefetch == 1) {\n\t\tgroup_rule->dns_serve_expired_ttl = 24 * 3600 * 7;\n\t} else {\n\t\tgroup_rule->dns_serve_expired_ttl = 24 * 3600;\n\t}\n\n\tgroup_rule->dns_serve_expired_reply_ttl = 3;\n\tgroup_rule->dns_max_reply_ip_num = DNS_MAX_REPLY_IP_NUM;\n\tgroup_rule->dns_response_mode = dns_conf.default_response_mode;\n\n\treturn 0;\n}\n\nint _config_current_group_push(const char *group_name, const char *inherit_group_name)\n{\n\tstruct dns_conf_group_info *group_info = NULL;\n\tstruct dns_conf_group *group_rule = NULL;\n\n\tgroup_info = zalloc(1, sizeof(*group_info));\n\tif (group_info == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (dns_conf_default_group_info != NULL) {\n\t\tgroup_name = _dns_conf_get_group_name(group_name);\n\t\tif (group_name == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tif (inherit_group_name == NULL && _config_current_rule_group() != NULL) {\n\t\tinherit_group_name = _config_current_rule_group()->group_name;\n\t}\n\n\tmemset(group_info, 0, sizeof(*group_info));\n\tgroup_info->inherit_group = inherit_group_name;\n\tINIT_LIST_HEAD(&group_info->list);\n\tlist_add_tail(&group_info->list, &dns_conf_group_info_list);\n\n\tgroup_rule = _config_rule_group_get(group_name);\n\tif (group_rule == NULL) {\n\t\tgroup_rule = _config_rule_group_new(group_name);\n\t\tif (group_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tgroup_info->group_name = group_name;\n\tgroup_info->rule = group_rule;\n\t_config_rule_group_setup_value(group_info);\n\n\tdns_conf_current_group_info = group_info;\n\tif (dns_conf_default_group_info == NULL) {\n\t\tdns_conf_default_group_info = group_info;\n\t}\n\n\treturn 0;\n\nerrout:\n\tif (group_info) {\n\t\tfree(group_info);\n\t}\n\treturn -1;\n}\n\nint _config_current_group_push_default(void)\n{\n\treturn _config_current_group_push(NULL, NULL);\n}\n\nint _config_current_group_pop_to(struct dns_conf_group_info *group_info)\n{\n\twhile (dns_conf_current_group_info != NULL && dns_conf_current_group_info != group_info) {\n\t\t_config_current_group_pop();\n\t}\n\n\treturn 0;\n}\n\nint _config_current_group_pop_to_default(void)\n{\n\treturn _config_current_group_pop_to(dns_conf_default_group_info);\n}\n\nint _config_current_group_pop_all(void)\n{\n\twhile (dns_conf_current_group_info != NULL && dns_conf_current_group_info != dns_conf_default_group_info) {\n\t\t_config_current_group_pop();\n\t}\n\n\tif (dns_conf_default_group_info == NULL) {\n\t\treturn 0;\n\t}\n\n\tlist_del(&dns_conf_default_group_info->list);\n\tfree(dns_conf_default_group_info);\n\tdns_conf_default_group_info = NULL;\n\tdns_conf_current_group_info = NULL;\n\n\treturn 0;\n}\n\nstruct dns_conf_group *_config_rule_group_get(const char *group_name)\n{\n\tuint32_t key = 0;\n\tstruct dns_conf_group *rule_group = NULL;\n\tif (group_name == NULL) {\n\t\tgroup_name = \"\";\n\t}\n\n\tkey = hash_string(group_name);\n\thash_for_each_possible(dns_conf_rule.group, rule_group, node, key)\n\t{\n\t\tif (strncmp(rule_group->group_name, group_name, DNS_GROUP_NAME_LEN) == 0) {\n\t\t\treturn rule_group;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstruct dns_conf_group *dns_server_get_rule_group(const char *group_name)\n{\n\tif (dns_conf_rule.group_num <= 1) {\n\t\treturn dns_conf_rule.default_conf;\n\t}\n\n\tstruct dns_conf_group *rule_group = _config_rule_group_get(group_name);\n\tif (rule_group) {\n\t\treturn rule_group;\n\t}\n\n\treturn dns_conf_rule.default_conf;\n}\n\nstruct dns_conf_group *dns_server_get_default_rule_group(void)\n{\n\treturn dns_conf_rule.default_conf;\n}\n\nstruct dns_conf_group *_config_rule_group_new(const char *group_name)\n{\n\tstruct dns_conf_group *rule_group = NULL;\n\tuint32_t key = 0;\n\n\tif (group_name == NULL) {\n\t\treturn NULL;\n\t}\n\n\trule_group = zalloc(1, sizeof(*rule_group));\n\tif (rule_group == NULL) {\n\t\treturn NULL;\n\t}\n\trule_group->group_name = group_name;\n\n\tINIT_HLIST_NODE(&rule_group->node);\n\tart_tree_init(&rule_group->domain_rule.tree);\n\n\trule_group->address_rule.ipv4 = New_Radix();\n\trule_group->address_rule.ipv6 = New_Radix();\n\n\tkey = hash_string(group_name);\n\thash_add(dns_conf_rule.group, &rule_group->node, key);\n\tdns_conf_rule.group_num++;\n\n\treturn rule_group;\n}\n\nstatic void _config_rule_group_remove(struct dns_conf_group *rule_group)\n{\n\thlist_del_init(&rule_group->node);\n\tart_iter(&rule_group->domain_rule.tree, _config_domain_iter_free, NULL);\n\tart_tree_destroy(&rule_group->domain_rule.tree);\n\tDestroy_Radix(rule_group->address_rule.ipv4, _config_ip_iter_free, NULL);\n\tDestroy_Radix(rule_group->address_rule.ipv6, _config_ip_iter_free, NULL);\n\tfree(rule_group->soa_table);\n\tdns_conf_rule.group_num--;\n\n\tfree(rule_group);\n}\n\nvoid _config_rule_group_destroy(void)\n{\n\tstruct dns_conf_group *group;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_conf_rule.group, i, tmp, group, node)\n\t{\n\t\t_config_rule_group_remove(group);\n\t}\n\n\tdns_conf_rule.default_conf = NULL;\n}\n\nvoid _dns_conf_group_post(void)\n{\n\tstruct dns_conf_group *group;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_conf_rule.group, i, tmp, group, node)\n\t{\n\t\tif ((group->dns_rr_ttl_min > group->dns_rr_ttl_max) && group->dns_rr_ttl_max > 0) {\n\t\t\tgroup->dns_rr_ttl_min = group->dns_rr_ttl_max;\n\t\t}\n\n\t\tif ((group->dns_rr_ttl_max < group->dns_rr_ttl_min) && group->dns_rr_ttl_max > 0) {\n\t\t\tgroup->dns_rr_ttl_max = group->dns_rr_ttl_min;\n\t\t}\n\n\t\tif (group->dns_serve_expired == 1 && group->dns_serve_expired_ttl == 0) {\n\t\t\tgroup->dns_serve_expired_ttl = DNS_MAX_SERVE_EXPIRED_TIME;\n\t\t}\n\t}\n}"
  },
  {
    "path": "src/dns_conf/dns_conf_group.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_CONF_GROUP_H_\n#define _DNS_CONF_CONF_GROUP_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nstruct dns_conf_group_info {\n\tstruct list_head list;\n\tconst char *group_name;\n\tconst char *inherit_group;\n\tstruct dns_conf_group *rule;\n};\n\nextern struct dns_conf_rule dns_conf_rule;\n\n// NOLINTNEXTLINE(bugprone-casting-through-void): offsetof result stored as void* for generic interface\n#define group_member(m) ((void *)offsetof(struct dns_conf_group, m))\nint _dns_conf_group_int(int value, int *data);\nint _dns_conf_group_int_base(int value, int *data);\nint _dns_conf_group_string(const char *value, char *data);\nint _dns_conf_group_yesno(int value, int *data);\nint _dns_conf_group_size(size_t value, size_t *data);\nint _dns_conf_group_ssize(ssize_t value, ssize_t *data);\nint _dns_conf_group_enum(int value, int *data);\n\nint _config_rule_group_init(void);\nvoid _config_rule_group_destroy(void);\n\nstruct dns_conf_group *_config_rule_group_new(const char *group_name);\n\nstruct dns_conf_group *_config_current_rule_group(void);\nstruct dns_conf_group_info *_config_current_group(void);\nstruct dns_conf_group_info *_config_default_group(void);\nvoid _config_set_current_group(struct dns_conf_group_info *group_info);\n\nvoid _config_current_group_pop(void);\nint _config_current_group_push(const char *group_name, const char *inherit_group_name);\nint _config_current_group_push_default(void);\nint _config_current_group_pop_to_default(void);\nint _config_current_group_pop_to(struct dns_conf_group_info *group_info);\nint _config_current_group_pop_all(void);\n\nvoid _dns_conf_group_post(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/domain_rule.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"domain_rule.h\"\n#include \"address.h\"\n#include \"cname.h\"\n#include \"dns_conf_group.h\"\n#include \"https_record.h\"\n#include \"ipset.h\"\n#include \"nameserver.h\"\n#include \"nftset.h\"\n#include \"server_group.h\"\n#include \"set_file.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n#include \"speed_check_mode.h\"\n\n#include <getopt.h>\n\nstatic inline uint8_t _get_required_capacity(enum domain_rule type, uint8_t current_capacity)\n{\n\tuint8_t required = type + 1;\n\n\t/* Ensure type is within valid range */\n\tif (type >= DOMAIN_RULE_MAX || type < 0) {\n\t\treturn 0;\n\t}\n\n\tif (current_capacity == 0) {\n\t\treturn required;\n\t}\n\n\t/* Expand by 2 slots at a time, but cap at DOMAIN_RULE_MAX */\n\tuint8_t new_capacity = ((required - current_capacity + 1) / 2) * 2 + current_capacity;\n\n\tif (new_capacity > DOMAIN_RULE_MAX) {\n\t\tnew_capacity = DOMAIN_RULE_MAX;\n\t}\n\n\treturn new_capacity;\n}\n\nstatic struct dns_domain_rule *_alloc_domain_rule(uint8_t capacity)\n{\n\tsize_t size = sizeof(struct dns_domain_rule) + capacity * sizeof(struct dns_rule *);\n\tstruct dns_domain_rule *rule = malloc(size);\n\n\tif (rule == NULL) {\n\t\treturn NULL;\n\t}\n\n\tmemset(rule, 0, size);\n\trule->capacity = capacity;\n\n\treturn rule;\n}\n\n/*\n * Ensure the domain rule has enough capacity for the given rule type\n * Reallocates if necessary, preserving existing rules\n */\nstatic struct dns_domain_rule *_ensure_domain_rule_capacity(struct dns_domain_rule *domain_rule, enum domain_rule type)\n{\n\tif (type >= DOMAIN_RULE_MAX || type < 0) {\n\t\ttlog(TLOG_ERROR, \"Invalid domain rule type %d\", type);\n\t\treturn NULL;\n\t}\n\n\tif (domain_rule == NULL) {\n\t\tuint8_t capacity = _get_required_capacity(type, 0);\n\t\treturn _alloc_domain_rule(capacity);\n\t}\n\n\tif (type < domain_rule->capacity) {\n\t\treturn domain_rule;\n\t}\n\n\tuint8_t new_capacity = _get_required_capacity(type, domain_rule->capacity);\n\tif (new_capacity == 0) {\n\t\treturn NULL;\n\t}\n\n\tif (new_capacity <= domain_rule->capacity) {\n\t\treturn domain_rule;\n\t}\n\n\tsize_t new_size = sizeof(struct dns_domain_rule) + new_capacity * sizeof(struct dns_rule *);\n\tstruct dns_domain_rule *new_rule = realloc(domain_rule, new_size);\n\tif (new_rule == NULL) {\n\t\treturn NULL;\n\t}\n\n\tuint8_t old_capacity = new_rule->capacity;\n\tmemset((void *)(new_rule->rules + old_capacity), 0, (new_capacity - old_capacity) * sizeof(struct dns_rule *));\n\tnew_rule->capacity = new_capacity;\n\n\treturn new_rule;\n}\n\nstruct dns_rule_info {\n\tint size;\n\tint (*get_size)(struct dns_rule *rule);\n\tvoid (*clone)(struct dns_rule *new_rule, struct dns_rule *old_rule);\n};\n\nstatic int _rule_address_ipv4_get_size(struct dns_rule *rule)\n{\n\treturn sizeof(struct dns_rule_address_IPV4) + ((struct dns_rule_address_IPV4 *)rule)->addr_num * DNS_RR_A_LEN;\n}\n\nstatic int _rule_address_ipv6_get_size(struct dns_rule *rule)\n{\n\treturn sizeof(struct dns_rule_address_IPV6) + ((struct dns_rule_address_IPV6 *)rule)->addr_num * DNS_RR_AAAA_LEN;\n}\n\nstatic void _rule_https_clone(struct dns_rule *new_rule, struct dns_rule *old_rule)\n{\n\tstruct dns_https_record_rule *new_https = (struct dns_https_record_rule *)new_rule;\n\tstruct dns_https_record_rule *old_https = (struct dns_https_record_rule *)old_rule;\n\tstruct dns_https_record *record;\n\n\tINIT_LIST_HEAD(&new_https->record_list);\n\tif (old_https->record_list.next != NULL && old_https->record_list.prev != NULL) {\n\t\tlist_for_each_entry(record, &old_https->record_list, list)\n\t\t{\n\t\t\tstruct dns_https_record *new_record = malloc(sizeof(struct dns_https_record));\n\t\t\tif (new_record) {\n\t\t\t\tmemcpy(new_record, record, sizeof(struct dns_https_record));\n\t\t\t\tlist_add_tail(&new_record->list, &new_https->record_list);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void _rule_srv_clone(struct dns_rule *new_rule, struct dns_rule *old_rule)\n{\n\tstruct dns_srv_record_rule *new_srv = (struct dns_srv_record_rule *)new_rule;\n\tstruct dns_srv_record_rule *old_srv = (struct dns_srv_record_rule *)old_rule;\n\tstruct dns_srv_record *record;\n\n\tINIT_LIST_HEAD(&new_srv->record_list);\n\tif (old_srv->record_list.next != NULL && old_srv->record_list.prev != NULL) {\n\t\tlist_for_each_entry(record, &old_srv->record_list, list)\n\t\t{\n\t\t\tstruct dns_srv_record *new_record = malloc(sizeof(struct dns_srv_record));\n\t\t\tif (new_record) {\n\t\t\t\tmemcpy(new_record, record, sizeof(struct dns_srv_record));\n\t\t\t\tlist_add_tail(&new_record->list, &new_srv->record_list);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic struct dns_rule_info dns_rule_info_table[DOMAIN_RULE_MAX] = {\n\t[DOMAIN_RULE_FLAGS] = {sizeof(struct dns_rule_flags), NULL, NULL},\n\t[DOMAIN_RULE_ADDRESS_IPV4] = {sizeof(struct dns_rule_address_IPV4), _rule_address_ipv4_get_size, NULL},\n\t[DOMAIN_RULE_ADDRESS_IPV6] = {sizeof(struct dns_rule_address_IPV6), _rule_address_ipv6_get_size, NULL},\n\t[DOMAIN_RULE_NAMESERVER] = {sizeof(struct dns_nameserver_rule), NULL, NULL},\n\t[DOMAIN_RULE_CHECKSPEED] = {sizeof(struct dns_domain_check_orders), NULL, NULL},\n\t[DOMAIN_RULE_IPSET] = {sizeof(struct dns_ipset_rule), NULL, NULL},\n\t[DOMAIN_RULE_NFTSET_IP] = {sizeof(struct dns_nftset_rule), NULL, NULL},\n\t[DOMAIN_RULE_IPSET_IPV4] = {sizeof(struct dns_ipset_rule), NULL, NULL},\n\t[DOMAIN_RULE_GROUP] = {sizeof(struct dns_group_rule), NULL, NULL},\n\t[DOMAIN_RULE_NFTSET_IP6] = {sizeof(struct dns_nftset_rule), NULL, NULL},\n\t[DOMAIN_RULE_IPSET_IPV6] = {sizeof(struct dns_ipset_rule), NULL, NULL},\n\t[DOMAIN_RULE_HTTPS] = {sizeof(struct dns_https_record_rule), NULL, _rule_https_clone},\n\t[DOMAIN_RULE_SRV] = {sizeof(struct dns_srv_record_rule), NULL, _rule_srv_clone},\n\t[DOMAIN_RULE_RESPONSE_MODE] = {sizeof(struct dns_response_mode_rule), NULL, NULL},\n\t[DOMAIN_RULE_CNAME] = {sizeof(struct dns_cname_rule), NULL, NULL},\n\t[DOMAIN_RULE_TTL] = {sizeof(struct dns_ttl_rule), NULL, NULL},\n};\n\nvoid *_new_dns_rule_ext(enum domain_rule domain_rule, int ext_size)\n{\n\tstruct dns_rule *rule;\n\tint size = 0;\n\n\tif (domain_rule >= DOMAIN_RULE_MAX) {\n\t\treturn NULL;\n\t}\n\n\tsize = dns_rule_info_table[domain_rule].size + ext_size;\n\tif (size <= 0) {\n\t\treturn NULL;\n\t}\n\n\trule = zalloc(1, size);\n\tif (!rule) {\n\t\treturn NULL;\n\t}\n\trule->rule = domain_rule;\n\tatomic_set(&rule->refcnt, 1);\n\treturn rule;\n}\n\nvoid *_new_dns_rule(enum domain_rule domain_rule)\n{\n\treturn _new_dns_rule_ext(domain_rule, 0);\n}\n\nstatic struct dns_rule *_dns_rule_clone(struct dns_rule *rule)\n{\n\tint size = 0;\n\tstruct dns_rule *new_rule;\n\n\tif (rule == NULL || rule->rule >= DOMAIN_RULE_MAX) {\n\t\treturn NULL;\n\t}\n\n\tif (dns_rule_info_table[rule->rule].get_size) {\n\t\tsize = dns_rule_info_table[rule->rule].get_size(rule);\n\t} else {\n\t\tsize = dns_rule_info_table[rule->rule].size;\n\t}\n\n\tif (size <= 0) {\n\t\treturn NULL;\n\t}\n\n\tnew_rule = zalloc(1, size);\n\tif (!new_rule) {\n\t\treturn NULL;\n\t}\n\n\tmemcpy(new_rule, rule, size);\n\tatomic_set(&new_rule->refcnt, 1);\n\n\tif (dns_rule_info_table[rule->rule].clone) {\n\t\tdns_rule_info_table[rule->rule].clone(new_rule, rule);\n\t}\n\n\treturn new_rule;\n}\n\nvoid _dns_rule_get(struct dns_rule *rule)\n{\n\tatomic_inc(&rule->refcnt);\n}\n\nstatic void _dns_rule_free(struct dns_rule *rule)\n{\n\tif (rule->rule == DOMAIN_RULE_HTTPS) {\n\t\tstruct dns_https_record_rule *https_rule = (struct dns_https_record_rule *)rule;\n\t\tstruct dns_https_record *record, *tmp;\n\t\tif (https_rule->record_list.next != NULL && https_rule->record_list.prev != NULL) {\n\t\t\tlist_for_each_entry_safe(record, tmp, &https_rule->record_list, list)\n\t\t\t{\n\t\t\t\tlist_del(&record->list);\n\t\t\t\tfree(record);\n\t\t\t}\n\t\t}\n\t} else if (rule->rule == DOMAIN_RULE_SRV) {\n\t\tstruct dns_srv_record_rule *srv_rule = (struct dns_srv_record_rule *)rule;\n\t\tstruct dns_srv_record *record, *tmp;\n\t\tif (srv_rule->record_list.next != NULL && srv_rule->record_list.prev != NULL) {\n\t\t\tlist_for_each_entry_safe(record, tmp, &srv_rule->record_list, list)\n\t\t\t{\n\t\t\t\tlist_del(&record->list);\n\t\t\t\tfree(record);\n\t\t\t}\n\t\t}\n\t}\n\tfree(rule);\n}\n\nvoid _dns_rule_put(struct dns_rule *rule)\n{\n\tif (atomic_dec_and_test(&rule->refcnt)) {\n\t\t_dns_rule_free(rule);\n\t}\n}\n\nstatic struct dns_domain_set_name_list *_config_get_domain_set_name_list(const char *name)\n{\n\tuint32_t key = 0;\n\tstruct dns_domain_set_name_list *set_name_list = NULL;\n\n\tkey = hash_string(name);\n\thash_for_each_possible(dns_domain_set_name_table.names, set_name_list, node, key)\n\t{\n\t\tif (strcmp(set_name_list->name, name) == 0) {\n\t\t\treturn set_name_list;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic int _config_domain_rule_set_each(const char *domain_set, set_rule_add_func callback, void *priv)\n{\n\tstruct dns_domain_set_name_list *set_name_list = NULL;\n\tstruct dns_domain_set_name *set_name_item = NULL;\n\n\tset_name_list = _config_get_domain_set_name_list(domain_set);\n\tif (set_name_list == NULL) {\n\t\ttlog(TLOG_WARN, \"domain set %s not found.\", domain_set);\n\t\treturn -1;\n\t}\n\n\tlist_for_each_entry(set_name_item, &set_name_list->set_name_list, list)\n\t{\n\t\tswitch (set_name_item->type) {\n\t\tcase DNS_DOMAIN_SET_LIST:\n\t\t\tif (_config_set_rule_each_from_list(set_name_item->file, callback, priv) != 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase DNS_DOMAIN_SET_GEOSITE:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\ttlog(TLOG_WARN, \"domain set %s type %d not support.\", set_name_list->name, set_name_item->type);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _config_domain_rule_add_callback(const char *domain, void *priv)\n{\n\tstruct dns_set_rule_add_callback_args *args = (struct dns_set_rule_add_callback_args *)priv;\n\treturn _config_domain_rule_add(domain, args->type, args->rule);\n}\n\nstatic int _config_setup_domain_key(const char *domain, char *domain_key, int domain_key_max_len, int *domain_key_len,\n\t\t\t\t\t\t\t\t\tint *root_rule_only, int *sub_rule_only)\n{\n\tint tmp_root_rule_only = 0;\n\tint tmp_sub_rule_only = 0;\n\tint domain_len = 0;\n\n\tint len = strlen(domain);\n\tdomain_len = len;\n\tif (!domain_key || !domain_key_len || domain_key_max_len <= 0 || len + 3 > domain_key_max_len) {\n\t\ttlog(TLOG_ERROR, \"invalid parameters or domain too long: %s (max %d)\", domain, domain_key_max_len - 3);\n\t\treturn -1;\n\t}\n\n\twhile (len > 0 && domain[len - 1] == '.') {\n\t\tlen--;\n\t}\n\n\treverse_string(domain_key + 1, domain, len, 1);\n\tif (domain[0] == '*' && domain_len > 1) {\n\t\t/* prefix wildcard */\n\t\tlen--;\n\t\tif (domain[1] == '.') {\n\t\t\ttmp_sub_rule_only = 1;\n\t\t} else if ((domain[1] == '-') && (domain[2] == '.')) {\n\t\t\tlen--;\n\t\t\ttmp_sub_rule_only = 1;\n\t\t\ttmp_root_rule_only = 1;\n\t\t}\n\t} else if (domain[0] == '-' && domain_len > 1) {\n\t\t/* root match only */\n\t\tlen--;\n\t\tif (domain[1] == '.') {\n\t\t\ttmp_root_rule_only = 1;\n\t\t}\n\t} else if (len > 0) {\n\t\t/* suffix match */\n\t\tif (len + 2 < domain_key_max_len) {\n\t\t\tdomain_key[len + 1] = '.';\n\t\t\tlen++;\n\t\t}\n\t}\n\n\t/* add dot to the front when sub rule only */\n\tdomain_key[0] = '.';\n\tif (tmp_sub_rule_only == 1 && tmp_root_rule_only == 0) {\n\t\tdomain_key[len + 1] = '\\0';\n\t} else if (tmp_root_rule_only == 1 && tmp_sub_rule_only == 0) {\n\t\tif (domain_key[len] == '.') {\n\t\t\tlen--;\n\t\t}\n\t\tdomain_key[len + 1] = '\\0';\n\t} else {\n\t\tdomain_key[len + 1] = '\\0';\n\t}\n\n\t*domain_key_len = len + 1;\n\tif (root_rule_only) {\n\t\t*root_rule_only = tmp_root_rule_only;\n\t}\n\n\tif (sub_rule_only) {\n\t\t*sub_rule_only = tmp_sub_rule_only;\n\t}\n\n\treturn 0;\n}\n\nstatic __attribute__((unused)) struct dns_domain_rule *_config_domain_rule_get(const char *domain)\n{\n\tchar domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};\n\tint len = 0;\n\n\tif (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, NULL, NULL) != 0) {\n\t\treturn NULL;\n\t}\n\n\treturn art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);\n}\n\nint _config_domain_rule_free(struct dns_domain_rule *domain_rule)\n{\n\tint i = 0;\n\n\tif (domain_rule == NULL) {\n\t\treturn 0;\n\t}\n\n\t/* Iterate only through allocated capacity, not DOMAIN_RULE_MAX */\n\tfor (i = 0; i < domain_rule->capacity; i++) {\n\t\tif (domain_rule->rules[i] == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_dns_rule_put(domain_rule->rules[i]);\n\t\tdomain_rule->rules[i] = NULL;\n\t}\n\n\tfree(domain_rule);\n\treturn 0;\n}\n\nint _config_domain_iter_free(void *data, const unsigned char *key, uint32_t key_len, void *value)\n{\n\tstruct dns_domain_rule *domain_rule = value;\n\treturn _config_domain_rule_free(domain_rule);\n}\n\nstatic int _config_domain_rule_delete_callback(const char *domain, void *priv)\n{\n\treturn _config_domain_rule_delete(domain);\n}\n\nint _config_domain_rule_delete(const char *domain)\n{\n\tchar domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};\n\tint len = 0;\n\n\tif (strncmp(domain, \"domain-set:\", sizeof(\"domain-set:\") - 1) == 0) {\n\t\treturn _config_domain_rule_set_each(domain + sizeof(\"domain-set:\") - 1, _config_domain_rule_delete_callback,\n\t\t\t\t\t\t\t\t\t\t\tNULL);\n\t}\n\t/* Reverse string, for suffix match */\n\n\tif (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, NULL, NULL) != 0) {\n\t\tgoto errout;\n\t}\n\n\t/* delete existing rules */\n\tvoid *rule = art_delete(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);\n\tif (rule) {\n\t\t_config_domain_rule_free(rule);\n\t}\n\n\treturn 0;\nerrout:\n\ttlog(TLOG_ERROR, \"delete domain %s rule failed\", domain);\n\treturn -1;\n}\n\nstatic int _config_domain_rule_flag_callback(const char *domain, void *priv)\n{\n\tstruct dns_set_rule_flags_callback_args *args = (struct dns_set_rule_flags_callback_args *)priv;\n\treturn _config_domain_rule_flag_set(domain, args->flags, args->is_clear_flag);\n}\n\nint _config_domain_rule_flag_set(const char *domain, unsigned int flag, unsigned int is_clear)\n{\n\tstruct dns_domain_rule *domain_rule = NULL;\n\tstruct dns_domain_rule *old_domain_rule = NULL;\n\tstruct dns_domain_rule *add_domain_rule = NULL;\n\tstruct dns_rule_flags *rule_flags = NULL;\n\n\tchar domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};\n\tint len = 0;\n\tint sub_rule_only = 0;\n\tint root_rule_only = 0;\n\n\tif (strncmp(domain, \"domain-set:\", sizeof(\"domain-set:\") - 1) == 0) {\n\t\tstruct dns_set_rule_flags_callback_args args;\n\t\targs.flags = flag;\n\t\targs.is_clear_flag = is_clear;\n\t\treturn _config_domain_rule_set_each(domain + sizeof(\"domain-set:\") - 1, _config_domain_rule_flag_callback,\n\t\t\t\t\t\t\t\t\t\t\t&args);\n\t}\n\n\tif (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, &root_rule_only, &sub_rule_only) != 0) {\n\t\tgoto errout;\n\t}\n\n\t/* Get existing domain rule */\n\tdomain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);\n\tif (domain_rule == NULL) {\n\t\t/* Allocate new domain rule with minimum capacity for flags */\n\t\tdomain_rule = _alloc_domain_rule(_get_required_capacity(DOMAIN_RULE_FLAGS, 0));\n\t\tif (domain_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\tadd_domain_rule = domain_rule;\n\t}\n\n\t/* add new rule to domain */\n\tif (domain_rule->rules[DOMAIN_RULE_FLAGS] == NULL) {\n\t\trule_flags = _new_dns_rule(DOMAIN_RULE_FLAGS);\n\t\trule_flags->flags = 0;\n\t\tdomain_rule->rules[DOMAIN_RULE_FLAGS] = (struct dns_rule *)rule_flags;\n\t}\n\n\trule_flags = (struct dns_rule_flags *)domain_rule->rules[DOMAIN_RULE_FLAGS];\n\tif (atomic_read(&rule_flags->head.refcnt) > 1 && (rule_flags->head.sub_only != sub_rule_only || rule_flags->head.root_only != root_rule_only)) {\n\t\tstruct dns_rule_flags *new_flags = (struct dns_rule_flags *)_dns_rule_clone(&rule_flags->head);\n\t\tif (new_flags) {\n\t\t\t_dns_rule_put(&rule_flags->head);\n\t\t\tdomain_rule->rules[DOMAIN_RULE_FLAGS] = (struct dns_rule *)new_flags;\n\t\t\trule_flags = new_flags;\n\t\t}\n\t}\n\trule_flags->head.sub_only = sub_rule_only;\n\trule_flags->head.root_only = root_rule_only;\n\tif (is_clear == false) {\n\t\trule_flags->flags |= flag;\n\t} else {\n\t\trule_flags->flags &= ~flag;\n\t}\n\trule_flags->is_flag_set |= flag;\n\n\t/* update domain rule */\n\tif (add_domain_rule) {\n\t\told_domain_rule = art_insert(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len,\n\t\t\t\t\t\t\t\t\t add_domain_rule);\n\t\tif (old_domain_rule) {\n\t\t\t_config_domain_rule_free(old_domain_rule);\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\tif (add_domain_rule) {\n\t\tfree(add_domain_rule);\n\t}\n\n\ttlog(TLOG_ERROR, \"add domain %s rule failed\", domain);\n\treturn 0;\n}\n\nint _config_domain_rule_remove(const char *domain, enum domain_rule type)\n{\n\tchar domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};\n\tint len = 0;\n\tint sub_rule_only = 0;\n\tint root_rule_only = 0;\n\n\tif (type < 0 || type >= DOMAIN_RULE_MAX) {\n\t\ttlog(TLOG_ERROR, \"invalid domain rule type %d\", type);\n\t\treturn -1;\n\t}\n\n\tif (strncmp(domain, \"domain-set:\", sizeof(\"domain-set:\") - 1) == 0) {\n\t\treturn _config_domain_rule_set_each(domain + sizeof(\"domain-set:\") - 1, _config_domain_rule_delete_callback,\n\t\t\t\t\t\t\t\t\t\t\tNULL);\n\t}\n\n\tif (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, &root_rule_only, &sub_rule_only) != 0) {\n\t\ttlog(TLOG_ERROR, \"setup domain key failed for %s\", domain);\n\t\treturn -1;\n\t}\n\n\tstruct dns_domain_rule *domain_rule =\n\t\tart_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);\n\tif (domain_rule == NULL) {\n\t\ttlog(TLOG_ERROR, \"domain %s not found\", domain);\n\t\treturn -1;\n\t}\n\n\tif (domain_rule->rules[type] == NULL) {\n\t\treturn 0;\n\t}\n\n\t_dns_rule_put(domain_rule->rules[type]);\n\tdomain_rule->rules[type] = NULL;\n\n\treturn 0;\n}\n\nint _config_domain_rule_add(const char *domain, enum domain_rule type, void *rule)\n{\n\tstruct dns_domain_rule *domain_rule = NULL;\n\tstruct dns_domain_rule *old_domain_rule = NULL;\n\tstruct dns_domain_rule *add_domain_rule = NULL;\n\n\tchar domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};\n\tint len = 0;\n\tint sub_rule_only = 0;\n\tint root_rule_only = 0;\n\n\tif (strncmp(domain, \"domain-set:\", sizeof(\"domain-set:\") - 1) == 0) {\n\t\tstruct dns_set_rule_add_callback_args args;\n\t\targs.type = type;\n\t\targs.rule = rule;\n\t\treturn _config_domain_rule_set_each(domain + sizeof(\"domain-set:\") - 1, _config_domain_rule_add_callback,\n\t\t\t\t\t\t\t\t\t\t\t&args);\n\t}\n\n\t/* Reverse string, for suffix match */\n\tif (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, &root_rule_only, &sub_rule_only) != 0) {\n\t\tgoto errout;\n\t}\n\n\tif (type >= DOMAIN_RULE_MAX) {\n\t\tgoto errout;\n\t}\n\n\t/* Get existing domain rule */\n\tdomain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);\n\n\t/* Track if this is a new allocation (before capacity expansion) */\n\tint was_new_allocation = (domain_rule == NULL);\n\tstruct dns_domain_rule *old_ptr = domain_rule;\n\n\t/* Ensure capacity for the new rule type */\n\tdomain_rule = _ensure_domain_rule_capacity(domain_rule, type);\n\tif (domain_rule == NULL) {\n\t\ttlog(TLOG_ERROR, \"failed to allocate capacity for domain %s rule type %d\", domain, type);\n\t\tgoto errout;\n\t}\n\n\t/* Set add_domain_rule if this was a new allocation or if realloc moved the memory */\n\tif (was_new_allocation) {\n\t\tadd_domain_rule = domain_rule;\n\t} else if (domain_rule != old_ptr) {\n\t\t/* Memory was moved by realloc, need to update ART tree\n\t\t * Note: old_ptr is already freed by realloc, so we don't free it again */\n\t\told_domain_rule =\n\t\t\tart_insert(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len, domain_rule);\n\t\t/* old_domain_rule == old_ptr, already freed by realloc, don't free again */\n\t\tadd_domain_rule = NULL;\n\t}\n\n\t/* add new rule to domain */\n\tstruct dns_rule *prule = (struct dns_rule *)rule;\n\tint is_cloned = 0;\n\tif (atomic_read(&prule->refcnt) > 1 && (prule->sub_only != sub_rule_only || prule->root_only != root_rule_only)) {\n\t\tstruct dns_rule *new_rule = _dns_rule_clone(prule);\n\t\tif (new_rule) {\n\t\t\tprule = new_rule;\n\t\t\trule = (void *)new_rule;\n\t\t\tis_cloned = 1;\n\t\t}\n\t}\n\n\tif (domain_rule->rules[type]) {\n\t\t_dns_rule_put(domain_rule->rules[type]);\n\t\tdomain_rule->rules[type] = NULL;\n\t}\n\n\tdomain_rule->rules[type] = prule;\n\tprule->sub_only = sub_rule_only;\n\tprule->root_only = root_rule_only;\n\tif (!is_cloned) {\n\t\t_dns_rule_get(prule);\n\t}\n\n\t/* update domain rule - only for new allocations */\n\tif (add_domain_rule) {\n\t\told_domain_rule = art_insert(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len,\n\t\t\t\t\t\t\t\t\t add_domain_rule);\n\t\tif (old_domain_rule) {\n\t\t\t_config_domain_rule_free(old_domain_rule);\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\tif (add_domain_rule) {\n\t\tfree(add_domain_rule);\n\t}\n\n\ttlog(TLOG_ERROR, \"add domain %s rule failed\", domain);\n\treturn -1;\n}\n\nstatic int _conf_domain_rule_rr_ttl(const char *domain, int ttl, int ttl_min, int ttl_max)\n{\n\tstruct dns_ttl_rule *rr_ttl = NULL;\n\n\tif (ttl < 0 || ttl_min < 0 || ttl_max < 0) {\n\t\ttlog(TLOG_ERROR, \"invalid ttl value.\");\n\t\tgoto errout;\n\t}\n\n\trr_ttl = _new_dns_rule(DOMAIN_RULE_TTL);\n\tif (rr_ttl == NULL) {\n\t\tgoto errout;\n\t}\n\n\trr_ttl->ttl = ttl;\n\trr_ttl->ttl_min = ttl_min;\n\trr_ttl->ttl_max = ttl_max;\n\n\tif (_config_domain_rule_add(domain, DOMAIN_RULE_TTL, rr_ttl) != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_rule_put(&rr_ttl->head);\n\n\treturn 0;\nerrout:\n\tif (rr_ttl != NULL) {\n\t\t_dns_rule_put(&rr_ttl->head);\n\t}\n\n\treturn -1;\n}\n\nstatic int _conf_domain_rule_no_serve_expired(const char *domain)\n{\n\treturn _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_SERVE_EXPIRED, 0);\n}\n\nstatic int _conf_domain_rule_delete(const char *domain)\n{\n\treturn _config_domain_rule_delete(domain);\n}\n\nstatic int _conf_domain_rule_no_cache(const char *domain)\n{\n\treturn _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_CACHE, 0);\n}\n\nstatic int _conf_domain_rule_enable_cache(const char *domain)\n{\n\treturn _config_domain_rule_flag_set(domain, DOMAIN_FLAG_ENABLE_CACHE, 0);\n}\n\nstatic int _conf_domain_rule_no_ipalias(const char *domain)\n{\n\treturn _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_IPALIAS, 0);\n}\n\nstatic int _conf_domain_rule_no_ignore_ip(const char *domain)\n{\n\treturn _config_domain_rule_flag_set(domain, DOMAIN_FLAG_NO_IGNORE_IP, 0);\n}\n\nint _conf_domain_rule_response_mode(char *domain, const char *mode)\n{\n\tenum response_mode_type response_mode_type = DNS_RESPONSE_MODE_FIRST_PING_IP;\n\tstruct dns_response_mode_rule *response_mode = NULL;\n\n\tfor (int i = 0; response_mode_list()[i].name != NULL; i++) {\n\t\tif (strcmp(mode, response_mode_list()[i].name) == 0) {\n\t\t\tresponse_mode_type = response_mode_list()[i].id;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tresponse_mode = _new_dns_rule(DOMAIN_RULE_RESPONSE_MODE);\n\tif (response_mode == NULL) {\n\t\tgoto errout;\n\t}\n\tresponse_mode->mode = response_mode_type;\n\n\tif (_config_domain_rule_add(domain, DOMAIN_RULE_RESPONSE_MODE, response_mode) != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_rule_put(&response_mode->head);\n\treturn 0;\nerrout:\n\tif (response_mode) {\n\t\t_dns_rule_put(&response_mode->head);\n\t}\n\n\treturn 0;\n}\n\nint _conf_domain_rule_speed_check(char *domain, const char *mode)\n{\n\tstruct dns_domain_check_orders *check_orders = NULL;\n\n\tcheck_orders = _new_dns_rule(DOMAIN_RULE_CHECKSPEED);\n\tif (check_orders == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (_config_speed_check_mode_parser(check_orders, mode) != 0) {\n\t\tgoto errout;\n\t}\n\n\tif (_config_domain_rule_add(domain, DOMAIN_RULE_CHECKSPEED, check_orders) != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_rule_put(&check_orders->head);\n\treturn 0;\nerrout:\n\tif (check_orders) {\n\t\t_dns_rule_put(&check_orders->head);\n\t}\n\treturn 0;\n}\n\nint _conf_domain_rule_group(const char *domain, const char *group_name)\n{\n\tstruct dns_group_rule *group_rule = NULL;\n\tconst char *group = NULL;\n\n\tif (strncmp(group_name, \"-\", sizeof(\"-\")) != 0) {\n\t\tgroup = _dns_conf_get_group_name(group_name);\n\t\tif (group == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tgroup_rule = _new_dns_rule(DOMAIN_RULE_GROUP);\n\t\tif (group_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tgroup_rule->group_name = group;\n\t} else {\n\t\t/* ignore this domain */\n\t\tif (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_GROUP_IGNORE, 0) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tif (_config_domain_rule_add(domain, DOMAIN_RULE_GROUP, group_rule) != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_rule_put(&group_rule->head);\n\n\treturn 0;\nerrout:\n\tif (group_rule) {\n\t\t_dns_rule_put(&group_rule->head);\n\t}\n\n\ttlog(TLOG_ERROR, \"add group %s, %s failed\", domain, group_name);\n\treturn 0;\n}\n\nstatic int _conf_domain_rule_dualstack_selection(char *domain, const char *yesno)\n{\n\tif (strncmp(yesno, \"yes\", sizeof(\"yes\")) == 0 || strncmp(yesno, \"Yes\", sizeof(\"Yes\")) == 0) {\n\t\tif (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_DUALSTACK_SELECT, 0) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t} else {\n\t\t/* ignore this domain */\n\t\tif (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_DUALSTACK_SELECT, 1) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\treturn 0;\n\nerrout:\n\ttlog(TLOG_ERROR, \"set dualstack for %s failed. \", domain);\n\treturn 1;\n}\n\nint _config_domain_rules(void *data, int argc, char *argv[])\n{\n\tint opt = 0;\n\tint optind_last = 0;\n\tchar domain[DNS_MAX_CONF_CNAME_LEN];\n\tchar *value = argv[1];\n\tint rr_ttl = 0;\n\tint rr_ttl_min = 0;\n\tint rr_ttl_max = 0;\n\tconst char *group = NULL;\n\tchar group_name[DNS_MAX_CONF_CNAME_LEN];\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"speed-check-mode\", required_argument, NULL, 'c'},\n\t\t{\"response-mode\", required_argument, NULL, 'r'},\n\t\t{\"address\", required_argument, NULL, 'a'},\n\t\t{\"https-record\", required_argument, NULL, 'h'},\n\t\t{\"ipset\", required_argument, NULL, 'p'},\n\t\t{\"nftset\", required_argument, NULL, 't'},\n\t\t{\"nameserver\", required_argument, NULL, 'n'},\n\t\t{\"group\", required_argument, NULL, 'g'},\n\t\t{\"dualstack-ip-selection\", required_argument, NULL, 'd'},\n\t\t{\"cname\", required_argument, NULL, 'A'},\n\t\t{\"rr-ttl\", required_argument, NULL, 251},\n\t\t{\"rr-ttl-min\", required_argument, NULL, 252},\n\t\t{\"rr-ttl-max\", required_argument, NULL, 253},\n\t\t{\"no-serve-expired\", no_argument, NULL, 254},\n\t\t{\"delete\", no_argument, NULL, 255},\n\t\t{\"no-cache\", no_argument, NULL, 256},\n\t\t{\"no-ip-alias\", no_argument, NULL, 257},\n\t\t{\"enable-cache\", no_argument, NULL, 258},\n\t\t{\"no-ignore-ip\", no_argument, NULL, 259},\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tif (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {\n\t\tgoto errout;\n\t}\n\n\t/* check domain set exists. */\n\tif (strncmp(domain, \"domain-set:\", sizeof(\"domain-set:\") - 1) == 0) {\n\t\tconst char *set_name = domain + sizeof(\"domain-set:\") - 1;\n\t\tstruct dns_domain_set_name_list *name = _config_get_domain_set_name_list(set_name);\n\t\tif (name == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"domain set '%s' not found.\", set_name);\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tfor (int i = 2; i < argc - 1; i++) {\n\t\tif (strncmp(argv[i], \"-g\", sizeof(\"-g\")) == 0 || strncmp(argv[i], \"--group\", sizeof(\"--group\")) == 0 ||\n\t\t\tstrncmp(argv[i], \"-group\", sizeof(\"-group\")) == 0) {\n\t\t\tsafe_strncpy(group_name, argv[i + 1], DNS_MAX_CONF_CNAME_LEN);\n\t\t\tgroup = group_name;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (group != NULL) {\n\t\t_config_current_group_push(group, NULL);\n\t}\n\n\t/* process extra options */\n\toptind = 1;\n\toptind_last = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"c:a:p:t:n:d:A:r:g:h:\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'c': {\n\t\t\tconst char *check_mode = optarg;\n\t\t\tif (check_mode == NULL) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (_conf_domain_rule_speed_check(domain, check_mode) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add check-speed-rule rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 'r': {\n\t\t\tconst char *response_mode = optarg;\n\t\t\tif (response_mode == NULL) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (_conf_domain_rule_response_mode(domain, response_mode) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add response-mode rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 'a': {\n\t\t\tconst char *address = optarg;\n\t\t\tif (address == NULL) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (_conf_domain_rule_address(domain, address) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add address rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 'h': {\n\t\t\tconst char *https_record = optarg;\n\t\t\tif (https_record == NULL) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (_conf_domain_rule_https_record(domain, https_record) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add https-record rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 'p': {\n\t\t\tconst char *ipsetname = optarg;\n\t\t\tif (ipsetname == NULL) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (_conf_domain_rule_ipset(domain, ipsetname) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add ipset rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 'n': {\n\t\t\tconst char *nameserver_group = optarg;\n\t\t\tif (nameserver_group == NULL) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (_conf_domain_rule_nameserver(domain, nameserver_group) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add nameserver rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 'A': {\n\t\t\tconst char *cname = optarg;\n\n\t\t\tif (_conf_domain_rule_cname(domain, cname) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add cname rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 'd': {\n\t\t\tconst char *yesno = optarg;\n\t\t\tif (_conf_domain_rule_dualstack_selection(domain, yesno) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"set dualstack selection rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 't': {\n\t\t\tconst char *nftsetname = optarg;\n\t\t\tif (nftsetname == NULL) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (_conf_domain_rule_nftset(domain, nftsetname) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add nftset rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 'g': {\n\t\t\tbreak;\n\t\t}\n\t\tcase 251: {\n\t\t\trr_ttl = atoi(optarg);\n\t\t\tbreak;\n\t\t}\n\t\tcase 252: {\n\t\t\trr_ttl_min = atoi(optarg);\n\t\t\tbreak;\n\t\t}\n\t\tcase 253: {\n\t\t\trr_ttl_max = atoi(optarg);\n\t\t\tbreak;\n\t\t}\n\t\tcase 254: {\n\t\t\tif (_conf_domain_rule_no_serve_expired(domain) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"set no-serve-expired rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 255: {\n\t\t\tif (_conf_domain_rule_delete(domain) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"delete domain rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\treturn 0;\n\t\t}\n\t\tcase 256: {\n\t\t\tif (_conf_domain_rule_no_cache(domain) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"set no-cache rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 257: {\n\t\t\tif (_conf_domain_rule_no_ipalias(domain) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"set no-ipalias rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 258: {\n\t\t\tif (_conf_domain_rule_enable_cache(domain) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"set enable-cache rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tcase 259: {\n\t\t\tif (_conf_domain_rule_no_ignore_ip(domain) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"set no-ignore-ip rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tif (optind > optind_last) {\n\t\t\t\ttlog(TLOG_WARN, \"unknown domain-rules option: %s at '%s:%d'.\", argv[optind - 1], conf_get_conf_file(),\n\t\t\t\t\t conf_get_current_lineno());\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toptind_last = optind;\n\t}\n\n\tif (rr_ttl > 0 || rr_ttl_min > 0 || rr_ttl_max > 0) {\n\t\tif (_conf_domain_rule_rr_ttl(domain, rr_ttl, rr_ttl_min, rr_ttl_max) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"set rr-ttl rule failed.\");\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tif (group != NULL) {\n\t\t_config_current_group_pop();\n\t}\n\n\treturn 0;\nerrout:\n\tif (group != NULL) {\n\t\t_config_current_group_pop();\n\t}\n\treturn -1;\n}\n\nvoid *dns_conf_get_domain_rule(const char *domain, enum domain_rule type)\n{\n\tstruct dns_domain_rule *domain_rule = NULL;\n\tchar domain_key[DNS_MAX_CONF_CNAME_LEN] = {0};\n\tint len = 0;\n\tint sub_rule_only = 0;\n\tint root_rule_only = 0;\n\n\tif (_config_setup_domain_key(domain, domain_key, sizeof(domain_key), &len, &root_rule_only, &sub_rule_only) != 0) {\n\t\treturn NULL;\n\t}\n\n\tdomain_rule = art_search(&_config_current_rule_group()->domain_rule.tree, (unsigned char *)domain_key, len);\n\tif (domain_rule == NULL) {\n\t\treturn NULL;\n\t}\n\n\tif (type >= DOMAIN_RULE_MAX || type >= domain_rule->capacity) {\n\t\treturn NULL;\n\t}\n\n\treturn domain_rule->rules[type];\n}\n"
  },
  {
    "path": "src/dns_conf/domain_rule.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_DOMAIN_RULE_H_\n#define _DNS_CONF_DOMAIN_RULE_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_domain_iter_free(void *data, const unsigned char *key, uint32_t key_len, void *value);\n\nvoid *_new_dns_rule_ext(enum domain_rule domain_rule, int ext_size);\nvoid *_new_dns_rule(enum domain_rule domain_rule);\nvoid _dns_rule_get(struct dns_rule *rule);\nvoid _dns_rule_put(struct dns_rule *rule);\n\nint _config_domain_rule_add(const char *domain, enum domain_rule type, void *rule);\nint _config_domain_rule_remove(const char *domain, enum domain_rule type);\nint _config_domain_rule_flag_set(const char *domain, unsigned int flag, unsigned int is_clear);\nint _config_domain_rules(void *data, int argc, char *argv[]);\nint _config_domain_rule_delete(const char *domain);\nint _conf_domain_rule_group(const char *domain, const char *group_name);\nvoid *dns_conf_get_domain_rule(const char *domain, enum domain_rule type);\nint _config_domain_rule_free(struct dns_domain_rule *domain_rule);\n\nint _conf_domain_rule_speed_check(char *domain, const char *mode);\nint _conf_domain_rule_response_mode(char *domain, const char *mode);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/domain_set.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"domain_set.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <getopt.h>\n#include <string.h>\n\nstruct dns_domain_set_name_table dns_domain_set_name_table;\n\nint _config_domain_set(void *data, int argc, char *argv[])\n{\n\tint opt = 0;\n\tuint32_t key = 0;\n\tstruct dns_domain_set_name *domain_set = NULL;\n\tstruct dns_domain_set_name_list *domain_set_name_list = NULL;\n\tchar set_name[DNS_MAX_CNAME_LEN] = {0};\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"name\", required_argument, NULL, 'n'},\n\t\t{\"type\", required_argument, NULL, 't'},\n\t\t{\"file\", required_argument, NULL, 'f'},\n\t\t{NULL, 0, NULL, 0}\n\t};\n\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tdomain_set = zalloc(1, sizeof(*domain_set));\n\tif (domain_set == NULL) {\n\t\ttlog(TLOG_ERROR, \"cannot malloc memory.\");\n\t\tgoto errout;\n\t}\n\tINIT_LIST_HEAD(&domain_set->list);\n\n\toptind = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"n:t:f:\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'n':\n\t\t\tsafe_strncpy(set_name, optarg, DNS_MAX_CNAME_LEN);\n\t\t\tbreak;\n\t\tcase 't': {\n\t\t\tconst char *type = optarg;\n\t\t\tif (strncmp(type, \"list\", 5) == 0) {\n\t\t\t\tdomain_set->type = DNS_DOMAIN_SET_LIST;\n\t\t\t} else if (strncmp(type, \"geosite\", 7) == 0) {\n\t\t\t\tdomain_set->type = DNS_DOMAIN_SET_GEOSITE;\n\t\t\t} else {\n\t\t\t\ttlog(TLOG_ERROR, \"invalid domain set type.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'f':\n\t\t\tconf_get_conf_fullpath(optarg, domain_set->file, DNS_MAX_PATH);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\t/* clang-format on */\n\n\tif (set_name[0] == 0 || domain_set->file[0] == 0) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tif (access(domain_set->file, F_OK) != 0) {\n\t\ttlog(TLOG_ERROR, \"domain set file %s not readable. %s\", domain_set->file, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tkey = hash_string(set_name);\n\thash_for_each_possible(dns_domain_set_name_table.names, domain_set_name_list, node, key)\n\t{\n\t\tif (strcmp(domain_set_name_list->name, set_name) == 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (domain_set_name_list == NULL) {\n\t\tdomain_set_name_list = zalloc(1, sizeof(*domain_set_name_list));\n\t\tif (domain_set_name_list == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"cannot malloc memory.\");\n\t\t\tgoto errout;\n\t\t}\n\t\tINIT_LIST_HEAD(&domain_set_name_list->set_name_list);\n\t\tsafe_strncpy(domain_set_name_list->name, set_name, DNS_MAX_CNAME_LEN);\n\t\thash_add(dns_domain_set_name_table.names, &domain_set_name_list->node, key);\n\t}\n\n\tlist_add_tail(&domain_set->list, &domain_set_name_list->set_name_list);\n\treturn 0;\n\nerrout:\n\tif (domain_set) {\n\t\tfree(domain_set);\n\t}\n\treturn -1;\n}\n\nvoid _config_domain_set_name_table_init(void)\n{\n\thash_init(dns_domain_set_name_table.names);\n}\n\nvoid _config_domain_set_name_table_destroy(void)\n{\n\tstruct dns_domain_set_name_list *set_name_list = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tstruct dns_domain_set_name *set_name = NULL;\n\tstruct dns_domain_set_name *tmp1 = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_domain_set_name_table.names, i, tmp, set_name_list, node)\n\t{\n\t\thlist_del_init(&set_name_list->node);\n\t\tlist_for_each_entry_safe(set_name, tmp1, &set_name_list->set_name_list, list)\n\t\t{\n\t\t\tlist_del(&set_name->list);\n\t\t\tfree(set_name);\n\t\t}\n\n\t\tfree(set_name_list);\n\t}\n}"
  },
  {
    "path": "src/dns_conf/domain_set.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_DOMAIN_SET_H_\n#define _DNS_CONF_DOMAIN_SET_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_domain_set(void *data, int argc, char *argv[]);\n\nvoid _config_domain_set_name_table_init(void);\n\nvoid _config_domain_set_name_table_destroy(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/get_domain.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"get_domain.h\"\n#include \"smartdns/lib/idna.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\nint _get_domain(char *value, char *domain, int max_domain_size, char **ptr_after_domain)\n{\n\tchar *begin = NULL;\n\tchar *end = NULL;\n\tint len = 0;\n\n\tif (value == NULL || domain == NULL) {\n\t\tgoto errout;\n\t}\n\n\t/* first field */\n\tbegin = strstr(value, \"/\");\n\tif (begin == NULL) {\n\t\tsafe_strncpy(domain, \".\", max_domain_size);\n\t\treturn 0;\n\t}\n\n\t/* second field */\n\tbegin++;\n\tend = strstr(begin, \"/\");\n\tif (end == NULL) {\n\t\tgoto errout;\n\t}\n\n\t/* remove prefix . */\n\twhile (*begin == '.') {\n\t\tif (begin + 1 == end) {\n\t\t\tbreak;\n\t\t}\n\t\tbegin++;\n\t}\n\n\t/* Get domain */\n\tlen = end - begin;\n\tif (len >= max_domain_size) {\n\t\ttlog(TLOG_ERROR, \"domain name %s too long\", value);\n\t\tgoto errout;\n\t}\n\n\tsize_t domain_len = max_domain_size;\n\tif (strncmp(begin, \"domain-set:\", sizeof(\"domain-set:\") - 1) == 0) {\n\t\tmemcpy(domain, begin, len);\n\t\tdomain_len = len;\n\t} else {\n\t\tdomain_len = utf8_to_punycode(begin, len, domain, domain_len);\n\t\tif (domain_len <= 0) {\n\t\t\ttlog(TLOG_ERROR, \"domain name %s invalid\", value);\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tdomain[domain_len] = '\\0';\n\n\tif (ptr_after_domain) {\n\t\t*ptr_after_domain = end + 1;\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}"
  },
  {
    "path": "src/dns_conf/get_domain.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_GET_DOMAIN_H_\n#define _DNS_CONF_GET_DOMAIN_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _get_domain(char *value, char *domain, int max_domain_size, char **ptr_after_domain);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/group.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"group.h\"\n#include \"client_rule.h\"\n#include \"dns_conf_group.h\"\n#include \"domain_rule.h\"\n#include \"smartdns/lib/stringutil.h\"\n\n#include <getopt.h>\n\nint _config_group_begin(void *data, int argc, char *argv[])\n{\n\tint opt = 0;\n\n\tconst char *group_name = NULL;\n\tconst char *inherit_group_name = NULL;\n\tif (argc < 2) {\n\t\treturn -1;\n\t}\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"inherit\", required_argument, NULL, 'h'},\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\n\tgroup_name = argv[1];\n\tif (group_name[0] == '\\0') {\n\t\tgroup_name = NULL;\n\t}\n\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"n\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'h': {\n\t\t\tinherit_group_name = optarg;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (_config_current_group_push(group_name, inherit_group_name) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _config_group_end(void *data, int argc, char *argv[])\n{\n\t_config_current_group_pop();\n\treturn 0;\n}\n\nint _config_group_match(void *data, int argc, char *argv[])\n{\n\tint opt = 0;\n\tint optind_last = 0;\n\tstruct dns_conf_group_info *saved_group_info = _config_current_group();\n\tconst char *group_name = saved_group_info->group_name;\n\tchar group_name_buf[DNS_MAX_CONF_CNAME_LEN];\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"domain\", required_argument, NULL, 'd'},\n\t\t{\"client-ip\", required_argument, NULL, 'c'},\n\t\t{\"group\", required_argument, NULL, 'g'},\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\n\tif (argc <= 1 || group_name == NULL) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\t_config_set_current_group(_config_default_group());\n\n\tfor (int i = 1; i < argc - 1; i++) {\n\t\tif (strncmp(argv[i], \"-g\", sizeof(\"-g\")) == 0 || strncmp(argv[i], \"--group\", sizeof(\"--group\")) == 0 ||\n\t\t\tstrncmp(argv[i], \"-group\", sizeof(\"-group\")) == 0) {\n\t\t\tsafe_strncpy(group_name_buf, argv[i + 1], DNS_MAX_CONF_CNAME_LEN);\n\t\t\tgroup_name = group_name_buf;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"g:\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'g': {\n\t\t\tgroup_name = optarg;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'd': {\n\t\t\tconst char *domain = optarg;\n\n\t\t\tif (_conf_domain_rule_group(domain, group_name) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"set group match for domain %s failed.\", optarg);\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'c': {\n\t\t\tchar *client_ip = optarg;\n\t\t\tif (_config_client_rule_group_add(client_ip, group_name) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add group rule failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tif (optind > optind_last) {\n\t\t\t\ttlog(TLOG_WARN, \"unknown group-match option: %s at '%s:%d'.\", argv[optind - 1], conf_get_conf_file(),\n\t\t\t\t\t conf_get_current_lineno());\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\toptind_last = optind;\n\t}\n\n\t_config_set_current_group(saved_group_info);\n\n\treturn 0;\nerrout:\n\t_config_set_current_group(saved_group_info);\n\treturn -1;\n}"
  },
  {
    "path": "src/dns_conf/group.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_GROUP_H_\n#define _DNS_CONF_GROUP_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_group_begin(void *data, int argc, char *argv[]);\n\nint _config_group_match(void *data, int argc, char *argv[]);\n\nint _config_group_end(void *data, int argc, char *argv[]);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/host_file.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"host_file.h\"\n#include \"local_domain.h\"\n#include \"ptr.h\"\n#include \"set_file.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <stdio.h>\n#include <string.h>\n\nstruct dns_hosts_table dns_hosts_table;\nint dns_hosts_record_num;\n\nstatic int _conf_hosts_file_add(const char *file, void *priv)\n{\n\tFILE *fp = NULL;\n\tchar line[MAX_LINE_LEN];\n\tchar ip[DNS_MAX_IPLEN];\n\tchar hostname[DNS_MAX_CNAME_LEN];\n\tint ret = 0;\n\tint line_no = 0;\n\n\tfp = fopen(file, \"r\");\n\tif (fp == NULL) {\n\t\ttlog(TLOG_WARN, \"open file %s error, %s\", file, strerror(errno));\n\t\treturn -1;\n\t}\n\n\tline_no = 0;\n\twhile (fgets(line, MAX_LINE_LEN, fp)) {\n\t\tline_no++;\n\t\tint is_ptr_add = 0;\n\n\t\tchar *token = strtok(line, \" \\t\\n\");\n\t\tif (token == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tsafe_strncpy(ip, token, sizeof(ip) - 1);\n\t\tif (ip[0] == '#') {\n\t\t\tcontinue;\n\t\t}\n\n\t\twhile ((token = strtok(NULL, \" \\t\\n\")) != NULL) {\n\t\t\tsafe_strncpy(hostname, token, sizeof(hostname) - 1);\n\t\t\tchar *skip_hostnames[] = {\n\t\t\t\t\"*\",\n\t\t\t};\n\n\t\t\tint skip = 0;\n\t\t\tfor (size_t i = 0; i < sizeof(skip_hostnames) / sizeof(skip_hostnames[0]); i++) {\n\t\t\t\tif (strncmp(hostname, skip_hostnames[i], DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t\t\t\tskip = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (skip == 1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tret = _conf_host_add(hostname, ip, DNS_HOST_TYPE_HOST, 0);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_WARN, \"add hosts-file failed at '%s:%d'.\", file, line_no);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (is_ptr_add == 1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tret = _conf_ptr_add(hostname, ip, 0);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_WARN, \"add hosts-file failed at '%s:%d'.\", file, line_no);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tis_ptr_add = 1;\n\t\t}\n\t}\n\n\tfclose(fp);\n\n\treturn 0;\n}\n\nint _config_hosts_file(void *data, int argc, char *argv[])\n{\n\tconst char *file_pattern = NULL;\n\tif (argc < 1) {\n\t\treturn -1;\n\t}\n\n\tfile_pattern = argv[1];\n\tif (file_pattern == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn _config_foreach_file(file_pattern, _conf_hosts_file_add, NULL);\n}\n\nvoid _config_host_table_init(void)\n{\n\thash_init(dns_hosts_table.hosts);\n}\n\nvoid _config_host_table_destroy(int only_dynamic)\n{\n\tstruct dns_hosts *host = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_hosts_table.hosts, i, tmp, host, node)\n\t{\n\t\tif (only_dynamic != 0 && host->is_dynamic == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\thlist_del_init(&host->node);\n\t\tfree(host);\n\t}\n\n\tdns_hosts_record_num = 0;\n}\n\nstatic struct dns_hosts *_dns_conf_get_hosts(const char *hostname, int dns_type)\n{\n\tuint32_t key = 0;\n\tstruct dns_hosts *host = NULL;\n\n\tkey = hash_string_case(hostname);\n\tkey = jhash(&dns_type, sizeof(dns_type), key);\n\thash_for_each_possible(dns_hosts_table.hosts, host, node, key)\n\t{\n\t\tif (host->dns_type != dns_type) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (strncasecmp(host->domain, hostname, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn host;\n\t}\n\n\thost = zalloc(1, sizeof(*host));\n\tif (host == NULL) {\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(host->domain, hostname, DNS_MAX_CNAME_LEN);\n\thost->dns_type = dns_type;\n\thost->is_soa = 1;\n\thash_add(dns_hosts_table.hosts, &host->node, key);\n\n\treturn host;\nerrout:\n\tif (host) {\n\t\tfree(host);\n\t}\n\n\treturn NULL;\n}\n\nstatic int _conf_host_expand_local_domain(struct dns_hosts *host)\n{\n\tstruct dns_hosts *host_expand = NULL;\n\tconst char *local_domain = dns_conf_get_local_domain();\n\tchar domain[DNS_MAX_CNAME_LEN] = {0};\n\tint ret;\n\n\tif (local_domain == NULL || local_domain[0] == '\\0') {\n\t\treturn 0;\n\t}\n\n\tif (strstr(host->domain, \".\") != NULL) {\n\t\t// already has domain, skip\n\t\treturn 0;\n\t}\n\n\tret = snprintf(domain, sizeof(domain), \"%s.%s\", host->domain, local_domain);\n\tif (ret < 0 || ret >= (int)sizeof(domain)) {\n\t\ttlog(TLOG_WARN, \"expand host %s with local domain %s failed, too long.\", host->domain, local_domain);\n\t\treturn -1;\n\t}\n\n\thost_expand = _dns_conf_get_hosts(domain, host->dns_type);\n\tif (host_expand == NULL) {\n\t\tgoto errout;\n\t}\n\n\thost_expand->is_soa = host->is_soa;\n\thost_expand->is_dynamic = host->is_dynamic;\n\thost_expand->host_type = host->host_type;\n\tmemcpy(host_expand->ipv6_addr, host->ipv6_addr, DNS_RR_AAAA_LEN);\n\n\tdns_hosts_record_num++;\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nint _conf_host_add(const char *hostname, const char *ip, dns_hosts_type host_type, int is_dynamic)\n{\n\tstruct dns_hosts *host = NULL;\n\tstruct dns_hosts *host_other __attribute__((unused));\n\n\tstruct sockaddr_storage addr;\n\tsocklen_t addr_len = sizeof(addr);\n\tint dns_type = 0;\n\tint dns_type_other = 0;\n\n\tif (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) {\n\t\tgoto errout;\n\t}\n\n\tswitch (addr.ss_family) {\n\tcase AF_INET:\n\t\tdns_type = DNS_T_A;\n\t\tdns_type_other = DNS_T_AAAA;\n\t\tbreak;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)&addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tdns_type = DNS_T_A;\n\t\t\tdns_type_other = DNS_T_AAAA;\n\t\t} else {\n\t\t\tdns_type = DNS_T_AAAA;\n\t\t\tdns_type_other = DNS_T_A;\n\t\t}\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\thost = _dns_conf_get_hosts(hostname, dns_type);\n\tif (host == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (is_dynamic == 1 && host->is_soa == 0 && host->is_dynamic == 0) {\n\t\t/* already set fixed PTR, skip */\n\t\treturn 0;\n\t}\n\n\t/* add this to return SOA when addr is not exist */\n\thost_other = _dns_conf_get_hosts(hostname, dns_type_other);\n\thost->is_dynamic = is_dynamic;\n\thost->host_type = host_type;\n\n\tswitch (addr.ss_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)&addr;\n\t\tmemcpy(host->ipv4_addr, &addr_in->sin_addr.s_addr, 4);\n\t\thost->is_soa = 0;\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)&addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tmemcpy(host->ipv4_addr, addr_in6->sin6_addr.s6_addr + 12, 4);\n\t\t} else {\n\t\t\tmemcpy(host->ipv6_addr, addr_in6->sin6_addr.s6_addr, 16);\n\t\t}\n\t\thost->is_soa = 0;\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t}\n\n\tdns_hosts_record_num += 2;\n\n\t_conf_host_expand_local_domain(host);\n\t_conf_host_expand_local_domain(host_other);\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_conf/host_file.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_HOST_FILE_H_\n#define _DNS_CONF_HOST_FILE_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_hosts_file(void *data, int argc, char *argv[]);\n\nint _conf_host_add(const char *hostname, const char *ip, dns_hosts_type host_type, int is_dynamic);\n\nvoid _config_host_table_init(void);\n\nvoid _config_host_table_destroy(int only_dynamic);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/https_record.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"https_record.h\"\n#include \"domain_rule.h\"\n#include \"get_domain.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\nstatic int _conf_domain_rule_https_copy_alpn(char *alpn_data, int max_alpn_len, const char *alpn_str)\n{\n\tconst char *ptr = NULL;\n\tint alpn_len = 0;\n\tchar *alpn_len_ptr = NULL;\n\tchar *alpn_ptr = NULL;\n\tint total_len = 0;\n\n\tptr = alpn_str;\n\talpn_len_ptr = alpn_data;\n\talpn_ptr = alpn_data + 1;\n\ttotal_len++;\n\n\twhile (*ptr != '\\0') {\n\t\ttotal_len++;\n\t\tif (total_len > max_alpn_len) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (*ptr == ',') {\n\t\t\t*alpn_len_ptr = alpn_len;\n\t\t\talpn_len = 0;\n\t\t\talpn_len_ptr = alpn_ptr;\n\t\t\tptr++;\n\t\t\talpn_ptr++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t*alpn_ptr = *ptr;\n\t\talpn_len++;\n\t\talpn_ptr++;\n\t\tptr++;\n\t}\n\n\t*alpn_len_ptr = alpn_len;\n\treturn total_len;\n}\n\nint _conf_domain_rule_https_record(const char *domain, const char *host)\n{\n\tstruct dns_https_record_rule *https_record_rule = NULL;\n\tstruct dns_https_record *record = NULL;\n\tenum domain_rule type = DOMAIN_RULE_HTTPS;\n\tchar buff[4096];\n\tint key_num = 0;\n\tchar *keys[16];\n\tchar *value[16];\n\tint priority = -1;\n\t/*mode_type, 0: alias mode, 1: service mode */\n\tint mode_type = 0;\n\tint is_new = 0;\n\n\tsafe_strncpy(buff, host, sizeof(buff));\n\n\thttps_record_rule = dns_conf_get_domain_rule(domain, type);\n\tif (https_record_rule == NULL) {\n\t\thttps_record_rule = _new_dns_rule(type);\n\t\tif (https_record_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\tINIT_LIST_HEAD(&https_record_rule->record_list);\n\t\tis_new = 1;\n\t}\n\n\trecord = zalloc(1, sizeof(*record));\n\tif (record == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (conf_parse_key_values(buff, &key_num, keys, value) != 0) {\n\t\ttlog(TLOG_ERROR, \"input format error, don't have key-value.\");\n\t\tgoto errout;\n\t}\n\n\tif (key_num < 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tfor (int i = 0; i < key_num; i++) {\n\t\tconst char *key = keys[i];\n\t\tconst char *val = value[i];\n\t\tif (strncmp(key, \"#\", sizeof(\"#\")) == 0) {\n\t\t\tif (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_ADDR_HTTPS_SOA, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t} else if (strncmp(key, \"-\", sizeof(\"-\")) == 0) {\n\t\t\tif (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_ADDR_HTTPS_IGN, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_ADDR_IGN, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t} else if (strncmp(key, \"target\", sizeof(\"target\")) == 0) {\n\t\t\tsafe_strncpy(record->target, val, DNS_MAX_CONF_CNAME_LEN);\n\t\t\trecord->enable = 1;\n\t\t} else if (strncmp(key, \"noipv4hint\", sizeof(\"noipv4hint\")) == 0) {\n\t\t\thttps_record_rule->filter.no_ipv4hint = 1;\n\t\t} else if (strncmp(key, \"noipv6hint\", sizeof(\"noipv6hint\")) == 0) {\n\t\t\thttps_record_rule->filter.no_ipv6hint = 1;\n\t\t} else if (strncmp(key, \"noiphint\", sizeof(\"noiphint\")) == 0) {\n\t\t\thttps_record_rule->filter.no_ipv4hint = 1;\n\t\t\thttps_record_rule->filter.no_ipv6hint = 1;\n\t\t} else if (strncmp(key, \"noech\", sizeof(\"noech\")) == 0) {\n\t\t\thttps_record_rule->filter.no_ech = 1;\n\t\t} else {\n\t\t\tmode_type = 1;\n\t\t\trecord->enable = 1;\n\t\t\tif (strncmp(key, \"priority\", sizeof(\"priority\")) == 0) {\n\t\t\t\tpriority = atoi(val);\n\t\t\t} else if (strncmp(key, \"port\", sizeof(\"port\")) == 0) {\n\t\t\t\trecord->port = atoi(val);\n\n\t\t\t} else if (strncmp(key, \"alpn\", sizeof(\"alpn\")) == 0) {\n\t\t\t\tint alpn_len = _conf_domain_rule_https_copy_alpn(record->alpn, DNS_MAX_ALPN_LEN, val);\n\t\t\t\tif (alpn_len <= 0) {\n\t\t\t\t\ttlog(TLOG_ERROR, \"invalid option value for %s.\", key);\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\trecord->alpn_len = alpn_len;\n\t\t\t} else if (strncmp(key, \"ech\", sizeof(\"ech\")) == 0) {\n\t\t\t\tint ech_len = SSL_base64_decode(val, record->ech, DNS_MAX_ECH_LEN);\n\t\t\t\tif (ech_len < 0) {\n\t\t\t\t\ttlog(TLOG_ERROR, \"invalid option value for %s.\", key);\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\trecord->ech_len = ech_len;\n\t\t\t} else if (strncmp(key, \"ipv4hint\", sizeof(\"ipv4hint\")) == 0) {\n\t\t\t\tint addr_len = DNS_RR_A_LEN;\n\t\t\t\tif (get_raw_addr_by_ip(val, record->ipv4_addr, &addr_len) != 0) {\n\t\t\t\t\ttlog(TLOG_ERROR, \"invalid option value for %s, value: %s\", key, val);\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\n\t\t\t\tif (addr_len != DNS_RR_A_LEN) {\n\t\t\t\t\ttlog(TLOG_ERROR, \"invalid option value for %s, value: %s\", key, val);\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\trecord->has_ipv4 = 1;\n\t\t\t} else if (strncmp(key, \"ipv6hint\", sizeof(\"ipv6hint\")) == 0) {\n\t\t\t\tint addr_len = DNS_RR_AAAA_LEN;\n\t\t\t\tif (get_raw_addr_by_ip(val, record->ipv6_addr, &addr_len) != 0) {\n\t\t\t\t\ttlog(TLOG_ERROR, \"invalid option value for %s, value: %s\", key, val);\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\n\t\t\t\tif (addr_len != DNS_RR_AAAA_LEN) {\n\t\t\t\t\ttlog(TLOG_ERROR, \"invalid option value for %s, value: %s\", key, val);\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\trecord->has_ipv6 = 1;\n\t\t\t} else {\n\t\t\t\ttlog(TLOG_WARN, \"invalid parameter %s for https-record.\", key);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (mode_type == 0) {\n\t\tif (priority < 0) {\n\t\t\tpriority = 0;\n\t\t}\n\t} else {\n\t\tif (priority < 0) {\n\t\t\tpriority = 1;\n\t\t} else if (priority == 0) {\n\t\t\ttlog(TLOG_WARN, \"invalid priority %d for https-record.\", priority);\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\trecord->priority = priority;\n\tlist_add_tail(&record->list, &https_record_rule->record_list);\n\n\tif (is_new) {\n\t\tif (_config_domain_rule_add(domain, type, https_record_rule) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\t_dns_rule_put(&https_record_rule->head);\n\t\thttps_record_rule = NULL;\n\t}\n\n\treturn 0;\nerrout:\n\tif (is_new && https_record_rule) {\n\t\t_dns_rule_put(&https_record_rule->head);\n\t}\n\tif (record && record->list.next == NULL) {\n\t\tfree(record);\n\t}\n\n\treturn -1;\n}\n\nint _config_https_record(void *data, int argc, char *argv[])\n{\n\tchar *value = NULL;\n\tchar domain[DNS_MAX_CONF_CNAME_LEN];\n\tint ret = -1;\n\n\tif (argc < 2) {\n\t\tgoto errout;\n\t}\n\n\tvalue = argv[1];\n\tif (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {\n\t\tgoto errout;\n\t}\n\n\tret = _conf_domain_rule_https_record(domain, value);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\ttlog(TLOG_ERROR, \"add https-record %s:%s failed\", domain, value);\n\treturn -1;\n}"
  },
  {
    "path": "src/dns_conf/https_record.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_HTTPS_RECORD_H_\n#define _DNS_CONF_HTTPS_RECORD_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_https_record(void *data, int argc, char *argv[]);\nint _conf_domain_rule_https_record(const char *domain, const char *host);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/ip_alias.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ip_alias.h\"\n#include \"ip_rule.h\"\n\nstatic int _config_ip_alias_add_ip_callback(const char *ip_cidr, void *priv)\n{\n\treturn _config_ip_rule_alias_add_ip(ip_cidr, (struct ip_rule_alias *)priv);\n}\n\nint _conf_ip_alias(const char *ip_cidr, const char *ips)\n{\n\tstruct ip_rule_alias *ip_alias = NULL;\n\tchar *target_ips = NULL;\n\tint ret = 0;\n\n\tif (ip_cidr == NULL || ips == NULL) {\n\t\tgoto errout;\n\t}\n\n\tip_alias = _new_dns_ip_rule(IP_RULE_ALIAS);\n\tif (ip_alias == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (strncmp(ips, \"ip-set:\", sizeof(\"ip-set:\") - 1) == 0) {\n\t\tif (_config_ip_rule_set_each(ips + sizeof(\"ip-set:\") - 1, _config_ip_alias_add_ip_callback, ip_alias) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t} else {\n\t\ttarget_ips = strdup(ips);\n\t\tif (target_ips == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tfor (char *tok = strtok(target_ips, \",\"); tok != NULL; tok = strtok(NULL, \",\")) {\n\t\t\tret = _config_ip_rule_alias_add_ip(tok, ip_alias);\n\t\t\tif (ret != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (_config_ip_rule_add(ip_cidr, IP_RULE_ALIAS, ip_alias) != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_ip_rule_put(&ip_alias->head);\n\tif (target_ips) {\n\t\tfree(target_ips);\n\t}\n\n\treturn 0;\nerrout:\n\n\tif (ip_alias) {\n\t\t_dns_ip_rule_put(&ip_alias->head);\n\t}\n\n\tif (target_ips) {\n\t\tfree(target_ips);\n\t}\n\n\treturn -1;\n}\n\nint _config_ip_alias(void *data, int argc, char *argv[])\n{\n\tif (argc <= 2) {\n\t\treturn -1;\n\t}\n\n\treturn _conf_ip_alias(argv[1], argv[2]);\n}"
  },
  {
    "path": "src/dns_conf/ip_alias.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_IP_ALIAS_H_\n#define _DNS_CONF_IP_ALIAS_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _conf_ip_alias(const char *ip_cidr, const char *ips);\n\nint _config_ip_alias(void *data, int argc, char *argv[]);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/ip_rule.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ip_rule.h\"\n#include \"dns_conf_group.h\"\n#include \"ip_alias.h\"\n#include \"set_file.h\"\n#include \"smartdns/util.h\"\n\n#include <getopt.h>\n\nint _config_ip_rule_set_each(const char *ip_set, set_rule_add_func callback, void *priv)\n{\n\tstruct dns_ip_set_name_list *set_name_list = NULL;\n\tstruct dns_ip_set_name *set_name_item = NULL;\n\n\tuint32_t key = 0;\n\n\tkey = hash_string(ip_set);\n\thash_for_each_possible(dns_ip_set_name_table.names, set_name_list, node, key)\n\t{\n\t\tif (strcmp(set_name_list->name, ip_set) == 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (set_name_list == NULL) {\n\t\ttlog(TLOG_WARN, \"ip set %s not found.\", ip_set);\n\t\treturn -1;\n\t}\n\n\tlist_for_each_entry(set_name_item, &set_name_list->set_name_list, list)\n\t{\n\t\tswitch (set_name_item->type) {\n\t\tcase DNS_IP_SET_LIST:\n\t\t\tif (_config_set_rule_each_from_list(set_name_item->file, callback, priv) != 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\ttlog(TLOG_WARN, \"ip set %s type %d not support.\", set_name_list->name, set_name_item->type);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic void _dns_iplist_ip_address_add(struct dns_iplist_ip_addresses *iplist, unsigned char addr[], int addr_len)\n{\n\tstruct dns_iplist_ip_address *new_ipaddr = realloc(iplist->ipaddr, (iplist->ipaddr_num + 1) * sizeof(struct dns_iplist_ip_address));\n\tif (new_ipaddr == NULL) {\n\t\treturn;\n\t}\n\tiplist->ipaddr = new_ipaddr;\n\tmemset(&iplist->ipaddr[iplist->ipaddr_num], 0, sizeof(struct dns_iplist_ip_address));\n\tiplist->ipaddr[iplist->ipaddr_num].addr_len = addr_len;\n\tmemcpy(iplist->ipaddr[iplist->ipaddr_num].addr, addr, addr_len);\n\tiplist->ipaddr_num++;\n}\n\nint _config_ip_rules(void *data, int argc, char *argv[])\n{\n\tint opt = 0;\n\tint optind_last = 0;\n\tchar *ip_cidr = argv[1];\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"blacklist-ip\", no_argument, NULL, 'b'},\n\t\t{\"whitelist-ip\", no_argument, NULL, 'w'},\n\t\t{\"bogus-nxdomain\", no_argument, NULL, 'n'},\n\t\t{\"ignore-ip\", no_argument, NULL, 'i'},\n\t\t{\"ip-alias\", required_argument, NULL, 'a'},\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\t/* process extra options */\n\toptind = 1;\n\toptind_last = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'b': {\n\t\t\tif (_config_ip_rule_flag_set(ip_cidr, IP_RULE_FLAG_BLACKLIST, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'w': {\n\t\t\tif (_config_ip_rule_flag_set(ip_cidr, IP_RULE_FLAG_WHITELIST, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'n': {\n\t\t\tif (_config_ip_rule_flag_set(ip_cidr, IP_RULE_FLAG_BOGUS, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'i': {\n\t\t\tif (_config_ip_rule_flag_set(ip_cidr, IP_RULE_FLAG_IP_IGNORE, 0) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'a': {\n\t\t\tif (_conf_ip_alias(ip_cidr, optarg) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tif (optind > optind_last) {\n\t\t\t\ttlog(TLOG_WARN, \"unknown ip-rules option: %s at '%s:%d'.\", argv[optind - 1], conf_get_conf_file(),\n\t\t\t\t\t conf_get_current_lineno());\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toptind_last = optind;\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstatic int _config_ip_rules_free(struct dns_ip_rules *ip_rules)\n{\n\tint i = 0;\n\n\tif (ip_rules == NULL) {\n\t\treturn 0;\n\t}\n\n\tfor (i = 0; i < IP_RULE_MAX; i++) {\n\t\tif (ip_rules->rules[i] == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_dns_ip_rule_put(ip_rules->rules[i]);\n\t\tip_rules->rules[i] = NULL;\n\t}\n\n\tfree(ip_rules);\n\treturn 0;\n}\n\nstatic radix_node_t *_create_addr_node(const char *addr)\n{\n\tradix_node_t *node = NULL;\n\tvoid *p = NULL;\n\tprefix_t prefix;\n\tconst char *errmsg = NULL;\n\tradix_tree_t *tree = NULL;\n\n\tp = prefix_pton(addr, -1, &prefix, &errmsg);\n\tif (p == NULL) {\n\t\treturn NULL;\n\t}\n\n\tswitch (prefix.family) {\n\tcase AF_INET:\n\t\ttree = _config_current_rule_group()->address_rule.ipv4;\n\t\tbreak;\n\tcase AF_INET6:\n\t\ttree = _config_current_rule_group()->address_rule.ipv6;\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t}\n\n\tnode = radix_lookup(tree, &prefix);\n\treturn node;\n}\n\nstatic int _config_ip_rule_flag_callback(const char *ip_cidr, void *priv)\n{\n\tstruct dns_set_rule_flags_callback_args *args = (struct dns_set_rule_flags_callback_args *)priv;\n\treturn _config_ip_rule_flag_set(ip_cidr, args->flags, args->is_clear_flag);\n}\n\nint _config_ip_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear)\n{\n\tstruct dns_ip_rules *ip_rules = NULL;\n\tstruct dns_ip_rules *add_ip_rules = NULL;\n\tstruct ip_rule_flags *ip_rule_flags = NULL;\n\tradix_node_t *node = NULL;\n\n\tif (strncmp(ip_cidr, \"ip-set:\", sizeof(\"ip-set:\") - 1) == 0) {\n\t\tstruct dns_set_rule_flags_callback_args args;\n\t\targs.flags = flag;\n\t\targs.is_clear_flag = is_clear;\n\t\treturn _config_ip_rule_set_each(ip_cidr + sizeof(\"ip-set:\") - 1, _config_ip_rule_flag_callback, &args);\n\t}\n\n\t/* Get existing or create domain rule */\n\tnode = _create_addr_node(ip_cidr);\n\tif (node == NULL) {\n\t\ttlog(TLOG_ERROR, \"create addr node failed.\");\n\t\tgoto errout;\n\t}\n\n\tip_rules = node->data;\n\tif (ip_rules == NULL) {\n\t\tadd_ip_rules = zalloc(1, sizeof(*add_ip_rules));\n\t\tif (add_ip_rules == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\tip_rules = add_ip_rules;\n\t\tnode->data = ip_rules;\n\t}\n\n\t/* add new rule to domain */\n\tif (ip_rules->rules[IP_RULE_FLAGS] == NULL) {\n\t\tip_rule_flags = _new_dns_ip_rule(IP_RULE_FLAGS);\n\t\tip_rule_flags->flags = 0;\n\t\tip_rules->rules[IP_RULE_FLAGS] = &ip_rule_flags->head;\n\t}\n\n\tip_rule_flags = container_of(ip_rules->rules[IP_RULE_FLAGS], struct ip_rule_flags, head);\n\tif (is_clear == false) {\n\t\tip_rule_flags->flags |= flag;\n\t} else {\n\t\tip_rule_flags->flags &= ~flag;\n\t}\n\tip_rule_flags->is_flag_set |= flag;\n\n\treturn 0;\nerrout:\n\tif (add_ip_rules) {\n\t\tfree(add_ip_rules);\n\t}\n\n\ttlog(TLOG_ERROR, \"set ip %s flags failed\", ip_cidr);\n\n\treturn 0;\n}\n\nstatic int _config_ip_rule_add_callback(const char *ip_cidr, void *priv)\n{\n\tstruct dns_set_rule_add_callback_args *args = (struct dns_set_rule_add_callback_args *)priv;\n\treturn _config_ip_rule_add(ip_cidr, args->type, args->rule);\n}\n\nint _config_ip_rule_add(const char *ip_cidr, enum ip_rule type, void *rule)\n{\n\tstruct dns_ip_rules *ip_rules = NULL;\n\tstruct dns_ip_rules *add_ip_rules = NULL;\n\tradix_node_t *node = NULL;\n\n\tif (ip_cidr == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (type >= IP_RULE_MAX) {\n\t\tgoto errout;\n\t}\n\n\tif (strncmp(ip_cidr, \"ip-set:\", sizeof(\"ip-set:\") - 1) == 0) {\n\t\tstruct dns_set_rule_add_callback_args args;\n\t\targs.type = type;\n\t\targs.rule = rule;\n\t\treturn _config_ip_rule_set_each(ip_cidr + sizeof(\"ip-set:\") - 1, _config_ip_rule_add_callback, &args);\n\t}\n\n\t/* Get existing or create domain rule */\n\tnode = _create_addr_node(ip_cidr);\n\tif (node == NULL) {\n\t\ttlog(TLOG_ERROR, \"create addr node failed.\");\n\t\tgoto errout;\n\t}\n\n\tip_rules = node->data;\n\tif (ip_rules == NULL) {\n\t\tadd_ip_rules = zalloc(1, sizeof(*add_ip_rules));\n\t\tif (add_ip_rules == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\tip_rules = add_ip_rules;\n\t\tnode->data = ip_rules;\n\t}\n\n\t/* add new rule to domain */\n\tif (ip_rules->rules[type]) {\n\t\t_dns_ip_rule_put(ip_rules->rules[type]);\n\t\tip_rules->rules[type] = NULL;\n\t}\n\n\tip_rules->rules[type] = rule;\n\t_dns_ip_rule_get(rule);\n\n\treturn 0;\nerrout:\n\tif (add_ip_rules) {\n\t\tfree(add_ip_rules);\n\t}\n\n\ttlog(TLOG_ERROR, \"add ip %s rule failed\", ip_cidr);\n\treturn -1;\n}\n\nstatic void *_new_dns_ip_rule_ext(enum ip_rule ip_rule, int ext_size)\n{\n\tstruct dns_ip_rule *rule;\n\tint size = 0;\n\n\tif (ip_rule >= IP_RULE_MAX) {\n\t\treturn NULL;\n\t}\n\n\tswitch (ip_rule) {\n\tcase IP_RULE_FLAGS:\n\t\tsize = sizeof(struct ip_rule_flags);\n\t\tbreak;\n\tcase IP_RULE_ALIAS:\n\t\tsize = sizeof(struct ip_rule_alias);\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t}\n\n\tsize += ext_size;\n\trule = zalloc(1, size);\n\tif (!rule) {\n\t\treturn NULL;\n\t}\n\trule->rule = ip_rule;\n\tatomic_set(&rule->refcnt, 1);\n\treturn rule;\n}\n\nvoid *_new_dns_ip_rule(enum ip_rule ip_rule)\n{\n\treturn _new_dns_ip_rule_ext(ip_rule, 0);\n}\n\nvoid _dns_ip_rule_get(struct dns_ip_rule *rule)\n{\n\tatomic_inc(&rule->refcnt);\n}\n\nvoid _dns_ip_rule_put(struct dns_ip_rule *rule)\n{\n\tif (atomic_dec_and_test(&rule->refcnt)) {\n\t\tif (rule->rule == IP_RULE_ALIAS) {\n\t\t\tstruct ip_rule_alias *alias = container_of(rule, struct ip_rule_alias, head);\n\t\t\tif (alias->ip_alias.ipaddr) {\n\t\t\t\tfree(alias->ip_alias.ipaddr);\n\t\t\t\talias->ip_alias.ipaddr = NULL;\n\t\t\t\talias->ip_alias.ipaddr_num = 0;\n\t\t\t}\n\t\t}\n\t\tfree(rule);\n\t}\n}\n\nint _config_ip_rule_alias_add_ip(const char *ip, struct ip_rule_alias *ip_alias)\n{\n\tstruct sockaddr_storage addr;\n\tsocklen_t addr_len = sizeof(addr);\n\tunsigned char *paddr = NULL;\n\tint ret = 0;\n\n\tret = getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"ip is invalid: %s\", ip);\n\t\tgoto errout;\n\t}\n\n\tswitch (addr.ss_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)&addr;\n\t\tpaddr = (unsigned char *)&(addr_in->sin_addr.s_addr);\n\t\t_dns_iplist_ip_address_add(&ip_alias->ip_alias, paddr, DNS_RR_A_LEN);\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)&addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tpaddr = addr_in6->sin6_addr.s6_addr + 12;\n\t\t\t_dns_iplist_ip_address_add(&ip_alias->ip_alias, paddr, DNS_RR_A_LEN);\n\t\t} else {\n\t\t\tpaddr = addr_in6->sin6_addr.s6_addr;\n\t\t\t_dns_iplist_ip_address_add(&ip_alias->ip_alias, paddr, DNS_RR_AAAA_LEN);\n\t\t}\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nint _config_blacklist_ip(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\treturn -1;\n\t}\n\n\treturn _config_ip_rule_flag_set(argv[1], IP_RULE_FLAG_BLACKLIST, 0);\n}\n\nint _config_bogus_nxdomain(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\treturn -1;\n\t}\n\n\treturn _config_ip_rule_flag_set(argv[1], IP_RULE_FLAG_BOGUS, 0);\n}\n\nint _config_ip_ignore(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\treturn -1;\n\t}\n\n\treturn _config_ip_rule_flag_set(argv[1], IP_RULE_FLAG_IP_IGNORE, 0);\n}\n\nint _config_whitelist_ip(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\treturn -1;\n\t}\n\n\treturn _config_ip_rule_flag_set(argv[1], IP_RULE_FLAG_WHITELIST, 0);\n}\n\nvoid _config_ip_iter_free(radix_node_t *node, void *cbctx)\n{\n\tstruct dns_ip_rules *ip_rules = NULL;\n\tif (node == NULL) {\n\t\treturn;\n\t}\n\n\tif (node->data == NULL) {\n\t\treturn;\n\t}\n\n\tip_rules = node->data;\n\t_config_ip_rules_free(ip_rules);\n\tnode->data = NULL;\n}\n"
  },
  {
    "path": "src/dns_conf/ip_rule.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_IP_RULE_H_\n#define _DNS_CONF_IP_RULE_H_\n\n#include \"dns_conf.h\"\n#include \"set_file.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _config_ip_iter_free(radix_node_t *node, void *cbctx);\n\nint _config_ip_rule_flag_set(const char *ip_cidr, unsigned int flag, unsigned int is_clear);\nint _config_ip_rule_set_each(const char *ip_set, set_rule_add_func callback, void *priv);\n\nint _config_blacklist_ip(void *data, int argc, char *argv[]);\nint _config_bogus_nxdomain(void *data, int argc, char *argv[]);\nint _config_ip_ignore(void *data, int argc, char *argv[]);\nint _config_whitelist_ip(void *data, int argc, char *argv[]);\nint _config_ip_rules(void *data, int argc, char *argv[]);\n\nint _config_ip_rule_alias_add_ip(const char *ip, struct ip_rule_alias *ip_alias);\nint _config_ip_rule_add(const char *ip_cidr, enum ip_rule type, void *rule);\n\nvoid *_new_dns_ip_rule(enum ip_rule ip_rule);\nvoid _dns_ip_rule_get(struct dns_ip_rule *rule);\nvoid _dns_ip_rule_put(struct dns_ip_rule *rule);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/ip_set.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ip_set.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <getopt.h>\n#include <string.h>\n\nstruct dns_ip_set_name_table dns_ip_set_name_table;\n\nint _config_ip_set(void *data, int argc, char *argv[])\n{\n\tint opt = 0;\n\tuint32_t key = 0;\n\tstruct dns_ip_set_name *ip_set = NULL;\n\tstruct dns_ip_set_name_list *ip_set_name_list = NULL;\n\tchar set_name[DNS_MAX_CNAME_LEN] = {0};\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"name\", required_argument, NULL, 'n'},\n\t\t{\"type\", required_argument, NULL, 't'},\n\t\t{\"file\", required_argument, NULL, 'f'},\n\t\t{NULL, 0, NULL, 0}\n\t};\n\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tip_set = zalloc(1, sizeof(*ip_set));\n\tif (ip_set == NULL) {\n\t\ttlog(TLOG_ERROR, \"cannot malloc memory.\");\n\t\tgoto errout;\n\t}\n\tINIT_LIST_HEAD(&ip_set->list);\n\n\toptind = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"n:t:f:\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'n':\n\t\t\tsafe_strncpy(set_name, optarg, DNS_MAX_CNAME_LEN);\n\t\t\tbreak;\n\t\tcase 't': {\n\t\t\tconst char *type = optarg;\n\t\t\tif (strncmp(type, \"list\", 5) == 0) {\n\t\t\t\tip_set->type = DNS_IP_SET_LIST;\n\t\t\t} else {\n\t\t\t\ttlog(TLOG_ERROR, \"invalid domain set type.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'f':\n\t\t\tconf_get_conf_fullpath(optarg, ip_set->file, DNS_MAX_PATH);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\t/* clang-format on */\n\n\tif (access(ip_set->file, F_OK) != 0) {\n\t\ttlog(TLOG_ERROR, \"ip set file %s not readable. %s\", ip_set->file, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (set_name[0] == 0 || ip_set->file[0] == 0) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tkey = hash_string(set_name);\n\thash_for_each_possible(dns_ip_set_name_table.names, ip_set_name_list, node, key)\n\t{\n\t\tif (strcmp(ip_set_name_list->name, set_name) == 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (ip_set_name_list == NULL) {\n\t\tip_set_name_list = zalloc(1, sizeof(*ip_set_name_list));\n\t\tif (ip_set_name_list == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"cannot malloc memory.\");\n\t\t\tgoto errout;\n\t\t}\n\t\tINIT_LIST_HEAD(&ip_set_name_list->set_name_list);\n\t\tsafe_strncpy(ip_set_name_list->name, set_name, DNS_MAX_CNAME_LEN);\n\t\thash_add(dns_ip_set_name_table.names, &ip_set_name_list->node, key);\n\t}\n\n\tlist_add_tail(&ip_set->list, &ip_set_name_list->set_name_list);\n\treturn 0;\n\nerrout:\n\tif (ip_set) {\n\t\tfree(ip_set);\n\t}\n\n\tif (ip_set_name_list != NULL) {\n\t\tfree(ip_set_name_list);\n\t}\n\treturn -1;\n}\n\nvoid _config_ip_set_name_table_init(void)\n{\n\thash_init(dns_ip_set_name_table.names);\n}\n\nvoid _config_ip_set_name_table_destroy(void)\n{\n\tstruct dns_ip_set_name_list *set_name_list = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tstruct dns_ip_set_name *set_name = NULL;\n\tstruct dns_ip_set_name *tmp1 = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_ip_set_name_table.names, i, tmp, set_name_list, node)\n\t{\n\t\thlist_del_init(&set_name_list->node);\n\t\tlist_for_each_entry_safe(set_name, tmp1, &set_name_list->set_name_list, list)\n\t\t{\n\t\t\tlist_del(&set_name->list);\n\t\t\tfree(set_name);\n\t\t}\n\n\t\tfree(set_name_list);\n\t}\n}"
  },
  {
    "path": "src/dns_conf/ip_set.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_IP_SET_H_\n#define _DNS_CONF_IP_SET_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_ip_set(void *data, int argc, char *argv[]);\n\nvoid _config_ip_set_name_table_init(void);\n\nvoid _config_ip_set_name_table_destroy(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/ipset.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ipset.h\"\n#include \"dns_conf_group.h\"\n#include \"domain_rule.h\"\n#include \"get_domain.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n/* ipset */\nstruct dns_ipset_table {\n\tDECLARE_HASHTABLE(ipset, 8);\n};\nstatic struct dns_ipset_table dns_ipset_table;\n\nint _config_ipset_init(void)\n{\n\thash_init(dns_ipset_table.ipset);\n\treturn 0;\n}\n\nvoid _config_ipset_table_destroy(void)\n{\n\tstruct dns_ipset_name *ipset_name = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_ipset_table.ipset, i, tmp, ipset_name, node)\n\t{\n\t\thlist_del_init(&ipset_name->node);\n\t\tfree(ipset_name);\n\t}\n}\n\nconst char *_dns_conf_get_ipset(const char *ipsetname)\n{\n\tuint32_t key = 0;\n\tstruct dns_ipset_name *ipset_name = NULL;\n\n\tkey = hash_string(ipsetname);\n\thash_for_each_possible(dns_ipset_table.ipset, ipset_name, node, key)\n\t{\n\t\tif (strncmp(ipset_name->ipsetname, ipsetname, DNS_MAX_IPSET_NAMELEN) == 0) {\n\t\t\treturn ipset_name->ipsetname;\n\t\t}\n\t}\n\n\tipset_name = zalloc(1, sizeof(*ipset_name));\n\tif (ipset_name == NULL) {\n\t\tgoto errout;\n\t}\n\n\tkey = hash_string(ipsetname);\n\tsafe_strncpy(ipset_name->ipsetname, ipsetname, DNS_MAX_IPSET_NAMELEN);\n\thash_add(dns_ipset_table.ipset, &ipset_name->node, key);\n\n\treturn ipset_name->ipsetname;\nerrout:\n\tif (ipset_name) {\n\t\tfree(ipset_name);\n\t}\n\n\treturn NULL;\n}\n\nint _conf_domain_rule_ipset(char *domain, const char *ipsetname)\n{\n\tstruct dns_ipset_rule *ipset_rule = NULL;\n\tconst char *ipset = NULL;\n\tchar *copied_name = NULL;\n\tenum domain_rule type = 0;\n\tint ignore_flag = 0;\n\tint ret = -1;\n\n\tcopied_name = strdup(ipsetname);\n\n\tif (copied_name == NULL) {\n\t\tgoto errout;\n\t}\n\n\tfor (char *tok = strtok(copied_name, \",\"); tok; tok = strtok(NULL, \",\")) {\n\t\tif (tok[0] == '#') {\n\t\t\tif (strncmp(tok, \"#6:\", 3U) == 0) {\n\t\t\t\ttype = DOMAIN_RULE_IPSET_IPV6;\n\t\t\t\tignore_flag = DOMAIN_FLAG_IPSET_IPV6_IGN;\n\t\t\t} else if (strncmp(tok, \"#4:\", 3U) == 0) {\n\t\t\t\ttype = DOMAIN_RULE_IPSET_IPV4;\n\t\t\t\tignore_flag = DOMAIN_FLAG_IPSET_IPV4_IGN;\n\t\t\t} else {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\ttok += 3;\n\t\t} else {\n\t\t\ttype = DOMAIN_RULE_IPSET;\n\t\t\tignore_flag = DOMAIN_FLAG_IPSET_IGN;\n\t\t}\n\n\t\tif (strncmp(tok, \"-\", 1) == 0) {\n\t\t\t_config_domain_rule_flag_set(domain, ignore_flag, 0);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* new ipset domain */\n\t\tipset = _dns_conf_get_ipset(tok);\n\t\tif (ipset == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tipset_rule = _new_dns_rule(type);\n\t\tif (ipset_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tipset_rule->ipsetname = ipset;\n\n\t\tif (_config_domain_rule_add(domain, type, ipset_rule) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\t_dns_rule_put(&ipset_rule->head);\n\t\tipset_rule = NULL;\n\t}\n\n\tret = 0;\n\tgoto clear;\n\nerrout:\n\ttlog(TLOG_ERROR, \"add ipset %s failed\", ipsetname);\n\n\tif (ipset_rule) {\n\t\t_dns_rule_put(&ipset_rule->head);\n\t}\n\nclear:\n\tif (copied_name) {\n\t\tfree(copied_name);\n\t}\n\n\treturn ret;\n}\n\nstatic int _config_ipset_setvalue(struct dns_ipset_names *ipsets, const char *ipsetvalue)\n{\n\tchar *copied_name = NULL;\n\tconst char *ipset = NULL;\n\tstruct dns_ipset_rule *ipset_rule_array[2] = {NULL, NULL};\n\tchar *ipset_rule_enable_array[2] = {NULL, NULL};\n\tint ipset_num = 0;\n\n\tcopied_name = strdup(ipsetvalue);\n\n\tif (copied_name == NULL) {\n\t\tgoto errout;\n\t}\n\n\tfor (char *tok = strtok(copied_name, \",\"); tok && ipset_num <= 2; tok = strtok(NULL, \",\")) {\n\t\tif (tok[0] == '#') {\n\t\t\tif (strncmp(tok, \"#6:\", 3U) == 0) {\n\t\t\t\tipset_rule_array[ipset_num] = &ipsets->ipv6;\n\t\t\t\tipset_rule_enable_array[ipset_num] = &ipsets->ipv6_enable;\n\t\t\t\tipset_num++;\n\t\t\t} else if (strncmp(tok, \"#4:\", 3U) == 0) {\n\t\t\t\tipset_rule_array[ipset_num] = &ipsets->ipv4;\n\t\t\t\tipset_rule_enable_array[ipset_num] = &ipsets->ipv4_enable;\n\t\t\t\tipset_num++;\n\t\t\t} else {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\ttok += 3;\n\t\t}\n\n\t\tif (ipset_num == 0) {\n\t\t\tipset_rule_array[0] = &ipsets->inet;\n\t\t\tipset_rule_enable_array[0] = &ipsets->inet_enable;\n\t\t\tipset_num = 1;\n\t\t}\n\n\t\tif (strncmp(tok, \"-\", 1) == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* new ipset domain */\n\t\tipset = _dns_conf_get_ipset(tok);\n\t\tif (ipset == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tfor (int i = 0; i < ipset_num; i++) {\n\t\t\tipset_rule_array[i]->ipsetname = ipset;\n\t\t\t*ipset_rule_enable_array[i] = 1;\n\t\t}\n\n\t\tipset_num = 0;\n\t}\n\n\tfree(copied_name);\n\treturn 0;\nerrout:\n\tif (copied_name) {\n\t\tfree(copied_name);\n\t}\n\n\treturn 0;\n}\n\nint _config_ipset(void *data, int argc, char *argv[])\n{\n\tchar domain[DNS_MAX_CONF_CNAME_LEN];\n\tchar *value = argv[1];\n\tint ret = 0;\n\n\tif (argc <= 1) {\n\t\tgoto errout;\n\t}\n\n\tif (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {\n\t\tgoto errout;\n\t}\n\n\tret = _conf_domain_rule_ipset(domain, value);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\ttlog(TLOG_WARN, \"add ipset %s failed.\", value);\n\treturn ret;\n}\n\nint _config_ipset_no_speed(void *data, int argc, char *argv[])\n{\n\tchar *ipsetname = argv[1];\n\n\tif (argc <= 1) {\n\t\tgoto errout;\n\t}\n\n\tif (_config_ipset_setvalue(&_config_current_rule_group()->ipset_nftset.ipset_no_speed, ipsetname) != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\ttlog(TLOG_ERROR, \"add ipset-no-speed %s failed\", ipsetname);\n\treturn 0;\n}"
  },
  {
    "path": "src/dns_conf/ipset.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_IPSET_H_\n#define _DNS_CONF_IPSET_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nconst char *_dns_conf_get_ipset(const char *ipsetname);\nint _config_ipset_init(void);\nvoid _config_ipset_table_destroy(void);\n\nint _conf_domain_rule_ipset(char *domain, const char *ipsetname);\n\nint _config_ipset_no_speed(void *data, int argc, char *argv[]);\n\nint _config_ipset(void *data, int argc, char *argv[]);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/local_domain.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"local_domain.h\"\n#include \"domain_rule.h\"\n#include \"nameserver.h\"\n#include \"smartdns/lib/stringutil.h\"\n\nstatic char local_domain[DNS_MAX_CNAME_LEN] = {0};\n\nconst char *dns_conf_get_local_domain(void)\n{\n\treturn local_domain;\n}\n\nint _config_local_domain(void *data, int argc, char *argv[])\n{\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\treturn -1;\n\t}\n\n\tconst char *domain = argv[1];\n\n\tif (local_domain[0] != '\\0') {\n\t\t_config_domain_rule_remove(local_domain, DOMAIN_RULE_NAMESERVER);\n        local_domain[0] = '\\0';\n\t}\n\n    if (domain[0] == '\\0' || strncmp(domain, \"-\", sizeof(\"-\")) == 0) {\n        return 0;\n    }\n\n\tsafe_strncpy(local_domain, domain, sizeof(local_domain));\n\t_conf_domain_rule_nameserver(local_domain, DNS_SERVER_GROUP_MDNS);\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_conf/local_domain.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_LOCAL_DOMAIN_H_\n#define _DNS_CONF_LOCAL_DOMAIN_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nconst char *dns_conf_get_local_domain(void);\n\nint _config_local_domain(void *data, int argc, char *argv[]);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/nameserver.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"nameserver.h\"\n#include \"domain_rule.h\"\n#include \"get_domain.h\"\n#include \"server_group.h\"\n\nint _conf_domain_rule_nameserver(const char *domain, const char *group_name)\n{\n\tstruct dns_nameserver_rule *nameserver_rule = NULL;\n\tconst char *group = NULL;\n\n\tif (strncmp(group_name, \"-\", sizeof(\"-\")) != 0) {\n\t\tgroup = _dns_conf_get_group_name(group_name);\n\t\tif (group == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tnameserver_rule = _new_dns_rule(DOMAIN_RULE_NAMESERVER);\n\t\tif (nameserver_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tnameserver_rule->group_name = group;\n\t} else {\n\t\t/* ignore this domain */\n\t\tif (_config_domain_rule_flag_set(domain, DOMAIN_FLAG_NAMESERVER_IGNORE, 0) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tif (_config_domain_rule_add(domain, DOMAIN_RULE_NAMESERVER, nameserver_rule) != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_rule_put(&nameserver_rule->head);\n\n\treturn 0;\nerrout:\n\tif (nameserver_rule) {\n\t\t_dns_rule_put(&nameserver_rule->head);\n\t}\n\n\ttlog(TLOG_ERROR, \"add nameserver %s, %s failed\", domain, group_name);\n\treturn 0;\n}\n\nint _config_nameserver(void *data, int argc, char *argv[])\n{\n\tchar domain[DNS_MAX_CONF_CNAME_LEN];\n\tchar *value = argv[1];\n\n\tif (argc <= 1) {\n\t\tgoto errout;\n\t}\n\n\tif (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn _conf_domain_rule_nameserver(domain, value);\nerrout:\n\ttlog(TLOG_ERROR, \"add nameserver %s failed\", value);\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_conf/nameserver.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_NAMESERVER_H_\n#define _DNS_CONF_NAMESERVER_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_nameserver(void *data, int argc, char *argv[]);\n\nint _conf_domain_rule_nameserver(const char *domain, const char *group_name);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/nftset.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"nftset.h\"\n#include \"dns_conf_group.h\"\n#include \"domain_rule.h\"\n#include \"get_domain.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\nstruct dns_nftset_table {\n\tDECLARE_HASHTABLE(nftset, 8);\n};\nstatic struct dns_nftset_table dns_nftset_table;\n\nvoid _config_nftset_table_destroy(void)\n{\n\tstruct dns_nftset_name *nftset = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_nftset_table.nftset, i, tmp, nftset, node)\n\t{\n\t\thlist_del_init(&nftset->node);\n\t\tfree(nftset);\n\t}\n}\n\nconst struct dns_nftset_name *_dns_conf_get_nftable(const char *familyname, const char *tablename, const char *setname)\n{\n\tuint32_t key = 0;\n\tstruct dns_nftset_name *nftset_name = NULL;\n\n\tif (familyname == NULL || tablename == NULL || setname == NULL) {\n\t\treturn NULL;\n\t}\n\n\tconst char *hasher[4] = {familyname, tablename, setname, NULL};\n\n\tkey = hash_string_array(hasher);\n\thash_for_each_possible(dns_nftset_table.nftset, nftset_name, node, key)\n\t{\n\t\tif (strncmp(nftset_name->nftfamilyname, familyname, DNS_MAX_NFTSET_FAMILYLEN) == 0 &&\n\t\t\tstrncmp(nftset_name->nfttablename, tablename, DNS_MAX_NFTSET_NAMELEN) == 0 &&\n\t\t\tstrncmp(nftset_name->nftsetname, setname, DNS_MAX_NFTSET_NAMELEN) == 0) {\n\t\t\treturn nftset_name;\n\t\t}\n\t}\n\n\tnftset_name = zalloc(1, sizeof(*nftset_name));\n\tif (nftset_name == NULL) {\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(nftset_name->nftfamilyname, familyname, DNS_MAX_NFTSET_FAMILYLEN);\n\tsafe_strncpy(nftset_name->nfttablename, tablename, DNS_MAX_NFTSET_NAMELEN);\n\tsafe_strncpy(nftset_name->nftsetname, setname, DNS_MAX_NFTSET_NAMELEN);\n\thash_add(dns_nftset_table.nftset, &nftset_name->node, key);\n\n\treturn nftset_name;\nerrout:\n\tif (nftset_name) {\n\t\tfree(nftset_name);\n\t}\n\n\treturn NULL;\n}\n\nint _conf_domain_rule_nftset(char *domain, const char *nftsetname)\n{\n\tstruct dns_nftset_rule *nftset_rule = NULL;\n\tconst struct dns_nftset_name *nftset = NULL;\n\tchar *copied_name = NULL;\n\tenum domain_rule type = 0;\n\tint ignore_flag = 0;\n\tchar *setname = NULL;\n\tchar *tablename = NULL;\n\tchar *family = NULL;\n\tint ret = -1;\n\n\tcopied_name = strdup(nftsetname);\n\n\tif (copied_name == NULL) {\n\t\tgoto errout;\n\t}\n\n\tfor (char *tok = strtok(copied_name, \",\"); tok; tok = strtok(NULL, \",\")) {\n\t\tchar *saveptr = NULL;\n\t\tchar *tok_set = NULL;\n\t\tnftset_rule = NULL;\n\n\t\tif (strncmp(tok, \"#4:\", 3U) == 0) {\n\t\t\ttype = DOMAIN_RULE_NFTSET_IP;\n\t\t\tignore_flag = DOMAIN_FLAG_NFTSET_IP_IGN;\n\t\t} else if (strncmp(tok, \"#6:\", 3U) == 0) {\n\t\t\ttype = DOMAIN_RULE_NFTSET_IP6;\n\t\t\tignore_flag = DOMAIN_FLAG_NFTSET_IP6_IGN;\n\t\t} else if (strncmp(tok, \"-\", 2U) == 0) {\n\t\t\t_config_domain_rule_flag_set(domain, DOMAIN_FLAG_NFTSET_INET_IGN, 0);\n\t\t\tcontinue;\n\t\t} else {\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttok_set = tok + 3;\n\n\t\tif (strncmp(tok_set, \"-\", 2U) == 0) {\n\t\t\t_config_domain_rule_flag_set(domain, ignore_flag, 0);\n\t\t\tcontinue;\n\t\t}\n\n\t\tfamily = strtok_r(tok_set, \"#\", &saveptr);\n\t\tif (family == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttablename = strtok_r(NULL, \"#\", &saveptr);\n\t\tif (tablename == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tsetname = strtok_r(NULL, \"#\", &saveptr);\n\t\tif (setname == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\t/* new nftset domain */\n\t\tnftset = _dns_conf_get_nftable(family, tablename, setname);\n\t\tif (nftset == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tnftset_rule = _new_dns_rule(type);\n\t\tif (nftset_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tnftset_rule->nfttablename = nftset->nfttablename;\n\t\tnftset_rule->nftsetname = nftset->nftsetname;\n\t\tnftset_rule->familyname = nftset->nftfamilyname;\n\n\t\tif (_config_domain_rule_add(domain, type, nftset_rule) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\t_dns_rule_put(&nftset_rule->head);\n\t\tnftset_rule = NULL;\n\t}\n\n\tret = 0;\n\tgoto clear;\n\nerrout:\n\ttlog(TLOG_ERROR, \"add nftset %s %s failed.\", domain, nftsetname);\n\n\tif (nftset_rule) {\n\t\t_dns_rule_put(&nftset_rule->head);\n\t}\n\nclear:\n\tif (copied_name) {\n\t\tfree(copied_name);\n\t}\n\n\treturn ret;\n}\n\nstatic int _config_nftset_setvalue(struct dns_nftset_names *nftsets, const char *nftsetvalue)\n{\n\tconst struct dns_nftset_name *nftset = NULL;\n\tchar *copied_name = NULL;\n\tint nftset_num = 0;\n\tchar *setname = NULL;\n\tchar *tablename = NULL;\n\tchar *family = NULL;\n\tint ret = -1;\n\tstruct dns_nftset_rule *nftset_rule_array[2] = {NULL, NULL};\n\tchar *nftset_rule_enable_array[2] = {NULL, NULL};\n\n\tif (nftsetvalue == NULL) {\n\t\tgoto errout;\n\t}\n\n\tcopied_name = strdup(nftsetvalue);\n\n\tif (copied_name == NULL) {\n\t\tgoto errout;\n\t}\n\n\tfor (char *tok = strtok(copied_name, \",\"); tok && nftset_num <= 2; tok = strtok(NULL, \",\")) {\n\t\tchar *saveptr = NULL;\n\t\tchar *tok_set = NULL;\n\n\t\tif (strncmp(tok, \"#4:\", 3U) == 0) {\n\t\t\tnftsets->ip_enable = 1;\n\t\t\tnftset_rule_array[nftset_num] = &nftsets->ip;\n\t\t\tnftset_rule_enable_array[nftset_num] = &nftsets->ip_enable;\n\t\t\tnftset_num++;\n\t\t} else if (strncmp(tok, \"#6:\", 3U) == 0) {\n\t\t\tnftset_rule_enable_array[nftset_num] = &nftsets->ip6_enable;\n\t\t\tnftset_rule_array[nftset_num] = &nftsets->ip6;\n\t\t\tnftset_num++;\n\t\t} else if (strncmp(tok, \"-\", 2U) == 0) {\n\t\t\tcontinue;\n\t\t\tcontinue;\n\t\t} else {\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttok_set = tok + 3;\n\n\t\tif (nftset_num == 0) {\n\t\t\tnftset_rule_array[0] = &nftsets->ip;\n\t\t\tnftset_rule_enable_array[0] = &nftsets->ip_enable;\n\t\t\tnftset_rule_array[1] = &nftsets->ip6;\n\t\t\tnftset_rule_enable_array[1] = &nftsets->ip6_enable;\n\t\t\tnftset_num = 2;\n\t\t}\n\n\t\tif (strncmp(tok_set, \"-\", 2U) == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfamily = strtok_r(tok_set, \"#\", &saveptr);\n\t\tif (family == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttablename = strtok_r(NULL, \"#\", &saveptr);\n\t\tif (tablename == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tsetname = strtok_r(NULL, \"#\", &saveptr);\n\t\tif (setname == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\t/* new nftset domain */\n\t\tnftset = _dns_conf_get_nftable(family, tablename, setname);\n\t\tif (nftset == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tfor (int i = 0; i < nftset_num; i++) {\n\t\t\tnftset_rule_array[i]->familyname = nftset->nftfamilyname;\n\t\t\tnftset_rule_array[i]->nfttablename = nftset->nfttablename;\n\t\t\tnftset_rule_array[i]->nftsetname = nftset->nftsetname;\n\t\t\t*nftset_rule_enable_array[i] = 1;\n\t\t}\n\n\t\tnftset_num = 0;\n\t}\n\n\tret = 0;\n\tgoto clear;\n\nerrout:\n\tret = -1;\nclear:\n\tif (copied_name) {\n\t\tfree(copied_name);\n\t}\n\n\treturn ret;\n}\n\nint _config_nftset(void *data, int argc, char *argv[])\n{\n\tchar domain[DNS_MAX_CONF_CNAME_LEN];\n\tchar *value = argv[1];\n\tint ret = 0;\n\n\tif (argc <= 1) {\n\t\tgoto errout;\n\t}\n\n\tif (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn _conf_domain_rule_nftset(domain, value);\nerrout:\n\ttlog(TLOG_ERROR, \"add nftset %s failed\", value);\n\treturn ret;\n}\n\nint _config_nftset_no_speed(void *data, int argc, char *argv[])\n{\n\tchar *nftsetname = argv[1];\n\n\tif (argc <= 1) {\n\t\tgoto errout;\n\t}\n\n\tif (_config_nftset_setvalue(&_config_current_rule_group()->ipset_nftset.nftset_no_speed, nftsetname) != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\ttlog(TLOG_ERROR, \"add nftset %s failed\", nftsetname);\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_conf/nftset.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_NFTSET_H_\n#define _DNS_CONF_NFTSET_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nconst struct dns_nftset_name *_dns_conf_get_nftable(const char *familyname, const char *tablename, const char *setname);\n\nvoid _config_nftset_table_destroy(void);\n\nint _config_nftset(void *data, int argc, char *argv[]);\n\nint _config_nftset_no_speed(void *data, int argc, char *argv[]);\n\nint _conf_domain_rule_nftset(char *domain, const char *nftsetname);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/plugin.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"plugin.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <stdio.h>\n\nstruct dns_conf_plugin_table dns_conf_plugin_table;\n\nstatic int _config_plugin_iter_free(void *data, const unsigned char *key, uint32_t key_len, void *value)\n{\n\tfree(value);\n\treturn 0;\n}\n\nstatic struct dns_conf_plugin *_config_get_plugin(const char *file)\n{\n\tuint32_t key = 0;\n\tstruct dns_conf_plugin *plugin = NULL;\n\n\tkey = hash_string(file);\n\thash_for_each_possible(dns_conf_plugin_table.plugins, plugin, node, key)\n\t{\n\t\tif (strncmp(plugin->file, file, DNS_MAX_PATH) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn plugin;\n\t}\n\n\treturn NULL;\n}\n\nconst char *dns_conf_get_plugin_conf(const char *key)\n{\n\tif (key == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn art_search(&dns_conf_plugin_table.plugins_conf, (unsigned char *)key, strlen(key) + 1);\n}\n\nint _config_plugin(void *data, int argc, char *argv[])\n{\n\t// clang-format off\n\tconst char *plugin_dir [] = {\n#if UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFULL\n\t\t\"/lib64/smartdns\",\n\t\t\"/usr/lib64/smartdns\",\n\t\t\"/usr/local/lib64/smartdns\",\n\t\t\"/lib64\",\n\t\t\"/usr/lib64\",\n\t\t\"/usr/local/lib64\",\n#endif\n\t\t\"/lib/smartdns\",\n\t\t\"/usr/lib/smartdns\",\n\t\t\"/usr/local/lib/smartdns\",\n\t\t\"/lib\",\n\t\t\"/usr/lib\",\n\t\t\"/usr/local/lib\",\n\t};\n\t// clang-format on\n\tconst int plugin_dir_len = sizeof(plugin_dir) / sizeof(plugin_dir[0]);\n#ifdef BUILD_STATIC\n\ttlog(TLOG_ERROR, \"plugin not support in static release, please install dynamic release.\");\n\tgoto errout;\n#endif\n\tchar file[DNS_MAX_PATH];\n\tunsigned int key = 0;\n\tint i = 0;\n\tchar *ptr = NULL;\n\tchar *ptr_end = NULL;\n\n\tif (argc < 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tif (argv[1] == NULL || argv[1][0] == '\\0') {\n\t\ttlog(TLOG_ERROR, \"plugin: invalid parameter.\");\n\t\tgoto errout;\n\t}\n\n\tfile[0] = '\\0';\n\tif (strstr(argv[1], \"/\") == NULL) {\n\t\t/* relative path, search in plugin dir */\n\t\tfor (i = 0; i < plugin_dir_len; i++) {\n\t\t\tsnprintf(file, sizeof(file), \"%s/%s\", plugin_dir[i], argv[1]);\n\t\t\tif (access(file, F_OK) == 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (i == plugin_dir_len) {\n\t\t\tfile[0] = '\\0';\n\t\t}\n\t}\n\n\tif (file[0] == '\\0') {\n\t\tconf_get_conf_fullpath(argv[1], file, sizeof(file));\n\t\tif (file[0] == '\\0') {\n\t\t\ttlog(TLOG_ERROR, \"plugin: invalid parameter.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (access(file, F_OK) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"plugin '%s' not exists.\", argv[1]);\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tstruct dns_conf_plugin *plugin = _config_get_plugin(file);\n\tif (plugin != NULL) {\n\t\ttlog(TLOG_ERROR, \"plugin '%s' already exists.\", file);\n\t\tgoto errout;\n\t}\n\n\tplugin = zalloc(1, sizeof(*plugin));\n\tif (plugin == NULL) {\n\t\tgoto errout;\n\t}\n\tsafe_strncpy(plugin->file, file, sizeof(plugin->file) - 1);\n\tptr = plugin->args;\n\tptr_end = plugin->args + sizeof(plugin->args) - 2;\n\tfor (i = 1; i < argc && ptr < ptr_end; i++) {\n\t\tsafe_strncpy(ptr, argv[i], ptr_end - ptr - 1);\n\t\tptr += strlen(argv[i]) + 1;\n\t}\n\tplugin->argc = argc - 1;\n\tplugin->args_len = ptr - plugin->args;\n\n\tkey = hash_string(file);\n\thash_add(dns_conf_plugin_table.plugins, &plugin->node, key);\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint _config_plugin_conf_add(const char *key, const char *value)\n{\n\tchar *old_value = NULL;\n\tchar *new_value = NULL;\n\tif (key == NULL || value == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (key[0] == '\\0' || value[0] == '\\0') {\n\t\tgoto errout;\n\t}\n\n\tnew_value = strdup(value);\n\tif (new_value == NULL) {\n\t\tgoto errout;\n\t}\n\n\told_value = art_insert(&dns_conf_plugin_table.plugins_conf, (unsigned char *)key, strlen(key) + 1, new_value);\n\tif (old_value) {\n\t\tfree(old_value);\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nvoid _config_plugin_table_init(void)\n{\n\thash_init(dns_conf_plugin_table.plugins);\n\tart_tree_init(&dns_conf_plugin_table.plugins_conf);\n}\n\nvoid _config_plugin_table_destroy(void)\n{\n\tstruct dns_conf_plugin *plugin = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_conf_plugin_table.plugins, i, tmp, plugin, node)\n\t{\n\t\thlist_del_init(&plugin->node);\n\t\tfree(plugin);\n\t}\n}\n\nvoid _config_plugin_table_conf_destroy(void)\n{\n\tart_iter(&dns_conf_plugin_table.plugins_conf, _config_plugin_iter_free, NULL);\n\tart_tree_destroy(&dns_conf_plugin_table.plugins_conf);\n}\n\nvoid dns_conf_clear_all_plugin_conf(void)\n{\n\t_config_plugin_table_conf_destroy();\n}"
  },
  {
    "path": "src/dns_conf/plugin.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_PLUGIN_H_\n#define _DNS_CONF_PLUGIN_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_plugin(void *data, int argc, char *argv[]);\n\nint _config_plugin_conf_add(const char *key, const char *value);\n\nvoid _config_plugin_table_init(void);\n\nvoid _config_plugin_table_destroy(void);\n\nvoid dns_conf_clear_all_plugin_conf(void);\n\nvoid _config_plugin_table_conf_destroy(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/proxy_names.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"proxy_names.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\nstruct dns_proxy_table dns_proxy_table;\n\nstruct dns_proxy_names *dns_server_get_proxy_names(const char *proxyname)\n{\n\tuint32_t key = 0;\n\tstruct dns_proxy_names *proxy = NULL;\n\n\tkey = hash_string(proxyname);\n\thash_for_each_possible(dns_proxy_table.proxy, proxy, node, key)\n\t{\n\t\tif (strncmp(proxy->proxy_name, proxyname, DNS_GROUP_NAME_LEN) == 0) {\n\t\t\treturn proxy;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n/* create and get dns server group */\nstatic struct dns_proxy_names *_dns_conf_get_proxy(const char *proxy_name)\n{\n\tuint32_t key = 0;\n\tstruct dns_proxy_names *proxy = NULL;\n\n\tkey = hash_string(proxy_name);\n\thash_for_each_possible(dns_proxy_table.proxy, proxy, node, key)\n\t{\n\t\tif (strncmp(proxy->proxy_name, proxy_name, PROXY_NAME_LEN) == 0) {\n\t\t\treturn proxy;\n\t\t}\n\t}\n\n\tproxy = zalloc(1, sizeof(*proxy));\n\tif (proxy == NULL) {\n\t\tgoto errout;\n\t}\n\tsafe_strncpy(proxy->proxy_name, proxy_name, PROXY_NAME_LEN);\n\thash_add(dns_proxy_table.proxy, &proxy->node, key);\n\tINIT_LIST_HEAD(&proxy->server_list);\n\n\treturn proxy;\nerrout:\n\tif (proxy) {\n\t\tfree(proxy);\n\t}\n\n\treturn NULL;\n}\n\nint _dns_conf_proxy_servers_add(const char *proxy_name, struct dns_proxy_servers *server)\n{\n\tstruct dns_proxy_names *proxy = NULL;\n\n\tproxy = _dns_conf_get_proxy(proxy_name);\n\tif (proxy == NULL) {\n\t\treturn -1;\n\t}\n\n\tlist_add_tail(&server->list, &proxy->server_list);\n\n\treturn 0;\n}\n\nconst char *_dns_conf_get_proxy_name(const char *proxy_name)\n{\n\tstruct dns_proxy_names *proxy = NULL;\n\n\tproxy = _dns_conf_get_proxy(proxy_name);\n\tif (proxy == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn proxy->proxy_name;\n}\n\nvoid _config_proxy_table_destroy(void)\n{\n\tstruct dns_proxy_names *proxy = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned int i;\n\tstruct dns_proxy_servers *server = NULL;\n\tstruct dns_proxy_servers *server_tmp = NULL;\n\n\thash_for_each_safe(dns_proxy_table.proxy, i, tmp, proxy, node)\n\t{\n\t\thlist_del_init(&proxy->node);\n\t\tlist_for_each_entry_safe(server, server_tmp, &proxy->server_list, list)\n\t\t{\n\t\t\tlist_del(&server->list);\n\t\t\tfree(server);\n\t\t}\n\t\tfree(proxy);\n\t}\n}\n"
  },
  {
    "path": "src/dns_conf/proxy_names.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_PROXY_NAMES_H_\n#define _DNS_CONF_PROXY_NAMES_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_conf_proxy_servers_add(const char *proxy_name, struct dns_proxy_servers *server);\nconst char *_dns_conf_get_proxy_name(const char *proxy_name);\n\nvoid _config_proxy_table_destroy(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/proxy_server.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"proxy_server.h\"\n#include \"proxy_names.h\"\n#include \"smartdns/util.h\"\n\n#include <getopt.h>\n\nint _config_proxy_server(void *data, int argc, char *argv[])\n{\n\tchar *servers_name = NULL;\n\tstruct dns_proxy_servers *server = NULL;\n\tproxy_type_t type = PROXY_TYPE_END;\n\n\tchar *ip = NULL;\n\tint opt = 0;\n\tint use_domain = 0;\n\tchar scheme[DNS_MAX_CNAME_LEN] = {0};\n\tint port = PORT_NOT_DEFINED;\n\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"name\", required_argument, NULL, 'n'}, \n\t\t{\"use-domain\", no_argument, NULL, 'd'},\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\n\tif (argc <= 1) {\n\t\treturn 0;\n\t}\n\n\tserver = zalloc(1, sizeof(*server));\n\tif (server == NULL) {\n\t\ttlog(TLOG_WARN, \"malloc memory failed.\");\n\t\tgoto errout;\n\t}\n\n\tip = argv[1];\n\tif (parse_uri_ext(ip, scheme, server->username, server->password, server->server, &port, NULL) != 0) {\n\t\tgoto errout;\n\t}\n\n\t/* process extra options */\n\toptind = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"n:d\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'n': {\n\t\t\tservers_name = optarg;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'd': {\n\t\t\tuse_domain = 1;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (strcasecmp(scheme, \"socks5\") == 0) {\n\t\tif (port == PORT_NOT_DEFINED) {\n\t\t\tport = 1080;\n\t\t}\n\n\t\ttype = PROXY_SOCKS5;\n\t} else if (strcasecmp(scheme, \"http\") == 0) {\n\t\tif (port == PORT_NOT_DEFINED) {\n\t\t\tport = 3128;\n\t\t}\n\n\t\ttype = PROXY_HTTP;\n\t} else {\n\t\ttlog(TLOG_ERROR, \"invalid scheme %s\", scheme);\n\t\treturn -1;\n\t}\n\n\tif (servers_name == NULL) {\n\t\ttlog(TLOG_ERROR, \"please set name\");\n\t\tgoto errout;\n\t}\n\n\tif (_dns_conf_proxy_servers_add(servers_name, server) != 0) {\n\t\ttlog(TLOG_ERROR, \"add group failed.\");\n\t\tgoto errout;\n\t}\n\n\t/* add new server */\n\tserver->type = type;\n\tserver->port = port;\n\tserver->use_domain = use_domain;\n\ttlog(TLOG_DEBUG, \"add proxy server %s\", ip);\n\n\treturn 0;\n\nerrout:\n\tif (server) {\n\t\tfree(server);\n\t}\n\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_conf/proxy_server.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_PROXY_SERVER_H_\n#define _DNS_CONF_PROXY_SERVER_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_proxy_server(void *data, int argc, char *argv[]);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/ptr.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ptr.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <stdio.h>\n\nstruct dns_ptr_table dns_ptr_table;\n\nstatic struct dns_ptr *_dns_conf_get_ptr(const char *ptr_domain)\n{\n\tuint32_t key = 0;\n\tstruct dns_ptr *ptr = NULL;\n\n\tkey = hash_string(ptr_domain);\n\thash_for_each_possible(dns_ptr_table.ptr, ptr, node, key)\n\t{\n\t\tif (strncmp(ptr->ptr_domain, ptr_domain, DNS_MAX_PTR_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn ptr;\n\t}\n\n\tptr = zalloc(1, sizeof(*ptr));\n\tif (ptr == NULL) {\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(ptr->ptr_domain, ptr_domain, DNS_MAX_PTR_LEN);\n\thash_add(dns_ptr_table.ptr, &ptr->node, key);\n\tptr->is_soa = 1;\n\n\treturn ptr;\nerrout:\n\tif (ptr) {\n\t\tfree(ptr);\n\t}\n\n\treturn NULL;\n}\n\nint _conf_ptr_add(const char *hostname, const char *ip, int is_dynamic)\n{\n\tstruct dns_ptr *ptr = NULL;\n\tstruct sockaddr_storage addr;\n\tunsigned char *paddr = NULL;\n\tsocklen_t addr_len = sizeof(addr);\n\tchar ptr_domain[DNS_MAX_PTR_LEN];\n\n\tif (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) {\n\t\tgoto errout;\n\t}\n\n\tswitch (addr.ss_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)&addr;\n\t\tpaddr = (unsigned char *)&(addr_in->sin_addr.s_addr);\n\t\tsnprintf(ptr_domain, sizeof(ptr_domain), \"%d.%d.%d.%d.in-addr.arpa\", paddr[3], paddr[2], paddr[1], paddr[0]);\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)&addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tpaddr = addr_in6->sin6_addr.s6_addr + 12;\n\t\t\tsnprintf(ptr_domain, sizeof(ptr_domain), \"%d.%d.%d.%d.in-addr.arpa\", paddr[3], paddr[2], paddr[1],\n\t\t\t\t\t paddr[0]);\n\t\t} else {\n\t\t\tpaddr = addr_in6->sin6_addr.s6_addr;\n\t\t\tsnprintf(ptr_domain, sizeof(ptr_domain),\n\t\t\t\t\t \"%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.\"\n\t\t\t\t\t \"%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.\"\n\t\t\t\t\t \"%x.ip6.arpa\",\n\t\t\t\t\t paddr[15] & 0xF, (paddr[15] >> 4) & 0xF, paddr[14] & 0xF, (paddr[14] >> 4) & 0xF, paddr[13] & 0xF,\n\t\t\t\t\t (paddr[13] >> 4) & 0xF, paddr[12] & 0xF, (paddr[12] >> 4) & 0xF, paddr[11] & 0xF,\n\t\t\t\t\t (paddr[11] >> 4) & 0xF, paddr[10] & 0xF, (paddr[10] >> 4) & 0xF, paddr[9] & 0xF,\n\t\t\t\t\t (paddr[9] >> 4) & 0xF, paddr[8] & 0xF, (paddr[8] >> 4) & 0xF, paddr[7] & 0xF,\n\t\t\t\t\t (paddr[7] >> 4) & 0xF, paddr[6] & 0xF, (paddr[6] >> 4) & 0xF, paddr[5] & 0xF,\n\t\t\t\t\t (paddr[5] >> 4) & 0xF, paddr[4] & 0xF, (paddr[4] >> 4) & 0xF, paddr[3] & 0xF,\n\t\t\t\t\t (paddr[3] >> 4) & 0xF, paddr[2] & 0xF, (paddr[2] >> 4) & 0xF, paddr[1] & 0xF,\n\t\t\t\t\t (paddr[1] >> 4) & 0xF, paddr[0] & 0xF, (paddr[0] >> 4) & 0xF);\n\t\t}\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\tptr = _dns_conf_get_ptr(ptr_domain);\n\tif (ptr == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (is_dynamic == 1 && ptr->is_soa == 0 && ptr->is_dynamic == 0) {\n\t\t/* already set fix PTR, skip */\n\t\treturn 0;\n\t}\n\n\tptr->is_dynamic = is_dynamic;\n\tptr->is_soa = 0;\n\tsafe_strncpy(ptr->hostname, hostname, DNS_MAX_CNAME_LEN);\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nvoid _config_ptr_table_init(void)\n{\n\thash_init(dns_ptr_table.ptr);\n}\n\nvoid _config_ptr_table_destroy(int only_dynamic)\n{\n\tstruct dns_ptr *ptr = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_ptr_table.ptr, i, tmp, ptr, node)\n\t{\n\t\tif (only_dynamic != 0 && ptr->is_dynamic == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\thlist_del_init(&ptr->node);\n\t\tfree(ptr);\n\t}\n}\n"
  },
  {
    "path": "src/dns_conf/ptr.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_PTR_H_\n#define _DNS_CONF_PTR_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _conf_ptr_add(const char *hostname, const char *ip, int is_dynamic);\n\nvoid _config_ptr_table_init(void);\n\nvoid _config_ptr_table_destroy(int only_dynamic);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/qtype_soa.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"qtype_soa.h\"\n#include \"dns_conf_group.h\"\n#include \"smartdns/lib/stringutil.h\"\n\nstatic int _conf_qtype_soa(uint8_t *soa_table, int argc, char *argv[])\n{\n\tint i = 0;\n\tint j = 0;\n\tint is_clear = 0;\n\n\tif (argc <= 1) {\n\t\treturn -1;\n\t}\n\n\tif (argc >= 2) {\n\t\tif (strncmp(argv[1], \"-\", sizeof(\"-\")) == 0) {\n\t\t\tif (argc == 2) {\n\t\t\t\tmemset(soa_table, 0, MAX_QTYPE_NUM / 8 + 1);\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tis_clear = 1;\n\t\t}\n\n\t\tif (strncmp(argv[1], \"-,\", sizeof(\",\")) == 0) {\n\t\t\tis_clear = 1;\n\t\t}\n\t}\n\n\tfor (i = 1; i < argc; i++) {\n\t\tchar sub_arg[1024];\n\t\tsafe_strncpy(sub_arg, argv[i], sizeof(sub_arg));\n\t\tfor (char *tok = strtok(sub_arg, \",\"); tok; tok = strtok(NULL, \",\")) {\n\t\t\tchar *dash = strstr(tok, \"-\");\n\t\t\tif (dash != NULL) {\n\t\t\t\t*dash = '\\0';\n\t\t\t}\n\n\t\t\tif (*tok == '\\0') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlong start = atol(tok);\n\t\t\tlong end = start;\n\n\t\t\tif (start > MAX_QTYPE_NUM || start < 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"invalid qtype %ld\", start);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (dash != NULL && *(dash + 1) != '\\0') {\n\t\t\t\tend = atol(dash + 1);\n\t\t\t\tif (end > MAX_QTYPE_NUM) {\n\t\t\t\t\tend = MAX_QTYPE_NUM;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (j = start; j <= end; j++) {\n\t\t\t\tint offset = j / 8;\n\t\t\t\tint bit = j % 8;\n\t\t\t\tif (is_clear) {\n\t\t\t\t\tsoa_table[offset] &= ~(1 << bit);\n\t\t\t\t} else {\n\t\t\t\t\tsoa_table[offset] |= (1 << bit);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint _config_qtype_soa(void *data, int argc, char *argv[])\n{\n\treturn _conf_qtype_soa(_config_current_rule_group()->soa_table, argc, argv);\n}"
  },
  {
    "path": "src/dns_conf/qtype_soa.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_QTYPE_SOA_H_\n#define _DNS_CONF_QTYPE_SOA_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_qtype_soa(void *data, int argc, char *argv[]);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/server.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"server.h\"\n#include \"client_subnet.h\"\n#include \"dns_conf_group.h\"\n#include \"proxy_names.h\"\n#include \"server_group.h\"\n#include \"smartdns/util.h\"\n\n#include <getopt.h>\n\nstatic int _config_server(int argc, char *argv[], dns_server_type_t type, int default_port)\n{\n\tint index = dns_conf.server_num;\n\tstruct dns_servers *server = NULL;\n\tint port = -1;\n\tchar *ip = NULL;\n\tchar scheme[DNS_MAX_CNAME_LEN] = {0};\n\tint opt = 0;\n\tint optind_last = 0;\n\tunsigned int result_flag = 0;\n\tunsigned int server_flag = 0;\n\tunsigned char *spki = NULL;\n\tint drop_packet_latency_ms = 0;\n\tint tcp_keepalive = -1;\n\tint is_bootstrap_dns = 0;\n\tchar host_ip[DNS_MAX_IPLEN] = {0};\n\tint no_tls_host_name = 0;\n\tint no_tls_host_verify = 0;\n\tconst char *group_name = NULL;\n\n\tint ttl = 0;\n\t/* clang-format off */\n\tstatic struct option long_options[] = {\n\t\t{\"drop-packet-latency\", required_argument, NULL, 'D'},\n\t\t{\"exclude-default-group\", no_argument, NULL, 'e'}, /* exclude this from default group */\n\t\t{\"group\", required_argument, NULL, 'g'}, /* add to group */\n\t\t{\"proxy\", required_argument, NULL, 'p'}, /* proxy server */\n\t\t{\"no-check-certificate\", no_argument, NULL, 'k'}, /* do not check certificate */\n\t\t{\"bootstrap-dns\", no_argument, NULL, 'b'}, /* set as bootstrap dns */\n\t\t{\"interface\", required_argument, NULL, 250}, /* interface */\n#ifdef FEATURE_CHECK_EDNS\n\t\t/* experimental feature */\n\t\t{\"check-edns\", no_argument, NULL, 251},   /* check edns */\n#endif \n\t\t{\"whitelist-ip\", no_argument, NULL, 252}, /* filtering with whitelist-ip */\n\t\t{\"blacklist-ip\", no_argument, NULL, 253}, /* filtering with blacklist-ip */\n\t\t{\"set-mark\", required_argument, NULL, 254}, /* set mark */\n\t\t{\"subnet\", required_argument, NULL, 256}, /* set subnet */\n\t\t{\"hitchhiking\", no_argument, NULL, 257}, /* hitchhiking */\n\t\t{\"host-ip\", required_argument, NULL, 258}, /* host ip */\n\t\t{\"spki-pin\", required_argument, NULL, 259}, /* check SPKI pin */\n\t\t{\"host-name\", required_argument, NULL, 260}, /* host name */\n\t\t{\"http-host\", required_argument, NULL, 261}, /* http host */\n\t\t{\"tls-host-verify\", required_argument, NULL, 262 }, /* verify tls hostname */\n\t\t{\"tcp-keepalive\", required_argument, NULL, 263}, /* tcp keepalive */\n\t\t{\"subnet-all-query-types\", no_argument, NULL, 264}, /* send subnent for all query types.*/\n\t\t{\"fallback\", no_argument, NULL, 265}, /* fallback */\n\t\t{\"alpn\", required_argument, NULL, 266}, /* alpn */\n\t\t{NULL, no_argument, NULL, 0}\n\t};\n\t/* clang-format on */\n\tif (argc <= 1) {\n\t\ttlog(TLOG_ERROR, \"invalid parameter.\");\n\t\treturn -1;\n\t}\n\n\tip = argv[1];\n\tif (index >= DNS_MAX_SERVERS) {\n\t\ttlog(TLOG_WARN, \"exceeds max server number, %s\", ip);\n\t\treturn 0;\n\t}\n\n\tserver = &dns_conf.servers[index];\n\tserver->spki[0] = '\\0';\n\tserver->path[0] = '\\0';\n\tserver->hostname[0] = '\\0';\n\tserver->httphost[0] = '\\0';\n\tserver->tls_host_verify[0] = '\\0';\n\tserver->proxyname[0] = '\\0';\n\tserver->set_mark = -1;\n\tserver->drop_packet_latency_ms = drop_packet_latency_ms;\n\tserver->tcp_keepalive = tcp_keepalive;\n\tserver->subnet_all_query_types = 0;\n\n\tif (parse_uri(ip, scheme, server->server, &port, server->path) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (scheme[0] != '\\0') {\n\t\tif (strcasecmp(scheme, \"https\") == 0) {\n\t\t\ttype = DNS_SERVER_HTTPS;\n\t\t\tdefault_port = DEFAULT_DNS_HTTPS_PORT;\n\t\t} else if (strcasecmp(scheme, \"http3\") == 0) {\n\t\t\ttype = DNS_SERVER_HTTP3;\n\t\t\tdefault_port = DEFAULT_DNS_HTTPS_PORT;\n\t\t} else if (strcasecmp(scheme, \"h3\") == 0) {\n\t\t\ttype = DNS_SERVER_HTTP3;\n\t\t\tdefault_port = DEFAULT_DNS_HTTPS_PORT;\n\t\t} else if (strcasecmp(scheme, \"quic\") == 0) {\n\t\t\ttype = DNS_SERVER_QUIC;\n\t\t\tdefault_port = DEFAULT_DNS_QUIC_PORT;\n\t\t} else if (strcasecmp(scheme, \"tls\") == 0) {\n\t\t\ttype = DNS_SERVER_TLS;\n\t\t\tdefault_port = DEFAULT_DNS_TLS_PORT;\n\t\t} else if (strcasecmp(scheme, \"tcp\") == 0) {\n\t\t\ttype = DNS_SERVER_TCP;\n\t\t\tdefault_port = DEFAULT_DNS_PORT;\n\t\t} else if (strcasecmp(scheme, \"udp\") == 0) {\n\t\t\ttype = DNS_SERVER_UDP;\n\t\t\tdefault_port = DEFAULT_DNS_PORT;\n\t\t} else {\n\t\t\ttlog(TLOG_ERROR, \"invalid scheme: %s\", scheme);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (dns_is_quic_supported() == 0) {\n\t\tif (type == DNS_SERVER_QUIC || type == DNS_SERVER_HTTP3) {\n\t\t\ttlog(TLOG_ERROR, \"QUIC/HTTP3 is not supported in this version.\");\n\t\t\ttlog(TLOG_ERROR, \"Please install the latest release with QUIC/HTTP3 support.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/* if port is not defined, set port to default 53 */\n\tif (port == PORT_NOT_DEFINED) {\n\t\tport = default_port;\n\t}\n\n\t/* get current group */\n\tif (_config_current_group()) {\n\t\tgroup_name = _config_current_group()->group_name;\n\t}\n\n\t/* if server is defined in a group, exclude from default group */\n\tif (group_name && group_name[0] != '\\0') {\n\t\tserver_flag |= SERVER_FLAG_EXCLUDE_DEFAULT;\n\t}\n\n\t/* process extra options */\n\toptind = 1;\n\toptind_last = 1;\n\twhile (1) {\n\t\topt = getopt_long_only(argc, argv, \"D:kg:p:eb\", long_options, NULL);\n\t\tif (opt == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (opt) {\n\t\tcase 'D': {\n\t\t\tdrop_packet_latency_ms = atoi(optarg);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'e': {\n\t\t\tserver_flag |= SERVER_FLAG_EXCLUDE_DEFAULT;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'g': {\n\t\t\t/* first group, add later */\n\t\t\tif (group_name == NULL) {\n\t\t\t\tgroup_name = optarg;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (_dns_conf_get_group_set(optarg, server) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add group failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'p': {\n\t\t\tif (_dns_conf_get_proxy_name(optarg) == NULL) {\n\t\t\t\ttlog(TLOG_ERROR, \"add proxy server failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tsafe_strncpy(server->proxyname, optarg, PROXY_NAME_LEN);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'k': {\n\t\t\tserver->skip_check_cert = 1;\n\t\t\tno_tls_host_verify = 1;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'b': {\n\t\t\tis_bootstrap_dns = 1;\n\t\t\tbreak;\n\t\t}\n\t\tcase 250: {\n\t\t\tsafe_strncpy(server->ifname, optarg, MAX_INTERFACE_LEN);\n\t\t\tbreak;\n\t\t}\n\t\tcase 251: {\n\t\t\tresult_flag |= DNSSERVER_FLAG_CHECK_EDNS;\n\t\t\tbreak;\n\t\t}\n\t\tcase 252: {\n\t\t\tresult_flag |= DNSSERVER_FLAG_WHITELIST_IP;\n\t\t\tbreak;\n\t\t}\n\t\tcase 253: {\n\t\t\tresult_flag |= DNSSERVER_FLAG_BLACKLIST_IP;\n\t\t\tbreak;\n\t\t}\n\t\tcase 254: {\n\t\t\tserver->set_mark = atoll(optarg);\n\t\t\tbreak;\n\t\t}\n\t\tcase 256: {\n\t\t\t_conf_client_subnet(optarg, &server->ipv4_ecs, &server->ipv6_ecs);\n\t\t\tbreak;\n\t\t}\n\t\tcase 257: {\n\t\t\tserver_flag |= SERVER_FLAG_HITCHHIKING;\n\t\t\tbreak;\n\t\t}\n\t\tcase 258: {\n\t\t\tif (check_is_ipaddr(optarg) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tsafe_strncpy(host_ip, optarg, DNS_MAX_IPLEN);\n\t\t\tbreak;\n\t\t}\n\t\tcase 259: {\n\t\t\tsafe_strncpy(server->spki, optarg, DNS_MAX_SPKI_LEN);\n\t\t\tbreak;\n\t\t}\n\t\tcase 260: {\n\t\t\tsafe_strncpy(server->hostname, optarg, DNS_MAX_CNAME_LEN);\n\t\t\tif (strncmp(server->hostname, \"-\", 2) == 0) {\n\t\t\t\tserver->hostname[0] = '\\0';\n\t\t\t\tno_tls_host_name = 1;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 261: {\n\t\t\tsafe_strncpy(server->httphost, optarg, DNS_MAX_CNAME_LEN);\n\t\t\tbreak;\n\t\t}\n\t\tcase 262: {\n\t\t\tsafe_strncpy(server->tls_host_verify, optarg, DNS_MAX_CNAME_LEN);\n\t\t\tif (strncmp(server->tls_host_verify, \"-\", 2) == 0) {\n\t\t\t\tserver->tls_host_verify[0] = '\\0';\n\t\t\t\tno_tls_host_verify = 1;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 263: {\n\t\t\tserver->tcp_keepalive = atoi(optarg);\n\t\t\tbreak;\n\t\t}\n\t\tcase 264: {\n\t\t\tserver->subnet_all_query_types = 1;\n\t\t\tbreak;\n\t\t}\n\t\tcase 265: {\n\t\t\tserver->fallback = 1;\n\t\t\tbreak;\n\t\t}\n\t\tcase 266: {\n\t\t\tsafe_strncpy(server->alpn, optarg, DNS_MAX_ALPN_LEN);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tif (optind > optind_last) {\n\t\t\t\ttlog(TLOG_WARN, \"unknown server option: %s at '%s:%d'.\", argv[optind - 1], conf_get_conf_file(),\n\t\t\t\t\t conf_get_current_lineno());\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\toptind_last = optind;\n\t}\n\n\tif (check_is_ipaddr(server->server) != 0) {\n\t\t/* if server is domain name, then verify domain */\n\t\tif (server->tls_host_verify[0] == '\\0' && no_tls_host_verify == 0) {\n\t\t\tsafe_strncpy(server->tls_host_verify, server->server, DNS_MAX_CNAME_LEN);\n\t\t}\n\n\t\tif (server->hostname[0] == '\\0' && no_tls_host_name == 0) {\n\t\t\tsafe_strncpy(server->hostname, server->server, DNS_MAX_CNAME_LEN);\n\t\t}\n\n\t\tif (server->httphost[0] == '\\0') {\n\t\t\tsafe_strncpy(server->httphost, server->server, DNS_MAX_CNAME_LEN);\n\t\t}\n\n\t\tif (host_ip[0] != '\\0') {\n\t\t\tsafe_strncpy(server->server, host_ip, DNS_MAX_IPLEN);\n\t\t}\n\t}\n\n\t/* if server is domain name, then verify domain */\n\tif (server->tls_host_verify[0] == '\\0' && server->hostname[0] != '\\0' && no_tls_host_verify == 0) {\n\t\tsafe_strncpy(server->tls_host_verify, server->hostname, DNS_MAX_CNAME_LEN);\n\t}\n\n\t/* add new server */\n\tserver->type = type;\n\tserver->port = port;\n\tserver->result_flag = result_flag;\n\tserver->server_flag = server_flag;\n\tserver->ttl = ttl;\n\tserver->drop_packet_latency_ms = drop_packet_latency_ms;\n\n\tif (server->type == DNS_SERVER_HTTPS || server->type == DNS_SERVER_HTTP3) {\n\t\tif (server->path[0] == 0) {\n\t\t\tsafe_strncpy(server->path, \"/\", sizeof(server->path));\n\t\t}\n\n\t\tif (server->httphost[0] == '\\0') {\n\t\t\tset_http_host(server->server, server->port, DEFAULT_DNS_HTTPS_PORT, server->httphost);\n\t\t}\n\t}\n\n\tif (group_name) {\n\t\tif (_dns_conf_get_group_set(group_name, server) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"add group failed.\");\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tdns_conf.server_num++;\n\ttlog(TLOG_DEBUG, \"add server %s, flag: %X, ttl: %d\", ip, result_flag, ttl);\n\n\tif (is_bootstrap_dns) {\n\t\tserver->server_flag |= SERVER_FLAG_EXCLUDE_DEFAULT;\n\t\t_dns_conf_get_group_set(\"bootstrap-dns\", server);\n\t\tdns_conf_exist_bootstrap_dns = 1;\n\t}\n\n\treturn 0;\n\nerrout:\n\tif (spki) {\n\t\tfree(spki);\n\t}\n\n\treturn -1;\n}\n\nint _config_server_udp(void *data, int argc, char *argv[])\n{\n\treturn _config_server(argc, argv, DNS_SERVER_UDP, DEFAULT_DNS_PORT);\n}\n\nint _config_server_tcp(void *data, int argc, char *argv[])\n{\n\treturn _config_server(argc, argv, DNS_SERVER_TCP, DEFAULT_DNS_PORT);\n}\n\nint _config_server_tls(void *data, int argc, char *argv[])\n{\n\treturn _config_server(argc, argv, DNS_SERVER_TLS, DEFAULT_DNS_TLS_PORT);\n}\n\nint _config_server_https(void *data, int argc, char *argv[])\n{\n\tint ret = 0;\n\tret = _config_server(argc, argv, DNS_SERVER_HTTPS, DEFAULT_DNS_HTTPS_PORT);\n\n\treturn ret;\n}\n\nint _config_server_quic(void *data, int argc, char *argv[])\n{\n\tint ret = 0;\n\tret = _config_server(argc, argv, DNS_SERVER_QUIC, DEFAULT_DNS_QUIC_PORT);\n\n\treturn ret;\n}\n\nint _config_server_http3(void *data, int argc, char *argv[])\n{\n\tint ret = 0;\n\tret = _config_server(argc, argv, DNS_SERVER_HTTP3, DEFAULT_DNS_HTTPS_PORT);\n\n\treturn ret;\n}"
  },
  {
    "path": "src/dns_conf/server.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_SERVER_H_\n#define _DNS_CONF_SERVER_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_server_udp(void *data, int argc, char *argv[]);\nint _config_server_tcp(void *data, int argc, char *argv[]);\nint _config_server_tls(void *data, int argc, char *argv[]);\nint _config_server_https(void *data, int argc, char *argv[]);\nint _config_server_quic(void *data, int argc, char *argv[]);\nint _config_server_http3(void *data, int argc, char *argv[]);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/server_group.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"server_group.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n/* dns groups */\nstruct dns_group_table dns_group_table;\n\nstruct dns_server_groups *_dns_conf_get_group(const char *group_name)\n{\n\tuint32_t key = 0;\n\tstruct dns_server_groups *group = NULL;\n\n\tkey = hash_string(group_name);\n\thash_for_each_possible(dns_group_table.group, group, node, key)\n\t{\n\t\tif (strncmp(group->group_name, group_name, DNS_GROUP_NAME_LEN) == 0) {\n\t\t\treturn group;\n\t\t}\n\t}\n\n\tgroup = zalloc(1, sizeof(*group));\n\tif (group == NULL) {\n\t\tgoto errout;\n\t}\n\tsafe_strncpy(group->group_name, group_name, DNS_GROUP_NAME_LEN);\n\thash_add(dns_group_table.group, &group->node, key);\n\n\treturn group;\nerrout:\n\tif (group) {\n\t\tfree(group);\n\t}\n\n\treturn NULL;\n}\n\nint _dns_conf_get_group_set(const char *group_name, struct dns_servers *server)\n{\n\tstruct dns_server_groups *group = NULL;\n\tint i = 0;\n\n\tgroup = _dns_conf_get_group(group_name);\n\tif (group == NULL) {\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < group->server_num; i++) {\n\t\tif (group->servers[i] == server) {\n\t\t\t/* already in group */\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tif (group->server_num >= DNS_MAX_SERVERS) {\n\t\treturn -1;\n\t}\n\n\tgroup->servers[group->server_num] = server;\n\tgroup->server_num++;\n\n\treturn 0;\n}\n\nconst char *_dns_conf_get_group_name(const char *group_name)\n{\n\tstruct dns_server_groups *group = NULL;\n\n\tgroup = _dns_conf_get_group(group_name);\n\tif (group == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn group->group_name;\n}\n\nvoid _config_group_table_init(void)\n{\n\thash_init(dns_group_table.group);\n}\n\nvoid _config_group_table_destroy(void)\n{\n\tstruct dns_server_groups *group = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(dns_group_table.group, i, tmp, group, node)\n\t{\n\t\thlist_del_init(&group->node);\n\t\tfree(group);\n\t}\n}"
  },
  {
    "path": "src/dns_conf/server_group.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_SERVER_GROUP_H_\n#define _DNS_CONF_SERVER_GROUP_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_conf_get_group_set(const char *group_name, struct dns_servers *server);\n\nstruct dns_server_groups *_dns_conf_get_group(const char *group_name);\n\nconst char *_dns_conf_get_group_name(const char *group_name);\n\nstruct dns_conf_group *_config_rule_group_get(const char *group_name);\n\nstruct dns_conf_group *dns_server_get_rule_group(const char *group_name);\n\nstruct dns_conf_group *dns_server_get_default_rule_group(void);\n\nstruct dns_conf_group *_config_rule_group_new(const char *group_name);\n\nvoid _config_group_table_init(void);\n\nvoid _config_group_table_destroy(void);\n\nvoid _config_rule_group_destroy(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/set_file.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"set_file.h\"\n#include \"smartdns/lib/idna.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <getopt.h>\n#include <glob.h>\n#include <libgen.h>\n#include <limits.h>\n#include <stdio.h>\n\nint _config_set_rule_each_from_list(const char *file, set_rule_add_func callback, void *priv)\n{\n\tFILE *fp = NULL;\n\tchar line[MAX_LINE_LEN];\n\tchar value[DNS_MAX_CNAME_LEN];\n\tint ret = 0;\n\tint line_no = 0;\n\tint filed_num = 0;\n\n\tfp = fopen(file, \"r\");\n\tif (fp == NULL) {\n\t\ttlog(TLOG_ERROR, \"open file %s error, %s\", file, strerror(errno));\n\t\treturn -1;\n\t}\n\n\tline_no = 0;\n\twhile (fgets(line, MAX_LINE_LEN, fp)) {\n\t\tchar *p = line;\n\t\tline_no++;\n\n\t\t/* skip UTF-8 BOM */\n\t\tif (line_no == 1 && (unsigned char)line[0] == 0xEF && (unsigned char)line[1] == 0xBB && (unsigned char)line[2] == 0xBF) {\n\t\t\tp += 3;\n\t\t}\n\n\t\tfiled_num = sscanf(p, \"%255s\", value);\n\t\tif (filed_num <= 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (value[0] == '#' || value[0] == '\\n') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Normalize domain */\n\t\tchar domain[DNS_MAX_CNAME_LEN];\n\t\tchar *d_ptr = value;\n\t\t/* remove prefix . */\n\t\twhile (*d_ptr == '.') {\n\t\t\tif (*(d_ptr + 1) == '\\0') {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\td_ptr++;\n\t\t}\n\n\t\tif (utf8_to_punycode(d_ptr, strlen(d_ptr), domain, sizeof(domain)) <= 0) {\n\t\t\ttlog(TLOG_WARN, \"process file %s failed at line %d, invalid domain %s.\", file, line_no, d_ptr);\n\t\t\tcontinue;\n\t\t}\n\n\t\tret = callback(domain, priv);\n\t\tif (ret != 0) {\n\t\t\ttlog(TLOG_WARN, \"process file %s failed at line %d.\", file, line_no);\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\tfclose(fp);\n\treturn ret;\n}\n\nint _config_foreach_file(const char *file_pattern, int (*callback)(const char *file, void *priv), void *priv)\n{\n\tchar file_path[PATH_MAX];\n\tchar file_path_dir[PATH_MAX];\n\tglob_t globbuf = {0};\n\n\tif (file_pattern == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (file_pattern[0] != '/') {\n\t\tsafe_strncpy(file_path_dir, conf_get_conf_file(), DNS_MAX_PATH);\n\t\tdir_name(file_path_dir);\n\t\tif (strncmp(file_path_dir, conf_get_conf_file(), sizeof(file_path_dir)) == 0) {\n\t\t\tif (snprintf(file_path, DNS_MAX_PATH, \"%s\", file_pattern) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t} else {\n\t\t\tif (snprintf(file_path, DNS_MAX_PATH, \"%s/%s\", file_path_dir, file_pattern) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t} else {\n\t\tsafe_strncpy(file_path, file_pattern, DNS_MAX_PATH);\n\t}\n\n\terrno = 0;\n\tif (glob(file_path, 0, NULL, &globbuf) != 0) {\n\t\tif (errno == 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\ttlog(TLOG_ERROR, \"open config file '%s' failed, %s\", file_path, strerror(errno));\n\t\treturn -1;\n\t}\n\n\tfor (size_t i = 0; i != globbuf.gl_pathc; ++i) {\n\t\tconst char *file = globbuf.gl_pathv[i];\n\t\tstruct stat statbuf;\n\n\t\tif (stat(file, &statbuf) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!S_ISREG(statbuf.st_mode)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (callback(file, priv) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"load config file '%s' failed.\", file);\n\t\t\tglobfree(&globbuf);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tglobfree(&globbuf);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_conf/set_file.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_SET_FILE_H_\n#define _DNS_CONF_SET_FILE_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _get_domain(char *value, char *domain, int max_domain_size, char **ptr_after_domain);\n\nint _config_foreach_file(const char *file_pattern, int (*callback)(const char *file, void *priv), void *priv);\n\ntypedef int (*set_rule_add_func)(const char *value, void *priv);\nint _config_set_rule_each_from_list(const char *file, set_rule_add_func callback, void *priv);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/smartdns_domain.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns_domain.h\"\n#include \"domain_rule.h\"\n\n#include <stdio.h>\n\nvoid _config_setup_smartdns_domain(void)\n{\n\tchar hostname[DNS_MAX_CNAME_LEN];\n\tchar domainname[DNS_MAX_CNAME_LEN];\n\n\thostname[0] = '\\0';\n\tdomainname[0] = '\\0';\n\n\t/* get local domain name */\n\tif (getdomainname(domainname, DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t/* check domain is valid */\n\t\tif (strncmp(domainname, \"(none)\", DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t\tdomainname[0] = '\\0';\n\t\t}\n\t}\n\n\tif (gethostname(hostname, DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t/* check hostname is valid */\n\t\tif (strncmp(hostname, \"(none)\", DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t\thostname[0] = '\\0';\n\t\t}\n\t}\n\n\tif (dns_conf.resolv_hostname == 1) {\n\t\t/* add hostname to rule table */\n\t\tif (hostname[0] != '\\0') {\n\t\t\t_config_domain_rule_flag_set(hostname, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0);\n\t\t}\n\n\t\t/* add domainname to rule table */\n\t\tif (domainname[0] != '\\0') {\n\t\t\tchar full_domain[DNS_MAX_CNAME_LEN];\n\t\t\tsnprintf(full_domain, DNS_MAX_CNAME_LEN, \"%.64s.%.128s\", hostname, domainname);\n\t\t\t_config_domain_rule_flag_set(full_domain, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0);\n\t\t}\n\t}\n\n\t/* add server name to rule table */\n\tif (dns_conf.server_name[0] != '\\0' &&\n\t\tstrncmp(dns_conf.server_name, \"smartdns\", DNS_MAX_SERVER_NAME_LEN - 1) != 0) {\n\t\t_config_domain_rule_flag_set(dns_conf.server_name, DOMAIN_FLAG_SMARTDNS_DOMAIN, 0);\n\t}\n\n\t_config_domain_rule_flag_set(\"smartdns\", DOMAIN_FLAG_SMARTDNS_DOMAIN, 0);\n}\n"
  },
  {
    "path": "src/dns_conf/smartdns_domain.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_SMARTDNS_DOMAIN_H_\n#define _DNS_CONF_SMARTDNS_DOMAIN_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _config_setup_smartdns_domain(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/speed_check_mode.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"speed_check_mode.h\"\n#include \"dns_conf_group.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <string.h>\n\nstatic int dns_has_cap_ping = 0;\nint dns_has_raw_cap = 0;\nint dns_ping_cap_force_enable = 0;\n\nstatic void _config_speed_check_mode_clear(struct dns_domain_check_orders *check_orders)\n{\n\tmemset(check_orders->orders, 0, sizeof(check_orders->orders));\n}\n\nint _config_speed_check_mode_parser(struct dns_domain_check_orders *check_orders, const char *mode)\n{\n\tchar tmpbuff[DNS_MAX_OPT_LEN];\n\tchar *field = NULL;\n\tchar *ptr = NULL;\n\tint order = 0;\n\tint port = 80;\n\tint i = 0;\n\n\tsafe_strncpy(tmpbuff, mode, DNS_MAX_OPT_LEN);\n\t_config_speed_check_mode_clear(check_orders);\n\n\tptr = tmpbuff;\n\tdo {\n\t\tfield = ptr;\n\t\tif (field == NULL || order >= DOMAIN_CHECK_NUM) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tptr = strstr(ptr, \",\");\n\t\tif (ptr) {\n\t\t\t*ptr = 0;\n\t\t}\n\n\t\tif (strncmp(field, \"ping\", sizeof(\"ping\")) == 0) {\n\t\t\tif (dns_has_cap_ping == 0) {\n\t\t\t\tif (ptr) {\n\t\t\t\t\tptr++;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcheck_orders->orders[order].type = DOMAIN_CHECK_ICMP;\n\t\t\tcheck_orders->orders[order].tcp_port = 0;\n\t\t\tdns_conf.has_icmp_check = 1;\n\t\t} else if (strstr(field, \"tcp-syn\") == field) {\n\t\t\tchar *port_str = strstr(field, \":\");\n\t\t\tif (port_str) {\n\t\t\t\tport = atoi(port_str + 1);\n\t\t\t\tif (port <= 0 || port >= 65535) {\n\t\t\t\t\tport = 80;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcheck_orders->orders[order].type = DOMAIN_CHECK_TCP_SYN;\n\t\t\tcheck_orders->orders[order].tcp_port = port;\n\t\t} else if (strstr(field, \"tcp\") == field) {\n\t\t\tchar *port_str = strstr(field, \":\");\n\t\t\tif (port_str) {\n\t\t\t\tport = atoi(port_str + 1);\n\t\t\t\tif (port <= 0 || port >= 65535) {\n\t\t\t\t\tport = 80;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcheck_orders->orders[order].type = DOMAIN_CHECK_TCP;\n\t\t\tcheck_orders->orders[order].tcp_port = port;\n\t\t\tdns_conf.has_tcp_check = 1;\n\t\t} else if (strncmp(field, \"none\", sizeof(\"none\")) == 0) {\n\t\t\tfor (i = order; i < DOMAIN_CHECK_NUM; i++) {\n\t\t\t\tcheck_orders->orders[i].type = DOMAIN_CHECK_NONE;\n\t\t\t\tcheck_orders->orders[i].tcp_port = 0;\n\t\t\t}\n\n\t\t\treturn 0;\n\t\t}\n\n\t\torder++;\n\t\tif (ptr) {\n\t\t\tptr++;\n\t\t}\n\t} while (ptr);\n\n\treturn 0;\n}\n\nint _config_speed_check_mode(void *data, int argc, char *argv[])\n{\n\tchar mode[DNS_MAX_OPT_LEN];\n\n\tif (argc <= 1) {\n\t\treturn -1;\n\t}\n\n\tsafe_strncpy(mode, argv[1], sizeof(mode));\n\n\treturn _config_speed_check_mode_parser(&_config_current_rule_group()->check_orders, mode);\n}\n\nint _dns_conf_speed_check_mode_verify(void)\n{\n\tstruct dns_conf_group *group;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long k = 0;\n\tint i = 0;\n\tint j = 0;\n\tint print_log = 0;\n\tint trim_tail = 0;\n\tint tcp_sync_fallback = 0;\n\n\thash_for_each_safe(dns_conf_rule.group, k, tmp, group, node)\n\t{\n\t\tstruct dns_domain_check_orders *check_orders = &group->check_orders;\n\t\tfor (i = 0; i < DOMAIN_CHECK_NUM; i++) {\n\t\t\tif (check_orders->orders[i].type == DOMAIN_CHECK_ICMP) {\n\t\t\t\tif (dns_has_cap_ping == 0) {\n\t\t\t\t\ttrim_tail = 1;\n\t\t\t\t}\n\t\t\t\tdns_conf.has_icmp_check = 1;\n\t\t\t}\n\n\t\t\tif (check_orders->orders[i].type == DOMAIN_CHECK_TCP) {\n\t\t\t\tdns_conf.has_tcp_check = 1;\n\t\t\t}\n\n\t\t\tif (dns_has_raw_cap == 0) {\n\t\t\t\tif (check_orders->orders[i].type == DOMAIN_CHECK_TCP_SYN) {\n\t\t\t\t\tif (dns_has_raw_cap == 0) {\n\t\t\t\t\t\ttcp_sync_fallback = 1;\n\t\t\t\t\t\tcheck_orders->orders[i].type = DOMAIN_CHECK_TCP;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdns_conf.has_tcp_syn_check = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (trim_tail == 1) {\n\t\t\t\tfor (j = i + 1; j < DOMAIN_CHECK_NUM; j++) {\n\t\t\t\t\tcheck_orders->orders[j - 1].type = check_orders->orders[j].type;\n\t\t\t\t\tcheck_orders->orders[j - 1].tcp_port = check_orders->orders[j].tcp_port;\n\t\t\t\t}\n\t\t\t\tcheck_orders->orders[j - 1].type = DOMAIN_CHECK_NONE;\n\t\t\t\tcheck_orders->orders[j - 1].tcp_port = 0;\n\t\t\t\tprint_log = 1;\n\t\t\t}\n\n\t\t\ttrim_tail = 0;\n\t\t}\n\t}\n\n\tif (print_log) {\n\t\ttlog(TLOG_WARN,\n\t\t\t \"speed check by ping or tcp-syn is disabled because smartdns does not have network raw privileges\");\n\t}\n\n\tif (tcp_sync_fallback) {\n\t\ttlog(TLOG_WARN,\n\t\t\t \"tcp-syn speed check fallback to tcp mode because smartdns does not have network raw privileges\");\n\t}\n\n\treturn 0;\n}\n\nint _dns_ping_cap_check(void)\n{\n\tint has_ping = 0;\n\tint has_raw_cap = 0;\n\n\thas_raw_cap = has_network_raw_cap();\n\thas_ping = has_unprivileged_ping();\n\tif (has_ping == 0) {\n\t\tif (errno == EACCES && has_raw_cap == 0) {\n\t\t\ttlog(TLOG_WARN, \"unprivileged ping is disabled, please enable by setting net.ipv4.ping_group_range\");\n\t\t}\n\t}\n\n\tif (has_ping == 1 || has_raw_cap == 1) {\n\t\tdns_has_cap_ping = 1;\n\t}\n\n\tif (dns_ping_cap_force_enable) {\n\t\tdns_has_cap_ping = 1;\n\t}\n\n\tdns_has_raw_cap = has_raw_cap;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_conf/speed_check_mode.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_SPEED_CHECK_MODE_H_\n#define _DNS_CONF_SPEED_CHECK_MODE_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_ping_cap_check(void);\n\nint _config_speed_check_mode(void *data, int argc, char *argv[]);\n\nint _dns_conf_speed_check_mode_verify(void);\n\nint _config_speed_check_mode_parser(struct dns_domain_check_orders *check_orders, const char *mode);\n\nextern int dns_has_raw_cap;\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_conf/srv_record.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"srv_record.h\"\n#include \"set_file.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n#include \"domain_rule.h\"\n\n\nstatic int _confg_srv_record_add(const char *domain, const char *host, unsigned short priority, unsigned short weight,\n\t\t\t\t\t\t\t\t unsigned short port)\n{\n\tstruct dns_srv_record_rule *srv_rule = NULL;\n\tstruct dns_srv_record *srv_record = NULL;\n\tint is_new = 0;\n\tenum domain_rule type = DOMAIN_RULE_SRV;\n\n\tsrv_rule = dns_conf_get_domain_rule(domain, type);\n\tif (srv_rule == NULL) {\n\t\tsrv_rule = _new_dns_rule(type);\n\t\tif (srv_rule == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\tINIT_LIST_HEAD(&srv_rule->record_list);\n\t\tis_new = 1;\n\t}\n\n\tsrv_record = zalloc(1, sizeof(*srv_record));\n\tif (srv_record == NULL) {\n\t\tgoto errout;\n\t}\n\tsafe_strncpy(srv_record->host, host, DNS_MAX_CONF_CNAME_LEN);\n\tsrv_record->priority = priority;\n\tsrv_record->weight = weight;\n\tsrv_record->port = port;\n\tlist_add_tail(&srv_record->list, &srv_rule->record_list);\n\n\tif (is_new) {\n\t\tif (_config_domain_rule_add(domain, type, srv_rule) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\t_dns_rule_put(&srv_rule->head);\n\t\tsrv_rule = NULL;\n\t}\n\n\treturn 0;\nerrout:\n\tif (is_new && srv_rule != NULL) {\n\t\t_dns_rule_put(&srv_rule->head);\n\t}\n\tif (srv_record && srv_record->list.next == NULL) {\n\t\tfree(srv_record);\n\t}\n\treturn -1;\n}\n\nint _config_srv_record(void *data, int argc, char *argv[])\n{\n\tchar *value = NULL;\n\tchar domain[DNS_MAX_CONF_CNAME_LEN];\n\tchar buff[DNS_MAX_CONF_CNAME_LEN];\n\tchar *ptr = NULL;\n\tint ret = -1;\n\n\tchar *host_s;\n\tchar *priority_s;\n\tchar *weight_s;\n\tchar *port_s;\n\n\tunsigned short priority = 0;\n\tunsigned short weight = 0;\n\tunsigned short port = 1;\n\n\tif (argc < 2) {\n\t\tgoto errout;\n\t}\n\n\tvalue = argv[1];\n\tif (_get_domain(value, domain, DNS_MAX_CONF_CNAME_LEN, &value) != 0) {\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(buff, value, sizeof(buff));\n\n\thost_s = strtok_r(buff, \",\", &ptr);\n\tif (host_s == NULL) {\n\t\thost_s = \"\";\n\t\tgoto out;\n\t}\n\n\tport_s = strtok_r(NULL, \",\", &ptr);\n\tif (port_s != NULL) {\n\t\tport = atoi(port_s);\n\t}\n\n\tpriority_s = strtok_r(NULL, \",\", &ptr);\n\tif (priority_s != NULL) {\n\t\tpriority = atoi(priority_s);\n\t}\n\n\tweight_s = strtok_r(NULL, \",\", &ptr);\n\tif (weight_s != NULL) {\n\t\tweight = atoi(weight_s);\n\t}\nout:\n\tret = _confg_srv_record_add(domain, host_s, priority, weight, port);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\ttlog(TLOG_ERROR, \"add srv-record %s:%s failed\", domain, value);\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_conf/srv_record.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_CONF_SRV_RECORD_H_\n#define _DNS_CONF_SRV_RECORD_H_\n\n#include \"dns_conf.h\"\n#include \"smartdns/dns_conf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _config_srv_record(void *data, int argc, char *argv[]);\n\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_plugin.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/dns_plugin.h\"\n\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/lib/conf.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/lib/list.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n#include <dlfcn.h>\n#include <limits.h>\n#include <pthread.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nstruct dns_plugin_ops {\n\tstruct list_head list;\n\tstruct smartdns_operations ops;\n};\n\n#define DNS_PLUGIN_MAX_ARGS 32\nstruct dns_plugin {\n\tstruct hlist_node node;\n\tchar file[PATH_MAX];\n\tchar args[PATH_MAX];\n\tint argc;\n\tchar *argv[DNS_PLUGIN_MAX_ARGS];\n\tvoid *handle;\n\tdns_plugin_init_func init_func;\n\tdns_plugin_exit_func exit_func;\n};\n\nstruct dns_plugins {\n\tstruct list_head list;\n\tpthread_rwlock_t lock;\n\tDECLARE_HASHTABLE(plugin, 4);\n};\n\nstatic struct dns_plugins plugins;\nstatic atomic_t is_plugin_init = ATOMIC_INIT(0);\n\nint smartdns_plugin_func_server_recv(struct dns_packet *packet, unsigned char *inpacket, int inpacket_len,\n\t\t\t\t\t\t\t\t\t struct sockaddr_storage *local, socklen_t local_len, struct sockaddr_storage *from,\n\t\t\t\t\t\t\t\t\t socklen_t from_len)\n{\n\tstruct dns_plugin_ops *chain = NULL;\n\tint ret = 0;\n\n\tif (unlikely(atomic_read(&is_plugin_init) == 0)) {\n\t\treturn 0;\n\t}\n\n\tpthread_rwlock_rdlock(&plugins.lock);\n\tlist_for_each_entry(chain, &plugins.list, list)\n\t{\n\t\tif (!chain->ops.server_recv) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tret = chain->ops.server_recv(packet, inpacket, inpacket_len, local, local_len, from, from_len);\n\t\tif (ret != 0) {\n\t\t\tpthread_rwlock_unlock(&plugins.lock);\n\t\t\treturn ret;\n\t\t}\n\t}\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn 0;\n}\n\nvoid smartdns_plugin_func_server_complete_request(struct dns_request *request)\n{\n\tstruct dns_plugin_ops *chain = NULL;\n\n\tif (unlikely(atomic_read(&is_plugin_init) == 0)) {\n\t\treturn;\n\t}\n\n\tpthread_rwlock_rdlock(&plugins.lock);\n\tlist_for_each_entry(chain, &plugins.list, list)\n\t{\n\t\tif (!chain->ops.server_query_complete) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tchain->ops.server_query_complete(request);\n\t}\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn;\n}\n\nvoid smartdns_plugin_func_server_log_callback(smartdns_log_level level, const char *msg, int msg_len)\n{\n\tstruct dns_plugin_ops *chain = NULL;\n\n\tif (unlikely(atomic_read(&is_plugin_init) == 0)) {\n\t\treturn;\n\t}\n\n\tpthread_rwlock_rdlock(&plugins.lock);\n\tlist_for_each_entry(chain, &plugins.list, list)\n\t{\n\t\tif (!chain->ops.server_log) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tchain->ops.server_log(level, msg, msg_len);\n\t}\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn;\n}\n\nvoid smartdns_plugin_func_server_audit_log_callback(const char *msg, int msg_len)\n{\n\tstruct dns_plugin_ops *chain = NULL;\n\n\tif (unlikely(atomic_read(&is_plugin_init) == 0)) {\n\t\treturn;\n\t}\n\n\tpthread_rwlock_rdlock(&plugins.lock);\n\tlist_for_each_entry(chain, &plugins.list, list)\n\t{\n\t\tif (!chain->ops.server_audit_log) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tchain->ops.server_audit_log(msg, msg_len);\n\t}\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn;\n}\n\nint smartdns_operations_register(const struct smartdns_operations *operations)\n{\n\tstruct dns_plugin_ops *chain = NULL;\n\n\tchain = (struct dns_plugin_ops *)malloc(sizeof(struct dns_plugin_ops));\n\tif (!chain) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(&chain->ops, operations, sizeof(struct smartdns_operations));\n\tpthread_rwlock_wrlock(&plugins.lock);\n\tlist_add_tail(&chain->list, &plugins.list);\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn 0;\n}\n\nint smartdns_operations_unregister(const struct smartdns_operations *operations)\n{\n\tstruct dns_plugin_ops *chain = NULL;\n\tstruct dns_plugin_ops *tmp = NULL;\n\n\tpthread_rwlock_wrlock(&plugins.lock);\n\tlist_for_each_entry_safe(chain, tmp, &plugins.list, list)\n\t{\n\t\tif (memcmp(&chain->ops, operations, sizeof(struct smartdns_operations)) == 0) {\n\t\t\tlist_del(&chain->list);\n\t\t\tpthread_rwlock_unlock(&plugins.lock);\n\t\t\tfree(chain);\n\t\t\treturn 0;\n\t\t}\n\t}\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn -1;\n}\n\nstatic struct dns_plugin *_dns_plugin_get(const char *plugin_file)\n{\n\tstruct dns_plugin *plugin = NULL;\n\tunsigned int key = 0;\n\n\tkey = hash_string(plugin_file);\n\tpthread_rwlock_rdlock(&plugins.lock);\n\thash_for_each_possible(plugins.plugin, plugin, node, key)\n\t{\n\t\tif (strncmp(plugin->file, plugin_file, PATH_MAX - 1) == 0) {\n\t\t\tpthread_rwlock_unlock(&plugins.lock);\n\t\t\treturn plugin;\n\t\t}\n\t}\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn NULL;\n}\n\nstatic int _dns_plugin_load_library(struct dns_plugin *plugin)\n{\n\tvoid *handle = NULL;\n\tdns_plugin_api_version_func version_func = NULL;\n\tdns_plugin_init_func init_func = NULL;\n\tdns_plugin_exit_func exit_func = NULL;\n\tunsigned int api_version = 0;\n\n\ttlog(TLOG_DEBUG, \"load plugin %s\", plugin->file);\n\n\thandle = dlopen(plugin->file, RTLD_LAZY | RTLD_LOCAL);\n\tif (!handle) {\n\t\ttlog(TLOG_ERROR, \"load plugin %s failed: %s\", plugin->file, dlerror());\n\t\treturn -1;\n\t}\n\n\tversion_func = (dns_plugin_api_version_func)dlsym(handle, DNS_PLUGIN_API_VERSION_FUNC);\n\tif (!version_func) {\n\t\ttlog(TLOG_ERROR,\n\t\t\t \"plugin %s has no api version function, maybe an old version plugin, please download latest version.\",\n\t\t\t plugin->file);\n\t\tgoto errout;\n\t}\n\n\tinit_func = (dns_plugin_init_func)dlsym(handle, DNS_PLUGIN_INIT_FUNC);\n\tif (!init_func) {\n\t\ttlog(TLOG_ERROR, \"load plugin failed: %s\", dlerror());\n\t\ttlog(TLOG_ERROR, \"%s is not a valid smartdns plugin, please check 'plugin' option.\", plugin->file);\n\t\tgoto errout;\n\t}\n\n\texit_func = (dns_plugin_exit_func)dlsym(handle, DNS_PLUGIN_EXIT_FUNC);\n\tif (!exit_func) {\n\t\ttlog(TLOG_ERROR, \"load plugin failed: %s\", dlerror());\n\t\ttlog(TLOG_ERROR, \"%s not a valid smartdns plugin, please check 'plugin' option.\", plugin->file);\n\t\tgoto errout;\n\t}\n\n\tapi_version = version_func();\n\tif (SMARTDNS_PLUGIN_API_VERSION_MAJOR(api_version) !=\n\t\tSMARTDNS_PLUGIN_API_VERSION_MAJOR(SMARTDNS_PLUGIN_API_VERSION)) {\n\t\ttlog(TLOG_ERROR,\n\t\t\t \"plugin %s api version %u.%u not compatible with smartdns api version %u.%u, please download matching \"\n\t\t\t \"version.\",\n\t\t\t plugin->file, SMARTDNS_PLUGIN_API_VERSION_MAJOR(api_version),\n\t\t\t SMARTDNS_PLUGIN_API_VERSION_MINOR(api_version),\n\t\t\t SMARTDNS_PLUGIN_API_VERSION_MAJOR(SMARTDNS_PLUGIN_API_VERSION),\n\t\t\t SMARTDNS_PLUGIN_API_VERSION_MINOR(SMARTDNS_PLUGIN_API_VERSION));\n\t\tgoto errout;\n\t} else if (SMARTDNS_PLUGIN_API_VERSION_MINOR(api_version) >\n\t\t\t   SMARTDNS_PLUGIN_API_VERSION_MINOR(SMARTDNS_PLUGIN_API_VERSION)) {\n\t\ttlog(TLOG_ERROR,\n\t\t\t \"plugin %s api version %u.%u is newer than smartdns api version %u.%u, please download matching version.\",\n\t\t\t plugin->file, SMARTDNS_PLUGIN_API_VERSION_MAJOR(api_version),\n\t\t\t SMARTDNS_PLUGIN_API_VERSION_MINOR(api_version),\n\t\t\t SMARTDNS_PLUGIN_API_VERSION_MAJOR(SMARTDNS_PLUGIN_API_VERSION),\n\t\t\t SMARTDNS_PLUGIN_API_VERSION_MINOR(SMARTDNS_PLUGIN_API_VERSION));\n\t\tgoto errout;\n\t}\n\n\tconf_getopt_reset();\n\tint ret = init_func(plugin);\n\tconf_getopt_reset();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"init plugin %s failed\", plugin->file);\n\t\tgoto errout;\n\t}\n\n\tplugin->handle = handle;\n\tplugin->init_func = init_func;\n\tplugin->exit_func = exit_func;\n\n\treturn 0;\n\nerrout:\n\tif (handle) {\n\t\tdlclose(handle);\n\t}\n\treturn -1;\n}\n\nstatic int _dns_plugin_unload_library(struct dns_plugin *plugin)\n{\n\tint ret = 0;\n\tif (plugin->exit_func) {\n\t\tret = plugin->exit_func(plugin);\n\t\tif (ret != 0) {\n\t\t\ttlog(TLOG_ERROR, \"exit plugin %s failed\", plugin->file);\n\t\t}\n\t}\n\n\tif (plugin->handle) {\n\t\tdlclose(plugin->handle);\n\t\tplugin->handle = NULL;\n\t}\n\n\treturn 0;\n}\n\nstatic struct dns_plugin *_dns_plugin_new(const char *plugin_file)\n{\n\tstruct dns_plugin *plugin = NULL;\n\n\tplugin = _dns_plugin_get(plugin_file);\n\tif (plugin) {\n\t\treturn NULL;\n\t}\n\n\tplugin = (struct dns_plugin *)zalloc(1, sizeof(struct dns_plugin));\n\tif (!plugin) {\n\t\treturn NULL;\n\t}\n\tstrncpy(plugin->file, plugin_file, PATH_MAX - 1);\n\n\treturn plugin;\n}\n\nstatic int _dns_plugin_remove(struct dns_plugin *plugin)\n{\n\t_dns_plugin_unload_library(plugin);\n\tpthread_rwlock_wrlock(&plugins.lock);\n\thash_del(&plugin->node);\n\tpthread_rwlock_unlock(&plugins.lock);\n\tfree(plugin);\n\n\treturn 0;\n}\n\nint dns_plugin_get_argc(struct dns_plugin *plugin)\n{\n\treturn plugin->argc;\n}\n\nconst char **dns_plugin_get_argv(struct dns_plugin *plugin)\n{\n\treturn (const char **)plugin->argv;\n}\n\nint dns_plugin_add(const char *plugin_file, int argc, const char *args, int args_len)\n{\n\tstruct dns_plugin *plugin = NULL;\n\tconst char *plugin_args = NULL;\n\n\tplugin = _dns_plugin_new(plugin_file);\n\tif (!plugin) {\n\t\ttlog(TLOG_ERROR, \"add plugin %s failed\", plugin_file);\n\t\treturn -1;\n\t}\n\n\tmemcpy(plugin->args, args, PATH_MAX - 1);\n\tplugin->argc = argc;\n\tplugin_args = plugin->args;\n\tfor (int i = 0; i < argc && i < DNS_PLUGIN_MAX_ARGS; i++) {\n\t\tplugin->argv[i] = (char *)plugin_args;\n\t\tplugin_args += strlen(plugin_args) + 1;\n\t}\n\n\tif (_dns_plugin_load_library(plugin) != 0) {\n\t\tgoto errout;\n\t}\n\n\thash_add(plugins.plugin, &plugin->node, hash_string(plugin_file));\n\n\treturn 0;\nerrout:\n\tif (plugin) {\n\t\t_dns_plugin_remove(plugin);\n\t}\n\treturn -1;\n}\n\nint dns_plugin_remove(const char *plugin_file)\n{\n\tstruct dns_plugin *plugin = NULL;\n\n\tplugin = _dns_plugin_get(plugin_file);\n\tif (plugin == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn _dns_plugin_remove(plugin);\n}\n\nstatic int _dns_plugin_remove_all_ops(void)\n{\n\tstruct dns_plugin_ops *chain = NULL;\n\tstruct dns_plugin_ops *tmp = NULL;\n\n\tpthread_rwlock_wrlock(&plugins.lock);\n\tlist_for_each_entry_safe(chain, tmp, &plugins.list, list)\n\t{\n\t\tlist_del(&chain->list);\n\t\tfree(chain);\n\t}\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn 0;\n}\n\nstatic int _dns_plugin_remove_all(void)\n{\n\tstruct dns_plugin *plugin = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned int key = 0;\n\n\tpthread_rwlock_wrlock(&plugins.lock);\n\t/* avoid hang */\n\twhile (!hash_empty(plugins.plugin)) {\n\t\tpthread_rwlock_unlock(&plugins.lock);\n\t\thash_for_each_safe(plugins.plugin, key, tmp, plugin, node)\n\t\t{\n\t\t\t_dns_plugin_remove(plugin);\n\t\t\tbreak;\n\t\t}\n\t\tpthread_rwlock_wrlock(&plugins.lock);\n\t}\n\tpthread_rwlock_unlock(&plugins.lock);\n\n\treturn -1;\n}\n\nint dns_server_plugin_init(void)\n{\n\tif (atomic_read(&is_plugin_init) == 1) {\n\t\treturn 0;\n\t}\n\n\thash_init(plugins.plugin);\n\tINIT_LIST_HEAD(&plugins.list);\n\tif (pthread_rwlock_init(&plugins.lock, NULL) != 0) {\n\t\ttlog(TLOG_ERROR, \"init plugin rwlock failed.\");\n\t\treturn -1;\n\t}\n\tatomic_set(&is_plugin_init, 1);\n\treturn 0;\n}\n\nvoid dns_server_plugin_exit(void)\n{\n\tif (atomic_read(&is_plugin_init) == 0) {\n\t\treturn;\n\t}\n\n\t_dns_plugin_remove_all_ops();\n\t_dns_plugin_remove_all();\n\n\tpthread_rwlock_destroy(&plugins.lock);\n\tatomic_set(&is_plugin_init, 0);\n\treturn;\n}\n\nvoid smartdns_plugin_log(smartdns_log_level level, const char *file, int line, const char *func, const char *msg)\n{\n\ttlog_ext((tlog_level)level, file, line, func, NULL, \"%s\", msg);\n}\n\nint smartdns_plugin_can_log(smartdns_log_level level)\n{\n\treturn tlog_getlevel() <= (tlog_level)level;\n}\n\nvoid smartdns_plugin_log_setlevel(smartdns_log_level level)\n{\n\ttlog_setlevel((tlog_level)level);\n}\n\nint smartdns_plugin_log_getlevel(void)\n{\n\treturn tlog_getlevel();\n}\n\nint smartdns_plugin_is_audit_enabled(void)\n{\n\treturn dns_conf.audit_enable;\n}\n\nconst char *smartdns_plugin_get_config(const char *key)\n{\n\treturn dns_conf_get_plugin_conf(key);\n}\n\nvoid smartdns_plugin_clear_all_config(void)\n{\n\tdns_conf_clear_all_plugin_conf();\n}"
  },
  {
    "path": "src/dns_server/address.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"address.h\"\n#include \"context.h\"\n#include \"dns_server.h\"\n#include \"ptr.h\"\n#include \"request.h\"\n#include \"rules.h\"\n#include \"speed_check.h\"\n\nint _dns_server_is_adblock_ipv6(const unsigned char addr[16])\n{\n\tint i = 0;\n\n\tfor (i = 0; i < 15; i++) {\n\t\tif (addr[i]) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (addr[15] == 0 || addr[15] == 1) {\n\t\treturn 0;\n\t}\n\n\treturn -1;\n}\n\nint _dns_server_address_generate_order(int orders[], int order_num, int max_order_count)\n{\n\tint i = 0;\n\tint j = 0;\n\tint k = 0;\n\tunsigned int seed = time(NULL);\n\n\tfor (i = 0; i < order_num && i < max_order_count; i++) {\n\t\torders[i] = i;\n\t}\n\n\tfor (i = 0; i < order_num && max_order_count; i++) {\n\t\tk = rand_r(&seed) % order_num;\n\t\tj = rand_r(&seed) % order_num;\n\t\tif (j == k) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tint temp = orders[j];\n\t\torders[j] = orders[k];\n\t\torders[k] = temp;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_process_address(struct dns_request *request)\n{\n\tstruct dns_rule_address_IPV4 *address_ipv4 = NULL;\n\tstruct dns_rule_address_IPV6 *address_ipv6 = NULL;\n\tint orders[DNS_MAX_REPLY_IP_NUM];\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) {\n\t\tgoto errout;\n\t}\n\n\t/* address /domain/ rule */\n\tswitch (request->qtype) {\n\tcase DNS_T_A:\n\t\tif (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\taddress_ipv4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV4);\n\t\tif (address_ipv4 == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\t_dns_server_address_generate_order(orders, address_ipv4->addr_num, DNS_MAX_REPLY_IP_NUM);\n\n\t\tmemcpy(request->ip_addr, address_ipv4->ipv4_addr[orders[0]], DNS_RR_A_LEN);\n\t\tfor (int i = 1; i < address_ipv4->addr_num; i++) {\n\t\t\tint index = orders[i];\n\t\t\tif (index >= address_ipv4->addr_num) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t_dns_ip_address_check_add(request, request->cname, address_ipv4->ipv4_addr[index], DNS_T_A, 1, NULL);\n\t\t}\n\t\tbreak;\n\tcase DNS_T_AAAA:\n\t\tif (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\taddress_ipv6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV6);\n\t\tif (address_ipv6 == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\t_dns_server_address_generate_order(orders, address_ipv6->addr_num, DNS_MAX_REPLY_IP_NUM);\n\n\t\tmemcpy(request->ip_addr, address_ipv6->ipv6_addr[orders[0]], DNS_RR_AAAA_LEN);\n\t\tfor (int i = 1; i < address_ipv6->addr_num; i++) {\n\t\t\tint index = orders[i];\n\t\t\tif (index >= address_ipv6->addr_num) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t_dns_ip_address_check_add(request, request->cname, address_ipv6->ipv6_addr[index], DNS_T_AAAA, 1, NULL);\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\trequest->rcode = DNS_RC_NOERROR;\n\trequest->ip_ttl = _dns_server_get_local_ttl(request);\n\trequest->has_ip = 1;\n\n\tstruct dns_server_post_context context;\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_reply = 1;\n\tcontext.do_audit = 1;\n\tcontext.do_ipset = 1;\n\tcontext.select_all_best_ip = 1;\n\t_dns_request_post(&context);\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint _dns_ip_address_check_add(struct dns_request *request, char *cname, unsigned char *addr, dns_type_t addr_type,\n\t\t\t\t\t\t\t  int ping_time, struct dns_ip_address **out_addr_map)\n{\n\tuint32_t key = 0;\n\tstruct dns_ip_address *addr_map = NULL;\n\tint addr_len = 0;\n\n\tif (ping_time == 0) {\n\t\tping_time = -1;\n\t}\n\n\tif (addr_type == DNS_T_A) {\n\t\taddr_len = DNS_RR_A_LEN;\n\t} else if (addr_type == DNS_T_AAAA) {\n\t\taddr_len = DNS_RR_AAAA_LEN;\n\t} else {\n\t\treturn -1;\n\t}\n\n\t/* store the ip address and the number of hits */\n\tkey = jhash(addr, addr_len, 0);\n\tkey = jhash(&addr_type, sizeof(addr_type), key);\n\tpthread_mutex_lock(&request->ip_map_lock);\n\thash_for_each_possible(request->ip_map, addr_map, node, key)\n\t{\n\t\tif (addr_map->addr_type != addr_type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (memcmp(addr_map->ip_addr, addr, addr_len) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\taddr_map->hitnum++;\n\t\taddr_map->recv_tick = get_tick_count();\n\t\tpthread_mutex_unlock(&request->ip_map_lock);\n\t\treturn -1;\n\t}\n\n\tatomic_inc(&request->ip_map_num);\n\taddr_map = zalloc(1, sizeof(*addr_map));\n\tif (addr_map == NULL) {\n\t\tpthread_mutex_unlock(&request->ip_map_lock);\n\t\ttlog(TLOG_ERROR, \"malloc addr map failed\");\n\t\treturn -1;\n\t}\n\n\taddr_map->addr_type = addr_type;\n\taddr_map->hitnum = 1;\n\taddr_map->recv_tick = get_tick_count();\n\taddr_map->ping_time = ping_time;\n\tmemcpy(addr_map->ip_addr, addr, addr_len);\n\tif (request->conf->dns_force_no_cname == 0) {\n\t\tsafe_strncpy(addr_map->cname, cname, DNS_MAX_CNAME_LEN);\n\t}\n\n\thash_add(request->ip_map, &addr_map->node, key);\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\tif (out_addr_map != NULL) {\n\t\t*out_addr_map = addr_map;\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_server_select_possible_ipaddress(struct dns_request *request)\n{\n\tint maxhit = 0;\n\tunsigned long bucket = 0;\n\tunsigned long max_recv_tick = 0;\n\tstruct dns_ip_address *addr_map = NULL;\n\tstruct dns_ip_address *maxhit_addr_map = NULL;\n\tstruct dns_ip_address *last_recv_addr_map = NULL;\n\tstruct dns_ip_address *selected_addr_map = NULL;\n\tstruct hlist_node *tmp = NULL;\n\n\tif (atomic_read(&request->notified) > 0) {\n\t\treturn;\n\t}\n\n\tif (request->no_select_possible_ip != 0) {\n\t\treturn;\n\t}\n\n\tif (request->ping_time > 0) {\n\t\treturn;\n\t}\n\n\t/* Return the most likely correct IP address */\n\t/* Returns the IP with the most hits, or the last returned record is considered to be the most likely\n\t * correct. */\n\tpthread_mutex_lock(&request->ip_map_lock);\n\thash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)\n\t{\n\t\tif (addr_map->addr_type != request->qtype) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (addr_map->recv_tick - request->send_tick > max_recv_tick) {\n\t\t\tmax_recv_tick = addr_map->recv_tick - request->send_tick;\n\t\t\tlast_recv_addr_map = addr_map;\n\t\t}\n\n\t\tif (addr_map->hitnum > maxhit) {\n\t\t\tmaxhit = addr_map->hitnum;\n\t\t\tmaxhit_addr_map = addr_map;\n\t\t}\n\t}\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\tif (maxhit_addr_map && maxhit > 1) {\n\t\tselected_addr_map = maxhit_addr_map;\n\t} else if (last_recv_addr_map) {\n\t\tselected_addr_map = last_recv_addr_map;\n\t}\n\n\tif (selected_addr_map == NULL) {\n\t\treturn;\n\t}\n\n\ttlog(TLOG_DEBUG, \"select best ip address, %s\", request->domain);\n\tswitch (request->qtype) {\n\tcase DNS_T_A: {\n\t\tmemcpy(request->ip_addr, selected_addr_map->ip_addr, DNS_RR_A_LEN);\n\t\ttlog(TLOG_DEBUG, \"possible result: %s, rcode: %d,  hitnum: %d, %d.%d.%d.%d\", request->domain, request->rcode,\n\t\t\t selected_addr_map->hitnum, request->ip_addr[0], request->ip_addr[1], request->ip_addr[2],\n\t\t\t request->ip_addr[3]);\n\t} break;\n\tcase DNS_T_AAAA: {\n\t\tmemcpy(request->ip_addr, selected_addr_map->ip_addr, DNS_RR_AAAA_LEN);\n\t\ttlog(TLOG_DEBUG,\n\t\t\t \"possible result: %s, rcode: %d,  hitnum: %d, \"\n\t\t\t \"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t\t request->domain, request->rcode, selected_addr_map->hitnum, request->ip_addr[0], request->ip_addr[1],\n\t\t\t request->ip_addr[2], request->ip_addr[3], request->ip_addr[4], request->ip_addr[5], request->ip_addr[6],\n\t\t\t request->ip_addr[7], request->ip_addr[8], request->ip_addr[9], request->ip_addr[10], request->ip_addr[11],\n\t\t\t request->ip_addr[12], request->ip_addr[13], request->ip_addr[14], request->ip_addr[15]);\n\t} break;\n\tdefault:\n\t\tbreak;\n\t}\n}\n\nstruct dns_ip_address *_dns_ip_address_get(struct dns_request *request, unsigned char *addr, dns_type_t addr_type)\n{\n\tuint32_t key = 0;\n\tstruct dns_ip_address *addr_map = NULL;\n\tstruct dns_ip_address *addr_tmp = NULL;\n\tint addr_len = 0;\n\n\tif (addr_type == DNS_T_A) {\n\t\taddr_len = DNS_RR_A_LEN;\n\t} else if (addr_type == DNS_T_AAAA) {\n\t\taddr_len = DNS_RR_AAAA_LEN;\n\t} else {\n\t\treturn NULL;\n\t}\n\n\t/* store the ip address and the number of hits */\n\tkey = jhash(addr, addr_len, 0);\n\tkey = jhash(&addr_type, sizeof(addr_type), key);\n\tpthread_mutex_lock(&request->ip_map_lock);\n\thash_for_each_possible(request->ip_map, addr_tmp, node, key)\n\t{\n\t\tif (addr_type != addr_tmp->addr_type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (memcmp(addr_tmp->ip_addr, addr, addr_len) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\taddr_map = addr_tmp;\n\t\tbreak;\n\t}\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\treturn addr_map;\n}\n"
  },
  {
    "path": "src/dns_server/address.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_ADDRESS_\n#define _DNS_SERVER_ADDRESS_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_server_select_possible_ipaddress(struct dns_request *request);\n\nint _dns_ip_address_check_add(struct dns_request *request, char *cname, unsigned char *addr, dns_type_t addr_type,\n\t\t\t\t\t\t\t  int ping_time, struct dns_ip_address **out_addr_map);\n\nstruct dns_ip_address *_dns_ip_address_get(struct dns_request *request, unsigned char *addr, dns_type_t addr_type);\n\nint _dns_server_process_address(struct dns_request *request);\n\nint _dns_server_is_adblock_ipv6(const unsigned char addr[16]);\n\nint _dns_server_address_generate_order(int orders[], int order_num, int max_order_count);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/answer.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"answer.h\"\n#include \"address.h\"\n#include \"dns_server.h\"\n#include \"ip_rule.h\"\n#include \"request.h\"\n#include \"rules.h\"\n#include \"soa.h\"\n#include \"speed_check.h\"\n\n#include <math.h>\n\nstatic int _dns_server_process_answer_A_IP(struct dns_request *request, char *cname, unsigned char addr[4], int ttl,\n\t\t\t\t\t\t\t\t\t\t   unsigned int result_flag)\n{\n\tchar ip[DNS_MAX_CNAME_LEN] = {0};\n\tint ip_check_result = 0;\n\tunsigned char *paddrs[MAX_IP_NUM];\n\tint paddr_num = 0;\n\tstruct dns_iplist_ip_addresses *alias = NULL;\n\n\tpaddrs[paddr_num] = addr;\n\tpaddr_num = 1;\n\n\t/* ip rule check */\n\tip_check_result = _dns_server_process_ip_rule(request, addr, 4, DNS_T_A, result_flag, &alias);\n\tif (ip_check_result == 0) {\n\t\t/* match */\n\t\treturn -1;\n\t} else if (ip_check_result == -2 || ip_check_result == -3) {\n\t\t/* skip, nxdomain */\n\t\treturn ip_check_result;\n\t}\n\n\tint ret = _dns_server_process_ip_alias(request, alias, paddrs, &paddr_num, MAX_IP_NUM, DNS_RR_A_LEN);\n\tif (ret != 0) {\n\t\treturn ret;\n\t}\n\n\tfor (int i = 0; i < paddr_num; i++) {\n\t\tunsigned char *paddr = paddrs[i];\n\t\tif (atomic_read(&request->ip_map_num) == 0) {\n\t\t\trequest->has_ip = 1;\n\t\t\trequest->ip_addr_type = DNS_T_A;\n\t\t\tmemcpy(request->ip_addr, paddr, DNS_RR_A_LEN);\n\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\tif (cname[0] != 0 && request->has_cname == 0 && request->conf->dns_force_no_cname == 0) {\n\t\t\t\trequest->has_cname = 1;\n\t\t\t\tsafe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN);\n\t\t\t}\n\t\t} else {\n\t\t\tif (ttl < request->ip_ttl) {\n\t\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t}\n\t\t}\n\n\t\t/* Ad blocking result */\n\t\tif (paddr[0] == 0 || paddr[0] == 127) {\n\t\t\t/* If half of the servers return the same result, then ignore this address */\n\t\t\tif (atomic_inc_return(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) {\n\t\t\t\trequest->rcode = DNS_RC_NOERROR;\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\t/* add this ip to request */\n\t\tif (_dns_ip_address_check_add(request, cname, paddr, DNS_T_A, 0, NULL) != 0) {\n\t\t\t/* skip result */\n\t\t\treturn -2;\n\t\t}\n\n\t\tsnprintf(ip, sizeof(ip), \"%d.%d.%d.%d\", paddr[0], paddr[1], paddr[2], paddr[3]);\n\n\t\t/* start ping */\n\t\t_dns_server_request_get(request);\n\t\tif (_dns_server_check_speed(request, ip) != 0) {\n\t\t\t_dns_server_request_release(request);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_server_process_answer_AAAA_IP(struct dns_request *request, char *cname, unsigned char addr[16], int ttl,\n\t\t\t\t\t\t\t\t\t\t\t  unsigned int result_flag)\n{\n\tchar ip[DNS_MAX_CNAME_LEN] = {0};\n\tint ip_check_result = 0;\n\tunsigned char *paddrs[MAX_IP_NUM];\n\tstruct dns_iplist_ip_addresses *alias = NULL;\n\tint paddr_num = 0;\n\n\tpaddrs[paddr_num] = addr;\n\tpaddr_num = 1;\n\n\tip_check_result = _dns_server_process_ip_rule(request, addr, 16, DNS_T_AAAA, result_flag, &alias);\n\tif (ip_check_result == 0) {\n\t\t/* match */\n\t\treturn -1;\n\t} else if (ip_check_result == -2 || ip_check_result == -3) {\n\t\t/* skip, nxdomain */\n\t\treturn ip_check_result;\n\t}\n\n\tint ret = _dns_server_process_ip_alias(request, alias, paddrs, &paddr_num, MAX_IP_NUM, DNS_RR_AAAA_LEN);\n\tif (ret != 0) {\n\t\treturn ret;\n\t}\n\n\tfor (int i = 0; i < paddr_num; i++) {\n\t\tunsigned char *paddr = paddrs[i];\n\t\tif (atomic_read(&request->ip_map_num) == 0) {\n\t\t\trequest->has_ip = 1;\n\t\t\trequest->ip_addr_type = DNS_T_AAAA;\n\t\t\tmemcpy(request->ip_addr, paddr, DNS_RR_AAAA_LEN);\n\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\tif (cname[0] != 0 && request->has_cname == 0 && request->conf->dns_force_no_cname == 0) {\n\t\t\t\trequest->has_cname = 1;\n\t\t\t\tsafe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN);\n\t\t\t}\n\t\t} else {\n\t\t\tif (ttl < request->ip_ttl) {\n\t\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t}\n\t\t}\n\n\t\t/* Ad blocking result */\n\t\tif (_dns_server_is_adblock_ipv6(paddr) == 0) {\n\t\t\t/* If half of the servers return the same result, then ignore this address */\n\t\t\tif (atomic_inc_return(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) {\n\t\t\t\trequest->rcode = DNS_RC_NOERROR;\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\t/* add this ip to request */\n\t\tif (_dns_ip_address_check_add(request, cname, paddr, DNS_T_AAAA, 0, NULL) != 0) {\n\t\t\t/* skip result */\n\t\t\treturn -2;\n\t\t}\n\n\t\tsnprintf(ip, sizeof(ip), \"[%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x]\", paddr[0],\n\t\t\t\t paddr[1], paddr[2], paddr[3], paddr[4], paddr[5], paddr[6], paddr[7], paddr[8], paddr[9], paddr[10],\n\t\t\t\t paddr[11], paddr[12], paddr[13], paddr[14], paddr[15]);\n\n\t\t/* start ping */\n\t\t_dns_server_request_get(request);\n\t\tif (_dns_server_check_speed(request, ip) != 0) {\n\t\t\t_dns_server_request_release(request);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_server_process_answer_A(struct dns_rrs *rrs, struct dns_request *request, const char *domain,\n\t\t\t\t\t\t\t\t\t\tchar *cname, unsigned int result_flag)\n{\n\tint ttl = 0;\n\tunsigned char addr[4];\n\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\n\tif (request->qtype != DNS_T_A) {\n\t\treturn -1;\n\t}\n\n\t/* get A result */\n\tdns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);\n\n\ttlog(TLOG_DEBUG, \"domain: %s TTL: %d IP: %d.%d.%d.%d\", name, ttl, addr[0], addr[1], addr[2], addr[3]);\n\n\t/* if domain is not match */\n\tif (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) {\n\t\treturn -1;\n\t}\n\n\t_dns_server_request_get(request);\n\tint ret = _dns_server_process_answer_A_IP(request, cname, addr, ttl, result_flag);\n\t_dns_server_request_release(request);\n\n\treturn ret;\n}\n\nstatic int _dns_server_process_answer_AAAA(struct dns_rrs *rrs, struct dns_request *request, const char *domain,\n\t\t\t\t\t\t\t\t\t\t   char *cname, unsigned int result_flag)\n{\n\tunsigned char addr[16];\n\n\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\n\tint ttl = 0;\n\n\tif (request->qtype != DNS_T_AAAA) {\n\t\t/* ignore non-matched query type */\n\t\treturn -1;\n\t}\n\n\tdns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);\n\n\ttlog(TLOG_DEBUG, \"domain: %s TTL: %d IP: %.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t name, ttl, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10],\n\t\t addr[11], addr[12], addr[13], addr[14], addr[15]);\n\n\t/* if domain is not match */\n\tif (strncmp(name, domain, DNS_MAX_CNAME_LEN) != 0 && strncmp(cname, name, DNS_MAX_CNAME_LEN) != 0) {\n\t\treturn -1;\n\t}\n\n\t_dns_server_request_get(request);\n\tint ret = _dns_server_process_answer_AAAA_IP(request, cname, addr, ttl, result_flag);\n\t_dns_server_request_release(request);\n\n\treturn ret;\n}\n\nstatic int _dns_server_process_answer_HTTPS(struct dns_rrs *rrs, struct dns_request *request, const char *domain,\n\t\t\t\t\t\t\t\t\t\t\tchar *cname, unsigned int result_flag)\n{\n\tint ttl = 0;\n\tint ret = -1;\n\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\tchar target[DNS_MAX_CNAME_LEN] = {0};\n\tstruct dns_svcparam *p = NULL;\n\tint priority = 0;\n\tstruct dns_request_https *https_svcb;\n\tint no_ipv4 = 0;\n\tint no_ipv6 = 0;\n\tint no_ech = 0;\n\tstruct dns_https_record_rule *https_record_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_HTTPS);\n\n\tif (https_record_rule) {\n\t\tno_ipv4 = https_record_rule->filter.no_ipv4hint;\n\t\tno_ipv6 = https_record_rule->filter.no_ipv6hint;\n\t\tno_ech = https_record_rule->filter.no_ech;\n\t}\n\n\tret = dns_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);\n\tif (ret != 0) {\n\t\ttlog(TLOG_WARN, \"get HTTPS svcparm failed\");\n\t\treturn -1;\n\t}\n\n\thttps_svcb = zalloc(1, sizeof(*https_svcb));\n\tif (https_svcb == NULL) {\n\t\treturn -1;\n\t}\n\tINIT_LIST_HEAD(&https_svcb->list);\n\tlist_add_tail(&https_svcb->list, &request->https_svcb_list);\n\n\ttlog(TLOG_DEBUG, \"domain: %s HTTPS: %s TTL: %d priority: %d\", name, target, ttl, priority);\n\thttps_svcb->ttl = ttl;\n\thttps_svcb->priority = priority;\n\tsafe_strncpy(https_svcb->target, target, sizeof(https_svcb->target));\n\tsafe_strncpy(https_svcb->domain, name, sizeof(https_svcb->domain));\n\trequest->ip_ttl = ttl;\n\n\t_dns_server_request_get(request);\n\tfor (; p; p = dns_svcparm_next(rrs, p)) {\n\t\tswitch (p->key) {\n\t\tcase DNS_HTTPS_T_MANDATORY: {\n\t\t} break;\n\t\tcase DNS_HTTPS_T_ALPN: {\n\t\t\tmemcpy(https_svcb->alpn, p->value, sizeof(https_svcb->alpn));\n\t\t\thttps_svcb->alpn_len = p->len;\n\t\t} break;\n\t\tcase DNS_HTTPS_T_NO_DEFAULT_ALPN: {\n\t\t} break;\n\t\tcase DNS_HTTPS_T_PORT: {\n\t\t\tint port = *(unsigned short *)(p->value);\n\t\t\thttps_svcb->port = ntohs(port);\n\t\t} break;\n\t\tcase DNS_HTTPS_T_IPV4HINT: {\n\t\t\tstruct dns_rule_address_IPV4 *address_ipv4 = NULL;\n\t\t\tif (_dns_server_is_return_soa_qtype(request, DNS_T_A) || no_ipv4 == 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\taddress_ipv4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV4);\n\t\t\tif (address_ipv4 != NULL) {\n\t\t\t\tmemcpy(request->ip_addr, address_ipv4->ipv4_addr, DNS_RR_A_LEN);\n\t\t\t\trequest->has_ip = 1;\n\t\t\t\trequest->ip_addr_type = DNS_T_A;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor (int k = 0; k < p->len / 4; k++) {\n\t\t\t\t_dns_server_process_answer_A_IP(request, cname, p->value + k * 4, ttl, result_flag);\n\t\t\t}\n\t\t} break;\n\t\tcase DNS_HTTPS_T_ECH: {\n\t\t\tif (no_ech == 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (p->len > sizeof(https_svcb->ech)) {\n\t\t\t\ttlog(TLOG_WARN, \"ech too long\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tmemcpy(https_svcb->ech, p->value, p->len);\n\t\t\thttps_svcb->ech_len = p->len;\n\t\t} break;\n\t\tcase DNS_HTTPS_T_IPV6HINT: {\n\t\t\tstruct dns_rule_address_IPV6 *address_ipv6 = NULL;\n\n\t\t\tif (_dns_server_is_return_soa_qtype(request, DNS_T_AAAA) || no_ipv6 == 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_ADDR) == 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\taddress_ipv6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_ADDRESS_IPV6);\n\t\t\tif (address_ipv6 != NULL) {\n\t\t\t\tmemcpy(request->ip_addr, address_ipv6->ipv6_addr, DNS_RR_AAAA_LEN);\n\t\t\t\trequest->has_ip = 1;\n\t\t\t\trequest->ip_addr_type = DNS_T_AAAA;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor (int k = 0; k < p->len / 16; k++) {\n\t\t\t\t_dns_server_process_answer_AAAA_IP(request, cname, p->value + k * 16, ttl, result_flag);\n\t\t\t}\n\t\t} break;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t_dns_server_request_release(request);\n\n\treturn 0;\n}\n\nint _dns_server_process_answer(struct dns_request *request, const char *domain, struct dns_packet *packet,\n\t\t\t\t\t\t\t   unsigned int result_flag, int *need_passthrouh)\n{\n\tint ttl = 0;\n\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\tchar cname[DNS_MAX_CNAME_LEN] = {0};\n\tint rr_count = 0;\n\tint i = 0;\n\tint j = 0;\n\tstruct dns_rrs *rrs = NULL;\n\tint ret = 0;\n\tint is_skip = 0;\n\tint has_result = 0;\n\tint is_rcode_set = 0;\n\n\tif (packet->head.rcode != DNS_RC_NOERROR && packet->head.rcode != DNS_RC_NXDOMAIN) {\n\t\tif (request->rcode == DNS_RC_SERVFAIL) {\n\t\t\trequest->rcode = packet->head.rcode;\n\t\t\trequest->remote_server_fail = 1;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"inquery failed, %s, rcode = %d, id = %d\\n\", domain, packet->head.rcode, packet->head.id);\n\n\t\tif (request->remote_server_fail == 0) {\n\t\t\treturn DNS_CLIENT_ACTION_DROP;\n\t\t}\n\n\t\treturn DNS_CLIENT_ACTION_UNDEFINE;\n\t}\n\n\tfor (j = 1; j < DNS_RRS_OPT; j++) {\n\t\trrs = dns_get_rrs_start(packet, j, &rr_count);\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\t\thas_result = 1;\n\t\t\tswitch (rrs->type) {\n\t\t\tcase DNS_T_A: {\n\t\t\t\tret = _dns_server_process_answer_A(rrs, request, domain, cname, result_flag);\n\t\t\t\tif (ret == -1) {\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (ret == -2) {\n\t\t\t\t\tis_skip = 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if (ret == -3) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\trequest->rcode = packet->head.rcode;\n\t\t\t\tis_rcode_set = 1;\n\t\t\t} break;\n\t\t\tcase DNS_T_AAAA: {\n\t\t\t\tret = _dns_server_process_answer_AAAA(rrs, request, domain, cname, result_flag);\n\t\t\t\tif (ret == -1) {\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (ret == -2) {\n\t\t\t\t\tis_skip = 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if (ret == -3) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\trequest->rcode = packet->head.rcode;\n\t\t\t\tis_rcode_set = 1;\n\t\t\t} break;\n\t\t\tcase DNS_T_NS: {\n\t\t\t\tchar nsname[DNS_MAX_CNAME_LEN];\n\t\t\t\tdns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, nsname, DNS_MAX_CNAME_LEN);\n\t\t\t\ttlog(TLOG_DEBUG, \"NS: %s ttl: %d nsname: %s\\n\", name, ttl, nsname);\n\t\t\t} break;\n\t\t\tcase DNS_T_CNAME: {\n\t\t\t\tchar domain_name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tchar domain_cname[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tdns_get_CNAME(rrs, domain_name, DNS_MAX_CNAME_LEN, &ttl, domain_cname, DNS_MAX_CNAME_LEN);\n\t\t\t\tif (strncasecmp(domain_name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 &&\n\t\t\t\t\tstrncasecmp(domain_name, cname, DNS_MAX_CNAME_LEN - 1) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tsafe_strncpy(cname, domain_cname, DNS_MAX_CNAME_LEN);\n\t\t\t\trequest->ttl_cname = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t\ttlog(TLOG_DEBUG, \"name: %s ttl: %d cname: %s\\n\", domain_name, ttl, cname);\n\t\t\t} break;\n\t\t\tcase DNS_T_HTTPS: {\n\t\t\t\tret = _dns_server_process_answer_HTTPS(rrs, request, domain, cname, result_flag);\n\t\t\t\tif (ret == -1) {\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (ret == -2) {\n\t\t\t\t\tis_skip = 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\trequest->rcode = packet->head.rcode;\n\t\t\t\tis_rcode_set = 1;\n\t\t\t} break;\n\t\t\tcase DNS_T_SOA: {\n\t\t\t\t/* if DNS64 enabled, skip check SOA. */\n\t\t\t\tif (_dns_server_is_dns64_request(request)) {\n\t\t\t\t\tif (request->has_ip) {\n\t\t\t\t\t\t_dns_server_request_complete(request);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\trequest->has_soa = 1;\n\t\t\t\tif (request->rcode != DNS_RC_NOERROR) {\n\t\t\t\t\trequest->rcode = packet->head.rcode;\n\t\t\t\t\tis_rcode_set = 1;\n\t\t\t\t}\n\t\t\t\tdns_get_SOA(rrs, name, 128, &ttl, &request->soa);\n\t\t\t\ttlog(TLOG_DEBUG,\n\t\t\t\t\t \"domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, \"\n\t\t\t\t\t \"expire: \"\n\t\t\t\t\t \"%d, minimum: %d\",\n\t\t\t\t\t domain, request->qtype, request->soa.mname, request->soa.rname, request->soa.serial,\n\t\t\t\t\t request->soa.refresh, request->soa.retry, request->soa.expire, request->soa.minimum);\n\n\t\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t\tint soa_num = atomic_inc_return(&request->soa_num);\n\t\t\t\tif ((soa_num >= ((int)ceilf((float)dns_server_alive_num() / 3) + 1) || soa_num > 4) &&\n\t\t\t\t\tatomic_read(&request->ip_map_num) <= 0) {\n\t\t\t\t\trequest->ip_ttl = ttl;\n\t\t\t\t\t_dns_server_request_complete(request);\n\t\t\t\t}\n\t\t\t} break;\n\t\t\tdefault:\n\t\t\t\ttlog(TLOG_DEBUG, \"%s, qtype: %d, rrstype = %d\", name, rrs->type, j);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\trequest->remote_server_fail = 0;\n\tif (request->rcode == DNS_RC_SERVFAIL && is_skip == 0) {\n\t\trequest->rcode = packet->head.rcode;\n\t}\n\n\t/* return NOERROR if all ips are skipped */\n\tif (request->rcode == DNS_RC_SERVFAIL && has_result == 1 && is_skip == 1) {\n\t\trequest->rcode = DNS_RC_NOERROR;\n\t}\n\n\tif (has_result == 0 && request->rcode == DNS_RC_NOERROR && packet->head.tc == 1 && request->has_ip == 0 &&\n\t\trequest->has_soa == 0) {\n\t\ttlog(TLOG_DEBUG, \"result is truncated, %s qtype: %d, rcode: %d, id: %d, retry.\", domain, request->qtype,\n\t\t\t packet->head.rcode, packet->head.id);\n\t\treturn DNS_CLIENT_ACTION_RETRY;\n\t}\n\n\tif (is_rcode_set == 0 && has_result == 1 && is_skip == 0) {\n\t\t/* need retry for some server. */\n\t\treturn DNS_CLIENT_ACTION_MAY_RETRY;\n\t}\n\n\treturn DNS_CLIENT_ACTION_OK;\n}\n"
  },
  {
    "path": "src/dns_server/answer.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_ANSWER_\n#define _DNS_SERVER_ANSWER_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_process_answer(struct dns_request *request, const char *domain, struct dns_packet *packet,\n\t\t\t\t\t\t\t   unsigned int result_flag, int *need_passthrouh);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/audit.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"audit.h\"\n#include \"dns_server.h\"\n\n#include \"smartdns/dns_plugin.h\"\n\n#include <syslog.h>\n\nstatic tlog_log *dns_audit;\n\nvoid _dns_server_audit_log(struct dns_server_post_context *context)\n{\n\tchar req_host[MAX_IP_LEN];\n\tchar req_result[1024] = {0};\n\tchar *ip_msg = req_result;\n\tchar req_time[MAX_IP_LEN] = {0};\n\tstruct tlog_time tm;\n\tint i = 0;\n\tint j = 0;\n\tint rr_count = 0;\n\tstruct dns_rrs *rrs = NULL;\n\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\tint ttl = 0;\n\tint len = 0;\n\tint left_len = sizeof(req_result);\n\tint total_len = 0;\n\tint ip_num = 0;\n\tstruct dns_request *request = context->request;\n\tint has_soa = request->has_soa;\n\n\tif (atomic_read(&request->notified) == 1) {\n\t\trequest->query_time = get_tick_count() - request->send_tick;\n\t}\n\n\tif (dns_audit == NULL || !dns_conf.audit_enable || context->do_audit == 0) {\n\t\treturn;\n\t}\n\n\t/* skip log prefetch request and dualstack selection request */\n\tif (request->prefetch || request->dualstack_selection_query) {\n\t\treturn;\n\t}\n\n\tfor (j = 1; j < DNS_RRS_OPT && context->packet; j++) {\n\t\trrs = dns_get_rrs_start(context->packet, j, &rr_count);\n\t\tfor (i = 0; i < rr_count && rrs && left_len > 0; i++, rrs = dns_get_rrs_next(context->packet, rrs)) {\n\t\t\tswitch (rrs->type) {\n\t\t\tcase DNS_T_A: {\n\t\t\t\tunsigned char ipv4_addr[4];\n\t\t\t\tif (dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv4_addr) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 &&\n\t\t\t\t\tstrncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst char *fmt = \"%d.%d.%d.%d\";\n\t\t\t\tif (ip_num > 0) {\n\t\t\t\t\tfmt = \", %d.%d.%d.%d\";\n\t\t\t\t}\n\n\t\t\t\tlen =\n\t\t\t\t\tsnprintf(ip_msg + total_len, left_len, fmt, ipv4_addr[0], ipv4_addr[1], ipv4_addr[2], ipv4_addr[3]);\n\t\t\t\tip_num++;\n\t\t\t\thas_soa = 0;\n\t\t\t} break;\n\t\t\tcase DNS_T_AAAA: {\n\t\t\t\tunsigned char ipv6_addr[16];\n\t\t\t\tif (dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv6_addr) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 &&\n\t\t\t\t\tstrncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst char *fmt = \"%s\";\n\t\t\t\tif (ip_num > 0) {\n\t\t\t\t\tfmt = \", %s\";\n\t\t\t\t}\n\t\t\t\treq_host[0] = '\\0';\n\t\t\t\tinet_ntop(AF_INET6, ipv6_addr, req_host, sizeof(req_host));\n\t\t\t\tlen = snprintf(ip_msg + total_len, left_len, fmt, req_host);\n\t\t\t\tip_num++;\n\t\t\t\thas_soa = 0;\n\t\t\t} break;\n\t\t\tcase DNS_T_SOA: {\n\t\t\t\tif (ip_num == 0) {\n\t\t\t\t\thas_soa = 1;\n\t\t\t\t}\n\t\t\t} break;\n\t\t\tdefault:\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (len < 0 || len >= left_len) {\n\t\t\t\tleft_len = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tleft_len -= len;\n\t\t\ttotal_len += len;\n\t\t}\n\t}\n\n\tif (has_soa && ip_num == 0) {\n\t\tif (!dns_conf.audit_log_SOA) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (request->dualstack_selection_force_soa) {\n\t\t\tsnprintf(req_result, left_len, \"dualstack soa\");\n\t\t} else {\n\t\t\tsnprintf(req_result, left_len, \"soa\");\n\t\t}\n\t}\n\n\tget_host_by_addr(req_host, sizeof(req_host), &request->addr);\n\ttlog_localtime(&tm);\n\n\tif (req_host[0] == '\\0') {\n\t\tsafe_strncpy(req_host, \"API\", MAX_IP_LEN);\n\t}\n\n\tif (dns_conf.audit_syslog == 0) {\n\t\tsnprintf(req_time, sizeof(req_time), \"[%.4d-%.2d-%.2d %.2d:%.2d:%.2d,%.3d] \", tm.year, tm.mon, tm.mday, tm.hour,\n\t\t\t\t tm.min, tm.sec, tm.usec / 1000);\n\t}\n\n\ttlog_printf(dns_audit, \"%s%s query %s, type %d, time %dms, speed: %.1fms, group %s, result %s\\n\", req_time,\n\t\t\t\treq_host, request->domain, request->qtype, request->query_time, ((float)request->ping_time) / 10,\n\t\t\t\trequest->dns_group_name[0] != '\\0' ? request->dns_group_name : DNS_SERVER_GROUP_DEFAULT, req_result);\n}\n\nstatic int _dns_server_audit_syslog(struct tlog_log *log, const char *buff, int bufflen)\n{\n\tsyslog(LOG_INFO, \"%.*s\", bufflen, buff);\n\treturn 0;\n}\n\nstatic int _dns_server_audit_output_callback(struct tlog_log *log, const char *buff, int bufflen)\n{\n\tsmartdns_plugin_func_server_audit_log_callback(buff, bufflen);\n\n\tif (dns_conf.audit_syslog) {\n\t\treturn _dns_server_audit_syslog(log, buff, bufflen);\n\t}\n\n\treturn tlog_write(log, buff, bufflen);\n}\n\nint _dns_server_audit_init(void)\n{\n\tchar *audit_file = SMARTDNS_AUDIT_FILE;\n\tunsigned int tlog_flag = 0;\n\n\tif (dns_conf.audit_enable == 0) {\n\t\treturn 0;\n\t}\n\n\tif (dns_conf.audit_file[0] != 0) {\n\t\taudit_file = dns_conf.audit_file;\n\t}\n\n\tif (dns_conf.audit_syslog) {\n\t\ttlog_flag |= TLOG_SEGMENT;\n\t}\n\n\tdns_audit = tlog_open(audit_file, dns_conf.audit_size, dns_conf.audit_num, 0, tlog_flag);\n\tif (dns_audit == NULL) {\n\t\treturn -1;\n\t}\n\n\ttlog_reg_output_func(dns_audit, _dns_server_audit_output_callback);\n\n\tif (dns_conf.audit_file_mode > 0) {\n\t\ttlog_set_permission(dns_audit, dns_conf.audit_file_mode, dns_conf.audit_file_mode);\n\t}\n\n\tif (dns_conf.audit_console != 0) {\n\t\ttlog_logscreen(dns_audit, 1);\n\t}\n\n\treturn 0;\n}"
  },
  {
    "path": "src/dns_server/audit.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_AUDIT_\n#define _DNS_SERVER_AUDIT_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_server_audit_log(struct dns_server_post_context *context);\n\nint _dns_server_audit_init(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/cache.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"cache.h\"\n#include \"answer.h\"\n#include \"context.h\"\n#include \"dns_server.h\"\n#include \"prefetch.h\"\n#include \"request.h\"\n#include \"rules.h\"\n#include \"soa.h\"\n\n#include <errno.h>\n#include <signal.h>\n#include <string.h>\n#include <sys/wait.h>\n\nint _dns_server_expired_cache_ttl(struct dns_cache *cache, int serve_expired_ttl)\n{\n\treturn cache->info.insert_time + cache->info.ttl + serve_expired_ttl - time(NULL);\n}\n\nstatic int _dns_cache_is_specify_packet(int qtype)\n{\n\tswitch (qtype) {\n\tcase DNS_T_PTR:\n\tcase DNS_T_HTTPS:\n\tcase DNS_T_TXT:\n\tcase DNS_T_SRV:\n\tcase DNS_T_CAA:\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t\tbreak;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_server_get_cache_timeout(struct dns_request *request, struct dns_cache_key *cache_key, int ttl)\n{\n\tint timeout = 0;\n\tint prefetch_time = 0;\n\tint is_serve_expired = request->conf->dns_serve_expired;\n\n\tif (request->rcode != DNS_RC_NOERROR) {\n\t\treturn ttl + 1;\n\t}\n\n\tif (request->is_mdns_lookup == 1) {\n\t\treturn ttl + 1;\n\t}\n\n\tif (request->conf->dns_prefetch) {\n\t\tprefetch_time = 1;\n\t}\n\n\tif ((request->prefetch_flags & PREFETCH_FLAGS_NOPREFETCH)) {\n\t\tprefetch_time = 0;\n\t}\n\n\tif (request->edns0_do == 1) {\n\t\tprefetch_time = 0;\n\t}\n\n\tif (request->no_serve_expired) {\n\t\tis_serve_expired = 0;\n\t}\n\n\tif (prefetch_time == 1) {\n\t\tif (is_serve_expired) {\n\t\t\ttimeout = request->conf->dns_serve_expired_prefetch_time;\n\t\t\tif (timeout == 0) {\n\t\t\t\ttimeout = request->conf->dns_serve_expired_ttl / 2;\n\t\t\t\tif (timeout == 0 || timeout > EXPIRED_DOMAIN_PREFETCH_TIME) {\n\t\t\t\t\ttimeout = EXPIRED_DOMAIN_PREFETCH_TIME;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ((request->prefetch_flags & PREFETCH_FLAGS_EXPIRED) == 0) {\n\t\t\t\ttimeout += ttl;\n\t\t\t} else if (cache_key != NULL) {\n\t\t\t\tstruct dns_cache *old_cache = dns_cache_lookup(cache_key);\n\t\t\t\tif (old_cache) {\n\t\t\t\t\ttime_t next_ttl = _dns_server_expired_cache_ttl(old_cache, request->conf->dns_serve_expired_ttl) -\n\t\t\t\t\t\t\t\t\t  old_cache->info.ttl + ttl;\n\t\t\t\t\tif (next_ttl < timeout) {\n\t\t\t\t\t\ttimeout = next_ttl;\n\t\t\t\t\t}\n\t\t\t\t\tdns_cache_release(old_cache);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\ttimeout = ttl - 3;\n\t\t}\n\t} else {\n\t\ttimeout = ttl;\n\t\tif (is_serve_expired) {\n\t\t\ttimeout += request->conf->dns_serve_expired_ttl;\n\t\t}\n\n\t\ttimeout += 3;\n\t}\n\n\tif (timeout <= 0) {\n\t\ttimeout = 1;\n\t}\n\n\treturn timeout;\n}\n\nint _dns_server_request_update_cache(struct dns_request *request, int speed, dns_type_t qtype,\n\t\t\t\t\t\t\t\t\t struct dns_cache_data *cache_data, int cache_ttl)\n{\n\tint ttl = 0;\n\tint ret = -1;\n\n\tif (qtype != DNS_T_A && qtype != DNS_T_AAAA && qtype != DNS_T_HTTPS) {\n\t\tgoto errout;\n\t}\n\n\tif (cache_ttl > 0) {\n\t\tttl = cache_ttl;\n\t} else {\n\t\tttl = _dns_server_get_conf_ttl(request, request->ip_ttl);\n\t}\n\n\ttlog(TLOG_DEBUG, \"cache %s qtype: %d ttl: %d\\n\", request->domain, qtype, ttl);\n\n\t/* if doing prefetch, update cache only */\n\tstruct dns_cache_key cache_key;\n\tcache_key.dns_group_name = request->dns_group_name;\n\tcache_key.domain = request->domain;\n\tcache_key.qtype = request->qtype;\n\tcache_key.query_flag = request->server_flags;\n\n\tif (request->prefetch) {\n\t\t/* no prefetch for mdns request */\n\t\tif (request->is_mdns_lookup) {\n\t\t\tret = 0;\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (dns_cache_replace(&cache_key, request->rcode, ttl, speed,\n\t\t\t\t\t\t\t  _dns_server_get_cache_timeout(request, &cache_key, ttl),\n\t\t\t\t\t\t\t  !(request->prefetch_flags & PREFETCH_FLAGS_EXPIRED), cache_data) != 0) {\n\t\t\tret = 0;\n\t\t\tgoto errout;\n\t\t}\n\t} else {\n\t\t/* insert result to cache */\n\t\tif (dns_cache_insert(&cache_key, request->rcode, ttl, speed, _dns_server_get_cache_timeout(request, NULL, ttl),\n\t\t\t\t\t\t\t cache_data) != 0) {\n\t\t\tret = -1;\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\tif (cache_data) {\n\t\tdns_cache_data_put(cache_data);\n\t}\n\treturn ret;\n}\n\nint _dns_cache_cname_packet(struct dns_server_post_context *context)\n{\n\tstruct dns_packet *packet = context->packet;\n\tstruct dns_packet *cname_packet = NULL;\n\tint ret = -1;\n\tint i = 0;\n\tint j = 0;\n\tint rr_count = 0;\n\tint ttl = 0;\n\tint speed = 0;\n\tunsigned char packet_buff[DNS_PACKSIZE];\n\tunsigned char inpacket_buff[DNS_IN_PACKSIZE];\n\tint inpacket_len = 0;\n\n\tstruct dns_cache_data *cache_packet = NULL;\n\tstruct dns_rrs *rrs = NULL;\n\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\tcname_packet = (struct dns_packet *)packet_buff;\n\tint has_result = 0;\n\n\tstruct dns_request *request = context->request;\n\n\tif (request->has_cname == 0 || request->no_cache_cname == 1 || request->no_cache == 1) {\n\t\treturn 0;\n\t}\n\n\t/* init a new DNS packet */\n\tret = dns_packet_init(cname_packet, DNS_PACKSIZE, &packet->head);\n\tif (ret != 0) {\n\t\treturn -1;\n\t}\n\n\t/* add request domain */\n\tret = dns_add_domain(cname_packet, request->cname, context->qtype, DNS_C_IN);\n\tif (ret != 0) {\n\t\treturn -1;\n\t}\n\n\tfor (j = 1; j < DNS_RRS_OPT && context->packet; j++) {\n\t\trrs = dns_get_rrs_start(context->packet, j, &rr_count);\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(context->packet, rrs)) {\n\t\t\tswitch (rrs->type) {\n\t\t\tcase DNS_T_A: {\n\t\t\t\tunsigned char ipv4_addr[4];\n\t\t\t\tif (dns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv4_addr) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (strncasecmp(request->cname, name, DNS_MAX_CNAME_LEN - 1) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tret = dns_add_A(cname_packet, DNS_RRS_AN, request->cname, ttl, ipv4_addr);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\thas_result = 1;\n\t\t\t} break;\n\t\t\tcase DNS_T_AAAA: {\n\t\t\t\tunsigned char ipv6_addr[16];\n\t\t\t\tif (dns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, ipv6_addr) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (strncasecmp(request->cname, name, DNS_MAX_CNAME_LEN - 1) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tret = dns_add_AAAA(cname_packet, DNS_RRS_AN, request->cname, ttl, ipv6_addr);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\thas_result = 1;\n\t\t\t} break;\n\t\t\tcase DNS_T_SOA: {\n\t\t\t\tstruct dns_soa soa;\n\t\t\t\tif (dns_get_SOA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, &soa) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tret = dns_add_SOA(cname_packet, DNS_RRS_AN, request->cname, ttl, &soa);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\thas_result = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (has_result == 0) {\n\t\treturn 0;\n\t}\n\n\tinpacket_len = dns_encode(inpacket_buff, DNS_IN_PACKSIZE, cname_packet);\n\tif (inpacket_len <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (context->qtype != DNS_T_A && context->qtype != DNS_T_AAAA) {\n\t\treturn -1;\n\t}\n\n\tcache_packet = dns_cache_new_data_packet(inpacket_buff, inpacket_len);\n\tif (cache_packet == NULL) {\n\t\tgoto errout;\n\t}\n\n\tttl = _dns_server_get_conf_ttl(request, request->ip_ttl);\n\tspeed = request->ping_time;\n\n\ttlog(TLOG_DEBUG, \"Cache CNAME: %s, qtype: %d, speed: %d\", request->cname, request->qtype, speed);\n\n\t/* if doing prefetch, update cache only */\n\tstruct dns_cache_key cache_key;\n\tcache_key.dns_group_name = request->dns_group_name;\n\tcache_key.domain = request->cname;\n\tcache_key.qtype = context->qtype;\n\tcache_key.query_flag = request->server_flags;\n\n\tif (request->prefetch) {\n\t\tif (dns_cache_replace(&cache_key, request->rcode, ttl, speed,\n\t\t\t\t\t\t\t  _dns_server_get_cache_timeout(request, &cache_key, ttl),\n\t\t\t\t\t\t\t  !(request->prefetch_flags & PREFETCH_FLAGS_EXPIRED), cache_packet) != 0) {\n\t\t\tret = 0;\n\t\t\tgoto errout;\n\t\t}\n\t} else {\n\t\t/* insert result to cache */\n\t\tif (dns_cache_insert(&cache_key, request->rcode, ttl, speed, _dns_server_get_cache_timeout(request, NULL, ttl),\n\t\t\t\t\t\t\t cache_packet) != 0) {\n\t\t\tret = -1;\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\tif (cache_packet) {\n\t\tdns_cache_data_put(cache_packet);\n\t}\n\n\treturn ret;\n}\n\nint _dns_cache_packet(struct dns_server_post_context *context)\n{\n\tstruct dns_request *request = context->request;\n\tint ret = -1;\n\n\tstruct dns_cache_data *cache_packet = dns_cache_new_data_packet(context->inpacket, context->inpacket_len);\n\tif (cache_packet == NULL) {\n\t\tgoto errout;\n\t}\n\n\t/* if doing prefetch, update cache only */\n\tstruct dns_cache_key cache_key;\n\tcache_key.dns_group_name = request->dns_group_name;\n\tcache_key.domain = request->domain;\n\tcache_key.qtype = context->qtype;\n\tcache_key.query_flag = request->server_flags;\n\n\tif (request->prefetch) {\n\t\t/* no prefetch for mdns request */\n\t\tif (request->is_mdns_lookup) {\n\t\t\tret = 0;\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (dns_cache_replace(&cache_key, request->rcode, request->ip_ttl, -1,\n\t\t\t\t\t\t\t  _dns_server_get_cache_timeout(request, &cache_key, request->ip_ttl),\n\t\t\t\t\t\t\t  !(request->prefetch_flags & PREFETCH_FLAGS_EXPIRED), cache_packet) != 0) {\n\t\t\tret = 0;\n\t\t\tgoto errout;\n\t\t}\n\t} else {\n\t\t/* insert result to cache */\n\t\tif (dns_cache_insert(&cache_key, request->rcode, request->ip_ttl, -1,\n\t\t\t\t\t\t\t _dns_server_get_cache_timeout(request, NULL, request->ip_ttl), cache_packet) != 0) {\n\t\t\tret = -1;\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\tif (cache_packet) {\n\t\tdns_cache_data_put(cache_packet);\n\t}\n\n\treturn ret;\n}\n\nint _dns_cache_specify_packet(struct dns_server_post_context *context)\n{\n\tif (_dns_cache_is_specify_packet(context->qtype) != 0) {\n\t\treturn 0;\n\t}\n\n\treturn _dns_cache_packet(context);\n}\n\nint _dns_cache_try_keep_old_cache(struct dns_request *request)\n{\n\tstruct dns_cache_key cache_key;\n\tcache_key.dns_group_name = request->dns_group_name;\n\tcache_key.domain = request->domain;\n\tcache_key.qtype = request->qtype;\n\tcache_key.query_flag = request->server_flags;\n\treturn dns_cache_update_timer(&cache_key, DNS_SERVER_TMOUT_TTL);\n}\n\nstatic int _dns_server_process_cache_packet(struct dns_request *request, struct dns_cache *dns_cache)\n{\n\tint ret = -1;\n\tstruct dns_cache_packet *cache_packet = NULL;\n\tif (dns_cache->info.qtype != request->qtype) {\n\t\tgoto out;\n\t}\n\n\tcache_packet = (struct dns_cache_packet *)dns_cache_get_data(dns_cache);\n\tif (cache_packet == NULL) {\n\t\tgoto out;\n\t}\n\n\tint do_ipset = (dns_cache_get_ttl(dns_cache) == 0);\n\tif (dns_cache_is_visited(dns_cache) == 0) {\n\t\tdo_ipset = 1;\n\t}\n\n\tstruct dns_server_post_context context;\n\t_dns_server_post_context_init(&context, request);\n\n\tif (request->original_domain != NULL && cache_packet->head.size < DNS_IN_PACKSIZE) {\n\t\tcontext.inpacket = context.inpacket_buff;\n\t\tmemcpy(context.inpacket, cache_packet->data, cache_packet->head.size);\n\t} else {\n\t\tcontext.inpacket = cache_packet->data;\n\t}\n\tcontext.inpacket_len = cache_packet->head.size;\n\trequest->ping_time = dns_cache->info.speed;\n\n\tif (dns_decode(context.packet, context.packet_maxlen, cache_packet->data, cache_packet->head.size) != 0) {\n\t\ttlog(TLOG_ERROR, \"decode cache failed, %d, %d\", context.packet_maxlen, context.inpacket_len);\n\t\tgoto out;\n\t}\n\n\t/* Check if records in cache contain DNSSEC, if not exist, skip cache */\n\tif (request->passthrough == 1) {\n\t\tif ((dns_get_OPT_option(context.packet) & DNS_OPT_FLAG_DO) == 0 && request->edns0_do == 1) {\n\t\t\tgoto out;\n\t\t}\n\t}\n\n\trequest->is_cache_reply = 1;\n\trequest->rcode = context.packet->head.rcode;\n\tcontext.do_cache = 0;\n\tcontext.do_ipset = do_ipset;\n\tcontext.do_audit = 1;\n\tcontext.do_reply = 1;\n\tcontext.is_cache_reply = 1;\n\tcontext.reply_ttl = _dns_server_get_expired_ttl_reply(request, dns_cache);\n\tret = _dns_server_reply_passthrough(&context);\nout:\n\tif (cache_packet) {\n\t\tdns_cache_data_put((struct dns_cache_data *)cache_packet);\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_server_process_cache_data(struct dns_request *request, struct dns_cache *dns_cache)\n{\n\tint ret = -1;\n\n\trequest->ping_time = dns_cache->info.speed;\n\tret = _dns_server_process_cache_packet(request, dns_cache);\n\tif (ret != 0) {\n\t\tgoto out;\n\t}\n\n\treturn 0;\nout:\n\treturn -1;\n}\n\nint _dns_server_process_cache(struct dns_request *request)\n{\n\tstruct dns_cache *dns_cache = NULL;\n\tstruct dns_cache *dualstack_dns_cache = NULL;\n\tint ret = -1;\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_CACHE) == 0) {\n\t\tgoto out;\n\t}\n\n\tstruct dns_cache_key cache_key;\n\tcache_key.dns_group_name = request->dns_group_name;\n\tcache_key.domain = request->domain;\n\tcache_key.qtype = request->qtype;\n\tcache_key.query_flag = request->server_flags;\n\n\tdns_cache = dns_cache_lookup(&cache_key);\n\tif (dns_cache == NULL) {\n\t\tgoto out;\n\t}\n\n\tif (request->qtype != dns_cache->info.qtype) {\n\t\tgoto out;\n\t}\n\n\tif (request->qtype == DNS_T_A && request->conf->dns_dualstack_ip_allow_force_AAAA == 0) {\n\t\tgoto reply_cache;\n\t}\n\n\tif (request->qtype != DNS_T_A && request->qtype != DNS_T_AAAA) {\n\t\tgoto reply_cache;\n\t}\n\n\tif (request->dualstack_selection) {\n\t\tint dualstack_qtype = 0;\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tdualstack_qtype = DNS_T_AAAA;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tdualstack_qtype = DNS_T_A;\n\t\t} else {\n\t\t\tgoto reply_cache;\n\t\t}\n\n\t\tif (_dns_server_is_dns64_request(request) == 1) {\n\t\t\tgoto reply_cache;\n\t\t}\n\n\t\tcache_key.qtype = dualstack_qtype;\n\t\tdualstack_dns_cache = dns_cache_lookup(&cache_key);\n\t\tif (dualstack_dns_cache == NULL && request->cname[0] != '\\0') {\n\t\t\tcache_key.domain = request->cname;\n\t\t\tdualstack_dns_cache = dns_cache_lookup(&cache_key);\n\t\t}\n\n\t\tif (dualstack_dns_cache && (dualstack_dns_cache->info.speed > 0)) {\n\t\t\tif ((dualstack_dns_cache->info.speed + (request->conf->dns_dualstack_ip_selection_threshold * 10)) <\n\t\t\t\t\tdns_cache->info.speed ||\n\t\t\t\tdns_cache->info.speed < 0) {\n\t\t\t\ttlog(TLOG_DEBUG, \"cache result: %s, qtype: %d, force %s preferred, id: %d, time1: %d, time2: %d\",\n\t\t\t\t\t request->domain, request->qtype, request->qtype == DNS_T_AAAA ? \"IPv4\" : \"IPv6\", request->id,\n\t\t\t\t\t dns_cache->info.speed, dualstack_dns_cache->info.speed);\n\t\t\t\trequest->ip_ttl = _dns_server_get_expired_ttl_reply(request, dualstack_dns_cache);\n\t\t\t\tret = _dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t\t\t\tgoto out_update_cache;\n\t\t\t}\n\t\t}\n\t}\n\nreply_cache:\n\tif (dns_cache_get_ttl(dns_cache) <= 0 && request->no_serve_expired == 1) {\n\t\tgoto out;\n\t}\n\n\tret = _dns_server_process_cache_data(request, dns_cache);\n\tif (ret != 0) {\n\t\tgoto out;\n\t}\n\nout_update_cache:\n\tif (dns_cache_get_ttl(dns_cache) == 0) {\n\t\tstruct dns_server_query_option dns_query_options;\n\t\tint prefetch_flags = 0;\n\t\tdns_query_options.server_flags = request->server_flags;\n\t\tdns_query_options.dns_group_name = request->dns_group_name;\n\t\tif (request->conn == NULL) {\n\t\t\tdns_query_options.server_flags = dns_cache_get_query_flag(dns_cache);\n\t\t\tdns_query_options.dns_group_name = dns_cache_get_dns_group_name(dns_cache);\n\t\t}\n\n\t\tdns_query_options.ecs_enable_flag = 0;\n\t\tif (request->has_ecs) {\n\t\t\tdns_query_options.ecs_enable_flag |= DNS_QUEY_OPTION_ECS_DNS;\n\t\t\tmemcpy(&dns_query_options.ecs_dns, &request->ecs, sizeof(dns_query_options.ecs_dns));\n\t\t}\n\n\t\tif (request->edns0_do) {\n\t\t\tdns_query_options.ecs_enable_flag |= DNS_QUEY_OPTION_EDNS0_DO;\n\t\t\tprefetch_flags |= PREFETCH_FLAGS_NOPREFETCH;\n\t\t}\n\n\t\t_dns_server_prefetch_request(request->domain, request->qtype, &dns_query_options, prefetch_flags);\n\t} else {\n\t\tdns_cache_update(dns_cache);\n\t}\n\nout:\n\tif (dns_cache) {\n\t\tdns_cache_release(dns_cache);\n\t}\n\n\tif (dualstack_dns_cache) {\n\t\tdns_cache_release(dualstack_dns_cache);\n\t\tdualstack_dns_cache = NULL;\n\t}\n\n\treturn ret;\n}\n\nvoid _dns_server_save_cache_to_file(void)\n{\n\ttime_t now;\n\tint check_time = dns_conf.cache_checkpoint_time;\n\n\tif (dns_conf.cache_persist == 0 || dns_conf.cachesize <= 0 || dns_conf.cache_checkpoint_time <= 0) {\n\t\treturn;\n\t}\n\n\ttime(&now);\n\tif (server.cache_save_pid > 0) {\n\t\tint ret = waitpid(server.cache_save_pid, NULL, WNOHANG);\n\t\tif (ret == server.cache_save_pid) {\n\t\t\tserver.cache_save_pid = 0;\n\t\t} else if (ret < 0) {\n\t\t\ttlog(TLOG_ERROR, \"waitpid failed, errno %d, error info '%s'\", errno, strerror(errno));\n\t\t\tserver.cache_save_pid = 0;\n\t\t} else {\n\t\t\tif (now - 30 > server.cache_save_time) {\n\t\t\t\tkill(server.cache_save_pid, SIGKILL);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (check_time < 120) {\n\t\tcheck_time = 120;\n\t}\n\n\tif (now - check_time < server.cache_save_time) {\n\t\treturn;\n\t}\n\n\t/* server is busy, skip*/\n\tpthread_mutex_lock(&server.request_list_lock);\n\tif (list_empty(&server.request_list) != 0) {\n\t\tpthread_mutex_unlock(&server.request_list_lock);\n\t\treturn;\n\t}\n\tpthread_mutex_unlock(&server.request_list_lock);\n\n\tserver.cache_save_time = now;\n\n\tint pid = fork();\n\tif (pid == 0) {\n\t\t/* child process */\n\t\tfor (int i = 3; i < 1024; i++) {\n\t\t\tclose(i);\n\t\t}\n\n\t\ttlog_setlevel(TLOG_OFF);\n\t\t_dns_server_cache_save(1);\n\t\t_exit(0);\n\t} else if (pid < 0) {\n\t\ttlog(TLOG_DEBUG, \"fork failed, errno %d, error info '%s'\", errno, strerror(errno));\n\t\treturn;\n\t}\n\n\tserver.cache_save_pid = pid;\n}\n\nstatic dns_cache_tmout_action_t _dns_server_cache_expired(struct dns_cache *dns_cache)\n{\n\tif (dns_cache->info.rcode != DNS_RC_NOERROR) {\n\t\treturn DNS_CACHE_TMOUT_ACTION_DEL;\n\t}\n\n\tstruct dns_conf_group *conf_group = dns_server_get_rule_group(dns_cache->info.dns_group_name);\n\n\tif (conf_group->dns_prefetch == 1) {\n\t\tif (conf_group->dns_serve_expired == 1) {\n\t\t\treturn _dns_server_prefetch_expired_domain(conf_group, dns_cache);\n\t\t} else {\n\t\t\treturn _dns_server_prefetch_domain(conf_group, dns_cache);\n\t\t}\n\t}\n\n\treturn DNS_CACHE_TMOUT_ACTION_DEL;\n}\n\nint _dns_server_cache_init(void)\n{\n\tif (dns_cache_init(dns_conf.cachesize, dns_conf.cache_max_memsize, _dns_server_cache_expired) != 0) {\n\t\ttlog(TLOG_ERROR, \"init cache failed.\");\n\t\treturn -1;\n\t}\n\n\tconst char *dns_cache_file = dns_conf_get_cache_dir();\n\tif (dns_conf.cache_persist == 2) {\n\t\tuint64_t freespace = get_free_space(dns_cache_file);\n\t\tif (freespace >= CACHE_AUTO_ENABLE_SIZE) {\n\t\t\ttlog(TLOG_INFO, \"auto enable cache persist.\");\n\t\t\tdns_conf.cache_persist = 1;\n\t\t}\n\t}\n\n\tif (dns_conf.cachesize <= 0 || dns_conf.cache_persist == 0) {\n\t\treturn 0;\n\t}\n\n\tif (dns_cache_load(dns_cache_file) != 0) {\n\t\ttlog(TLOG_WARN, \"Load cache failed.\");\n\t\treturn 0;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_cache_save(int check_lock)\n{\n\tconst char *dns_cache_file = dns_conf_get_cache_dir();\n\n\tif (dns_conf.cache_persist == 0 || dns_conf.cachesize <= 0) {\n\t\tif (access(dns_cache_file, F_OK) == 0) {\n\t\t\tunlink(dns_cache_file);\n\t\t}\n\t\treturn 0;\n\t}\n\n\tif (dns_cache_save(dns_cache_file, check_lock) != 0) {\n\t\ttlog(TLOG_WARN, \"save cache failed.\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/cache.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_CACHE_\n#define _DNS_SERVER_CACHE_\n\n#include \"dns_server.h\"\n#include \"smartdns/dns_cache.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_cache_save(int check_lock);\n\nvoid _dns_server_save_cache_to_file(void);\n\nint _dns_server_cache_init(void);\n\nint _dns_server_process_cache(struct dns_request *request);\n\nint _dns_cache_cname_packet(struct dns_server_post_context *context);\n\nint _dns_server_request_update_cache(struct dns_request *request, int speed, dns_type_t qtype,\n\t\t\t\t\t\t\t\t\t struct dns_cache_data *cache_data, int cache_ttl);\n\nint _dns_cache_packet(struct dns_server_post_context *context);\n\nint _dns_cache_try_keep_old_cache(struct dns_request *request);\n\nint _dns_cache_specify_packet(struct dns_server_post_context *context);\n\nint _dns_server_expired_cache_ttl(struct dns_cache *cache, int serve_expired_ttl);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/client_rule.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client_rule.h\"\n#include \"request.h\"\n\nint _dns_server_request_set_client_rules(struct dns_request *request, struct dns_client_rules *client_rule)\n{\n\tif (client_rule == NULL) {\n\t\tif (_dns_server_has_bind_flag(request, BIND_FLAG_ACL) == 0 || dns_conf.acl_enable) {\n\t\t\trequest->send_tick = get_tick_count();\n\t\t\trequest->rcode = DNS_RC_REFUSED;\n\t\t\trequest->no_cache = 1;\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t}\n\n\ttlog(TLOG_DEBUG, \"match client rule.\");\n\n\tif (client_rule->rules[CLIENT_RULE_GROUP]) {\n\t\tstruct client_rule_group *group = (struct client_rule_group *)client_rule->rules[CLIENT_RULE_GROUP];\n\t\tif (group && group->group_name[0] != '\\0') {\n\t\t\tsafe_strncpy(request->dns_group_name, group->group_name, sizeof(request->dns_group_name));\n\t\t}\n\t}\n\n\tif (client_rule->rules[CLIENT_RULE_FLAGS]) {\n\t\tstruct client_rule_flags *flags = (struct client_rule_flags *)client_rule->rules[CLIENT_RULE_FLAGS];\n\t\tif (flags) {\n\t\t\trequest->server_flags = flags->flags;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/client_rule.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_CLIENT_RULE_\n#define _DNS_SERVER_CLIENT_RULE_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_request_set_client_rules(struct dns_request *request, struct dns_client_rules *client_rule);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/cname.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"cname.h\"\n#include \"request.h\"\n#include \"rules.h\"\n\nstatic DNS_CHILD_POST_RESULT _dns_server_process_cname_callback(struct dns_request *request,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstruct dns_request *child_request, int is_first_resp)\n{\n\t_dns_server_request_copy(request, child_request);\n\tif (child_request->rcode == DNS_RC_NOERROR && request->conf->dns_force_no_cname == 0 &&\n\t\tchild_request->has_soa == 0) {\n\t\tsafe_strncpy(request->cname, child_request->domain, sizeof(request->cname));\n\t\trequest->has_cname = 1;\n\t\trequest->ttl_cname = _dns_server_get_conf_ttl(request, child_request->ip_ttl);\n\t}\n\n\treturn DNS_CHILD_POST_SUCCESS;\n}\n\nint _dns_server_process_cname_pre(struct dns_request *request)\n{\n\tstruct dns_cname_rule *cname = NULL;\n\tstruct dns_request_domain_rule domain_rule;\n\tuint32_t flags = _dns_server_get_rule_flags(request);\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_CNAME) == 0) {\n\t\treturn 0;\n\t}\n\n\tif (request->has_cname_loop == 1) {\n\t\treturn 0;\n\t}\n\n\t/* get domain rule flag */\n\tif (flags & DOMAIN_FLAG_CNAME_IGN) {\n\t\treturn 0;\n\t}\n\n\tcname = _dns_server_get_dns_rule(request, DOMAIN_RULE_CNAME);\n\tif (cname == NULL) {\n\t\treturn 0;\n\t}\n\n\trequest->skip_domain_rule = 0;\n\t/* copy child rules */\n\tmemcpy(&domain_rule, &request->domain_rule, sizeof(domain_rule));\n\tmemset(&request->domain_rule, 0, sizeof(request->domain_rule));\n\t_dns_server_get_domain_rule_by_domain(request, cname->cname, 0);\n\trequest->domain_rule.rules[DOMAIN_RULE_CNAME] = domain_rule.rules[DOMAIN_RULE_CNAME];\n\trequest->domain_rule.is_sub_rule[DOMAIN_RULE_CNAME] = domain_rule.is_sub_rule[DOMAIN_RULE_CNAME];\n\n\trequest->no_select_possible_ip = 1;\n\trequest->no_cache_cname = 1;\n\tsafe_strncpy(request->cname, cname->cname, sizeof(request->cname));\n\n\treturn 0;\n}\n\nint _dns_server_process_cname(struct dns_request *request)\n{\n\tstruct dns_cname_rule *cname = NULL;\n\tconst char *child_group_name = NULL;\n\tint ret = 0;\n\tstruct dns_rule_flags *rule_flag = NULL;\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_CNAME) == 0) {\n\t\treturn 0;\n\t}\n\n\tif (request->has_cname_loop == 1) {\n\t\treturn 0;\n\t}\n\n\t/* get domain rule flag */\n\trule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS);\n\tif (rule_flag != NULL) {\n\t\tif (rule_flag->flags & DOMAIN_FLAG_CNAME_IGN) {\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tcname = _dns_server_get_dns_rule(request, DOMAIN_RULE_CNAME);\n\tif (cname == NULL) {\n\t\treturn 0;\n\t}\n\n\ttlog(TLOG_INFO, \"query %s with cname %s\", request->domain, cname->cname);\n\n\tstruct dns_request *child_request =\n\t\t_dns_server_new_child_request(request, cname->cname, request->qtype, _dns_server_process_cname_callback);\n\tif (child_request == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\treturn -1;\n\t}\n\n\t/* check cname rule loop */\n\tstruct dns_request *check_request = child_request->parent_request;\n\tstruct dns_cname_rule *child_cname = _dns_server_get_dns_rule(child_request, DOMAIN_RULE_CNAME);\n\n\t/* sub domain rule*/\n\tif (child_cname != NULL && strncasecmp(child_request->domain, child_cname->cname, DNS_MAX_CNAME_LEN) == 0) {\n\t\tchild_request->domain_rule.rules[DOMAIN_RULE_CNAME] = NULL;\n\t\tchild_request->has_cname_loop = 1;\n\t}\n\n\t/* loop rule */\n\twhile (check_request != NULL && child_cname != NULL) {\n\t\tstruct dns_cname_rule *check_cname = _dns_server_get_dns_rule(check_request, DOMAIN_RULE_CNAME);\n\t\tif (check_cname == NULL) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (strstr(child_request->domain, check_request->domain) != NULL &&\n\t\t\tcheck_request != child_request->parent_request) {\n\t\t\tchild_request->domain_rule.rules[DOMAIN_RULE_CNAME] = NULL;\n\t\t\tchild_request->has_cname_loop = 1;\n\t\t\tbreak;\n\t\t}\n\n\t\tcheck_request = check_request->parent_request;\n\t}\n\n\t/* query cname domain  */\n\tif (child_request->has_cname_loop == 1 && strncasecmp(request->domain, cname->cname, DNS_MAX_CNAME_LEN) == 0) {\n\t\trequest->has_cname_loop = 0;\n\t\trequest->domain_rule.rules[DOMAIN_RULE_CNAME] = NULL;\n\t\ttlog(TLOG_DEBUG, \"query cname domain %s\", request->domain);\n\t\tgoto out;\n\t}\n\n\tchild_group_name = _dns_server_get_request_server_groupname(child_request);\n\tif (child_group_name) {\n\t\t/* reset dns group and setup child request domain group again when do query.*/\n\t\tchild_request->dns_group_name[0] = '\\0';\n\t}\n\n\trequest->request_wait++;\n\tret = _dns_server_do_query(child_request, 0);\n\tif (ret != 0) {\n\t\trequest->request_wait--;\n\t\ttlog(TLOG_ERROR, \"do query %s type %d failed.\\n\", request->domain, request->qtype);\n\t\tgoto errout;\n\t}\n\n\t_dns_server_request_release_complete(child_request, 0);\n\treturn 1;\n\nerrout:\n\tif (child_request) {\n\t\trequest->child_request = NULL;\n\t\t_dns_server_request_release(child_request);\n\t}\n\n\treturn -1;\n\nout:\n\tif (child_request) {\n\t\tchild_request->parent_request = NULL;\n\t\trequest->child_request = NULL;\n\t\t_dns_server_request_release(child_request);\n\t\t_dns_server_request_release(request);\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/cname.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_CNAME_\n#define _DNS_SERVER_CNAME_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_process_cname_pre(struct dns_request *request);\n\nint _dns_server_process_cname(struct dns_request *request);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/connection.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"connection.h\"\n#include \"dns_server.h\"\n#include \"server_http2.h\"\n\n#include \"smartdns/http2.h\"\n\n#include <openssl/ssl.h>\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n\nint _dns_server_epoll_ctl(struct dns_server_conn_head *head, int op, uint32_t events)\n{\n\tstruct epoll_event event;\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = events;\n\tevent.data.ptr = head;\n\n\tif (epoll_ctl(server.epoll_fd, op, head->fd, &event) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_server_conn_release(struct dns_server_conn_head *conn)\n{\n\tif (conn == NULL) {\n\t\treturn;\n\t}\n\n\tint refcnt = atomic_dec_return(&conn->refcnt);\n\n\tif (refcnt) {\n\t\tif (refcnt < 0) {\n\t\t\tBUG(\"BUG: refcnt is %d, type = %d\", refcnt, conn->type);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (conn->type == DNS_CONN_TYPE_TLS_CLIENT || conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)conn;\n\t\tif (tls_client->ssl != NULL) {\n\t\t\tSSL_free(tls_client->ssl);\n\t\t\ttls_client->ssl = NULL;\n\t\t}\n\n\t\tif (tls_client->http2_ctx != NULL) {\n\t\t\thttp2_ctx_put(tls_client->http2_ctx);\n\t\t\ttls_client->http2_ctx = NULL;\n\t\t}\n\t\tpthread_mutex_destroy(&tls_client->ssl_lock);\n\t} else if (conn->type == DNS_CONN_TYPE_TLS_SERVER || conn->type == DNS_CONN_TYPE_HTTPS_SERVER) {\n\t\tstruct dns_server_conn_tls_server *tls_server = (struct dns_server_conn_tls_server *)conn;\n\t\tif (tls_server->ssl_ctx != NULL) {\n\t\t\tSSL_CTX_free(tls_server->ssl_ctx);\n\t\t\ttls_server->ssl_ctx = NULL;\n\t\t}\n\t} else if (conn->type == DNS_CONN_TYPE_HTTP2_STREAM) {\n\t\tstruct dns_server_conn_http2_stream *http2_stream = (struct dns_server_conn_http2_stream *)conn;\n\t\tif (http2_stream->stream != NULL) {\n\t\t\thttp2_stream_close(http2_stream->stream);\n\t\t\thttp2_stream->stream = NULL;\n\t\t}\n\t}\n\n\tif (conn->fd > 0) {\n\t\tclose(conn->fd);\n\t\tconn->fd = -1;\n\t}\n\n\tpthread_mutex_lock(&server.conn_list_lock);\n\tlist_del_init(&conn->list);\n\tpthread_mutex_unlock(&server.conn_list_lock);\n\tfree(conn);\n}\n\nvoid _dns_server_conn_get(struct dns_server_conn_head *conn)\n{\n\tif (conn == NULL) {\n\t\treturn;\n\t}\n\n\tif (atomic_inc_return(&conn->refcnt) <= 0) {\n\t\tBUG(\"BUG: client ref is invalid.\");\n\t}\n}\n\nvoid _dns_server_close_socket(void)\n{\n\tstruct dns_server_conn_head *conn = NULL;\n\tstruct dns_server_conn_head *tmp = NULL;\n\n\tpthread_mutex_lock(&server.conn_list_lock);\n\tlist_for_each_entry_safe(conn, tmp, &server.conn_list, list)\n\t{\n\t\t/* Force cleanup of TLS/HTTPS client connections to prevent memory leaks */\n\t\tif (conn->type == DNS_CONN_TYPE_TLS_CLIENT || conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\t\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)conn;\n\n\t\t\t/* Free SSL connection */\n\t\t\tif (tls_client->ssl != NULL) {\n\t\t\t\tSSL_free(tls_client->ssl);\n\t\t\t\ttls_client->ssl = NULL;\n\t\t\t}\n\t\t}\n\n\t\t_dns_server_client_close(conn);\n\t}\n\tpthread_mutex_unlock(&server.conn_list_lock);\n}\n\nvoid _dns_server_close_socket_server(void)\n{\n\tstruct dns_server_conn_head *conn = NULL;\n\tstruct dns_server_conn_head *tmp = NULL;\n\n\tlist_for_each_entry_safe(conn, tmp, &server.conn_list, list)\n\t{\n\t\tswitch (conn->type) {\n\t\tcase DNS_CONN_TYPE_HTTPS_SERVER:\n\t\tcase DNS_CONN_TYPE_TLS_SERVER: {\n\t\t\tstruct dns_server_conn_tls_server *tls_server = (struct dns_server_conn_tls_server *)conn;\n\t\t\tif (tls_server->ssl_ctx) {\n\t\t\t\tSSL_CTX_free(tls_server->ssl_ctx);\n\t\t\t\ttls_server->ssl_ctx = NULL;\n\t\t\t}\n\t\t\t_dns_server_client_close(conn);\n\t\t\tbreak;\n\t\t}\n\t\tcase DNS_CONN_TYPE_UDP_SERVER:\n\t\tcase DNS_CONN_TYPE_TCP_SERVER:\n\t\t\t_dns_server_client_close(conn);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid _dns_server_client_touch(struct dns_server_conn_head *conn)\n{\n\ttime(&conn->last_request_time);\n}\n\nint _dns_server_client_close(struct dns_server_conn_head *conn)\n{\n\tif (conn->fd > 0) {\n\t\t_dns_server_epoll_ctl(conn, EPOLL_CTL_DEL, 0);\n\t}\n\n\tpthread_mutex_lock(&server.conn_list_lock);\n\tlist_del_init(&conn->list);\n\tpthread_mutex_unlock(&server.conn_list_lock);\n\n\tif (conn->type == DNS_CONN_TYPE_TLS_CLIENT || conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)conn;\n\t\tif (tls_client->http2_ctx != NULL) {\n\t\t\thttp2_ctx_close(tls_client->http2_ctx);\n\t\t\ttls_client->http2_ctx = NULL;\n\t\t}\n\t}\n\n\t_dns_server_conn_release(conn);\n\n\treturn 0;\n}\n\nint _dns_server_update_request_connection_timeout(struct dns_server_conn_head *conn, int timeout)\n{\n\tif (conn == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (timeout == 0) {\n\t\treturn 0;\n\t}\n\n\tswitch (conn->type) {\n\tcase DNS_CONN_TYPE_TCP_CLIENT: {\n\t\tstruct dns_server_conn_tcp_client *tcpclient = (struct dns_server_conn_tcp_client *)conn;\n\t\ttcpclient->conn_idle_timeout = timeout;\n\t} break;\n\tcase DNS_CONN_TYPE_TLS_CLIENT:\n\tcase DNS_CONN_TYPE_HTTPS_CLIENT: {\n\t\tstruct dns_server_conn_tls_client *tlsclient = (struct dns_server_conn_tls_client *)conn;\n\t\ttlsclient->tcp.conn_idle_timeout = timeout;\n\t} break;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_server_conn_head_init(struct dns_server_conn_head *conn, int fd, int type)\n{\n\tmemset(conn, 0, sizeof(*conn));\n\tconn->fd = fd;\n\tconn->type = type;\n\tatomic_set(&conn->refcnt, 0);\n\tINIT_LIST_HEAD(&conn->list);\n}\n\nint _dns_server_set_flags(struct dns_server_conn_head *head, struct dns_bind_ip *bind_ip)\n{\n\ttime(&head->last_request_time);\n\thead->server_flags = bind_ip->flags;\n\thead->dns_group = bind_ip->group;\n\thead->ipset_nftset_rule = &bind_ip->nftset_ipset_rule;\n\tatomic_set(&head->refcnt, 0);\n\tlist_add(&head->list, &server.conn_list);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/connection.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_CONNECTION_\n#define _DNS_SERVER_CONNECTION_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_server_close_socket_server(void);\n\nint _dns_server_client_close(struct dns_server_conn_head *conn);\n\nvoid _dns_server_client_touch(struct dns_server_conn_head *conn);\n\nint _dns_server_set_flags(struct dns_server_conn_head *head, struct dns_bind_ip *bind_ip);\n\nvoid _dns_server_conn_head_init(struct dns_server_conn_head *conn, int fd, int type);\n\nvoid _dns_server_conn_get(struct dns_server_conn_head *conn);\n\nvoid _dns_server_conn_release(struct dns_server_conn_head *conn);\n\nint _dns_server_epoll_ctl(struct dns_server_conn_head *head, int op, uint32_t events);\n\nvoid _dns_server_close_socket(void);\n\nint _dns_server_update_request_connection_timeout(struct dns_server_conn_head *conn, int timeout);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/context.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"context.h\"\n#include \"smartdns/dns_conf.h\"\n#include \"address.h\"\n#include \"audit.h\"\n#include \"cache.h\"\n#include \"dns_server.h\"\n#include \"ip_rule.h\"\n#include \"ipset_nftset.h\"\n#include \"request.h\"\n#include \"request_pending.h\"\n#include \"rules.h\"\n#include \"soa.h\"\n\nvoid _dns_server_post_context_init(struct dns_server_post_context *context, struct dns_request *request)\n{\n\tmemset(context, 0, sizeof(*context));\n\tcontext->packet = (struct dns_packet *)(context->packet_buff);\n\tcontext->packet_maxlen = sizeof(context->packet_buff);\n\tcontext->inpacket = (unsigned char *)(context->inpacket_buff);\n\tcontext->inpacket_maxlen = sizeof(context->inpacket_buff);\n\tcontext->qtype = request->qtype;\n\tcontext->request = request;\n}\n\nstatic void _dns_server_context_add_ip(struct dns_server_post_context *context, const unsigned char *ip_addr)\n{\n\tif (context->ip_num < MAX_IP_NUM) {\n\t\tcontext->ip_addr[context->ip_num] = ip_addr;\n\t}\n\n\tcontext->ip_num++;\n}\n\nvoid _dns_server_post_context_init_from(struct dns_server_post_context *context, struct dns_request *request,\n\t\t\t\t\t\t\t\t\t\tstruct dns_packet *packet, unsigned char *inpacket, int inpacket_len)\n{\n\tmemset(context, 0, sizeof(*context));\n\tcontext->packet = packet;\n\tcontext->packet_maxlen = sizeof(context->packet_buff);\n\tcontext->inpacket = inpacket;\n\tcontext->inpacket_len = inpacket_len;\n\tcontext->inpacket_maxlen = sizeof(context->inpacket);\n\tcontext->qtype = request->qtype;\n\tcontext->request = request;\n}\n\nstatic void _dns_rrs_result_log(struct dns_server_post_context *context, struct dns_ip_address *addr_map)\n{\n\tstruct dns_request *request = context->request;\n\n\tif (context->do_log_result == 0 || addr_map == NULL) {\n\t\treturn;\n\t}\n\n\tif (addr_map->addr_type == DNS_T_A) {\n\t\ttlog(TLOG_INFO, \"result: %s, id: %d, index: %d, rtt: %.1f ms, %d.%d.%d.%d\", request->domain, request->id,\n\t\t\t context->ip_num, ((float)addr_map->ping_time) / 10, addr_map->ip_addr[0], addr_map->ip_addr[1],\n\t\t\t addr_map->ip_addr[2], addr_map->ip_addr[3]);\n\t} else if (addr_map->addr_type == DNS_T_AAAA) {\n\t\ttlog(TLOG_INFO,\n\t\t\t \"result: %s, id: %d, index: %d, rtt: %.1f ms, \"\n\t\t\t \"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t\t request->domain, request->id, context->ip_num, ((float)addr_map->ping_time) / 10, addr_map->ip_addr[0],\n\t\t\t addr_map->ip_addr[1], addr_map->ip_addr[2], addr_map->ip_addr[3], addr_map->ip_addr[4],\n\t\t\t addr_map->ip_addr[5], addr_map->ip_addr[6], addr_map->ip_addr[7], addr_map->ip_addr[8],\n\t\t\t addr_map->ip_addr[9], addr_map->ip_addr[10], addr_map->ip_addr[11], addr_map->ip_addr[12],\n\t\t\t addr_map->ip_addr[13], addr_map->ip_addr[14], addr_map->ip_addr[15]);\n\t}\n}\n\nstatic int _dns_rrs_add_all_best_ip(struct dns_server_post_context *context)\n{\n\tstruct dns_ip_address *addr_map = NULL;\n\tstruct dns_ip_address *added_ip_addr = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tstruct dns_request *request = context->request;\n\tunsigned long bucket = 0;\n\n\tchar *domain = NULL;\n\tint ret = 0;\n\tint ignore_speed = 0;\n\tint maxhit = 0;\n\n\tif (context->select_all_best_ip == 0 || context->ip_num >= request->conf->dns_max_reply_ip_num) {\n\t\treturn 0;\n\t}\n\n\tdomain = request->domain;\n\t/* add CNAME record */\n\tif (request->has_cname) {\n\t\tdomain = request->cname;\n\t}\n\n\t/* add fasted ip address at first place of dns RR */\n\tif (request->has_ip) {\n\t\tadded_ip_addr = _dns_ip_address_get(request, request->ip_addr, request->qtype);\n\t\t_dns_rrs_result_log(context, added_ip_addr);\n\t}\n\n\tif (request->passthrough == 2) {\n\t\tignore_speed = 1;\n\t}\n\n\twhile (true) {\n\t\tpthread_mutex_lock(&request->ip_map_lock);\n\t\thash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)\n\t\t{\n\t\t\tif (context->ip_num >= request->conf->dns_max_reply_ip_num) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (context->qtype != addr_map->addr_type) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (addr_map == added_ip_addr) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (addr_map->hitnum > maxhit) {\n\t\t\t\tmaxhit = addr_map->hitnum;\n\t\t\t}\n\n\t\t\tif (addr_map->ping_time < 0 && ignore_speed == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (addr_map->hitnum < maxhit && ignore_speed == 1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* if ping time is larger than 5ms, check again. */\n\t\t\tif (addr_map->ping_time - request->ping_time >= 50) {\n\t\t\t\tint ttl_range = request->ping_time + request->ping_time / 10 + 5;\n\t\t\t\tif ((ttl_range < addr_map->ping_time) && addr_map->ping_time >= 100 && ignore_speed == 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_dns_server_context_add_ip(context, addr_map->ip_addr);\n\t\t\tif (addr_map->addr_type == DNS_T_A) {\n\t\t\t\tret |= dns_add_A(context->packet, DNS_RRS_AN, domain, request->ip_ttl, addr_map->ip_addr);\n\t\t\t} else if (addr_map->addr_type == DNS_T_AAAA) {\n\t\t\t\tret |= dns_add_AAAA(context->packet, DNS_RRS_AN, domain, request->ip_ttl, addr_map->ip_addr);\n\t\t\t}\n\t\t\t_dns_rrs_result_log(context, addr_map);\n\t\t}\n\t\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\t\tif (context->ip_num <= 0 && ignore_speed == 0) {\n\t\t\tignore_speed = 1;\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_server_add_srv(struct dns_server_post_context *context)\n{\n\tstruct dns_request *request = context->request;\n\tstruct dns_request_srv *srv = NULL;\n\tint ret = 0;\n\n\tlist_for_each_entry(srv, &request->srv_list, list) {\n\t\tret = dns_add_SRV(context->packet, DNS_RRS_AN, request->domain, request->ip_ttl, srv->priority, srv->weight,\n\t\t\t\t\t\t  srv->port, srv->host);\n\t\tif (ret != 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_add_rrs_ip_hint(struct dns_server_post_context *context, struct dns_rr_nested *param, dns_type_t qtype)\n{\n\ttypedef int (*addfunc)(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);\n\tstruct dns_request *request = context->request;\n\tstruct dns_ip_address *addr_map = NULL;\n\tunsigned long bucket = 0;\n\tstruct hlist_node *tmp = NULL;\n\tint ret = 0;\n\tint all_ips = 0;\n\tint addr_num = 0;\n\taddfunc add_func = NULL;\n\n\tunsigned char *addr[8];\n\tint addr_buffer_size = sizeof(addr) / sizeof(addr[0]);\n\n\tif (qtype == DNS_T_A) {\n\t\tadd_func = dns_HTTPS_add_ipv4hint;\n\t} else if (qtype == DNS_T_AAAA) {\n\t\tadd_func = dns_HTTPS_add_ipv6hint;\n\t} else {\n\t\treturn 0; // Unsupported type\n\t}\n\n\tif (request->passthrough == 2) {\n\t\tall_ips = 1;\n\t}\n\n\tif (request->has_ip == 0) {\n\t\treturn 0;\n\t}\n\n\tif (all_ips == 0) {\n\t\tif (request->ip_addr_type == (int)qtype) {\n\t\t\taddr[0] = request->ip_addr;\n\t\t\tret = add_func(param, addr, 1);\n\n\t\t\treturn ret;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tret = 0;\n\tpthread_mutex_lock(&request->ip_map_lock);\n\thash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)\n\t{\n\t\tif (addr_map->addr_type == qtype) {\n\t\t\taddr[addr_num] = addr_map->ip_addr;\n\t\t\taddr_num++;\n\t\t\tif (addr_num >= addr_buffer_size) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\tif (addr_num > 0) {\n\t\tret = add_func(param, addr, addr_num);\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_add_rrs_HTTPS(struct dns_server_post_context *context)\n{\n\tstruct dns_request *request = context->request;\n\tstruct dns_request_https *https_svcb;\n\tint ret = 0;\n\tstruct dns_rr_nested param;\n\n\tif (request->qtype != DNS_T_HTTPS) {\n\t\treturn 0;\n\t}\n\n\tlist_for_each_entry(https_svcb, &request->https_svcb_list, list) {\n\t\tret = dns_add_HTTPS_start(&param, context->packet, DNS_RRS_AN, https_svcb->domain, https_svcb->ttl,\n\t\t\t\t\t\t\t\t  https_svcb->priority, https_svcb->target);\n\t\tif (ret != 0) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tif (https_svcb->alpn[0] != '\\0' && https_svcb->alpn_len > 0) {\n\t\t\tret = dns_HTTPS_add_alpn(&param, https_svcb->alpn, https_svcb->alpn_len);\n\t\t\tif (ret != 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\n\t\tif (https_svcb->port != 0) {\n\t\t\tret = dns_HTTPS_add_port(&param, https_svcb->port);\n\t\t\tif (ret != 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\n\t\tif (https_svcb->has_ipv4) {\n\t\t\tunsigned char *addr[1];\n\t\t\taddr[0] = https_svcb->ipv4_addr;\n\t\t\tret = dns_HTTPS_add_ipv4hint(&param, addr, 1);\n\t\t} else {\n\t\t\tret = _dns_add_rrs_ip_hint(context, &param, DNS_T_A);\n\t\t}\n\t\tif (ret != 0) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tif (https_svcb->ech_len > 0) {\n\t\t\tret = dns_HTTPS_add_ech(&param, https_svcb->ech, https_svcb->ech_len);\n\t\t\tif (ret != 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\n\t\tif (https_svcb->has_ipv6) {\n\t\t\tunsigned char *addr[1];\n\t\t\taddr[0] = https_svcb->ipv6_addr;\n\t\t\tret = dns_HTTPS_add_ipv6hint(&param, addr, 1);\n\t\t} else {\n\t\t\tret = _dns_add_rrs_ip_hint(context, &param, DNS_T_AAAA);\n\t\t}\n\t\tif (ret != 0) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tdns_add_HTTPS_end(&param);\n\t}\n\treturn 0;\n}\n\nstatic int _dns_add_rrs(struct dns_server_post_context *context)\n{\n\tstruct dns_request *request = context->request;\n\tint ret = 0;\n\tint has_soa = request->has_soa;\n\tchar *domain = request->domain;\n\tif (request->has_ptr) {\n\t\t/* add PTR record */\n\t\tret = dns_add_PTR(context->packet, DNS_RRS_AN, request->domain, request->ip_ttl, request->ptr_hostname);\n\t}\n\n\t/* add CNAME record */\n\tif (request->has_cname && context->do_force_soa == 0) {\n\t\tret |= dns_add_CNAME(context->packet, DNS_RRS_AN, request->domain, request->ttl_cname, request->cname);\n\t\tdomain = request->cname;\n\t}\n\n\tif (!list_empty(&request->https_svcb_list)) {\n\t\tret = _dns_add_rrs_HTTPS(context);\n\t}\n\n\t/* add A record */\n\tif (request->has_ip && context->do_force_soa == 0) {\n\t\t_dns_server_context_add_ip(context, request->ip_addr);\n\t\tif (context->qtype == DNS_T_A) {\n\t\t\tret |= dns_add_A(context->packet, DNS_RRS_AN, domain, request->ip_ttl, request->ip_addr);\n\t\t\ttlog(TLOG_DEBUG, \"result: %s, rtt: %.1f ms, %d.%d.%d.%d\", request->domain, ((float)request->ping_time) / 10,\n\t\t\t\t request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3]);\n\t\t}\n\n\t\t/* add AAAA record */\n\t\tif (context->qtype == DNS_T_AAAA) {\n\t\t\tret |= dns_add_AAAA(context->packet, DNS_RRS_AN, domain, request->ip_ttl, request->ip_addr);\n\t\t\ttlog(TLOG_DEBUG,\n\t\t\t\t \"result: %s, rtt: %.1f ms, \"\n\t\t\t\t \"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t\t\t request->domain, ((float)request->ping_time) / 10, request->ip_addr[0], request->ip_addr[1],\n\t\t\t\t request->ip_addr[2], request->ip_addr[3], request->ip_addr[4], request->ip_addr[5],\n\t\t\t\t request->ip_addr[6], request->ip_addr[7], request->ip_addr[8], request->ip_addr[9],\n\t\t\t\t request->ip_addr[10], request->ip_addr[11], request->ip_addr[12], request->ip_addr[13],\n\t\t\t\t request->ip_addr[14], request->ip_addr[15]);\n\t\t}\n\t}\n\n\tif (context->do_force_soa == 0) {\n\t\tret |= _dns_rrs_add_all_best_ip(context);\n\t}\n\n\tif (context->qtype == DNS_T_A || context->qtype == DNS_T_AAAA) {\n\t\tif (context->ip_num > 0) {\n\t\t\thas_soa = 0;\n\t\t}\n\t}\n\t/* add SOA record */\n\tif (has_soa) {\n\t\tret |= dns_add_SOA(context->packet, DNS_RRS_NS, domain, request->ip_ttl, &request->soa);\n\t\ttlog(TLOG_DEBUG, \"result: %s, qtype: %d, return SOA\", request->domain, context->qtype);\n\t} else if (context->do_force_soa == 1) {\n\t\t_dns_server_setup_soa(request);\n\t\tret |= dns_add_SOA(context->packet, DNS_RRS_NS, domain, request->ip_ttl, &request->soa);\n\t}\n\n\tif (request->has_ecs) {\n\t\tret |= dns_add_OPT_ECS(context->packet, &request->ecs);\n\t}\n\n\tif (!list_empty(&request->srv_list)) {\n\t\tret |= _dns_server_add_srv(context);\n\t}\n\n\tif (request->rcode != DNS_RC_NOERROR) {\n\t\ttlog(TLOG_INFO, \"result: %s, qtype: %d, rtcode: %d, id: %d\", domain, context->qtype, request->rcode,\n\t\t\t request->id);\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_setup_dns_packet(struct dns_server_post_context *context)\n{\n\tstruct dns_head head;\n\tstruct dns_request *request = context->request;\n\tint ret = 0;\n\n\tmemset(&head, 0, sizeof(head));\n\thead.id = request->id;\n\thead.qr = DNS_QR_ANSWER;\n\thead.opcode = DNS_OP_QUERY;\n\thead.rd = 1;\n\thead.ra = 1;\n\thead.aa = 0;\n\thead.tc = 0;\n\thead.rcode = request->rcode;\n\n\t/* init a new DNS packet */\n\tret = dns_packet_init(context->packet, context->packet_maxlen, &head);\n\tif (ret != 0) {\n\t\treturn -1;\n\t}\n\n\tif (request->domain[0] == '\\0') {\n\t\treturn 0;\n\t}\n\n\t/* add request domain */\n\tret = dns_add_domain(context->packet, request->domain, context->qtype, request->qclass);\n\tif (ret != 0) {\n\t\treturn -1;\n\t}\n\n\t/* add RECORDs */\n\tret = _dns_add_rrs(context);\n\tif (ret != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_setup_dns_raw_packet(struct dns_server_post_context *context)\n{\n\t/* encode to binary data */\n\tint encode_len = dns_encode(context->inpacket, context->inpacket_maxlen, context->packet);\n\tif (encode_len <= 0) {\n\t\ttlog(TLOG_DEBUG, \"encode raw packet failed for %s\", context->request->domain);\n\t\treturn -1;\n\t}\n\n\tcontext->inpacket_len = encode_len;\n\n\treturn 0;\n}\n\nstatic int _dns_result_callback(struct dns_server_post_context *context)\n{\n\tstruct dns_result result;\n\tchar ip[DNS_MAX_CNAME_LEN];\n\tunsigned int ping_time = -1;\n\tstruct dns_request *request = context->request;\n\n\tif (request->result_callback == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (atomic_inc_return(&request->do_callback) != 1) {\n\t\treturn 0;\n\t}\n\n\tip[0] = 0;\n\tmemset(&result, 0, sizeof(result));\n\tping_time = request->ping_time;\n\tresult.domain = request->domain;\n\tresult.rtcode = request->rcode;\n\tresult.addr_type = request->qtype;\n\tresult.ip = ip;\n\tresult.has_soa = request->has_soa | context->do_force_soa;\n\tresult.ping_time = ping_time;\n\tresult.ip_num = 0;\n\n\tif (request->has_ip != 0 && context->do_force_soa == 0) {\n\t\tfor (int i = 0; i < context->ip_num && i < MAX_IP_NUM; i++) {\n\t\t\tresult.ip_addr[i] = context->ip_addr[i];\n\t\t\tresult.ip_num++;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsnprintf(ip, sizeof(ip), \"%d.%d.%d.%d\", request->ip_addr[0], request->ip_addr[1], request->ip_addr[2],\n\t\t\t\t\t request->ip_addr[3]);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsnprintf(ip, sizeof(ip), \"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t\t\t\t request->ip_addr[0], request->ip_addr[1], request->ip_addr[2], request->ip_addr[3],\n\t\t\t\t\t request->ip_addr[4], request->ip_addr[5], request->ip_addr[6], request->ip_addr[7],\n\t\t\t\t\t request->ip_addr[8], request->ip_addr[9], request->ip_addr[10], request->ip_addr[11],\n\t\t\t\t\t request->ip_addr[12], request->ip_addr[13], request->ip_addr[14], request->ip_addr[15]);\n\t\t}\n\t}\n\n\treturn request->result_callback(&result, request->user_ptr);\n}\n\nstatic int _dns_server_setup_ipset_nftset_packet(struct dns_server_post_context *context)\n{\n\tint ttl = 0;\n\tstruct dns_request *request = context->request;\n\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\tint rr_count = 0;\n\tint timeout_value = 0;\n\tint ipset_timeout_value = 0;\n\tint nftset_timeout_value = 0;\n\tint i = 0;\n\tint j = 0;\n\tstruct dns_conf_group *conf;\n\tstruct dns_rrs *rrs = NULL;\n\tstruct dns_ipset_rule *rule = NULL;\n\tstruct dns_ipset_rule *ipset_rule = NULL;\n\tstruct dns_ipset_rule *ipset_rule_v4 = NULL;\n\tstruct dns_ipset_rule *ipset_rule_v6 = NULL;\n\tstruct dns_nftset_rule *nftset_ip = NULL;\n\tstruct dns_nftset_rule *nftset_ip6 = NULL;\n\tstruct dns_rule_flags *rule_flags = NULL;\n\tint check_no_speed_rule = 0;\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_IPSET) == 0) {\n\t\treturn 0;\n\t}\n\n\tif (context->do_ipset == 0) {\n\t\treturn 0;\n\t}\n\n\tif (context->ip_num <= 0) {\n\t\treturn 0;\n\t}\n\n\tif (request->ping_time < 0 && request->has_ip > 0 && request->passthrough == 0) {\n\t\tcheck_no_speed_rule = 1;\n\t}\n\n\tconf = request->conf;\n\n\t/* check ipset rule */\n\trule_flags = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS);\n\tif (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IGN) == 0) {\n\t\tipset_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET);\n\t\tif (ipset_rule == NULL) {\n\t\t\tipset_rule = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_IPSET);\n\t\t}\n\n\t\tif (ipset_rule == NULL && check_no_speed_rule && conf->ipset_nftset.ipset_no_speed.inet_enable) {\n\t\t\tipset_rule_v4 = &conf->ipset_nftset.ipset_no_speed.inet;\n\t\t}\n\t}\n\n\tif (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IPV4_IGN) == 0) {\n\t\tipset_rule_v4 = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET_IPV4);\n\t\tif (ipset_rule_v4 == NULL) {\n\t\t\tipset_rule_v4 = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_IPSET_IPV4);\n\t\t}\n\n\t\tif (ipset_rule_v4 == NULL && check_no_speed_rule && conf->ipset_nftset.ipset_no_speed.ipv4_enable) {\n\t\t\tipset_rule_v4 = &conf->ipset_nftset.ipset_no_speed.ipv4;\n\t\t}\n\t}\n\n\tif (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_IPSET_IPV6_IGN) == 0) {\n\t\tipset_rule_v6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_IPSET_IPV6);\n\t\tif (ipset_rule_v6 == NULL) {\n\t\t\tipset_rule_v6 = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_IPSET_IPV6);\n\t\t}\n\n\t\tif (ipset_rule_v6 == NULL && check_no_speed_rule && conf->ipset_nftset.ipset_no_speed.ipv6_enable) {\n\t\t\tipset_rule_v6 = &conf->ipset_nftset.ipset_no_speed.ipv6;\n\t\t}\n\t}\n\n\tif (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_NFTSET_IP_IGN) == 0) {\n\t\tnftset_ip = _dns_server_get_dns_rule(request, DOMAIN_RULE_NFTSET_IP);\n\t\tif (nftset_ip == NULL) {\n\t\t\tnftset_ip = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_NFTSET_IP);\n\t\t}\n\n\t\tif (nftset_ip == NULL && check_no_speed_rule && conf->ipset_nftset.nftset_no_speed.ip_enable) {\n\t\t\tnftset_ip = &conf->ipset_nftset.nftset_no_speed.ip;\n\t\t}\n\t}\n\n\tif (!rule_flags || (rule_flags->flags & DOMAIN_FLAG_NFTSET_IP6_IGN) == 0) {\n\t\tnftset_ip6 = _dns_server_get_dns_rule(request, DOMAIN_RULE_NFTSET_IP6);\n\n\t\tif (nftset_ip6 == NULL) {\n\t\t\tnftset_ip6 = _dns_server_get_bind_ipset_nftset_rule(request, DOMAIN_RULE_NFTSET_IP6);\n\t\t}\n\n\t\tif (nftset_ip6 == NULL && check_no_speed_rule && conf->ipset_nftset.nftset_no_speed.ip6_enable) {\n\t\t\tnftset_ip6 = &conf->ipset_nftset.nftset_no_speed.ip6;\n\t\t}\n\t}\n\n\tif (!(ipset_rule || ipset_rule_v4 || ipset_rule_v6 || nftset_ip || nftset_ip6)) {\n\t\treturn 0;\n\t}\n\n\ttimeout_value = request->ip_ttl * 3;\n\tif (timeout_value == 0) {\n\t\ttimeout_value = _dns_server_get_conf_ttl(request, 0) * 3;\n\t}\n\n\tif (conf->ipset_nftset.ipset_timeout_enable) {\n\t\tipset_timeout_value = timeout_value;\n\t}\n\n\tif (conf->ipset_nftset.nftset_timeout_enable) {\n\t\tnftset_timeout_value = timeout_value;\n\t}\n\n\tfor (j = 1; j < DNS_RRS_OPT; j++) {\n\t\trrs = dns_get_rrs_start(context->packet, j, &rr_count);\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(context->packet, rrs)) {\n\t\t\tswitch (rrs->type) {\n\t\t\tcase DNS_T_A: {\n\t\t\t\tunsigned char addr[4];\n\t\t\t\tif (context->qtype != DNS_T_A) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t/* get A result */\n\t\t\t\tdns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);\n\n\t\t\t\trule = ipset_rule_v4 ? ipset_rule_v4 : ipset_rule;\n\t\t\t\t_dns_server_add_ipset_nftset(request, rule, nftset_ip, addr, DNS_RR_A_LEN, ipset_timeout_value,\n\t\t\t\t\t\t\t\t\t\t\t nftset_timeout_value);\n\t\t\t} break;\n\t\t\tcase DNS_T_AAAA: {\n\t\t\t\tunsigned char addr[16];\n\t\t\t\tif (context->qtype != DNS_T_AAAA) {\n\t\t\t\t\t/* ignore non-matched query type */\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);\n\n\t\t\t\trule = ipset_rule_v6 ? ipset_rule_v6 : ipset_rule;\n\t\t\t\t_dns_server_add_ipset_nftset(request, rule, nftset_ip6, addr, DNS_RR_AAAA_LEN, ipset_timeout_value,\n\t\t\t\t\t\t\t\t\t\t\t nftset_timeout_value);\n\t\t\t} break;\n\t\t\tcase DNS_T_HTTPS: {\n\t\t\t\tchar target[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tstruct dns_svcparam *p = NULL;\n\t\t\t\tint priority = 0;\n\n\t\t\t\tint ret = dns_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,\n\t\t\t\t\t\t\t\t\t\t\t\t\t  DNS_MAX_CNAME_LEN);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\ttlog(TLOG_WARN, \"get HTTPS svcparm failed\");\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\tfor (; p; p = dns_svcparm_next(rrs, p)) {\n\t\t\t\t\tswitch (p->key) {\n\t\t\t\t\tcase DNS_HTTPS_T_IPV4HINT: {\n\t\t\t\t\t\tunsigned char *addr;\n\t\t\t\t\t\tfor (int k = 0; k < p->len / 4; k++) {\n\t\t\t\t\t\t\taddr = p->value + k * 4;\n\t\t\t\t\t\t\trule = ipset_rule_v4 ? ipset_rule_v4 : ipset_rule;\n\t\t\t\t\t\t\t_dns_server_add_ipset_nftset(request, rule, nftset_ip, addr, DNS_RR_A_LEN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t ipset_timeout_value, nftset_timeout_value);\n\t\t\t\t\t\t}\n\t\t\t\t\t} break;\n\t\t\t\t\tcase DNS_HTTPS_T_IPV6HINT: {\n\t\t\t\t\t\tunsigned char *addr;\n\t\t\t\t\t\tfor (int k = 0; k < p->len / 16; k++) {\n\t\t\t\t\t\t\taddr = p->value + k * 16;\n\t\t\t\t\t\t\trule = ipset_rule_v6 ? ipset_rule_v6 : ipset_rule;\n\t\t\t\t\t\t\t_dns_server_add_ipset_nftset(request, rule, nftset_ip6, addr, DNS_RR_AAAA_LEN,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t ipset_timeout_value, nftset_timeout_value);\n\t\t\t\t\t\t}\n\t\t\t\t\t} break;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} break;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_result_child_post(struct dns_server_post_context *context)\n{\n\tstruct dns_request *request = context->request;\n\tstruct dns_request *parent_request = request->parent_request;\n\tDNS_CHILD_POST_RESULT child_ret = DNS_CHILD_POST_FAIL;\n\n\t/* not a child request */\n\tif (parent_request == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (request->child_callback) {\n\t\tint is_first_resp = context->no_release_parent;\n\t\tchild_ret = request->child_callback(parent_request, request, is_first_resp);\n\t}\n\n\tif (context->do_reply == 1 && child_ret == DNS_CHILD_POST_SUCCESS) {\n\t\tstruct dns_server_post_context parent_context;\n\t\t_dns_server_post_context_init(&parent_context, parent_request);\n\t\tparent_context.do_cache = context->do_cache;\n\t\tparent_context.do_ipset = context->do_ipset;\n\t\tparent_context.do_force_soa = context->do_force_soa;\n\t\tparent_context.do_audit = context->do_audit;\n\t\tparent_context.do_reply = context->do_reply;\n\t\tparent_context.reply_ttl = context->reply_ttl;\n\t\tparent_context.cache_ttl = context->cache_ttl;\n\t\tparent_context.skip_notify_count = context->skip_notify_count;\n\t\tparent_context.select_all_best_ip = 1;\n\t\tparent_context.no_release_parent = context->no_release_parent;\n\n\t\t_dns_request_post(&parent_context);\n\t\t_dns_server_reply_all_pending_list(parent_request, &parent_context);\n\t}\n\n\tif (context->no_release_parent == 0) {\n\t\ttlog(TLOG_DEBUG, \"query %s with child %s done\", parent_request->domain, request->domain);\n\t\trequest->parent_request = NULL;\n\t\tparent_request->request_wait--;\n\t\t_dns_server_request_release(parent_request);\n\t}\n\n\tif (child_ret == DNS_CHILD_POST_FAIL) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_request_update_id_ttl_domain(struct dns_server_post_context *context)\n{\n\tint ttl = context->reply_ttl;\n\tstruct dns_request *request = context->request;\n\n\tif (request->conf->dns_rr_ttl_reply_max > 0) {\n\t\tif (request->ip_ttl > request->conf->dns_rr_ttl_reply_max && ttl == 0) {\n\t\t\tttl = request->ip_ttl;\n\t\t}\n\n\t\tif (ttl > request->conf->dns_rr_ttl_reply_max) {\n\t\t\tttl = request->conf->dns_rr_ttl_reply_max;\n\t\t}\n\n\t\tif (ttl == 0) {\n\t\t\tttl = request->conf->dns_rr_ttl_reply_max;\n\t\t}\n\t}\n\n\tif (ttl == 0) {\n\t\tttl = request->ip_ttl;\n\t\tif (ttl == 0) {\n\t\t\tttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t}\n\t}\n\n\tstruct dns_update_param param;\n\tparam.id = request->id;\n\tparam.cname_ttl = ttl;\n\tparam.ip_ttl = ttl;\n\tparam.query_domain = request->original_domain;\n\tif (dns_packet_update(context->inpacket, context->inpacket_len, &param) != 0) {\n\t\ttlog(TLOG_DEBUG, \"update packet info failed.\");\n\t}\n\n\treturn 0;\n}\n\nint _dns_request_post(struct dns_server_post_context *context)\n{\n\tstruct dns_request *request = context->request;\n\tchar clientip[DNS_MAX_CNAME_LEN] = {0};\n\tint ret = 0;\n\n\ttlog(TLOG_DEBUG, \"reply %s qtype: %d, rcode: %d, reply: %d\", request->domain, request->qtype,\n\t\t context->packet->head.rcode, context->do_reply);\n\n\t/* init a new DNS packet */\n\tret = _dns_setup_dns_packet(context);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"setup dns packet failed.\");\n\t\treturn -1;\n\t}\n\n\tret = _dns_setup_dns_raw_packet(context);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"set dns raw packet failed.\");\n\t\treturn -1;\n\t}\n\n\t/* cache reply packet */\n\tret = _dns_cache_reply_packet(context);\n\tif (ret != 0) {\n\t\ttlog(TLOG_WARN, \"cache packet for %s failed.\", request->domain);\n\t}\n\n\t/* setup ipset */\n\t_dns_server_setup_ipset_nftset_packet(context);\n\n\t/* reply child request */\n\t_dns_result_child_post(context);\n\n\tif (context->do_reply == 0) {\n\t\treturn 0;\n\t}\n\n\tif (context->skip_notify_count == 0) {\n\t\tif (atomic_inc_return(&request->notified) != 1) {\n\t\t\ttlog(TLOG_DEBUG, \"skip reply %s %d\", request->domain, request->qtype);\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\t/* log audit log */\n\t_dns_server_audit_log(context);\n\n\t/* reply API callback */\n\t_dns_result_callback(context);\n\n\tif (request->conn == NULL) {\n\t\treturn 0;\n\t}\n\n\tret = _dns_request_update_id_ttl_domain(context);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"update packet ttl failed.\");\n\t\treturn -1;\n\t}\n\n\ttlog(TLOG_INFO, \"result: %s, client: %s, qtype: %d, id: %d, group: %s, time: %lums\", request->domain,\n\t\t get_host_by_addr(clientip, sizeof(clientip), (struct sockaddr *)&request->addr), request->qtype, request->id,\n\t\t request->dns_group_name[0] != '\\0' ? request->dns_group_name : DNS_SERVER_GROUP_DEFAULT,\n\t\t get_tick_count() - request->send_tick);\n\n\tret = _dns_reply_inpacket(request, context->inpacket, context->inpacket_len);\n\tif (ret != 0) {\n\t\ttlog(TLOG_DEBUG, \"reply raw packet to client failed.\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_get_answer(struct dns_server_post_context *context)\n{\n\tint i = 0;\n\tint j = 0;\n\tint ttl = 0;\n\tstruct dns_rrs *rrs = NULL;\n\tint rr_count = 0;\n\tstruct dns_request *request = context->request;\n\tstruct dns_packet *packet = context->packet;\n\n\tfor (j = 1; j < DNS_RRS_OPT; j++) {\n\t\trrs = dns_get_rrs_start(packet, j, &rr_count);\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\t\tswitch (rrs->type) {\n\t\t\tcase DNS_T_A: {\n\t\t\t\tunsigned char addr[4];\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tstruct dns_ip_address *addr_map = NULL;\n\n\t\t\t\tif (request->qtype != DNS_T_A) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t/* get A result */\n\t\t\t\tdns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);\n\n\t\t\t\tif (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 &&\n\t\t\t\t\tstrncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (context->no_check_add_ip == 0 &&\n\t\t\t\t\t_dns_ip_address_check_add(request, name, addr, DNS_T_A, request->ping_time, &addr_map) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (addr_map != NULL) {\n\t\t\t\t\t_dns_server_context_add_ip(context, addr_map->ip_addr);\n\t\t\t\t}\n\n\t\t\t\tif (request->has_ip == 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tmemcpy(request->ip_addr, addr, DNS_RR_A_LEN);\n\t\t\t\t/* add this ip to request */\n\t\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t\trequest->has_ip = 1;\n\t\t\t\trequest->rcode = packet->head.rcode;\n\t\t\t} break;\n\t\t\tcase DNS_T_AAAA: {\n\t\t\t\tunsigned char addr[16];\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tstruct dns_ip_address *addr_map = NULL;\n\n\t\t\t\tif (request->qtype != DNS_T_AAAA) {\n\t\t\t\t\t/* ignore non-matched query type */\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tdns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);\n\n\t\t\t\tif (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 &&\n\t\t\t\t\tstrncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (context->no_check_add_ip == 0 &&\n\t\t\t\t\t_dns_ip_address_check_add(request, name, addr, DNS_T_AAAA, request->ping_time, &addr_map) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (addr_map != NULL) {\n\t\t\t\t\t_dns_server_context_add_ip(context, addr_map->ip_addr);\n\t\t\t\t}\n\n\t\t\t\tif (request->has_ip == 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tmemcpy(request->ip_addr, addr, DNS_RR_AAAA_LEN);\n\t\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t\trequest->has_ip = 1;\n\t\t\t\trequest->rcode = packet->head.rcode;\n\t\t\t} break;\n\t\t\tcase DNS_T_NS: {\n\t\t\t\tchar cname[DNS_MAX_CNAME_LEN];\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tdns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN);\n\t\t\t\ttlog(TLOG_DEBUG, \"NS: %s, ttl: %d, cname: %s\\n\", name, ttl, cname);\n\t\t\t} break;\n\t\t\tcase DNS_T_CNAME: {\n\t\t\t\tchar cname[DNS_MAX_CNAME_LEN];\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tif (request->conf->dns_force_no_cname) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tdns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN);\n\t\t\t\ttlog(TLOG_DEBUG, \"name: %s, ttl: %d, cname: %s\\n\", name, ttl, cname);\n\t\t\t\tif (strncasecmp(name, request->domain, DNS_MAX_CNAME_LEN - 1) != 0 &&\n\t\t\t\t\tstrncasecmp(name, request->cname, DNS_MAX_CNAME_LEN - 1) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tsafe_strncpy(request->cname, cname, DNS_MAX_CNAME_LEN);\n\t\t\t\trequest->ttl_cname = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t\trequest->has_cname = 1;\n\t\t\t} break;\n\t\t\tcase DNS_T_SOA: {\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\trequest->has_soa = 1;\n\t\t\t\tif (request->rcode != DNS_RC_NOERROR) {\n\t\t\t\t\trequest->rcode = packet->head.rcode;\n\t\t\t\t}\n\t\t\t\tdns_get_SOA(rrs, name, 128, &ttl, &request->soa);\n\t\t\t\ttlog(TLOG_DEBUG,\n\t\t\t\t\t \"domain: %s, qtype: %d, SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, \"\n\t\t\t\t\t \"expire: \"\n\t\t\t\t\t \"%d, minimum: %d\",\n\t\t\t\t\t request->domain, request->qtype, request->soa.mname, request->soa.rname, request->soa.serial,\n\t\t\t\t\t request->soa.refresh, request->soa.retry, request->soa.expire, request->soa.minimum);\n\t\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t} break;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint _dns_cache_reply_packet(struct dns_server_post_context *context)\n{\n\tstruct dns_request *request = context->request;\n\tint speed = -1;\n\tif (context->do_cache == 0 || request->no_cache == 1) {\n\t\treturn 0;\n\t}\n\n\tif (context->packet->head.rcode == DNS_RC_SERVFAIL || context->packet->head.rcode == DNS_RC_NXDOMAIN ||\n\t\tcontext->packet->head.rcode == DNS_RC_NOTIMP) {\n\t\tcontext->reply_ttl = DNS_SERVER_FAIL_TTL;\n\t\t/* Do not cache record if cannot connect to remote */\n\t\tif (request->remote_server_fail == 0 && context->packet->head.rcode == DNS_RC_SERVFAIL) {\n\t\t\t/* Try keep old cache if server fail */\n\t\t\t_dns_cache_try_keep_old_cache(request);\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (context->packet->head.rcode == DNS_RC_NOTIMP) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (context->packet->head.rcode == DNS_RC_NXDOMAIN) {\n\t\t\tcontext->reply_ttl = 0;\n\t\t}\n\n\t\treturn _dns_cache_packet(context);\n\t}\n\n\tif (context->qtype != DNS_T_AAAA && context->qtype != DNS_T_A && context->qtype != DNS_T_HTTPS) {\n\t\treturn _dns_cache_specify_packet(context);\n\t}\n\n\tstruct dns_cache_data *cache_packet = dns_cache_new_data_packet(context->inpacket, context->inpacket_len);\n\tif (cache_packet == NULL) {\n\t\treturn -1;\n\t}\n\n\tspeed = request->ping_time;\n\tif (context->do_force_soa) {\n\t\tspeed = -1;\n\t}\n\n\tif (_dns_server_request_update_cache(request, speed, context->qtype, cache_packet, context->cache_ttl) != 0) {\n\t\ttlog(TLOG_WARN, \"update packet cache failed.\");\n\t}\n\n\t_dns_cache_cname_packet(context);\n\n\treturn 0;\n}\n\nint _dns_server_reply_passthrough(struct dns_server_post_context *context)\n{\n\tstruct dns_request *request = context->request;\n\n\tif (atomic_inc_return(&request->notified) != 1) {\n\t\treturn 0;\n\t}\n\n\t_dns_server_get_answer(context);\n\n\t_dns_cache_reply_packet(context);\n\n\tif (_dns_server_setup_ipset_nftset_packet(context) != 0) {\n\t\ttlog(TLOG_DEBUG, \"setup ipset failed.\");\n\t}\n\n\t_dns_result_callback(context);\n\n\t_dns_server_audit_log(context);\n\n\t/* reply child request */\n\t_dns_result_child_post(context);\n\n\tif (request->conn && context->do_reply == 1) {\n\t\tchar clientip[DNS_MAX_CNAME_LEN] = {0};\n\n\t\t/* When passthrough, modify the id to be the id of the client request. */\n\t\tint ret = _dns_request_update_id_ttl_domain(context);\n\t\tif (ret != 0) {\n\t\t\ttlog(TLOG_ERROR, \"update packet ttl failed.\");\n\t\t\treturn -1;\n\t\t}\n\n\t\t_dns_reply_inpacket(request, context->inpacket, context->inpacket_len);\n\n\t\ttlog(TLOG_INFO, \"result: %s, client: %s, qtype: %d, id: %d, group: %s, time: %lums\", request->domain,\n\t\t\t get_host_by_addr(clientip, sizeof(clientip), (struct sockaddr *)&request->addr), request->qtype,\n\t\t\t request->id, request->dns_group_name[0] != '\\0' ? request->dns_group_name : DNS_SERVER_GROUP_DEFAULT,\n\t\t\t get_tick_count() - request->send_tick);\n\t}\n\n\treturn _dns_server_reply_all_pending_list(request, context);\n}\n"
  },
  {
    "path": "src/dns_server/context.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_CONTEXT_\n#define _DNS_SERVER_CONTEXT_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_request_post(struct dns_server_post_context *context);\n\nvoid _dns_server_post_context_init(struct dns_server_post_context *context, struct dns_request *request);\n\nint _dns_server_reply_passthrough(struct dns_server_post_context *context);\n\nint _dns_cache_reply_packet(struct dns_server_post_context *context);\n\nint _dns_server_get_answer(struct dns_server_post_context *context);\n\nvoid _dns_server_post_context_init_from(struct dns_server_post_context *context, struct dns_request *request,\n\t\t\t\t\t\t\t\t\t\tstruct dns_packet *packet, unsigned char *inpacket, int inpacket_len);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/ddr.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ddr.h\"\n#include \"context.h\"\n#include \"dns_server.h\"\n#include \"request.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/util.h\"\n#include \"soa.h\"\n#include <netinet/in.h>\n\nstatic const char *_ddr_get_alpn(const struct dns_bind_ip *bind_ip)\n{\n\tif (bind_ip->alpn[0] != '\\0') {\n\t\treturn bind_ip->alpn;\n\t}\n\n\tswitch (bind_ip->type) {\n\tcase DNS_BIND_TYPE_TLS:\n\t\treturn \"dot\";\n\tcase DNS_BIND_TYPE_HTTPS:\n\t\treturn \"h2,http/1.1\";\n\tdefault:\n\t\treturn NULL;\n\t}\n}\n\nstatic void _ddr_extract_local_addresses(const struct sockaddr_storage *addr, unsigned char *ipv4_addr, int *ipv4_num,\n\t\t\t\t\t\t\t\t\t\t unsigned char *ipv6_addr, int *ipv6_num)\n{\n\t*ipv4_num = 0;\n\t*ipv6_num = 0;\n\n\tif (addr == NULL) {\n\t\treturn;\n\t}\n\n\tswitch (addr->ss_family) {\n\tcase AF_INET: {\n\t\tconst struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr;\n\t\tmemcpy(ipv4_addr, &addr_in->sin_addr.s_addr, DNS_RR_A_LEN);\n\t\t*ipv4_num = 1;\n\t} break;\n\tcase AF_INET6: {\n\t\tconst struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr;\n\t\tmemcpy(ipv6_addr, addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN);\n\t\t*ipv6_num = 1;\n\t} break;\n\tdefault:\n\t\tbreak;\n\t}\n}\n\nstatic int _ddr_build_svcb_record(struct dns_packet *packet, const char *domain, int ttl, int priority,\n\t\t\t\t\t\t\t\t  const char *alpn, int port, unsigned char *ipv4_addr, int ipv4_num,\n\t\t\t\t\t\t\t\t  unsigned char *ipv6_addr, int ipv6_num)\n{\n\tstruct dns_rr_nested svcparam_buffer;\n\n\tif (dns_add_SVCB_start(&svcparam_buffer, packet, DNS_RRS_AN, domain, ttl, priority, NULL) != 0) {\n\t\treturn -1;\n\t}\n\n\t/* Add ALPN parameter */\n\tif (alpn != NULL) {\n\t\tuint8_t alpn_data[DNS_MAX_ALPN_LEN];\n\t\tint alpn_data_len = encode_alpn_protos(alpn, alpn_data, sizeof(alpn_data));\n\t\tif (alpn_data_len > 0) {\n\t\t\tdns_SVCB_add_alpn(&svcparam_buffer, alpn_data, alpn_data_len);\n\t\t}\n\t}\n\n\t/* Add port parameter */\n\tif (port > 0) {\n\t\tdns_SVCB_add_port(&svcparam_buffer, port);\n\t}\n\n\t/* Add IPv4 hint */\n\tif (ipv4_num > 0 && ipv4_addr != NULL) {\n\t\tunsigned char *ip_addr[1] = {ipv4_addr};\n\t\tdns_SVCB_add_ipv4hint(&svcparam_buffer, ip_addr, ipv4_num);\n\t}\n\n\t/* Add IPv6 hint */\n\tif (ipv6_num > 0 && ipv6_addr != NULL) {\n\t\tunsigned char *ip_addr[1] = {ipv6_addr};\n\t\tdns_SVCB_add_ipv6hint(&svcparam_buffer, ip_addr, ipv6_num);\n\t}\n\n\tdns_add_SVCB_end(&svcparam_buffer);\n\treturn 0;\n}\n\nint _dns_server_process_DDR(struct dns_request *request)\n{\n\tstruct dns_server_post_context context;\n\tint ret = 0;\n\tint added_svcb = 0;\n\tint ttl = request->ip_ttl;\n\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_reply = 1;\n\n\t/* Initialize DNS response head */\n\tstruct dns_head head;\n\tmemset(&head, 0, sizeof(head));\n\thead.id = request->id;\n\thead.qr = DNS_QR_ANSWER;\n\thead.opcode = DNS_OP_QUERY;\n\thead.aa = 0;\n\thead.rd = 0;\n\thead.ra = 1;\n\thead.rcode = DNS_RC_NOERROR;\n\n\t/* Initialize DNS packet */\n\tret = dns_packet_init(context.packet, context.packet_maxlen, &head);\n\tif (ret != 0) {\n\t\treturn _dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t}\n\n\t/* Add request domain */\n\tret = dns_add_domain(context.packet, request->domain, request->qtype, request->qclass);\n\tif (ret != 0) {\n\t\treturn _dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t}\n\n\t/* Set default TTL if not set */\n\tif (ttl <= 0) {\n\t\tttl = 60;\n\t}\n\n\t/* Get local address for IP hints */\n\tstruct sockaddr_storage *local_addr = (struct sockaddr_storage *)dns_server_request_get_local_addr(request);\n\n\t/* Iterate through all bind IPs and create SVCB records for DDR-enabled bindings */\n\tint priority = 1;\n\tfor (int i = 0; i < dns_conf.bind_ip_num; i++) {\n\t\tstruct dns_bind_ip *bind_ip = &dns_conf.bind_ip[i];\n\t\tconst char *alpn = NULL;\n\t\tint port = 0;\n\t\tchar ip[DNS_MAX_IPLEN];\n\t\tunsigned char ipv4_addr[DNS_RR_A_LEN];\n\t\tint ipv4_num = 0;\n\t\tunsigned char ipv6_addr[DNS_RR_AAAA_LEN];\n\t\tint ipv6_num = 0;\n\n\t\t/* Skip if DDR is not enabled for this binding */\n\t\tif ((bind_ip->flags & BIND_FLAG_DDR) == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Determine ALPN */\n\t\talpn = _ddr_get_alpn(bind_ip);\n\t\tif (alpn == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Extract port from IP string */\n\t\tif (parse_ip(bind_ip->ip, ip, &port) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Extract local addresses for IP hints */\n\t\t_ddr_extract_local_addresses(local_addr, ipv4_addr, &ipv4_num, ipv6_addr, &ipv6_num);\n\n\t\t/* Build SVCB record */\n\t\tret = _ddr_build_svcb_record(context.packet, request->domain, ttl, priority, alpn, port, ipv4_addr, ipv4_num,\n\t\t\t\t\t\t\t\t\t ipv6_addr, ipv6_num);\n\t\tif (ret == 0) {\n\t\t\tadded_svcb++;\n\t\t\tpriority++;\n\t\t}\n\t}\n\n\t/* If no SVCB records were added, return SOA */\n\tif (added_svcb == 0) {\n\t\treturn _dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t}\n\n\t/* Encode to binary data */\n\tint encode_len = dns_encode(context.inpacket, context.inpacket_maxlen, context.packet);\n\tif (encode_len <= 0) {\n\t\treturn _dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t}\n\n\tcontext.inpacket_len = encode_len;\n\tcontext.do_cache = 0;\n\tcontext.do_ipset = 0;\n\t_dns_server_reply_passthrough(&context);\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/ddr.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_DDR_H\n#define _DNS_SERVER_DDR_H\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_process_DDR(struct dns_request *request);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n\n#endif // _DNS_SERVER_DDR_H\n"
  },
  {
    "path": "src/dns_server/dns64.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"dns64.h\"\n#include \"address.h\"\n#include \"context.h\"\n#include \"dns_server.h\"\n#include \"ptr.h\"\n#include \"request.h\"\n#include \"request_pending.h\"\n#include \"rules.h\"\n#include \"soa.h\"\n\n#include \"smartdns/dns_conf.h\"\n\n#include <errno.h>\n#include <string.h>\n\nint _dns_server_is_dns64_request(struct dns_request *request)\n{\n\tif (request->conf->dns_dns64.prefix_len <= 0) {\n\t\treturn 0;\n\t}\n\n\tif (request->dualstack_selection_query == 1) {\n\t\treturn 0;\n\t}\n\n\tif (strncmp(request->domain, DNS64_IPV4ONLY_APRA_DOMAIN, sizeof(DNS64_IPV4ONLY_APRA_DOMAIN)) == 0) {\n\t\treturn 1;\n\t}\n\n\tif (request->qtype != DNS_T_AAAA) {\n\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n\nstatic int _dns_server_process_ipv4only_arpa(struct dns_request *request)\n{\n\tif (strncmp(request->domain, DNS64_IPV4ONLY_APRA_DOMAIN, sizeof(DNS64_IPV4ONLY_APRA_DOMAIN)) != 0) {\n\t\treturn -1;\n\t}\n\n\t/* address /domain/ rule */\n\tswitch (request->qtype) {\n\tcase DNS_T_A: {\n\t\tu_int8_t *ipv4_addr1 = (u_int8_t[]){192, 0, 0, 170};\n\t\tu_int8_t *ipv4_addr2 = (u_int8_t[]){192, 0, 0, 171};\n\t\tmemcpy(request->ip_addr, ipv4_addr1, DNS_RR_A_LEN);\n\t\t_dns_ip_address_check_add(request, request->cname, ipv4_addr1, DNS_T_A, 1, NULL);\n\t\t_dns_ip_address_check_add(request, request->cname, ipv4_addr2, DNS_T_A, 1, NULL);\n\t\trequest->has_ip = 1;\n\t} break;\n\tcase DNS_T_AAAA:\n\t\t/* no AAAA record for ipv4only.arpa */\n\t\trequest->has_ip = 0;\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\trequest->rcode = DNS_RC_NOERROR;\n\trequest->ip_ttl = _dns_server_get_local_ttl(request);\n\trequest->dualstack_selection = 0;\n\n\tstruct dns_server_post_context context;\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_reply = 1;\n\tcontext.do_audit = 1;\n\tcontext.do_ipset = 0;\n\tcontext.do_cache = 0;\n\tcontext.select_all_best_ip = 1;\n\t_dns_request_post(&context);\n\t_dns_server_reply_all_pending_list(request, &context);\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstatic enum DNS_CHILD_POST_RESULT\n_dns_server_process_dns64_callback(struct dns_request *request, struct dns_request *child_request, int is_first_resp)\n{\n\tunsigned long bucket = 0;\n\tstruct dns_ip_address *addr_map = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tuint32_t key = 0;\n\tint addr_len = 0;\n\n\tif (request->has_ip == 1) {\n\t\tif (memcmp(request->ip_addr, request->conf->dns_dns64.prefix, 12) != 0) {\n\t\t\treturn DNS_CHILD_POST_SKIP;\n\t\t}\n\t}\n\n\tif (child_request->qtype != DNS_T_A) {\n\t\treturn DNS_CHILD_POST_FAIL;\n\t}\n\n\tif (child_request->has_cname == 1) {\n\t\tsafe_strncpy(request->cname, child_request->cname, sizeof(request->cname));\n\t\trequest->has_cname = 1;\n\t\trequest->ttl_cname = child_request->ttl_cname;\n\t}\n\n\tif (child_request->has_ip == 0 && request->has_ip == 0) {\n\t\trequest->rcode = child_request->rcode;\n\t\tif (child_request->has_soa) {\n\t\t\tmemcpy(&request->soa, &child_request->soa, sizeof(struct dns_soa));\n\t\t\trequest->has_soa = 1;\n\t\t\treturn DNS_CHILD_POST_SKIP;\n\t\t}\n\n\t\tif (request->has_soa == 0) {\n\t\t\t_dns_server_setup_soa(request);\n\t\t\trequest->has_soa = 1;\n\t\t}\n\t\treturn DNS_CHILD_POST_FAIL;\n\t}\n\n\tif (request->has_ip == 0 && child_request->has_ip == 1) {\n\t\trequest->rcode = child_request->rcode;\n\t\tmemcpy(request->ip_addr, request->conf->dns_dns64.prefix, 12);\n\t\tmemcpy(request->ip_addr + 12, child_request->ip_addr, 4);\n\t\trequest->ip_ttl = child_request->ip_ttl;\n\t\trequest->has_ip = 1;\n\t\trequest->has_soa = 0;\n\t}\n\n\tpthread_mutex_lock(&request->ip_map_lock);\n\thash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)\n\t{\n\t\thash_del(&addr_map->node);\n\t\tfree(addr_map);\n\t}\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\tpthread_mutex_lock(&child_request->ip_map_lock);\n\thash_for_each_safe(child_request->ip_map, bucket, tmp, addr_map, node)\n\t{\n\t\tstruct dns_ip_address *new_addr_map = NULL;\n\n\t\tif (addr_map->addr_type == DNS_T_A) {\n\t\t\taddr_len = DNS_RR_A_LEN;\n\t\t} else {\n\t\t\tcontinue;\n\t\t}\n\n\t\tnew_addr_map = zalloc(1, sizeof(struct dns_ip_address));\n\t\tif (new_addr_map == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\t\tpthread_mutex_unlock(&child_request->ip_map_lock);\n\t\t\treturn DNS_CHILD_POST_FAIL;\n\t\t}\n\n\t\tnew_addr_map->addr_type = DNS_T_AAAA;\n\t\taddr_len = DNS_RR_AAAA_LEN;\n\t\tmemcpy(new_addr_map->ip_addr, request->conf->dns_dns64.prefix, 16);\n\t\tmemcpy(new_addr_map->ip_addr + 12, addr_map->ip_addr, 4);\n\n\t\tnew_addr_map->ping_time = addr_map->ping_time;\n\t\tkey = jhash(new_addr_map->ip_addr, addr_len, 0);\n\t\tkey = jhash(&new_addr_map->addr_type, sizeof(new_addr_map->addr_type), key);\n\t\tpthread_mutex_lock(&request->ip_map_lock);\n\t\thash_add(request->ip_map, &new_addr_map->node, key);\n\t\tpthread_mutex_unlock(&request->ip_map_lock);\n\t}\n\tpthread_mutex_unlock(&child_request->ip_map_lock);\n\n\tif (request->dualstack_selection == 1) {\n\t\treturn DNS_CHILD_POST_NO_RESPONSE;\n\t}\n\n\treturn DNS_CHILD_POST_SKIP;\n}\n\nint _dns_server_process_dns64(struct dns_request *request)\n{\n\tif (_dns_server_is_dns64_request(request) == 0) {\n\t\treturn 0;\n\t}\n\n\ttlog(TLOG_DEBUG, \"query %s with dns64\", request->domain);\n\n\tif (_dns_server_process_ipv4only_arpa(request) == 0) {\n\t\treturn 2;\n\t}\n\n\tstruct dns_request *child_request =\n\t\t_dns_server_new_child_request(request, request->domain, DNS_T_A, _dns_server_process_dns64_callback);\n\tif (child_request == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\treturn -1;\n\t}\n\n\trequest->dualstack_selection = 0;\n\tchild_request->prefetch_flags |= PREFETCH_FLAGS_NO_DUALSTACK;\n\trequest->request_wait++;\n\tint ret = _dns_server_do_query(child_request, 0);\n\tif (ret != 0) {\n\t\trequest->request_wait--;\n\t\ttlog(TLOG_ERROR, \"do query %s type %d failed.\\n\", request->domain, request->qtype);\n\t\tgoto errout;\n\t}\n\n\t_dns_server_request_release_complete(child_request, 0);\n\treturn 0;\n\nerrout:\n\n\tif (child_request) {\n\t\trequest->child_request = NULL;\n\t\t_dns_server_request_release(child_request);\n\t}\n\n\treturn -1;\n}"
  },
  {
    "path": "src/dns_server/dns64.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_DNS64_\n#define _DNS_SERVER_DNS64_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_process_dns64(struct dns_request *request);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/dns_server.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include \"dns_server.h\"\n#include \"address.h\"\n#include \"answer.h\"\n#include \"audit.h\"\n#include \"cache.h\"\n#include \"client_rule.h\"\n#include \"cname.h\"\n#include \"connection.h\"\n#include \"context.h\"\n#include \"dualstack.h\"\n#include \"ip_rule.h\"\n#include \"local_addr.h\"\n#include \"mdns.h\"\n#include \"neighbor.h\"\n#include \"ptr.h\"\n#include \"request.h\"\n#include \"request_pending.h\"\n#include \"rules.h\"\n#include \"server_https.h\"\n#include \"server_socket.h\"\n#include \"server_tcp.h\"\n#include \"server_tls.h\"\n#include \"server_udp.h\"\n#include \"server_http2.h\"\n#include \"soa.h\"\n#include \"speed_check.h\"\n\n#include \"smartdns/dns_cache.h\"\n#include \"smartdns/dns_client.h\"\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/dns_plugin.h\"\n#include \"smartdns/dns_stats.h\"\n#include \"smartdns/fast_ping.h\"\n#include \"smartdns/http_parse.h\"\n#include \"smartdns/lib/atomic.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/lib/list.h\"\n#include \"smartdns/lib/nftset.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <signal.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n\nstatic int is_server_init;\nstruct dns_server server;\n\nstatic void _dns_server_wakeup_thread(void)\n{\n\tuint64_t u = 1;\n\tint unused __attribute__((unused));\n\tunused = write(server.event_fd, &u, sizeof(u));\n}\n\nstatic int _dns_server_forward_request(unsigned char *inpacket, int inpacket_len)\n{\n\treturn -1;\n}\n\nint _dns_reply_inpacket(struct dns_request *request, unsigned char *inpacket, int inpacket_len)\n{\n\tstruct dns_server_conn_head *conn = request->conn;\n\tint ret = 0;\n\n\tif (conn == NULL) {\n\t\ttlog(TLOG_ERROR, \"client is invalid, domain: %s\", request->domain);\n\t\treturn -1;\n\t}\n\n\tif (conn->type == DNS_CONN_TYPE_UDP_SERVER) {\n\t\tret = _dns_server_reply_udp(request, (struct dns_server_conn_udp *)conn, inpacket, inpacket_len);\n\t} else if (conn->type == DNS_CONN_TYPE_TCP_CLIENT) {\n\t\tret = _dns_server_reply_tcp(request, (struct dns_server_conn_tcp_client *)conn, inpacket, inpacket_len);\n\t} else if (conn->type == DNS_CONN_TYPE_TLS_CLIENT) {\n\t\tret = _dns_server_reply_tcp(request, (struct dns_server_conn_tcp_client *)conn, inpacket, inpacket_len);\n\t} else if (conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\tret = _dns_server_reply_https(request, (struct dns_server_conn_tcp_client *)conn, inpacket, inpacket_len);\n\t} else if (conn->type == DNS_CONN_TYPE_HTTP2_STREAM) {\n\t\tret = _dns_server_reply_http2(request, (struct dns_server_conn_http2_stream *)conn, inpacket, inpacket_len);\n\t} else {\n\t\tret = -1;\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_server_resolve_callback_reply_passthrough(struct dns_request *request, const char *domain,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  struct dns_packet *packet, unsigned char *inpacket,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t  int inpacket_len, unsigned int result_flag)\n{\n\tstruct dns_server_post_context context;\n\tint ttl = 0;\n\tint ret = 0;\n\n\tret = _dns_server_passthrough_rule_check(request, domain, packet, result_flag, &ttl);\n\tif (ret == 0) {\n\t\treturn 0;\n\t}\n\n\tttl = _dns_server_get_conf_ttl(request, ttl);\n\t_dns_server_post_context_init_from(&context, request, packet, inpacket, inpacket_len);\n\tcontext.do_cache = 1;\n\tcontext.do_audit = 1;\n\tcontext.do_reply = 1;\n\tcontext.do_ipset = 1;\n\tcontext.reply_ttl = ttl;\n\treturn _dns_server_reply_passthrough(&context);\n}\n\nstatic int dns_server_resolve_callback(const char *domain, dns_result_type rtype, struct dns_server_info *server_info,\n\t\t\t\t\t\t\t\t\t   struct dns_packet *packet, unsigned char *inpacket, int inpacket_len,\n\t\t\t\t\t\t\t\t\t   void *user_ptr)\n{\n\tstruct dns_request *request = user_ptr;\n\tint ret = 0;\n\tint need_passthrouh = 0;\n\tunsigned long result_flag = dns_client_server_result_flag(server_info);\n\n\tif (request == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (rtype == DNS_QUERY_RESULT) {\n\t\ttlog(TLOG_DEBUG, \"query result from server %s:%d, type: %d, domain: %s qtype: %d rcode: %d, id: %d\",\n\t\t\t dns_client_get_server_ip(server_info), dns_client_get_server_port(server_info),\n\t\t\t dns_client_get_server_type(server_info), domain, request->qtype, packet->head.rcode, request->id);\n\n\t\tif (request->passthrough == 1 && atomic_read(&request->notified) == 0) {\n\t\t\treturn _dns_server_resolve_callback_reply_passthrough(request, domain, packet, inpacket, inpacket_len,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  result_flag);\n\t\t}\n\n\t\tif (request->prefetch == 0 && request->response_mode == DNS_RESPONSE_MODE_FASTEST_RESPONSE &&\n\t\t\tatomic_read(&request->notified) == 0) {\n\t\t\tstruct dns_server_post_context context;\n\t\t\tint ttl = 0;\n\t\t\tret = _dns_server_passthrough_rule_check(request, domain, packet, result_flag, &ttl);\n\t\t\tif (ret != 0) {\n\t\t\t\t_dns_server_post_context_init_from(&context, request, packet, inpacket, inpacket_len);\n\t\t\t\tcontext.do_cache = 1;\n\t\t\t\tcontext.do_audit = 1;\n\t\t\t\tcontext.do_reply = 1;\n\t\t\t\tcontext.do_ipset = 1;\n\t\t\t\tcontext.reply_ttl = _dns_server_get_reply_ttl(request, ttl);\n\t\t\t\tcontext.cache_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t\trequest->ip_ttl = context.cache_ttl;\n\t\t\t\t_dns_server_reply_passthrough(&context);\n\t\t\t\trequest->cname[0] = 0;\n\t\t\t\trequest->has_ip = 0;\n\t\t\t\trequest->has_cname = 0;\n\t\t\t\trequest->has_ping_result = 0;\n\t\t\t\trequest->has_soa = 0;\n\t\t\t\trequest->has_ptr = 0;\n\t\t\t\trequest->ping_time = -1;\n\t\t\t\trequest->ip_ttl = 0;\n\t\t\t}\n\t\t}\n\n\t\tret = _dns_server_process_answer(request, domain, packet, result_flag, &need_passthrouh);\n\t\tif (ret == 0 && need_passthrouh == 1 && atomic_read(&request->notified) == 0) {\n\t\t\t/* not supported record, passthrouth */\n\t\t\trequest->passthrough = 1;\n\t\t\treturn _dns_server_resolve_callback_reply_passthrough(request, domain, packet, inpacket, inpacket_len,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  result_flag);\n\t\t}\n\t\t_dns_server_passthrough_may_complete(request);\n\t\treturn ret;\n\t} else if (rtype == DNS_QUERY_ERR) {\n\t\ttlog(TLOG_ERROR, \"request failed, %s\", domain);\n\t\treturn -1;\n\t} else {\n\t\t_dns_server_query_end(request);\n\t}\n\n\treturn 0;\n}\n\nint dns_server_get_server_name(char *name, int name_len)\n{\n\tif (name == NULL || name_len <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (dns_conf.server_name[0] == 0) {\n\t\tchar hostname[DNS_MAX_CNAME_LEN];\n\t\tchar domainname[DNS_MAX_CNAME_LEN];\n\n\t\t/* get local domain name */\n\t\tif (getdomainname(domainname, DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t\t/* check domain is valid */\n\t\t\tif (strncmp(domainname, \"(none)\", DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t\t\tdomainname[0] = '\\0';\n\t\t\t}\n\t\t}\n\n\t\tif (gethostname(hostname, DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t\t/* check hostname is valid */\n\t\t\tif (strncmp(hostname, \"(none)\", DNS_MAX_CNAME_LEN - 1) == 0) {\n\t\t\t\thostname[0] = '\\0';\n\t\t\t}\n\t\t}\n\n\t\tif (hostname[0] != '\\0' && domainname[0] != '\\0') {\n\t\t\tsnprintf(name, name_len, \"%.64s.%.128s\", hostname, domainname);\n\t\t} else if (hostname[0] != '\\0') {\n\t\t\tsafe_strncpy(name, hostname, name_len);\n\t\t} else {\n\t\t\tsafe_strncpy(name, \"smartdns\", name_len);\n\t\t}\n\t} else {\n\t\t/* return configured server name */\n\t\tsafe_strncpy(name, dns_conf.server_name, name_len);\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_do_query(struct dns_request *request, int skip_notify_event)\n{\n\tint ret = -1;\n\tconst char *server_group_name = NULL;\n\tstruct dns_query_options options;\n\tchar *request_domain = request->domain;\n\tchar domain_buffer[DNS_MAX_CNAME_LEN * 2];\n\n\trequest->send_tick = get_tick_count();\n\n\tif (_dns_server_setup_request_conf_pre(request) != 0) {\n\t\tgoto errout;\n\t}\n\n\t/* lookup domain rule */\n\t_dns_server_get_domain_rule(request);\n\n\t_dns_server_setup_dns_group_name(request, &server_group_name);\n\n\tif (_dns_server_setup_request_conf(request) != 0) {\n\t\tgoto errout;\n\t}\n\n\tif (_dns_server_mdns_query_setup(request, server_group_name, &request_domain, domain_buffer,\n\t\t\t\t\t\t\t\t\t sizeof(domain_buffer)) != 0) {\n\t\tgoto errout;\n\t}\n\n\tif (_dns_server_process_cname_pre(request) != 0) {\n\t\tgoto errout;\n\t}\n\n\t_dns_server_set_dualstack_selection(request);\n\n\tif (_dns_server_process_special_query(request) == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\tif (_dns_server_pre_process_server_flags(request) == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\t/* process domain flag */\n\tif (_dns_server_pre_process_rule_flags(request) == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\t/* process domain address */\n\tif (_dns_server_process_address(request) == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\tif (_dns_server_process_https_svcb(request) != 0) {\n\t\tgoto clean_exit;\n\t}\n\n\tif (_dns_server_process_smartdns_domain(request) == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\tif (_dns_server_process_host(request) == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\t/* process qtype soa */\n\tif (_dns_server_qtype_soa(request) == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\t/* process speed check rule */\n\t_dns_server_process_speed_rule(request);\n\n\t/* check and set passthrough */\n\t_dns_server_check_set_passthrough(request);\n\n\t/* process ptr */\n\tif (_dns_server_process_ptr_query(request) == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\t/* process cache */\n\tif (request->prefetch == 0 && request->dualstack_selection_query == 0) {\n\t\t_dns_server_mdns_query_setup_server_group(request, &server_group_name);\n\t\tif (_dns_server_process_cache(request) == 0) {\n\t\t\tgoto clean_exit;\n\t\t}\n\t}\n\n\tret = _dns_server_set_to_pending_list(request);\n\tif (ret == 0) {\n\t\tgoto clean_exit;\n\t}\n\n\tif (_dns_server_process_cname(request) != 0) {\n\t\tgoto clean_exit;\n\t}\n\n\t// setup options\n\t_dns_server_setup_query_option(request, &options);\n\t_dns_server_mdns_query_setup_server_group(request, &server_group_name);\n\n\tpthread_mutex_lock(&server.request_list_lock);\n\tif (list_empty(&server.request_list) && skip_notify_event == 1) {\n\t\t_dns_server_wakeup_thread();\n\t}\n\tlist_add_tail(&request->list, &server.request_list);\n\tpthread_mutex_unlock(&server.request_list_lock);\n\n\tret = _dns_server_process_dns64(request);\n\tif (ret != 0) {\n\t\tif (ret == 2) {\n\t\t\t/* dns64 processing, return success */\n\t\t\tgoto clean_exit;\n\t\t}\n\n\t\tgoto errout;\n\t}\n\n\t// Get reference for DNS query\n\trequest->request_wait++;\n\t_dns_server_request_get(request);\n\tif (dns_client_query(request_domain, request->qtype, dns_server_resolve_callback, request, server_group_name,\n\t\t\t\t\t\t &options) != 0) {\n\t\trequest->request_wait--;\n\t\t_dns_server_request_release(request);\n\t\ttlog(TLOG_DEBUG, \"send dns request failed.\");\n\t\tgoto errout;\n\t}\n\n\t/* When the dual stack ip preference is enabled, both A and AAAA records are requested. */\n\t_dns_server_query_dualstack(request);\n\nclean_exit:\n\treturn 0;\nerrout:\n\treturn ret;\n}\n\nstatic int _dns_server_reply_format_error(struct dns_request *request, struct dns_server_conn_head *conn,\n\t\t\t\t\t\t\t\t\t\t  unsigned char *inpacket, int inpacket_len, struct sockaddr_storage *local,\n\t\t\t\t\t\t\t\t\t\t  socklen_t local_len, struct sockaddr_storage *from, socklen_t from_len)\n{\n\tunsigned char packet_buff[DNS_PACKSIZE];\n\tstruct dns_packet *packet = (struct dns_packet *)packet_buff;\n\tint decode_len = 0;\n\tint need_release = 0;\n\tint ret = -1;\n\n\tif (request == NULL) {\n\t\tdecode_len = dns_decode_head_only(packet, DNS_PACKSIZE, inpacket, inpacket_len);\n\t\tif (decode_len < 0) {\n\t\t\tret = -1;\n\t\t\tgoto out;\n\t\t}\n\n\t\trequest = _dns_server_new_request();\n\t\tif (request == NULL) {\n\t\t\tret = -1;\n\t\t\tgoto out;\n\t\t}\n\n\t\tneed_release = 1;\n\t\tmemcpy(&request->localaddr, local, local_len);\n\t\t_dns_server_request_set_client(request, conn);\n\t\t_dns_server_request_set_client_addr(request, from, from_len);\n\t\t_dns_server_request_set_id(request, packet->head.id);\n\t}\n\n\trequest->rcode = DNS_RC_FORMERR;\n\trequest->no_cache = 1;\n\trequest->send_tick = get_tick_count();\n\tret = 0;\nout:\n\tif (request && need_release) {\n\t\t_dns_server_request_release(request);\n\t}\n\n\treturn ret;\n}\n\nint _dns_server_recv(struct dns_server_conn_head *conn, unsigned char *inpacket, int inpacket_len,\n\t\t\t\t\t struct sockaddr_storage *local, socklen_t local_len, struct sockaddr_storage *from,\n\t\t\t\t\t socklen_t from_len)\n{\n\tint decode_len = 0;\n\tint ret = -1;\n\tunsigned char packet_buff[DNS_PACKSIZE];\n\tchar name[DNS_MAX_CNAME_LEN];\n\tstruct dns_packet *packet = (struct dns_packet *)packet_buff;\n\tstruct dns_request *request = NULL;\n\tstruct dns_client_rules *client_rules = NULL;\n\n\t/* decode packet */\n\ttlog(TLOG_DEBUG, \"recv query packet from %s, len = %d, type = %d\",\n\t\t get_host_by_addr(name, sizeof(name), (struct sockaddr *)from), inpacket_len, conn->type);\n\tdecode_len = dns_decode(packet, DNS_PACKSIZE, inpacket, inpacket_len);\n\tif (decode_len < 0) {\n\t\ttlog(TLOG_DEBUG, \"decode failed.\\n\");\n\t\tret = RECV_ERROR_INVALID_PACKET;\n\t\tif (dns_conf.dns_save_fail_packet) {\n\t\t\tdns_packet_save(dns_conf.dns_save_fail_packet_dir, \"server\", name, inpacket, inpacket_len);\n\t\t}\n\t\tgoto errout;\n\t}\n\n\tif (smartdns_plugin_func_server_recv(packet, inpacket, inpacket_len, local, local_len, from, from_len) != 0) {\n\t\treturn 0;\n\t}\n\n\ttlog(TLOG_DEBUG,\n\t\t \"request qdcount = %d, ancount = %d, nscount = %d, nrcount = %d, len = %d, id = %d, tc = %d, rd = %d, \"\n\t\t \"ra = \"\n\t\t \"%d, rcode = %d\\n\",\n\t\t packet->head.qdcount, packet->head.ancount, packet->head.nscount, packet->head.nrcount, inpacket_len,\n\t\t packet->head.id, packet->head.tc, packet->head.rd, packet->head.ra, packet->head.rcode);\n\tclient_rules = _dns_server_get_client_rules(from, from_len);\n\trequest = _dns_server_new_request();\n\tif (request == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tmemcpy(&request->localaddr, local, local_len);\n\t_dns_server_request_set_mac(request, from, from_len);\n\t_dns_server_request_set_client(request, conn);\n\t_dns_server_request_set_client_addr(request, from, from_len);\n\t_dns_server_request_set_id(request, packet->head.id);\n\tstats_inc(&dns_stats.request.from_client_count);\n\n\tif (_dns_server_parser_request(request, packet) != 0) {\n\t\ttlog(TLOG_DEBUG, \"parser request failed.\");\n\t\tret = RECV_ERROR_INVALID_PACKET;\n\t\tgoto errout;\n\t}\n\n\ttlog(TLOG_DEBUG, \"query %s from %s, qtype: %d, id: %d, query-num: %ld\", request->domain, name, request->qtype,\n\t\t request->id, atomic_read(&server.request_num));\n\n\tif (atomic_read(&server.request_num) > dns_conf.max_query_limit && dns_conf.max_query_limit > 0) {\n\t\tstatic time_t last_log_time = 0;\n\t\ttime_t now = time(NULL);\n\t\tif (now - last_log_time > 120) {\n\t\t\tlast_log_time = now;\n\t\t\ttlog(TLOG_WARN, \"maximum number of dns queries reached, max: %d\", dns_conf.max_query_limit);\n\t\t}\n\t\trequest->rcode = DNS_RC_REFUSED;\n\t\tret = 0;\n\t\tgoto errout;\n\t}\n\n\tret = _dns_server_request_set_client_rules(request, client_rules);\n\tif (ret != 0) {\n\t\tret = 0;\n\t\tgoto errout;\n\t}\n\n\tret = _dns_server_do_query(request, 1);\n\tif (ret != 0) {\n\t\ttlog(TLOG_DEBUG, \"do query %s failed.\\n\", request->domain);\n\t\tgoto errout;\n\t}\n\t_dns_server_request_release_complete(request, 0);\n\treturn ret;\nerrout:\n\tif (ret == RECV_ERROR_INVALID_PACKET) {\n\t\tif (_dns_server_reply_format_error(request, conn, inpacket, inpacket_len, local, local_len, from, from_len) ==\n\t\t\t0) {\n\t\t\tret = 0;\n\t\t}\n\t}\n\n\tif (request) {\n\t\trequest->send_tick = get_tick_count();\n\t\trequest->no_cache = 1;\n\t\t_dns_server_forward_request(inpacket, inpacket_len);\n\t\t_dns_server_request_release(request);\n\t}\n\n\treturn ret;\n}\n\nint dns_server_query(const char *domain, int qtype, struct dns_server_query_option *server_query_option,\n\t\t\t\t\t dns_result_callback callback, void *user_ptr)\n{\n\tint ret = -1;\n\tstruct dns_request *request = NULL;\n\n\trequest = _dns_server_new_request();\n\tif (request == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(request->domain, domain, sizeof(request->domain));\n\trequest->qtype = qtype;\n\t_dns_server_setup_server_query_options(request, server_query_option);\n\t_dns_server_request_set_callback(request, callback, user_ptr);\n\tret = _dns_server_do_query(request, 0);\n\tif (ret != 0) {\n\t\ttlog(TLOG_DEBUG, \"do query %s failed.\\n\", domain);\n\t\tgoto errout;\n\t}\n\n\t_dns_server_request_release_complete(request, 0);\n\treturn ret;\nerrout:\n\tif (request) {\n\t\t_dns_server_request_set_callback(request, NULL, NULL);\n\t\t_dns_server_request_release(request);\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_server_process(struct dns_server_conn_head *conn, struct epoll_event *event, unsigned long now)\n{\n\tint ret = 0;\n\t_dns_server_client_touch(conn);\n\t_dns_server_conn_get(conn);\n\tif (conn->type == DNS_CONN_TYPE_UDP_SERVER) {\n\t\tstruct dns_server_conn_udp *udpconn = (struct dns_server_conn_udp *)conn;\n\t\tret = _dns_server_process_udp(udpconn, event, now);\n\t} else if (conn->type == DNS_CONN_TYPE_TCP_SERVER) {\n\t\tstruct dns_server_conn_tcp_server *tcpserver = (struct dns_server_conn_tcp_server *)conn;\n\t\tret = _dns_server_tcp_accept(tcpserver, event, now);\n\t} else if (conn->type == DNS_CONN_TYPE_TCP_CLIENT) {\n\t\tstruct dns_server_conn_tcp_client *tcpclient = (struct dns_server_conn_tcp_client *)conn;\n\t\tret = _dns_server_process_tcp(tcpclient, event, now);\n\t\tif (ret != 0) {\n\t\t\tchar name[DNS_MAX_CNAME_LEN];\n\t\t\ttlog(TLOG_DEBUG, \"process TCP packet from %s failed.\",\n\t\t\t\t get_host_by_addr(name, sizeof(name), (struct sockaddr *)&tcpclient->addr));\n\t\t}\n\t} else if (conn->type == DNS_CONN_TYPE_TLS_SERVER || conn->type == DNS_CONN_TYPE_HTTPS_SERVER) {\n\t\tstruct dns_server_conn_tls_server *tls_server = (struct dns_server_conn_tls_server *)conn;\n\t\tret = _dns_server_tls_accept(tls_server, event, now);\n\t} else if (conn->type == DNS_CONN_TYPE_TLS_CLIENT || conn->type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)conn;\n\t\tret = _dns_server_process_tls(tls_client, event, now);\n\t\tif (ret != 0) {\n\t\t\tchar name[DNS_MAX_CNAME_LEN];\n\t\t\ttlog(TLOG_DEBUG, \"process TLS packet from %s failed.\",\n\t\t\t\t get_host_by_addr(name, sizeof(name), (struct sockaddr *)&tls_client->tcp.addr));\n\t\t}\n\t} else {\n\t\ttlog(TLOG_ERROR, \"unsupported dns server type %d\", conn->type);\n\t\t_dns_server_client_close(conn);\n\t\tret = -1;\n\t}\n\t_dns_server_conn_release(conn);\n\n\tif (ret == RECV_ERROR_INVALID_PACKET) {\n\t\tret = 0;\n\t}\n\n\treturn ret;\n}\n\nstatic int _dns_server_socket(void)\n{\n\tint i = 0;\n\n\tfor (i = 0; i < dns_conf.bind_ip_num; i++) {\n\t\tstruct dns_bind_ip *bind_ip = &dns_conf.bind_ip[i];\n\t\ttlog(TLOG_INFO, \"bind ip %s, type %d\", bind_ip->ip, bind_ip->type);\n\n\t\tswitch (bind_ip->type) {\n\t\tcase DNS_BIND_TYPE_UDP:\n\t\t\tif (_dns_server_socket_udp(bind_ip) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase DNS_BIND_TYPE_TCP:\n\t\t\tif (_dns_server_socket_tcp(bind_ip) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase DNS_BIND_TYPE_HTTPS:\n\t\t\tif (_dns_server_socket_tls(bind_ip, DNS_CONN_TYPE_HTTPS_SERVER) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase DNS_BIND_TYPE_TLS:\n\t\t\tif (_dns_server_socket_tls(bind_ip, DNS_CONN_TYPE_TLS_SERVER) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\n\treturn -1;\n}\n\n#ifdef TEST\nstatic void _dns_server_check_need_exit(void)\n{\n\tstatic int parent_pid = 0;\n\tif (parent_pid == 0) {\n\t\tparent_pid = getppid();\n\t}\n\n\tif (parent_pid != getppid()) {\n\t\ttlog(TLOG_WARN, \"parent process exit, exit too.\");\n\t\tdns_server_stop();\n\t}\n}\n#else\n#define _dns_server_check_need_exit()\n#endif\n\nstatic void _dns_server_period_run_second(void)\n{\n\tstatic unsigned int sec = 0;\n\tsec++;\n\n\t_dns_server_tcp_idle_check();\n\t_dns_server_check_need_exit();\n\n\tif (sec % IPV6_READY_CHECK_TIME == 0 && is_ipv6_ready == 0) {\n\t\tdns_server_check_ipv6_ready();\n\t}\n\n\tif (sec % 60 == 0) {\n\t\tif (dns_server_check_update_hosts() == 0) {\n\t\t\ttlog(TLOG_INFO, \"Update host file data\");\n\t\t}\n\t}\n\n\t_dns_server_save_cache_to_file();\n\n\tdns_stats_period_run_second();\n}\n\nstatic void _dns_server_period_run(unsigned int msec)\n{\n\tstruct dns_request *request = NULL;\n\tstruct dns_request *tmp = NULL;\n\tLIST_HEAD(check_list);\n\n\tif ((msec % 10) == 0) {\n\t\t_dns_server_period_run_second();\n\t}\n\n\tunsigned long now = get_tick_count();\n\n\tpthread_mutex_lock(&server.request_list_lock);\n\tlist_for_each_entry_safe(request, tmp, &server.request_list, list)\n\t{\n\t\t/* Need to use tcping detection speed */\n\t\tint check_order = request->check_order + 1;\n\t\tif (atomic_read(&request->ip_map_num) == 0 || request->has_soa) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ((request->send_tick < now - (check_order * DNS_PING_CHECK_INTERVAL) && request->has_ping_result == 0) ||\n\t\t\trequest->ping_time > DNS_PING_RTT_CHECK_THRESHOLD) {\n\t\t\t_dns_server_request_get(request);\n\t\t\tlist_add_tail(&request->check_list, &check_list);\n\t\t\trequest->check_order++;\n\t\t}\n\t}\n\tpthread_mutex_unlock(&server.request_list_lock);\n\n\tlist_for_each_entry_safe(request, tmp, &check_list, check_list)\n\t{\n\t\t_dns_server_second_ping_check(request);\n\t\tlist_del_init(&request->check_list);\n\t\t_dns_server_request_release(request);\n\t}\n}\n\nint dns_server_run(void)\n{\n\tstruct epoll_event events[DNS_MAX_EVENTS + 1];\n\tint num = 0;\n\tint i = 0;\n\tunsigned long now = {0};\n\tunsigned int msec = 0;\n\tint sleep = 100;\n\tint sleep_time = 0;\n\tunsigned long expect_time = 0;\n\tunsigned long start_time = 0;\n\n\tnow = get_tick_count();\n\tstart_time = now;\n\texpect_time = now + sleep;\n\n\twhile (atomic_read(&server.run)) {\n\t\tnow = get_tick_count();\n\n\t\tif (now >= expect_time) {\n\t\t\tunsigned long elapsed_from_start = now - start_time;\n\t\t\tunsigned int current_period = (elapsed_from_start + sleep / 2) / sleep;\n\n\t\t\tif (current_period > msec) {\n\t\t\t\tmsec = current_period;\n\t\t\t}\n\t\t\texpect_time = start_time + (msec + 1) * sleep;\n\n\t\t\t_dns_server_period_run(msec);\n\t\t\tmsec++;\n\n\t\t\t/* When server is idle, the sleep time is 1000ms, to reduce CPU usage */\n\t\t\tpthread_mutex_lock(&server.request_list_lock);\n\t\t\tif (list_empty(&server.request_list)) {\n\t\t\t\tif (msec % 10 != 0) {\n\t\t\t\t\tmsec = ((msec / 10) + 1) * 10;\n\t\t\t\t\texpect_time = start_time + msec * sleep;\n\t\t\t\t}\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&server.request_list_lock);\n\t\t}\n\n\t\tsleep_time = (int)(expect_time - now);\n\t\tif (sleep_time < 0) {\n\t\t\tsleep_time = 0;\n\t\t}\n\n\t\tnum = epoll_wait(server.epoll_fd, events, DNS_MAX_EVENTS, sleep_time);\n\t\tif (num < 0) {\n\t\t\tusleep(100000);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (num == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (i = 0; i < num; i++) {\n\t\t\tstruct epoll_event *event = &events[i];\n\t\t\t/* read event */\n\t\t\tif (unlikely(event->data.fd == server.event_fd)) {\n\t\t\t\tuint64_t value;\n\t\t\t\tint unused __attribute__((unused));\n\t\t\t\tunused = read(server.event_fd, &value, sizeof(uint64_t));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (unlikely(event->data.fd == server.local_addr_cache.fd_netlink)) {\n\t\t\t\t_dns_server_process_local_addr_cache(event->data.fd, event, now);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tstruct dns_server_conn_head *conn_head = event->data.ptr;\n\t\t\tif (conn_head == NULL) {\n\t\t\t\ttlog(TLOG_ERROR, \"invalid fd\\n\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (_dns_server_process(conn_head, event, now) != 0) {\n\t\t\t\ttlog(TLOG_DEBUG, \"dns server process failed.\");\n\t\t\t}\n\t\t}\n\t}\n\n\t_dns_server_close_socket_server();\n\tclose(server.epoll_fd);\n\tserver.epoll_fd = -1;\n\n\treturn 0;\n}\n\nint dns_server_start(void)\n{\n\tstruct dns_server_conn_head *conn = NULL;\n\n\tlist_for_each_entry(conn, &server.conn_list, list)\n\t{\n\t\tif (conn->fd <= 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (_dns_server_epoll_ctl(conn, EPOLL_CTL_ADD, EPOLLIN) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed.\");\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_server_init_wakeup_event(void)\n{\n\tint fdevent = -1;\n\tfdevent = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);\n\tif (fdevent < 0) {\n\t\ttlog(TLOG_ERROR, \"create eventfd failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tstruct epoll_event event;\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLERR;\n\tevent.data.fd = fdevent;\n\tif (epoll_ctl(server.epoll_fd, EPOLL_CTL_ADD, fdevent, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"set eventfd failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tserver.event_fd = fdevent;\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint dns_server_init(void)\n{\n\tpthread_mutexattr_t attr;\n\tint epollfd = -1;\n\tint ret = -1;\n\n\t_dns_server_check_need_exit();\n\n\tif (is_server_init == 1) {\n\t\treturn -1;\n\t}\n\n\tif (server.epoll_fd > 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_server_audit_init() != 0) {\n\t\ttlog(TLOG_ERROR, \"init audit failed.\");\n\t\tgoto errout;\n\t}\n\n\tmemset(&server, 0, sizeof(server));\n\n\tpthread_mutexattr_init(&attr);\n\tpthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);\n\n\tINIT_LIST_HEAD(&server.conn_list);\n\ttime(&server.cache_save_time);\n\tatomic_set(&server.request_num, 0);\n\tpthread_mutex_init(&server.request_list_lock, NULL);\n\tpthread_mutex_init(&server.conn_list_lock, &attr);\n\tINIT_LIST_HEAD(&server.request_list);\n\tpthread_mutexattr_destroy(&attr);\n\n\tepollfd = epoll_create1(EPOLL_CLOEXEC);\n\tif (epollfd < 0) {\n\t\ttlog(TLOG_ERROR, \"create epoll failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tret = _dns_server_socket();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"create server socket failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tserver.epoll_fd = epollfd;\n\tatomic_set(&server.run, 1);\n\n\tif (dns_server_start() != 0) {\n\t\ttlog(TLOG_ERROR, \"start service failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tdns_server_check_ipv6_ready();\n\ttlog(TLOG_INFO, \"%s\",\n\t\t (is_ipv6_ready) ? \"IPV6 is ready, enable IPV6 features\"\n\t\t\t\t\t\t : \"IPV6 is not ready or speed check is disabled, disable IPV6 features\");\n\n\tif (_dns_server_init_wakeup_event() != 0) {\n\t\ttlog(TLOG_ERROR, \"init wakeup event failed.\");\n\t\tgoto errout;\n\t}\n\n\tif (_dns_server_cache_init() != 0) {\n\t\ttlog(TLOG_ERROR, \"init dns cache filed.\");\n\t\tgoto errout;\n\t}\n\n\tif (_dns_server_local_addr_cache_init() != 0) {\n\t\ttlog(TLOG_WARN, \"init local addr cache failed, disable local ptr.\");\n\t\tdns_conf.local_ptr_enable = 0;\n\t}\n\n\tif (_dns_server_neighbor_cache_init() != 0) {\n\t\ttlog(TLOG_ERROR, \"init neighbor cache failed.\");\n\t\tgoto errout;\n\t}\n\n\tis_server_init = 1;\n\treturn 0;\nerrout:\n\tatomic_set(&server.run, 0);\n\n\tif (epollfd) {\n\t\tclose(epollfd);\n\t}\n\n\t_dns_server_close_socket();\n\tpthread_mutex_destroy(&server.request_list_lock);\n\n\treturn -1;\n}\n\nvoid dns_server_stop(void)\n{\n\tatomic_set(&server.run, 0);\n\t_dns_server_wakeup_thread();\n}\n\nvoid dns_server_exit(void)\n{\n\tif (is_server_init == 0) {\n\t\treturn;\n\t}\n\n\tif (server.event_fd > 0) {\n\t\tclose(server.event_fd);\n\t\tserver.event_fd = -1;\n\t}\n\n\tif (server.cache_save_pid > 0) {\n\t\tkill(server.cache_save_pid, SIGKILL);\n\t\tserver.cache_save_pid = 0;\n\t}\n\n\t_dns_server_close_socket();\n\t_dns_server_local_addr_cache_destroy();\n\t_dns_server_neighbor_cache_remove_all();\n\t_dns_server_cache_save(0);\n\t_dns_server_request_remove_all();\n\tpthread_mutex_destroy(&server.request_list_lock);\n\tdns_cache_destroy();\n\n\tis_server_init = 0;\n}\n"
  },
  {
    "path": "src/dns_server/dns_server.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_H_\n#define _DNS_SERVER_H_\n\n#include \"smartdns/lib/atomic.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/lib/list.h\"\n\n#include \"smartdns/dns.h\"\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/dns_server.h\"\n#include \"smartdns/http2.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n\n#include <openssl/ssl.h>\n#include <pthread.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\n#define DNS_MAX_EVENTS 256\n#define IPV6_READY_CHECK_TIME 180\n#define DNS_SERVER_TMOUT_TTL (5 * 60)\n#define DNS_SERVER_FAIL_TTL (3)\n#define DNS_SERVER_SOA_TTL (30)\n#define DNS_SERVER_ADDR_TTL (60)\n#define DNS_CONN_BUFF_SIZE 4096\n#define DNS_REQUEST_MAX_TIMEOUT 950\n#define DNS_PING_TIMEOUT (DNS_REQUEST_MAX_TIMEOUT)\n#define DNS_PING_CHECK_INTERVAL (100)\n#define DNS_PING_RTT_CHECK_THRESHOLD (100 * 10)\n#define DNS_PING_SECOND_TIMEOUT (DNS_REQUEST_MAX_TIMEOUT - DNS_PING_CHECK_INTERVAL)\n#define SOCKET_IP_TOS (IPTOS_LOWDELAY | IPTOS_RELIABILITY)\n#define SOCKET_PRIORITY (6)\n#define CACHE_AUTO_ENABLE_SIZE (1024 * 1024 * 128)\n#define EXPIRED_DOMAIN_PREFETCH_TIME (3600 * 8)\n#define DNS_MAX_DOMAIN_REFETCH_NUM 64\n#define DNS_SERVER_NEIGHBOR_CACHE_TIMEOUT       1200\n#define DNS_SERVER_NEIGHBOR_CACHE_NOMAC_TIMEOUT 60\n#define DNS_SERVER_NEIGHBOR_CACHE_MAX_NUM (1024 * 16)\n\n#define PREFETCH_FLAGS_NO_DUALSTACK (1 << 0)\n#define PREFETCH_FLAGS_EXPIRED (1 << 1)\n#define PREFETCH_FLAGS_NOPREFETCH (1 << 2)\n\n#define RECV_ERROR_AGAIN 1\n#define RECV_ERROR_OK 0\n#define RECV_ERROR_FAIL (-1)\n#define RECV_ERROR_CLOSE (-2)\n#define RECV_ERROR_INVALID_PACKET (-3)\n#define RECV_ERROR_BAD_PATH (-4)\n\ntypedef enum {\n\tDNS_CONN_TYPE_UDP_SERVER = 0,\n\tDNS_CONN_TYPE_TCP_SERVER,\n\tDNS_CONN_TYPE_TCP_CLIENT,\n\tDNS_CONN_TYPE_TLS_SERVER,\n\tDNS_CONN_TYPE_TLS_CLIENT,\n\tDNS_CONN_TYPE_HTTPS_SERVER,\n\tDNS_CONN_TYPE_HTTPS_CLIENT,\n\tDNS_CONN_TYPE_HTTP2_STREAM,\n} DNS_CONN_TYPE;\n\ntypedef enum DNS_CHILD_POST_RESULT {\n\tDNS_CHILD_POST_SUCCESS = 0,\n\tDNS_CHILD_POST_FAIL,\n\tDNS_CHILD_POST_SKIP,\n\tDNS_CHILD_POST_NO_RESPONSE,\n} DNS_CHILD_POST_RESULT;\n\nstruct rule_walk_args {\n\tvoid *args;\n\tint rule_index;\n\tuint32_t full_key_len;\n\tunsigned char *key[DOMAIN_RULE_MAX];\n\tuint32_t key_len[DOMAIN_RULE_MAX];\n};\n\nstruct neighbor_enum_args {\n\tuint8_t *netaddr;\n\tint netaddr_len;\n\tstruct client_roue_group_mac *group_mac;\n};\n\nstruct neighbor_cache_item {\n\tstruct hlist_node node;\n\tstruct list_head list;\n\tunsigned char ip_addr[DNS_RR_AAAA_LEN];\n\tint ip_addr_len;\n\tunsigned char mac[6];\n\tint has_mac;\n\ttime_t last_update_time;\n};\n\nstruct neighbor_cache {\n\tDECLARE_HASHTABLE(cache, 6);\n\tatomic_t cache_num;\n\tstruct list_head list;\n\tpthread_mutex_t lock;\n};\n\nstruct local_addr_cache_item {\n\tunsigned char ip_addr[DNS_RR_AAAA_LEN];\n\tint ip_addr_len;\n\tint mask_len;\n};\n\nstruct local_addr_cache {\n\tradix_tree_t *addr;\n\tint fd_netlink;\n};\n\nstruct dns_conn_buf {\n\tuint8_t buf[DNS_CONN_BUFF_SIZE];\n\tint buffsize;\n\tint size;\n};\n\nstruct dns_server_conn_head {\n\tDNS_CONN_TYPE type;\n\tint fd;\n\tstruct list_head list;\n\ttime_t last_request_time;\n\tatomic_t refcnt;\n\tconst char *dns_group;\n\tuint32_t server_flags;\n\tstruct nftset_ipset_rules *ipset_nftset_rule;\n};\n\nstruct dns_server_post_context {\n\tunsigned char inpacket_buff[DNS_IN_PACKSIZE];\n\tunsigned char *inpacket;\n\tint inpacket_maxlen;\n\tint inpacket_len;\n\tunsigned char packet_buff[DNS_PACKSIZE];\n\tunsigned int packet_maxlen;\n\tstruct dns_request *request;\n\tstruct dns_packet *packet;\n\tint ip_num;\n\tconst unsigned char *ip_addr[MAX_IP_NUM];\n\tdns_type_t qtype;\n\tint do_cache;\n\tint do_reply;\n\tint do_ipset;\n\tint do_log_result;\n\tint reply_ttl;\n\tint cache_ttl;\n\tint no_check_add_ip;\n\tint do_audit;\n\tint do_force_soa;\n\tint skip_notify_count;\n\tint select_all_best_ip;\n\tint no_release_parent;\n\tint is_cache_reply;\n};\n\ntypedef enum dns_server_client_status {\n\tDNS_SERVER_CLIENT_STATUS_INIT = 0,\n\tDNS_SERVER_CLIENT_STATUS_CONNECTING,\n\tDNS_SERVER_CLIENT_STATUS_CONNECTIONLESS,\n\tDNS_SERVER_CLIENT_STATUS_CONNECTED,\n\tDNS_SERVER_CLIENT_STATUS_DISCONNECTED,\n} dns_server_client_status;\n\nstruct dns_server_conn_udp {\n\tstruct dns_server_conn_head head;\n\tsocklen_t addr_len;\n\tstruct sockaddr_storage addr;\n};\n\nstruct dns_server_conn_tcp_server {\n\tstruct dns_server_conn_head head;\n};\n\nstruct dns_server_conn_tls_server {\n\tstruct dns_server_conn_head head;\n\tSSL_CTX *ssl_ctx;\n};\n\nstruct dns_server_conn_tcp_client {\n\tstruct dns_server_conn_head head;\n\tstruct dns_conn_buf recvbuff;\n\tstruct dns_conn_buf sndbuff;\n\tsocklen_t addr_len;\n\tstruct sockaddr_storage addr;\n\n\tsocklen_t localaddr_len;\n\tstruct sockaddr_storage localaddr;\n\n\tint conn_idle_timeout;\n\tdns_server_client_status status;\n};\n\nstruct dns_server_conn_tls_client {\n\tstruct dns_server_conn_tcp_client tcp;\n\tSSL *ssl;\n\tint ssl_want_write;\n\tpthread_mutex_t ssl_lock;\n\tvoid *http2_ctx;\n\tchar alpn_selected[32];\n};\n\n/* ip address lists of domain */\nstruct dns_ip_address {\n\tstruct hlist_node node;\n\tint hitnum;\n\tunsigned long recv_tick;\n\tint ping_time;\n\tdns_type_t addr_type;\n\tchar cname[DNS_MAX_CNAME_LEN];\n\tunsigned char ip_addr[DNS_RR_AAAA_LEN];\n};\n\nstruct dns_request_pending_list {\n\tpthread_mutex_t request_list_lock;\n\tunsigned short qtype;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tuint32_t server_flags;\n\tchar dns_group_name[DNS_GROUP_NAME_LEN];\n\tstruct list_head request_list;\n\tstruct hlist_node node;\n};\n\nstruct dns_request_domain_rule {\n\tuint32_t flags;\n\tstruct dns_rule *rules[DOMAIN_RULE_MAX];\n\tint is_sub_rule[DOMAIN_RULE_MAX];\n};\n\ntypedef DNS_CHILD_POST_RESULT (*child_request_callback)(struct dns_request *request, struct dns_request *child_request,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tint is_first_resp);\n\nstruct dns_request_https {\n\tstruct list_head list;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tchar target[DNS_MAX_CNAME_LEN];\n\tint ttl;\n\tint priority;\n\tchar alpn[DNS_MAX_ALPN_LEN];\n\tint alpn_len;\n\tint port;\n\tchar ech[DNS_MAX_ECH_LEN];\n\tint ech_len;\n\t\n\tint has_ipv4;\n\tunsigned char ipv4_addr[DNS_RR_A_LEN];\n\tint has_ipv6;\n\tunsigned char ipv6_addr[DNS_RR_AAAA_LEN];\n};\n\nstruct dns_request_srv {\n\tstruct list_head list;\n\tchar host[DNS_MAX_CNAME_LEN];\n\tunsigned short priority;\n\tunsigned short weight;\n\tunsigned short port;\n};\n\nstruct dns_request {\n\tatomic_t refcnt;\n\n\tstruct dns_server_conn_head *conn;\n\tstruct dns_conf_group *conf;\n\tuint32_t server_flags;\n\tchar dns_group_name[DNS_GROUP_NAME_LEN];\n\n\t/* dns request list */\n\tstruct list_head list;\n\n\tstruct list_head pending_list;\n\n\t/* dns request timeout check list */\n\tstruct list_head check_list;\n\n\t/* dns query */\n\tchar domain[DNS_MAX_CNAME_LEN];\n\tchar *original_domain;\n\tdns_type_t qtype;\n\tint qclass;\n\tunsigned long send_tick;\n\tunsigned short id;\n\tunsigned short rcode;\n\tunsigned short ss_family;\n\tchar remote_server_fail;\n\tchar skip_qtype_soa;\n\tunion {\n\t\tstruct sockaddr_in in;\n\t\tstruct sockaddr_in6 in6;\n\t\tstruct sockaddr addr;\n\t};\n\tsocklen_t addr_len;\n\tstruct sockaddr_storage localaddr;\n\tuint8_t mac[6];\n\tint has_ecs;\n\tstruct dns_opt_ecs ecs;\n\tint edns0_do;\n\n\tstruct list_head https_svcb_list;\n\n\tdns_result_callback result_callback;\n\tvoid *user_ptr;\n\n\tint has_ping_result;\n\tint has_ping_tcp;\n\tint has_ptr;\n\tchar ptr_hostname[DNS_MAX_CNAME_LEN];\n\n\tint has_cname;\n\tchar cname[DNS_MAX_CNAME_LEN];\n\tint ttl_cname;\n\n\tint has_ip;\n\tint ping_time;\n\tint ip_ttl;\n\tunsigned char ip_addr[DNS_RR_AAAA_LEN];\n\tint ip_addr_type;\n\n\tstruct dns_soa soa;\n\tint has_soa;\n\tint force_soa;\n\n\tint is_mdns_lookup;\n\n\tint is_cache_reply;\n\n\tstruct list_head srv_list;\n\n\tatomic_t notified;\n\tatomic_t do_callback;\n\tatomic_t adblock;\n\tatomic_t soa_num;\n\tatomic_t plugin_complete_called;\n\n\t/* send original raw packet to server/client like proxy */\n\n\t/*\n\t 0: not passthrough, reply to client\n\t 1: passthrough, reply to client, no modify packet\n\t 2: passthrough, reply to client, check and filter ip addresses.\n\t */\n\tint passthrough;\n\n\tint request_wait;\n\tint prefetch;\n\tint prefetch_flags;\n\n\tint dualstack_selection;\n\tint dualstack_selection_force_soa;\n\tint dualstack_selection_query;\n\tint dualstack_selection_ping_time;\n\tint dualstack_selection_has_ip;\n\tstruct dns_request *dualstack_request;\n\tint no_serve_expired;\n\n\tpthread_mutex_t ip_map_lock;\n\n\tstruct dns_request *child_request;\n\tstruct dns_request *parent_request;\n\tchild_request_callback child_callback;\n\n\tatomic_t ip_map_num;\n\tDECLARE_HASHTABLE(ip_map, 4);\n\n\tstruct dns_request_domain_rule domain_rule;\n\tint skip_domain_rule;\n\tconst struct dns_domain_check_orders *check_order_list;\n\tint check_order;\n\n\tenum response_mode_type response_mode;\n\n\tstruct dns_request_pending_list *request_pending_list;\n\n\tint no_select_possible_ip;\n\tint no_cache_cname;\n\tint no_cache;\n\tint no_ipalias;\n\n\tint has_cname_loop;\n\n\tvoid *private_data;\n\n\tuint64_t query_timestamp;\n\tint query_time;\n};\n\n/* dns server data */\nstruct dns_server {\n\tatomic_t run;\n\tint epoll_fd;\n\tint event_fd;\n\tstruct list_head conn_list;\n\tpthread_mutex_t conn_list_lock;\n\n\tpid_t cache_save_pid;\n\ttime_t cache_save_time;\n\n\t/* dns request list */\n\tpthread_mutex_t request_list_lock;\n\tstruct list_head request_list;\n\tatomic_t request_num;\n\n\tDECLARE_HASHTABLE(request_pending, 4);\n\tpthread_mutex_t request_pending_lock;\n\n\tint update_neighbor_cache;\n\tstruct neighbor_cache neighbor_cache;\n\n\tstruct local_addr_cache local_addr_cache;\n};\n\nextern struct dns_server server;\n\nint _dns_server_recv(struct dns_server_conn_head *conn, unsigned char *inpacket, int inpacket_len,\n\t\t\t\t\t struct sockaddr_storage *local, socklen_t local_len, struct sockaddr_storage *from,\n\t\t\t\t\t socklen_t from_len);\n\nint _dns_reply_inpacket(struct dns_request *request, unsigned char *inpacket, int inpacket_len);\n\nint _dns_server_do_query(struct dns_request *request, int skip_notify_event);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/dualstack.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"dualstack.h\"\n#include \"dns_server.h\"\n#include \"request.h\"\n#include \"rules.h\"\n#include \"smartdns/fast_ping.h\"\n\n#include <errno.h>\n#include <string.h>\n\nint is_ipv6_ready;\n\nint dns_is_ipv6_ready(void)\n{\n\treturn is_ipv6_ready;\n}\n\nvoid dns_server_check_ipv6_ready(void)\n{\n\tstatic int do_get_conf = 0;\n\tstatic int is_icmp_check_set;\n\tstatic int is_tcp_check_set;\n\tstatic int is_tcp_syn_check_set;\n\n\tif (do_get_conf == 0) {\n\t\tif (dns_conf.has_icmp_check == 1) {\n\t\t\tis_icmp_check_set = 1;\n\t\t}\n\n\t\tif (dns_conf.has_tcp_check == 1) {\n\t\t\tis_tcp_check_set = 1;\n\t\t}\n\n\t\tif (dns_conf.has_tcp_syn_check == 1) {\n\t\t\tis_tcp_syn_check_set = 1;\n\t\t}\n\n\t\tif (is_icmp_check_set == 0) {\n\t\t\ttlog(TLOG_INFO, \"ICMP ping is disabled, no ipv6 icmp check feature\");\n\t\t}\n\n\t\tif (is_tcp_syn_check_set == 0) {\n\t\t\ttlog(TLOG_INFO, \"TCP-SYN ping is disabled, no ipv6 tcp-syn check feature\");\n\t\t}\n\n\t\tdo_get_conf = 1;\n\t}\n\n\tif (is_icmp_check_set) {\n\t\tstruct ping_host_struct *check_ping = fast_ping_start(PING_TYPE_ICMP, \"2001::\", 1, 0, 100, NULL, NULL);\n\t\tif (check_ping) {\n\t\t\tfast_ping_stop(check_ping);\n\t\t\tis_ipv6_ready = 1;\n\t\t\treturn;\n\t\t}\n\n\t\tif (errno == EADDRNOTAVAIL) {\n\t\t\tis_ipv6_ready = 0;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (is_tcp_check_set) {\n\t\tstruct ping_host_struct *check_ping = fast_ping_start(PING_TYPE_TCP, \"2001::\", 1, 0, 100, NULL, NULL);\n\t\tif (check_ping) {\n\t\t\tfast_ping_stop(check_ping);\n\t\t\tis_ipv6_ready = 1;\n\t\t\treturn;\n\t\t}\n\n\t\tif (errno == EADDRNOTAVAIL) {\n\t\t\tis_ipv6_ready = 0;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (is_tcp_syn_check_set) {\n\t\tstruct ping_host_struct *check_ping = fast_ping_start(PING_TYPE_TCP_SYN, \"2001::\", 1, 0, 100, NULL, NULL);\n\t\tif (check_ping) {\n\t\t\tfast_ping_stop(check_ping);\n\t\t\tis_ipv6_ready = 1;\n\t\t\treturn;\n\t\t}\n\n\t\tif (errno == EADDRNOTAVAIL) {\n\t\t\tis_ipv6_ready = 0;\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nvoid _dns_server_set_dualstack_selection(struct dns_request *request)\n{\n\tstruct dns_rule_flags *rule_flag = NULL;\n\n\tif (request->dualstack_selection_query || is_ipv6_ready == 0) {\n\t\trequest->dualstack_selection = 0;\n\t\treturn;\n\t}\n\n\tif ((request->prefetch_flags & PREFETCH_FLAGS_NO_DUALSTACK) != 0 ||\n\t\t(request->prefetch_flags & PREFETCH_FLAGS_EXPIRED) != 0) {\n\t\trequest->dualstack_selection = 0;\n\t\treturn;\n\t}\n\n\trule_flag = _dns_server_get_dns_rule(request, DOMAIN_RULE_FLAGS);\n\tif (rule_flag) {\n\t\tif (rule_flag->flags & DOMAIN_FLAG_DUALSTACK_SELECT) {\n\t\t\trequest->dualstack_selection = 1;\n\t\t\treturn;\n\t\t}\n\n\t\tif (rule_flag->is_flag_set & DOMAIN_FLAG_DUALSTACK_SELECT) {\n\t\t\trequest->dualstack_selection = 0;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_DUALSTACK_SELECTION) == 0) {\n\t\trequest->dualstack_selection = 0;\n\t\treturn;\n\t}\n\n\trequest->dualstack_selection = request->conf->dualstack_ip_selection;\n}\n\nstatic void _dns_server_check_complete_dualstack(struct dns_request *request, struct dns_request *dualstack_request)\n{\n\tif (dualstack_request == NULL || request == NULL) {\n\t\treturn;\n\t}\n\n\tif (dualstack_request->qtype == DNS_T_A && request->conf->dns_dualstack_ip_allow_force_AAAA == 0) {\n\t\treturn;\n\t}\n\n\tif (dualstack_request->ping_time > 0) {\n\t\treturn;\n\t}\n\n\tif (dualstack_request->dualstack_selection_query == 1) {\n\t\treturn;\n\t}\n\n\tif (request->ping_time <= (request->conf->dns_dualstack_ip_selection_threshold * 10)) {\n\t\treturn;\n\t}\n\n\tdualstack_request->dualstack_selection_has_ip = request->has_ip;\n\tdualstack_request->dualstack_selection_ping_time = request->ping_time;\n\tdualstack_request->dualstack_selection_force_soa = 1;\n\t_dns_server_request_complete(dualstack_request);\n}\n\nint _dns_server_force_dualstack(struct dns_request *request)\n{\n\t/* for dualstack request as first pending request, check if need to choose another request*/\n\tif (request->dualstack_request) {\n\t\tstruct dns_request *dualstack_request = request->dualstack_request;\n\t\trequest->dualstack_selection_has_ip = dualstack_request->has_ip;\n\t\trequest->dualstack_selection_ping_time = dualstack_request->ping_time;\n\t\trequest->dualstack_selection = 1;\n\t\t/* if another request still waiting for ping, force complete another request */\n\t\t_dns_server_check_complete_dualstack(request, dualstack_request);\n\t}\n\n\tif (request->dualstack_selection_ping_time < 0 || request->dualstack_selection == 0) {\n\t\treturn -1;\n\t}\n\n\tif (request->has_soa || request->rcode != DNS_RC_NOERROR) {\n\t\treturn -1;\n\t}\n\n\tif (request->dualstack_selection_has_ip == 0) {\n\t\treturn -1;\n\t}\n\n\tif (request->ping_time > 0) {\n\t\tif (request->dualstack_selection_ping_time + (request->conf->dns_dualstack_ip_selection_threshold * 10) >\n\t\t\trequest->ping_time) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (request->qtype == DNS_T_A && request->conf->dns_dualstack_ip_allow_force_AAAA == 0) {\n\t\treturn -1;\n\t}\n\n\t/* if ipv4 is fasting than ipv6, add ipv4 to cache, and return SOA for AAAA request */\n\ttlog(TLOG_INFO, \"result: %s, qtype: %d, force %s preferred, id: %d, time1: %d, time2: %d\", request->domain,\n\t\t request->qtype, request->qtype == DNS_T_AAAA ? \"IPv4\" : \"IPv6\", request->id, request->ping_time,\n\t\t request->dualstack_selection_ping_time);\n\trequest->dualstack_selection_force_soa = 1;\n\n\treturn 0;\n}\n\nstatic int dns_server_dualstack_callback(const struct dns_result *result, void *user_ptr)\n{\n\tstruct dns_request *request = (struct dns_request *)user_ptr;\n\ttlog(TLOG_DEBUG, \"dualstack result: domain: %s, ip: %s, type: %d, ping: %d, rcode: %d\", result->domain, result->ip,\n\t\t result->addr_type, result->ping_time, result->rtcode);\n\tif (request == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (result->rtcode == DNS_RC_NOERROR && result->ip[0] != 0) {\n\t\trequest->dualstack_selection_has_ip = 1;\n\t}\n\n\trequest->dualstack_selection_ping_time = result->ping_time;\n\n\t_dns_server_query_end(request);\n\n\treturn 0;\n}\n\nint _dns_server_query_dualstack(struct dns_request *request)\n{\n\tint ret = -1;\n\tstruct dns_request *request_dualstack = NULL;\n\tdns_type_t qtype = request->qtype;\n\n\tif (request->dualstack_selection == 0) {\n\t\treturn 0;\n\t}\n\n\tif (qtype == DNS_T_A) {\n\t\tqtype = DNS_T_AAAA;\n\t} else if (qtype == DNS_T_AAAA) {\n\t\tqtype = DNS_T_A;\n\t} else {\n\t\treturn 0;\n\t}\n\n\trequest_dualstack = _dns_server_new_request();\n\tif (request_dualstack == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\trequest_dualstack->server_flags = request->server_flags;\n\tsafe_strncpy(request_dualstack->dns_group_name, request->dns_group_name, sizeof(request->dns_group_name));\n\tsafe_strncpy(request_dualstack->domain, request->domain, sizeof(request->domain));\n\trequest_dualstack->qtype = qtype;\n\trequest_dualstack->dualstack_selection_query = 1;\n\trequest_dualstack->has_cname_loop = request->has_cname_loop;\n\trequest_dualstack->prefetch = request->prefetch;\n\trequest_dualstack->prefetch_flags = request->prefetch_flags;\n\trequest_dualstack->conf = request->conf;\n\t_dns_server_request_get(request);\n\trequest_dualstack->dualstack_request = request;\n\t_dns_server_request_set_callback(request_dualstack, dns_server_dualstack_callback, request);\n\trequest->request_wait++;\n\tret = _dns_server_do_query(request_dualstack, 0);\n\tif (ret != 0) {\n\t\trequest->request_wait--;\n\t\ttlog(TLOG_DEBUG, \"do query %s type %d failed.\\n\", request->domain, qtype);\n\t\tgoto errout;\n\t}\n\n\t_dns_server_request_release(request_dualstack);\n\treturn ret;\nerrout:\n\tif (request_dualstack) {\n\t\t_dns_server_request_set_callback(request_dualstack, NULL, NULL);\n\t\t_dns_server_request_release(request_dualstack);\n\t}\n\n\t_dns_server_request_release(request);\n\n\treturn ret;\n}\n"
  },
  {
    "path": "src/dns_server/dualstack.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_DUALSTACK_\n#define _DNS_SERVER_DUALSTACK_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nextern int is_ipv6_ready;\n\nint _dns_server_force_dualstack(struct dns_request *request);\n\nvoid _dns_server_set_dualstack_selection(struct dns_request *request);\n\nint _dns_server_query_dualstack(struct dns_request *request);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/ip_rule.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ip_rule.h\"\n#include \"dns_server.h\"\n#include \"neighbor.h\"\n#include \"rules.h\"\n#include \"soa.h\"\n\nstruct dns_client_rules *_dns_server_get_client_rules(struct sockaddr_storage *addr, socklen_t addr_len)\n{\n\tprefix_t prefix;\n\tradix_node_t *node = NULL;\n\tuint8_t netaddr[DNS_RR_AAAA_LEN] = {0};\n\tstruct dns_client_rules *client_rules = NULL;\n\tint netaddr_len = sizeof(netaddr);\n\n\tif (get_raw_addr_by_sockaddr(addr, addr_len, netaddr, &netaddr_len) != 0) {\n\t\treturn NULL;\n\t}\n\n\tclient_rules = _dns_server_get_client_rules_by_mac(netaddr, netaddr_len);\n\tif (client_rules != NULL) {\n\t\treturn client_rules;\n\t}\n\n\tif (prefix_from_blob(netaddr, netaddr_len, netaddr_len * 8, &prefix) == NULL) {\n\t\treturn NULL;\n\t}\n\n\tnode = radix_search_best(dns_conf.client_rule.rule, &prefix);\n\tif (node == NULL) {\n\t\treturn NULL;\n\t}\n\n\tclient_rules = node->data;\n\n\treturn client_rules;\n}\n\nstatic struct dns_ip_rules *_dns_server_ip_rule_get(struct dns_request *request, unsigned char *addr, int addr_len,\n\t\t\t\t\t\t\t\t\t\t\t\t\tdns_type_t addr_type)\n{\n\tprefix_t prefix;\n\tradix_node_t *node = NULL;\n\tstruct dns_ip_rules *rule = NULL;\n\n\tif (request->conf == NULL) {\n\t\treturn NULL;\n\t}\n\n\t/* Match IP address rules */\n\tif (prefix_from_blob(addr, addr_len, addr_len * 8, &prefix) == NULL) {\n\t\treturn NULL;\n\t}\n\n\tswitch (prefix.family) {\n\tcase AF_INET:\n\t\tnode = radix_search_best(request->conf->address_rule.ipv4, &prefix);\n\t\tbreak;\n\tcase AF_INET6:\n\t\tnode = radix_search_best(request->conf->address_rule.ipv6, &prefix);\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\tif (node == NULL) {\n\t\treturn NULL;\n\t}\n\n\tif (node->data == NULL) {\n\t\treturn NULL;\n\t}\n\n\trule = node->data;\n\n\treturn rule;\n}\n\nstatic int _dns_server_ip_rule_check(struct dns_request *request, struct dns_ip_rules *ip_rules, int result_flag)\n{\n\tstruct ip_rule_flags *rule_flags = NULL;\n\tif (ip_rules == NULL) {\n\t\tgoto rule_not_found;\n\t}\n\n\tstruct dns_ip_rule *rule = ip_rules->rules[IP_RULE_FLAGS];\n\tif (rule != NULL) {\n\t\trule_flags = container_of(rule, struct ip_rule_flags, head);\n\t\tif (rule_flags != NULL) {\n\t\t\tif (rule_flags->flags & IP_RULE_FLAG_BOGUS) {\n\t\t\t\trequest->rcode = DNS_RC_NXDOMAIN;\n\t\t\t\trequest->has_soa = 1;\n\t\t\t\trequest->force_soa = 1;\n\t\t\t\t_dns_server_setup_soa(request);\n\t\t\t\tgoto nxdomain;\n\t\t\t}\n\n\t\t\t/* blacklist-ip */\n\t\t\tif (rule_flags->flags & IP_RULE_FLAG_BLACKLIST) {\n\t\t\t\tif (result_flag & DNSSERVER_FLAG_BLACKLIST_IP) {\n\t\t\t\t\tgoto match;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* ignore-ip */\n\t\t\tif (rule_flags->flags & IP_RULE_FLAG_IP_IGNORE) {\n\t\t\t\tuint32_t domain_flags = _dns_server_get_rule_flags(request);\n\t\t\t\tif (domain_flags & DOMAIN_FLAG_NO_IGNORE_IP) {\n\t\t\t\t\tgoto rule_not_found;\n\t\t\t\t}\n\t\t\t\tgoto skip;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (ip_rules->rules[IP_RULE_ALIAS] != NULL) {\n\t\tgoto match;\n\t}\n\nrule_not_found:\n\tif (result_flag & DNSSERVER_FLAG_WHITELIST_IP) {\n\t\tif (rule_flags == NULL) {\n\t\t\tgoto skip;\n\t\t}\n\n\t\tif (!(rule_flags->flags & IP_RULE_FLAG_WHITELIST)) {\n\t\t\tgoto skip;\n\t\t}\n\t}\n\treturn -1;\nskip:\n\treturn -2;\nnxdomain:\n\treturn -3;\nmatch:\n\tif (request->rcode == DNS_RC_SERVFAIL) {\n\t\trequest->rcode = DNS_RC_NXDOMAIN;\n\t}\n\treturn 0;\n}\n\nint _dns_server_process_ip_alias(struct dns_request *request, struct dns_iplist_ip_addresses *alias,\n\t\t\t\t\t\t\t\t unsigned char **paddrs, int *paddr_num, int max_paddr_num, int addr_len)\n{\n\tint addr_num = 0;\n\n\tif (alias == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (request == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (alias->ipaddr_num <= 0) {\n\t\treturn 0;\n\t}\n\n\tfor (int i = 0; i < alias->ipaddr_num && i < max_paddr_num; i++) {\n\t\tif (alias->ipaddr[i].addr_len != addr_len) {\n\t\t\tcontinue;\n\t\t}\n\t\tpaddrs[i] = alias->ipaddr[i].addr;\n\t\taddr_num++;\n\t}\n\n\t*paddr_num = addr_num;\n\treturn 0;\n}\n\nint _dns_server_process_ip_rule(struct dns_request *request, unsigned char *addr, int addr_len, dns_type_t addr_type,\n\t\t\t\t\t\t\t\tint result_flag, struct dns_iplist_ip_addresses **alias)\n{\n\tstruct dns_ip_rules *ip_rules = NULL;\n\tint ret = 0;\n\n\tip_rules = _dns_server_ip_rule_get(request, addr, addr_len, addr_type);\n\tret = _dns_server_ip_rule_check(request, ip_rules, result_flag);\n\tif (ret != 0) {\n\t\treturn ret;\n\t}\n\n\tif (ip_rules->rules[IP_RULE_ALIAS] && alias != NULL) {\n\t\tif (request->no_ipalias == 0) {\n\t\t\tstruct ip_rule_alias *rule = container_of(ip_rules->rules[IP_RULE_ALIAS], struct ip_rule_alias, head);\n\t\t\t*alias = &rule->ip_alias;\n\t\t\tif (alias == NULL) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\t/* need process ip alias */\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/ip_rule.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_IP_RULE_\n#define _DNS_SERVER_IP_RULE_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_process_ip_rule(struct dns_request *request, unsigned char *addr, int addr_len, dns_type_t addr_type,\n\t\t\t\t\t\t\t\tint result_flag, struct dns_iplist_ip_addresses **alias);\n\nint _dns_server_process_ip_alias(struct dns_request *request, struct dns_iplist_ip_addresses *alias,\n\t\t\t\t\t\t\t\t unsigned char **paddrs, int *paddr_num, int max_paddr_num, int addr_len);\n\nstruct dns_client_rules *_dns_server_get_client_rules(struct sockaddr_storage *addr, socklen_t addr_len);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/ipset_nftset.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ipset_nftset.h\"\n#include \"dns_server.h\"\n#include \"smartdns/lib/nftset.h\"\n#include \"smartdns/util.h\"\n\nvoid _dns_server_add_ipset_nftset(struct dns_request *request, struct dns_ipset_rule *ipset_rule,\n\t\t\t\t\t\t\t\t  struct dns_nftset_rule *nftset_rule, const unsigned char addr[], int addr_len,\n\t\t\t\t\t\t\t\t  int ipset_timeout_value, int nftset_timeout_value)\n{\n\tif (ipset_rule != NULL) {\n\t\t/* add IPV4 to ipset */\n\t\tif (addr_len == DNS_RR_A_LEN) {\n\t\t\ttlog(TLOG_DEBUG, \"IPSET-MATCH: domain: %s, ipset: %s, IP: %d.%d.%d.%d\", request->domain,\n\t\t\t\t ipset_rule->ipsetname, addr[0], addr[1], addr[2], addr[3]);\n\t\t\tipset_add(ipset_rule->ipsetname, addr, DNS_RR_A_LEN, ipset_timeout_value);\n\t\t} else if (addr_len == DNS_RR_AAAA_LEN) {\n\t\t\ttlog(TLOG_DEBUG,\n\t\t\t\t \"IPSET-MATCH: domain: %s, ipset: %s, IP: \"\n\t\t\t\t \"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t\t\t request->domain, ipset_rule->ipsetname, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6],\n\t\t\t\t addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]);\n\t\t\tipset_add(ipset_rule->ipsetname, addr, DNS_RR_AAAA_LEN, ipset_timeout_value);\n\t\t}\n\t}\n\n\tif (nftset_rule != NULL) {\n\t\t/* add IPV4 to ipset */\n\t\tif (addr_len == DNS_RR_A_LEN) {\n\t\t\ttlog(TLOG_DEBUG, \"NFTSET-MATCH: domain: %s, nftset: %s %s %s, IP: %d.%d.%d.%d\", request->domain,\n\t\t\t\t nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr[0], addr[1], addr[2],\n\t\t\t\t addr[3]);\n\t\t\tnftset_add(nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr, DNS_RR_A_LEN,\n\t\t\t\t\t   nftset_timeout_value);\n\t\t} else if (addr_len == DNS_RR_AAAA_LEN) {\n\t\t\ttlog(TLOG_DEBUG,\n\t\t\t\t \"NFTSET-MATCH: domain: %s, nftset: %s %s %s, IP: \"\n\t\t\t\t \"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t\t\t request->domain, nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr[0],\n\t\t\t\t addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11],\n\t\t\t\t addr[12], addr[13], addr[14], addr[15]);\n\t\t\tnftset_add(nftset_rule->familyname, nftset_rule->nfttablename, nftset_rule->nftsetname, addr,\n\t\t\t\t\t   DNS_RR_AAAA_LEN, nftset_timeout_value);\n\t\t}\n\t}\n}\n\nvoid *_dns_server_get_bind_ipset_nftset_rule(struct dns_request *request, enum domain_rule type)\n{\n\tif (request->conn == NULL) {\n\t\treturn NULL;\n\t}\n\n\tif (request->conn->ipset_nftset_rule == NULL) {\n\t\treturn NULL;\n\t}\n\n\tswitch (type) {\n\tcase DOMAIN_RULE_IPSET:\n\t\treturn request->conn->ipset_nftset_rule->ipset;\n\tcase DOMAIN_RULE_IPSET_IPV4:\n\t\treturn request->conn->ipset_nftset_rule->ipset_ip;\n\tcase DOMAIN_RULE_IPSET_IPV6:\n\t\treturn request->conn->ipset_nftset_rule->ipset_ip6;\n\tcase DOMAIN_RULE_NFTSET_IP:\n\t\treturn request->conn->ipset_nftset_rule->nftset_ip;\n\tcase DOMAIN_RULE_NFTSET_IP6:\n\t\treturn request->conn->ipset_nftset_rule->nftset_ip6;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn NULL;\n}"
  },
  {
    "path": "src/dns_server/ipset_nftset.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_IPSET_NFTSET_\n#define _DNS_SERVER_IPSET_NFTSET_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_server_add_ipset_nftset(struct dns_request *request, struct dns_ipset_rule *ipset_rule,\n\t\t\t\t\t\t\t\t  struct dns_nftset_rule *nftset_rule, const unsigned char addr[], int addr_len,\n\t\t\t\t\t\t\t\t  int ipset_timeout_value, int nftset_timeout_value);\n\nvoid *_dns_server_get_bind_ipset_nftset_rule(struct dns_request *request, enum domain_rule type);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/local_addr.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"local_addr.h\"\n#include \"dns_server.h\"\n\n#include <errno.h>\n#include <linux/netlink.h>\n#include <linux/rtnetlink.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n\nstatic void _dns_server_local_addr_cache_add(unsigned char *netaddr, int netaddr_len, int prefix_len)\n{\n\tprefix_t prefix;\n\tstruct local_addr_cache_item *addr_cache_item = NULL;\n\tradix_node_t *node = NULL;\n\n\tif (prefix_from_blob(netaddr, netaddr_len, prefix_len, &prefix) == NULL) {\n\t\treturn;\n\t}\n\n\tnode = radix_lookup(server.local_addr_cache.addr, &prefix);\n\tif (node == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (node->data == NULL) {\n\t\taddr_cache_item = zalloc(1, sizeof(struct local_addr_cache_item));\n\t\tif (addr_cache_item == NULL) {\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\taddr_cache_item = node->data;\n\t}\n\n\taddr_cache_item->ip_addr_len = netaddr_len;\n\tmemcpy(addr_cache_item->ip_addr, netaddr, netaddr_len);\n\taddr_cache_item->mask_len = prefix_len;\n\tnode->data = addr_cache_item;\n\n\treturn;\nerrout:\n\tif (addr_cache_item) {\n\t\tfree(addr_cache_item);\n\t}\n\n\treturn;\n}\n\nstatic void _dns_server_local_addr_cache_del(unsigned char *netaddr, int netaddr_len, int prefix_len)\n{\n\tradix_node_t *node = NULL;\n\tprefix_t prefix;\n\n\tif (prefix_from_blob(netaddr, netaddr_len, prefix_len, &prefix) == NULL) {\n\t\treturn;\n\t}\n\n\tnode = radix_search_exact(server.local_addr_cache.addr, &prefix);\n\tif (node == NULL) {\n\t\treturn;\n\t}\n\n\tif (node->data != NULL) {\n\t\tfree(node->data);\n\t}\n\n\tnode->data = NULL;\n\tradix_remove(server.local_addr_cache.addr, node);\n}\n\nvoid _dns_server_process_local_addr_cache(int fd_netlink, struct epoll_event *event, unsigned long now)\n{\n\tchar buffer[1024 * 8];\n\tstruct iovec iov = {buffer, sizeof(buffer)};\n\tstruct sockaddr_nl sa;\n\tstruct msghdr msg;\n\tstruct nlmsghdr *nh;\n\n\tmemset(&msg, 0, sizeof(msg));\n\tmsg.msg_name = &sa;\n\tmsg.msg_namelen = sizeof(sa);\n\tmsg.msg_iov = &iov;\n\tmsg.msg_iovlen = 1;\n\n\twhile (1) {\n\t\tssize_t len = recvmsg(fd_netlink, &msg, 0);\n\t\tif (len == -1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tfor (nh = (struct nlmsghdr *)buffer; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {\n\t\t\tif (nh->nlmsg_type == NLMSG_DONE) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (nh->nlmsg_type == NLMSG_ERROR) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (nh->nlmsg_type != RTM_NEWADDR && nh->nlmsg_type != RTM_DELADDR) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tstruct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nh);\n\t\t\tstruct rtattr *rth = IFA_RTA(ifa);\n\t\t\tint rtl = IFA_PAYLOAD(nh);\n\n\t\t\twhile (rtl && RTA_OK(rth, rtl)) {\n\t\t\t\tif (rth->rta_type == IFA_ADDRESS) {\n\t\t\t\t\tunsigned char *netaddr = RTA_DATA(rth);\n\t\t\t\t\tint netaddr_len = 0;\n\n\t\t\t\t\tif (ifa->ifa_family == AF_INET) {\n\t\t\t\t\t\tnetaddr_len = 4;\n\t\t\t\t\t} else if (ifa->ifa_family == AF_INET6) {\n\t\t\t\t\t\tnetaddr_len = 16;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (nh->nlmsg_type == RTM_NEWADDR) {\n\t\t\t\t\t\t_dns_server_local_addr_cache_add(netaddr, netaddr_len, netaddr_len * 8);\n\t\t\t\t\t\t_dns_server_local_addr_cache_add(netaddr, netaddr_len, ifa->ifa_prefixlen);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t_dns_server_local_addr_cache_del(netaddr, netaddr_len, netaddr_len * 8);\n\t\t\t\t\t\t_dns_server_local_addr_cache_del(netaddr, netaddr_len, ifa->ifa_prefixlen);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trth = RTA_NEXT(rth, rtl);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void _dns_server_local_addr_cache_item_free(radix_node_t *node, void *cbctx)\n{\n\tstruct local_addr_cache_item *cache_item = NULL;\n\tif (node == NULL) {\n\t\treturn;\n\t}\n\n\tif (node->data == NULL) {\n\t\treturn;\n\t}\n\n\tcache_item = node->data;\n\tfree(cache_item);\n\tnode->data = NULL;\n}\n\nint _dns_server_local_addr_cache_destroy(void)\n{\n\tif (server.local_addr_cache.addr) {\n\t\tDestroy_Radix(server.local_addr_cache.addr, _dns_server_local_addr_cache_item_free, NULL);\n\t\tserver.local_addr_cache.addr = NULL;\n\t}\n\n\tif (server.local_addr_cache.fd_netlink > 0) {\n\t\tclose(server.local_addr_cache.fd_netlink);\n\t\tserver.local_addr_cache.fd_netlink = -1;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_local_addr_cache_init(void)\n{\n\tint fd = -1;\n\tstruct sockaddr_nl sa;\n\n\tserver.local_addr_cache.fd_netlink = -1;\n\tserver.local_addr_cache.addr = NULL;\n\n\tif (dns_conf.local_ptr_enable == 0) {\n\t\treturn 0;\n\t}\n\n\tfd = socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_ROUTE);\n\tif (fd < 0) {\n\t\ttlog(TLOG_WARN, \"create netlink socket failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tmemset(&sa, 0, sizeof(sa));\n\tsa.nl_family = AF_NETLINK;\n\tsa.nl_groups = RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_IFADDR;\n\tif (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n\t\ttlog(TLOG_WARN, \"bind netlink socket failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tstruct epoll_event event;\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLERR;\n\tevent.data.fd = fd;\n\tif (epoll_ctl(server.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"set eventfd failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tserver.local_addr_cache.fd_netlink = fd;\n\tserver.local_addr_cache.addr = New_Radix();\n\n\tstruct {\n\t\tstruct nlmsghdr nh;\n\t\tstruct rtgenmsg gen;\n\t} request;\n\n\tmemset(&request, 0, sizeof(request));\n\trequest.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));\n\trequest.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;\n\trequest.nh.nlmsg_type = RTM_GETADDR;\n\trequest.gen.rtgen_family = AF_UNSPEC;\n\n\tif (send(fd, &request, request.nh.nlmsg_len, 0) < 0) {\n\t\ttlog(TLOG_WARN, \"send netlink request failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_server/local_addr.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_LOCAL_ADDR_\n#define _DNS_SERVER_LOCAL_ADDR_\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_local_addr_cache_destroy(void);\n\nint _dns_server_local_addr_cache_init(void);\n\nvoid _dns_server_process_local_addr_cache(int fd_netlink, struct epoll_event *event, unsigned long now);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/mdns.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"mdns.h\"\n#include \"dns_server.h\"\n#include \"request.h\"\n\nvoid _dns_server_need_append_mdns_local_cname(struct dns_request *request)\n{\n\tif (request->is_mdns_lookup == 0) {\n\t\treturn;\n\t}\n\n\tif (request->has_cname != 0) {\n\t\treturn;\n\t}\n\n\tif (request->domain[0] == '\\0') {\n\t\treturn;\n\t}\n\n\tif (strstr(request->domain, \".\") != NULL) {\n\t\treturn;\n\t}\n\n\trequest->has_cname = 1;\n\tsnprintf(request->cname, sizeof(request->cname), \"%.*s.%s\",\n\t\t\t (int)(sizeof(request->cname) - sizeof(DNS_SERVER_GROUP_LOCAL) - 1), request->domain,\n\t\t\t DNS_SERVER_GROUP_LOCAL);\n\treturn;\n}\n\nvoid _dns_server_mdns_query_setup_server_group(struct dns_request *request, const char **group_name)\n{\n\tif (request->is_mdns_lookup == 0 || group_name == NULL) {\n\t\treturn;\n\t}\n\n\t*group_name = DNS_SERVER_GROUP_MDNS;\n\tsafe_strncpy(request->dns_group_name, *group_name, sizeof(request->dns_group_name));\n\treturn;\n}\n\nint _dns_server_mdns_query_setup(struct dns_request *request, const char *server_group_name, char **request_domain,\n\t\t\t\t\t\t\t\t char *domain_buffer, int domain_buffer_len)\n{\n\n\tif (dns_conf.mdns_lookup != 1) {\n\t\treturn 0;\n\t}\n\n\tswitch (request->qtype) {\n\tcase DNS_T_A:\n\tcase DNS_T_AAAA:\n\tcase DNS_T_SRV:\n\t\tif (request->domain[0] != '\\0' && strstr(request->domain, \".\") == NULL) {\n\t\t\tsnprintf(domain_buffer, domain_buffer_len, \"%s.%s\", request->domain, DNS_SERVER_GROUP_LOCAL);\n\t\t\t*request_domain = domain_buffer;\n\t\t\t_dns_server_set_request_mdns(request);\n\t\t}\n\n\t\tif (server_group_name != NULL && strncmp(server_group_name, DNS_SERVER_GROUP_MDNS, DNS_GROUP_NAME_LEN) == 0) {\n\t\t\t_dns_server_set_request_mdns(request);\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn 0;\n}"
  },
  {
    "path": "src/dns_server/mdns.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_MDNS_\n#define _DNS_SERVER_MDNS_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_server_need_append_mdns_local_cname(struct dns_request *request);\n\nvoid _dns_server_mdns_query_setup_server_group(struct dns_request *request, const char **group_name);\n\nint _dns_server_mdns_query_setup(struct dns_request *request, const char *server_group_name, char **request_domain,\n\t\t\t\t\t\t\t\t char *domain_buffer, int domain_buffer_len);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/neighbor.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"neighbor.h\"\n#include \"dns_server.h\"\n\n#include \"smartdns/fast_ping.h\"\n\n#include <linux/netlink.h>\n#include <linux/rtnetlink.h>\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n\nvoid dns_server_enable_update_neighbor_cache(int enable)\n{\n\tif (enable) {\n\t\tserver.update_neighbor_cache = 1;\n\t} else {\n\t\tif (dns_conf.client_rule.mac_num > 0) {\n\t\t\treturn;\n\t\t}\n\t\tserver.update_neighbor_cache = 0;\n\t}\n}\n\nstatic void _dns_server_neighbor_cache_free_item(struct neighbor_cache_item *item)\n{\n\thash_del(&item->node);\n\tlist_del_init(&item->list);\n\tfree(item);\n\tatomic_dec(&server.neighbor_cache.cache_num);\n}\n\nvoid _dns_server_neighbor_cache_remove_all(void)\n{\n\tstruct neighbor_cache_item *item = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long bucket = 0;\n\n\thash_for_each_safe(server.neighbor_cache.cache, bucket, tmp, item, node)\n\t{\n\t\t_dns_server_neighbor_cache_free_item(item);\n\t}\n\n\tpthread_mutex_destroy(&server.neighbor_cache.lock);\n}\n\nint _dns_server_neighbor_cache_init(void)\n{\n\thash_init(server.neighbor_cache.cache);\n\tINIT_LIST_HEAD(&server.neighbor_cache.list);\n\tatomic_set(&server.neighbor_cache.cache_num, 0);\n\tpthread_mutex_init(&server.neighbor_cache.lock, NULL);\n\n\tif (dns_conf.client_rule.mac_num > 0) {\n\t\tserver.update_neighbor_cache = 1;\n\t}\n\n\treturn 0;\n}\n\nstatic void _dns_server_neighbor_cache_free_last_used_item(void)\n{\n\tstruct neighbor_cache_item *item = NULL;\n\n\tif (atomic_read(&server.neighbor_cache.cache_num) < DNS_SERVER_NEIGHBOR_CACHE_MAX_NUM) {\n\t\treturn;\n\t}\n\n\titem = list_last_entry(&server.neighbor_cache.list, struct neighbor_cache_item, list);\n\tif (item == NULL) {\n\t\treturn;\n\t}\n\n\t_dns_server_neighbor_cache_free_item(item);\n}\n\nstruct neighbor_cache_item *_dns_server_neighbor_cache_get_item(const uint8_t *net_addr, int net_addr_len)\n{\n\tstruct neighbor_cache_item *item = NULL, *item_result = NULL;\n\tuint32_t key = 0;\n\n\tkey = jhash(net_addr, net_addr_len, 0);\n\thash_for_each_possible(server.neighbor_cache.cache, item, node, key)\n\t{\n\t\tif (item->ip_addr_len != net_addr_len) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (memcmp(item->ip_addr, net_addr, net_addr_len) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\titem_result = item;\n\t\tbreak;\n\t}\n\n\treturn item_result;\n}\n\nstatic int _dns_server_neighbor_cache_add(const uint8_t *net_addr, int net_addr_len, const uint8_t *mac)\n{\n\tstruct neighbor_cache_item *item = NULL;\n\tuint32_t key = 0;\n\n\tif (net_addr_len > DNS_RR_AAAA_LEN) {\n\t\treturn -1;\n\t}\n\n\titem = _dns_server_neighbor_cache_get_item(net_addr, net_addr_len);\n\tif (item == NULL) {\n\t\titem = zalloc(1, sizeof(*item));\n\t\tif (item == NULL) {\n\t\t\treturn -1;\n\t\t}\n\t\tINIT_LIST_HEAD(&item->list);\n\t\tINIT_HLIST_NODE(&item->node);\n\t}\n\n\tmemcpy(item->ip_addr, net_addr, net_addr_len);\n\titem->ip_addr_len = net_addr_len;\n\titem->last_update_time = time(NULL);\n\tif (mac == NULL) {\n\t\titem->has_mac = 0;\n\t} else {\n\t\tmemcpy(item->mac, mac, 6);\n\t\titem->has_mac = 1;\n\t}\n\tkey = jhash(net_addr, net_addr_len, 0);\n\thash_del(&item->node);\n\thash_add(server.neighbor_cache.cache, &item->node, key);\n\tlist_del_init(&item->list);\n\tlist_add(&item->list, &server.neighbor_cache.list);\n\tatomic_inc(&server.neighbor_cache.cache_num);\n\n\t_dns_server_neighbor_cache_free_last_used_item();\n\n\treturn 0;\n}\n\nstatic int _dns_server_neighbors_callback(const uint8_t *net_addr, int net_addr_len, const uint8_t mac[6], void *arg)\n{\n\tstruct neighbor_enum_args *args = arg;\n\n\t_dns_server_neighbor_cache_add(net_addr, net_addr_len, mac);\n\n\tif (net_addr_len != args->netaddr_len) {\n\t\treturn 0;\n\t}\n\n\tif (memcmp(net_addr, args->netaddr, net_addr_len) != 0) {\n\t\treturn 0;\n\t}\n\n\targs->group_mac = dns_server_rule_group_mac_get(mac);\n\n\treturn 1;\n}\n\nstatic int _dns_server_neighbor_cache_is_valid(struct neighbor_cache_item *item)\n{\n\tif (item == NULL) {\n\t\treturn -1;\n\t}\n\n\ttime_t now = time(NULL);\n\n\tif (item->last_update_time + DNS_SERVER_NEIGHBOR_CACHE_TIMEOUT < now) {\n\t\treturn -1;\n\t}\n\n\tif (item->has_mac) {\n\t\treturn 0;\n\t}\n\n\tif (item->last_update_time + DNS_SERVER_NEIGHBOR_CACHE_NOMAC_TIMEOUT < now) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstruct dns_client_rules *_dns_server_get_client_rules_by_mac(uint8_t *netaddr, int netaddr_len)\n{\n\tstruct client_roue_group_mac *group_mac = NULL;\n\tstruct neighbor_cache_item *item = NULL;\n\tint family = AF_UNSPEC;\n\tint ret = 0;\n\tstruct neighbor_enum_args args;\n\n\tif (server.update_neighbor_cache == 0) {\n\t\treturn NULL;\n\t}\n\n\titem = _dns_server_neighbor_cache_get_item(netaddr, netaddr_len);\n\tif (_dns_server_neighbor_cache_is_valid(item) == 0) {\n\t\tif (item->has_mac == 0) {\n\t\t\treturn NULL;\n\t\t}\n\t\tgroup_mac = dns_server_rule_group_mac_get(item->mac);\n\t\tif (group_mac != NULL) {\n\t\t\treturn group_mac->rules;\n\t\t}\n\n\t\treturn NULL;\n\t}\n\n\tif (netaddr_len == 4) {\n\t\tfamily = AF_INET;\n\t} else if (netaddr_len == 16) {\n\t\tfamily = AF_INET6;\n\t}\n\n\targs.group_mac = group_mac;\n\targs.netaddr = netaddr;\n\targs.netaddr_len = netaddr_len;\n\n\tfor (int i = 0; i < 1; i++) {\n\t\tret = netlink_get_neighbors(family, netaddr, netaddr_len, _dns_server_neighbors_callback, &args);\n\t\tif (ret < 0) {\n\t\t\tgoto add_cache;\n\t\t}\n\t}\n\n\tif (args.group_mac == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn args.group_mac->rules;\n\nadd_cache:\n\t_dns_server_neighbor_cache_add(netaddr, netaddr_len, NULL);\n\tint probe_fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);\n\tif (probe_fd >= 0) {\n\t\tstruct sockaddr_storage dest;\n\t\tmemset(&dest, 0, sizeof(dest));\n\t\tdest.ss_family = family;\n\t\tif (family == AF_INET) {\n\t\t\tstruct sockaddr_in *in = (struct sockaddr_in *)&dest;\n\t\t\tmemcpy(&in->sin_addr, netaddr, 4);\n\t\t\tin->sin_port = htons(53); /* dummy port */\n\t\t\tconnect(probe_fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_in));\n\t\t} else if (family == AF_INET6) {\n\t\t\tstruct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&dest;\n\t\t\tmemcpy(&in6->sin6_addr, netaddr, 16);\n\t\t\tin6->sin6_port = htons(53); /* dummy port */\n\t\t\tconnect(probe_fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_in6));\n\t\t}\n\t\tclose(probe_fd);\n\t}\n\n\treturn NULL;\n}\n"
  },
  {
    "path": "src/dns_server/neighbor.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_NEIGHBOR_\n#define _DNS_SERVER_NEIGHBOR_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nstruct neighbor_cache_item *_dns_server_neighbor_cache_get_item(const uint8_t *net_addr, int net_addr_len);\n\nstruct dns_client_rules *_dns_server_get_client_rules_by_mac(uint8_t *netaddr, int netaddr_len);\n\nint _dns_server_neighbor_cache_init(void);\n\nvoid _dns_server_neighbor_cache_remove_all(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/prefetch.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"prefetch.h\"\n#include \"cache.h\"\n#include \"dns_server.h\"\n#include \"request.h\"\n\n#include \"smartdns/dns_cache.h\"\n\nint _dns_server_prefetch_request(char *domain, dns_type_t qtype, struct dns_server_query_option *server_query_option,\n\t\t\t\t\t\t\t\t int prefetch_flag)\n{\n\tint ret = -1;\n\tstruct dns_request *request = NULL;\n\n\trequest = _dns_server_new_request();\n\tif (request == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\trequest->prefetch = 1;\n\trequest->prefetch_flags = prefetch_flag;\n\tsafe_strncpy(request->domain, domain, sizeof(request->domain));\n\trequest->qtype = qtype;\n\t_dns_server_setup_server_query_options(request, server_query_option);\n\tret = _dns_server_do_query(request, 0);\n\tif (ret != 0) {\n\t\ttlog(TLOG_DEBUG, \"prefetch do query %s failed.\\n\", request->domain);\n\t\tgoto errout;\n\t}\n\n\t_dns_server_request_release(request);\n\treturn ret;\nerrout:\n\tif (request) {\n\t\t_dns_server_request_release(request);\n\t}\n\n\treturn ret;\n}\n\ndns_cache_tmout_action_t _dns_server_prefetch_domain(struct dns_conf_group *conf_group, struct dns_cache *dns_cache)\n{\n\t/* If there are still hits, continue pre-fetching */\n\tstruct dns_server_query_option server_query_option;\n\tint hitnum = dns_cache_hitnum_dec_get(dns_cache);\n\tif (hitnum <= 0) {\n\t\treturn DNS_CACHE_TMOUT_ACTION_DEL;\n\t}\n\n\t/* start prefetch domain */\n\ttlog(TLOG_DEBUG, \"prefetch by cache %s, qtype %d, ttl %d, hitnum %d\", dns_cache->info.domain, dns_cache->info.qtype,\n\t\t dns_cache->info.ttl, hitnum);\n\tserver_query_option.dns_group_name = dns_cache_get_dns_group_name(dns_cache);\n\tserver_query_option.server_flags = dns_cache_get_query_flag(dns_cache);\n\tserver_query_option.ecs_enable_flag = 0;\n\tif (_dns_server_prefetch_request(dns_cache->info.domain, dns_cache->info.qtype, &server_query_option,\n\t\t\t\t\t\t\t\t\t PREFETCH_FLAGS_NO_DUALSTACK) != 0) {\n\t\ttlog(TLOG_ERROR, \"prefetch domain %s, qtype %d, failed.\", dns_cache->info.domain, dns_cache->info.qtype);\n\t\treturn DNS_CACHE_TMOUT_ACTION_RETRY;\n\t}\n\n\treturn DNS_CACHE_TMOUT_ACTION_OK;\n}\n\ndns_cache_tmout_action_t _dns_server_prefetch_expired_domain(struct dns_conf_group *conf_group,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t struct dns_cache *dns_cache)\n{\n\ttime_t ttl = _dns_server_expired_cache_ttl(dns_cache, conf_group->dns_serve_expired_ttl);\n\tif (ttl <= 1) {\n\t\treturn DNS_CACHE_TMOUT_ACTION_DEL;\n\t}\n\n\t/* start prefetch domain */\n\ttlog(TLOG_DEBUG,\n\t\t \"expired domain, total %d, prefetch by cache %s, qtype %d, ttl %llu, rcode %d, insert time %llu replace time \"\n\t\t \"%llu\",\n\t\t dns_cache_total_num(), dns_cache->info.domain, dns_cache->info.qtype, (unsigned long long)ttl,\n\t\t dns_cache->info.rcode, (unsigned long long)dns_cache->info.insert_time,\n\t\t (unsigned long long)dns_cache->info.replace_time);\n\n\tstruct dns_server_query_option server_query_option;\n\tserver_query_option.dns_group_name = dns_cache_get_dns_group_name(dns_cache);\n\tserver_query_option.server_flags = dns_cache_get_query_flag(dns_cache);\n\tserver_query_option.ecs_enable_flag = 0;\n\n\tif (_dns_server_prefetch_request(dns_cache->info.domain, dns_cache->info.qtype, &server_query_option,\n\t\t\t\t\t\t\t\t\t PREFETCH_FLAGS_EXPIRED) != 0) {\n\t\ttlog(TLOG_DEBUG, \"prefetch domain %s, qtype %d, failed.\", dns_cache->info.domain, dns_cache->info.qtype);\n\t\treturn DNS_CACHE_TMOUT_ACTION_RETRY;\n\t}\n\n\treturn DNS_CACHE_TMOUT_ACTION_OK;\n}\n"
  },
  {
    "path": "src/dns_server/prefetch.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_PREFETCH_\n#define _DNS_SERVER_PREFETCH_\n\n#include \"dns_server.h\"\n#include \"smartdns/dns_cache.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_prefetch_request(char *domain, dns_type_t qtype, struct dns_server_query_option *server_query_option,\n\t\t\t\t\t\t\t\t int prefetch_flag);\n\ndns_cache_tmout_action_t _dns_server_prefetch_domain(struct dns_conf_group *conf_group, struct dns_cache *dns_cache);\n\ndns_cache_tmout_action_t _dns_server_prefetch_expired_domain(struct dns_conf_group *conf_group,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t struct dns_cache *dns_cache);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/ptr.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ptr.h\"\n#include \"context.h\"\n#include \"dns_server.h\"\n#include \"mdns.h\"\n#include \"request.h\"\n#include \"rules.h\"\n#include \"soa.h\"\n\n#include <ifaddrs.h>\n\nstatic int _dns_server_is_private_address(const unsigned char *addr, int addr_len)\n{\n\tif (addr_len == 4) {\n\t\tif (addr[0] == 10 || (addr[0] == 172 && addr[1] >= 16 && addr[1] <= 31) || (addr[0] == 192 && addr[1] == 168)) {\n\t\t\treturn 0;\n\t\t}\n\t} else if (addr_len == 16) {\n\t\tif (addr[0] == 0xfe && addr[1] == 0x80) {\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\nint _dns_server_get_inet_by_addr(struct sockaddr_storage *localaddr, struct sockaddr_storage *addr, int family)\n{\n\tstruct ifaddrs *ifaddr = NULL;\n\tstruct ifaddrs *ifa = NULL;\n\tchar ethname[16] = {0};\n\n\tif (getifaddrs(&ifaddr) == -1) {\n\t\treturn -1;\n\t}\n\n\tfor (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {\n\t\tif (ifa->ifa_addr == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (localaddr->ss_family != ifa->ifa_addr->sa_family) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (ifa->ifa_addr->sa_family) {\n\t\tcase AF_INET: {\n\t\t\tstruct sockaddr_in *addr_in_1 = NULL;\n\t\t\tstruct sockaddr_in *addr_in_2 = NULL;\n\t\t\taddr_in_1 = (struct sockaddr_in *)ifa->ifa_addr;\n\t\t\taddr_in_2 = (struct sockaddr_in *)localaddr;\n\t\t\tif (memcmp(&(addr_in_1->sin_addr.s_addr), &(addr_in_2->sin_addr.s_addr), 4) != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} break;\n\t\tcase AF_INET6: {\n\t\t\tstruct sockaddr_in6 *addr_in6_1 = NULL;\n\t\t\tstruct sockaddr_in6 *addr_in6_2 = NULL;\n\t\t\taddr_in6_1 = (struct sockaddr_in6 *)ifa->ifa_addr;\n\t\t\taddr_in6_2 = (struct sockaddr_in6 *)localaddr;\n\t\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6_1->sin6_addr)) {\n\t\t\t\tunsigned char *addr1 = addr_in6_1->sin6_addr.s6_addr + 12;\n\t\t\t\tunsigned char *addr2 = addr_in6_2->sin6_addr.s6_addr + 12;\n\t\t\t\tif (memcmp(addr1, addr2, 4) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tunsigned char *addr1 = addr_in6_1->sin6_addr.s6_addr;\n\t\t\t\tunsigned char *addr2 = addr_in6_2->sin6_addr.s6_addr;\n\t\t\t\tif (memcmp(addr1, addr2, 16) != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t} break;\n\t\tdefault:\n\t\t\tcontinue;\n\t\t\tbreak;\n\t\t}\n\n\t\tsafe_strncpy(ethname, ifa->ifa_name, sizeof(ethname));\n\t\tbreak;\n\t}\n\n\tif (ethname[0] == '\\0') {\n\t\tgoto errout;\n\t}\n\n\tfor (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {\n\t\tif (ifa->ifa_addr == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (ifa->ifa_addr->sa_family != family) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(ethname, ifa->ifa_name, sizeof(ethname)) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (family == AF_INET) {\n\t\t\tmemcpy(addr, ifa->ifa_addr, sizeof(struct sockaddr_in));\n\t\t} else if (family == AF_INET6) {\n\t\t\tmemcpy(addr, ifa->ifa_addr, sizeof(struct sockaddr_in6));\n\t\t}\n\n\t\tbreak;\n\t}\n\n\tif (ifa == NULL) {\n\t\tgoto errout;\n\t}\n\n\tfreeifaddrs(ifaddr);\n\treturn 0;\nerrout:\n\tif (ifaddr) {\n\t\tfreeifaddrs(ifaddr);\n\t}\n\n\treturn -1;\n}\n\nstatic int _dns_server_parser_addr_from_apra(const char *arpa, unsigned char *addr, int *addr_len, int max_addr_len)\n{\n\tint high, low;\n\tchar *endptr = NULL;\n\n\tif (arpa == NULL || addr == NULL || addr_len == NULL || max_addr_len < 4) {\n\t\treturn -1;\n\t}\n\n\tint ret = sscanf(arpa, \"%hhu.%hhu.%hhu.%hhu.in-addr.arpa\", &addr[3], &addr[2], &addr[1], &addr[0]);\n\tif (ret == 4 && strstr(arpa, \".in-addr.arpa\") != NULL) {\n\t\t*addr_len = 4;\n\t\treturn 0;\n\t}\n\n\tif (max_addr_len != 16) {\n\t\treturn -1;\n\t}\n\n\tfor (int i = 15; i >= 0; i--) {\n\t\tlow = strtol(arpa, &endptr, 16);\n\t\tif (endptr == NULL || *endptr != '.' || *endptr == '\\0') {\n\t\t\treturn -1;\n\t\t}\n\n\t\tarpa = endptr + 1;\n\t\thigh = strtol(arpa, &endptr, 16);\n\t\tif (endptr == NULL || *endptr != '.' || *endptr == '\\0') {\n\t\t\treturn -1;\n\t\t}\n\n\t\tarpa = endptr + 1;\n\t\taddr[i] = (high << 4) | low;\n\t}\n\n\tif (strstr(arpa, \"ip6.arpa\") == NULL) {\n\t\treturn -1;\n\t}\n\n\t*addr_len = 16;\n\n\treturn 0;\n}\n\nint _dns_server_process_ptr_query(struct dns_request *request)\n{\n\tif (request->qtype != DNS_T_PTR) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_server_process_ptr(request) == 0) {\n\t\treturn 0;\n\t}\n\n\trequest->passthrough = 1;\n\treturn -1;\n}\n\nint _dns_server_process_ptrs(struct dns_request *request)\n{\n\tuint32_t key = 0;\n\tstruct dns_ptr *ptr = NULL;\n\tstruct dns_ptr *ptr_tmp = NULL;\n\tkey = hash_string(request->domain);\n\thash_for_each_possible(dns_ptr_table.ptr, ptr_tmp, node, key)\n\t{\n\t\tif (strncmp(ptr_tmp->ptr_domain, request->domain, DNS_MAX_PTR_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tptr = ptr_tmp;\n\t\tbreak;\n\t}\n\n\tif (ptr == NULL) {\n\t\tgoto errout;\n\t}\n\n\trequest->has_ptr = 1;\n\tsafe_strncpy(request->ptr_hostname, ptr->hostname, DNS_MAX_CNAME_LEN);\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint _dns_server_process_ptr(struct dns_request *request)\n{\n\tif (_dns_server_process_ptrs(request) == 0) {\n\t\tgoto reply_exit;\n\t}\n\n\tif (_dns_server_process_local_ptr(request) == 0) {\n\t\tgoto reply_exit;\n\t}\n\n\treturn -1;\n\nreply_exit:\n\trequest->rcode = DNS_RC_NOERROR;\n\trequest->ip_ttl = _dns_server_get_local_ttl(request);\n\tstruct dns_server_post_context context;\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_reply = 1;\n\tcontext.do_audit = 0;\n\tcontext.do_cache = 1;\n\t_dns_request_post(&context);\n\treturn 0;\n}\n\nint _dns_server_process_local_ptr(struct dns_request *request)\n{\n\tunsigned char ptr_addr[16];\n\tint ptr_addr_len = 0;\n\tint found = 0;\n\tprefix_t prefix;\n\tradix_node_t *node = NULL;\n\tstruct local_addr_cache_item *addr_cache_item = NULL;\n\tstruct dns_nameserver_rule *ptr_nameserver_rule;\n\n\tif (_dns_server_parser_addr_from_apra(request->domain, ptr_addr, &ptr_addr_len, sizeof(ptr_addr)) != 0) {\n\t\t/* Determine if the smartdns service is in effect. */\n\t\tif (strncasecmp(request->domain, \"smartdns\", sizeof(\"smartdns\")) != 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tfound = 1;\n\t\tgoto out;\n\t}\n\n\tif (dns_conf.local_ptr_enable == 0) {\n\t\tgoto out;\n\t}\n\n\tif (prefix_from_blob(ptr_addr, ptr_addr_len, ptr_addr_len * 8, &prefix) == NULL) {\n\t\tgoto out;\n\t}\n\n\tnode = radix_search_best(server.local_addr_cache.addr, &prefix);\n\tif (node == NULL) {\n\t\tgoto out;\n\t}\n\n\tif (node->data == NULL) {\n\t\tgoto out;\n\t}\n\n\taddr_cache_item = node->data;\n\tif (addr_cache_item->mask_len == ptr_addr_len * 8) {\n\t\tfound = 1;\n\t\tgoto out;\n\t}\n\n\tif (dns_conf.mdns_lookup) {\n\t\t_dns_server_set_request_mdns(request);\n\t\tgoto errout;\n\t}\n\nout:\n\tptr_nameserver_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_NAMESERVER);\n\tif (ptr_nameserver_rule != NULL && ptr_nameserver_rule->group_name[0] != 0) {\n\t\tgoto errout;\n\t}\n\n\tif (found == 0 && _dns_server_is_private_address(ptr_addr, ptr_addr_len) == 0) {\n\t\trequest->has_soa = 1;\n\t\t_dns_server_setup_soa(request);\n\t\tgoto clear;\n\t}\n\n\tif (found == 0) {\n\t\tgoto errout;\n\t}\n\n\tchar full_hostname[DNS_MAX_CNAME_LEN];\n\tif (dns_server_get_server_name(full_hostname, sizeof(full_hostname)) != 0) {\n\t\tgoto errout;\n\t}\n\n\trequest->has_ptr = 1;\n\tsafe_strncpy(request->ptr_hostname, full_hostname, DNS_MAX_CNAME_LEN);\nclear:\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint _dns_server_get_local_ttl(struct dns_request *request)\n{\n\tstruct dns_ttl_rule *ttl_rule;\n\n\t/* get domain rule flag */\n\tttl_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_TTL);\n\tif (ttl_rule != NULL) {\n\t\tif (ttl_rule->ttl > 0) {\n\t\t\treturn ttl_rule->ttl;\n\t\t}\n\t}\n\n\tif (dns_conf.local_ttl > 0) {\n\t\treturn dns_conf.local_ttl;\n\t}\n\n\tif (request->conf->dns_rr_ttl > 0) {\n\t\treturn request->conf->dns_rr_ttl;\n\t}\n\n\tif (request->conf->dns_rr_ttl_min > 0) {\n\t\treturn request->conf->dns_rr_ttl_min;\n\t}\n\n\treturn DNS_SERVER_ADDR_TTL;\n}\n"
  },
  {
    "path": "src/dns_server/ptr.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_PTR_\n#define _DNS_SERVER_PTR_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_get_local_ttl(struct dns_request *request);\n\nint _dns_server_process_local_ptr(struct dns_request *request);\n\nint _dns_server_process_ptrs(struct dns_request *request);\n\nint _dns_server_process_ptr(struct dns_request *request);\n\nint _dns_server_get_inet_by_addr(struct sockaddr_storage *localaddr, struct sockaddr_storage *addr, int family);\n\nint _dns_server_process_ptr_query(struct dns_request *request);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/request.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"request.h\"\n#include \"address.h\"\n#include \"connection.h\"\n#include \"context.h\"\n#include \"ddr.h\"\n#include \"dns64.h\"\n#include \"dns_server.h\"\n#include \"dualstack.h\"\n#include \"mdns.h\"\n#include \"neighbor.h\"\n#include \"ptr.h\"\n#include \"request_pending.h\"\n#include \"rules.h\"\n#include \"soa.h\"\n\n#include \"smartdns/dns_plugin.h\"\n#include \"smartdns/dns_stats.h\"\n\nint _dns_server_has_bind_flag(struct dns_request *request, uint32_t flag)\n{\n\tif (request->server_flags & flag) {\n\t\treturn 0;\n\t}\n\n\treturn -1;\n}\n\nstatic int _dns_server_request_complete_with_all_IPs(struct dns_request *request, int with_all_ips)\n{\n\tint ttl = 0;\n\tstruct dns_server_post_context context;\n\n\tif (request->rcode == DNS_RC_SERVFAIL) {\n\t\tttl = DNS_SERVER_FAIL_TTL;\n\t}\n\n\tif (request->ip_ttl == 0) {\n\t\trequest->ip_ttl = ttl;\n\t}\n\n\tif (request->prefetch == 1) {\n\t\treturn 0;\n\t}\n\n\tif (atomic_inc_return(&request->notified) != 1) {\n\t\treturn 0;\n\t}\n\n\tif (request->has_ip != 0 && request->passthrough == 0) {\n\t\trequest->has_soa = 0;\n\t\tif (request->has_ping_result == 0 && request->ip_ttl > DNS_SERVER_TMOUT_TTL) {\n\t\t\trequest->ip_ttl = DNS_SERVER_TMOUT_TTL;\n\t\t}\n\t\tttl = request->ip_ttl;\n\t}\n\n\tif (_dns_server_force_dualstack(request) == 0) {\n\t\tgoto out;\n\t}\n\n\t_dns_server_need_append_mdns_local_cname(request);\n\n\tif (request->has_soa) {\n\t\ttlog(TLOG_INFO, \"result: %s, qtype: %d, SOA\", request->domain, request->qtype);\n\t} else {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\ttlog(TLOG_INFO, \"result: %s, qtype: %d, rtt: %.1f ms, %d.%d.%d.%d\", request->domain, request->qtype,\n\t\t\t\t ((float)request->ping_time) / 10, request->ip_addr[0], request->ip_addr[1], request->ip_addr[2],\n\t\t\t\t request->ip_addr[3]);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\ttlog(TLOG_INFO,\n\t\t\t\t \"result: %s, qtype: %d, rtt: %.1f ms, \"\n\t\t\t\t \"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t\t\t request->domain, request->qtype, ((float)request->ping_time) / 10, request->ip_addr[0],\n\t\t\t\t request->ip_addr[1], request->ip_addr[2], request->ip_addr[3], request->ip_addr[4],\n\t\t\t\t request->ip_addr[5], request->ip_addr[6], request->ip_addr[7], request->ip_addr[8],\n\t\t\t\t request->ip_addr[9], request->ip_addr[10], request->ip_addr[11], request->ip_addr[12],\n\t\t\t\t request->ip_addr[13], request->ip_addr[14], request->ip_addr[15]);\n\t\t}\n\n\t\tif (request->rcode == DNS_RC_SERVFAIL && request->has_ip) {\n\t\t\trequest->rcode = DNS_RC_NOERROR;\n\t\t}\n\t}\n\nout:\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_cache = 1;\n\tcontext.do_ipset = 1;\n\tcontext.do_force_soa = request->dualstack_selection_force_soa | request->force_soa;\n\tcontext.do_audit = 1;\n\tcontext.do_reply = 1;\n\tcontext.reply_ttl = _dns_server_get_reply_ttl(request, ttl);\n\tcontext.skip_notify_count = 1;\n\tcontext.select_all_best_ip = with_all_ips;\n\tcontext.no_release_parent = 1;\n\t_dns_request_post(&context);\n\treturn _dns_server_reply_all_pending_list(request, &context);\n}\n\nint _dns_server_request_complete(struct dns_request *request)\n{\n\treturn _dns_server_request_complete_with_all_IPs(request, 0);\n}\n\nvoid _dns_server_request_remove_all(void)\n{\n\tstruct dns_request *request = NULL;\n\tstruct dns_request *tmp = NULL;\n\tLIST_HEAD(remove_list);\n\n\tpthread_mutex_lock(&server.request_list_lock);\n\tlist_for_each_entry_safe(request, tmp, &server.request_list, list)\n\t{\n\t\tlist_add_tail(&request->check_list, &remove_list);\n\t\t_dns_server_request_get(request);\n\t}\n\tpthread_mutex_unlock(&server.request_list_lock);\n\n\tlist_for_each_entry_safe(request, tmp, &remove_list, check_list)\n\t{\n\t\t_dns_server_request_complete(request);\n\t\t_dns_server_request_release(request);\n\t}\n}\n\nstatic void _dns_server_delete_request(struct dns_request *request)\n{\n\tif (atomic_read(&request->notified) == 0) {\n\t\t_dns_server_request_complete(request);\n\t}\n\n\tif (request->conn) {\n\t\t_dns_server_conn_release(request->conn);\n\t}\n\n\tpthread_mutex_destroy(&request->ip_map_lock);\n\n\tstruct dns_request_https *https_svcb, *tmp_https;\n\tlist_for_each_entry_safe(https_svcb, tmp_https, &request->https_svcb_list, list)\n\t{\n\t\tlist_del(&https_svcb->list);\n\t\tfree(https_svcb);\n\t}\n\n\tstruct dns_request_srv *srv, *tmp_srv;\n\tlist_for_each_entry_safe(srv, tmp_srv, &request->srv_list, list)\n\t{\n\t\tlist_del(&srv->list);\n\t\tfree(srv);\n\t}\n\n\tif (request->original_domain) {\n\t\tfree(request->original_domain);\n\t}\n\n\tmemset(request, 0, sizeof(*request));\n\tfree(request);\n\tatomic_dec(&server.request_num);\n}\n\nstatic void _dns_server_complete_with_multi_ipaddress(struct dns_request *request)\n{\n\tstruct dns_server_post_context context;\n\tint do_reply = 0;\n\n\tif (atomic_read(&request->ip_map_num) > 0) {\n\t\trequest->has_soa = 0;\n\t}\n\n\tif (atomic_inc_return(&request->notified) == 1) {\n\t\tdo_reply = 1;\n\t\t_dns_server_force_dualstack(request);\n\t}\n\n\tif (request->passthrough && do_reply == 0) {\n\t\treturn;\n\t}\n\n\t_dns_server_need_append_mdns_local_cname(request);\n\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_cache = 1;\n\tcontext.do_ipset = 1;\n\tcontext.do_reply = do_reply;\n\tcontext.do_log_result = 1;\n\tcontext.select_all_best_ip = 1;\n\tcontext.skip_notify_count = 1;\n\tcontext.do_force_soa = request->dualstack_selection_force_soa | request->force_soa;\n\t_dns_request_post(&context);\n\t_dns_server_reply_all_pending_list(request, &context);\n}\n\nvoid _dns_server_request_release_complete(struct dns_request *request, int do_complete)\n{\n\tstruct dns_ip_address *addr_map = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long bucket = 0;\n\n\tpthread_mutex_lock(&server.request_list_lock);\n\tint refcnt = atomic_dec_return(&request->refcnt);\n\tif (refcnt) {\n\t\tpthread_mutex_unlock(&server.request_list_lock);\n\t\tif (refcnt < 0) {\n\t\t\tBUG(\"BUG: refcnt is %d, domain %s, qtype %d\", refcnt, request->domain, request->qtype);\n\t\t}\n\t\treturn;\n\t}\n\n\tlist_del_init(&request->list);\n\tlist_del_init(&request->check_list);\n\tpthread_mutex_unlock(&server.request_list_lock);\n\n\tpthread_mutex_lock(&server.request_pending_lock);\n\tlist_del_init(&request->pending_list);\n\tpthread_mutex_unlock(&server.request_pending_lock);\n\n\tif (do_complete && atomic_read(&request->plugin_complete_called) == 0) {\n\t\t/* Select max hit ip address, and return to client */\n\t\t_dns_server_select_possible_ipaddress(request);\n\t\t_dns_server_complete_with_multi_ipaddress(request);\n\t}\n\n\tif (request->parent_request != NULL) {\n\t\t_dns_server_request_release(request->parent_request);\n\t\trequest->parent_request = NULL;\n\t}\n\n\tatomic_inc(&request->refcnt);\n\tif (atomic_inc_return(&request->plugin_complete_called) == 1) {\n\t\tsmartdns_plugin_func_server_complete_request(request);\n\t}\n\n\tif (atomic_dec_return(&request->refcnt) > 0) {\n\t\t/* plugin may hold request. */\n\t\treturn;\n\t}\n\n\tpthread_mutex_lock(&request->ip_map_lock);\n\thash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)\n\t{\n\t\thash_del(&addr_map->node);\n\t\tfree(addr_map);\n\t}\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\tif (request->rcode == DNS_RC_NOERROR) {\n\t\tstats_inc(&dns_stats.request.success_count);\n\t}\n\n\tif (request->conn) {\n\t\tdns_stats_avg_time_add(request->query_time);\n\t}\n\t_dns_server_delete_request(request);\n}\n\nvoid _dns_server_request_release(struct dns_request *request)\n{\n\t_dns_server_request_release_complete(request, 1);\n}\n\nvoid _dns_server_request_get(struct dns_request *request)\n{\n\tif (atomic_inc_return(&request->refcnt) <= 0) {\n\t\tBUG(\"BUG: request ref is invalid, %s\", request->domain);\n\t}\n}\n\nconst struct sockaddr *dns_server_request_get_remote_addr(struct dns_request *request)\n{\n\tif (request->conn == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn &request->addr;\n}\n\nconst struct sockaddr *dns_server_request_get_local_addr(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn (struct sockaddr *)&request->localaddr;\n}\n\nconst uint8_t *dns_server_request_get_remote_mac(struct dns_request *request)\n{\n\tif (request->conn == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn request->mac;\n};\n\nconst char *dns_server_request_get_group_name(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn request->dns_group_name;\n}\n\nconst char *dns_server_request_get_domain(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn request->domain;\n}\n\nint dns_server_request_get_qtype(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn request->qtype;\n}\n\nint dns_server_request_get_qclass(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn request->qclass;\n}\n\nint dns_server_request_get_query_time(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn request->query_time;\n}\n\nuint64_t dns_server_request_get_query_timestamp(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn request->query_timestamp;\n}\n\nfloat dns_server_request_get_ping_time(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn (float)request->ping_time / 10;\n}\n\nint dns_server_request_is_prefetch(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn request->prefetch;\n}\n\nint dns_server_request_is_dualstack(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn request->dualstack_selection_query;\n}\n\nint dns_server_request_is_blocked(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (request->qtype == DNS_T_HTTPS || request->qtype == DNS_T_SVCB) {\n\t\tuint32_t flags = _dns_server_get_rule_flags(request);\n\t\tif (flags & DOMAIN_FLAG_ADDR_HTTPS_SOA) {\n\t\t\treturn 1;\n\t\t}\n\t\treturn 0;\n\t}\n\n\treturn _dns_server_is_return_soa(request);\n}\n\nint dns_server_request_is_cached(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn request->is_cache_reply;\n}\n\nint dns_server_request_get_id(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn request->id;\n}\n\nint dns_server_request_get_rcode(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn DNS_RC_SERVFAIL;\n\t}\n\n\treturn request->rcode;\n}\n\nvoid dns_server_request_get(struct dns_request *request)\n{\n\t_dns_server_request_get(request);\n}\n\nvoid dns_server_request_put(struct dns_request *request)\n{\n\t_dns_server_request_release(request);\n}\n\nvoid dns_server_request_set_private(struct dns_request *request, void *private_data)\n{\n\tif (request == NULL) {\n\t\treturn;\n\t}\n\n\trequest->private_data = private_data;\n}\n\nvoid *dns_server_request_get_private(struct dns_request *request)\n{\n\tif (request == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn request->private_data;\n}\n\nstruct dns_request *_dns_server_new_request(void)\n{\n\tstruct dns_request *request = NULL;\n\n\trequest = zalloc(1, sizeof(*request));\n\tif (request == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc request failed.\\n\");\n\t\tgoto errout;\n\t}\n\tpthread_mutex_init(&request->ip_map_lock, NULL);\n\tatomic_set(&request->adblock, 0);\n\tatomic_set(&request->soa_num, 0);\n\tatomic_set(&request->ip_map_num, 0);\n\tatomic_set(&request->refcnt, 0);\n\tatomic_set(&request->notified, 0);\n\tatomic_set(&request->do_callback, 0);\n\tatomic_set(&request->plugin_complete_called, 0);\n\trequest->ping_time = -1;\n\trequest->prefetch = 0;\n\trequest->dualstack_selection = 0;\n\trequest->dualstack_selection_ping_time = -1;\n\trequest->rcode = DNS_RC_SERVFAIL;\n\trequest->conn = NULL;\n\trequest->qclass = DNS_C_IN;\n\trequest->result_callback = NULL;\n\trequest->conf = dns_server_get_default_rule_group();\n\trequest->check_order_list = &dns_conf.default_check_orders;\n\trequest->response_mode = dns_conf.default_response_mode;\n\trequest->query_timestamp = get_utc_time_ms();\n\tINIT_LIST_HEAD(&request->list);\n\tINIT_LIST_HEAD(&request->pending_list);\n\tINIT_LIST_HEAD(&request->check_list);\n\tINIT_LIST_HEAD(&request->https_svcb_list);\n\tINIT_LIST_HEAD(&request->srv_list);\n\thash_init(request->ip_map);\n\t_dns_server_request_get(request);\n\tatomic_add(1, &server.request_num);\n\tstats_inc(&dns_stats.request.total);\n\n\treturn request;\nerrout:\n\treturn NULL;\n}\n\nvoid _dns_server_query_end(struct dns_request *request)\n{\n\tint ip_num = 0;\n\tint request_wait = 0;\n\tstruct dns_conf_group *conf = request->conf;\n\n\t/* if mdns request timeout */\n\tif (request->is_mdns_lookup == 1 && request->rcode == DNS_RC_SERVFAIL) {\n\t\trequest->rcode = DNS_RC_NOERROR;\n\t\trequest->force_soa = 1;\n\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, DNS_SERVER_ADDR_TTL);\n\t}\n\n\tpthread_mutex_lock(&request->ip_map_lock);\n\tip_num = atomic_read(&request->ip_map_num);\n\trequest_wait = request->request_wait;\n\trequest->request_wait--;\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\t/* Not need to wait check result if only has one ip address */\n\tif (ip_num <= 1 && request_wait == 1) {\n\t\tif (request->dualstack_selection_query == 1) {\n\t\t\tif ((conf->ipset_nftset.ipset_no_speed.ipv4_enable || conf->ipset_nftset.nftset_no_speed.ip_enable ||\n\t\t\t\t conf->ipset_nftset.ipset_no_speed.ipv6_enable || conf->ipset_nftset.nftset_no_speed.ip6_enable) &&\n\t\t\t\trequest->conf->dns_dns64.prefix_len == 0) {\n\t\t\t\t/* if speed check fail enabled, we need reply quickly, otherwise wait for ping result.*/\n\t\t\t\t_dns_server_request_complete(request);\n\t\t\t}\n\t\t\tgoto out;\n\t\t}\n\n\t\tif (request->dualstack_selection_has_ip && request->dualstack_selection_ping_time > 0) {\n\t\t\tgoto out;\n\t\t}\n\n\t\trequest->has_ping_result = 1;\n\t\t_dns_server_request_complete(request);\n\t}\n\nout:\n\t_dns_server_request_release(request);\n}\n\nvoid _dns_server_passthrough_may_complete(struct dns_request *request)\n{\n\tconst unsigned char *addr;\n\tif (request->passthrough != 2) {\n\t\treturn;\n\t}\n\n\tif (request->has_ip == 0 && request->has_soa == 0) {\n\t\treturn;\n\t}\n\n\tif (request->qtype == DNS_T_A && request->has_ip == 1) {\n\t\t/* Ad blocking result */\n\t\taddr = request->ip_addr;\n\t\tif (addr[0] == 0 || addr[0] == 127) {\n\t\t\t/* If half of the servers return the same result, then ignore this address */\n\t\t\tif (atomic_read(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (request->qtype == DNS_T_AAAA && request->has_ip == 1) {\n\t\taddr = request->ip_addr;\n\t\tif (_dns_server_is_adblock_ipv6(addr) == 0) {\n\t\t\t/* If half of the servers return the same result, then ignore this address */\n\t\t\tif (atomic_read(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t_dns_server_request_complete_with_all_IPs(request, 1);\n}\n\nstatic int _dns_server_reply_request_eth_ip(struct dns_request *request)\n{\n\tstruct sockaddr_in *addr_in = NULL;\n\tstruct sockaddr_in6 *addr_in6 = NULL;\n\tstruct sockaddr_storage *localaddr = NULL;\n\tstruct sockaddr_storage localaddr_buff;\n\n\tlocaladdr = &request->localaddr;\n\n\t/* address /domain/ rule */\n\tswitch (request->qtype) {\n\tcase DNS_T_A:\n\t\tif (localaddr->ss_family != AF_INET) {\n\t\t\tif (_dns_server_get_inet_by_addr(localaddr, &localaddr_buff, AF_INET) != 0) {\n\t\t\t\t_dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tlocaladdr = &localaddr_buff;\n\t\t}\n\t\taddr_in = (struct sockaddr_in *)localaddr;\n\t\tmemcpy(request->ip_addr, &addr_in->sin_addr.s_addr, DNS_RR_A_LEN);\n\t\tbreak;\n\tcase DNS_T_AAAA:\n\t\tif (localaddr->ss_family != AF_INET6) {\n\t\t\tif (_dns_server_get_inet_by_addr(localaddr, &localaddr_buff, AF_INET6) != 0) {\n\t\t\t\t_dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tlocaladdr = &localaddr_buff;\n\t\t}\n\t\taddr_in6 = (struct sockaddr_in6 *)localaddr;\n\t\tmemcpy(request->ip_addr, &addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN);\n\t\tbreak;\n\tdefault:\n\t\tgoto out;\n\t\tbreak;\n\t}\n\n\trequest->rcode = DNS_RC_NOERROR;\n\trequest->ip_ttl = dns_conf.local_ttl;\n\trequest->has_ip = 1;\n\n\tstruct dns_server_post_context context;\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_reply = 1;\n\t_dns_request_post(&context);\n\n\treturn 0;\nout:\n\treturn -1;\n}\n\nvoid _dns_server_set_request_mdns(struct dns_request *request)\n{\n\tif (dns_conf.mdns_lookup != 1) {\n\t\treturn;\n\t}\n\n\trequest->is_mdns_lookup = 1;\n}\n\nstatic int _dns_server_process_local_SOA(struct dns_request *request)\n{\n\tstruct dns_soa *soa = NULL;\n\tchar *mname = \"ns.local\";\n\tchar *rname = \"admin.local\";\n\n\tif (strncasecmp(\"local\", request->domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\tmname = \"ns.lan\";\n\t\trname = \"admin.lan\";\n\t\tif (strncasecmp(\"lan\", request->domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tsoa = &request->soa;\n\n\tsafe_strncpy(soa->mname, mname, DNS_MAX_CNAME_LEN);\n\tsafe_strncpy(soa->rname, rname, DNS_MAX_CNAME_LEN);\n\tsoa->serial = 1;\n\tsoa->refresh = 3600;\n\tsoa->retry = 900;\n\tsoa->expire = 604800;\n\tsoa->minimum = 86400;\n\n\treturn _dns_server_reply_SOA_ext(DNS_RC_NOERROR, request);\n}\n\nint _dns_server_process_srv(struct dns_request *request)\n{\n\tstruct dns_srv_record *srv_record;\n\tstruct dns_request_srv *srv;\n\tstruct dns_srv_record_rule *srv_rule =\n\t\t(struct dns_srv_record_rule *)_dns_server_get_dns_rule(request, DOMAIN_RULE_SRV);\n\tif (srv_rule == NULL) {\n\t\treturn -1;\n\t}\n\n\trequest->rcode = DNS_RC_NOERROR;\n\trequest->ip_ttl = _dns_server_get_local_ttl(request);\n\n\tlist_for_each_entry(srv_record, &srv_rule->record_list, list)\n\t{\n\t\tsrv = zalloc(1, sizeof(*srv));\n\t\tif (srv == NULL) {\n\t\t\tcontinue;\n\t\t}\n\t\tsafe_strncpy(srv->host, srv_record->host, sizeof(srv->host));\n\t\tsrv->priority = srv_record->priority;\n\t\tsrv->weight = srv_record->weight;\n\t\tsrv->port = srv_record->port;\n\t\tlist_add_tail(&srv->list, &request->srv_list);\n\t}\n\n\tstruct dns_server_post_context context;\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_audit = 1;\n\tcontext.do_reply = 1;\n\tcontext.do_cache = 0;\n\tcontext.do_force_soa = 0;\n\t_dns_request_post(&context);\n\n\treturn 0;\n}\n\nint _dns_server_process_svcb(struct dns_request *request)\n{\n\tif (strncasecmp(\"_dns.resolver.arpa\", request->domain, DNS_MAX_CNAME_LEN) == 0) {\n\t\treturn _dns_server_process_DDR(request);\n\t}\n\n\treturn -1;\n}\n\nint _dns_server_pre_process_server_flags(struct dns_request *request)\n{\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_CACHE) == 0) {\n\t\trequest->no_cache = 1;\n\t}\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_IP_ALIAS) == 0) {\n\t\trequest->no_ipalias = 1;\n\t}\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_PREFETCH) == 0) {\n\t\trequest->prefetch_flags |= PREFETCH_FLAGS_NOPREFETCH;\n\t}\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_SERVE_EXPIRED) == 0) {\n\t\trequest->no_serve_expired = 1;\n\t}\n\n\tif (request->qtype == DNS_T_HTTPS && _dns_server_has_bind_flag(request, BIND_FLAG_FORCE_HTTPS_SOA) == 0) {\n\t\t_dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t\treturn 0;\n\t}\n\n\treturn -1;\n}\n\nstruct dns_request *_dns_server_new_child_request(struct dns_request *request, const char *domain, dns_type_t qtype,\n\t\t\t\t\t\t\t\t\t\t\t\t  child_request_callback child_callback)\n{\n\tstruct dns_request *child_request = NULL;\n\n\tchild_request = _dns_server_new_request();\n\tif (child_request == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tchild_request->server_flags = request->server_flags;\n\tsafe_strncpy(child_request->dns_group_name, request->dns_group_name, sizeof(request->dns_group_name));\n\tsafe_strncpy(child_request->domain, domain, sizeof(child_request->domain));\n\tchild_request->prefetch = request->prefetch;\n\tchild_request->prefetch_flags = request->prefetch_flags;\n\tchild_request->child_callback = child_callback;\n\tchild_request->parent_request = request;\n\tchild_request->qtype = qtype;\n\tchild_request->qclass = request->qclass;\n\tchild_request->conf = request->conf;\n\n\tif (request->has_ecs) {\n\t\tmemcpy(&child_request->ecs, &request->ecs, sizeof(child_request->ecs));\n\t\tchild_request->has_ecs = request->has_ecs;\n\t}\n\t_dns_server_request_get(request);\n\t/* reference count is 1 hold by parent request */\n\trequest->child_request = child_request;\n\t_dns_server_get_domain_rule(child_request);\n\treturn child_request;\nerrout:\n\tif (child_request) {\n\t\t_dns_server_request_release(child_request);\n\t}\n\n\treturn NULL;\n}\n\nint _dns_server_request_copy(struct dns_request *request, struct dns_request *from)\n{\n\tunsigned long bucket = 0;\n\tstruct dns_ip_address *addr_map = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tuint32_t key = 0;\n\tint addr_len = 0;\n\n\trequest->rcode = from->rcode;\n\n\tif (from->has_ip) {\n\t\trequest->has_ip = 1;\n\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, from->ip_ttl);\n\t\trequest->ping_time = from->ping_time;\n\t\tmemcpy(request->ip_addr, from->ip_addr, sizeof(request->ip_addr));\n\t}\n\n\tif (from->has_cname) {\n\t\trequest->has_cname = 1;\n\t\trequest->ttl_cname = from->ttl_cname;\n\t\tsafe_strncpy(request->cname, from->cname, sizeof(request->cname));\n\t}\n\n\tif (from->has_soa) {\n\t\trequest->has_soa = 1;\n\t\tmemcpy(&request->soa, &from->soa, sizeof(request->soa));\n\t}\n\n\tpthread_mutex_lock(&request->ip_map_lock);\n\thash_for_each_safe(request->ip_map, bucket, tmp, addr_map, node)\n\t{\n\t\thash_del(&addr_map->node);\n\t\tfree(addr_map);\n\t}\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\tpthread_mutex_lock(&from->ip_map_lock);\n\thash_for_each_safe(from->ip_map, bucket, tmp, addr_map, node)\n\t{\n\t\tstruct dns_ip_address *new_addr_map = NULL;\n\n\t\tif (addr_map->addr_type == DNS_T_A) {\n\t\t\taddr_len = DNS_RR_A_LEN;\n\t\t} else if (addr_map->addr_type == DNS_T_AAAA) {\n\t\t\taddr_len = DNS_RR_AAAA_LEN;\n\t\t} else {\n\t\t\tcontinue;\n\t\t}\n\n\t\tnew_addr_map = malloc(sizeof(struct dns_ip_address));\n\t\tif (new_addr_map == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\t\tpthread_mutex_unlock(&from->ip_map_lock);\n\t\t\treturn -1;\n\t\t}\n\n\t\tmemcpy(new_addr_map, addr_map, sizeof(struct dns_ip_address));\n\t\tnew_addr_map->ping_time = addr_map->ping_time;\n\t\tkey = jhash(new_addr_map->ip_addr, addr_len, 0);\n\t\tkey = jhash(&addr_map->addr_type, sizeof(addr_map->addr_type), key);\n\t\tpthread_mutex_lock(&request->ip_map_lock);\n\t\thash_add(request->ip_map, &new_addr_map->node, key);\n\t\tpthread_mutex_unlock(&request->ip_map_lock);\n\t}\n\tpthread_mutex_unlock(&from->ip_map_lock);\n\n\treturn 0;\n}\n\nconst char *_dns_server_get_request_server_groupname(struct dns_request *request)\n{\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_NAMESERVER) == 0) {\n\t\treturn NULL;\n\t}\n\n\t/* Get the nameserver rule */\n\tif (request->domain_rule.rules[DOMAIN_RULE_NAMESERVER]) {\n\t\tstruct dns_nameserver_rule *nameserver_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_NAMESERVER);\n\t\treturn nameserver_rule->group_name;\n\t}\n\n\treturn NULL;\n}\n\nint _dns_server_get_expired_ttl_reply(struct dns_request *request, struct dns_cache *dns_cache)\n{\n\tint ttl = dns_cache_get_ttl(dns_cache);\n\tif (ttl > 0) {\n\t\treturn ttl;\n\t}\n\n\treturn request->conf->dns_serve_expired_reply_ttl;\n}\n\nint _dns_server_process_https_svcb(struct dns_request *request)\n{\n\tstruct dns_https_record_rule *https_record_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_HTTPS);\n\tstruct dns_https_record *record;\n\tstruct dns_request_https *svcb;\n\n\tif (request->qtype != DNS_T_HTTPS) {\n\t\treturn 0;\n\t}\n\n\tif (!list_empty(&request->https_svcb_list)) {\n\t\treturn 0;\n\t}\n\n\tif (https_record_rule == NULL) {\n\t\treturn 0;\n\t}\n\n\tlist_for_each_entry(record, &https_record_rule->record_list, list)\n\t{\n\t\tif (record->enable == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tsvcb = zalloc(1, sizeof(*svcb));\n\t\tif (svcb == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tsafe_strncpy(svcb->domain, request->domain, sizeof(svcb->domain));\n\t\tsafe_strncpy(svcb->target, record->target, sizeof(svcb->target));\n\t\tsvcb->priority = record->priority;\n\t\tsvcb->port = record->port;\n\t\tmemcpy(svcb->ech, record->ech, record->ech_len);\n\t\tsvcb->ech_len = record->ech_len;\n\t\tmemcpy(svcb->alpn, record->alpn, sizeof(svcb->alpn));\n\t\tsvcb->alpn_len = record->alpn_len;\n\n\t\tif (record->has_ipv4) {\n\t\t\tsvcb->has_ipv4 = 1;\n\t\t\tmemcpy(svcb->ipv4_addr, record->ipv4_addr, sizeof(svcb->ipv4_addr));\n\t\t\trequest->has_ip = 1;\n\t\t}\n\n\t\tif (record->has_ipv6) {\n\t\t\tsvcb->has_ipv6 = 1;\n\t\t\tmemcpy(svcb->ipv6_addr, record->ipv6_addr, sizeof(svcb->ipv6_addr));\n\t\t\trequest->has_ip = 1;\n\t\t}\n\n\t\tlist_add_tail(&svcb->list, &request->https_svcb_list);\n\t}\n\n\tif (!list_empty(&request->https_svcb_list)) {\n\t\trequest->rcode = DNS_RC_NOERROR;\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_server_request_set_client(struct dns_request *request, struct dns_server_conn_head *conn)\n{\n\trequest->conn = conn;\n\trequest->server_flags = conn->server_flags;\n\t_dns_server_conn_get(conn);\n}\n\nvoid _dns_server_request_set_id(struct dns_request *request, unsigned short id)\n{\n\trequest->id = id;\n}\n\nvoid _dns_server_request_set_mac(struct dns_request *request, struct sockaddr_storage *from, socklen_t from_len)\n{\n\tuint8_t netaddr[DNS_RR_AAAA_LEN] = {0};\n\tint netaddr_len = sizeof(netaddr);\n\n\tif (get_raw_addr_by_sockaddr(from, from_len, netaddr, &netaddr_len) != 0) {\n\t\treturn;\n\t}\n\n\tstruct neighbor_cache_item *item = _dns_server_neighbor_cache_get_item(netaddr, netaddr_len);\n\tif (item) {\n\t\tif (item->has_mac) {\n\t\t\tmemcpy(request->mac, item->mac, 6);\n\t\t}\n\t}\n}\n\nint _dns_server_request_set_client_addr(struct dns_request *request, struct sockaddr_storage *from, socklen_t from_len)\n{\n\tswitch (from->ss_family) {\n\tcase AF_INET:\n\t\tmemcpy(&request->in, from, from_len);\n\t\trequest->addr_len = from_len;\n\t\tbreak;\n\tcase AF_INET6:\n\t\tmemcpy(&request->in6, from, from_len);\n\t\trequest->addr_len = from_len;\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t\tbreak;\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_server_request_set_callback(struct dns_request *request, dns_result_callback callback, void *user_ptr)\n{\n\trequest->result_callback = callback;\n\trequest->user_ptr = user_ptr;\n}\n\nint _dns_server_process_smartdns_domain(struct dns_request *request)\n{\n\tuint32_t flags = _dns_server_get_rule_flags(request);\n\n\tif (_dns_server_is_dns_rule_extract_match(request, DOMAIN_RULE_FLAGS) == 0) {\n\t\treturn -1;\n\t}\n\n\tif (!(flags & DOMAIN_FLAG_SMARTDNS_DOMAIN)) {\n\t\treturn -1;\n\t}\n\n\treturn _dns_server_reply_request_eth_ip(request);\n}\n\nint _dns_server_process_special_query(struct dns_request *request)\n{\n\tint ret = 0;\n\n\tswitch (request->qtype) {\n\tcase DNS_T_PTR:\n\t\tbreak;\n\tcase DNS_T_SOA:\n\t\tret = _dns_server_process_local_SOA(request);\n\t\tif (ret == 0) {\n\t\t\tgoto clean_exit;\n\t\t} else {\n\t\t\t/* pass to upstream server */\n\t\t\trequest->passthrough = 1;\n\t\t}\n\t\tbreak;\n\tcase DNS_T_SRV:\n\t\tret = _dns_server_process_srv(request);\n\t\tif (ret == 0) {\n\t\t\tgoto clean_exit;\n\t\t} else {\n\t\t\t/* pass to upstream server */\n\t\t\trequest->passthrough = 1;\n\t\t}\n\tcase DNS_T_HTTPS:\n\t\tbreak;\n\tcase DNS_T_SVCB:\n\t\tret = _dns_server_process_svcb(request);\n\t\tif (ret == 0) {\n\t\t\tgoto clean_exit;\n\t\t} else {\n\t\t\t/* pass to upstream server */\n\t\t\trequest->passthrough = 1;\n\t\t}\n\t\tbreak;\n\tcase DNS_T_A:\n\t\tbreak;\n\tcase DNS_T_AAAA:\n\t\tbreak;\n\tdefault:\n\t\ttlog(TLOG_DEBUG, \"unsupported qtype: %d, domain: %s\", request->qtype, request->domain);\n\t\trequest->passthrough = 1;\n\t\t/* pass request to upstream server */\n\t\tbreak;\n\t}\n\n\treturn -1;\nclean_exit:\n\treturn 0;\n}\n\nvoid _dns_server_check_set_passthrough(struct dns_request *request)\n{\n\tif (request->check_order_list->orders[0].type == DOMAIN_CHECK_NONE) {\n\t\trequest->passthrough = 1;\n\t}\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_SPEED_CHECK) == 0) {\n\t\trequest->passthrough = 1;\n\t}\n\n\tif (is_ipv6_ready == 0 && request->qtype == DNS_T_AAAA) {\n\t\trequest->passthrough = 1;\n\t}\n\n\tif (request->passthrough == 1) {\n\t\trequest->dualstack_selection = 0;\n\t}\n\n\tif (request->passthrough == 1 &&\n\t\t(request->qtype == DNS_T_A || request->qtype == DNS_T_AAAA || request->qtype == DNS_T_HTTPS) &&\n\t\trequest->edns0_do == 0) {\n\t\trequest->passthrough = 2;\n\t}\n}\n\nint _dns_server_process_host(struct dns_request *request)\n{\n\tuint32_t key = 0;\n\tstruct dns_hosts *host = NULL;\n\tstruct dns_hosts *host_tmp = NULL;\n\tint dns_type = request->qtype;\n\n\tif (dns_hosts_record_num <= 0) {\n\t\treturn -1;\n\t}\n\n\tkey = hash_string_case(request->domain);\n\tkey = jhash(&dns_type, sizeof(dns_type), key);\n\thash_for_each_possible(dns_hosts_table.hosts, host_tmp, node, key)\n\t{\n\t\tif (host_tmp->dns_type != dns_type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncasecmp(host_tmp->domain, request->domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\thost = host_tmp;\n\t\tbreak;\n\t}\n\n\tif (host == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (host->is_soa) {\n\t\trequest->has_soa = 1;\n\t\treturn _dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\t}\n\n\tswitch (request->qtype) {\n\tcase DNS_T_A:\n\t\tmemcpy(request->ip_addr, host->ipv4_addr, DNS_RR_A_LEN);\n\t\tbreak;\n\tcase DNS_T_AAAA:\n\t\tmemcpy(request->ip_addr, host->ipv6_addr, DNS_RR_AAAA_LEN);\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\trequest->rcode = DNS_RC_NOERROR;\n\trequest->ip_ttl = dns_conf.local_ttl;\n\trequest->has_ip = 1;\n\n\tstruct dns_server_post_context context;\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_reply = 1;\n\tcontext.do_audit = 1;\n\t_dns_request_post(&context);\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint _dns_server_setup_query_option(struct dns_request *request, struct dns_query_options *options)\n{\n\toptions->enable_flag = 0;\n\n\tif (request->has_ecs) {\n\t\tmemcpy(&options->ecs_dns, &request->ecs, sizeof(options->ecs_dns));\n\t\toptions->enable_flag |= DNS_QUEY_OPTION_ECS_DNS;\n\t}\n\n\tif (request->edns0_do) {\n\t\toptions->enable_flag |= DNS_QUEY_OPTION_EDNS0_DO;\n\t}\n\toptions->conf_group_name = request->dns_group_name;\n\treturn 0;\n}\n\nint _dns_server_setup_request_conf_pre(struct dns_request *request)\n{\n\tstruct dns_conf_group *rule_group = NULL;\n\tstruct dns_request_domain_rule domain_rule;\n\n\tif (request->skip_domain_rule != 0 && request->conf) {\n\t\treturn 0;\n\t}\n\n\tif (request->conn && request->conn->dns_group != NULL && request->dns_group_name[0] == '\\0') {\n\t\tsafe_strncpy(request->dns_group_name, request->conn->dns_group, sizeof(request->dns_group_name));\n\t}\n\n\trule_group = dns_server_get_rule_group(request->dns_group_name);\n\tif (rule_group == NULL) {\n\t\treturn -1;\n\t}\n\n\trequest->conf = rule_group;\n\tmemset(&domain_rule, 0, sizeof(domain_rule));\n\t_dns_server_get_domain_rule_by_domain_ext(rule_group, &domain_rule, DOMAIN_RULE_GROUP, request->domain, 1);\n\tif (domain_rule.rules[DOMAIN_RULE_GROUP] == NULL) {\n\t\treturn 0;\n\t}\n\n\tstruct dns_group_rule *group_rule = _dns_server_get_dns_rule_ext(&domain_rule, DOMAIN_RULE_GROUP);\n\tif (group_rule == NULL) {\n\t\treturn 0;\n\t}\n\n\trule_group = dns_server_get_rule_group(group_rule->group_name);\n\tif (rule_group == NULL) {\n\t\treturn 0;\n\t}\n\n\trequest->conf = rule_group;\n\tsafe_strncpy(request->dns_group_name, rule_group->group_name, sizeof(request->dns_group_name));\n\ttlog(TLOG_DEBUG, \"domain %s match group %s\", request->domain, rule_group->group_name);\n\n\treturn 0;\n}\n\nint _dns_server_setup_request_conf(struct dns_request *request)\n{\n\tstruct dns_conf_group *rule_group = NULL;\n\n\trule_group = dns_server_get_rule_group(request->dns_group_name);\n\tif (rule_group == NULL) {\n\t\treturn -1;\n\t}\n\n\trequest->conf = rule_group;\n\trequest->check_order_list = &rule_group->check_orders;\n\n\treturn 0;\n}\n\nvoid _dns_server_setup_dns_group_name(struct dns_request *request, const char **server_group_name)\n{\n\tconst char *group_name = NULL;\n\tconst char *temp_group_name = NULL;\n\n\ttemp_group_name = _dns_server_get_request_server_groupname(request);\n\tif (temp_group_name != NULL) {\n\t\tgroup_name = temp_group_name;\n\t}\n\n\tif (request->dns_group_name[0] != '\\0' && group_name == NULL) {\n\t\tgroup_name = request->dns_group_name;\n\t} else {\n\t\tsafe_strncpy(request->dns_group_name, group_name, sizeof(request->dns_group_name));\n\t}\n\n\t*server_group_name = group_name;\n}\n\nint _dns_server_check_request_supported(struct dns_request *request, struct dns_packet *packet)\n{\n\tif (request->qclass != DNS_C_IN) {\n\t\treturn -1;\n\t}\n\n\tif (packet->head.opcode != DNS_OP_QUERY) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_parser_request(struct dns_request *request, struct dns_packet *packet)\n{\n\tstruct dns_rrs *rrs = NULL;\n\tint rr_count = 0;\n\tint i = 0;\n\tint ret = 0;\n\tint qclass = 0;\n\tint qtype = DNS_T_ALL;\n\tchar domain[DNS_MAX_CNAME_LEN];\n\n\tif (packet->head.qr != DNS_QR_QUERY) {\n\t\tgoto errout;\n\t}\n\n\t/* get request domain and request qtype */\n\trrs = dns_get_rrs_start(packet, DNS_RRS_QD, &rr_count);\n\tif (rr_count > 1 || rr_count <= 0) {\n\t\tgoto errout;\n\t}\n\n\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\tret = dns_get_domain(rrs, domain, sizeof(domain), &qtype, &qclass);\n\t\tif (ret != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\t// Only support one question.\n\t\tint case_changed = 0;\n\t\tsafe_strncpy_lower(request->domain, domain, sizeof(request->domain), &case_changed);\n\n\t\t/* support draft dns0x20 */\n\t\tif (case_changed) {\n\t\t\trequest->original_domain = malloc(DNS_MAX_CNAME_LEN);\n\t\t\tif (request->original_domain == NULL) {\n\t\t\t\ttlog(TLOG_ERROR, \"malloc failed.\\n\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tsafe_strncpy(request->original_domain, domain, DNS_MAX_CNAME_LEN);\n\t\t\ttlog(TLOG_DEBUG, \"query %s by origin domain %s\", request->domain, request->original_domain);\n\t\t}\n\t\trequest->qtype = qtype;\n\t\tbreak;\n\t}\n\n\trequest->qclass = qclass;\n\tif (_dns_server_check_request_supported(request, packet) != 0) {\n\t\tgoto errout;\n\t}\n\n\tif ((dns_get_OPT_option(packet) & DNS_OPT_FLAG_DO) && packet->head.ad == 1) {\n\t\trequest->edns0_do = 1;\n\t}\n\n\t/* get request opts */\n\trr_count = 0;\n\trrs = dns_get_rrs_start(packet, DNS_RRS_OPT, &rr_count);\n\tif (rr_count <= 0) {\n\t\treturn 0;\n\t}\n\n\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\tswitch (rrs->type) {\n\t\tcase DNS_OPT_T_TCP_KEEPALIVE: {\n\t\t\tunsigned short idle_timeout = 0;\n\t\t\tret = dns_get_OPT_TCP_KEEPALIVE(rrs, &idle_timeout);\n\t\t\tif (idle_timeout == 0 || ret != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttlog(TLOG_DEBUG, \"set tcp connection timeout to %u\", idle_timeout);\n\t\t\t_dns_server_update_request_connection_timeout(request->conn, idle_timeout / 10);\n\t\t} break;\n\t\tcase DNS_OPT_T_ECS:\n\t\t\tret = dns_get_OPT_ECS(rrs, &request->ecs);\n\t\t\tif (ret != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\trequest->has_ecs = 1;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\trequest->rcode = DNS_RC_NOTIMP;\n\treturn -1;\n}\n\nint _dns_server_setup_server_query_options(struct dns_request *request,\n\t\t\t\t\t\t\t\t\t\t   struct dns_server_query_option *server_query_option)\n{\n\tif (server_query_option == NULL) {\n\t\treturn 0;\n\t}\n\n\trequest->server_flags = server_query_option->server_flags;\n\tif (server_query_option->dns_group_name) {\n\t\tsafe_strncpy(request->dns_group_name, server_query_option->dns_group_name, DNS_GROUP_NAME_LEN);\n\t}\n\n\tif (server_query_option->ecs_enable_flag & DNS_QUEY_OPTION_ECS_DNS) {\n\t\trequest->has_ecs = 1;\n\t\tmemcpy(&request->ecs, &server_query_option->ecs_dns, sizeof(request->ecs));\n\t}\n\n\tif (server_query_option->ecs_enable_flag & DNS_QUEY_OPTION_EDNS0_DO) {\n\t\trequest->edns0_do = 1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/request.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_REQUEST_\n#define _DNS_SERVER_REQUEST_\n\n#include \"dns_server.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/dns_cache.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _dns_server_request_release_complete(struct dns_request *request, int do_complete);\n\nvoid _dns_server_query_end(struct dns_request *request);\n\nvoid *dns_server_request_get_private(struct dns_request *request);\n\nstruct dns_request *_dns_server_new_request(void);\n\nstruct dns_request *_dns_server_new_child_request(struct dns_request *request, const char *domain, dns_type_t qtype,\n\t\t\t\t\t\t\t\t\t\t\t\t  child_request_callback child_callback);\n\nconst char *_dns_server_get_request_server_groupname(struct dns_request *request);\n\nint _dns_server_request_complete(struct dns_request *request);\n\nint _dns_server_request_copy(struct dns_request *request, struct dns_request *from);\n\nvoid _dns_server_request_set_callback(struct dns_request *request, dns_result_callback callback, void *user_ptr);\n\nint _dns_server_setup_request_conf_pre(struct dns_request *request);\n\nint _dns_server_setup_request_conf(struct dns_request *request);\n\nint _dns_server_setup_server_query_options(struct dns_request *request,\n\t\t\t\t\t\t\t\t\t\t   struct dns_server_query_option *server_query_option);\n\nint _dns_server_request_set_client_addr(struct dns_request *request, struct sockaddr_storage *from, socklen_t from_len);\n\nint _dns_server_check_request_supported(struct dns_request *request, struct dns_packet *packet);\n\nint _dns_server_parser_request(struct dns_request *request, struct dns_packet *packet);\n\nvoid _dns_server_set_request_mdns(struct dns_request *request);\n\nint _dns_server_process_svcb(struct dns_request *request);\n\nint _dns_server_process_srv(struct dns_request *request);\n\nint _dns_server_process_host(struct dns_request *request);\n\nvoid _dns_server_check_set_passthrough(struct dns_request *request);\n\nint _dns_server_process_special_query(struct dns_request *request);\n\nint _dns_server_process_dns64(struct dns_request *request);\n\nvoid _dns_server_setup_dns_group_name(struct dns_request *request, const char **server_group_name);\n\nint _dns_server_setup_query_option(struct dns_request *request, struct dns_query_options *options);\n\nint _dns_server_process_smartdns_domain(struct dns_request *request);\n\nvoid _dns_server_passthrough_may_complete(struct dns_request *request);\n\nint _dns_server_pre_process_server_flags(struct dns_request *request);\n\nint _dns_server_get_expired_ttl_reply(struct dns_request *request, struct dns_cache *dns_cache);\n\nint _dns_server_process_https_svcb(struct dns_request *request);\n\nvoid _dns_server_request_set_client(struct dns_request *request, struct dns_server_conn_head *conn);\n\nvoid _dns_server_request_set_id(struct dns_request *request, unsigned short id);\n\nvoid _dns_server_request_set_mac(struct dns_request *request, struct sockaddr_storage *from, socklen_t from_len);\n\nint _dns_server_has_bind_flag(struct dns_request *request, uint32_t flag);\n\nint _dns_server_is_dns64_request(struct dns_request *request);\n\nvoid _dns_server_request_release(struct dns_request *request);\n\nvoid _dns_server_request_get(struct dns_request *request);\n\nvoid _dns_server_request_remove_all(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/request_pending.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"request_pending.h\"\n#include \"answer.h\"\n#include \"context.h\"\n#include \"dns_server.h\"\n#include \"request.h\"\n\nint _dns_server_set_to_pending_list(struct dns_request *request)\n{\n\tstruct dns_request_pending_list *pending_list = NULL;\n\tstruct dns_request_pending_list *pending_list_tmp = NULL;\n\tuint32_t key = 0;\n\tint ret = -1;\n\tif (request->qtype != DNS_T_A && request->qtype != DNS_T_AAAA) {\n\t\treturn ret;\n\t}\n\n\tkey = hash_string(request->domain);\n\tkey = hash_string_initval(request->dns_group_name, key);\n\tkey = jhash(&(request->qtype), sizeof(request->qtype), key);\n\tkey = jhash(&(request->server_flags), sizeof(request->server_flags), key);\n\tpthread_mutex_lock(&server.request_pending_lock);\n\thash_for_each_possible(server.request_pending, pending_list_tmp, node, key)\n\t{\n\t\tif (request->qtype != pending_list_tmp->qtype) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (request->server_flags != pending_list_tmp->server_flags) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strcmp(request->dns_group_name, pending_list_tmp->dns_group_name) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(request->domain, pending_list_tmp->domain, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tpending_list = pending_list_tmp;\n\t\tbreak;\n\t}\n\n\tif (pending_list == NULL) {\n\t\tpending_list = zalloc(1, sizeof(*pending_list));\n\t\tif (pending_list == NULL) {\n\t\t\tret = -1;\n\t\t\tgoto out;\n\t\t}\n\t\tpthread_mutex_init(&pending_list->request_list_lock, NULL);\n\t\tINIT_LIST_HEAD(&pending_list->request_list);\n\t\tINIT_HLIST_NODE(&pending_list->node);\n\t\tpending_list->qtype = request->qtype;\n\t\tpending_list->server_flags = request->server_flags;\n\t\tsafe_strncpy(pending_list->domain, request->domain, DNS_MAX_CNAME_LEN);\n\t\tsafe_strncpy(pending_list->dns_group_name, request->dns_group_name, DNS_GROUP_NAME_LEN);\n\t\thash_add(server.request_pending, &pending_list->node, key);\n\t\trequest->request_pending_list = pending_list;\n\t} else {\n\t\tret = 0;\n\t}\n\n\tif (ret == 0) {\n\t\t_dns_server_request_get(request);\n\t}\n\tlist_add_tail(&request->pending_list, &pending_list->request_list);\nout:\n\tpthread_mutex_unlock(&server.request_pending_lock);\n\treturn ret;\n}\n\nint _dns_server_reply_all_pending_list(struct dns_request *request, struct dns_server_post_context *context)\n{\n\tstruct dns_request_pending_list *pending_list = NULL;\n\tstruct dns_request *req = NULL;\n\tstruct dns_request *tmp = NULL;\n\tint ret = 0;\n\n\tpthread_mutex_lock(&server.request_pending_lock);\n\tif (request->request_pending_list == NULL) {\n\t\tpthread_mutex_unlock(&server.request_pending_lock);\n\t\treturn 0;\n\t}\n\n\tpending_list = request->request_pending_list;\n\trequest->request_pending_list = NULL;\n\thlist_del_init(&pending_list->node);\n\tpthread_mutex_unlock(&server.request_pending_lock);\n\n\tpthread_mutex_lock(&pending_list->request_list_lock);\n\tlist_del_init(&request->pending_list);\n\tlist_for_each_entry_safe(req, tmp, &(pending_list->request_list), pending_list)\n\t{\n\t\tstruct dns_server_post_context context_pending;\n\t\t_dns_server_post_context_init_from(&context_pending, req, context->packet, context->inpacket,\n\t\t\t\t\t\t\t\t\t\t   context->inpacket_len);\n\t\treq->dualstack_selection = request->dualstack_selection;\n\t\treq->dualstack_selection_query = request->dualstack_selection_query;\n\t\treq->dualstack_selection_force_soa = request->dualstack_selection_force_soa;\n\t\treq->dualstack_selection_has_ip = request->dualstack_selection_has_ip;\n\t\treq->dualstack_selection_ping_time = request->dualstack_selection_ping_time;\n\t\treq->ping_time = request->ping_time;\n\t\treq->is_cache_reply = request->is_cache_reply;\n\t\t_dns_server_get_answer(&context_pending);\n\n\t\tcontext_pending.is_cache_reply = context->is_cache_reply;\n\t\tcontext_pending.do_cache = 0;\n\t\tcontext_pending.do_audit = context->do_audit;\n\t\tcontext_pending.do_reply = context->do_reply;\n\t\tcontext_pending.do_force_soa = context->do_force_soa;\n\t\tcontext_pending.do_ipset = 0;\n\t\tcontext_pending.reply_ttl = request->ip_ttl;\n\t\tcontext_pending.no_release_parent = 0;\n\n\t\t_dns_server_reply_passthrough(&context_pending);\n\n\t\treq->request_pending_list = NULL;\n\t\tlist_del_init(&req->pending_list);\n\t\t_dns_server_request_release_complete(req, 0);\n\t}\n\tpthread_mutex_unlock(&pending_list->request_list_lock);\n\n\tfree(pending_list);\n\n\treturn ret;\n}\n"
  },
  {
    "path": "src/dns_server/request_pending.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_REQUEST_PENDING_\n#define _DNS_SERVER_REQUEST_PENDING_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_reply_all_pending_list(struct dns_request *request, struct dns_server_post_context *context);\n\nint _dns_server_set_to_pending_list(struct dns_request *request);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/rules.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"rules.h\"\n#include \"address.h\"\n#include \"dns_server.h\"\n#include \"ip_rule.h\"\n#include \"request.h\"\n#include \"request_pending.h\"\n#include \"soa.h\"\n\nvoid *_dns_server_get_dns_rule_ext(struct dns_request_domain_rule *domain_rule, enum domain_rule rule)\n{\n\tif (rule >= DOMAIN_RULE_MAX || domain_rule == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn domain_rule->rules[rule];\n}\n\nstatic int _dns_server_is_dns_rule_extract_match_ext(struct dns_request_domain_rule *domain_rule, enum domain_rule rule)\n{\n\tif (rule >= DOMAIN_RULE_MAX || domain_rule == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn domain_rule->is_sub_rule[rule] == 0;\n}\n\nstatic void _dns_server_log_rule(const char *domain, enum domain_rule rule_type, unsigned char *rule_key,\n\t\t\t\t\t\t\t\t int rule_key_len)\n{\n\tchar rule_name[DNS_MAX_CNAME_LEN] = {0};\n\tif (rule_key_len <= 0) {\n\t\treturn;\n\t}\n\n\treverse_string(rule_name, (char *)rule_key, rule_key_len, 1);\n\trule_name[rule_key_len] = 0;\n\ttlog(TLOG_INFO, \"RULE-MATCH, type: %d, domain: %s, rule: %s\", rule_type, domain, rule_name);\n}\n\nstatic void _dns_server_update_rule_by_flags(struct dns_request_domain_rule *request_domain_rule)\n{\n\tunsigned int flags = 0;\n\tflags = request_domain_rule->flags;\n\n\tif (flags & DOMAIN_FLAG_ADDR_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV4] = NULL;\n\t\trequest_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_ADDR_IPV4_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV4] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_ADDR_IPV6_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_ADDRESS_IPV6] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_HTTPS] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_IPSET_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_IPSET] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_IPSET_IPV4_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_IPSET_IPV4] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_IPSET_IPV6_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_IPSET_IPV6] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_NFTSET_IP_IGN || flags & DOMAIN_FLAG_NFTSET_INET_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_NFTSET_IP] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_NFTSET_IP6_IGN || flags & DOMAIN_FLAG_NFTSET_INET_IGN) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_NFTSET_IP6] = NULL;\n\t}\n\n\tif (flags & DOMAIN_FLAG_NAMESERVER_IGNORE) {\n\t\trequest_domain_rule->rules[DOMAIN_RULE_NAMESERVER] = NULL;\n\t}\n}\n\nstatic int _dns_server_get_rules(unsigned char *key, uint32_t key_len, int is_subkey, void *value, void *arg)\n{\n\tstruct rule_walk_args *walk_args = arg;\n\tstruct dns_request_domain_rule *request_domain_rule = walk_args->args;\n\tstruct dns_domain_rule *domain_rule = value;\n\tint i = 0;\n\tif (domain_rule == NULL) {\n\t\treturn 0;\n\t}\n\n\t/* sub rule flag check */\n\tint is_effective_sub = 1;\n\tif (key_len == walk_args->full_key_len) {\n\t\tis_effective_sub = 0;\n\t} else if (key_len == walk_args->full_key_len - 1 && walk_args->full_key_len > 0) {\n\t\tis_effective_sub = 0;\n\t}\n\n\tif (walk_args->rule_index >= 0) {\n\t\ti = walk_args->rule_index;\n\t} else {\n\t\ti = 0;\n\t}\n\n\tfor (; i < domain_rule->capacity; i++) {\n\t\tif (domain_rule->rules[i] == NULL) {\n\t\t\tif (walk_args->rule_index >= 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (i == DOMAIN_RULE_FLAGS) {\n\t\t\tstruct dns_rule_flags *rule_flags = (struct dns_rule_flags *)domain_rule->rules[i];\n\t\t\tif (rule_flags->head.sub_only == 1 && is_effective_sub == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (rule_flags->head.root_only == 1 && is_effective_sub == 1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\trequest_domain_rule->flags |= ((struct dns_rule_flags *)domain_rule->rules[i])->flags;\n\t\t}\n\n\t\tif (domain_rule->rules[i]->sub_only == 1 && is_effective_sub == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (domain_rule->rules[i]->root_only == 1 && is_effective_sub == 1) {\n\t\t\tcontinue;\n\t\t}\n\n\t\trequest_domain_rule->rules[i] = domain_rule->rules[i];\n\t\trequest_domain_rule->is_sub_rule[i] = is_subkey;\n\t\twalk_args->key[i] = key;\n\t\twalk_args->key_len[i] = key_len;\n\t\tif (walk_args->rule_index >= 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* update rules by flags */\n\t_dns_server_update_rule_by_flags(request_domain_rule);\n\n\treturn 0;\n}\n\nvoid _dns_server_get_domain_rule_by_domain_ext(struct dns_conf_group *conf,\n\t\t\t\t\t\t\t\t\t\t\t   struct dns_request_domain_rule *request_domain_rule, int rule_index,\n\t\t\t\t\t\t\t\t\t\t\t   const char *domain, int out_log)\n{\n\tint domain_len = 0;\n\tchar domain_key[DNS_MAX_CNAME_LEN] = {0};\n\tstruct rule_walk_args walk_args;\n\tint matched_key_len = DNS_MAX_CNAME_LEN;\n\tunsigned char matched_key[DNS_MAX_CNAME_LEN] = {0};\n\tint i = 0;\n\n\tmemset(&walk_args, 0, sizeof(walk_args));\n\twalk_args.args = request_domain_rule;\n\twalk_args.rule_index = rule_index;\n\n\t/* reverse domain string */\n\tdomain_len = strlen(domain);\n\tif (domain_len >= (int)sizeof(domain_key) - 3) {\n\t\treturn;\n\t}\n\n\treverse_string(domain_key + 1, domain, domain_len, 1);\n\tdomain_key[domain_len + 1] = '.';\n\tdomain_key[0] = '.';\n\tdomain_len += 2;\n\tdomain_key[domain_len] = 0;\n\twalk_args.full_key_len = domain_len;\n\n\t/* find domain rule */\n\tart_substring_walk(&conf->domain_rule.tree, (unsigned char *)domain_key, domain_len, _dns_server_get_rules,\n\t\t\t\t\t   &walk_args);\n\tif (likely(dns_conf.log_level > TLOG_DEBUG) || out_log == 0) {\n\t\treturn;\n\t}\n\n\tif (walk_args.rule_index >= 0) {\n\t\ti = walk_args.rule_index;\n\t} else {\n\t\ti = 0;\n\t}\n\n\t/* output log rule */\n\tfor (; i < DOMAIN_RULE_MAX; i++) {\n\t\tif (walk_args.key[i] == NULL) {\n\t\t\tif (walk_args.rule_index >= 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tmatched_key_len = walk_args.key_len[i];\n\t\tif (walk_args.key_len[i] >= sizeof(matched_key)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tmemcpy(matched_key, walk_args.key[i], walk_args.key_len[i]);\n\n\t\tmatched_key_len--;\n\t\tmatched_key[matched_key_len] = 0;\n\t\t_dns_server_log_rule(domain, i, matched_key, matched_key_len);\n\n\t\tif (walk_args.rule_index >= 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid _dns_server_get_domain_rule(struct dns_request *request)\n{\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULES) == 0) {\n\t\treturn;\n\t}\n\n\t_dns_server_get_domain_rule_by_domain(request, request->domain, 1);\n}\n\nint _dns_server_passthrough_rule_check(struct dns_request *request, const char *domain, struct dns_packet *packet,\n\t\t\t\t\t\t\t\t\t   unsigned int result_flag, int *pttl)\n{\n\tint ttl = 0;\n\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\tchar cname[DNS_MAX_CNAME_LEN] = {0};\n\tint rr_count = 0;\n\tint i = 0;\n\tint j = 0;\n\tstruct dns_rrs *rrs = NULL;\n\tint ip_check_result = 0;\n\n\tif (packet->head.rcode != DNS_RC_NOERROR && packet->head.rcode != DNS_RC_NXDOMAIN) {\n\t\tif (request->rcode == DNS_RC_SERVFAIL) {\n\t\t\trequest->rcode = packet->head.rcode;\n\t\t\trequest->remote_server_fail = 1;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"inquery failed, %s, rcode = %d, id = %d\\n\", domain, packet->head.rcode, packet->head.id);\n\t\treturn 0;\n\t}\n\n\tfor (j = 1; j < DNS_RRS_OPT; j++) {\n\t\trrs = dns_get_rrs_start(packet, j, &rr_count);\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\t\tswitch (rrs->type) {\n\t\t\tcase DNS_T_A: {\n\t\t\t\tunsigned char addr[4];\n\t\t\t\tint ttl_tmp = 0;\n\t\t\t\tif (request->qtype != DNS_T_A) {\n\t\t\t\t\t/* ignore non-matched query type */\n\t\t\t\t\tif (request->dualstack_selection == 0) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_dns_server_request_get(request);\n\t\t\t\t/* get A result */\n\t\t\t\tdns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl_tmp, addr);\n\n\t\t\t\t/* if domain is not match */\n\t\t\t\tif (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 &&\n\t\t\t\t\tstrncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\t\t\t_dns_server_request_release(request);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttlog(TLOG_DEBUG, \"domain: %s TTL: %d IP: %d.%d.%d.%d\", name, ttl_tmp, addr[0], addr[1], addr[2],\n\t\t\t\t\t addr[3]);\n\n\t\t\t\t/* ip rule check */\n\t\t\t\tip_check_result = _dns_server_process_ip_rule(request, addr, 4, DNS_T_A, result_flag, NULL);\n\t\t\t\tif (ip_check_result == 0 || ip_check_result == -2 || ip_check_result == -3) {\n\t\t\t\t\t/* match, skip, nxdomain */\n\t\t\t\t\t_dns_server_request_release(request);\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\t/* Ad blocking result */\n\t\t\t\tif (addr[0] == 0 || addr[0] == 127) {\n\t\t\t\t\t/* If half of the servers return the same result, then ignore this address */\n\t\t\t\t\tif (atomic_read(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) {\n\t\t\t\t\t\t_dns_server_request_release(request);\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tttl = _dns_server_get_conf_ttl(request, ttl_tmp);\n\t\t\t\t_dns_server_request_release(request);\n\t\t\t} break;\n\t\t\tcase DNS_T_AAAA: {\n\t\t\t\tunsigned char addr[16];\n\t\t\t\tint ttl_tmp = 0;\n\t\t\t\tif (request->qtype != DNS_T_AAAA) {\n\t\t\t\t\t/* ignore non-matched query type */\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t_dns_server_request_get(request);\n\t\t\t\tdns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl_tmp, addr);\n\n\t\t\t\t/* if domain is not match */\n\t\t\t\tif (strncasecmp(name, domain, DNS_MAX_CNAME_LEN) != 0 &&\n\t\t\t\t\tstrncasecmp(cname, name, DNS_MAX_CNAME_LEN) != 0) {\n\t\t\t\t\t_dns_server_request_release(request);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttlog(TLOG_DEBUG,\n\t\t\t\t\t \"domain: %s TTL: %d IP: \"\n\t\t\t\t\t \"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x\",\n\t\t\t\t\t name, ttl_tmp, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8],\n\t\t\t\t\t addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]);\n\n\t\t\t\tip_check_result = _dns_server_process_ip_rule(request, addr, 16, DNS_T_AAAA, result_flag, NULL);\n\t\t\t\tif (ip_check_result == 0 || ip_check_result == -2 || ip_check_result == -3) {\n\t\t\t\t\t/* match, skip, nxdomain */\n\t\t\t\t\t_dns_server_request_release(request);\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\t/* Ad blocking result */\n\t\t\t\tif (_dns_server_is_adblock_ipv6(addr) == 0) {\n\t\t\t\t\t/* If half of the servers return the same result, then ignore this address */\n\t\t\t\t\tif (atomic_read(&request->adblock) <= (dns_server_alive_num() / 2 + dns_server_alive_num() % 2)) {\n\t\t\t\t\t\t_dns_server_request_release(request);\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tttl = _dns_server_get_conf_ttl(request, ttl_tmp);\n\t\t\t\t_dns_server_request_release(request);\n\t\t\t} break;\n\t\t\tcase DNS_T_CNAME: {\n\t\t\t\tdns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN);\n\t\t\t} break;\n\t\t\tcase DNS_T_HTTPS: {\n\t\t\t\tstruct dns_https_record_rule *https_record_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_HTTPS);\n\t\t\t\tif (https_record_rule) {\n\t\t\t\t\tif (https_record_rule->filter.no_ipv4hint || https_record_rule->filter.no_ipv6hint || \n\t\t\t\t\t\thttps_record_rule->filter.no_ech) {\n\t\t\t\t\t\t/* Need to filter, do not passthrough */\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} break;\n\t\t\tdefault:\n\t\t\t\tif (ttl == 0) {\n\t\t\t\t\t/* Get TTL */\n\t\t\t\t\tchar tmpname[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\t\tchar tmpbuf[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\t\tdns_get_CNAME(rrs, tmpname, DNS_MAX_CNAME_LEN, &ttl, tmpbuf, DNS_MAX_CNAME_LEN);\n\t\t\t\t\tif (request->ip_ttl == 0) {\n\t\t\t\t\t\trequest->ip_ttl = _dns_server_get_conf_ttl(request, ttl);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\trequest->remote_server_fail = 0;\n\tif (request->rcode == DNS_RC_SERVFAIL) {\n\t\trequest->rcode = packet->head.rcode;\n\t}\n\n\t*pttl = ttl;\n\treturn -1;\n}\n\nint _dns_server_get_conf_ttl(struct dns_request *request, int ttl)\n{\n\tint rr_ttl = request->conf->dns_rr_ttl;\n\tint rr_ttl_min = request->conf->dns_rr_ttl_min;\n\tint rr_ttl_max = request->conf->dns_rr_ttl_max;\n\n\tif (request->is_mdns_lookup) {\n\t\trr_ttl_min = DNS_SERVER_ADDR_TTL;\n\t}\n\n\tstruct dns_ttl_rule *ttl_rule = _dns_server_get_dns_rule(request, DOMAIN_RULE_TTL);\n\tif (ttl_rule != NULL) {\n\t\tif (ttl_rule->ttl > 0) {\n\t\t\trr_ttl = ttl_rule->ttl;\n\t\t}\n\n\t\t/* make domain rule ttl high priority */\n\t\tif (ttl_rule->ttl_min > 0) {\n\t\t\trr_ttl_min = ttl_rule->ttl_min;\n\t\t\tif (request->conf->dns_rr_ttl_max <= rr_ttl_min && request->conf->dns_rr_ttl_max > 0) {\n\t\t\t\trr_ttl_max = rr_ttl_min;\n\t\t\t}\n\t\t}\n\n\t\tif (ttl_rule->ttl_max > 0) {\n\t\t\trr_ttl_max = ttl_rule->ttl_max;\n\t\t\tif (request->conf->dns_rr_ttl_min >= rr_ttl_max && request->conf->dns_rr_ttl_min > 0 &&\n\t\t\t\tttl_rule->ttl_min <= 0) {\n\t\t\t\trr_ttl_min = rr_ttl_max;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (rr_ttl > 0) {\n\t\treturn rr_ttl;\n\t}\n\n\t/* make rr_ttl_min first priority */\n\tif (rr_ttl_max < rr_ttl_min && rr_ttl_max > 0) {\n\t\trr_ttl_max = rr_ttl_min;\n\t}\n\n\tif (rr_ttl_max > 0 && ttl >= rr_ttl_max) {\n\t\tttl = rr_ttl_max;\n\t} else if (rr_ttl_min > 0 && ttl <= rr_ttl_min) {\n\t\tttl = rr_ttl_min;\n\t}\n\n\treturn ttl;\n}\n\nint _dns_server_get_reply_ttl(struct dns_request *request, int ttl)\n{\n\tint reply_ttl = ttl;\n\n\tif ((request->passthrough == 0 || request->passthrough == 2) && dns_conf.cachesize > 0 &&\n\t\trequest->check_order_list->orders[0].type != DOMAIN_CHECK_NONE && request->no_serve_expired == 0 &&\n\t\trequest->has_soa == 0 && request->no_cache == 0) {\n\t\treply_ttl = request->conf->dns_serve_expired_reply_ttl;\n\t\tif (reply_ttl < 2) {\n\t\t\treply_ttl = 2;\n\t\t}\n\t}\n\n\tint rr_ttl = _dns_server_get_conf_ttl(request, ttl);\n\tif (reply_ttl > rr_ttl) {\n\t\treply_ttl = rr_ttl;\n\t}\n\n\treturn reply_ttl;\n}\n\nvoid *_dns_server_get_dns_rule(struct dns_request *request, enum domain_rule rule)\n{\n\tif (request == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn _dns_server_get_dns_rule_ext(&request->domain_rule, rule);\n}\n\nuint32_t _dns_server_get_rule_flags(struct dns_request *request)\n{\n\tstruct dns_rule_flags *rule_flag = NULL;\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (request->domain_rule.flags != 0) {\n\t\treturn request->domain_rule.flags;\n\t}\n\n\tif (request->domain_rule.rules[DOMAIN_RULE_FLAGS] == NULL) {\n\t\treturn 0;\n\t}\n\n\trule_flag = (struct dns_rule_flags *)request->domain_rule.rules[DOMAIN_RULE_FLAGS];\n\n\treturn rule_flag->flags;\n}\n\nint _dns_server_is_dns_rule_extract_match(struct dns_request *request, enum domain_rule rule)\n{\n\tif (request == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn _dns_server_is_dns_rule_extract_match_ext(&request->domain_rule, rule);\n}\n\nint _dns_server_pre_process_rule_flags(struct dns_request *request)\n{\n\t/* get domain rule flag */\n\tunsigned int flags = _dns_server_get_rule_flags(request);\n\tint rcode = DNS_RC_NOERROR;\n\n\tif (flags & DOMAIN_FLAG_NO_SERVE_EXPIRED) {\n\t\trequest->no_serve_expired = 1;\n\t}\n\n\tif (flags & DOMAIN_FLAG_NO_CACHE) {\n\t\trequest->no_cache = 1;\n\t}\n\n\tif (flags & DOMAIN_FLAG_ENABLE_CACHE) {\n\t\trequest->no_cache = 0;\n\t}\n\n\tif (flags & DOMAIN_FLAG_NO_IPALIAS) {\n\t\trequest->no_ipalias = 1;\n\t}\n\n\tif (flags & DOMAIN_FLAG_ADDR_IGN) {\n\t\t/* ignore this domain */\n\t\tgoto skip_soa_out;\n\t}\n\n\t/* return specific type of address */\n\tswitch (request->qtype) {\n\tcase DNS_T_A:\n\t\tif (flags & DOMAIN_FLAG_ADDR_IPV4_IGN) {\n\t\t\t/* ignore this domain for A request */\n\t\t\tgoto skip_soa_out;\n\t\t}\n\n\t\tif (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] != NULL) {\n\t\t\tgoto skip_soa_out;\n\t\t}\n\n\t\tif (_dns_server_is_return_soa(request)) {\n\t\t\t/* if AAAA exists, return SOA with NOERROR*/\n\t\t\tif (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] != NULL) {\n\t\t\t\tgoto soa;\n\t\t\t}\n\n\t\t\t/* if AAAA not exists, return SOA with NXDOMAIN */\n\t\t\tif (_dns_server_is_return_soa_qtype(request, DNS_T_AAAA)) {\n\t\t\t\trcode = DNS_RC_NXDOMAIN;\n\t\t\t}\n\t\t\tgoto soa;\n\t\t}\n\t\tgoto out;\n\t\tbreak;\n\tcase DNS_T_AAAA:\n\t\tif (flags & DOMAIN_FLAG_ADDR_IPV6_IGN) {\n\t\t\t/* ignore this domain for A request */\n\t\t\tgoto skip_soa_out;\n\t\t}\n\n\t\tif (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] != NULL) {\n\t\t\tgoto skip_soa_out;\n\t\t}\n\n\t\tif (_dns_server_is_return_soa(request)) {\n\t\t\t/* if A exists, return SOA with NOERROR*/\n\t\t\tif (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] != NULL) {\n\t\t\t\tgoto soa;\n\t\t\t}\n\t\t\t/* if A not exists, return SOA with NXDOMAIN */\n\t\t\tif (_dns_server_is_return_soa_qtype(request, DNS_T_A)) {\n\t\t\t\trcode = DNS_RC_NXDOMAIN;\n\t\t\t}\n\t\t\tgoto soa;\n\t\t}\n\n\t\tif (flags & DOMAIN_FLAG_ADDR_IPV4_SOA && request->dualstack_selection) {\n\t\t\t/* if IPV4 return SOA and dualstack-selection enabled, set request dualstack disable */\n\t\t\trequest->dualstack_selection = 0;\n\t\t}\n\t\tgoto out;\n\t\tbreak;\n\tcase DNS_T_HTTPS:\n\t\tif (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) {\n\t\t\t/* ignore this domain for A request */\n\t\t\tgoto skip_soa_out;\n\t\t}\n\n\t\tif (_dns_server_is_return_soa(request)) {\n\t\t\t/* if HTTPS exists, return SOA with NOERROR*/\n\t\t\tif (request->domain_rule.rules[DOMAIN_RULE_HTTPS] != NULL) {\n\t\t\t\tgoto soa;\n\t\t\t}\n\n\t\t\tif (_dns_server_is_return_soa_qtype(request, DNS_T_A) &&\n\t\t\t\t_dns_server_is_return_soa_qtype(request, DNS_T_AAAA)) {\n\t\t\t\t/* return SOA for HTTPS request */\n\t\t\t\trcode = DNS_RC_NXDOMAIN;\n\t\t\t\tgoto soa;\n\t\t\t}\n\t\t}\n\n\t\tif (request->domain_rule.rules[DOMAIN_RULE_HTTPS] != NULL) {\n\t\t\tgoto skip_soa_out;\n\t\t}\n\n\t\tgoto out;\n\t\tbreak;\n\tdefault:\n\t\tgoto out;\n\t\tbreak;\n\t}\n\n\tif (_dns_server_is_return_soa(request)) {\n\t\tgoto soa;\n\t}\nskip_soa_out:\n\trequest->skip_qtype_soa = 1;\nout:\n\treturn -1;\n\nsoa:\n\t/* return SOA */\n\t_dns_server_reply_SOA(rcode, request);\n\treturn 0;\n}\n\nvoid _dns_server_process_speed_rule(struct dns_request *request)\n{\n\tstruct dns_domain_check_orders *check_order = NULL;\n\tstruct dns_response_mode_rule *response_mode = NULL;\n\n\t/* get speed check mode */\n\tcheck_order = _dns_server_get_dns_rule(request, DOMAIN_RULE_CHECKSPEED);\n\tif (check_order != NULL) {\n\t\trequest->check_order_list = check_order;\n\t}\n\n\t/* get response mode */\n\tresponse_mode = _dns_server_get_dns_rule(request, DOMAIN_RULE_RESPONSE_MODE);\n\tif (response_mode != NULL) {\n\t\trequest->response_mode = response_mode->mode;\n\t} else {\n\t\trequest->response_mode = request->conf->dns_response_mode;\n\t}\n}\n\nvoid _dns_server_get_domain_rule_by_domain(struct dns_request *request, const char *domain, int out_log)\n{\n\tif (request->skip_domain_rule != 0) {\n\t\treturn;\n\t}\n\n\tif (request->conf == NULL) {\n\t\treturn;\n\t}\n\n\t_dns_server_get_domain_rule_by_domain_ext(request->conf, &request->domain_rule, -1, domain, out_log);\n\trequest->skip_domain_rule = 1;\n}"
  },
  {
    "path": "src/dns_server/rules.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_RULES_\n#define _DNS_SERVER_RULES_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid *_dns_server_get_dns_rule(struct dns_request *request, enum domain_rule rule);\n\nuint32_t _dns_server_get_rule_flags(struct dns_request *request);\n\nint _dns_server_get_conf_ttl(struct dns_request *request, int ttl);\n\nvoid *_dns_server_get_dns_rule_ext(struct dns_request_domain_rule *domain_rule, enum domain_rule rule);\n\nvoid _dns_server_get_domain_rule(struct dns_request *request);\n\nint _dns_server_pre_process_rule_flags(struct dns_request *request);\n\nint _dns_server_get_reply_ttl(struct dns_request *request, int ttl);\n\nint _dns_server_is_dns_rule_extract_match(struct dns_request *request, enum domain_rule rule);\n\nvoid _dns_server_get_domain_rule_by_domain_ext(struct dns_conf_group *conf,\n\t\t\t\t\t\t\t\t\t\t\t   struct dns_request_domain_rule *request_domain_rule, int rule_index,\n\t\t\t\t\t\t\t\t\t\t\t   const char *domain, int out_log);\n\nint _dns_server_passthrough_rule_check(struct dns_request *request, const char *domain, struct dns_packet *packet,\n\t\t\t\t\t\t\t\t\t   unsigned int result_flag, int *pttl);\n\nvoid _dns_server_process_speed_rule(struct dns_request *request);\n\nvoid _dns_server_get_domain_rule_by_domain(struct dns_request *request, const char *domain, int out_log);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/server_http2.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n *************************************************************************/\n\n#include \"server_http2.h\"\n#include \"connection.h\"\n#include \"dns_server.h\"\n#include \"server_tls.h\"\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/http2.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\n#define DNS_SERVER_HTTP2_MAX_CONCURRENT_STREAMS 4096\n\nstatic int _http2_server_bio_read(void *private_data, uint8_t *buf, int len)\n{\n\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)private_data;\n\treturn _dns_server_socket_ssl_recv(tls_client, buf, len);\n}\n\nstatic int _http2_server_bio_write(void *private_data, const uint8_t *buf, int len)\n{\n\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)private_data;\n\treturn _dns_server_socket_ssl_send(tls_client, buf, len);\n}\n\nstatic int _dns_server_http2_send_response(struct http2_stream *stream, int status, const char *content_type,\n\t\t\t\t\t\t\t\t\t\t   const void *body, int body_len)\n{\n\tchar content_length[32];\n\tsnprintf(content_length, sizeof(content_length), \"%d\", body_len);\n\n\tstruct http2_header_pair headers[] = {\n\t\t{\"content-type\", content_type}, {\"content-length\", content_length}, {NULL, NULL}};\n\n\tif (http2_stream_set_response(stream, status, headers, 2) < 0) {\n\t\treturn -1;\n\t}\n\n\tif (http2_stream_write_body(stream, (const uint8_t *)body, body_len, 1) < 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_reply_http2(struct dns_request *request, struct dns_server_conn_http2_stream *stream_conn,\n\t\t\t\t\t\t\tunsigned char *inpacket, int inpacket_len)\n{\n\tstruct http2_stream *stream = stream_conn->stream;\n\n\tif (stream == NULL) {\n\t\treturn -1;\n\t}\n\n\t/* Send DNS response */\n\t/* Content-Type for DoH is application/dns-message */\n\treturn _dns_server_http2_send_response(stream, 200, \"application/dns-message\", inpacket, inpacket_len);\n}\n\nstatic void _dns_server_http2_process_stream(struct dns_server_conn_tls_client *tls_client, struct http2_stream *stream)\n{\n\tuint8_t buf[DNS_IN_PACKSIZE];\n\tint len = 0;\n\n\tconst char *method = http2_stream_get_method(stream);\n\tif (method == NULL) {\n\t\treturn;\n\t}\n\n\tif (strcasecmp(method, \"POST\") == 0) {\n\t\t/* Read request body */\n\t\tlen = http2_stream_read_body(stream, buf, sizeof(buf));\n\t\tif (len < 0) {\n\t\t\t/* Error or no data yet */\n\t\t\tif (http2_stream_is_end(stream)) {\n\t\t\t\tgoto close_out;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (len == 0 && !http2_stream_is_end(stream)) {\n\t\t\t/* No data available but stream not ended */\n\t\t\treturn;\n\t\t}\n\t} else if (strcasecmp(method, \"GET\") == 0) {\n\t\tconst char *path = http2_stream_get_path(stream);\n\t\tchar *base64_query = NULL;\n\n\t\tif (http2_stream_get_ex_data(stream)) {\n\t\t\tgoto close_out;\n\t\t}\n\t\thttp2_stream_set_ex_data(stream, (void *)1);\n\n\t\t/* Consume any body (should be empty for GET) to mark stream as read-handled */\n\t\thttp2_stream_read_body(stream, NULL, 0);\n\n\t\tif (path == NULL) {\n\t\t\t_dns_server_http2_send_response(stream, 404, \"text/plain\", \"Not Found\", 9);\n\t\t\tgoto close_out;\n\t\t}\n\n\t\t/* Check path prefix */\n\t\tif (strncmp(path, \"/dns-query\", 10) != 0) {\n\t\t\t_dns_server_http2_send_response(stream, 404, \"text/plain\", \"Not Found\", 9);\n\t\t\tgoto close_out;\n\t\t}\n\n\t\t/* Parse query string */\n\t\tchar *query_val = http2_stream_get_query_param(stream, \"dns\");\n\t\tif (query_val == NULL) {\n\t\t\t_dns_server_http2_send_response(stream, 400, \"text/plain\", \"Bad Request\", 11);\n\t\t\tgoto close_out;\n\t\t}\n\n\t\tbase64_query = malloc(DNS_IN_PACKSIZE);\n\t\tif (base64_query == NULL) {\n\t\t\tfree(query_val);\n\t\t\t_dns_server_http2_send_response(stream, 500, \"text/plain\", \"Bad Request\", 11);\n\t\t\tgoto close_out;\n\t\t}\n\n\t\tif (urldecode(base64_query, DNS_IN_PACKSIZE, query_val) < 0) {\n\t\t\tfree(query_val);\n\t\t\tfree(base64_query);\n\t\t\t_dns_server_http2_send_response(stream, 400, \"text/plain\", \"Bad Request\", 11);\n\t\t\tgoto close_out;\n\t\t}\n\t\tfree(query_val);\n\n\t\tlen = SSL_base64_decode_ext(base64_query, buf, sizeof(buf), 1, 1);\n\t\tfree(base64_query);\n\n\t\tif (len <= 0) {\n\t\t\t_dns_server_http2_send_response(stream, 400, \"text/plain\", \"Bad Request\", 11);\n\t\t\tgoto close_out;\n\t\t}\n\t} else {\n\t\t_dns_server_http2_send_response(stream, 405, \"text/plain\", \"Method Not Allowed\", 18);\n\t\tgoto close_out;\n\t}\n\n\tif (len > 0) {\n\t\t/* Create a fake connection object for this stream */\n\t\tstruct dns_server_conn_http2_stream *stream_conn = zalloc(1, sizeof(struct dns_server_conn_http2_stream));\n\t\tif (stream_conn == NULL) {\n\t\t\t_dns_server_http2_send_response(stream, 500, \"text/plain\", \"Bad Request\", 11);\n\t\t\tgoto close_out;\n\t\t}\n\n\t\t/* Initialize the fake connection */\n\t\t_dns_server_conn_head_init(&stream_conn->head, -1, DNS_CONN_TYPE_HTTP2_STREAM);\n\t\tstream_conn->stream = http2_stream_get(stream);\n\t\tstream_conn->tls_client = tls_client;\n\n\t\t/* Copy properties from parent connection */\n\t\tstream_conn->head.server_flags = tls_client->tcp.head.server_flags;\n\t\tstream_conn->head.dns_group = tls_client->tcp.head.dns_group;\n\t\tstream_conn->head.ipset_nftset_rule = tls_client->tcp.head.ipset_nftset_rule;\n\n\t\t/* We need to increment refcnt because _dns_server_recv (via request) will eventually release it */\n\t\t_dns_server_conn_get(&stream_conn->head);\n\n\t\t/* Process the packet */\n\t\t/* Note: _dns_server_recv takes conn, inpacket, inpacket_len, local, local_len, from, from_len */\n\t\t_dns_server_recv(&stream_conn->head, buf, len, &tls_client->tcp.localaddr, tls_client->tcp.localaddr_len,\n\t\t\t\t\t\t &tls_client->tcp.addr, tls_client->tcp.addr_len);\n\n\t\t/* Release our reference (request holds one now) */\n\t\t_dns_server_conn_release(&stream_conn->head);\n\t}\n\n\treturn;\n\nclose_out:\n\tif (stream != NULL) {\n\t\t/* Close stream on error */\n\t\thttp2_stream_get(stream);\n\t\thttp2_stream_close(stream);\n\t}\n}\n\nint _dns_server_process_http2(struct dns_server_conn_tls_client *tls_client, struct epoll_event *event,\n\t\t\t\t\t\t\t  unsigned long now)\n{\n\tstruct http2_ctx *ctx = (struct http2_ctx *)tls_client->http2_ctx;\n\tint ret = 0;\n\n\t/* Initialize HTTP/2 context if not already done */\n\tif (ctx == NULL) {\n\t\tstruct http2_settings settings;\n\t\tmemset(&settings, 0, sizeof(settings));\n\t\tsettings.max_concurrent_streams = DNS_SERVER_HTTP2_MAX_CONCURRENT_STREAMS;\n\t\tctx = http2_ctx_server_new(\"smartdns-server\", _http2_server_bio_read, _http2_server_bio_write, tls_client,\n\t\t\t\t\t\t\t\t   &settings);\n\t\tif (ctx == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"init http2 context failed.\");\n\t\t\treturn -1;\n\t\t}\n\n\t\t/* Perform initial handshake */\n\t\tret = http2_ctx_handshake(ctx);\n\t\tif (ret < 0) {\n\t\t\tconst char *err_msg = http2_error_to_string(ret);\n\t\t\tint log_level = TLOG_ERROR;\n\t\t\tif (ret == HTTP2_ERR_EOF || ret == HTTP2_ERR_HTTP1) {\n\t\t\t\tlog_level = TLOG_DEBUG; /* Less noisy for clients that disconnect early or misbehave */\n\t\t\t}\n\t\t\ttlog(log_level, \"http2 handshake failed, ret=%d (%s), alpn=%s.\", ret, err_msg, tls_client->alpn_selected);\n\t\t\thttp2_ctx_close(ctx);\n\t\t\treturn -1;\n\t\t}\n\t\t\n\t\ttls_client->http2_ctx = ctx;\n\t}\n\n\t/* Handle EPOLLOUT - flush pending writes */\n\tif (event->events & EPOLLOUT) {\n\t\tint loop = 0;\n\t\twhile (http2_ctx_want_write(ctx) && loop++ < 10) {\n\t\t\tif (http2_ctx_poll(ctx, NULL, 0, NULL) < 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Handle EPOLLIN - read and process data */\n\tif (event->events & EPOLLIN) {\n\t\tstruct http2_poll_item poll_items[10];\n\t\tint poll_count = 0;\n\t\tint loop_count = 0;\n\t\tconst int MAX_LOOP_COUNT = 512;\n\n\t\t/* Ensure handshake is complete */\n\t\tret = http2_ctx_handshake(ctx);\n\t\tif (ret < 0) {\n\t\t\tconst char *err_msg = http2_error_to_string(ret);\n\t\t\tint log_level = TLOG_ERROR;\n\t\t\tif (ret == HTTP2_ERR_EOF || ret == HTTP2_ERR_HTTP1) {\n\t\t\t\tlog_level = TLOG_DEBUG; /* Less noisy for clients that disconnect early or misbehave */\n\t\t\t}\n\t\t\ttlog(log_level, \"http2 handshake failed, ret=%d (%s), alpn=%s.\", ret, err_msg, tls_client->alpn_selected);\n\t\t\treturn -1;\n\t\t} else if (ret == 0) {\n\t\t\t/* Handshake in progress */\n\t\t\tgoto update_epoll;\n\t\t}\n\n\t\t/* Poll and process */\n\t\twhile (loop_count++ < MAX_LOOP_COUNT) {\n\t\t\tpoll_count = 0;\n\t\t\tret = http2_ctx_poll_readable(ctx, poll_items, 10, &poll_count);\n\t\t\tif (ret < 0) {\n\t\t\t\tif (ret == HTTP2_ERR_EAGAIN) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (ret == HTTP2_ERR_EOF) {\n\t\t\t\t\t/* Connection closed by peer */\n\t\t\t\t\t_dns_server_client_close(&tls_client->tcp.head);\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\ttlog(TLOG_DEBUG, \"http2 poll failed, %s\", http2_error_to_string(ret));\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tif (poll_count == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (int i = 0; i < poll_count; i++) {\n\t\t\t\tif (poll_items[i].stream == NULL) {\n\t\t\t\t\tif (poll_items[i].readable) {\n\t\t\t\t\t\tstruct http2_stream *stream = http2_ctx_accept_stream(ctx);\n\t\t\t\t\t\tif (stream) {\n\t\t\t\t\t\t\t/* Accept and immediately process new HTTP/2 stream */\n\t\t\t\t\t\t\t_dns_server_http2_process_stream(tls_client, stream);\n\t\t\t\t\t\t\thttp2_stream_put(stream);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (poll_items[i].stream && poll_items[i].readable) {\n\t\t\t\t\t_dns_server_http2_process_stream(tls_client, poll_items[i].stream);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (poll_items[i].stream) {\n\t\t\t\t\thttp2_stream_put(poll_items[i].stream); /* Release poll reference */\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\nupdate_epoll:\n\t/* Update epoll events */\n\t{\n\t\tint epoll_events = EPOLLIN;\n\t\tif (http2_ctx_want_write(ctx)) {\n\t\t\tepoll_events |= EPOLLOUT;\n\t\t}\n\n\t\tstruct epoll_event mod_event;\n\t\tmemset(&mod_event, 0, sizeof(mod_event));\n\t\tmod_event.events = epoll_events;\n\t\tmod_event.data.ptr = tls_client;\n\n\t\tif (epoll_ctl(server.epoll_fd, EPOLL_CTL_MOD, tls_client->tcp.head.fd, &mod_event) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/server_http2.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n *************************************************************************/\n\n#ifndef _SERVER_HTTP2_H_\n#define _SERVER_HTTP2_H_\n\n#include \"dns_server.h\"\n#include \"smartdns/http2.h\"\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct dns_server_conn_http2_stream {\n\tstruct dns_server_conn_head head;\n\tstruct http2_stream *stream;\n\tstruct dns_server_conn_tls_client *tls_client;\n};\n\nint _dns_server_process_http2(struct dns_server_conn_tls_client *tls_client, struct epoll_event *event,\n\t\t\t\t\t\t\t  unsigned long now);\n\nint _dns_server_reply_http2(struct dns_request *request, struct dns_server_conn_http2_stream *stream_conn,\n\t\t\t\t\t\t\tunsigned char *inpacket, int inpacket_len);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "src/dns_server/server_https.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"server_https.h\"\n#include \"connection.h\"\n#include \"dns_server.h\"\n#include \"server_socket.h\"\n#include \"server_tcp.h\"\n#include \"server_tls.h\"\n\n#include \"smartdns/http2.h\"\n\n#include <errno.h>\n#include <string.h>\n\nint _dns_server_reply_http_error(struct dns_server_conn_tcp_client *tcpclient, int code, const char *code_msg,\n\t\t\t\t\t\t\t\t const char *message)\n{\n\tint send_len = 0;\n\tint http_len = 0;\n\tunsigned char data[DNS_IN_PACKSIZE];\n\tint msg_len = strlen(message);\n\n\thttp_len = snprintf((char *)data, DNS_IN_PACKSIZE,\n\t\t\t\t\t\t\"HTTP/1.1 %d %s\\r\\n\"\n\t\t\t\t\t\t\"Content-Length: %d\\r\\n\"\n\t\t\t\t\t\t\"\\r\\n\"\n\t\t\t\t\t\t\"%s\\r\\n\",\n\t\t\t\t\t\tcode, code_msg, msg_len + 2, message);\n\n\tsend_len = _dns_server_tcp_socket_send(tcpclient, data, http_len);\n\tif (send_len < 0) {\n\t\tif (errno == EAGAIN) {\n\t\t\t/* save data to buffer, and retry when EPOLLOUT is available */\n\t\t\treturn _dns_server_reply_tcp_to_buffer(tcpclient, data, http_len);\n\t\t}\n\t\treturn -1;\n\t} else if (send_len < http_len) {\n\t\t/* save remain data to buffer, and retry when EPOLLOUT is available */\n\t\treturn _dns_server_reply_tcp_to_buffer(tcpclient, data + send_len, http_len - send_len);\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_reply_https(struct dns_request *request, struct dns_server_conn_tcp_client *tcpclient, void *packet,\n\t\t\t\t\t\t\tunsigned short len)\n{\n\tint send_len = 0;\n\tint http_len = 0;\n\tunsigned char inpacket_data[DNS_IN_PACKSIZE];\n\tunsigned char *inpacket = inpacket_data;\n\n\tif (len > sizeof(inpacket_data)) {\n\t\ttlog(TLOG_ERROR, \"packet size is invalid.\");\n\t\treturn -1;\n\t}\n\n\thttp_len = snprintf((char *)inpacket, DNS_IN_PACKSIZE,\n\t\t\t\t\t\t\"HTTP/1.1 200 OK\\r\\n\"\n\t\t\t\t\t\t\"Content-Type: application/dns-message\\r\\n\"\n\t\t\t\t\t\t\"Content-Length: %d\\r\\n\"\n\t\t\t\t\t\t\"\\r\\n\",\n\t\t\t\t\t\tlen);\n\tif (http_len < 0 || http_len >= DNS_IN_PACKSIZE) {\n\t\ttlog(TLOG_ERROR, \"http header size is invalid.\");\n\t\treturn -1;\n\t}\n\n\tmemcpy(inpacket + http_len, packet, len);\n\thttp_len += len;\n\n\tsend_len = _dns_server_tcp_socket_send(tcpclient, inpacket, http_len);\n\tif (send_len < 0) {\n\t\tif (errno == EAGAIN) {\n\t\t\t/* save data to buffer, and retry when EPOLLOUT is available */\n\t\t\treturn _dns_server_reply_tcp_to_buffer(tcpclient, inpacket, http_len);\n\t\t}\n\t\treturn -1;\n\t} else if (send_len < http_len) {\n\t\t/* save remain data to buffer, and retry when EPOLLOUT is available */\n\t\treturn _dns_server_reply_tcp_to_buffer(tcpclient, inpacket + send_len, http_len - send_len);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/dns_server/server_https.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_HTTPS_\n#define _DNS_SERVER_HTTPS_\n\n#include \"dns_server.h\"\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_reply_http_error(struct dns_server_conn_tcp_client *tcpclient, int code, const char *code_msg,\n\t\t\t\t\t\t\t\t const char *message);\n\nint _dns_server_reply_https(struct dns_request *request, struct dns_server_conn_tcp_client *tcpclient, void *packet,\n\t\t\t\t\t\t\tunsigned short len);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/server_socket.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"server_socket.h\"\n#include \"dns_server.h\"\n\n#include <arpa/inet.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <net/if.h>\n#include <netdb.h>\n#include <netinet/in.h>\n#include <netinet/ip.h>\n#include <netinet/tcp.h>\n#include <string.h>\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <sys/types.h>\n\nstatic struct addrinfo *_dns_server_getaddr(const char *host, const char *port, int type, int protocol)\n{\n\tstruct addrinfo hints;\n\tstruct addrinfo *result = NULL;\n\n\tmemset(&hints, 0, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = type;\n\thints.ai_protocol = protocol;\n\thints.ai_flags = AI_PASSIVE;\n\tconst int s = getaddrinfo(host, port, &hints, &result);\n\tif (s != 0) {\n\t\tconst char *error_str;\n\t\tif (s == EAI_SYSTEM) {\n\t\t\terror_str = strerror(errno);\n\t\t} else {\n\t\t\terror_str = gai_strerror(s);\n\t\t}\n\t\ttlog(TLOG_ERROR, \"get addr info failed. %s.\\n\", error_str);\n\t\tgoto errout;\n\t}\n\n\treturn result;\nerrout:\n\tif (result) {\n\t\tfreeaddrinfo(result);\n\t}\n\treturn NULL;\n}\n\nint _dns_create_socket(const char *host_ip, int type)\n{\n\tint fd = -1;\n\tstruct addrinfo *gai = NULL;\n\tchar port_str[16];\n\tchar ip[MAX_IP_LEN];\n\tchar host_ip_device[MAX_IP_LEN * 2];\n\tint port = 0;\n\tchar *host = NULL;\n\tint optval = 1;\n\tint yes = 1;\n\tconst int priority = SOCKET_PRIORITY;\n\tconst int ip_tos = SOCKET_IP_TOS;\n\tconst char *ifname = NULL;\n\n\tsafe_strncpy(host_ip_device, host_ip, sizeof(host_ip_device));\n\tifname = strstr(host_ip_device, \"@\");\n\tif (ifname) {\n\t\t*(char *)ifname = '\\0';\n\t\tifname++;\n\t}\n\n\tif (parse_ip(host_ip_device, ip, &port) == 0) {\n\t\thost = ip;\n\t}\n\n\tif (port <= 0) {\n\t\tport = DEFAULT_DNS_PORT;\n\t}\n\n\tsnprintf(port_str, sizeof(port_str), \"%d\", port);\n\tgai = _dns_server_getaddr(host, port_str, type, 0);\n\tif (gai == NULL) {\n\t\ttlog(TLOG_ERROR, \"get address failed.\");\n\t\tgoto errout;\n\t}\n\n\tfd = socket(gai->ai_family, gai->ai_socktype, gai->ai_protocol);\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"create socket failed, family = %d, type = %d, proto = %d, %s\\n\", gai->ai_family,\n\t\t\t gai->ai_socktype, gai->ai_protocol, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (type == SOCK_STREAM) {\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"set socket opt failed.\");\n\t\t\tgoto errout;\n\t\t}\n\t\t/* enable TCP_FASTOPEN */\n\t\tsetsockopt(fd, SOL_TCP, TCP_FASTOPEN, &optval, sizeof(optval));\n\t\tsetsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));\n\t} else {\n\t\tsetsockopt(fd, IPPROTO_IP, IP_PKTINFO, &optval, sizeof(optval));\n\t\tsetsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &optval, sizeof(optval));\n\t}\n\tsetsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority));\n\tsetsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));\n\tif (dns_conf.dns_socket_buff_size > 0) {\n\t\tsetsockopt(fd, SOL_SOCKET, SO_SNDBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size));\n\t\tsetsockopt(fd, SOL_SOCKET, SO_RCVBUF, &dns_conf.dns_socket_buff_size, sizeof(dns_conf.dns_socket_buff_size));\n\t}\n\n\tif (ifname != NULL) {\n\t\tstruct ifreq ifr;\n\t\tmemset(&ifr, 0, sizeof(struct ifreq));\n\t\tsafe_strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));\n\t\tioctl(fd, SIOCGIFINDEX, &ifr);\n\t\tif (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(struct ifreq)) < 0) {\n\t\t\ttlog(TLOG_ERROR, \"bind socket to device %s failed, %s\\n\", ifr.ifr_name, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tif (bind(fd, gai->ai_addr, gai->ai_addrlen) != 0) {\n\t\ttlog(TLOG_ERROR, \"bind service %s failed, %s\\n\", host_ip, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (type == SOCK_STREAM) {\n\t\tif (listen(fd, 256) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"listen failed.\\n\");\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tfcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);\n\n\tfreeaddrinfo(gai);\n\n\treturn fd;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\n\ttlog(TLOG_ERROR, \"add server failed, host-ip: %s, type: %d\", host_ip, type);\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_server/server_socket.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_SOCKET_\n#define _DNS_SERVER_SOCKET_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_create_socket(const char *host_ip, int type);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/server_tcp.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"server_tcp.h\"\n#include \"connection.h\"\n#include \"dns_server.h\"\n#include \"server_https.h\"\n#include \"server_socket.h\"\n#include \"server_tls.h\"\n\n#include \"smartdns/http_parse.h\"\n\n#include <errno.h>\n#include <netinet/tcp.h>\n#include <string.h>\n#include <sys/epoll.h>\n\nint _dns_server_reply_tcp_to_buffer(struct dns_server_conn_tcp_client *tcpclient, void *packet, int len)\n{\n\tif ((int)sizeof(tcpclient->sndbuff.buf) - tcpclient->sndbuff.size < len) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(tcpclient->sndbuff.buf + tcpclient->sndbuff.size, packet, len);\n\ttcpclient->sndbuff.size += len;\n\n\tif (tcpclient->head.fd <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (_dns_server_epoll_ctl(&tcpclient->head, EPOLL_CTL_MOD, EPOLLIN | EPOLLOUT) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_reply_tcp(struct dns_request *request, struct dns_server_conn_tcp_client *tcpclient, void *packet,\n\t\t\t\t\t\t  unsigned short len)\n{\n\tint send_len = 0;\n\tunsigned char inpacket_data[DNS_IN_PACKSIZE];\n\tunsigned char *inpacket = inpacket_data;\n\n\tif (len > sizeof(inpacket_data) - 2) {\n\t\ttlog(TLOG_ERROR, \"packet size is invalid.\");\n\t\treturn -1;\n\t}\n\n\t/* TCP query format\n\t * | len (short) | dns query data |\n\t */\n\t*((unsigned short *)(inpacket)) = htons(len);\n\tmemcpy(inpacket + 2, packet, len);\n\tlen += 2;\n\n\tsend_len = _dns_server_tcp_socket_send(tcpclient, inpacket, len);\n\tif (send_len < 0) {\n\t\tif (errno == EAGAIN) {\n\t\t\t/* save data to buffer, and retry when EPOLLOUT is available */\n\t\t\treturn _dns_server_reply_tcp_to_buffer(tcpclient, inpacket, len);\n\t\t}\n\t\treturn -1;\n\t} else if (send_len < len) {\n\t\t/* save remain data to buffer, and retry when EPOLLOUT is available */\n\t\treturn _dns_server_reply_tcp_to_buffer(tcpclient, inpacket + send_len, len - send_len);\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_tcp_accept(struct dns_server_conn_tcp_server *tcpserver, struct epoll_event *event, unsigned long now)\n{\n\tstruct sockaddr_storage addr;\n\tstruct dns_server_conn_tcp_client *tcpclient = NULL;\n\tsocklen_t addr_len = sizeof(addr);\n\tint fd = -1;\n\n\tfd = accept4(tcpserver->head.fd, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC);\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"accept failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\ttcpclient = zalloc(1, sizeof(*tcpclient));\n\tif (tcpclient == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc for tcpclient failed.\");\n\t\tgoto errout;\n\t}\n\t_dns_server_conn_head_init(&tcpclient->head, fd, DNS_CONN_TYPE_TCP_CLIENT);\n\ttcpclient->head.server_flags = tcpserver->head.server_flags;\n\ttcpclient->head.dns_group = tcpserver->head.dns_group;\n\ttcpclient->head.ipset_nftset_rule = tcpserver->head.ipset_nftset_rule;\n\ttcpclient->conn_idle_timeout = dns_conf.tcp_idle_time;\n\n\tmemcpy(&tcpclient->addr, &addr, addr_len);\n\ttcpclient->addr_len = addr_len;\n\ttcpclient->localaddr_len = sizeof(struct sockaddr_storage);\n\tif (_dns_server_epoll_ctl(&tcpclient->head, EPOLL_CTL_ADD, EPOLLIN) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed.\");\n\t\treturn -1;\n\t}\n\n\tif (getsocket_inet(tcpclient->head.fd, (struct sockaddr *)&tcpclient->localaddr, &tcpclient->localaddr_len) != 0) {\n\t\ttlog(TLOG_ERROR, \"get local addr failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\t_dns_server_client_touch(&tcpclient->head);\n\n\tpthread_mutex_lock(&server.conn_list_lock);\n\tlist_add(&tcpclient->head.list, &server.conn_list);\n\tpthread_mutex_unlock(&server.conn_list_lock);\n\t_dns_server_conn_get(&tcpclient->head);\n\n\tset_sock_keepalive(fd, 30, 3, 5);\n\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\tif (tcpclient) {\n\t\tfree(tcpclient);\n\t}\n\treturn -1;\n}\n\nint _dns_server_tcp_socket_send(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len)\n{\n\tif (tcp_client->head.type == DNS_CONN_TYPE_TCP_CLIENT) {\n\t\treturn send(tcp_client->head.fd, data, data_len, MSG_NOSIGNAL);\n\t} else if (tcp_client->head.type == DNS_CONN_TYPE_TLS_CLIENT ||\n\t\t\t   tcp_client->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcp_client;\n\t\ttls_client->ssl_want_write = 0;\n\t\tint ret = _dns_server_socket_ssl_send(tls_client, data, data_len);\n\t\tif (ret < 0 && errno == EAGAIN) {\n\t\t\tif (_dns_server_ssl_poll_event(tls_client, SSL_ERROR_WANT_WRITE) == 0) {\n\t\t\t\terrno = EAGAIN;\n\t\t\t}\n\t\t}\n\t\treturn ret;\n\t} else {\n\t\treturn -1;\n\t}\n}\n\nint _dns_server_tcp_socket_recv(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len)\n{\n\tif (tcp_client->head.type == DNS_CONN_TYPE_TCP_CLIENT) {\n\t\treturn recv(tcp_client->head.fd, data, data_len, MSG_NOSIGNAL);\n\t} else if (tcp_client->head.type == DNS_CONN_TYPE_TLS_CLIENT ||\n\t\t\t   tcp_client->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcp_client;\n\t\tint ret = _dns_server_socket_ssl_recv(tls_client, data, data_len);\n\t\tif (ret == -SSL_ERROR_WANT_WRITE && errno == EAGAIN) {\n\t\t\tif (_dns_server_ssl_poll_event(tls_client, SSL_ERROR_WANT_WRITE) == 0) {\n\t\t\t\terrno = EAGAIN;\n\t\t\t\ttls_client->ssl_want_write = 1;\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t} else {\n\t\treturn -1;\n\t}\n}\n\nstatic int _dns_server_tcp_recv(struct dns_server_conn_tcp_client *tcpclient)\n{\n\tssize_t len = 0;\n\n\t/* Receive data */\n\twhile (tcpclient->recvbuff.size < (int)sizeof(tcpclient->recvbuff.buf)) {\n\t\tif (tcpclient->recvbuff.size == (int)sizeof(tcpclient->recvbuff.buf)) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (unlikely(tcpclient->recvbuff.size < 0)) {\n\t\t\tBUG(\"recv buffer size is invalid.\");\n\t\t}\n\n\t\tlen = _dns_server_tcp_socket_recv(tcpclient, tcpclient->recvbuff.buf + tcpclient->recvbuff.size,\n\t\t\t\t\t\t\t\t\t\t  sizeof(tcpclient->recvbuff.buf) - tcpclient->recvbuff.size);\n\t\tif (len < 0) {\n\t\t\tif (errno == EAGAIN) {\n\t\t\t\treturn RECV_ERROR_AGAIN;\n\t\t\t}\n\n\t\t\tif (errno == ECONNRESET) {\n\t\t\t\treturn RECV_ERROR_CLOSE;\n\t\t\t}\n\n\t\t\tif (errno == ETIMEDOUT) {\n\t\t\t\treturn RECV_ERROR_CLOSE;\n\t\t\t}\n\n\t\t\ttlog(TLOG_DEBUG, \"recv failed, %s\\n\", strerror(errno));\n\t\t\treturn RECV_ERROR_FAIL;\n\t\t} else if (len == 0) {\n\t\t\treturn RECV_ERROR_CLOSE;\n\t\t}\n\n\t\ttcpclient->recvbuff.size += len;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_server_tcp_process_one_request(struct dns_server_conn_tcp_client *tcpclient)\n{\n\tunsigned short request_len = 0;\n\tint total_len = tcpclient->recvbuff.size;\n\tint proceed_len = 0;\n\tunsigned char *request_data = NULL;\n\tint ret = RECV_ERROR_FAIL;\n\tint len = 0;\n\tstruct http_head *http_head = NULL;\n\tuint8_t *http_decode_data = NULL;\n\tchar *base64_query = NULL;\n\n\t/* Handling multiple requests */\n\tfor (;;) {\n\t\tret = RECV_ERROR_FAIL;\n\n\t\tif (proceed_len > tcpclient->recvbuff.size) {\n\t\t\ttlog(TLOG_DEBUG, \"proceed_len > recvbuff.size\");\n\t\t\tgoto out;\n\t\t}\n\n\t\tif (tcpclient->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\t\tif ((total_len - proceed_len) <= 0) {\n\t\t\t\tret = RECV_ERROR_AGAIN;\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\thttp_head = http_head_init(4096, HTTP_VERSION_1_1);\n\t\t\tif (http_head == NULL) {\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\tlen = http_head_parse(http_head, tcpclient->recvbuff.buf + proceed_len,\n\t\t\t\t\t\t\t\t  tcpclient->recvbuff.size - proceed_len);\n\t\t\tif (len < 0) {\n\t\t\t\tif (len == -1) {\n\t\t\t\t\tret = 0;\n\t\t\t\t\tgoto out;\n\t\t\t\t} else if (len == -3) {\n\t\t\t\t\ttcpclient->recvbuff.size = 0;\n\t\t\t\t\ttlog(TLOG_DEBUG, \"recv buffer is not enough.\");\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\n\t\t\t\ttlog(TLOG_DEBUG, \"parser http header failed.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (http_head_get_method(http_head) == HTTP_METHOD_POST) {\n\t\t\t\tconst char *content_type = http_head_get_fields_value(http_head, \"Content-Type\");\n\t\t\t\tif (content_type == NULL ||\n\t\t\t\t\tstrncasecmp(content_type, \"application/dns-message\", sizeof(\"application/dns-message\")) != 0) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"content type not supported, %s\", content_type);\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\n\t\t\t\trequest_len = http_head_get_data_len(http_head);\n\t\t\t\tif (request_len >= len) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"request length is invalid.\");\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\trequest_data = (unsigned char *)http_head_get_data(http_head);\n\t\t\t} else if (http_head_get_method(http_head) == HTTP_METHOD_GET) {\n\t\t\t\tconst char *path = http_head_get_url(http_head);\n\t\t\t\tif (path == NULL || strncasecmp(path, \"/dns-query\", sizeof(\"/dns-query\")) != 0) {\n\t\t\t\t\tret = RECV_ERROR_BAD_PATH;\n\t\t\t\t\ttlog(TLOG_DEBUG, \"path not supported, %s\", path);\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\n\t\t\t\tconst char *dns_query = http_head_get_params_value(http_head, \"dns\");\n\t\t\t\tif (dns_query == NULL) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"query is null.\");\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\n\t\t\t\tif (base64_query == NULL) {\n\t\t\t\t\tbase64_query = malloc(DNS_IN_PACKSIZE);\n\t\t\t\t\tif (base64_query == NULL) {\n\t\t\t\t\t\ttlog(TLOG_DEBUG, \"malloc failed.\");\n\t\t\t\t\t\tgoto errout;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (urldecode(base64_query, DNS_IN_PACKSIZE, dns_query) < 0) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"urldecode query failed.\");\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\n\t\t\t\tif (http_decode_data == NULL) {\n\t\t\t\t\thttp_decode_data = malloc(DNS_IN_PACKSIZE);\n\t\t\t\t\tif (http_decode_data == NULL) {\n\t\t\t\t\t\ttlog(TLOG_DEBUG, \"malloc failed.\");\n\t\t\t\t\t\tgoto errout;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tint decode_len = SSL_base64_decode_ext(base64_query, http_decode_data, DNS_IN_PACKSIZE, 1, 1);\n\t\t\t\tif (decode_len <= 0) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"decode query failed.\");\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\n\t\t\t\trequest_len = decode_len;\n\t\t\t\trequest_data = http_decode_data;\n\t\t\t} else {\n\t\t\t\ttlog(TLOG_DEBUG, \"http method is invalid.\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tproceed_len += len;\n\t\t} else {\n\t\t\tif ((total_len - proceed_len) <= (int)sizeof(unsigned short)) {\n\t\t\t\tret = RECV_ERROR_AGAIN;\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\t/* Get record length */\n\t\t\trequest_data = (unsigned char *)(tcpclient->recvbuff.buf + proceed_len);\n\t\t\tunsigned short request_data_len;\n\t\t\tmemcpy(&request_data_len, request_data, sizeof(unsigned short));\n\t\t\trequest_len = ntohs(request_data_len);\n\n\t\t\tif (request_len >= sizeof(tcpclient->recvbuff.buf)) {\n\t\t\t\ttlog(TLOG_DEBUG, \"request length is invalid. len = %d\", request_len);\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (request_len > (total_len - proceed_len - sizeof(unsigned short))) {\n\t\t\t\tret = RECV_ERROR_AGAIN;\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\trequest_data = (unsigned char *)(tcpclient->recvbuff.buf + proceed_len + sizeof(unsigned short));\n\t\t\tproceed_len += sizeof(unsigned short) + request_len;\n\t\t}\n\n\t\t/* process one record */\n\t\tret = _dns_server_recv(&tcpclient->head, request_data, request_len, &tcpclient->localaddr,\n\t\t\t\t\t\t\t   tcpclient->localaddr_len, &tcpclient->addr, tcpclient->addr_len);\n\t\tif (ret != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (http_head != NULL) {\n\t\t\thttp_head_destroy(http_head);\n\t\t\thttp_head = NULL;\n\t\t}\n\t}\n\nout:\n\tif (total_len > proceed_len && proceed_len > 0) {\n\t\tmemmove(tcpclient->recvbuff.buf, tcpclient->recvbuff.buf + proceed_len, total_len - proceed_len);\n\t}\n\n\ttcpclient->recvbuff.size -= proceed_len;\n\nerrout:\n\tif (http_head) {\n\t\thttp_head_destroy(http_head);\n\t}\n\n\tif (http_decode_data) {\n\t\tfree(http_decode_data);\n\t}\n\n\tif (base64_query) {\n\t\tfree(base64_query);\n\t}\n\n\tif (tcpclient->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\tif (ret == RECV_ERROR_BAD_PATH) {\n\t\t\t_dns_server_reply_http_error(tcpclient, 404, \"Not Found\", \"Not Found\");\n\t\t} else if (ret == RECV_ERROR_FAIL || ret == RECV_ERROR_INVALID_PACKET) {\n\t\t\t_dns_server_reply_http_error(tcpclient, 400, \"Bad Request\", \"Bad Request\");\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nint _dns_server_tcp_process_requests(struct dns_server_conn_tcp_client *tcpclient)\n{\n\tint recv_ret = 0;\n\tint request_ret = 0;\n\tint is_eof = 0;\n\tint i = 0;\n\n\tfor (i = 0; i < 32; i++) {\n\t\trecv_ret = _dns_server_tcp_recv(tcpclient);\n\t\tif (recv_ret < 0) {\n\t\t\tif (recv_ret == RECV_ERROR_CLOSE) {\n\t\t\t\treturn RECV_ERROR_CLOSE;\n\t\t\t}\n\n\t\t\tif (tcpclient->recvbuff.size > 0) {\n\t\t\t\tis_eof = RECV_ERROR_AGAIN;\n\t\t\t} else {\n\t\t\t\treturn RECV_ERROR_FAIL;\n\t\t\t}\n\t\t}\n\n\t\trequest_ret = _dns_server_tcp_process_one_request(tcpclient);\n\t\tif (request_ret < 0) {\n\t\t\t/* failed */\n\t\t\ttlog(TLOG_DEBUG, \"process one request failed.\");\n\t\t\treturn RECV_ERROR_FAIL;\n\t\t}\n\n\t\tif (request_ret == RECV_ERROR_AGAIN && is_eof == RECV_ERROR_AGAIN) {\n\t\t\t/* failed or remote shutdown */\n\t\t\treturn RECV_ERROR_FAIL;\n\t\t}\n\n\t\tif (recv_ret == RECV_ERROR_AGAIN && request_ret == RECV_ERROR_AGAIN) {\n\t\t\t/* process complete */\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_server_tls_want_write(struct dns_server_conn_tcp_client *tcpclient)\n{\n\tif (tcpclient->head.type == DNS_CONN_TYPE_TLS_CLIENT || tcpclient->head.type == DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\tstruct dns_server_conn_tls_client *tls_client = (struct dns_server_conn_tls_client *)tcpclient;\n\t\tif (tls_client->ssl_want_write == 1) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_server_tcp_send(struct dns_server_conn_tcp_client *tcpclient)\n{\n\tint len = 0;\n\twhile (tcpclient->sndbuff.size > 0 || _dns_server_tls_want_write(tcpclient) == 1) {\n\t\tlen = _dns_server_tcp_socket_send(tcpclient, tcpclient->sndbuff.buf, tcpclient->sndbuff.size);\n\t\tif (len < 0) {\n\t\t\tif (errno == EAGAIN) {\n\t\t\t\treturn RECV_ERROR_AGAIN;\n\t\t\t}\n\t\t\treturn RECV_ERROR_FAIL;\n\t\t} else if (len == 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\ttcpclient->sndbuff.size -= len;\n\t}\n\n\tif (_dns_server_epoll_ctl(&tcpclient->head, EPOLL_CTL_MOD, EPOLLIN) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed.\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_process_tcp(struct dns_server_conn_tcp_client *dnsserver, struct epoll_event *event, unsigned long now)\n{\n\tint ret = 0;\n\n\tif (event->events & EPOLLIN) {\n\t\tret = _dns_server_tcp_process_requests(dnsserver);\n\t\tif (ret != 0) {\n\t\t\t_dns_server_client_close(&dnsserver->head);\n\t\t\tif (ret == RECV_ERROR_CLOSE) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\ttlog(TLOG_DEBUG, \"process tcp request failed.\");\n\t\t\treturn RECV_ERROR_FAIL;\n\t\t}\n\t}\n\n\tif (event->events & EPOLLOUT) {\n\t\tif (_dns_server_tcp_send(dnsserver) != 0) {\n\t\t\t_dns_server_client_close(&dnsserver->head);\n\t\t\ttlog(TLOG_DEBUG, \"send tcp failed.\");\n\t\t\treturn RECV_ERROR_FAIL;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nvoid _dns_server_tcp_idle_check(void)\n{\n\tstruct dns_server_conn_head *conn = NULL;\n\tstruct dns_server_conn_head *tmp = NULL;\n\ttime_t now = 0;\n\n\ttime(&now);\n\tpthread_mutex_lock(&server.conn_list_lock);\n\tlist_for_each_entry_safe(conn, tmp, &server.conn_list, list)\n\t{\n\t\tif (conn->type != DNS_CONN_TYPE_TCP_CLIENT && conn->type != DNS_CONN_TYPE_TLS_CLIENT &&\n\t\t\tconn->type != DNS_CONN_TYPE_HTTPS_CLIENT) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tstruct dns_server_conn_tcp_client *tcpclient = (struct dns_server_conn_tcp_client *)conn;\n\n\t\tif (tcpclient->conn_idle_timeout <= 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (conn->last_request_time > now - tcpclient->conn_idle_timeout) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_dns_server_client_close(conn);\n\t}\n\tpthread_mutex_unlock(&server.conn_list_lock);\n}\n\nint _dns_server_socket_tcp(struct dns_bind_ip *bind_ip)\n{\n\tconst char *host_ip = NULL;\n\tstruct dns_server_conn_tcp_server *conn = NULL;\n\tint fd = -1;\n\tconst int on = 1;\n\n\thost_ip = bind_ip->ip;\n\n\tfd = _dns_create_socket(host_ip, SOCK_STREAM);\n\tif (fd <= 0) {\n\t\tgoto errout;\n\t}\n\n\tsetsockopt(fd, SOL_TCP, TCP_FASTOPEN, &on, sizeof(on));\n\n\tconn = zalloc(1, sizeof(struct dns_server_conn_tcp_server));\n\tif (conn == NULL) {\n\t\tgoto errout;\n\t}\n\t_dns_server_conn_head_init(&conn->head, fd, DNS_CONN_TYPE_TCP_SERVER);\n\t_dns_server_set_flags(&conn->head, bind_ip);\n\t_dns_server_conn_get(&conn->head);\n\n\treturn 0;\nerrout:\n\tif (conn) {\n\t\tfree(conn);\n\t\tconn = NULL;\n\t}\n\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_server/server_tcp.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_TCP_\n#define _DNS_SERVER_TCP_\n\n#include \"dns_server.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_reply_tcp_to_buffer(struct dns_server_conn_tcp_client *tcpclient, void *packet, int len);\n\nint _dns_server_tcp_socket_send(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len);\n\nint _dns_server_tcp_accept(struct dns_server_conn_tcp_server *tcpserver, struct epoll_event *event, unsigned long now);\n\nint _dns_server_tcp_socket_recv(struct dns_server_conn_tcp_client *tcp_client, void *data, int data_len);\n\nint _dns_server_tcp_process_requests(struct dns_server_conn_tcp_client *tcpclient);\n\nint _dns_server_process_tcp(struct dns_server_conn_tcp_client *dnsserver, struct epoll_event *event, unsigned long now);\n\nint _dns_server_reply_tcp(struct dns_request *request, struct dns_server_conn_tcp_client *tcpclient, void *packet,\n\t\t\t\t\t\t  unsigned short len);\n\nvoid _dns_server_tcp_idle_check(void);\n\nint _dns_server_socket_tcp(struct dns_bind_ip *bind_ip);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/server_tls.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"server_tls.h\"\n#include \"connection.h\"\n#include \"dns_server.h\"\n#include \"server_https.h\"\n#include \"server_socket.h\"\n#include \"server_tcp.h\"\n#include \"server_http2.h\"\n\n#include \"smartdns/http2.h\"\n\n#include <errno.h>\n#include <netinet/tcp.h>\n#include <openssl/err.h>\n#include <openssl/ssl.h>\n#include <pthread.h>\n#include <string.h>\n#include <sys/epoll.h>\n\nstatic int alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in,\n\t\t\t\t\t\t  unsigned int inlen, void *arg)\n{\n\tstruct dns_bind_ip *bind_ip = (struct dns_bind_ip *)arg;\n\tconst char *alpn = bind_ip->alpn;\n\tif (alpn[0] == '\\0') {\n\t\talpn = \"h2,http/1.1\";\n\t}\n\n\t/* Parse server ALPN list */\n\tchar alpn_copy[256];\n\tsafe_strncpy(alpn_copy, alpn, sizeof(alpn_copy));\n\tchar *saveptr = NULL;\n\tchar *proto = strtok_r(alpn_copy, \",\", &saveptr);\n\n\twhile (proto) {\n\t\tunsigned int proto_len = strlen(proto);\n\t\tfor (unsigned int i = 0; i < inlen;) {\n\t\t\tunsigned int len = in[i++];\n\t\t\tif (i + len > inlen) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (len == proto_len && memcmp(&in[i], proto, len) == 0) {\n\t\t\t\t*out = &in[i];\n\t\t\t\t*outlen = len;\n\t\t\t\treturn SSL_TLSEXT_ERR_OK;\n\t\t\t}\n\t\t\ti += len;\n\t\t}\n\t\tproto = strtok_r(NULL, \",\", &saveptr);\n\t}\n\n\t/* No match found */\n\ttlog(TLOG_DEBUG, \"ALPN negotiation failed: no matching protocol found\");\n\treturn SSL_TLSEXT_ERR_NOACK;\n}\n\nstatic ssize_t _ssl_read(struct dns_server_conn_tls_client *conn, void *buff, int num)\n{\n\tssize_t ret = 0;\n\tpthread_mutex_lock(&conn->ssl_lock);\n\tif (conn == NULL || buff == NULL) {\n\t\tpthread_mutex_unlock(&conn->ssl_lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n\tret = SSL_read(conn->ssl, buff, num);\n\tpthread_mutex_unlock(&conn->ssl_lock);\n\treturn ret;\n}\n\nstatic ssize_t _ssl_write(struct dns_server_conn_tls_client *conn, const void *buff, int num)\n{\n\tssize_t ret = 0;\n\tpthread_mutex_lock(&conn->ssl_lock);\n\tif (conn == NULL || buff == NULL || conn->ssl == NULL) {\n\t\tpthread_mutex_unlock(&conn->ssl_lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n\tret = SSL_write(conn->ssl, buff, num);\n\tpthread_mutex_unlock(&conn->ssl_lock);\n\treturn ret;\n}\n\nstatic int _ssl_get_error(struct dns_server_conn_tls_client *conn, int ret)\n{\n\tint err = 0;\n\tpthread_mutex_lock(&conn->ssl_lock);\n\tif (conn == NULL || conn->ssl == NULL) {\n\t\tpthread_mutex_unlock(&conn->ssl_lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n\terr = SSL_get_error(conn->ssl, ret);\n\tpthread_mutex_unlock(&conn->ssl_lock);\n\treturn err;\n}\n\nstatic int _ssl_do_accept(struct dns_server_conn_tls_client *conn)\n{\n\tint err = 0;\n\tpthread_mutex_lock(&conn->ssl_lock);\n\tif (conn == NULL || conn->ssl == NULL) {\n\t\tpthread_mutex_unlock(&conn->ssl_lock);\n\t\treturn SSL_ERROR_SYSCALL;\n\t}\n\n\terr = SSL_accept(conn->ssl);\n\tpthread_mutex_unlock(&conn->ssl_lock);\n\treturn err;\n}\n\nint _dns_server_socket_ssl_send(struct dns_server_conn_tls_client *tls_client, const void *buf, int num)\n{\n\tint ret = 0;\n\tint ssl_ret = 0;\n\tunsigned long ssl_err = 0;\n\n\tif (tls_client->ssl == NULL) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tif (num < 0) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tret = _ssl_write(tls_client, buf, num);\n\tif (ret > 0) {\n\t\treturn ret;\n\t}\n\n\tssl_ret = _ssl_get_error(tls_client, ret);\n\tswitch (ssl_ret) {\n\tcase SSL_ERROR_NONE:\n\tcase SSL_ERROR_ZERO_RETURN:\n\t\treturn 0;\n\t\tbreak;\n\tcase SSL_ERROR_WANT_READ:\n\t\terrno = EAGAIN;\n\t\tret = -SSL_ERROR_WANT_READ;\n\t\tbreak;\n\tcase SSL_ERROR_WANT_WRITE:\n\t\terrno = EAGAIN;\n\t\tret = -SSL_ERROR_WANT_WRITE;\n\t\tbreak;\n\tcase SSL_ERROR_SSL:\n\t\tssl_err = ERR_get_error();\n\t\tint ssl_reason = ERR_GET_REASON(ssl_err);\n\t\tif (ssl_reason == SSL_R_UNINITIALIZED || ssl_reason == SSL_R_PROTOCOL_IS_SHUTDOWN ||\n\t\t\tssl_reason == SSL_R_BAD_LENGTH || ssl_reason == SSL_R_SHUTDOWN_WHILE_IN_INIT ||\n\t\t\tssl_reason == SSL_R_BAD_WRITE_RETRY) {\n\t\t\terrno = EAGAIN;\n\t\t\treturn -1;\n\t\t}\n\n\t\ttlog(TLOG_ERROR, \"SSL write fail error no:  %s(%d)\\n\", ERR_reason_error_string(ssl_err), ssl_reason);\n\t\terrno = EFAULT;\n\t\tret = -1;\n\t\tbreak;\n\tcase SSL_ERROR_SYSCALL:\n\t\tif (errno == 0) {\n\t\t\ttlog(TLOG_DEBUG, \"SSL connection closed\");\n\t\t} else {\n\t\t\ttlog(TLOG_DEBUG, \"SSL syscall failed, %s\", strerror(errno));\n\t\t}\n\t\treturn ret;\n\tdefault:\n\t\terrno = EFAULT;\n\t\tret = -1;\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nint _dns_server_socket_ssl_recv(struct dns_server_conn_tls_client *tls_client, void *buf, int num)\n{\n\tssize_t ret = 0;\n\tint ssl_ret = 0;\n\tunsigned long ssl_err = 0;\n\n\tif (tls_client->ssl == NULL) {\n\t\terrno = EFAULT;\n\t\treturn -1;\n\t}\n\n\tret = _ssl_read(tls_client, buf, num);\n\tif (ret > 0) {\n\t\treturn ret;\n\t}\n\n\tssl_ret = _ssl_get_error(tls_client, ret);\n\tswitch (ssl_ret) {\n\tcase SSL_ERROR_NONE:\n\tcase SSL_ERROR_ZERO_RETURN:\n\t\treturn 0;\n\t\tbreak;\n\tcase SSL_ERROR_WANT_READ:\n\t\terrno = EAGAIN;\n\t\tret = -SSL_ERROR_WANT_READ;\n\t\tbreak;\n\tcase SSL_ERROR_WANT_WRITE:\n\t\terrno = EAGAIN;\n\t\tret = -SSL_ERROR_WANT_WRITE;\n\t\tbreak;\n\tcase SSL_ERROR_SSL:\n\t\tssl_err = ERR_get_error();\n\t\tint ssl_reason = ERR_GET_REASON(ssl_err);\n\t\tif (ssl_reason == SSL_R_UNINITIALIZED) {\n\t\t\terrno = EAGAIN;\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (ssl_reason == SSL_R_SHUTDOWN_WHILE_IN_INIT || ssl_reason == SSL_R_PROTOCOL_IS_SHUTDOWN) {\n\t\t\treturn 0;\n\t\t}\n\n#ifdef SSL_R_UNEXPECTED_EOF_WHILE_READING\n\t\tif (ssl_reason == SSL_R_UNEXPECTED_EOF_WHILE_READING) {\n\t\t\treturn 0;\n\t\t}\n#endif\n\n\t\ttlog(TLOG_DEBUG, \"SSL read fail error no: %s(%lx), reason: %d\\n\", ERR_reason_error_string(ssl_err), ssl_err,\n\t\t\t ssl_reason);\n\t\terrno = EFAULT;\n\t\tret = -1;\n\t\tbreak;\n\tcase SSL_ERROR_SYSCALL:\n\t\tif (errno == 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tret = -1;\n\t\treturn ret;\n\tdefault:\n\t\terrno = EFAULT;\n\t\tret = -1;\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nint _dns_server_ssl_poll_event(struct dns_server_conn_tls_client *tls_client, int ssl_ret)\n{\n\tstruct epoll_event fd_event;\n\n\tmemset(&fd_event, 0, sizeof(fd_event));\n\n\tif (ssl_ret == SSL_ERROR_WANT_READ) {\n\t\tfd_event.events = EPOLLIN;\n\t} else if (ssl_ret == SSL_ERROR_WANT_WRITE) {\n\t\tfd_event.events = EPOLLOUT | EPOLLIN;\n\t} else {\n\t\tgoto errout;\n\t}\n\n\tfd_event.data.ptr = tls_client;\n\tif (epoll_ctl(server.epoll_fd, EPOLL_CTL_MOD, tls_client->tcp.head.fd, &fd_event) != 0) {\n\t\tif (errno == ENOENT) {\n\t\t\t/* fd not found, ignore */\n\t\t\treturn 0;\n\t\t}\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nint _dns_server_tls_accept(struct dns_server_conn_tls_server *tls_server, struct epoll_event *event, unsigned long now)\n{\n\tstruct sockaddr_storage addr;\n\tstruct dns_server_conn_tls_client *tls_client = NULL;\n\tDNS_CONN_TYPE conn_type;\n\tsocklen_t addr_len = sizeof(addr);\n\tint fd = -1;\n\tSSL *ssl = NULL;\n\n\tfd = accept4(tls_server->head.fd, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC);\n\tif (fd < 0) {\n\t\ttlog(TLOG_ERROR, \"accept failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\tif (tls_server->head.type == DNS_CONN_TYPE_TLS_SERVER) {\n\t\tconn_type = DNS_CONN_TYPE_TLS_CLIENT;\n\t} else if (tls_server->head.type == DNS_CONN_TYPE_HTTPS_SERVER) {\n\t\tconn_type = DNS_CONN_TYPE_HTTPS_CLIENT;\n\t} else {\n\t\ttlog(TLOG_ERROR, \"invalid http server type.\");\n\t\tgoto errout;\n\t}\n\n\ttls_client = zalloc(1, sizeof(*tls_client));\n\tif (tls_client == NULL) {\n\t\ttlog(TLOG_ERROR, \"malloc for tls_client failed.\");\n\t\tgoto errout;\n\t}\n\t_dns_server_conn_head_init(&tls_client->tcp.head, fd, conn_type);\n\ttls_client->tcp.head.server_flags = tls_server->head.server_flags;\n\ttls_client->tcp.head.dns_group = tls_server->head.dns_group;\n\ttls_client->tcp.head.ipset_nftset_rule = tls_server->head.ipset_nftset_rule;\n\ttls_client->tcp.conn_idle_timeout = dns_conf.tcp_idle_time;\n\n\tatomic_set(&tls_client->tcp.head.refcnt, 0);\n\tmemcpy(&tls_client->tcp.addr, &addr, addr_len);\n\ttls_client->tcp.addr_len = addr_len;\n\ttls_client->tcp.localaddr_len = sizeof(struct sockaddr_storage);\n\tif (_dns_server_epoll_ctl(&tls_client->tcp.head, EPOLL_CTL_ADD, EPOLLIN) != 0) {\n\t\ttlog(TLOG_ERROR, \"epoll ctl failed.\");\n\t\treturn -1;\n\t}\n\n\tif (getsocket_inet(tls_client->tcp.head.fd, (struct sockaddr *)&tls_client->tcp.localaddr,\n\t\t\t\t\t   &tls_client->tcp.localaddr_len) != 0) {\n\t\ttlog(TLOG_ERROR, \"get local addr failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tssl = SSL_new(tls_server->ssl_ctx);\n\tif (ssl == NULL) {\n\t\ttlog(TLOG_ERROR, \"SSL_new failed.\");\n\t\tgoto errout;\n\t}\n\n\tif (SSL_set_fd(ssl, fd) != 1) {\n\t\ttlog(TLOG_ERROR, \"SSL_set_fd failed.\");\n\t\tgoto errout;\n\t}\n\n\ttls_client->ssl = ssl;\n\ttls_client->tcp.status = DNS_SERVER_CLIENT_STATUS_CONNECTING;\n\tpthread_mutex_init(&tls_client->ssl_lock, NULL);\n\t_dns_server_client_touch(&tls_client->tcp.head);\n\n\tpthread_mutex_lock(&server.conn_list_lock);\n\tlist_add(&tls_client->tcp.head.list, &server.conn_list);\n\tpthread_mutex_unlock(&server.conn_list_lock);\n\n\t_dns_server_conn_get(&tls_client->tcp.head);\n\n\tset_sock_keepalive(fd, 30, 3, 5);\n\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\tif (ssl) {\n\t\tSSL_free(ssl);\n\t}\n\n\tif (tls_client) {\n\t\tfree(tls_client);\n\t}\n\treturn -1;\n}\n\nint _dns_server_process_tls(struct dns_server_conn_tls_client *tls_client, struct epoll_event *event, unsigned long now)\n{\n\tint ret = 0;\n\tint ssl_ret = 0;\n\tstruct epoll_event fd_event;\n\n\tif (tls_client->tcp.status == DNS_SERVER_CLIENT_STATUS_CONNECTING) {\n\t\t/* do SSL hand shake */\n\t\tret = _ssl_do_accept(tls_client);\n\t\tif (ret <= 0) {\n\t\t\tmemset(&fd_event, 0, sizeof(fd_event));\n\t\t\tssl_ret = _ssl_get_error(tls_client, ret);\n\t\t\tif (_dns_server_ssl_poll_event(tls_client, ssl_ret) == 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tif (ssl_ret != SSL_ERROR_SYSCALL) {\n\t\t\t\tunsigned long ssl_err = ERR_get_error();\n\t\t\t\tint ssl_reason = ERR_GET_REASON(ssl_err);\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN];\n\t\t\t\ttlog(TLOG_DEBUG, \"Handshake with %s failed, error no: %s(%d, %d, %d)\\n\",\n\t\t\t\t\t get_host_by_addr(name, sizeof(name), (struct sockaddr *)&tls_client->tcp.addr),\n\t\t\t\t\t ERR_reason_error_string(ssl_err), ret, ssl_ret, ssl_reason);\n\t\t\t\tret = 0;\n\t\t\t}\n\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttls_client->tcp.status = DNS_SERVER_CLIENT_STATUS_CONNECTED;\n\n\t\t/* Get negotiated ALPN */\n\t\tconst unsigned char *alpn_data = NULL;\n\t\tunsigned int alpn_len = 0;\n\t\tSSL_get0_alpn_selected(tls_client->ssl, &alpn_data, &alpn_len);\n\t\tif (alpn_data && alpn_len > 0 && alpn_len < sizeof(tls_client->alpn_selected)) {\n\t\t\tmemcpy(tls_client->alpn_selected, alpn_data, alpn_len);\n\t\t\ttls_client->alpn_selected[alpn_len] = '\\0';\n\t\t} else {\n\t\t\tsafe_strncpy(tls_client->alpn_selected, \"http/1.1\", sizeof(tls_client->alpn_selected));\n\t\t}\n\n\t\tmemset(&fd_event, 0, sizeof(fd_event));\n\t\tfd_event.events = EPOLLIN | EPOLLOUT;\n\t\tfd_event.data.ptr = tls_client;\n\t\tif (epoll_ctl(server.epoll_fd, EPOLL_CTL_MOD, tls_client->tcp.head.fd, &fd_event) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"epoll ctl failed, %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\t/* if HTTP/2 was negotiated */\n\tif (strcmp(tls_client->alpn_selected, \"h2\") == 0) {\n\t\tret = _dns_server_process_http2(tls_client, event, now);\n\t\tif (ret != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\treturn ret;\n\t}\n\n\treturn _dns_server_process_tcp((struct dns_server_conn_tcp_client *)tls_client, event, now);\nerrout:\n\t_dns_server_client_close(&tls_client->tcp.head);\n\treturn ret;\n}\n\nstatic int _dns_server_socket_tls_ssl_pass_callback(char *buf, int size, int rwflag, void *userdata)\n{\n\tstruct dns_bind_ip *bind_ip = userdata;\n\tif (bind_ip->ssl_cert_key_pass == NULL || bind_ip->ssl_cert_key_pass[0] == '\\0') {\n\t\treturn 0;\n\t}\n\tsafe_strncpy(buf, bind_ip->ssl_cert_key_pass, size);\n\treturn strlen(buf);\n}\n\nint _dns_server_socket_tls(struct dns_bind_ip *bind_ip, DNS_CONN_TYPE conn_type)\n{\n\tconst char *host_ip = NULL;\n\tconst char *ssl_cert_file = NULL;\n\tconst char *ssl_cert_key_file = NULL;\n\n\tstruct dns_server_conn_tls_server *conn = NULL;\n\tint fd = -1;\n\tconst SSL_METHOD *method = NULL;\n\tSSL_CTX *ssl_ctx = NULL;\n\tconst int on = 1;\n\n\thost_ip = bind_ip->ip;\n\tssl_cert_file = bind_ip->ssl_cert_file;\n\tssl_cert_key_file = bind_ip->ssl_cert_key_file;\n\n\tif (ssl_cert_file == NULL || ssl_cert_key_file == NULL) {\n\t\ttlog(TLOG_WARN, \"no cert or cert key file\");\n\t\tgoto errout;\n\t}\n\n\tif (ssl_cert_file[0] == '\\0' || ssl_cert_key_file[0] == '\\0') {\n\t\ttlog(TLOG_WARN, \"no cert or cert key file\");\n\t\tgoto errout;\n\t}\n\n\tfd = _dns_create_socket(host_ip, SOCK_STREAM);\n\tif (fd <= 0) {\n\t\tgoto errout;\n\t}\n\n\tsetsockopt(fd, SOL_TCP, TCP_FASTOPEN, &on, sizeof(on));\n\n#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)\n\tmethod = TLS_server_method();\n\tif (method == NULL) {\n\t\tgoto errout;\n\t}\n#else\n\tmethod = SSLv23_server_method();\n#endif\n\n\tssl_ctx = SSL_CTX_new(method);\n\tif (ssl_ctx == NULL) {\n\t\tgoto errout;\n\t}\n\n\tSSL_CTX_set_session_cache_mode(ssl_ctx,\n\t\t\t\t\t\t\t\t   SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR);\n\tSSL_CTX_set_default_passwd_cb(ssl_ctx, _dns_server_socket_tls_ssl_pass_callback);\n\tSSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, bind_ip);\n\n\t/* Set the key and cert */\n\tif (ssl_cert_file[0] != '\\0' && SSL_CTX_use_certificate_chain_file(ssl_ctx, ssl_cert_file) <= 0) {\n\t\ttlog(TLOG_ERROR, \"load cert %s failed, %s\", ssl_cert_file, ERR_error_string(ERR_get_error(), NULL));\n\t\tgoto errout;\n\t}\n\n\tif (ssl_cert_key_file[0] != '\\0' &&\n\t\tSSL_CTX_use_PrivateKey_file(ssl_ctx, ssl_cert_key_file, SSL_FILETYPE_PEM) <= 0) {\n\t\ttlog(TLOG_ERROR, \"load cert key %s failed, %s\", ssl_cert_key_file, ERR_error_string(ERR_get_error(), NULL));\n\t\tgoto errout;\n\t}\n\n\t/* Set ALPN */\n\tSSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_cb, bind_ip);\n\n\tconn = zalloc(1, sizeof(struct dns_server_conn_tls_server));\n\tif (conn == NULL) {\n\t\tgoto errout;\n\t}\n\t_dns_server_conn_head_init(&conn->head, fd, conn_type);\n\tconn->ssl_ctx = ssl_ctx;\n\t_dns_server_set_flags(&conn->head, bind_ip);\n\t_dns_server_conn_get(&conn->head);\n\n\treturn 0;\nerrout:\n\tif (ssl_ctx) {\n\t\tSSL_CTX_free(ssl_ctx);\n\t\tssl_ctx = NULL;\n\t}\n\n\tif (conn) {\n\t\tfree(conn);\n\t\tconn = NULL;\n\t}\n\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_server/server_tls.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_TLS_\n#define _DNS_SERVER_TLS_\n\n#include \"dns_server.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_ssl_poll_event(struct dns_server_conn_tls_client *tls_client, int ssl_ret);\n\nint _dns_server_tls_accept(struct dns_server_conn_tls_server *tls_server, struct epoll_event *event, unsigned long now);\n\nint _dns_server_socket_ssl_recv(struct dns_server_conn_tls_client *tls_client, void *buf, int num);\n\nint _dns_server_socket_ssl_send(struct dns_server_conn_tls_client *tls_client, const void *buf, int num);\n\nint _dns_server_process_tls(struct dns_server_conn_tls_client *tls_client, struct epoll_event *event,\n\t\t\t\t\t\t\tunsigned long now);\n\nint _dns_server_socket_tls(struct dns_bind_ip *bind_ip, DNS_CONN_TYPE conn_type);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/server_udp.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"server_udp.h\"\n#include \"connection.h\"\n#include \"dns_server.h\"\n#include \"server_socket.h\"\n\n#include <errno.h>\n#include <linux/in.h>\n#include <netinet/in.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/socket.h>\n\nint _dns_server_reply_udp(struct dns_request *request, struct dns_server_conn_udp *udpserver, unsigned char *inpacket,\n\t\t\t\t\t\t  int inpacket_len)\n{\n\tint send_len = 0;\n\tstruct iovec iovec[1];\n\tstruct msghdr msg;\n\tstruct cmsghdr *cmsg;\n\tchar msg_control[64];\n\n\tif (atomic_read(&server.run) == 0 || inpacket == NULL || inpacket_len <= 0) {\n\t\treturn -1;\n\t}\n\n\tiovec[0].iov_base = inpacket;\n\tiovec[0].iov_len = inpacket_len;\n\tmemset(msg_control, 0, sizeof(msg_control));\n\tmsg.msg_iov = iovec;\n\tmsg.msg_iovlen = 1;\n\tmsg.msg_control = msg_control;\n\tmsg.msg_controllen = sizeof(msg_control);\n\tmsg.msg_flags = 0;\n\tmsg.msg_name = &request->addr;\n\tmsg.msg_namelen = request->addr_len;\n\n\tcmsg = CMSG_FIRSTHDR(&msg);\n\tif (request->localaddr.ss_family == AF_INET) {\n\t\tstruct sockaddr_in *s4 = (struct sockaddr_in *)&request->localaddr;\n\t\tcmsg->cmsg_level = SOL_IP;\n\t\tcmsg->cmsg_type = IP_PKTINFO;\n\t\tcmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));\n\t\tmsg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));\n\n\t\tstruct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);\n\t\tmemset(pktinfo, 0, sizeof(*pktinfo));\n\t\tpktinfo->ipi_spec_dst = s4->sin_addr;\n\t} else if (request->localaddr.ss_family == AF_INET6) {\n\t\tstruct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&request->localaddr;\n\t\tcmsg->cmsg_level = IPPROTO_IPV6;\n\t\tcmsg->cmsg_type = IPV6_PKTINFO;\n\t\tcmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));\n\t\tmsg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));\n\n\t\tstruct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);\n\t\tmemset(pktinfo, 0, sizeof(*pktinfo));\n\t\tpktinfo->ipi6_addr = s6->sin6_addr;\n\t} else {\n\t\tgoto use_send;\n\t}\n\n\tsend_len = sendmsg(udpserver->head.fd, &msg, 0);\n\tif (send_len == inpacket_len) {\n\t\treturn 0;\n\t}\n\nuse_send:\n\tsend_len = sendto(udpserver->head.fd, inpacket, inpacket_len, 0, &request->addr, request->addr_len);\n\tif (send_len != inpacket_len) {\n\t\ttlog(TLOG_DEBUG, \"send failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _dns_server_process_udp_one(struct dns_server_conn_udp *udpconn, struct epoll_event *event,\n\t\t\t\t\t\t\t\t\t   unsigned long now)\n{\n\tint len = 0;\n\tunsigned char inpacket[DNS_IN_PACKSIZE];\n\tstruct sockaddr_storage from;\n\tsocklen_t from_len = sizeof(from);\n\tstruct sockaddr_storage local;\n\tsocklen_t local_len = sizeof(local);\n\tstruct msghdr msg;\n\tstruct iovec iov;\n\tchar ans_data[4096];\n\tstruct cmsghdr *cmsg = NULL;\n\n\tmemset(&msg, 0, sizeof(msg));\n\tiov.iov_base = (char *)inpacket;\n\tiov.iov_len = sizeof(inpacket);\n\tmsg.msg_name = &from;\n\tmsg.msg_namelen = sizeof(from);\n\tmsg.msg_iov = &iov;\n\tmsg.msg_iovlen = 1;\n\tmsg.msg_control = ans_data;\n\tmsg.msg_controllen = sizeof(ans_data);\n\n\tlen = recvmsg(udpconn->head.fd, &msg, MSG_DONTWAIT);\n\tif (len < 0) {\n\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\treturn -2;\n\t\t}\n\t\ttlog(TLOG_ERROR, \"recvfrom failed, %s\\n\", strerror(errno));\n\t\treturn -1;\n\t}\n\tfrom_len = msg.msg_namelen;\n\n\tfor (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {\n\t\tif (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {\n\t\t\tconst struct in_pktinfo *pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);\n\t\t\tunsigned char *addr = (unsigned char *)&pktinfo->ipi_addr.s_addr;\n\t\t\tfill_sockaddr_by_ip(addr, sizeof(in_addr_t), 0, (struct sockaddr *)&local, &local_len);\n\t\t} else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {\n\t\t\tconst struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);\n\t\t\tunsigned char *addr = (unsigned char *)pktinfo->ipi6_addr.s6_addr;\n\t\t\tfill_sockaddr_by_ip(addr, sizeof(struct in6_addr), 0, (struct sockaddr *)&local, &local_len);\n\t\t}\n\t}\n\n\treturn _dns_server_recv(&udpconn->head, inpacket, len, &local, local_len, &from, from_len);\n}\n\nint _dns_server_process_udp(struct dns_server_conn_udp *udpconn, struct epoll_event *event, unsigned long now)\n{\n\tint count = 0;\n\twhile (count < 32) {\n\t\tint ret = _dns_server_process_udp_one(udpconn, event, now);\n\t\tif (ret != 0) {\n\t\t\tif (ret == -2) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn ret;\n\t\t}\n\n\t\tcount++;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_socket_udp(struct dns_bind_ip *bind_ip)\n{\n\tconst char *host_ip = NULL;\n\tstruct dns_server_conn_udp *conn = NULL;\n\tint fd = -1;\n\n\thost_ip = bind_ip->ip;\n\tfd = _dns_create_socket(host_ip, SOCK_DGRAM);\n\tif (fd <= 0) {\n\t\tgoto errout;\n\t}\n\n\tconn = zalloc(1, sizeof(struct dns_server_conn_udp));\n\tif (conn == NULL) {\n\t\tgoto errout;\n\t}\n\n\t_dns_server_conn_head_init(&conn->head, fd, DNS_CONN_TYPE_UDP_SERVER);\n\t_dns_server_set_flags(&conn->head, bind_ip);\n\t_dns_server_conn_get(&conn->head);\n\n\treturn 0;\nerrout:\n\tif (conn) {\n\t\tfree(conn);\n\t\tconn = NULL;\n\t}\n\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\treturn -1;\n}\n"
  },
  {
    "path": "src/dns_server/server_udp.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_UDP_\n#define _DNS_SERVER_UDP_\n\n#include \"dns_server.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_process_udp(struct dns_server_conn_udp *udpconn, struct epoll_event *event, unsigned long now);\n\nint _dns_server_socket_udp(struct dns_bind_ip *bind_ip);\n\nint _dns_server_reply_udp(struct dns_request *request, struct dns_server_conn_udp *udpserver, unsigned char *inpacket,\n\t\t\t\t\t\t  int inpacket_len);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/soa.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"soa.h\"\n#include \"context.h\"\n#include \"dns_server.h\"\n#include \"request.h\"\n#include \"rules.h\"\n\n#include \"smartdns/dns_stats.h\"\n\nint _dns_server_is_return_soa_qtype(struct dns_request *request, dns_type_t qtype)\n{\n\tuint32_t flags = _dns_server_get_rule_flags(request);\n\n\tif (_dns_server_has_bind_flag(request, BIND_FLAG_NO_RULE_SOA) == 0) {\n\t\t/* when both has no rule SOA and force AAAA soa, force AAAA soa has high priority */\n\t\tif (qtype == DNS_T_AAAA && _dns_server_has_bind_flag(request, BIND_FLAG_FORCE_AAAA_SOA) == 0) {\n\t\t\treturn 1;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tif (flags & DOMAIN_FLAG_ADDR_IGN) {\n\t\trequest->skip_qtype_soa = 1;\n\t\treturn 0;\n\t}\n\n\tif (flags & DOMAIN_FLAG_ADDR_SOA) {\n\t\tstats_inc(&dns_stats.request.blocked_count);\n\t\treturn 1;\n\t}\n\n\tswitch (qtype) {\n\tcase DNS_T_A:\n\t\tif (flags & DOMAIN_FLAG_ADDR_IPV4_SOA) {\n\t\t\tstats_inc(&dns_stats.request.blocked_count);\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (flags & DOMAIN_FLAG_ADDR_IPV4_IGN) {\n\t\t\trequest->skip_qtype_soa = 1;\n\t\t\treturn 0;\n\t\t}\n\t\tbreak;\n\tcase DNS_T_AAAA:\n\t\tif (flags & DOMAIN_FLAG_ADDR_IPV6_SOA) {\n\t\t\tstats_inc(&dns_stats.request.blocked_count);\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (flags & DOMAIN_FLAG_ADDR_IPV6_IGN) {\n\t\t\trequest->skip_qtype_soa = 1;\n\t\t\treturn 0;\n\t\t}\n\t\tbreak;\n\tcase DNS_T_HTTPS:\n\t\tif (flags & DOMAIN_FLAG_ADDR_HTTPS_SOA) {\n\t\t\tstats_inc(&dns_stats.request.blocked_count);\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (flags & DOMAIN_FLAG_ADDR_HTTPS_IGN) {\n\t\t\trequest->skip_qtype_soa = 1;\n\t\t\treturn 0;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\tif (qtype == DNS_T_AAAA) {\n\t\tif (_dns_server_has_bind_flag(request, BIND_FLAG_FORCE_AAAA_SOA) == 0 || request->conf->force_AAAA_SOA == 1) {\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] != NULL &&\n\t\t\trequest->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] == NULL) {\n\t\t\treturn 1;\n\t\t}\n\t} else if (qtype == DNS_T_A) {\n\t\tif (request->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV6] != NULL &&\n\t\t\trequest->domain_rule.rules[DOMAIN_RULE_ADDRESS_IPV4] == NULL) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_reply_SOA_ext(int rcode, struct dns_request *request)\n{\n\t/* return SOA record */\n\trequest->rcode = rcode;\n\tif (request->ip_ttl <= 0) {\n\t\trequest->ip_ttl = DNS_SERVER_SOA_TTL;\n\t}\n\trequest->has_soa = 1;\n\n\tstruct dns_server_post_context context;\n\t_dns_server_post_context_init(&context, request);\n\tcontext.do_audit = 1;\n\tcontext.do_reply = 1;\n\tcontext.do_force_soa = 1;\n\t_dns_request_post(&context);\n\n\treturn 0;\n}\n\nint _dns_server_reply_SOA(int rcode, struct dns_request *request)\n{\n\t_dns_server_setup_soa(request);\n\treturn _dns_server_reply_SOA_ext(rcode, request);\n}\n\nint _dns_server_qtype_soa(struct dns_request *request)\n{\n\tif (request->skip_qtype_soa || request->conf->soa_table == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (request->qtype >= 0 && request->qtype <= MAX_QTYPE_NUM) {\n\t\tint offset = request->qtype / 8;\n\t\tint bit = request->qtype % 8;\n\t\tif ((request->conf->soa_table[offset] & (1 << bit)) == 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t_dns_server_reply_SOA(DNS_RC_NOERROR, request);\n\ttlog(TLOG_DEBUG, \"force qtype %d soa\", request->qtype);\n\treturn 0;\n}\n\nint _dns_server_is_return_soa(struct dns_request *request)\n{\n\treturn _dns_server_is_return_soa_qtype(request, request->qtype);\n}\n\nvoid _dns_server_setup_soa(struct dns_request *request)\n{\n\tstruct dns_soa *soa = NULL;\n\tsoa = &request->soa;\n\n\tsafe_strncpy(soa->mname, \"a.gtld-servers.net\", DNS_MAX_CNAME_LEN);\n\tsafe_strncpy(soa->rname, \"nstld.verisign-grs.com\", DNS_MAX_CNAME_LEN);\n\tsoa->serial = 1800;\n\tsoa->refresh = 1800;\n\tsoa->retry = 900;\n\tsoa->expire = 604800;\n\tsoa->minimum = 86400;\n}\n"
  },
  {
    "path": "src/dns_server/soa.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_SOA_\n#define _DNS_SERVER_SOA_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_is_return_soa(struct dns_request *request);\n\nvoid _dns_server_setup_soa(struct dns_request *request);\n\nint _dns_server_is_return_soa_qtype(struct dns_request *request, dns_type_t qtype);\n\nint _dns_server_reply_SOA(int rcode, struct dns_request *request);\n\nint _dns_server_reply_SOA_ext(int rcode, struct dns_request *request);\n\nint _dns_server_qtype_soa(struct dns_request *request);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_server/speed_check.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"speed_check.h\"\n#include \"address.h\"\n#include \"dns_server.h\"\n#include \"dualstack.h\"\n#include \"request.h\"\n\n#include \"smartdns/fast_ping.h\"\n#include <errno.h>\n#include <string.h>\n\nstatic void _dns_server_ping_result(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result,\n\t\t\t\t\t\t\t\t\tstruct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv,\n\t\t\t\t\t\t\t\t\tint error, void *userptr)\n{\n\tstruct dns_request *request = userptr;\n\tint may_complete = 0;\n\tint threshold = 100;\n\tstruct dns_ip_address *addr_map = NULL;\n\tint last_rtt = 0;\n\n\tif (request == NULL) {\n\t\treturn;\n\t}\n\n\tlast_rtt = request->ping_time;\n\tif (result == PING_RESULT_END) {\n\t\t_dns_server_request_release(request);\n\t\tfast_ping_stop(ping_host);\n\t\treturn;\n\t} else if (result == PING_RESULT_TIMEOUT) {\n\t\ttlog(TLOG_DEBUG, \"ping %s timeout\", host);\n\t\tgoto out;\n\t\treturn;\n\t} else if (result == PING_RESULT_ERROR) {\n\t\tif (addr->sa_family != AF_INET6) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (is_ipv6_ready == 1 && (error == EADDRNOTAVAIL || errno == EACCES)) {\n\t\t\tif (is_private_addr_sockaddr(addr, addr_len) == 0) {\n\t\t\t\tis_ipv6_ready = 0;\n\t\t\t\ttlog(TLOG_WARN, \"IPV6 is not ready, disable all ipv6 feature, recheck after %ds\",\n\t\t\t\t\t IPV6_READY_CHECK_TIME);\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tint rtt = tv->tv_sec * 10000 + tv->tv_usec / 100;\n\tif (rtt == 0) {\n\t\trtt = 1;\n\t}\n\n\tif (result == PING_RESULT_RESPONSE) {\n\t\ttlog(TLOG_DEBUG, \"from %s: seq=%d time=%d, lasttime=%d id=%d\", host, seqno, rtt, last_rtt, request->id);\n\t} else {\n\t\ttlog(TLOG_DEBUG, \"from %s: seq=%d timeout, id=%d\", host, seqno, request->id);\n\t}\n\n\tswitch (addr->sa_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)addr;\n\t\taddr_map = _dns_ip_address_get(request, (unsigned char *)&addr_in->sin_addr.s_addr, DNS_T_A);\n\t\tif (addr_map) {\n\t\t\taddr_map->ping_time = rtt;\n\t\t}\n\n\t\tif (request->ping_time > rtt || request->ping_time == -1) {\n\t\t\tmemcpy(request->ip_addr, &addr_in->sin_addr.s_addr, 4);\n\t\t\trequest->ip_addr_type = DNS_T_A;\n\t\t\trequest->ping_time = rtt;\n\t\t\trequest->has_cname = 0;\n\t\t\trequest->has_ip = 1;\n\t\t\tif (addr_map && addr_map->cname[0] != 0) {\n\t\t\t\trequest->has_cname = 1;\n\t\t\t\tsafe_strncpy(request->cname, addr_map->cname, DNS_MAX_CNAME_LEN);\n\t\t\t} else {\n\t\t\t\trequest->has_cname = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (request->qtype == DNS_T_AAAA && request->dualstack_selection) {\n\t\t\tif (request->ping_time < 0 && request->has_soa == 0) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A || request->qtype == DNS_T_HTTPS) {\n\t\t\trequest->has_ping_result = 1;\n\t\t}\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\taddr_map = _dns_ip_address_get(request, addr_in6->sin6_addr.s6_addr + 12, DNS_T_A);\n\t\t\tif (addr_map) {\n\t\t\t\taddr_map->ping_time = rtt;\n\t\t\t}\n\n\t\t\tif (request->ping_time > rtt || request->ping_time == -1) {\n\t\t\t\trequest->ping_time = rtt;\n\t\t\t\trequest->has_cname = 0;\n\t\t\t\trequest->has_ip = 1;\n\t\t\t\tmemcpy(request->ip_addr, addr_in6->sin6_addr.s6_addr, 16);\n\t\t\t\trequest->ip_addr_type = DNS_T_A;\n\t\t\t\tif (addr_map && addr_map->cname[0] != 0) {\n\t\t\t\t\trequest->has_cname = 1;\n\t\t\t\t\tsafe_strncpy(request->cname, addr_map->cname, DNS_MAX_CNAME_LEN);\n\t\t\t\t} else {\n\t\t\t\t\trequest->has_cname = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (request->qtype == DNS_T_A || request->qtype == DNS_T_HTTPS) {\n\t\t\t\trequest->has_ping_result = 1;\n\t\t\t}\n\t\t} else {\n\t\t\taddr_map = _dns_ip_address_get(request, addr_in6->sin6_addr.s6_addr, DNS_T_AAAA);\n\t\t\tif (addr_map) {\n\t\t\t\taddr_map->ping_time = rtt;\n\t\t\t}\n\n\t\t\tif (request->ping_time > rtt || request->ping_time == -1) {\n\t\t\t\trequest->ping_time = rtt;\n\t\t\t\trequest->has_cname = 0;\n\t\t\t\trequest->has_ip = 1;\n\t\t\t\tmemcpy(request->ip_addr, addr_in6->sin6_addr.s6_addr, 16);\n\t\t\t\trequest->ip_addr_type = DNS_T_AAAA;\n\t\t\t\tif (addr_map && addr_map->cname[0] != 0) {\n\t\t\t\t\trequest->has_cname = 1;\n\t\t\t\t\tsafe_strncpy(request->cname, addr_map->cname, DNS_MAX_CNAME_LEN);\n\t\t\t\t} else {\n\t\t\t\t\trequest->has_cname = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (request->qtype == DNS_T_AAAA || request->qtype == DNS_T_HTTPS) {\n\t\t\t\trequest->has_ping_result = 1;\n\t\t\t}\n\t\t}\n\t} break;\n\tdefault:\n\t\tbreak;\n\t}\n\nout:\n\t/* If the ping delay is less than the threshold, the result is returned */\n\tif (request->ping_time > 0) {\n\t\tif (request->ping_time < threshold) {\n\t\t\tmay_complete = 1;\n\t\t} else if (request->ping_time < (int)(get_tick_count() - request->send_tick)) {\n\t\t\tmay_complete = 1;\n\t\t}\n\t}\n\n\t/* Get first ping result */\n\tif (request->response_mode == DNS_RESPONSE_MODE_FIRST_PING_IP && last_rtt == -1 && request->ping_time > 0) {\n\t\tmay_complete = 1;\n\t}\n\n\tif (may_complete && request->has_ping_result == 1) {\n\t\t_dns_server_request_complete(request);\n\t}\n}\n\nstatic int _dns_server_ping(struct dns_request *request, PING_TYPE type, char *ip, int timeout)\n{\n\tif (fast_ping_start(type, ip, 1, 0, timeout, _dns_server_ping_result, request) == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _dns_server_check_speed(struct dns_request *request, char *ip)\n{\n\tchar tcp_ip[DNS_MAX_CNAME_LEN] = {0};\n\tint port = 80;\n\tint type = DOMAIN_CHECK_NONE;\n\tint order = request->check_order;\n\tint ping_timeout = DNS_PING_TIMEOUT;\n\tunsigned long now = get_tick_count();\n\n\tif (order >= DOMAIN_CHECK_NUM || request->check_order_list == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (request->passthrough) {\n\t\treturn -1;\n\t}\n\n\tping_timeout = ping_timeout - (now - request->send_tick);\n\tswitch (request->response_mode) {\n\tcase DNS_RESPONSE_MODE_FIRST_PING_IP:\n\t\tif (ping_timeout > 200) {\n\t\t\tping_timeout = 200;\n\t\t}\n\t\tbreak;\n\tcase DNS_RESPONSE_MODE_FASTEST_IP:\n\t\tif (ping_timeout > DNS_PING_TIMEOUT) {\n\t\t\tping_timeout = DNS_PING_TIMEOUT;\n\t\t} else if (ping_timeout < 200) {\n\t\t\tping_timeout = 200;\n\t\t}\n\t\tbreak;\n\tcase DNS_RESPONSE_MODE_FASTEST_RESPONSE:\n\t\tif (ping_timeout < 200) {\n\t\t\tping_timeout = 200;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\tif (ping_timeout < 200) {\n\t\tping_timeout = 200;\n\t}\n\n\tport = request->check_order_list->orders[order].tcp_port;\n\ttype = request->check_order_list->orders[order].type;\n\tswitch (type) {\n\tcase DOMAIN_CHECK_ICMP:\n\t\ttlog(TLOG_DEBUG, \"ping %s with icmp, order: %d, timeout: %d\", ip, order, ping_timeout);\n\t\treturn _dns_server_ping(request, PING_TYPE_ICMP, ip, ping_timeout);\n\t\tbreak;\n\tcase DOMAIN_CHECK_TCP:\n\t\tsnprintf(tcp_ip, sizeof(tcp_ip), \"%s:%d\", ip, port);\n\t\ttlog(TLOG_DEBUG, \"ping %s with tcp, order: %d, timeout: %d\", tcp_ip, order, ping_timeout);\n\t\treturn _dns_server_ping(request, PING_TYPE_TCP, tcp_ip, ping_timeout);\n\t\tbreak;\n\tcase DOMAIN_CHECK_TCP_SYN:\n\t\tsnprintf(tcp_ip, sizeof(tcp_ip), \"%s:%d\", ip, port);\n\t\ttlog(TLOG_DEBUG, \"ping %s with tcp-syn, order: %d, timeout: %d\", tcp_ip, order, ping_timeout);\n\t\treturn _dns_server_ping(request, PING_TYPE_TCP_SYN, tcp_ip, ping_timeout);\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn -1;\n}\n\nint _dns_server_second_ping_check(struct dns_request *request)\n{\n\tstruct dns_ip_address *addr_map = NULL;\n\tunsigned long bucket = 0;\n\tchar ip[DNS_MAX_CNAME_LEN] = {0};\n\tint ret = -1;\n\n\tif (request->has_ping_result) {\n\t\treturn ret;\n\t}\n\n\t/* start tcping */\n\tpthread_mutex_lock(&request->ip_map_lock);\n\thash_for_each(request->ip_map, bucket, addr_map, node)\n\t{\n\t\tswitch (addr_map->addr_type) {\n\t\tcase DNS_T_A: {\n\t\t\t_dns_server_request_get(request);\n\t\t\tsnprintf(ip, sizeof(ip), \"%d.%d.%d.%d\", addr_map->ip_addr[0], addr_map->ip_addr[1], addr_map->ip_addr[2],\n\t\t\t\t\t addr_map->ip_addr[3]);\n\t\t\tret = _dns_server_check_speed(request, ip);\n\t\t\tif (ret != 0) {\n\t\t\t\t_dns_server_request_release(request);\n\t\t\t}\n\t\t} break;\n\t\tcase DNS_T_AAAA: {\n\t\t\t_dns_server_request_get(request);\n\t\t\tsnprintf(ip, sizeof(ip), \"[%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x]\",\n\t\t\t\t\t addr_map->ip_addr[0], addr_map->ip_addr[1], addr_map->ip_addr[2], addr_map->ip_addr[3],\n\t\t\t\t\t addr_map->ip_addr[4], addr_map->ip_addr[5], addr_map->ip_addr[6], addr_map->ip_addr[7],\n\t\t\t\t\t addr_map->ip_addr[8], addr_map->ip_addr[9], addr_map->ip_addr[10], addr_map->ip_addr[11],\n\t\t\t\t\t addr_map->ip_addr[12], addr_map->ip_addr[13], addr_map->ip_addr[14], addr_map->ip_addr[15]);\n\t\t\tret = _dns_server_check_speed(request, ip);\n\t\t\tif (ret != 0) {\n\t\t\t\t_dns_server_request_release(request);\n\t\t\t}\n\t\t} break;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\tpthread_mutex_unlock(&request->ip_map_lock);\n\n\treturn ret;\n}\n"
  },
  {
    "path": "src/dns_server/speed_check.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _DNS_SERVER_SPEED_CHECK_\n#define _DNS_SERVER_SPEED_CHECK_\n\n#include \"dns_server.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _dns_server_second_ping_check(struct dns_request *request);\n\nint _dns_server_check_speed(struct dns_request *request, char *ip);\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/dns_stats.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/dns_stats.h\"\n#include <stddef.h>\n#include <string.h>\n\nstruct dns_stats dns_stats;\n\nvoid dns_stats_avg_time_update_add(struct dns_stats_avg_time *avg_time, uint64_t time)\n{\n\tif (avg_time == NULL) {\n\t\treturn;\n\t}\n\n\tuint64_t total = (uint64_t)1 << 32 | time;\n\treturn stats_add(&avg_time->total, total);\n}\n\nvoid dns_stats_avg_time_update(struct dns_stats_avg_time *avg_time)\n{\n\tuint64_t total = stats_read_and_set(&avg_time->total, 0);\n\tuint64_t count = total >> 32;\n\tuint64_t time = total & 0xFFFFFFFF;\n\n\tif (count == 0) {\n\t\treturn;\n\t}\n\n\tdouble sample_avg = (double)time / count;\n\n\tif (avg_time->count == 0) {\n\t\tavg_time->avg_time = sample_avg;\n\t\tavg_time->count = count;\n\t\treturn;\n\t}\n\n\tint base = 1000;\n\tif (count > 100) {\n\t\tcount = 100;\n\t}\n\n\tdouble weight_new = (double)count / base;\n\tdouble weight_prev = 1.0 - weight_new;\n\n\tavg_time->avg_time = (avg_time->avg_time * weight_prev) + (sample_avg * weight_new);\n\tavg_time->count += count;\n}\n\nvoid dns_stats_period_run_second(void)\n{\n\tdns_stats_avg_time_update(&dns_stats.avg_time);\n}\n\nfloat dns_stats_avg_time_get(void)\n{\n\treturn dns_stats.avg_time.avg_time;\n}\n\nuint64_t dns_stats_request_total_get(void)\n{\n\treturn stats_read(&dns_stats.request.total);\n}\n\nuint64_t dns_stats_request_success_get(void)\n{\n\treturn stats_read(&dns_stats.request.success_count);\n}\n\nuint64_t dns_stats_request_from_client_get(void)\n{\n\treturn stats_read(&dns_stats.request.from_client_count);\n}\n\nuint64_t dns_stats_request_blocked_get(void)\n{\n\treturn stats_read(&dns_stats.request.blocked_count);\n}\n\nuint64_t dns_stats_cache_hit_get(void)\n{\n\treturn stats_read(&dns_stats.cache.hit_count);\n}\n\nfloat dns_stats_cache_hit_rate_get(void)\n{\n\tuint64_t total = stats_read(&dns_stats.cache.check_count);\n\tuint64_t hit = stats_read(&dns_stats.cache.hit_count);\n\n\tif (total == 0) {\n\t\treturn 0;\n\t}\n\n\treturn (float)(hit * 100) / total;\n}\n\nvoid dns_stats_server_stats_avg_time_add(struct dns_server_stats *server_stats, uint64_t time)\n{\n\tdns_stats_avg_time_update_add(&server_stats->avg_time, time);\n}\n\nvoid dns_stats_server_stats_avg_time_update(struct dns_server_stats *server_stats)\n{\n\tdns_stats_avg_time_update(&server_stats->avg_time);\n}\n\nuint64_t dns_stats_server_stats_total_get(struct dns_server_stats *server_stats)\n{\n\tif (server_stats == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn stats_read(&server_stats->total);\n}\n\nuint64_t dns_stats_server_stats_success_get(struct dns_server_stats *server_stats)\n{\n\tif (server_stats == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn stats_read(&server_stats->success_count);\n}\n\nuint64_t dns_stats_server_stats_recv_get(struct dns_server_stats *server_stats)\n{\n\tif (server_stats == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn stats_read(&server_stats->recv_count);\n}\n\nfloat dns_stats_server_stats_avg_time_get(struct dns_server_stats *server_stats)\n{\n\tif (server_stats == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn server_stats->avg_time.avg_time;\n}\n\nint dns_stats_init(void)\n{\n\tmemset(&dns_stats, 0, sizeof(dns_stats));\n\treturn 0;\n}\n\nvoid dns_stats_exit(void)\n{\n\tmemset(&dns_stats, 0, sizeof(dns_stats));\n\treturn;\n}"
  },
  {
    "path": "src/fast_ping/fast_ping.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/fast_ping.h\"\n#include \"smartdns/lib/atomic.h\"\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include \"fast_ping.h\"\n#include \"notify_event.h\"\n#include \"ping_fake.h\"\n#include \"ping_host.h\"\n#include \"ping_icmp.h\"\n#include \"ping_icmp6.h\"\n#include \"ping_tcp.h\"\n#include \"ping_tcp_syn.h\"\n#include \"ping_udp.h\"\n#include \"wakeup_event.h\"\n\n#include <errno.h>\n#include <netdb.h>\n#include <pthread.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n#include <sys/resource.h>\n#include <sys/socket.h>\n#include <sys/timerfd.h>\n#include <sys/types.h>\n\nstatic int is_fast_ping_init;\nstruct fast_ping_struct ping;\nstatic atomic_t ping_sid = ATOMIC_INIT(0);\nint bool_print_log = 1;\n\nuint32_t _fast_ping_hash_key(unsigned int sid, struct sockaddr *addr)\n{\n\tuint32_t key = 0;\n\tvoid *sin_addr = NULL;\n\tunsigned int sin_addr_len = 0;\n\n\tswitch (addr->sa_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)addr;\n\t\tsin_addr = &addr_in->sin_addr.s_addr;\n\t\tsin_addr_len = IPV4_ADDR_LEN;\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tsin_addr = addr_in6->sin6_addr.s6_addr + 12;\n\t\t\tsin_addr_len = IPV4_ADDR_LEN;\n\t\t} else {\n\t\t\tsin_addr = addr_in6->sin6_addr.s6_addr;\n\t\t\tsin_addr_len = IPV6_ADDR_LEN;\n\t\t}\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\tif (sin_addr == NULL) {\n\t\treturn -1;\n\t}\n\n\tkey = jhash(sin_addr, sin_addr_len, 0);\n\tkey = jhash(&sid, sizeof(sid), key);\n\n\treturn key;\nerrout:\n\treturn -1;\n}\n\nstruct addrinfo *_fast_ping_getaddr(const char *host, const char *port, int type, int protocol)\n{\n\tstruct addrinfo hints;\n\tstruct addrinfo *result = NULL;\n\tint errcode = 0;\n\n\tmemset(&hints, 0, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = type;\n\thints.ai_protocol = protocol;\n\terrcode = getaddrinfo(host, port, &hints, &result);\n\tif (errcode != 0) {\n\t\ttlog(TLOG_ERROR, \"get addr info failed. host:%s, port: %s, error %s\\n\", host != NULL ? host : \"\",\n\t\t\t port != NULL ? port : \"\", gai_strerror(errcode));\n\t\tgoto errout;\n\t}\n\n\treturn result;\nerrout:\n\tif (result) {\n\t\tfreeaddrinfo(result);\n\t}\n\treturn NULL;\n}\n\nint _fast_ping_getdomain(const char *host)\n{\n\tstruct addrinfo hints;\n\tstruct addrinfo *result = NULL;\n\tint domain = -1;\n\n\tmemset(&hints, 0, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = SOCK_STREAM;\n\thints.ai_protocol = 0;\n\tif (getaddrinfo(host, NULL, &hints, &result) != 0) {\n\t\ttlog(TLOG_ERROR, \"get addr info failed. %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tdomain = result->ai_family;\n\n\tfreeaddrinfo(result);\n\n\treturn domain;\nerrout:\n\tif (result) {\n\t\tfreeaddrinfo(result);\n\t}\n\treturn -1;\n}\n\nstatic int _fast_ping_sendping(struct ping_host_struct *ping_host)\n{\n\tint ret = -1;\n\tstruct fast_ping_fake_ip *fake = NULL;\n\tgettimeofday(&ping_host->last, NULL);\n\n\tfake = _fast_ping_fake_find(ping_host->type, &ping_host->addr, ping_host->addr_len);\n\tif (fake) {\n\t\tret = _fast_ping_send_fake(ping_host, fake);\n\t\t_fast_ping_fake_put(fake);\n\t\treturn ret;\n\t}\n\n\tif (ping_host->type == FAST_PING_ICMP) {\n\t\tret = _fast_ping_sendping_v4(ping_host);\n\t} else if (ping_host->type == FAST_PING_ICMP6) {\n\t\tret = _fast_ping_sendping_v6(ping_host);\n\t} else if (ping_host->type == FAST_PING_TCP) {\n\t\tret = _fast_ping_sendping_tcp(ping_host);\n\t} else if (ping_host->type == FAST_PING_TCP_SYN) {\n\t\tret = _fast_ping_sendping_tcp_syn(ping_host);\n\t} else if (ping_host->type == FAST_PING_UDP || ping_host->type == FAST_PING_UDP6) {\n\t\tret = _fast_ping_sendping_udp(ping_host);\n\t}\n\n\tping_host->send = 1;\n\n\tif (ret != 0) {\n\t\tping_host->error = errno;\n\t\treturn ret;\n\t} else {\n\t\tping_host->error = 0;\n\t}\n\n\treturn 0;\n}\n\nstatic void _fast_ping_print_result(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result,\n\t\t\t\t\t\t\t\t\tstruct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv,\n\t\t\t\t\t\t\t\t\tint error, void *userptr)\n{\n\tif (result == PING_RESULT_RESPONSE) {\n\t\tdouble rtt = tv->tv_sec * 1000.0 + tv->tv_usec / 1000.0;\n\t\ttlog(TLOG_INFO, \"from %15s: seq=%d ttl=%d time=%.3f\\n\", host, seqno, ttl, rtt);\n\t} else if (result == PING_RESULT_TIMEOUT) {\n\t\ttlog(TLOG_INFO, \"from %15s: seq=%d timeout\\n\", host, seqno);\n\t} else if (result == PING_RESULT_ERROR) {\n\t\ttlog(TLOG_DEBUG, \"from %15s: error is %s\\n\", host, strerror(error));\n\t} else if (result == PING_RESULT_END) {\n\t\tfast_ping_stop(ping_host);\n\t}\n}\n\nint _fast_ping_get_addr_by_type(PING_TYPE type, const char *ip_str, int port, struct addrinfo **out_gai,\n\t\t\t\t\t\t\t\t\t   FAST_PING_TYPE *out_ping_type)\n{\n\tswitch (type) {\n\tcase PING_TYPE_ICMP:\n\t\treturn _fast_ping_get_addr_by_icmp(ip_str, port, out_gai, out_ping_type);\n\t\tbreak;\n\tcase PING_TYPE_TCP:\n\t\treturn _fast_ping_get_addr_by_tcp(ip_str, port, out_gai, out_ping_type);\n\t\tbreak;\n\tcase PING_TYPE_TCP_SYN:\n\t\treturn _fast_ping_get_addr_by_tcp_syn(ip_str, port, out_gai, out_ping_type);\n\t\tbreak;\n\tcase PING_TYPE_DNS:\n\t\treturn _fast_ping_get_addr_by_dns(ip_str, port, out_gai, out_ping_type);\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn -1;\n}\n\nstruct ping_host_struct *fast_ping_start(PING_TYPE type, const char *host, int count, int interval, int timeout,\n\t\t\t\t\t\t\t\t\t\t fast_ping_result ping_callback, void *userptr)\n{\n\tstruct ping_host_struct *ping_host = NULL;\n\tstruct addrinfo *gai = NULL;\n\tuint32_t addrkey = 0;\n\tchar ip_str[PING_MAX_HOSTLEN];\n\tint port = -1;\n\tFAST_PING_TYPE ping_type = FAST_PING_END;\n\tint ret = 0;\n\tstruct fast_ping_fake_ip *fake = NULL;\n\tint fake_time_fd = -1;\n\n\tif (parse_ip(host, ip_str, &port) != 0) {\n\t\tgoto errout;\n\t}\n\n\tif (timeout <= 0) {\n\t\ttimeout = 100;\n\t}\n\n\tret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\tping_host = zalloc(1, sizeof(*ping_host));\n\tif (ping_host == NULL) {\n\t\tgoto errout;\n\t}\n\tsafe_strncpy(ping_host->host, host, PING_MAX_HOSTLEN);\n\tping_host->fd = -1;\n\tping_host->timeout = timeout;\n\tping_host->count = count;\n\tping_host->type = ping_type;\n\tping_host->userptr = userptr;\n\tatomic_set(&ping_host->ref, 0);\n\tatomic_set(&ping_host->notified, 0);\n\tping_host->sid = atomic_inc_return(&ping_sid);\n\tping_host->run = 0;\n\tif (ping_callback) {\n\t\tping_host->ping_callback = ping_callback;\n\t} else {\n\t\tping_host->ping_callback = _fast_ping_print_result;\n\t}\n\tping_host->interval = (timeout > interval) ? timeout : interval;\n\tping_host->addr_len = gai->ai_addrlen;\n\tping_host->port = port;\n\tping_host->ss_family = gai->ai_family;\n\tif (gai->ai_addrlen > sizeof(struct sockaddr_in6)) {\n\t\tgoto errout;\n\t}\n\tmemcpy(&ping_host->addr, gai->ai_addr, gai->ai_addrlen);\n\n\ttlog(TLOG_DEBUG, \"ping %s, id = %d\", host, ping_host->sid);\n\n\tfake = _fast_ping_fake_find(ping_host->type, gai->ai_addr, gai->ai_addrlen);\n\tif (fake) {\n\t\tfake_time_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);\n\t\tif (fake_time_fd < 0) {\n\t\t\ttlog(TLOG_ERROR, \"timerfd_create failed, %s\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t\t/* already take ownership by find. */\n\t\tping_host->fake = fake;\n\t\tping_host->fake_time_fd = fake_time_fd;\n\t\tfake = NULL;\n\t}\n\n\taddrkey = _fast_ping_hash_key(ping_host->sid, &ping_host->addr);\n\n\t_fast_ping_host_get(ping_host);\n\t_fast_ping_host_get(ping_host);\n\t// for ping race condition, get reference count twice\n\tif (_fast_ping_sendping(ping_host) != 0) {\n\t\tgoto errout_remove;\n\t}\n\n\tpthread_mutex_lock(&ping.map_lock);\n\t_fast_ping_host_get(ping_host);\n\tif (hash_empty(ping.addrmap)) {\n\t\t_fast_ping_wakeup_thread();\n\t}\n\thash_add(ping.addrmap, &ping_host->addr_node, addrkey);\n\tping_host->run = 1;\n\tpthread_mutex_unlock(&ping.map_lock);\n\tfreeaddrinfo(gai);\n\t_fast_ping_host_put(ping_host);\n\treturn ping_host;\nerrout_remove:\n\tping_host->ping_callback(ping_host, ping_host->host, PING_RESULT_ERROR, &ping_host->addr, ping_host->addr_len,\n\t\t\t\t\t\t\t ping_host->seq, ping_host->ttl, NULL, ping_host->error, ping_host->userptr);\n\tfast_ping_stop(ping_host);\n\t_fast_ping_host_put(ping_host);\n\tping_host = NULL;\nerrout:\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\n\tif (ping_host) {\n\t\tfree(ping_host);\n\t}\n\n\tif (fake_time_fd > 0) {\n\t\tclose(fake_time_fd);\n\t}\n\n\tif (fake) {\n\t\t_fast_ping_fake_put(fake);\n\t}\n\n\treturn NULL;\n}\n\nint fast_ping_stop(struct ping_host_struct *ping_host)\n{\n\tif (ping_host == NULL) {\n\t\treturn 0;\n\t}\n\n\tatomic_inc_return(&ping_host->notified);\n\t_fast_ping_host_remove(ping_host);\n\t_fast_ping_host_put(ping_host);\n\treturn 0;\n}\n\nvoid tv_sub(struct timeval *out, struct timeval *in)\n{\n\tif ((out->tv_usec -= in->tv_usec) < 0) { /* out -= in */\n\t\t--out->tv_sec;\n\t\tout->tv_usec += 1000000;\n\t}\n\tout->tv_sec -= in->tv_sec;\n}\n\nstatic int _fast_ping_process(struct ping_host_struct *ping_host, struct epoll_event *event, struct timeval *now)\n{\n\tint ret = -1;\n\n\tif (ping_host->fake != NULL) {\n\t\tret = _fast_ping_process_fake(ping_host, now);\n\t\treturn ret;\n\t}\n\n\tswitch (ping_host->type) {\n\tcase FAST_PING_ICMP6:\n\tcase FAST_PING_ICMP:\n\t\tret = _fast_ping_process_icmp(ping_host, now);\n\t\tbreak;\n\tcase FAST_PING_TCP:\n\t\tret = _fast_ping_process_tcp(ping_host, event, now);\n\t\tbreak;\n\tcase FAST_PING_TCP_SYN:\n\t\tret = _fast_ping_process_tcp_syn(ping_host, now);\n\t\tbreak;\n\tcase FAST_PING_UDP6:\n\tcase FAST_PING_UDP:\n\t\tret = _fast_ping_process_udp(ping_host, now);\n\t\tbreak;\n\tdefault:\n\n\t\tBUG(\"type error : %p, %d, %s, %d\", ping_host, ping_host->sid, ping_host->host, ping_host->fd);\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nstatic void _fast_ping_period_run(void)\n{\n\tstruct ping_host_struct *ping_host = NULL;\n\tstruct ping_host_struct *ping_host_tmp = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\tstruct timeval now;\n\tstruct timezone tz;\n\tstruct timeval interval;\n\tint64_t millisecond = 0;\n\tgettimeofday(&now, &tz);\n\tLIST_HEAD(action);\n\n\tpthread_mutex_lock(&ping.map_lock);\n\thash_for_each_safe(ping.addrmap, i, tmp, ping_host, addr_node)\n\t{\n\t\tif (ping_host->run == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tinterval = now;\n\t\ttv_sub(&interval, &ping_host->last);\n\t\tmillisecond = interval.tv_sec * 1000 + interval.tv_usec / 1000;\n\t\tif (millisecond >= ping_host->timeout && ping_host->send == 1) {\n\t\t\tlist_add_tail(&ping_host->action_list, &action);\n\t\t\t_fast_ping_host_get(ping_host);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (millisecond < ping_host->interval) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlist_add_tail(&ping_host->action_list, &action);\n\t\t_fast_ping_host_get(ping_host);\n\t}\n\tpthread_mutex_unlock(&ping.map_lock);\n\n\tlist_for_each_entry_safe(ping_host, ping_host_tmp, &action, action_list)\n\t{\n\t\tinterval = now;\n\t\ttv_sub(&interval, &ping_host->last);\n\t\tmillisecond = interval.tv_sec * 1000 + interval.tv_usec / 1000;\n\t\tif (millisecond >= ping_host->timeout && ping_host->send == 1) {\n\t\t\t_fast_ping_send_notify_event(ping_host, PING_RESULT_TIMEOUT, ping_host->seq, ping_host->ttl, &interval);\n\t\t\tping_host->send = 0;\n\t\t}\n\n\t\tif (millisecond < ping_host->interval) {\n\t\t\tlist_del_init(&ping_host->action_list);\n\t\t\t_fast_ping_host_put(ping_host);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (ping_host->count > 0) {\n\t\t\tif (ping_host->count == 1) {\n\t\t\t\t_fast_ping_host_remove(ping_host);\n\t\t\t\tlist_del_init(&ping_host->action_list);\n\t\t\t\t_fast_ping_host_put(ping_host);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tping_host->count--;\n\t\t}\n\n\t\t_fast_ping_sendping(ping_host);\n\t\tlist_del_init(&ping_host->action_list);\n\t\t_fast_ping_host_put(ping_host);\n\t}\n}\n\nstatic void *_fast_ping_work(void *arg)\n{\n\tstruct epoll_event events[PING_MAX_EVENTS + 1];\n\tint num = 0;\n\tint i = 0;\n\tunsigned long now = {0};\n\tstruct timeval tvnow = {0};\n\tint sleep = 100;\n\tint sleep_time = 0;\n\tunsigned long expect_time = 0;\n\tunsigned long start_time = 0;\n\n\tsetpriority(PRIO_PROCESS, 0, -5);\n\n\tnow = get_tick_count();\n\tstart_time = now;\n\texpect_time = now + sleep;\n\t\n\twhile (atomic_read(&ping.run)) {\n\t\tnow = get_tick_count();\n\t\t\n\t\tif (now >= expect_time) {\n\t\t\t_fast_ping_period_run();\n\t\t\tunsigned long elapsed_from_start = now - start_time;\n\t\t\tunsigned long next_period = (elapsed_from_start / sleep) + 1;\n\t\t\texpect_time = start_time + next_period * sleep;\n\t\t}\n\t\t\n\t\tsleep_time = (int)(expect_time - now);\n\t\tif (sleep_time < 0) {\n\t\t\tsleep_time = 0;\n\t\t}\n\n\t\tpthread_mutex_lock(&ping.map_lock);\n\t\tif (hash_empty(ping.addrmap)) {\n\t\t\tsleep_time = -1; \n\t\t}\n\t\tpthread_mutex_unlock(&ping.map_lock);\n\n\t\tnum = epoll_wait(ping.epoll_fd, events, PING_MAX_EVENTS, sleep_time);\n\t\tif (num < 0) {\n\t\t\tusleep(100000);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (sleep_time == -1) {\n\t\t\tnow = get_tick_count();\n\t\t\tstart_time = now;\n\t\t\texpect_time = now + sleep;\n\t\t}\n\n\t\tif (num == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tgettimeofday(&tvnow, NULL);\n\t\tfor (i = 0; i < num; i++) {\n\t\t\tstruct epoll_event *event = &events[i];\n\t\t\t/* read event */\n\t\t\tif (event->data.ptr == NULL) {\n\t\t\t\tuint64_t value;\n\t\t\t\tint unused __attribute__((unused));\n\t\t\t\tunused = read(ping.event_fd, &value, sizeof(uint64_t));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tstruct ping_host_struct *ping_host = (struct ping_host_struct *)event->data.ptr;\n\t\t\t_fast_ping_process(ping_host, event, &tvnow);\n\t\t}\n\t}\n\n\tclose(ping.epoll_fd);\n\tping.epoll_fd = -1;\n\n\treturn NULL;\n}\n\nint fast_ping_init(void)\n{\n\tpthread_attr_t attr;\n\tint epollfd = -1;\n\tint ret = 0;\n\tbool_print_log = 1;\n\n\tif (is_fast_ping_init == 1) {\n\t\treturn -1;\n\t}\n\n\tif (ping.epoll_fd > 0) {\n\t\treturn -1;\n\t}\n\n\tmemset(&ping, 0, sizeof(ping));\n\tpthread_attr_init(&attr);\n\n\tepollfd = epoll_create1(EPOLL_CLOEXEC);\n\tif (epollfd < 0) {\n\t\ttlog(TLOG_ERROR, \"create epoll failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tpthread_mutex_init(&ping.map_lock, NULL);\n\tpthread_mutex_init(&ping.lock, NULL);\n\tpthread_mutex_init(&ping.notify_lock, NULL);\n\tpthread_cond_init(&ping.notify_cond, NULL);\n\n\tINIT_LIST_HEAD(&ping.notify_event_list);\n\n\thash_init(ping.addrmap);\n\thash_init(ping.fake);\n\tping.no_unprivileged_ping = !has_unprivileged_ping();\n\tping.ident = (getpid() & 0XFFFF);\n\tatomic_set(&ping.run, 1);\n\n\tping.epoll_fd = epollfd;\n\tret = pthread_create(&ping.tid, &attr, _fast_ping_work, NULL);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"create ping work thread failed, %s\\n\", strerror(ret));\n\t\tgoto errout;\n\t}\n\n\tret = pthread_create(&ping.notify_tid, &attr, _fast_ping_notify_worker, NULL);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"create ping notifier work thread failed, %s\\n\", strerror(ret));\n\t\tgoto errout;\n\t}\n\n\tret = _fast_ping_init_wakeup_event();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"init wakeup event failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tis_fast_ping_init = 1;\n\treturn 0;\nerrout:\n\tif (ping.notify_tid) {\n\t\tvoid *retval = NULL;\n\t\tatomic_set(&ping.run, 0);\n\t\tpthread_cond_signal(&ping.notify_cond);\n\t\tpthread_join(ping.notify_tid, &retval);\n\t\tping.notify_tid = 0;\n\t}\n\n\tif (ping.tid) {\n\t\tvoid *retval = NULL;\n\t\tatomic_set(&ping.run, 0);\n\t\t_fast_ping_wakeup_thread();\n\t\tpthread_join(ping.tid, &retval);\n\t\tping.tid = 0;\n\t}\n\n\tif (epollfd > 0) {\n\t\tclose(epollfd);\n\t\tping.epoll_fd = -1;\n\t}\n\n\tif (ping.event_fd) {\n\t\tclose(ping.event_fd);\n\t\tping.event_fd = -1;\n\t}\n\n\tpthread_cond_destroy(&ping.notify_cond);\n\tpthread_mutex_destroy(&ping.notify_lock);\n\tpthread_mutex_destroy(&ping.lock);\n\tpthread_mutex_destroy(&ping.map_lock);\n\tmemset(&ping, 0, sizeof(ping));\n\n\treturn -1;\n}\n\nstatic void _fast_ping_close_fds(void)\n{\n\t_fast_ping_close_icmp();\n\t_fast_ping_close_udp();\n\t_fast_ping_close_tcp_syn();\n}\n\nvoid fast_ping_exit(void)\n{\n\tif (is_fast_ping_init == 0) {\n\t\treturn;\n\t}\n\n\tif (ping.notify_tid) {\n\t\tvoid *retval = NULL;\n\t\tatomic_set(&ping.run, 0);\n\t\tpthread_cond_signal(&ping.notify_cond);\n\t\tpthread_join(ping.notify_tid, &retval);\n\t\tping.notify_tid = 0;\n\t}\n\n\tif (ping.tid) {\n\t\tvoid *ret = NULL;\n\t\tatomic_set(&ping.run, 0);\n\t\t_fast_ping_wakeup_thread();\n\t\tpthread_join(ping.tid, &ret);\n\t\tping.tid = 0;\n\t}\n\n\tif (ping.event_fd > 0) {\n\t\tclose(ping.event_fd);\n\t\tping.event_fd = -1;\n\t}\n\n\t_fast_ping_close_fds();\n\t_fast_ping_remove_all();\n\t_fast_ping_remove_all_fake_ip();\n\t_fast_ping_remove_all_notify_event();\n\n\tpthread_cond_destroy(&ping.notify_cond);\n\tpthread_mutex_destroy(&ping.notify_lock);\n\tpthread_mutex_destroy(&ping.lock);\n\tpthread_mutex_destroy(&ping.map_lock);\n\n\tis_fast_ping_init = 0;\n}\n"
  },
  {
    "path": "src/fast_ping/fast_ping.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_H_\n#define _FAST_PING_H_\n\n#define _GNU_SOURCE\n\n#include \"smartdns/fast_ping.h\"\n#include \"smartdns/lib/atomic.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/lib/list.h\"\n#include \"smartdns/tlog.h\"\n\n#include <netinet/icmp6.h>\n#include <netinet/ip_icmp.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\n#define PING_MAX_EVENTS 128\n#define PING_MAX_HOSTLEN 128\n#define ICMP_PACKET_SIZE (1024 * 64)\n#define ICMP_INPACKET_SIZE 1024\n#define IPV4_ADDR_LEN 4\n#define IPV6_ADDR_LEN 16\n#define SOCKET_PRIORITY (6)\n\n#ifndef ICMP_FILTER\n#define ICMP_FILTER 1\nstruct icmp_filter {\n\tuint32_t data;\n};\n#endif\n\nstruct ping_dns_head {\n\tunsigned short id;\n\tunsigned short flag;\n\tunsigned short qdcount;\n\tunsigned short ancount;\n\tunsigned short nscount;\n\tunsigned short nrcount;\n\tchar qd_name;\n\tunsigned short q_qtype;\n\tunsigned short q_qclass;\n} __attribute__((packed));\n\ntypedef enum FAST_PING_TYPE {\n\tFAST_PING_ICMP = 1,\n\tFAST_PING_ICMP6 = 2,\n\tFAST_PING_TCP,\n\tFAST_PING_TCP_SYN,\n\tFAST_PING_UDP,\n\tFAST_PING_UDP6,\n\tFAST_PING_END,\n} FAST_PING_TYPE;\n\nstruct fast_ping_packet_msg {\n\tstruct timeval tv;\n\tunsigned int sid;\n\tunsigned int seq;\n};\n\nstruct fast_ping_packet {\n\tunion {\n\t\tstruct icmp icmp;\n\t\tstruct icmp6_hdr icmp6;\n\t};\n\tunsigned int ttl;\n\tstruct fast_ping_packet_msg msg;\n};\n\nstruct fast_ping_fake_ip {\n\tstruct hlist_node node;\n\tatomic_t ref;\n\tPING_TYPE type;\n\tFAST_PING_TYPE ping_type;\n\tchar host[PING_MAX_HOSTLEN];\n\tint ttl;\n\tfloat time;\n\tstruct sockaddr_storage addr;\n\tint addr_len;\n};\n\nstruct ping_host_struct {\n\tatomic_t ref;\n\tatomic_t notified;\n\tstruct hlist_node addr_node;\n\tstruct list_head action_list;\n\tFAST_PING_TYPE type;\n\n\tvoid *userptr;\n\tint error;\n\tfast_ping_result ping_callback;\n\tchar host[PING_MAX_HOSTLEN];\n\n\tint fd;\n\tunsigned short seq;\n\tint ttl;\n\tstruct timeval last;\n\tint interval;\n\tint timeout;\n\tint count;\n\tint send;\n\tint run;\n\tunsigned short sid;\n\tunsigned short port;\n\tunsigned short tcp_local_port;\n\tunsigned short ss_family;\n\tunion {\n\t\tstruct sockaddr addr;\n\t\tstruct sockaddr_in6 in6;\n\t\tstruct sockaddr_in in;\n\t};\n\tsocklen_t addr_len;\n\tstruct fast_ping_packet packet;\n\n\tstruct fast_ping_packet recv_packet_buffer;\n\n\tstruct fast_ping_fake_ip *fake;\n\tint fake_time_fd;\n};\n\nstruct fast_ping_notify_event {\n\tstruct list_head list;\n\tstruct ping_host_struct *ping_host;\n\tFAST_PING_RESULT ping_result;\n\tunsigned int seq;\n\tint ttl;\n\tstruct timeval tvresult;\n};\n\nstruct fast_ping_struct {\n\tatomic_t run;\n\tpthread_t tid;\n\tpthread_mutex_t lock;\n\tunsigned short ident;\n\n\tint epoll_fd;\n\tint no_unprivileged_ping;\n\tint fd_icmp;\n\tstruct ping_host_struct icmp_host;\n\tint fd_icmp6;\n\tstruct ping_host_struct icmp6_host;\n\tint fd_udp;\n\tstruct ping_host_struct udp_host;\n\tint fd_udp6;\n\tstruct ping_host_struct udp6_host;\n\tint fd_tcp_syn;\n\tstruct ping_host_struct tcp_syn_host;\n\tint fd_tcp_syn6;\n\tstruct ping_host_struct tcp_syn6_host;\n\tint fd_tcp_syn_bind;\n\tuint16_t tcp_syn_bind_port;\n\tstruct sockaddr_in tcp_syn_bind_addr;\n\tint fd_tcp_syn6_bind;\n\tuint16_t tcp_syn6_bind_port;\n\tstruct sockaddr_in6 tcp_syn6_bind_addr;\n\n\tint event_fd;\n\tpthread_t notify_tid;\n\tpthread_cond_t notify_cond;\n\tpthread_mutex_t notify_lock;\n\tstruct list_head notify_event_list;\n\n\tpthread_mutex_t map_lock;\n\tDECLARE_HASHTABLE(addrmap, 6);\n\tDECLARE_HASHTABLE(fake, 6);\n\tint fake_ip_num;\n};\n\nextern struct fast_ping_struct ping;\nextern int bool_print_log;\n\nuint32_t _fast_ping_hash_key(unsigned int sid, struct sockaddr *addr);\n\nstruct addrinfo *_fast_ping_getaddr(const char *host, const char *port, int type, int protocol);\n\nint _fast_ping_get_addr_by_type(PING_TYPE type, const char *ip_str, int port, struct addrinfo **out_gai,\n\t\t\t\t\t\t\t\tFAST_PING_TYPE *out_ping_type);\n\nvoid tv_sub(struct timeval *out, struct timeval *in);\n\nint _fast_ping_getdomain(const char *host);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_H_\n"
  },
  {
    "path": "src/fast_ping/notify_event.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"notify_event.h\"\n#include \"ping_host.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <pthread.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n\nstatic void _fast_ping_release_notify_event(struct fast_ping_notify_event *ping_notify_event)\n{\n\tpthread_mutex_lock(&ping.notify_lock);\n\tlist_del_init(&ping_notify_event->list);\n\tpthread_mutex_unlock(&ping.notify_lock);\n\n\tif (ping_notify_event->ping_host) {\n\t\t_fast_ping_host_put(ping_notify_event->ping_host);\n\t\tping_notify_event->ping_host = NULL;\n\t}\n\tfree(ping_notify_event);\n}\n\nint _fast_ping_send_notify_event(struct ping_host_struct *ping_host, FAST_PING_RESULT ping_result, unsigned int seq,\n\t\t\t\t\t\t\t\t int ttl, struct timeval *tvresult)\n{\n\tstruct fast_ping_notify_event *notify_event = NULL;\n\n\tnotify_event = zalloc(1, sizeof(struct fast_ping_notify_event));\n\tif (notify_event == NULL) {\n\t\tgoto errout;\n\t}\n\tINIT_LIST_HEAD(&notify_event->list);\n\tnotify_event->seq = seq;\n\tnotify_event->ttl = ttl;\n\tnotify_event->ping_result = ping_result;\n\tnotify_event->tvresult = *tvresult;\n\n\tpthread_mutex_lock(&ping.notify_lock);\n\tif (list_empty(&ping.notify_event_list)) {\n\t\tpthread_cond_signal(&ping.notify_cond);\n\t}\n\tlist_add_tail(&notify_event->list, &ping.notify_event_list);\n\tnotify_event->ping_host = ping_host;\n\t_fast_ping_host_get(ping_host);\n\tpthread_mutex_unlock(&ping.notify_lock);\n\n\treturn 0;\n\nerrout:\n\tif (notify_event) {\n\t\t_fast_ping_release_notify_event(notify_event);\n\t}\n\treturn -1;\n}\n\nstatic void _fast_ping_process_notify_event(struct fast_ping_notify_event *ping_notify_event)\n{\n\tstruct ping_host_struct *ping_host = ping_notify_event->ping_host;\n\tif (ping_host == NULL) {\n\t\treturn;\n\t}\n\n\tping_host->ping_callback(ping_host, ping_host->host, ping_notify_event->ping_result, &ping_host->addr,\n\t\t\t\t\t\t\t ping_host->addr_len, ping_notify_event->seq, ping_notify_event->ttl,\n\t\t\t\t\t\t\t &ping_notify_event->tvresult, ping_host->error, ping_host->userptr);\n}\n\nvoid *_fast_ping_notify_worker(void *arg)\n{\n\tstruct fast_ping_notify_event *ping_notify_event = NULL;\n\n\twhile (atomic_read(&ping.run)) {\n\t\tpthread_mutex_lock(&ping.notify_lock);\n\t\tif (list_empty(&ping.notify_event_list)) {\n\t\t\tpthread_cond_wait(&ping.notify_cond, &ping.notify_lock);\n\t\t}\n\n\t\tping_notify_event = list_first_entry_or_null(&ping.notify_event_list, struct fast_ping_notify_event, list);\n\t\tif (ping_notify_event) {\n\t\t\tlist_del_init(&ping_notify_event->list);\n\t\t}\n\t\tpthread_mutex_unlock(&ping.notify_lock);\n\n\t\tif (ping_notify_event == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t_fast_ping_process_notify_event(ping_notify_event);\n\t\t_fast_ping_release_notify_event(ping_notify_event);\n\t}\n\n\treturn NULL;\n}\n\nvoid _fast_ping_remove_all_notify_event(void)\n{\n\tstruct fast_ping_notify_event *notify_event = NULL;\n\tstruct fast_ping_notify_event *tmp = NULL;\n\tlist_for_each_entry_safe(notify_event, tmp, &ping.notify_event_list, list)\n\t{\n\t\t_fast_ping_process_notify_event(notify_event);\n\t\t_fast_ping_release_notify_event(notify_event);\n\t}\n}"
  },
  {
    "path": "src/fast_ping/notify_event.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_NOTIFY_EVENT_H_\n#define _FAST_PING_NOTIFY_EVENT_H_\n\n#include \"fast_ping.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _fast_ping_remove_all_notify_event(void);\n\nvoid *_fast_ping_notify_worker(void *arg);\n\nint _fast_ping_send_notify_event(struct ping_host_struct *ping_host, FAST_PING_RESULT ping_result, unsigned int seq,\n\t\t\t\t\t\t\t\t int ttl, struct timeval *tvresult);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_NOTIFY_EVENT_H_\n"
  },
  {
    "path": "src/fast_ping/ping_fake.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"smartdns/lib/stringutil.h\"\n#include \"smartdns/util.h\"\n\n#include \"notify_event.h\"\n#include \"ping_fake.h\"\n#include \"ping_host.h\"\n\n#include <errno.h>\n#include <pthread.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/timerfd.h>\n\nvoid _fast_ping_fake_put(struct fast_ping_fake_ip *fake)\n{\n\tint ref_cnt = atomic_dec_and_test(&fake->ref);\n\tif (!ref_cnt) {\n\t\tif (ref_cnt < 0) {\n\n\t\t\tBUG(\"invalid refcount of fake ping %s\", fake->host);\n\t\t}\n\t\treturn;\n\t}\n\n\tpthread_mutex_lock(&ping.map_lock);\n\tif (hash_hashed(&fake->node)) {\n\t\thash_del(&fake->node);\n\t}\n\tpthread_mutex_unlock(&ping.map_lock);\n\n\tfree(fake);\n}\n\nvoid _fast_ping_fake_remove(struct fast_ping_fake_ip *fake)\n{\n\tpthread_mutex_lock(&ping.map_lock);\n\tif (hash_hashed(&fake->node)) {\n\t\thash_del(&fake->node);\n\t}\n\tpthread_mutex_unlock(&ping.map_lock);\n\n\t_fast_ping_fake_put(fake);\n}\n\nvoid _fast_ping_fake_get(struct fast_ping_fake_ip *fake)\n{\n\tatomic_inc(&fake->ref);\n}\n\nstruct fast_ping_fake_ip *_fast_ping_fake_find(FAST_PING_TYPE ping_type, struct sockaddr *addr, int addr_len)\n{\n\tstruct fast_ping_fake_ip *fake = NULL;\n\tstruct fast_ping_fake_ip *ret = NULL;\n\tuint32_t key = 0;\n\n\tif (ping.fake_ip_num == 0) {\n\t\treturn NULL;\n\t}\n\n\tkey = jhash(addr, addr_len, 0);\n\tkey = jhash(&ping_type, sizeof(ping_type), key);\n\tpthread_mutex_lock(&ping.map_lock);\n\thash_for_each_possible(ping.fake, fake, node, key)\n\t{\n\t\tif (fake->ping_type != ping_type) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (fake->addr_len != addr_len) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (memcmp(&fake->addr, addr, fake->addr_len) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tret = fake;\n\t\t_fast_ping_fake_get(fake);\n\t\tbreak;\n\t}\n\tpthread_mutex_unlock(&ping.map_lock);\n\treturn ret;\n}\n\nint fast_ping_fake_ip_add(PING_TYPE type, const char *host, int ttl, float time)\n{\n\tstruct fast_ping_fake_ip *fake = NULL;\n\tstruct fast_ping_fake_ip *fake_old = NULL;\n\tchar ip_str[PING_MAX_HOSTLEN];\n\tint port = -1;\n\tFAST_PING_TYPE ping_type = FAST_PING_END;\n\tuint32_t key = 0;\n\tint ret = -1;\n\tstruct addrinfo *gai = NULL;\n\n\tif (parse_ip(host, ip_str, &port) != 0) {\n\t\tgoto errout;\n\t}\n\n\tret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\tfake_old = _fast_ping_fake_find(ping_type, gai->ai_addr, gai->ai_addrlen);\n\tfake = zalloc(1, sizeof(*fake));\n\tif (fake == NULL) {\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(fake->host, ip_str, PING_MAX_HOSTLEN);\n\tfake->ttl = ttl;\n\tfake->time = time;\n\tfake->type = type;\n\tfake->ping_type = ping_type;\n\tmemcpy(&fake->addr, gai->ai_addr, gai->ai_addrlen);\n\tfake->addr_len = gai->ai_addrlen;\n\tINIT_HLIST_NODE(&fake->node);\n\tatomic_set(&fake->ref, 1);\n\n\tkey = jhash(&fake->addr, fake->addr_len, 0);\n\tkey = jhash(&ping_type, sizeof(ping_type), key);\n\tpthread_mutex_lock(&ping.map_lock);\n\thash_add(ping.fake, &fake->node, key);\n\tpthread_mutex_unlock(&ping.map_lock);\n\tping.fake_ip_num++;\n\n\tif (fake_old != NULL) {\n\t\t_fast_ping_fake_put(fake_old);\n\t\t_fast_ping_fake_remove(fake_old);\n\t}\n\n\tfreeaddrinfo(gai);\n\treturn 0;\nerrout:\n\tif (fake != NULL) {\n\t\tfree(fake);\n\t}\n\n\tif (fake_old != NULL) {\n\t\t_fast_ping_fake_put(fake_old);\n\t}\n\n\tif (gai != NULL) {\n\t\tfreeaddrinfo(gai);\n\t}\n\n\treturn -1;\n}\n\nint fast_ping_fake_ip_remove(PING_TYPE type, const char *host)\n{\n\tstruct fast_ping_fake_ip *fake = NULL;\n\tchar ip_str[PING_MAX_HOSTLEN];\n\tint port = -1;\n\tint ret = -1;\n\tFAST_PING_TYPE ping_type = FAST_PING_END;\n\tstruct addrinfo *gai = NULL;\n\n\tif (parse_ip(host, ip_str, &port) != 0) {\n\t\treturn -1;\n\t}\n\n\tret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\tfake = _fast_ping_fake_find(ping_type, gai->ai_addr, gai->ai_addrlen);\n\tif (fake == NULL) {\n\t\tgoto errout;\n\t}\n\n\t_fast_ping_fake_remove(fake);\n\t_fast_ping_fake_put(fake);\n\tping.fake_ip_num--;\n\tfreeaddrinfo(gai);\n\treturn 0;\nerrout:\n\tif (gai != NULL) {\n\t\tfreeaddrinfo(gai);\n\t}\n\treturn -1;\n}\n\nint _fast_ping_send_fake(struct ping_host_struct *ping_host, struct fast_ping_fake_ip *fake)\n{\n\tstruct itimerspec its;\n\tint sec = fake->time / 1000;\n\tint cent_usec = ((long)(fake->time * 10)) % 10000;\n\tits.it_value.tv_sec = sec;\n\tits.it_value.tv_nsec = cent_usec * 1000 * 100;\n\tits.it_interval.tv_sec = 0;\n\tits.it_interval.tv_nsec = 0;\n\n\tif (timerfd_settime(ping_host->fake_time_fd, 0, &its, NULL) < 0) {\n\t\ttlog(TLOG_ERROR, \"timerfd_settime failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tstruct epoll_event ev;\n\tev.events = EPOLLIN;\n\tev.data.ptr = ping_host;\n\tif (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, ping_host->fake_time_fd, &ev) == -1) {\n\t\tif (errno != EEXIST) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tping_host->seq++;\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nint _fast_ping_process_fake(struct ping_host_struct *ping_host, struct timeval *now)\n{\n\tstruct timeval tvresult = *now;\n\tstruct timeval *tvsend = &ping_host->last;\n\tuint64_t exp;\n\tint ret;\n\n\tret = read(ping_host->fake_time_fd, &exp, sizeof(uint64_t));\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tping_host->ttl = ping_host->fake->ttl;\n\ttv_sub(&tvresult, tvsend);\n\tif (ping_host->ping_callback) {\n\t\t_fast_ping_send_notify_event(ping_host, PING_RESULT_RESPONSE, ping_host->seq, ping_host->ttl, &tvresult);\n\t}\n\n\tping_host->send = 0;\n\n\tif (ping_host->count == 1) {\n\t\t_fast_ping_host_remove(ping_host);\n\t}\n\n\treturn 0;\n}\n\nvoid _fast_ping_remove_all_fake_ip(void)\n{\n\tstruct fast_ping_fake_ip *fake = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\thash_for_each_safe(ping.fake, i, tmp, fake, node)\n\t{\n\t\t_fast_ping_fake_put(fake);\n\t}\n}\n"
  },
  {
    "path": "src/fast_ping/ping_fake.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_FAKE_H_\n#define _FAST_PING_FAKE_H_\n\n#include \"fast_ping.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _fast_ping_fake_put(struct fast_ping_fake_ip *fake);\n\nvoid _fast_ping_fake_remove(struct fast_ping_fake_ip *fake);\n\nvoid _fast_ping_fake_get(struct fast_ping_fake_ip *fake);\n\nstruct fast_ping_fake_ip *_fast_ping_fake_find(FAST_PING_TYPE ping_type, struct sockaddr *addr, int addr_len);\n\nvoid _fast_ping_remove_all_fake_ip(void);\n\nint _fast_ping_process_fake(struct ping_host_struct *ping_host, struct timeval *now);\n\nint _fast_ping_send_fake(struct ping_host_struct *ping_host, struct fast_ping_fake_ip *fake);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_H_\n"
  },
  {
    "path": "src/fast_ping/ping_host.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"ping_host.h\"\n#include \"notify_event.h\"\n#include \"ping_fake.h\"\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <pthread.h>\n#include <string.h>\n#include <sys/epoll.h>\n\nvoid _fast_ping_host_get(struct ping_host_struct *ping_host)\n{\n\tif (atomic_inc_return(&ping_host->ref) <= 0) {\n\n\t\tBUG(\"ping host ref is invalid, host: %s\", ping_host->host);\n\t}\n}\n\nvoid _fast_ping_close_host_sock(struct ping_host_struct *ping_host)\n{\n\tif (ping_host->fake_time_fd > 0) {\n\t\tstruct epoll_event *event = NULL;\n\t\tevent = (struct epoll_event *)1;\n\t\tepoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping_host->fake_time_fd, event);\n\n\t\tclose(ping_host->fake_time_fd);\n\t\tping_host->fake_time_fd = -1;\n\t}\n\n\tif (ping_host->fd < 0) {\n\t\treturn;\n\t}\n\tstruct epoll_event *event = NULL;\n\tevent = (struct epoll_event *)1;\n\tepoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping_host->fd, event);\n\tclose(ping_host->fd);\n\tping_host->fd = -1;\n}\n\nvoid _fast_ping_host_put(struct ping_host_struct *ping_host)\n{\n\tint ref_cnt = atomic_dec_and_test(&ping_host->ref);\n\tif (!ref_cnt) {\n\t\tif (ref_cnt < 0) {\n\n\t\t\tBUG(\"invalid refcount of ping_host %s\", ping_host->host);\n\t\t}\n\t\treturn;\n\t}\n\n\t_fast_ping_close_host_sock(ping_host);\n\tif (ping_host->fake != NULL) {\n\t\t_fast_ping_fake_put(ping_host->fake);\n\t\tping_host->fake = NULL;\n\t}\n\n\tpthread_mutex_lock(&ping.map_lock);\n\thash_del(&ping_host->addr_node);\n\tpthread_mutex_unlock(&ping.map_lock);\n\n\tif (atomic_inc_return(&ping_host->notified) == 1) {\n\t\tstruct timeval tv;\n\t\ttv.tv_sec = 0;\n\t\ttv.tv_usec = 0;\n\n\t\t_fast_ping_send_notify_event(ping_host, PING_RESULT_END, ping_host->seq, ping_host->ttl, &tv);\n\t}\n\n\ttlog(TLOG_DEBUG, \"ping %s end, id %d\", ping_host->host, ping_host->sid);\n\tping_host->type = FAST_PING_END;\n\tfree(ping_host);\n}\n\nvoid _fast_ping_host_remove(struct ping_host_struct *ping_host)\n{\n\t_fast_ping_close_host_sock(ping_host);\n\n\tpthread_mutex_lock(&ping.map_lock);\n\tif (!hash_hashed(&ping_host->addr_node)) {\n\t\tpthread_mutex_unlock(&ping.map_lock);\n\t\treturn;\n\t}\n\thash_del(&ping_host->addr_node);\n\n\tpthread_mutex_unlock(&ping.map_lock);\n\n\tif (atomic_inc_return(&ping_host->notified) == 1) {\n\t\tstruct timeval tv;\n\t\ttv.tv_sec = 0;\n\t\ttv.tv_usec = 0;\n\n\t\t_fast_ping_send_notify_event(ping_host, PING_RESULT_END, ping_host->seq, ping_host->ttl, &tv);\n\t}\n\n\t_fast_ping_host_put(ping_host);\n}\n\nvoid _fast_ping_remove_all(void)\n{\n\tstruct ping_host_struct *ping_host = NULL;\n\tstruct ping_host_struct *ping_host_tmp = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned long i = 0;\n\n\tLIST_HEAD(remove_list);\n\n\tpthread_mutex_lock(&ping.map_lock);\n\thash_for_each_safe(ping.addrmap, i, tmp, ping_host, addr_node)\n\t{\n\t\tlist_add_tail(&ping_host->action_list, &remove_list);\n\t}\n\tpthread_mutex_unlock(&ping.map_lock);\n\n\tlist_for_each_entry_safe(ping_host, ping_host_tmp, &remove_list, action_list)\n\t{\n\t\t_fast_ping_host_remove(ping_host);\n\t}\n}\n"
  },
  {
    "path": "src/fast_ping/ping_host.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_HOST_H_\n#define _FAST_PING_HOST_H_\n\n#include \"fast_ping.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _fast_ping_host_remove(struct ping_host_struct *ping_host);\n\nvoid _fast_ping_host_put(struct ping_host_struct *ping_host);\n\nvoid _fast_ping_host_get(struct ping_host_struct *ping_host);\n\nvoid _fast_ping_close_host_sock(struct ping_host_struct *ping_host);\n\nvoid _fast_ping_remove_all(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_HOST_H_\n"
  },
  {
    "path": "src/fast_ping/ping_icmp.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"smartdns/util.h\"\n\n#include \"notify_event.h\"\n#include \"ping_host.h\"\n#include \"ping_icmp.h\"\n#include \"ping_icmp6.h\"\n\n#include <errno.h>\n#include <linux/filter.h>\n#include <pthread.h>\n#include <string.h>\n#include <sys/epoll.h>\n\nstatic void _fast_ping_install_filter_v4(int sock)\n{\n\tstatic int once;\n\tstatic struct sock_filter insns[] = {\n\t\tBPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0),                    /* Skip IP header. F..g BSD... Look into ping6. */\n\t\tBPF_STMT(BPF_LD | BPF_H | BPF_IND, 4),                     /* Load icmp echo ident */\n\t\tBPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0xAAAA, 0, 1),         /* Ours? */\n\t\tBPF_STMT(BPF_RET | BPF_K, ~0U),                            /* Yes, it passes. */\n\t\tBPF_STMT(BPF_LD | BPF_B | BPF_IND, 0),                     /* Load icmp type */\n\t\tBPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */\n\t\tBPF_STMT(BPF_RET | BPF_K, 0xFFFFFFF),                      /* No. It passes. */\n\t\tBPF_STMT(BPF_RET | BPF_K, 0)                               /* Echo with wrong ident. Reject. */\n\t};\n\n\tstatic struct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns};\n\n\tif (once) {\n\t\treturn;\n\t}\n\tonce = 1;\n\n\t/* Patch bpflet for current identifier. */\n\tinsns[2] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(ping.ident), 0, 1);\n\n\tif (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {\n\t\ttlog(TLOG_WARN, \"WARNING: failed to install socket filter\\n\");\n\t}\n}\n\nint _fast_ping_sendping_v4(struct ping_host_struct *ping_host)\n{\n\tif (_fast_ping_icmp_create_socket(ping_host) < 0) {\n\t\tgoto errout;\n\t}\n\n\tif (ping.fd_icmp <= 0) {\n\t\terrno = EADDRNOTAVAIL;\n\t\tgoto errout;\n\t}\n\n\tstruct fast_ping_packet *packet = &ping_host->packet;\n\tstruct icmp *icmp = &packet->icmp;\n\tint len = 0;\n\n\tping_host->seq++;\n\tmemset(icmp, 0, sizeof(*icmp));\n\ticmp->icmp_type = ICMP_ECHO;\n\ticmp->icmp_code = 0;\n\ticmp->icmp_cksum = 0;\n\ticmp->icmp_id = ping.ident;\n\ticmp->icmp_seq = htons(ping_host->seq);\n\n\tgettimeofday(&packet->msg.tv, NULL);\n\tgettimeofday(&ping_host->last, NULL);\n\tpacket->msg.sid = ping_host->sid;\n\tpacket->msg.seq = ping_host->seq;\n\ticmp->icmp_cksum = _fast_ping_checksum((void *)packet, sizeof(struct fast_ping_packet));\n\n\tlen = sendto(ping.fd_icmp, packet, sizeof(struct fast_ping_packet), 0, &ping_host->addr, ping_host->addr_len);\n\tif (len != sizeof(struct fast_ping_packet)) {\n\t\tint err = errno;\n\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\tgoto errout;\n\t\t}\n\t\tif (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EPERM || errno == EACCES) {\n\t\t\tgoto errout;\n\t\t}\n\t\tchar ping_host_name[PING_MAX_HOSTLEN];\n\t\ttlog(TLOG_ERROR, \"sendto %s, id %d, %s\",\n\t\t\t get_host_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr),\n\t\t\t ping_host->sid, strerror(err));\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nstatic int _fast_ping_create_icmp_sock(FAST_PING_TYPE type)\n{\n\tint fd = -1;\n\tstruct ping_host_struct *icmp_host = NULL;\n\tstruct epoll_event event;\n\t/* Set receive and send buffer to 512KB, if buffer size is too small, ping may fail. */\n\tint buffsize = 512 * 1024;\n\tsocklen_t optlen = sizeof(buffsize);\n\tconst int val = 255;\n\tconst int on = 1;\n\tconst int ip_tos = (IPTOS_LOWDELAY | IPTOS_RELIABILITY);\n\n\tswitch (type) {\n\tcase FAST_PING_ICMP:\n\t\tif (ping.no_unprivileged_ping == 0) {\n\t\t\tfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);\n\t\t} else {\n\t\t\tfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);\n\t\t\tif (fd > 0) {\n\t\t\t\t_fast_ping_install_filter_v4(fd);\n\t\t\t}\n\t\t}\n\t\tif (fd < 0) {\n\t\t\tif (errno == EACCES || errno == EAFNOSUPPORT) {\n\t\t\t\tif (bool_print_log == 0) {\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\tbool_print_log = 0;\n\t\t\t}\n\t\t\ttlog(TLOG_ERROR, \"create icmp socket failed, %s\\n\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t\ticmp_host = &ping.icmp_host;\n\t\tbreak;\n\tcase FAST_PING_ICMP6:\n\t\tif (ping.no_unprivileged_ping == 0) {\n\t\t\tfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);\n\t\t} else {\n\t\t\tfd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);\n\t\t\tif (fd > 0) {\n\t\t\t\t_fast_ping_install_filter_v6(fd);\n\t\t\t}\n\t\t}\n\n\t\tif (fd < 0) {\n\t\t\tif (errno == EACCES || errno == EAFNOSUPPORT) {\n\t\t\t\tif (bool_print_log == 0) {\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\tbool_print_log = 0;\n\t\t\t}\n\t\t\ttlog(TLOG_INFO, \"create icmpv6 socket failed, %s\\n\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t\tsetsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on));\n\t\ticmp_host = &ping.icmp6_host;\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t}\n\n\tstruct icmp_filter filt;\n\tfilt.data = ~((1 << ICMP_SOURCE_QUENCH) | (1 << ICMP_DEST_UNREACH) | (1 << ICMP_TIME_EXCEEDED) |\n\t\t\t\t  (1 << ICMP_PARAMETERPROB) | (1 << ICMP_REDIRECT) | (1 << ICMP_ECHOREPLY));\n\tsetsockopt(fd, SOL_RAW, ICMP_FILTER, &filt, sizeof filt);\n\tsetsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const char *)&buffsize, optlen);\n\tsetsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char *)&buffsize, optlen);\n\tsetsockopt(fd, SOL_IP, IP_TTL, &val, sizeof(val));\n\tsetsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));\n\tset_fd_nonblock(fd, 1);\n\n\ticmp_host->fd = fd;\n\ticmp_host->type = type;\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN;\n\tevent.data.ptr = icmp_host;\n\tif (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn fd;\n\nerrout:\n\tclose(fd);\n\tif (icmp_host) {\n\t\ticmp_host->fd = -1;\n\t\ticmp_host->type = 0;\n\t}\n\treturn -1;\n}\n\nstatic int _fast_ping_create_icmp(FAST_PING_TYPE type)\n{\n\tint fd = -1;\n\tint *set_fd = NULL;\n\n\tpthread_mutex_lock(&ping.lock);\n\tswitch (type) {\n\tcase FAST_PING_ICMP:\n\t\tset_fd = &ping.fd_icmp;\n\t\tbreak;\n\tcase FAST_PING_ICMP6:\n\t\tset_fd = &ping.fd_icmp6;\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\tif (*set_fd > 0) {\n\t\tgoto out;\n\t}\n\n\tfd = _fast_ping_create_icmp_sock(type);\n\tif (fd < 0) {\n\t\tgoto errout;\n\t}\n\n\t*set_fd = fd;\nout:\n\tpthread_mutex_unlock(&ping.lock);\n\treturn *set_fd;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\tpthread_mutex_unlock(&ping.lock);\n\treturn -1;\n}\n\nint _fast_ping_icmp_create_socket(struct ping_host_struct *ping_host)\n{\n\tif (_fast_ping_create_icmp(ping_host->type) < 0) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstruct fast_ping_packet *_fast_ping_icmp_packet(struct ping_host_struct *ping_host, struct msghdr *msg,\n\t\t\t\t\t\t\t\t\t\t\t\tu_char *packet_data, int data_len)\n{\n\tstruct ip *ip = (struct ip *)packet_data;\n\tstruct fast_ping_packet *packet = NULL;\n\tstruct icmp *icmp = NULL;\n\tint hlen = 0;\n\tint icmp_len = 0;\n\n\tif (ping.no_unprivileged_ping) {\n\t\thlen = ip->ip_hl << 2;\n\t\tif (ip->ip_p != IPPROTO_ICMP) {\n\t\t\ttlog(TLOG_DEBUG, \"ip type failed, %d:%d\", ip->ip_p, IPPROTO_ICMP);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tif (data_len - hlen < (int)sizeof(struct icmp)) {\n\t\ttlog(TLOG_DEBUG, \"response ping package length is invalid, len: %d\", data_len);\n\t\treturn NULL;\n\t}\n\n\tint align = __alignof__(struct fast_ping_packet);\n\tif (((uintptr_t)(packet_data + hlen) % align) == 0 && ping.no_unprivileged_ping == 0) {\n\t\tpacket = (struct fast_ping_packet *)(packet_data + hlen);\n\t} else {\n\t\tint copy_len = sizeof(ping_host->recv_packet_buffer);\n\t\tif (copy_len > data_len - hlen) {\n\t\t\tcopy_len = data_len - hlen;\n\t\t}\n\t\tmemcpy(&ping_host->recv_packet_buffer, packet_data + hlen, copy_len);\n\t\tpacket = &ping_host->recv_packet_buffer;\n\t}\n\n\ticmp = &packet->icmp;\n\ticmp_len = data_len - hlen;\n\tif (icmp_len < 16) {\n\t\ttlog(TLOG_ERROR, \"length is invalid, %d\", icmp_len);\n\t\treturn NULL;\n\t}\n\n\tif (icmp->icmp_type != ICMP_ECHOREPLY) {\n\t\terrno = ENETUNREACH;\n\t\treturn NULL;\n\t}\n\n\tif (icmp->icmp_id != ping.ident && ping.no_unprivileged_ping) {\n\t\ttlog(TLOG_WARN, \"ident failed, %d:%d\", icmp->icmp_id, ping.ident);\n\t\treturn NULL;\n\t}\n\n\tpacket->ttl = ip->ip_ttl;\n\treturn packet;\n}\n\nstatic struct fast_ping_packet *_fast_ping_recv_packet(struct ping_host_struct *ping_host, struct msghdr *msg,\n\t\t\t\t\t\t\t\t\t\t\t\t\t   u_char *inpacket, int len, struct timeval *tvrecv)\n{\n\tstruct fast_ping_packet *packet = NULL;\n\n\tif (ping_host->type == FAST_PING_ICMP6) {\n\t\tpacket = _fast_ping_icmp6_packet(ping_host, msg, inpacket, len);\n\t\tif (packet == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t} else if (ping_host->type == FAST_PING_ICMP) {\n\t\tpacket = _fast_ping_icmp_packet(ping_host, msg, inpacket, len);\n\t\tif (packet == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t} else {\n\t\ttlog(TLOG_ERROR, \"ping host type is invalid, %d\", ping_host->type);\n\t\tgoto errout;\n\t}\n\n\treturn packet;\nerrout:\n\treturn NULL;\n}\n\nint _fast_ping_process_icmp(struct ping_host_struct *ping_host, struct timeval *now)\n{\n\tint len = 0;\n\tu_char inpacket[ICMP_INPACKET_SIZE];\n\tstruct sockaddr_storage from;\n\tstruct ping_host_struct *recv_ping_host = NULL;\n\tstruct fast_ping_packet *packet = NULL;\n\tsocklen_t from_len = sizeof(from);\n\tuint32_t addrkey = 0;\n\tstruct timeval tvresult = *now;\n\tstruct timeval *tvsend = NULL;\n\tunsigned int sid = 0;\n\tunsigned int seq = 0;\n\tstruct msghdr msg;\n\tstruct iovec iov;\n\tchar ans_data[4096];\n\n\tmemset(&msg, 0, sizeof(msg));\n\tiov.iov_base = (char *)inpacket;\n\tiov.iov_len = sizeof(inpacket);\n\tmsg.msg_name = &from;\n\tmsg.msg_namelen = sizeof(from);\n\tmsg.msg_iov = &iov;\n\tmsg.msg_iovlen = 1;\n\tmsg.msg_control = ans_data;\n\tmsg.msg_controllen = sizeof(ans_data);\n\n\tlen = recvmsg(ping_host->fd, &msg, MSG_DONTWAIT);\n\tif (len < 0) {\n\t\ttlog(TLOG_ERROR, \"recvfrom failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tfrom_len = msg.msg_namelen;\n\tpacket = _fast_ping_recv_packet(ping_host, &msg, inpacket, len, now);\n\tif (packet == NULL) {\n\t\tchar name[PING_MAX_HOSTLEN];\n\t\tif (errno == ENETUNREACH) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"recv ping packet from %s failed.\",\n\t\t\t get_host_by_addr(name, sizeof(name), (struct sockaddr *)&from));\n\t\tgoto errout;\n\t}\n\n\ttvsend = &packet->msg.tv;\n\tsid = packet->msg.sid;\n\tseq = packet->msg.seq;\n\taddrkey = _fast_ping_hash_key(sid, (struct sockaddr *)&from);\n\tpthread_mutex_lock(&ping.map_lock);\n\thash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey)\n\t{\n\t\tif (_fast_ping_sockaddr_ip_cmp(&recv_ping_host->addr, recv_ping_host->addr_len, (struct sockaddr *)&from,\n\t\t\t\t\t\t\t\t\t   from_len) == 0 &&\n\t\t\trecv_ping_host->sid == sid) {\n\t\t\t_fast_ping_host_get(recv_ping_host);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tpthread_mutex_unlock(&ping.map_lock);\n\n\tif (recv_ping_host == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (recv_ping_host->seq != seq) {\n\t\ttlog(TLOG_ERROR, \"seq num mismatch, expect %u, real %u\", recv_ping_host->seq, seq);\n\t\t_fast_ping_host_put(recv_ping_host);\n\t\treturn -1;\n\t}\n\n\trecv_ping_host->ttl = packet->ttl;\n\ttv_sub(&tvresult, tvsend);\n\tif (recv_ping_host->ping_callback) {\n\t\t_fast_ping_send_notify_event(recv_ping_host, PING_RESULT_RESPONSE, recv_ping_host->seq, recv_ping_host->ttl,\n\t\t\t\t\t\t\t\t\t &tvresult);\n\t}\n\n\trecv_ping_host->send = 0;\n\n\tif (recv_ping_host->count == 1) {\n\t\t_fast_ping_host_remove(recv_ping_host);\n\t}\n\n\t_fast_ping_host_put(recv_ping_host);\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint _fast_ping_get_addr_by_icmp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type)\n{\n\tstruct addrinfo *gai = NULL;\n\tint socktype = 0;\n\tint domain = -1;\n\tFAST_PING_TYPE ping_type = 0;\n\tint sockproto = 0;\n\tchar *service = NULL;\n\n\tsocktype = SOCK_RAW;\n\tdomain = _fast_ping_getdomain(ip_str);\n\tif (domain < 0) {\n\t\tgoto errout;\n\t}\n\n\tswitch (domain) {\n\tcase AF_INET:\n\t\tsockproto = IPPROTO_ICMP;\n\t\tping_type = FAST_PING_ICMP;\n\t\tbreak;\n\tcase AF_INET6:\n\t\tsockproto = IPPROTO_ICMPV6;\n\t\tping_type = FAST_PING_ICMP6;\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\tif (out_gai != NULL) {\n\t\tgai = _fast_ping_getaddr(ip_str, service, socktype, sockproto);\n\t\tif (gai == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\t*out_gai = gai;\n\t}\n\n\tif (out_ping_type != NULL) {\n\t\t*out_ping_type = ping_type;\n\t}\n\n\treturn 0;\nerrout:\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\treturn -1;\n}\n\nint _fast_ping_sockaddr_ip_cmp(struct sockaddr *first_addr, socklen_t first_addr_len, struct sockaddr *second_addr,\n\t\t\t\t\t\t\t   socklen_t second_addr_len)\n{\n\tvoid *ip1, *ip2;\n\tint len1, len2;\n\n\tif (first_addr->sa_family == AF_INET) {\n\t\tip1 = &((struct sockaddr_in *)first_addr)->sin_addr.s_addr;\n\t\tlen1 = IPV4_ADDR_LEN;\n\t} else if (first_addr->sa_family == AF_INET6) {\n\t\tstruct in6_addr *in6 = &((struct sockaddr_in6 *)first_addr)->sin6_addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(in6)) {\n\t\t\tip1 = in6->s6_addr + 12;\n\t\t\tlen1 = IPV4_ADDR_LEN;\n\t\t} else {\n\t\t\tip1 = in6->s6_addr;\n\t\t\tlen1 = IPV6_ADDR_LEN;\n\t\t}\n\t} else {\n\t\treturn -1;\n\t}\n\n\tif (second_addr->sa_family == AF_INET) {\n\t\tip2 = &((struct sockaddr_in *)second_addr)->sin_addr.s_addr;\n\t\tlen2 = IPV4_ADDR_LEN;\n\t} else if (second_addr->sa_family == AF_INET6) {\n\t\tstruct in6_addr *in6 = &((struct sockaddr_in6 *)second_addr)->sin6_addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(in6)) {\n\t\t\tip2 = in6->s6_addr + 12;\n\t\t\tlen2 = IPV4_ADDR_LEN;\n\t\t} else {\n\t\t\tip2 = in6->s6_addr;\n\t\t\tlen2 = IPV6_ADDR_LEN;\n\t\t}\n\t} else {\n\t\treturn -1;\n\t}\n\n\tif (len1 != len2) {\n\t\treturn -1;\n\t}\n\n\treturn memcmp(ip1, ip2, len1);\n}\n\nuint16_t _fast_ping_checksum(uint16_t *header, size_t len)\n{\n\tuint32_t sum = 0;\n\tunsigned int i = 0;\n\n\tfor (i = 0; i < len / sizeof(uint16_t); i++) {\n\t\tsum += ntohs(header[i]);\n\t}\n\n\treturn htons(~((sum >> 16) + (sum & 0xffff)));\n}\n\nvoid _fast_ping_close_icmp(void)\n{\n\tif (ping.fd_icmp > 0) {\n\t\tepoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping.fd_icmp, NULL);\n\t\tclose(ping.fd_icmp);\n\t\tping.fd_icmp = -1;\n\t}\n\n\tif (ping.fd_icmp6 > 0) {\n\t\tepoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping.fd_icmp6, NULL);\n\t\tclose(ping.fd_icmp6);\n\t\tping.fd_icmp6 = -1;\n\t}\n}\n"
  },
  {
    "path": "src/fast_ping/ping_icmp.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_ICMP_H_\n#define _FAST_PING_ICMP_H_\n\n#include \"fast_ping.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _fast_ping_sendping_v4(struct ping_host_struct *ping_host);\n\nstruct fast_ping_packet *_fast_ping_icmp_packet(struct ping_host_struct *ping_host, struct msghdr *msg,\n\t\t\t\t\t\t\t\t\t\t\t\tu_char *packet_data, int data_len);\n\nint _fast_ping_sockaddr_ip_cmp(struct sockaddr *first_addr, socklen_t first_addr_len, struct sockaddr *second_addr,\n\t\t\t\t\t\t\t   socklen_t second_addr_len);\n\nuint16_t _fast_ping_checksum(uint16_t *header, size_t len);\n\nint _fast_ping_icmp_create_socket(struct ping_host_struct *ping_host);\n\nint _fast_ping_process_icmp(struct ping_host_struct *ping_host, struct timeval *now);\n\nint _fast_ping_get_addr_by_icmp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type);\n\nvoid _fast_ping_close_icmp(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_ICMP_H_\n"
  },
  {
    "path": "src/fast_ping/ping_icmp6.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"smartdns/util.h\"\n\n#include \"ping_icmp.h\"\n#include \"ping_icmp6.h\"\n\n#include <errno.h>\n#include <linux/filter.h>\n#include <string.h>\n#include <sys/epoll.h>\n\nvoid _fast_ping_install_filter_v6(int sock)\n{\n\tstruct icmp6_filter icmp6_filter;\n\tICMP6_FILTER_SETBLOCKALL(&icmp6_filter);\n\tICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &icmp6_filter);\n\tsetsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &icmp6_filter, sizeof(struct icmp6_filter));\n\n\tstatic int once;\n\tstatic struct sock_filter insns[] = {\n\t\tBPF_STMT(BPF_LD | BPF_H | BPF_ABS, 4),                       /* Load icmp echo ident */\n\t\tBPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0xAAAA, 0, 1),           /* Ours? */\n\t\tBPF_STMT(BPF_RET | BPF_K, ~0U),                              /* Yes, it passes. */\n\t\tBPF_STMT(BPF_LD | BPF_B | BPF_ABS, 0),                       /* Load icmp type */\n\t\tBPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP6_ECHO_REPLY, 1, 0), /* Echo? */\n\t\tBPF_STMT(BPF_RET | BPF_K, ~0U),                              /* No. It passes. This must not happen. */\n\t\tBPF_STMT(BPF_RET | BPF_K, 0),                                /* Echo with wrong ident. Reject. */\n\t};\n\tstatic struct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns};\n\n\tif (once) {\n\t\treturn;\n\t}\n\tonce = 1;\n\n\t/* Patch bpflet for current identifier. */\n\tinsns[1] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(ping.ident), 0, 1);\n\n\tif (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {\n\t\ttlog(TLOG_WARN, \"WARNING: failed to install socket filter\\n\");\n\t}\n}\n\nint _fast_ping_sendping_v6(struct ping_host_struct *ping_host)\n{\n\tstruct fast_ping_packet *packet = &ping_host->packet;\n\tstruct icmp6_hdr *icmp6 = &packet->icmp6;\n\tint len = 0;\n\n\tif (_fast_ping_icmp_create_socket(ping_host) < 0) {\n\t\tgoto errout;\n\t}\n\n\tif (ping.fd_icmp6 <= 0) {\n\t\terrno = EADDRNOTAVAIL;\n\t\tgoto errout;\n\t}\n\n\tping_host->seq++;\n\tmemset(icmp6, 0, sizeof(*icmp6));\n\ticmp6->icmp6_type = ICMP6_ECHO_REQUEST;\n\ticmp6->icmp6_code = 0;\n\ticmp6->icmp6_cksum = 0;\n\ticmp6->icmp6_id = ping.ident;\n\ticmp6->icmp6_seq = htons(ping_host->seq);\n\n\tgettimeofday(&packet->msg.tv, NULL);\n\tgettimeofday(&ping_host->last, NULL);\n\tpacket->msg.sid = ping_host->sid;\n\tpacket->msg.seq = ping_host->seq;\n\ticmp6->icmp6_cksum = _fast_ping_checksum((void *)packet, sizeof(struct fast_ping_packet));\n\n\tlen = sendto(ping.fd_icmp6, &ping_host->packet, sizeof(struct fast_ping_packet), 0, &ping_host->addr,\n\t\t\t\t ping_host->addr_len);\n\tif (len != sizeof(struct fast_ping_packet)) {\n\t\tint err = errno;\n\t\tswitch (err) {\n\t\tcase ENETUNREACH:\n\t\tcase EINVAL:\n\t\tcase EADDRNOTAVAIL:\n\t\tcase EHOSTUNREACH:\n\t\tcase ENOBUFS:\n\t\tcase EACCES:\n\t\tcase EPERM:\n\t\tcase EAFNOSUPPORT:\n\t\tcase EAGAIN:\n#if EWOULDBLOCK != EAGAIN\n\t\tcase EWOULDBLOCK:\n#endif\n\t\t\tgoto errout;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\n\t\tif (is_private_addr_sockaddr(&ping_host->addr, ping_host->addr_len)) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tchar ping_host_name[PING_MAX_HOSTLEN];\n\t\ttlog(TLOG_WARN, \"sendto %s, id %d, %s\",\n\t\t\t get_host_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr),\n\t\t\t ping_host->sid, strerror(err));\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nstruct fast_ping_packet *_fast_ping_icmp6_packet(struct ping_host_struct *ping_host, struct msghdr *msg,\n\t\t\t\t\t\t\t\t\t\t\t\t u_char *packet_data, int data_len)\n{\n\tint icmp_len = 0;\n\tstruct fast_ping_packet *packet = (struct fast_ping_packet *)packet_data;\n\tstruct icmp6_hdr *icmp6 = &packet->icmp6;\n\tstruct cmsghdr *c = NULL;\n\tint hops = 0;\n\n\tif (data_len < (int)sizeof(struct icmp6_hdr)) {\n\t\ttlog(TLOG_DEBUG, \"ping package length is invalid, %d, %d\", data_len, (int)sizeof(struct fast_ping_packet));\n\t\treturn NULL;\n\t}\n\n\tfor (c = CMSG_FIRSTHDR(msg); c; c = CMSG_NXTHDR(msg, c)) {\n\t\tif (c->cmsg_level != IPPROTO_IPV6) {\n\t\t\tcontinue;\n\t\t}\n\t\tswitch (c->cmsg_type) {\n\t\tcase IPV6_HOPLIMIT:\n#ifdef IPV6_2292HOPLIMIT\n\t\tcase IPV6_2292HOPLIMIT:\n#endif\n\t\t\tif (c->cmsg_len < CMSG_LEN(sizeof(int))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tmemcpy(&hops, CMSG_DATA(c), sizeof(hops));\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tpacket->ttl = hops;\n\tif (icmp6->icmp6_type != ICMP6_ECHO_REPLY) {\n\t\terrno = ENETUNREACH;\n\t\treturn NULL;\n\t}\n\n\ticmp_len = data_len;\n\tif (icmp_len < 16) {\n\t\ttlog(TLOG_ERROR, \"length is invalid, %d\", icmp_len);\n\t\treturn NULL;\n\t}\n\n\tif (ping.no_unprivileged_ping) {\n\t\tif (icmp6->icmp6_id != ping.ident) {\n\t\t\ttlog(TLOG_ERROR, \"ident failed, %d:%d\", icmp6->icmp6_id, ping.ident);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn packet;\n}\n"
  },
  {
    "path": "src/fast_ping/ping_icmp6.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_ICMP6_H_\n#define _FAST_PING_ICMP6_H_\n\n#include \"fast_ping.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nstruct fast_ping_packet *_fast_ping_icmp6_packet(struct ping_host_struct *ping_host, struct msghdr *msg,\n\t\t\t\t\t\t\t\t\t\t\t\t u_char *packet_data, int data_len);\n\nvoid _fast_ping_install_filter_v6(int sock);\n\nint _fast_ping_sendping_v6(struct ping_host_struct *ping_host);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_ICMP6_H_\n"
  },
  {
    "path": "src/fast_ping/ping_tcp.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"smartdns/util.h\"\n\n#include \"notify_event.h\"\n#include \"ping_host.h\"\n#include \"ping_tcp.h\"\n\n#include <errno.h>\n#include <fcntl.h>\n#include <linux/filter.h>\n#include <netinet/tcp.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/epoll.h>\n\nint _fast_ping_sendping_tcp(struct ping_host_struct *ping_host)\n{\n\tstruct epoll_event event;\n\tint flags = 0;\n\tint fd = -1;\n\tint yes = 1;\n\tconst int priority = SOCKET_PRIORITY;\n\tconst int ip_tos = IP_TOS;\n\n\t_fast_ping_close_host_sock(ping_host);\n\n\tfd = socket(ping_host->ss_family, SOCK_STREAM, 0);\n\tif (fd < 0) {\n\t\tgoto errout;\n\t}\n\n\tflags = fcntl(fd, F_GETFL, 0);\n\tfcntl(fd, F_SETFL, flags | O_NONBLOCK);\n\tsetsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));\n\tsetsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority));\n\tsetsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));\n\tset_sock_keepalive(fd, 0, 0, 0);\n\t/* Set the socket lingering so we will RST connections instead of wasting\n\t * bandwidth with the four-step close\n\t */\n\tset_sock_lingertime(fd, 0);\n\n\tping_host->seq++;\n\tif (connect(fd, &ping_host->addr, ping_host->addr_len) != 0) {\n\t\tif (errno != EINPROGRESS) {\n\t\t\tchar ping_host_name[PING_MAX_HOSTLEN];\n\t\t\tif (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EHOSTUNREACH) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (errno == EACCES || errno == EPERM) {\n\t\t\t\tif (bool_print_log == 0) {\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\tbool_print_log = 0;\n\t\t\t}\n\n\t\t\ttlog(TLOG_INFO, \"connect %s, id %d, %s\",\n\t\t\t\t get_host_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr),\n\t\t\t\t ping_host->sid, strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tgettimeofday(&ping_host->last, NULL);\n\tping_host->fd = fd;\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLOUT | EPOLLERR;\n\tevent.data.ptr = ping_host;\n\tif (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\tping_host->fd = -1;\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t\tping_host->fd = -1;\n\t}\n\treturn -1;\n}\n\nint _fast_ping_get_addr_by_tcp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type)\n{\n\tstruct addrinfo *gai = NULL;\n\tint socktype = 0;\n\tFAST_PING_TYPE ping_type = 0;\n\tint sockproto = 0;\n\tchar *service = NULL;\n\tchar port_str[MAX_IP_LEN];\n\n\tif (port <= 0) {\n\t\tport = 80;\n\t}\n\n\tsockproto = 0;\n\tsocktype = SOCK_STREAM;\n\tsnprintf(port_str, MAX_IP_LEN, \"%d\", port);\n\tservice = port_str;\n\tping_type = FAST_PING_TCP;\n\n\tgai = _fast_ping_getaddr(ip_str, service, socktype, sockproto);\n\tif (gai == NULL) {\n\t\tgoto errout;\n\t}\n\n\t*out_gai = gai;\n\t*out_ping_type = ping_type;\n\n\treturn 0;\nerrout:\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\treturn -1;\n}\n\nint _fast_ping_process_tcp(struct ping_host_struct *ping_host, struct epoll_event *event, struct timeval *now)\n{\n\tstruct timeval tvresult = *now;\n\tstruct timeval *tvsend = &ping_host->last;\n\tint connect_error = 0;\n\tsocklen_t len = sizeof(connect_error);\n\n\tif (event->events & EPOLLIN || event->events & EPOLLERR) {\n\t\tif (getsockopt(ping_host->fd, SOL_SOCKET, SO_ERROR, (char *)&connect_error, &len) != 0) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (connect_error != 0 && connect_error != ECONNREFUSED) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\ttv_sub(&tvresult, tvsend);\n\tif (ping_host->ping_callback) {\n\t\t_fast_ping_send_notify_event(ping_host, PING_RESULT_RESPONSE, ping_host->seq, ping_host->ttl, &tvresult);\n\t}\n\n\tping_host->send = 0;\n\n\t_fast_ping_close_host_sock(ping_host);\n\n\tif (ping_host->count == 1) {\n\t\t_fast_ping_host_remove(ping_host);\n\t}\n\treturn 0;\nerrout:\n\t_fast_ping_host_remove(ping_host);\n\n\treturn -1;\n}\n"
  },
  {
    "path": "src/fast_ping/ping_tcp.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_TCP_H_\n#define _FAST_PING_TCP_H_\n\n#include \"fast_ping.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _fast_ping_process_tcp(struct ping_host_struct *ping_host, struct epoll_event *event, struct timeval *now);\n\nint _fast_ping_get_addr_by_tcp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type);\n\nint _fast_ping_sendping_tcp(struct ping_host_struct *ping_host);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_TCP_H_\n"
  },
  {
    "path": "src/fast_ping/ping_tcp_syn.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"smartdns/util.h\"\n\n#include \"notify_event.h\"\n#include \"ping_host.h\"\n#include \"ping_icmp.h\"\n#include \"ping_tcp_syn.h\"\n\n#include <arpa/inet.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <linux/filter.h>\n#include <netinet/ip.h>\n#include <netinet/ip6.h>\n#include <netinet/tcp.h>\n#include <pthread.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/socket.h>\n#include <unistd.h>\n\n/* TCP/IP header structures */\nstruct pseudo_header {\n\tuint32_t source_address;\n\tuint32_t dest_address;\n\tuint8_t placeholder;\n\tuint8_t protocol;\n\tuint16_t tcp_length;\n} __attribute__((packed));\n\nstruct pseudo_header6 {\n\tstruct in6_addr source_address;\n\tstruct in6_addr dest_address;\n\tuint32_t tcp_length;\n\tuint8_t zeros[3];\n\tuint8_t next_header;\n} __attribute__((packed));\n\n/* Get local IP address based on destination address and routing table */\nstatic int _tcp_syn_get_local_addr(int family, struct sockaddr *dest, struct sockaddr_storage *local)\n{\n\tint sock = -1;\n\tsocklen_t addr_len;\n\tint ret = -1;\n\n\tsock = socket(family, SOCK_DGRAM, 0);\n\tif (sock < 0) {\n\t\treturn -1;\n\t}\n\n\taddr_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);\n\n\tgetsocket_inet(sock, (struct sockaddr *)local, &addr_len);\n\t/* Connect to determine which local interface will be used */\n\tif (connect(sock, dest, addr_len) != 0) {\n\t\tgoto cleanup;\n\t}\n\n\tif (getsocket_inet(sock, (struct sockaddr *)local, &addr_len) != 0) {\n\t\tgoto cleanup;\n\t}\n\n\tret = 0;\n\ncleanup:\n\tclose(sock);\n\treturn ret;\n}\n\n/* Reserve a TCP port by binding a SOCK_STREAM socket */\nstatic int _tcp_syn_reserve_port(int family, int *out_fd, uint16_t *out_port, struct sockaddr_storage *out_addr)\n{\n\tint sock = -1;\n\tsocklen_t addr_len;\n\tstruct sockaddr_storage bind_addr;\n\n\tmemset(&bind_addr, 0, sizeof(bind_addr));\n\n\tsock = socket(family, SOCK_STREAM, 0);\n\tif (sock < 0) {\n\t\ttlog(TLOG_ERROR, \"create TCP socket for port reservation failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\t/* Bind to any address with port 0 (kernel will assign an available port) */\n\tif (family == AF_INET) {\n\t\tstruct sockaddr_in *addr_in = (struct sockaddr_in *)&bind_addr;\n\t\taddr_in->sin_family = AF_INET;\n\t\taddr_in->sin_addr.s_addr = INADDR_ANY;\n\t\taddr_in->sin_port = 0;\n\t\taddr_len = sizeof(struct sockaddr_in);\n\t} else if (family == AF_INET6) {\n\t\tstruct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&bind_addr;\n\t\taddr_in6->sin6_family = AF_INET6;\n\t\taddr_in6->sin6_addr = in6addr_any;\n\t\taddr_in6->sin6_port = 0;\n\t\taddr_len = sizeof(struct sockaddr_in6);\n\t} else {\n\t\tclose(sock);\n\t\treturn -1;\n\t}\n\n\tif (bind(sock, (struct sockaddr *)&bind_addr, addr_len) < 0) {\n\t\ttlog(TLOG_ERROR, \"bind socket for port reservation failed, %s\", strerror(errno));\n\t\tclose(sock);\n\t\treturn -1;\n\t}\n\n\t/* Retrieve the assigned port */\n\tif (getsockname(sock, (struct sockaddr *)&bind_addr, &addr_len) != 0) {\n\t\ttlog(TLOG_ERROR, \"getsockname failed, %s\", strerror(errno));\n\t\tclose(sock);\n\t\treturn -1;\n\t}\n\n\t/* Extract port number */\n\tif (family == AF_INET) {\n\t\t*out_port = ntohs(((struct sockaddr_in *)&bind_addr)->sin_port);\n\t} else {\n\t\t*out_port = ntohs(((struct sockaddr_in6 *)&bind_addr)->sin6_port);\n\t}\n\n\t*out_fd = sock;\n\tmemcpy(out_addr, &bind_addr, addr_len);\n\n\treturn 0;\n}\n/* Install BPF filter for IPv4 to reduce unwanted TCP packets */\nstatic int _tcp_syn_install_bpf_ipv4(int fd, uint16_t port)\n{\n\t/* Filter logic (same pattern as ICMP):\n\t * Accept packets where TCP destination port == our port\n\t * This filters out most unwanted TCP traffic\n\t */\n\tstruct sock_filter insns[] = {\n\t\tBPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0),          /* X = IP header length */\n\t\tBPF_STMT(BPF_LD | BPF_H | BPF_IND, 2),           /* A = TCP dst port at [X+2] */\n\t\tBPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, port, 0, 1), /* if (A == port) skip 0 else skip 1 */\n\t\tBPF_STMT(BPF_RET | BPF_K, ~0U),                  /* accept: return -1 (pass) */\n\t\tBPF_STMT(BPF_RET | BPF_K, 0)                     /* reject: return 0 (drop) */\n\t};\n\n\tstruct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns};\n\n\tif (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) < 0) {\n\t\ttlog(TLOG_WARN, \"WARNING: failed to install TCP SYN socket filter: %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\n/* Install BPF filter for IPv6 to reduce unwanted TCP packets */\nstatic int _tcp_syn_install_bpf_ipv6(int fd, uint16_t port)\n{\n\t/* Filter logic (same pattern as ICMP6):\n\t * Accept packets where TCP destination port == our port\n\t * IPv6 raw sockets don't include IP header, so use BPF_ABS mode\n\t */\n\tstruct sock_filter insns[] = {\n\t\tBPF_STMT(BPF_LD | BPF_H | BPF_ABS, 2),           /* A = TCP dst port at offset 2 */\n\t\tBPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, port, 0, 1), /* if (A == port) skip 0 else skip 1 */\n\t\tBPF_STMT(BPF_RET | BPF_K, ~0U),                  /* accept: return -1 (pass) */\n\t\tBPF_STMT(BPF_RET | BPF_K, 0),                    /* reject: return 0 (drop) */\n\t};\n\n\tstruct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns};\n\n\tif (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) < 0) {\n\t\ttlog(TLOG_WARN, \"ERROR: failed to install TCP SYN IPv6 socket filter: %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\n/* Build and send IPv4 RST packet */\nstatic int _tcp_syn_send_rst_ipv4(struct ping_host_struct *ping_host, struct sockaddr_storage *local_addr, uint32_t seq,\n\t\t\t\t\t\t\t\t  uint32_t ack_seq)\n{\n\tchar packet[4096];\n\tstruct tcphdr *tcp_packet;\n\tint packet_len;\n\tchar *pseudo_packet = NULL;\n\tint ret = -1;\n\n\t/* Verify address family */\n\tif (local_addr->ss_family != AF_INET) {\n\t\treturn -1;\n\t}\n\n\tstruct iphdr *ip = (struct iphdr *)packet;\n\tstruct sockaddr_in *local_in = (struct sockaddr_in *)local_addr;\n\tstruct sockaddr_in *dest_in = (struct sockaddr_in *)&ping_host->addr;\n\n\tmemset(packet, 0, sizeof(packet));\n\ttcp_packet = (struct tcphdr *)(packet + sizeof(struct iphdr));\n\tpacket_len = sizeof(struct iphdr) + sizeof(struct tcphdr); /* RST packet should only contain TCP header, no data */\n\n\t/* Fill IP header */\n\tip->ihl = 5;\n\tip->version = 4;\n\tip->tos = 0;\n\tip->tot_len = htons(packet_len);\n\tip->id = htons(ping_host->sid);\n\tip->frag_off = 0;\n\tip->ttl = 64;\n\tip->protocol = IPPROTO_TCP;\n\tip->check = 0;\n\tip->saddr = local_in->sin_addr.s_addr;\n\tip->daddr = dest_in->sin_addr.s_addr;\n\tip->check = _fast_ping_checksum((uint16_t *)packet, sizeof(struct iphdr));\n\n\t/* Fill TCP header */\n\ttcp_packet->source = htons(ping_host->tcp_local_port); /* Use the same source port as SYN packet */\n\ttcp_packet->dest = dest_in->sin_port;\n\ttcp_packet->seq = htonl(seq);\n\ttcp_packet->ack_seq = htonl(ack_seq);\n\ttcp_packet->doff = 5;\n\ttcp_packet->rst = 1;\n\ttcp_packet->ack = 1;\n\ttcp_packet->window = 0;\n\ttcp_packet->check = 0;\n\ttcp_packet->urg_ptr = 0;\n\n\t/* Calculate TCP checksum with pseudo header */\n\tpseudo_packet = zalloc(1, sizeof(struct pseudo_header) + sizeof(struct tcphdr));\n\tif (pseudo_packet == NULL) {\n\t\tgoto errout;\n\t}\n\n\tstruct pseudo_header *psh = (struct pseudo_header *)pseudo_packet;\n\tpsh->source_address = local_in->sin_addr.s_addr;\n\tpsh->dest_address = dest_in->sin_addr.s_addr;\n\tpsh->placeholder = 0;\n\tpsh->protocol = IPPROTO_TCP;\n\tpsh->tcp_length = htons(sizeof(struct tcphdr));\n\n\tmemcpy(pseudo_packet + sizeof(struct pseudo_header), tcp_packet, sizeof(struct tcphdr));\n\n\ttcp_packet->check =\n\t\t_fast_ping_checksum((uint16_t *)pseudo_packet, sizeof(struct pseudo_header) + sizeof(struct tcphdr));\n\n\tif (sendto(ping.fd_tcp_syn, packet, packet_len, 0, &ping_host->addr, ping_host->addr_len) < 0) {\n\t\ttlog(TLOG_DEBUG, \"send IPv4 RST packet failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tret = 0;\n\nerrout:\n\tif (pseudo_packet) {\n\t\tfree(pseudo_packet);\n\t}\n\treturn ret;\n}\n\n/* Build and send IPv6 RST packet */\nstatic int _tcp_syn_send_rst_ipv6(struct ping_host_struct *ping_host, struct sockaddr_storage *local_addr, uint32_t seq,\n\t\t\t\t\t\t\t\t  uint32_t ack_seq)\n{\n\tchar packet[4096];\n\tstruct tcphdr *tcp_packet;\n\tint packet_len;\n\tchar *pseudo_packet = NULL;\n\tint ret = -1;\n\n\t/* Verify address family */\n\tif (local_addr->ss_family != AF_INET6) {\n\t\treturn -1;\n\t}\n\n\tstruct sockaddr_in6 *local_in6 = (struct sockaddr_in6 *)local_addr;\n\tstruct sockaddr_in6 *dest_in6 = (struct sockaddr_in6 *)&ping_host->addr;\n\n\tmemset(packet, 0, sizeof(packet));\n\ttcp_packet = (struct tcphdr *)packet;\n\tpacket_len = sizeof(struct tcphdr); /* RST packet should only contain TCP header, no data */\n\n\t/* Fill TCP header */\n\ttcp_packet->source = htons(ping_host->tcp_local_port); /* Use the same source port as SYN packet */\n\ttcp_packet->dest = dest_in6->sin6_port;\n\ttcp_packet->seq = htonl(seq);\n\ttcp_packet->ack_seq = htonl(ack_seq);\n\ttcp_packet->doff = 5;\n\ttcp_packet->rst = 1;\n\ttcp_packet->ack = 1;\n\ttcp_packet->window = 0;\n\ttcp_packet->check = 0;\n\ttcp_packet->urg_ptr = 0;\n\n\t/* Calculate TCP checksum with pseudo header */\n\tpseudo_packet = malloc(sizeof(struct pseudo_header6) + sizeof(struct tcphdr));\n\tif (pseudo_packet == NULL) {\n\t\tgoto errout;\n\t}\n\n\tstruct pseudo_header6 *psh6 = (struct pseudo_header6 *)pseudo_packet;\n\tmemcpy(&psh6->source_address, &local_in6->sin6_addr, sizeof(struct in6_addr));\n\tmemcpy(&psh6->dest_address, &dest_in6->sin6_addr, sizeof(struct in6_addr));\n\tpsh6->tcp_length = htonl(sizeof(struct tcphdr));\n\tmemset(psh6->zeros, 0, 3);\n\tpsh6->next_header = IPPROTO_TCP;\n\n\tmemcpy(pseudo_packet + sizeof(struct pseudo_header6), tcp_packet, sizeof(struct tcphdr));\n\n\ttcp_packet->check =\n\t\t_fast_ping_checksum((uint16_t *)pseudo_packet, sizeof(struct pseudo_header6) + sizeof(struct tcphdr));\n\n\t/* For IPv6 raw TCP socket, sin6_port must be 0 */\n\tstruct sockaddr_storage dest_addr;\n\tmemcpy(&dest_addr, &ping_host->addr, ping_host->addr_len);\n\t((struct sockaddr_in6 *)&dest_addr)->sin6_port = 0;\n\n\tif (sendto(ping.fd_tcp_syn6, packet, packet_len, 0, (struct sockaddr *)&dest_addr, ping_host->addr_len) < 0) {\n\t\ttlog(TLOG_DEBUG, \"send IPv6 RST packet failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tret = 0;\n\nerrout:\n\tif (pseudo_packet) {\n\t\tfree(pseudo_packet);\n\t}\n\treturn ret;\n}\n\n/* Send RST packet to close connection */\n__attribute__((unused)) static int _tcp_syn_send_rst(struct ping_host_struct *ping_host, uint32_t seq, uint32_t ack_seq)\n{\n\tstruct sockaddr_storage local_addr;\n\n\tmemset(&local_addr, 0, sizeof(local_addr));\n\tif (_tcp_syn_get_local_addr(ping_host->ss_family, &ping_host->addr, &local_addr) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (ping_host->ss_family == AF_INET) {\n\t\treturn _tcp_syn_send_rst_ipv4(ping_host, &local_addr, seq, ack_seq);\n\t} else if (ping_host->ss_family == AF_INET6) {\n\t\treturn _tcp_syn_send_rst_ipv6(ping_host, &local_addr, seq, ack_seq);\n\t}\n\n\treturn -1;\n}\n\n/* Build IPv4 SYN packet */\nstatic int _tcp_syn_build_packet_ipv4(char *packet, struct ping_host_struct *ping_host,\n\t\t\t\t\t\t\t\t\t  struct sockaddr_storage *local_addr, uint16_t local_port, uint32_t seq_num)\n{\n\tstruct iphdr *ip = (struct iphdr *)packet;\n\tstruct tcphdr *tcph;\n\tstruct sockaddr_in *local_in = (struct sockaddr_in *)local_addr;\n\tstruct sockaddr_in *dest_in = (struct sockaddr_in *)&ping_host->addr;\n\tchar *pseudo_packet = NULL;\n\tint packet_len;\n\tint ret = -1;\n\n\ttcph = (struct tcphdr *)(packet + sizeof(struct iphdr));\n\tpacket_len = sizeof(struct iphdr) + sizeof(struct tcphdr);\n\n\t/* Fill IP header */\n\tip->ihl = 5;\n\tip->version = 4;\n\tip->tos = 0;\n\tip->tot_len = htons(packet_len);\n\tip->id = htons(ping_host->sid);\n\tip->frag_off = 0;\n\tip->ttl = 64;\n\tip->protocol = IPPROTO_TCP;\n\tip->check = 0;\n\tip->saddr = local_in->sin_addr.s_addr;\n\tip->daddr = dest_in->sin_addr.s_addr;\n\tip->check = _fast_ping_checksum((uint16_t *)packet, sizeof(struct iphdr));\n\n\t/* Fill TCP header - use reserved port */\n\tmemset(tcph, 0, sizeof(struct tcphdr));\n\ttcph->source = htons(local_port);\n\ttcph->dest = dest_in->sin_port;\n\ttcph->seq = htonl(seq_num);\n\ttcph->ack_seq = htonl(0);\n\ttcph->doff = 5;\n\ttcph->fin = 0;\n\ttcph->syn = 1;\n\ttcph->rst = 0;\n\ttcph->psh = 0;\n\ttcph->ack = 0;\n\ttcph->urg = 0;\n\ttcph->window = htons(8192);\n\ttcph->check = 0;\n\ttcph->urg_ptr = htons(0);\n\n\t/* Calculate TCP checksum with pseudo header */\n\tpseudo_packet = zalloc(1, sizeof(struct pseudo_header) + sizeof(struct tcphdr));\n\tif (pseudo_packet == NULL) {\n\t\tgoto errout;\n\t}\n\n\tstruct pseudo_header *psh = (struct pseudo_header *)pseudo_packet;\n\tpsh->source_address = local_in->sin_addr.s_addr;\n\tpsh->dest_address = dest_in->sin_addr.s_addr;\n\tpsh->placeholder = 0;\n\tpsh->protocol = IPPROTO_TCP;\n\tpsh->tcp_length = htons(sizeof(struct tcphdr));\n\n\ttcph->check = 0;\n\tmemcpy(pseudo_packet + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr));\n\n\ttcph->check = _fast_ping_checksum((uint16_t *)pseudo_packet, sizeof(struct pseudo_header) + sizeof(struct tcphdr));\n\n\tret = packet_len;\n\nerrout:\n\tif (pseudo_packet) {\n\t\tfree(pseudo_packet);\n\t}\n\treturn ret;\n}\n\n/* Build IPv6 SYN packet */\nstatic int _tcp_syn_build_packet_ipv6(char *packet, struct ping_host_struct *ping_host,\n\t\t\t\t\t\t\t\t\t  struct sockaddr_storage *local_addr, uint16_t local_port, uint32_t seq_num)\n{\n\tstruct tcphdr *tcph;\n\tstruct sockaddr_in6 *local_in6 = (struct sockaddr_in6 *)local_addr;\n\tstruct sockaddr_in6 *dest_in6 = (struct sockaddr_in6 *)&ping_host->addr;\n\tchar *pseudo_packet = NULL;\n\tint packet_len;\n\tint ret = -1;\n\n\ttcph = (struct tcphdr *)packet;\n\tpacket_len = sizeof(struct tcphdr);\n\n\tmemset(tcph, 0, sizeof(struct tcphdr));\n\ttcph->source = htons(local_port);\n\ttcph->dest = dest_in6->sin6_port;\n\ttcph->seq = htonl(seq_num);\n\ttcph->ack_seq = 0;\n\ttcph->doff = 5; /* TCP header length: 5 * 4 = 20 bytes */\n\ttcph->syn = 1;\n\ttcph->window = htons(8192);\n\ttcph->check = 0;\n\ttcph->urg_ptr = 0;\n\n\t/* Calculate TCP checksum with pseudo header */\n\tpseudo_packet = malloc(sizeof(struct pseudo_header6) + sizeof(struct tcphdr));\n\tif (pseudo_packet == NULL) {\n\t\tgoto errout;\n\t}\n\n\tstruct pseudo_header6 *psh6 = (struct pseudo_header6 *)pseudo_packet;\n\tmemcpy(&psh6->source_address, &local_in6->sin6_addr, sizeof(struct in6_addr));\n\tmemcpy(&psh6->dest_address, &dest_in6->sin6_addr, sizeof(struct in6_addr));\n\tpsh6->tcp_length = htonl(sizeof(struct tcphdr));\n\tmemset(psh6->zeros, 0, 3);\n\tpsh6->next_header = IPPROTO_TCP;\n\n\tmemcpy(pseudo_packet + sizeof(struct pseudo_header6), tcph, sizeof(struct tcphdr));\n\n\ttcph->check = _fast_ping_checksum((uint16_t *)pseudo_packet, sizeof(struct pseudo_header6) + sizeof(struct tcphdr));\n\n\tret = packet_len;\n\nerrout:\n\tif (pseudo_packet) {\n\t\tfree(pseudo_packet);\n\t}\n\treturn ret;\n}\n\nint _fast_ping_sendping_tcp_syn(struct ping_host_struct *ping_host)\n{\n\tchar packet[4096];\n\tint packet_len = 0;\n\tint fd = -1;\n\tstruct sockaddr_storage local_addr;\n\tuint32_t seq_num = 0;\n\tuint16_t local_port = 0;\n\n\t/* Create socket on first use */\n\tif (_fast_ping_tcp_syn_create_socket(ping_host) < 0) {\n\t\treturn -1;\n\t}\n\n\tmemset(&local_addr, 0, sizeof(local_addr));\n\tif (_tcp_syn_get_local_addr(ping_host->ss_family, &ping_host->addr, &local_addr) != 0) {\n\t\treturn -1;\n\t}\n\n\t/* Use the reserved port instead of getting a new one each time */\n\tif (ping_host->ss_family == AF_INET) {\n\t\tif (ping.tcp_syn_bind_port == 0) {\n\t\t\ttlog(TLOG_ERROR, \"TCP SYN bind port not initialized\");\n\t\t\treturn -1;\n\t\t}\n\t\tlocal_port = ping.tcp_syn_bind_port;\n\t\tping_host->tcp_local_port = local_port;\n\t\tfd = ping.fd_tcp_syn;\n\t} else if (ping_host->ss_family == AF_INET6) {\n\t\tif (ping.tcp_syn6_bind_port == 0) {\n\t\t\ttlog(TLOG_ERROR, \"TCP SYN IPv6 bind port not initialized\");\n\t\t\treturn -1;\n\t\t}\n\t\tlocal_port = ping.tcp_syn6_bind_port;\n\t\tping_host->tcp_local_port = local_port;\n\t\tfd = ping.fd_tcp_syn6;\n\t} else {\n\t\treturn -1;\n\t}\n\n\tmemset(packet, 0, sizeof(packet));\n\n\t/* Generate sequence number: combine sid and timestamp for uniqueness */\n\tseq_num = (ping_host->sid << 16) | (get_tick_count() & 0xFFFF);\n\tping_host->seq++;\n\n\t/* Build packet based on address family */\n\tif (ping_host->ss_family == AF_INET) {\n\t\tpacket_len = _tcp_syn_build_packet_ipv4(packet, ping_host, &local_addr, local_port, seq_num);\n\t} else if (ping_host->ss_family == AF_INET6) {\n\t\tpacket_len = _tcp_syn_build_packet_ipv6(packet, ping_host, &local_addr, local_port, seq_num);\n\t} else {\n\t\treturn -1;\n\t}\n\n\tif (packet_len < 0 || fd < 0) {\n\t\treturn -1;\n\t}\n\n\t/* Send SYN packet */\n\tgettimeofday(&ping_host->last, NULL);\n\tstruct sockaddr_storage dest_addr;\n\tmemcpy(&dest_addr, &ping_host->addr, ping_host->addr_len);\n\n\tif (ping_host->ss_family == AF_INET6) {\n\t\tstruct sockaddr_in6 *dest_in6 = (struct sockaddr_in6 *)&dest_addr;\n\t\tdest_in6->sin6_port = 0; /* Must be 0 for raw TCP socket */\n\t}\n\n\tssize_t len = sendto(fd, packet, packet_len, 0, (struct sockaddr *)&dest_addr, ping_host->addr_len);\n\tif (len < 0) {\n\t\ttlog(TLOG_DEBUG, \"send SYN packet failed, %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint _fast_ping_get_addr_by_tcp_syn(const char *ip_str, int port, struct addrinfo **out_gai,\n\t\t\t\t\t\t\t\t   FAST_PING_TYPE *out_ping_type)\n{\n\tstruct addrinfo *gai = NULL;\n\tchar port_str[MAX_IP_LEN];\n\n\tif (port <= 0) {\n\t\tport = 80;\n\t}\n\n\tsnprintf(port_str, sizeof(port_str), \"%d\", port);\n\n\tgai = _fast_ping_getaddr(ip_str, port_str, SOCK_STREAM, 0);\n\tif (gai == NULL) {\n\t\treturn -1;\n\t}\n\n\t*out_gai = gai;\n\t*out_ping_type = FAST_PING_TCP_SYN;\n\n\treturn 0;\n}\n\nint _fast_ping_process_tcp_syn(struct ping_host_struct *ping_host, struct timeval *now)\n{\n\tchar packet[4096];\n\tstruct sockaddr_storage from_addr;\n\tsocklen_t from_len = sizeof(from_addr);\n\tint fd = -1;\n\tssize_t recv_len = 0;\n\tstruct ping_host_struct *recv_ping_host = NULL;\n\n\t/* Determine which socket received data */\n\tif (ping_host->type == FAST_PING_TCP_SYN && ping_host->fd == ping.fd_tcp_syn) {\n\t\tfd = ping.fd_tcp_syn;\n\t} else if (ping_host->type == FAST_PING_TCP_SYN && ping_host->fd == ping.fd_tcp_syn6) {\n\t\tfd = ping.fd_tcp_syn6;\n\t} else {\n\t\treturn 0;\n\t}\n\n\tif (fd < 0) {\n\t\treturn -1;\n\t}\n\n\t/* Receive all available packets */\n\twhile (1) {\n\t\tmemset(packet, 0, sizeof(packet));\n\t\tfrom_len = sizeof(from_addr);\n\t\trecv_len = recvfrom(fd, packet, sizeof(packet), MSG_DONTWAIT, (struct sockaddr *)&from_addr, &from_len);\n\t\tif (recv_len < 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\ttlog(TLOG_DEBUG, \"recvfrom error: %s\", strerror(errno));\n\t\t\treturn -1;\n\t\t}\n\n\t\t/* Parse packet based on socket type */\n\t\tstruct tcphdr *tcp = NULL;\n\n\t\t/* For IPv4 raw socket, packet includes IP header */\n\t\tif (fd == ping.fd_tcp_syn) {\n\t\t\tif (recv_len < (ssize_t)(sizeof(struct iphdr) + sizeof(struct tcphdr))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tstruct iphdr *ip = (struct iphdr *)packet;\n\n\t\t\t/* Validate IP header length */\n\t\t\tif (ip->ihl < 5 || ip->ihl > 15) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Ensure we have enough data for the full IP header */\n\t\t\tsize_t ip_header_len = ip->ihl * 4;\n\t\t\tif (recv_len < (ssize_t)(ip_header_len + sizeof(struct tcphdr))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttcp = (struct tcphdr *)(packet + ip_header_len);\n\n\t\t\t/* Extract source address from IP header */\n\t\t\tstruct sockaddr_in *from_in = (struct sockaddr_in *)&from_addr;\n\t\t\tfrom_in->sin_family = AF_INET;\n\t\t\tfrom_in->sin_addr.s_addr = ip->saddr;\n\t\t} else if (fd == ping.fd_tcp_syn6) {\n\t\t\t/* For IPv6 raw socket, packet starts with TCP header */\n\t\t\tif (recv_len < (ssize_t)(sizeof(struct tcphdr))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ttcp = (struct tcphdr *)packet;\n\t\t} else {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Validate TCP header data offset */\n\t\tif (tcp->doff < 5 || tcp->doff > 15) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Ensure we have enough data for the full TCP header */\n\t\tsize_t tcp_header_len = tcp->doff * 4;\n\t\tif (fd == ping.fd_tcp_syn) {\n\t\t\t/* For IPv4, account for IP header */\n\t\t\tsize_t ip_header_len = ((struct iphdr *)packet)->ihl * 4;\n\t\t\tif (recv_len < (ssize_t)(ip_header_len + tcp_header_len)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else {\n\t\t\t/* For IPv6 */\n\t\t\tif (recv_len < (ssize_t)tcp_header_len) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t/* Check if SYN-ACK or RST */\n\t\tif (!((tcp->syn && tcp->ack) || (tcp->rst && tcp->ack))) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Extract sid from ack_seq\n\t\t * We sent: seq = (sid << 16) | timestamp\n\t\t * Server responds: ack_seq = our_seq + 1\n\t\t * So: (ack_seq - 1) >> 16 should equal our sid */\n\t\tuint32_t received_ack = ntohl(tcp->ack_seq);\n\t\tuint16_t received_sid = (received_ack - 1) >> 16;\n\n\t\t/* Calculate hash key using sid and source address */\n\t\tuint32_t addrkey = _fast_ping_hash_key(received_sid, (struct sockaddr *)&from_addr);\n\n\t\t/* Find matching ping_host using hash table lookup */\n\t\tpthread_mutex_lock(&ping.map_lock);\n\t\trecv_ping_host = NULL;\n\t\thash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey)\n\t\t{\n\t\t\tif (recv_ping_host->type != FAST_PING_TCP_SYN) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Verify sid matches */\n\t\t\tif (recv_ping_host->sid != received_sid) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Verify address matches */\n\t\t\tif (_fast_ping_sockaddr_ip_cmp(&recv_ping_host->addr, recv_ping_host->addr_len,\n\t\t\t\t\t\t\t\t\t\t   (struct sockaddr *)&from_addr, from_len) != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Check if currently sending - skip old entries */\n\t\t\tif (recv_ping_host->send == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Found matching ping_host */\n\t\t\t_fast_ping_host_get(recv_ping_host);\n\t\t\tbreak;\n\t\t}\n\t\tpthread_mutex_unlock(&ping.map_lock);\n\n\t\tif (recv_ping_host == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* No need to send RST, the linux kernel will send RST automatically for raw TCP sockets */\n\n\t\t/* Calculate RTT */\n\t\tstruct timeval tvresult = *now;\n\t\tstruct timeval *tvsend = &recv_ping_host->last;\n\t\ttv_sub(&tvresult, tvsend);\n\n\t\t/* Report success */\n\t\t_fast_ping_send_notify_event(recv_ping_host, PING_RESULT_RESPONSE, recv_ping_host->seq, 64, &tvresult);\n\t\trecv_ping_host->send = 0;\n\n\t\tif (recv_ping_host->count == 1) {\n\t\t\t/* Remove this ping_host */\n\t\t\t_fast_ping_host_remove(recv_ping_host);\n\t\t}\n\n\t\t_fast_ping_host_put(recv_ping_host);\n\t}\n\n\treturn 0;\n}\n\n/* Create IPv4 TCP SYN raw socket with port reservation and BPF filter */\nstatic int _fast_ping_create_tcp_syn_sock_ipv4(void)\n{\n\tint fd = -1;\n\tconst int on = 1;\n\tuint16_t bind_port = 0;\n\n\t/* Reserve a port for IPv4 TCP SYN ping */\n\tif (ping.fd_tcp_syn_bind <= 0) {\n\t\tif (_tcp_syn_reserve_port(AF_INET, &ping.fd_tcp_syn_bind, &ping.tcp_syn_bind_port,\n\t\t\t\t\t\t\t\t  (struct sockaddr_storage *)&ping.tcp_syn_bind_addr) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"failed to reserve IPv4 port for TCP SYN\");\n\t\t\tgoto errout;\n\t\t}\n\t}\n\tbind_port = ping.tcp_syn_bind_port;\n\n\t/* Create IPv4 raw socket */\n\tfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);\n\tif (fd < 0) {\n\t\tif (errno == EPERM || errno == EACCES) {\n\t\t\ttlog(TLOG_DEBUG, \"create TCP SYN raw socket failed, %s (need root or CAP_NET_RAW capability)\",\n\t\t\t\t strerror(errno));\n\t\t} else {\n\t\t\ttlog(TLOG_ERROR, \"create TCP SYN raw socket failed, %s\", strerror(errno));\n\t\t}\n\t\tgoto errout;\n\t}\n\n\t/* Set socket options */\n\tif (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {\n\t\ttlog(TLOG_ERROR, \"setsockopt IP_HDRINCL failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\t/* Install BPF filter */\n\t_tcp_syn_install_bpf_ipv4(fd, bind_port);\n\n\t/* Set non-blocking */\n\tif (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) {\n\t\ttlog(TLOG_ERROR, \"fcntl set non-blocking failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\treturn fd;\n\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\treturn -1;\n}\n\n/* Create IPv6 TCP SYN raw socket with port reservation and BPF filter */\nstatic int _fast_ping_create_tcp_syn_sock_ipv6(void)\n{\n\tint fd = -1;\n\tuint16_t bind_port = 0;\n\n\t/* Reserve a port for IPv6 TCP SYN ping */\n\tif (ping.fd_tcp_syn6_bind <= 0) {\n\t\tif (_tcp_syn_reserve_port(AF_INET6, &ping.fd_tcp_syn6_bind, &ping.tcp_syn6_bind_port,\n\t\t\t\t\t\t\t\t  (struct sockaddr_storage *)&ping.tcp_syn6_bind_addr) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"failed to reserve IPv6 port for TCP SYN\");\n\t\t\tgoto errout;\n\t\t}\n\t}\n\tbind_port = ping.tcp_syn6_bind_port;\n\n\t/* Create IPv6 raw socket */\n\tfd = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP);\n\tif (fd < 0) {\n\t\tif (errno != EAFNOSUPPORT) {\n\t\t\ttlog(TLOG_DEBUG, \"create TCP SYN IPv6 raw socket failed, %s\", strerror(errno));\n\t\t}\n\t\tgoto errout;\n\t}\n\n\t/* Install BPF filter */\n\t_tcp_syn_install_bpf_ipv6(fd, bind_port);\n\n\t/* Set non-blocking */\n\tif (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) {\n\t\ttlog(TLOG_ERROR, \"fcntl set non-blocking failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\treturn fd;\n\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\treturn -1;\n}\n\nstatic int _fast_ping_create_tcp_syn_sock(int is_ipv6)\n{\n\tint fd = -1;\n\tstruct epoll_event event;\n\tstruct ping_host_struct *tcp_syn_host = NULL;\n\n\tif (!is_ipv6) {\n\t\tfd = _fast_ping_create_tcp_syn_sock_ipv4();\n\t\tif (fd < 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\ttcp_syn_host = &ping.tcp_syn_host;\n\t} else {\n\t\tfd = _fast_ping_create_tcp_syn_sock_ipv6();\n\t\tif (fd < 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\ttcp_syn_host = &ping.tcp_syn6_host;\n\t}\n\n\ttcp_syn_host->fd = fd;\n\ttcp_syn_host->type = FAST_PING_TCP_SYN;\n\n\t/* Add to epoll */\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN;\n\tevent.data.ptr = tcp_syn_host;\n\tif (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"add TCP SYN socket to epoll failed, %s\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\treturn fd;\n\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\tif (tcp_syn_host) {\n\t\ttcp_syn_host->fd = -1;\n\t\ttcp_syn_host->type = 0;\n\t}\n\treturn -1;\n}\n\nstatic int _fast_ping_create_tcp_syn(int is_ipv6)\n{\n\tint fd = -1;\n\tint *set_fd = NULL;\n\n\tpthread_mutex_lock(&ping.lock);\n\n\tif (!is_ipv6) {\n\t\tset_fd = &ping.fd_tcp_syn;\n\t} else {\n\t\tset_fd = &ping.fd_tcp_syn6;\n\t}\n\n\tif (*set_fd > 0) {\n\t\tgoto out;\n\t}\n\n\tfd = _fast_ping_create_tcp_syn_sock(is_ipv6);\n\tif (fd < 0) {\n\t\tgoto errout;\n\t}\n\n\t*set_fd = fd;\nout:\n\tpthread_mutex_unlock(&ping.lock);\n\treturn *set_fd;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\tpthread_mutex_unlock(&ping.lock);\n\treturn -1;\n}\n\nint _fast_ping_tcp_syn_create_socket(struct ping_host_struct *ping_host)\n{\n\tFAST_PING_TYPE type = ping_host->type;\n\n\tif (type != FAST_PING_TCP_SYN) {\n\t\tgoto errout;\n\t}\n\n\t/* Determine IPv4 or IPv6 based on address family */\n\tif (ping_host->ss_family == AF_INET) {\n\t\tif (_fast_ping_create_tcp_syn(0) < 0) {\n\t\t\tgoto errout;\n\t\t}\n\t\tif (ping.fd_tcp_syn <= 0) {\n\t\t\terrno = EADDRNOTAVAIL;\n\t\t\tgoto errout;\n\t\t}\n\t} else if (ping_host->ss_family == AF_INET6) {\n\t\t/* For IPv6, we need to create a separate socket */\n\t\t/* Use a special internal type indicator */\n\t\tpthread_mutex_lock(&ping.lock);\n\t\tif (ping.fd_tcp_syn6 <= 0) {\n\t\t\tint fd = _fast_ping_create_tcp_syn_sock(1);\n\t\t\tif (fd > 0) {\n\t\t\t\tping.fd_tcp_syn6 = fd;\n\t\t\t}\n\t\t}\n\t\tpthread_mutex_unlock(&ping.lock);\n\n\t\tif (ping.fd_tcp_syn6 <= 0) {\n\t\t\terrno = EADDRNOTAVAIL;\n\t\t\tgoto errout;\n\t\t}\n\t} else {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nvoid _fast_ping_close_tcp_syn(void)\n{\n\tif (ping.fd_tcp_syn > 0) {\n\t\tclose(ping.fd_tcp_syn);\n\t\tping.fd_tcp_syn = -1;\n\t}\n\n\tif (ping.fd_tcp_syn6 > 0) {\n\t\tclose(ping.fd_tcp_syn6);\n\t\tping.fd_tcp_syn6 = -1;\n\t}\n\n\t/* Close bind sockets */\n\tif (ping.fd_tcp_syn_bind > 0) {\n\t\tclose(ping.fd_tcp_syn_bind);\n\t\tping.fd_tcp_syn_bind = -1;\n\t\tping.tcp_syn_bind_port = 0;\n\t}\n\n\tif (ping.fd_tcp_syn6_bind > 0) {\n\t\tclose(ping.fd_tcp_syn6_bind);\n\t\tping.fd_tcp_syn6_bind = -1;\n\t\tping.tcp_syn6_bind_port = 0;\n\t}\n}\n"
  },
  {
    "path": "src/fast_ping/ping_tcp_syn.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_TCP_SYN_H_\n#define _FAST_PING_TCP_SYN_H_\n\n#include \"fast_ping.h\"\n\n#include <sys/epoll.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _fast_ping_process_tcp_syn(struct ping_host_struct *ping_host, struct timeval *now);\n\nint _fast_ping_get_addr_by_tcp_syn(const char *ip_str, int port, struct addrinfo **out_gai,\n\t\t\t\t\t\t\t\t   FAST_PING_TYPE *out_ping_type);\n\nint _fast_ping_sendping_tcp_syn(struct ping_host_struct *ping_host);\n\nint _fast_ping_tcp_syn_create_socket(struct ping_host_struct *ping_host);\n\nvoid _fast_ping_close_tcp_syn(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_TCP_SYN_H_\n"
  },
  {
    "path": "src/fast_ping/ping_udp.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"smartdns/util.h\"\n\n#include \"notify_event.h\"\n#include \"ping_host.h\"\n#include \"ping_icmp.h\"\n#include \"ping_udp.h\"\n\n#include <errno.h>\n#include <pthread.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/epoll.h>\n\nint _fast_ping_sendping_udp(struct ping_host_struct *ping_host)\n{\n\tstruct ping_dns_head dns_head;\n\tint len = 0;\n\tint flag = 0;\n\tint fd = -1;\n\n\tflag |= (0 << 15) & 0x8000;\n\tflag |= (2 << 11) & 0x7800;\n\tflag |= (0 << 10) & 0x0400;\n\tflag |= (0 << 9) & 0x0200;\n\tflag |= (0 << 8) & 0x0100;\n\tflag |= (0 << 7) & 0x0080;\n\tflag |= (0 << 0) & 0x000F;\n\n\tif (ping_host->type == FAST_PING_UDP) {\n\t\tfd = ping.fd_udp;\n\t} else if (ping_host->type == FAST_PING_UDP6) {\n\t\tfd = ping.fd_udp6;\n\t} else {\n\t\treturn -1;\n\t}\n\n\tping_host->seq++;\n\tmemset(&dns_head, 0, sizeof(dns_head));\n\tdns_head.id = htons(ping_host->sid);\n\tdns_head.flag = flag;\n\tdns_head.qdcount = htons(1);\n\tdns_head.qd_name = 0;\n\tdns_head.q_qtype = htons(2); /* DNS_T_NS */\n\tdns_head.q_qclass = htons(1);\n\n\tgettimeofday(&ping_host->last, NULL);\n\tlen = sendto(fd, &dns_head, sizeof(dns_head), 0, &ping_host->addr, ping_host->addr_len);\n\tif (len != sizeof(dns_head)) {\n\t\tint err = errno;\n\t\tif (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EPERM || errno == EACCES) {\n\t\t\tgoto errout;\n\t\t}\n\t\tchar ping_host_name[PING_MAX_HOSTLEN];\n\t\ttlog(TLOG_ERROR, \"sendto %s, id %d, %s\",\n\t\t\t get_host_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr),\n\t\t\t ping_host->sid, strerror(err));\n\t\tgoto errout;\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nstatic int _fast_ping_create_udp_sock(FAST_PING_TYPE type)\n{\n\tint fd = -1;\n\tstruct ping_host_struct *udp_host = NULL;\n\tstruct epoll_event event;\n\tconst int val = 255;\n\tconst int on = 1;\n\tconst int ip_tos = (IPTOS_LOWDELAY | IPTOS_RELIABILITY);\n\n\tswitch (type) {\n\tcase FAST_PING_UDP:\n\t\tfd = socket(AF_INET, SOCK_DGRAM, 0);\n\t\tif (fd < 0) {\n\t\t\ttlog(TLOG_ERROR, \"create udp socket failed, %s\\n\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\tudp_host = &ping.udp_host;\n\t\tudp_host->type = FAST_PING_UDP;\n\t\tbreak;\n\tcase FAST_PING_UDP6:\n\t\tfd = socket(AF_INET6, SOCK_DGRAM, 0);\n\t\tif (fd < 0) {\n\t\t\ttlog(TLOG_ERROR, \"create udp socket failed, %s\\n\", strerror(errno));\n\t\t\tgoto errout;\n\t\t}\n\n\t\tudp_host = &ping.udp6_host;\n\t\tudp_host->type = FAST_PING_UDP6;\n\t\tsetsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on));\n\t\tsetsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on));\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t}\n\n\tsetsockopt(fd, SOL_IP, IP_TTL, &val, sizeof(val));\n\tsetsockopt(fd, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on));\n\tsetsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos));\n\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN;\n\tevent.data.ptr = udp_host;\n\tif (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) {\n\t\tgoto errout;\n\t}\n\n\tudp_host->fd = fd;\n\treturn fd;\n\nerrout:\n\tclose(fd);\n\treturn -1;\n}\n\nstatic int _fast_ping_create_udp(FAST_PING_TYPE type)\n{\n\tint fd = -1;\n\tint *set_fd = NULL;\n\n\tpthread_mutex_lock(&ping.lock);\n\tswitch (type) {\n\tcase FAST_PING_UDP:\n\t\tset_fd = &ping.fd_udp;\n\t\tbreak;\n\tcase FAST_PING_UDP6:\n\t\tset_fd = &ping.fd_udp6;\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\tif (*set_fd > 0) {\n\t\tgoto out;\n\t}\n\n\tfd = _fast_ping_create_udp_sock(type);\n\tif (fd < 0) {\n\t\tgoto errout;\n\t}\n\n\t*set_fd = fd;\nout:\n\tpthread_mutex_unlock(&ping.lock);\n\treturn *set_fd;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\tpthread_mutex_unlock(&ping.lock);\n\treturn -1;\n}\n\nint _fast_ping_get_addr_by_dns(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type)\n{\n\tstruct addrinfo *gai = NULL;\n\tint socktype = 0;\n\tFAST_PING_TYPE ping_type = 0;\n\tint sockproto = 0;\n\tchar port_str[MAX_IP_LEN];\n\tint domain = -1;\n\tchar *service = NULL;\n\n\tif (port <= 0) {\n\t\tport = 53;\n\t}\n\n\tdomain = _fast_ping_getdomain(ip_str);\n\tif (domain < 0) {\n\t\tgoto errout;\n\t}\n\n\tswitch (domain) {\n\tcase AF_INET:\n\t\tping_type = FAST_PING_UDP;\n\t\tbreak;\n\tcase AF_INET6:\n\t\tping_type = FAST_PING_UDP6;\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\tsockproto = 0;\n\tsocktype = SOCK_DGRAM;\n\tsnprintf(port_str, MAX_IP_LEN, \"%d\", port);\n\tservice = port_str;\n\n\tif (_fast_ping_create_udp(ping_type) < 0) {\n\t\tgoto errout;\n\t}\n\n\tgai = _fast_ping_getaddr(ip_str, service, socktype, sockproto);\n\tif (gai == NULL) {\n\t\tgoto errout;\n\t}\n\n\t*out_gai = gai;\n\t*out_ping_type = ping_type;\n\n\treturn 0;\nerrout:\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\treturn -1;\n}\n\nint _fast_ping_process_udp(struct ping_host_struct *ping_host, struct timeval *now)\n{\n\tssize_t len = 0;\n\tu_char inpacket[ICMP_INPACKET_SIZE];\n\tstruct sockaddr_storage from;\n\tstruct ping_host_struct *recv_ping_host = NULL;\n\tstruct ping_dns_head *dns_head = NULL;\n\tsocklen_t from_len = sizeof(from);\n\tuint32_t addrkey = 0;\n\tstruct timeval tvresult = *now;\n\tstruct timeval *tvsend = NULL;\n\tunsigned int sid = 0;\n\tstruct msghdr msg;\n\tstruct iovec iov;\n\tchar ans_data[4096];\n\tstruct cmsghdr *cmsg = NULL;\n\tint ttl = 0;\n\n\tmemset(&msg, 0, sizeof(msg));\n\tiov.iov_base = (char *)inpacket;\n\tiov.iov_len = sizeof(inpacket);\n\tmsg.msg_name = &from;\n\tmsg.msg_namelen = sizeof(from);\n\tmsg.msg_iov = &iov;\n\tmsg.msg_iovlen = 1;\n\tmsg.msg_control = ans_data;\n\tmsg.msg_controllen = sizeof(ans_data);\n\n\tlen = recvmsg(ping_host->fd, &msg, MSG_DONTWAIT);\n\tif (len < 0) {\n\t\ttlog(TLOG_ERROR, \"recvfrom failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tfor (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {\n\t\tif (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TTL) {\n\t\t\tif (cmsg->cmsg_len >= sizeof(int)) {\n\t\t\t\tint *ttlPtr = (int *)CMSG_DATA(cmsg);\n\t\t\t\tttl = *ttlPtr;\n\t\t\t}\n\t\t} else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) {\n\t\t\tif (cmsg->cmsg_len >= sizeof(int)) {\n\t\t\t\tint *ttlPtr = (int *)CMSG_DATA(cmsg);\n\t\t\t\tttl = *ttlPtr;\n\t\t\t}\n\t\t}\n\t}\n\n\tfrom_len = msg.msg_namelen;\n\tdns_head = (struct ping_dns_head *)inpacket;\n\tif (len < (ssize_t)sizeof(*dns_head)) {\n\t\tgoto errout;\n\t}\n\n\tsid = ntohs(dns_head->id);\n\taddrkey = _fast_ping_hash_key(sid, (struct sockaddr *)&from);\n\tpthread_mutex_lock(&ping.map_lock);\n\thash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey)\n\t{\n\t\tif (_fast_ping_sockaddr_ip_cmp(&recv_ping_host->addr, recv_ping_host->addr_len, (struct sockaddr *)&from,\n\t\t\t\t\t\t\t\t\t   from_len) == 0 &&\n\t\t\trecv_ping_host->sid == sid) {\n\t\t\t_fast_ping_host_get(recv_ping_host);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tpthread_mutex_unlock(&ping.map_lock);\n\n\tif (recv_ping_host == NULL) {\n\t\treturn -1;\n\t}\n\n\trecv_ping_host->ttl = ttl;\n\ttvsend = &recv_ping_host->last;\n\ttv_sub(&tvresult, tvsend);\n\tif (recv_ping_host->ping_callback) {\n\t\t_fast_ping_send_notify_event(recv_ping_host, PING_RESULT_RESPONSE, recv_ping_host->seq, recv_ping_host->ttl,\n\t\t\t\t\t\t\t\t\t &tvresult);\n\t}\n\n\trecv_ping_host->send = 0;\n\n\tif (recv_ping_host->count == 1) {\n\t\t_fast_ping_host_remove(recv_ping_host);\n\t}\n\n\t_fast_ping_host_put(recv_ping_host);\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nvoid _fast_ping_close_udp(void)\n{\n\tif (ping.fd_udp > 0) {\n\t\tepoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping.fd_udp, NULL);\n\t\tclose(ping.fd_udp);\n\t\tping.fd_udp = -1;\n\t}\n\n\tif (ping.fd_udp6 > 0) {\n\t\tepoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping.fd_udp6, NULL);\n\t\tclose(ping.fd_udp6);\n\t\tping.fd_udp6 = -1;\n\t}\n}\n"
  },
  {
    "path": "src/fast_ping/ping_udp.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_UDP_H_\n#define _FAST_PING_UDP_H_\n\n#include \"fast_ping.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint _fast_ping_get_addr_by_dns(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type);\n\nint _fast_ping_sendping_udp(struct ping_host_struct *ping_host);\n\nint _fast_ping_process_udp(struct ping_host_struct *ping_host, struct timeval *now);\n\nvoid _fast_ping_close_udp(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_UDP_H_\n"
  },
  {
    "path": "src/fast_ping/wakeup_event.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"wakeup_event.h\"\n\n#include <errno.h>\n#include <string.h>\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n\nvoid _fast_ping_wakeup_thread(void)\n{\n\tuint64_t u = 1;\n\tint unused __attribute__((unused));\n\tunused = write(ping.event_fd, &u, sizeof(u));\n}\n\nint _fast_ping_init_wakeup_event(void)\n{\n\tint fdevent = -1;\n\tfdevent = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);\n\tif (fdevent < 0) {\n\t\ttlog(TLOG_ERROR, \"create eventfd failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tstruct epoll_event event;\n\tmemset(&event, 0, sizeof(event));\n\tevent.events = EPOLLIN | EPOLLERR;\n\tevent.data.ptr = NULL;\n\tif (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fdevent, &event) != 0) {\n\t\ttlog(TLOG_ERROR, \"set eventfd failed, %s\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tping.event_fd = fdevent;\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n"
  },
  {
    "path": "src/fast_ping/wakeup_event.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _FAST_PING_WAKEUP_EVENT_H_\n#define _FAST_PING_WAKEUP_EVENT_H_\n\n#include \"fast_ping.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nvoid _fast_ping_wakeup_thread(void);\n\nint _fast_ping_init_wakeup_event(void);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif // !_FAST_PING_WAKEUP_EVENT_H_\n"
  },
  {
    "path": "src/http_parse/hpack.c",
    "content": "#include \"hpack.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/* HPACK static table (RFC 7541 Appendix A) */\nstruct hpack_static_entry {\n\tconst char *name;\n\tconst char *value;\n};\n\n/* clang-format off */\nstatic const struct hpack_static_entry hpack_static_table[] = {\n\t{\":authority\", \"\"},\n\t{\":method\", \"GET\"},\n\t{\":method\", \"POST\"},\n\t{\":path\", \"/\"},\n\t{\":path\", \"/index.html\"},\n\t{\":scheme\", \"http\"},\n\t{\":scheme\", \"https\"},\n\t{\":status\", \"200\"},\n\t{\":status\", \"204\"},\n\t{\":status\", \"206\"},\n\t{\":status\", \"304\"},\n\t{\":status\", \"400\"},\n\t{\":status\", \"404\"},\n\t{\":status\", \"500\"},\n\t{\"accept-charset\", \"\"},\n\t{\"accept-encoding\", \"gzip, deflate\"},\n\t{\"accept-language\", \"\"},\n\t{\"accept-ranges\", \"\"},\n\t{\"accept\", \"\"},\n\t{\"access-control-allow-origin\", \"\"},\n\t{\"age\", \"\"},\n\t{\"allow\", \"\"},\n\t{\"authorization\", \"\"},\n\t{\"cache-control\", \"\"},\n\t{\"content-disposition\", \"\"},\n\t{\"content-encoding\", \"\"},\n\t{\"content-language\", \"\"},\n\t{\"content-length\", \"\"},\n\t{\"content-location\", \"\"},\n\t{\"content-range\", \"\"},\n\t{\"content-type\", \"\"},\n\t{\"cookie\", \"\"},\n\t{\"date\", \"\"},\n\t{\"etag\", \"\"},\n\t{\"expect\", \"\"},\n\t{\"expires\", \"\"},\n\t{\"from\", \"\"},\n\t{\"host\", \"\"},\n\t{\"if-match\", \"\"},\n\t{\"if-modified-since\", \"\"},\n\t{\"if-none-match\", \"\"},\n\t{\"if-range\", \"\"},\n\t{\"if-unmodified-since\", \"\"},\n\t{\"last-modified\", \"\"},\n\t{\"link\", \"\"},\n\t{\"location\", \"\"},\n\t{\"max-forwards\", \"\"},\n\t{\"proxy-authenticate\", \"\"},\n\t{\"proxy-authorization\", \"\"},\n\t{\"range\", \"\"},\n\t{\"referer\", \"\"},\n\t{\"refresh\", \"\"},\n\t{\"retry-after\", \"\"},\n\t{\"server\", \"\"},\n\t{\"set-cookie\", \"\"},\n\t{\"strict-transport-security\", \"\"},\n\t{\"transfer-encoding\", \"\"},\n\t{\"user-agent\", \"\"},\n\t{\"vary\", \"\"},\n\t{\"via\", \"\"},\n\t{\"www-authenticate\", \"\"}\n};\n/* clang-format on */\n\n#define HPACK_STATIC_TABLE_SIZE (sizeof(hpack_static_table) / sizeof(hpack_static_table[0]))\n\n/* HPACK integer encoding/decoding */\n\nstatic int hpack_encode_integer(uint64_t value, int prefix_bits, uint8_t *buf, int buf_size)\n{\n\tint max_prefix = (1 << prefix_bits) - 1;\n\tint offset = 0;\n\n\tif (value < (uint64_t)max_prefix) {\n\t\tif (buf_size < 1) {\n\t\t\treturn -1;\n\t\t}\n\t\tbuf[0] |= (uint8_t)value;\n\t\treturn 1;\n\t}\n\n\tif (buf_size < 1) {\n\t\treturn -1;\n\t}\n\tbuf[offset++] |= (uint8_t)max_prefix;\n\tvalue -= max_prefix;\n\n\twhile (value >= 128) {\n\t\tif (offset >= buf_size) {\n\t\t\treturn -1;\n\t\t}\n\t\tbuf[offset++] = (uint8_t)((value & 0x7F) | 0x80);\n\t\tvalue >>= 7;\n\t}\n\n\tif (offset >= buf_size) {\n\t\treturn -1;\n\t}\n\tbuf[offset++] = (uint8_t)value;\n\treturn offset;\n}\n\nstatic int hpack_decode_integer(const uint8_t *data, int data_len, int prefix_bits, uint64_t *value)\n{\n\tint max_prefix = (1 << prefix_bits) - 1;\n\tint offset = 0;\n\tuint64_t result;\n\tint shift = 0;\n\n\tif (data_len < 1) {\n\t\treturn -1;\n\t}\n\n\tresult = data[offset++] & max_prefix;\n\tif (result < (uint64_t)max_prefix) {\n\t\t*value = result;\n\t\treturn offset;\n\t}\n\n\twhile (offset < data_len) {\n\t\tuint8_t byte = data[offset++];\n\t\tresult += (uint64_t)(byte & 0x7F) << shift;\n\t\tshift += 7;\n\t\tif ((byte & 0x80) == 0) {\n\t\t\t*value = result;\n\t\t\treturn offset;\n\t\t}\n\t\tif (shift > 63) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\n/* HPACK string encoding/decoding */\n\nstatic int hpack_encode_string(const char *str, uint8_t *buf, int buf_size)\n{\n\tint len = strlen(str);\n\tint offset = 0;\n\tint ret;\n\n\tif (buf_size < 1) {\n\t\treturn -1;\n\t}\n\n\tbuf[offset] = 0; /* No Huffman encoding */\n\tret = hpack_encode_integer(len, 7, buf + offset, buf_size - offset);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\toffset += ret;\n\n\tif (offset + len > buf_size) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(buf + offset, str, len);\n\toffset += len;\n\n\treturn offset;\n}\n\n/* HPACK Huffman decoding table based on RFC 7541 Appendix B */\n/* Each entry contains: symbol, code length in bits */\nstruct huffman_decode_entry {\n\tuint32_t bits;  /* Huffman code bits */\n\tuint8_t nbits;  /* Number of bits in code */\n\tuint8_t symbol; /* Decoded symbol */\n};\n\n/* Complete Huffman decoding table for HPACK (RFC 7541 Appendix B) */\n/* Sorted by code for binary search */\nstatic const struct huffman_decode_entry huffman_table[] = {\n\t/* 5-bit codes */\n\t{0x00, 5, '0'},\n\t{0x01, 5, '1'},\n\t{0x02, 5, '2'},\n\t{0x03, 5, 'a'},\n\t{0x04, 5, 'c'},\n\t{0x05, 5, 'e'},\n\t{0x06, 5, 'i'},\n\t{0x07, 5, 'o'},\n\t{0x08, 5, 's'},\n\t{0x09, 5, 't'},\n\n\t/* 6-bit codes */\n\t{0x14, 6, ' '},\n\t{0x15, 6, '%'},\n\t{0x16, 6, '-'},\n\t{0x17, 6, '.'},\n\t{0x18, 6, '/'},\n\t{0x19, 6, '3'},\n\t{0x1a, 6, '4'},\n\t{0x1b, 6, '5'},\n\t{0x1c, 6, '6'},\n\t{0x1d, 6, '7'},\n\t{0x1e, 6, '8'},\n\t{0x1f, 6, '9'},\n\t{0x20, 6, '='},\n\t{0x21, 6, 'A'},\n\t{0x22, 6, '_'},\n\t{0x23, 6, 'b'},\n\t{0x24, 6, 'd'},\n\t{0x25, 6, 'f'},\n\t{0x26, 6, 'g'},\n\t{0x27, 6, 'h'},\n\t{0x28, 6, 'l'},\n\t{0x29, 6, 'm'},\n\t{0x2a, 6, 'n'},\n\t{0x2b, 6, 'p'},\n\t{0x2c, 6, 'r'},\n\t{0x2d, 6, 'u'},\n\n\t/* 7-bit codes */\n\t{0x5c, 7, ':'},\n\t{0x5d, 7, 'B'},\n\t{0x5e, 7, 'C'},\n\t{0x5f, 7, 'D'},\n\t{0x60, 7, 'E'},\n\t{0x61, 7, 'F'},\n\t{0x62, 7, 'G'},\n\t{0x63, 7, 'H'},\n\t{0x64, 7, 'I'},\n\t{0x65, 7, 'J'},\n\t{0x66, 7, 'K'},\n\t{0x67, 7, 'L'},\n\t{0x68, 7, 'M'},\n\t{0x69, 7, 'N'},\n\t{0x6a, 7, 'O'},\n\t{0x6b, 7, 'P'},\n\t{0x6c, 7, 'Q'},\n\t{0x6d, 7, 'R'},\n\t{0x6e, 7, 'S'},\n\t{0x6f, 7, 'T'},\n\t{0x70, 7, 'U'},\n\t{0x71, 7, 'V'},\n\t{0x72, 7, 'W'},\n\t{0x73, 7, 'Y'},\n\t{0x74, 7, 'j'},\n\t{0x75, 7, 'k'},\n\t{0x76, 7, 'q'},\n\t{0x77, 7, 'v'},\n\t{0x78, 7, 'w'},\n\t{0x79, 7, 'x'},\n\t{0x7a, 7, 'y'},\n\t{0x7b, 7, 'z'},\n\n\t/* 8-bit codes */\n\t{0xf8, 8, '&'},\n\t{0xf9, 8, '*'},\n\t{0xfa, 8, ','},\n\t{0xfb, 8, ';'},\n\t{0xfc, 8, 'X'},\n\t{0xfd, 8, 'Z'},\n\n\t/* 10-bit codes */\n\t{0x3f8, 10, '!'},\n\t{0x3f9, 10, '\"'},\n\t{0x3fa, 10, '('},\n\t{0x3fb, 10, ')'},\n\t{0x3fc, 10, '?'},\n\n\t/* 11-bit codes */\n\t{0x7fa, 11, '#'},\n\t{0x7fb, 11, '>'},\n\n\t/* 12-bit codes */\n\t{0xffa, 12, '$'},\n\t{0xffb, 12, '@'},\n\t{0xffc, 12, '['},\n\t{0xffd, 12, ']'},\n\t{0xffe, 12, '~'},\n\n\t/* 13-bit codes */\n\t{0x1ff8, 13, '+'},\n\t{0x1ff9, 13, '<'},\n\t{0x1ffa, 13, '\\\\'},\n\n\t/* 14-bit codes */\n\t{0x3ffc, 14, '\\''},\n\t{0x3ffd, 14, '|'},\n\n\t/* 15-bit codes */\n\t{0x7ffc, 15, '`'},\n\t{0x7ffd, 15, '{'},\n\n\t/* 19-bit codes */\n\t{0x7fff0, 19, '}'},\n\n\t/* 20-bit codes and above - less common characters */\n\t{0xffff8, 20, 0x00},\n\t{0xffff9, 20, 0x01},\n\t{0xffffa, 20, 0x02},\n\t{0xffffb, 20, 0x03},\n\t{0xffffc, 20, 0x04},\n\t{0xffffd, 20, 0x05},\n\t{0xffffe, 20, 0x06},\n\t{0xfffff, 20, 0x07},\n\t{0x1ffff8, 21, 0x08},\n\t{0x1ffff9, 21, 0x09},\n\t{0x1ffffa, 21, 0x0a},\n\t{0x1ffffb, 21, 0x0b},\n\t{0x1ffffc, 21, 0x0c},\n\t{0x1ffffd, 21, 0x0d},\n\t{0x1ffffe, 21, 0x0e},\n\t{0x1fffff, 21, 0x0f},\n\t{0x3ffff8, 22, 0x10},\n\t{0x3ffff9, 22, 0x11},\n\t{0x3ffffa, 22, 0x12},\n\t{0x3ffffb, 22, 0x13},\n\t{0x3ffffc, 22, 0x14},\n\t{0x3ffffd, 22, 0x15},\n\t{0x3ffffe, 22, 0x16},\n\t{0x3fffff, 22, 0x17},\n\t{0x7ffff8, 23, 0x18},\n\t{0x7ffff9, 23, 0x19},\n\t{0x7ffffa, 23, 0x1a},\n\t{0x7ffffb, 23, 0x1b},\n\t{0x7ffffc, 23, 0x1c},\n\t{0x7ffffd, 23, 0x1d},\n\t{0x7ffffe, 23, 0x1e},\n\t{0x7fffff, 23, 0x1f},\n\t{0xfffff8, 24, 0x7f},\n\t{0xfffff9, 24, 0x20},\n\t{0xfffffa, 24, 0x21},\n\t{0xfffffb, 24, 0x22},\n\t{0xfffffc, 24, 0x23},\n\t{0xfffffd, 24, 0x24},\n\t{0xfffffe, 24, 0x25},\n\t{0xffffff, 24, 0x26},\n\t{0x1fffff8, 25, 0x27},\n\t{0x1fffff9, 25, 0x28},\n\t{0x1fffffa, 25, 0x29},\n\t{0x1fffffb, 25, 0x2a},\n\t{0x1fffffc, 25, 0x2b},\n\t{0x1fffffd, 25, 0x2c},\n\t{0x1fffffe, 25, 0x2d},\n\t{0x1ffffff, 25, 0x2e},\n\t{0x3fffff8, 26, 0x2f},\n\t{0x3fffff9, 26, 0x30},\n\t{0x3fffffa, 26, 0x31},\n\t{0x3fffffb, 26, 0x32},\n\t{0x3fffffc, 26, 0x33},\n\t{0x3fffffd, 26, 0x34},\n\t{0x3fffffe, 26, 0x35},\n\t{0x3ffffff, 26, 0x36},\n\t{0x7fffff8, 27, 0x37},\n\t{0x7fffff9, 27, 0x38},\n\t{0x7fffffa, 27, 0x39},\n\t{0x7fffffb, 27, 0x3a},\n\t{0x7fffffc, 27, 0x3b},\n\t{0x7fffffd, 27, 0x3c},\n\t{0x7fffffe, 27, 0x3d},\n\t{0x7ffffff, 27, 0x3e},\n\t{0xffffff8, 28, 0x3f},\n\t{0xffffff9, 28, 0x40},\n\t{0xffffffa, 28, 0x41},\n\t{0xffffffb, 28, 0x42},\n\t{0xffffffc, 28, 0x43},\n\t{0xffffffd, 28, 0x44},\n\t{0xffffffe, 28, 0x45},\n\t{0xfffffff, 28, 0x46},\n};\n\n#define HUFFMAN_TABLE_SIZE (sizeof(huffman_table) / sizeof(huffman_table[0]))\n\n/* Huffman decoder using bit-by-bit decoding */\nstatic int hpack_decode_huffman(const uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len)\n{\n\tsize_t dst_pos = 0;\n\tuint64_t bits = 0;\n\tint nbits = 0;\n\tsize_t i;\n\n\tfor (i = 0; i < src_len; i++) {\n\t\tif (nbits > 56) {\n\t\t\t/* Bit buffer would overflow on next byte */\n\t\t\treturn -1;\n\t\t}\n\t\tbits = (bits << 8) | src[i];\n\t\tnbits += 8;\n\n\t\t/* Try to decode symbols */\n\t\twhile (nbits >= 5) { /* Minimum code length is 5 bits */\n\t\t\tint found = 0;\n\t\t\tint len;\n\n\t\t\t/* Try different code lengths from longest to shortest for current bits */\n\t\t\tfor (len = (nbits > 30 ? 30 : nbits); len >= 5; len--) {\n\t\t\t\tuint32_t code = (uint32_t)((bits >> (nbits - len)) & (((uint64_t)1 << len) - 1));\n\t\t\t\tsize_t j;\n\n\t\t\t\t/* Search for matching code in table */\n\t\t\t\tfor (j = 0; j < HUFFMAN_TABLE_SIZE; j++) {\n\t\t\t\t\tif (huffman_table[j].nbits == (uint8_t)len && huffman_table[j].bits == code) {\n\t\t\t\t\t\tif (dst_pos >= dst_len) {\n\t\t\t\t\t\t\treturn -1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdst[dst_pos++] = huffman_table[j].symbol;\n\t\t\t\t\t\tnbits -= len;\n\t\t\t\t\t\tbits &= (((uint64_t)1 << nbits) - 1); /* Clear decoded bits */\n\t\t\t\t\t\tfound = 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (found) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!found) {\n\t\t\t\t/* No match found - might need more bits or it's padding */\n\t\t\t\tif (i == src_len - 1) {\n\t\t\t\t\t/* Last byte - remaining bits should be padding (all 1s) */\n\t\t\t\t\tuint32_t padding_mask = (1U << nbits) - 1;\n\t\t\t\t\tuint32_t remaining = (uint32_t)(bits & padding_mask);\n\t\t\t\t\tif (remaining == padding_mask) {\n\t\t\t\t\t\t/* Valid padding */\n\t\t\t\t\t\treturn dst_pos;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak; /* Need more bits */\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dst_pos;\n}\n\nstatic int hpack_decode_string(const uint8_t *data, int data_len, char **str)\n{\n\tuint64_t len;\n\tint huffman;\n\tint offset = 0;\n\tint ret;\n\n\tif (data_len < 1) {\n\t\treturn -1;\n\t}\n\n\thuffman = (data[0] & 0x80) != 0;\n\tret = hpack_decode_integer(data, data_len, 7, &len);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\toffset += ret;\n\n\tif (offset + (int)len > data_len) {\n\t\treturn -1;\n\t}\n\n\tif (huffman) {\n\t\t/* Huffman decoding */\n\n\t\t/* Allocate buffer for decoded string (worst case: same size as encoded) */\n\t\tuint8_t *decoded = malloc(len * 2 + 1); /* Extra space for safety */\n\t\tif (!decoded) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tint decoded_len = hpack_decode_huffman(data + offset, len, decoded, len * 2);\n\t\tif (decoded_len < 0) {\n\t\t\tfree(decoded);\n\t\t\treturn -1;\n\t\t}\n\n\t\t*str = malloc(decoded_len + 1);\n\t\tif (!*str) {\n\t\t\tfree(decoded);\n\t\t\treturn -1;\n\t\t}\n\n\t\tmemcpy(*str, decoded, decoded_len);\n\t\t(*str)[decoded_len] = '\\0';\n\t\tfree(decoded);\n\t} else {\n\t\t/* Literal string */\n\t\t*str = malloc(len + 1);\n\t\tif (*str == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tmemcpy(*str, data + offset, len);\n\t\t(*str)[len] = '\\0';\n\t}\n\n\toffset += len;\n\n\treturn offset;\n}\n\n/* HPACK dynamic table management */\n\nvoid hpack_init_context(struct hpack_context *hpack)\n{\n\thpack->dynamic_table = NULL;\n\thpack->dynamic_table_size = 0;\n\thpack->max_dynamic_table_size = 65536; /* Default size */\n\thpack->entry_count = 0;\n}\n\nvoid hpack_free_context(struct hpack_context *hpack)\n{\n\tstruct hpack_dynamic_entry *entry = hpack->dynamic_table;\n\twhile (entry) {\n\t\tstruct hpack_dynamic_entry *next = entry->next;\n\t\tfree(entry->name);\n\t\tfree(entry->value);\n\t\tfree(entry);\n\t\tentry = next;\n\t}\n\thpack->dynamic_table = NULL;\n\thpack->dynamic_table_size = 0;\n\thpack->entry_count = 0;\n}\n\nstatic int hpack_add_dynamic_entry(struct hpack_context *hpack, const char *name, const char *value)\n{\n\tstruct hpack_dynamic_entry *entry;\n\tsize_t entry_size = strlen(name) + strlen(value) + 32;\n\n\t/* Evict entries if necessary */\n\twhile (hpack->dynamic_table_size + entry_size > hpack->max_dynamic_table_size && hpack->dynamic_table) {\n\t\tstruct hpack_dynamic_entry *last = hpack->dynamic_table;\n\t\tstruct hpack_dynamic_entry *prev = NULL;\n\n\t\twhile (last->next) {\n\t\t\tprev = last;\n\t\t\tlast = last->next;\n\t\t}\n\n\t\tif (prev) {\n\t\t\tprev->next = NULL;\n\t\t} else {\n\t\t\thpack->dynamic_table = NULL;\n\t\t}\n\n\t\thpack->dynamic_table_size -= last->size;\n\t\thpack->entry_count--;\n\t\tfree(last->name);\n\t\tfree(last->value);\n\t\tfree(last);\n\t}\n\n\tentry = malloc(sizeof(*entry));\n\tif (!entry) {\n\t\treturn -1;\n\t}\n\n\tentry->name = strdup(name);\n\tentry->value = strdup(value);\n\tif (!entry->name || !entry->value) {\n\t\tfree(entry->name);\n\t\tfree(entry->value);\n\t\tfree(entry);\n\t\treturn -1;\n\t}\n\n\tentry->size = entry_size;\n\tentry->next = hpack->dynamic_table;\n\thpack->dynamic_table = entry;\n\thpack->dynamic_table_size += entry_size;\n\thpack->entry_count++;\n\n\treturn 0;\n}\n\nstatic int hpack_get_entry(struct hpack_context *hpack, uint64_t index, const char **name, const char **value)\n{\n\tif (index == 0) {\n\t\treturn -1;\n\t}\n\n\tif (index <= HPACK_STATIC_TABLE_SIZE) {\n\t\t*name = hpack_static_table[index - 1].name;\n\t\t*value = hpack_static_table[index - 1].value;\n\t\treturn 0;\n\t}\n\n\t/* Dynamic table */\n\tuint64_t dynamic_index = index - HPACK_STATIC_TABLE_SIZE - 1;\n\tstruct hpack_dynamic_entry *entry = hpack->dynamic_table;\n\tuint64_t i = 0;\n\n\twhile (entry && i < dynamic_index) {\n\t\tentry = entry->next;\n\t\ti++;\n\t}\n\n\tif (!entry) {\n\t\treturn -1;\n\t}\n\n\t*name = entry->name;\n\t*value = entry->value;\n\treturn 0;\n}\n\nstatic int hpack_find_index(struct hpack_context *hpack, const char *name, const char *value, int *index,\n\t\t\t\t\t\t\tint *name_only_index)\n{\n\tint i;\n\n\t*index = 0;\n\t*name_only_index = 0;\n\n\t/* Search static table */\n\tfor (i = 0; i < (int)HPACK_STATIC_TABLE_SIZE; i++) {\n\t\tif (strcmp(hpack_static_table[i].name, name) == 0) {\n\t\t\tif (*name_only_index == 0) {\n\t\t\t\t*name_only_index = i + 1;\n\t\t\t}\n\t\t\tif (strcmp(hpack_static_table[i].value, value) == 0) {\n\t\t\t\t*index = i + 1;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Search dynamic table */\n\tstruct hpack_dynamic_entry *entry = hpack->dynamic_table;\n\ti = 0;\n\twhile (entry) {\n\t\tif (strcmp(entry->name, name) == 0) {\n\t\t\tif (*name_only_index == 0) {\n\t\t\t\t*name_only_index = HPACK_STATIC_TABLE_SIZE + 1 + i;\n\t\t\t}\n\t\t\tif (strcmp(entry->value, value) == 0) {\n\t\t\t\t*index = HPACK_STATIC_TABLE_SIZE + 1 + i;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\tentry = entry->next;\n\t\ti++;\n\t}\n\n\treturn 0;\n}\n\n/* HPACK encoding */\n\nint hpack_encode_header(struct hpack_context *hpack, const char *name, const char *value, uint8_t *buf, int buf_size)\n{\n\tint index, name_only_index;\n\tint offset = 0;\n\tint ret;\n\n\thpack_find_index(hpack, name, value, &index, &name_only_index);\n\n\tif (index > 0) {\n\t\t/* Indexed header field */\n\t\tif (buf_size < 1) {\n\t\t\treturn -1;\n\t\t}\n\t\tbuf[offset] = 0x80;\n\t\tret = hpack_encode_integer(index, 7, buf + offset, buf_size - offset);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn ret;\n\t}\n\n\tif (name_only_index > 0) {\n\t\t/* Literal with incremental indexing - indexed name */\n\t\tif (buf_size < 1) {\n\t\t\treturn -1;\n\t\t}\n\t\tbuf[offset] = 0x40;\n\t\tret = hpack_encode_integer(name_only_index, 6, buf + offset, buf_size - offset);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\toffset += ret;\n\n\t\tret = hpack_encode_string(value, buf + offset, buf_size - offset);\n\t\tif (ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\toffset += ret;\n\n\t\thpack_add_dynamic_entry(hpack, name, value);\n\t\treturn offset;\n\t}\n\n\t/* Literal with incremental indexing - new name */\n\tif (buf_size < 1) {\n\t\treturn -1;\n\t}\n\tbuf[offset++] = 0x40;\n\n\tret = hpack_encode_string(name, buf + offset, buf_size - offset);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\toffset += ret;\n\n\tret = hpack_encode_string(value, buf + offset, buf_size - offset);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\toffset += ret;\n\n\thpack_add_dynamic_entry(hpack, name, value);\n\treturn offset;\n}\n\n/* HPACK decoding */\n\nvoid hpack_resize_dynamic_table(struct hpack_context *hpack, size_t new_size)\n{\n\thpack->max_dynamic_table_size = new_size;\n\n\t/* Evict entries if necessary */\n\twhile (hpack->dynamic_table_size > hpack->max_dynamic_table_size && hpack->dynamic_table) {\n\t\tstruct hpack_dynamic_entry *last = hpack->dynamic_table;\n\t\tstruct hpack_dynamic_entry *prev = NULL;\n\n\t\twhile (last->next) {\n\t\t\tprev = last;\n\t\t\tlast = last->next;\n\t\t}\n\n\t\tif (prev) {\n\t\t\tprev->next = NULL;\n\t\t} else {\n\t\t\thpack->dynamic_table = NULL;\n\t\t}\n\n\t\thpack->dynamic_table_size -= last->size;\n\t\thpack->entry_count--;\n\t\tfree(last->name);\n\t\tfree(last->value);\n\t\tfree(last);\n\t}\n}\n\nint hpack_decode_headers(struct hpack_context *hpack, const uint8_t *data, int data_len, hpack_on_header_fn on_header,\n\t\t\t\t\t\t void *ctx)\n{\n\tint offset = 0;\n\n\twhile (offset < data_len) {\n\t\tconst char *name = NULL;\n\t\tconst char *value = NULL;\n\t\tchar *allocated_name = NULL;\n\t\tchar *allocated_value = NULL;\n\n\t\tif ((data[offset] & 0x80) != 0) {\n\t\t\t/* Indexed header field */\n\t\t\tuint64_t index;\n\t\t\tconst char *static_name, *static_value;\n\t\t\tint ret = hpack_decode_integer(data + offset, data_len - offset, 7, &index);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (hpack_get_entry(hpack, index, &static_name, &static_value) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\toffset += ret;\n\n\t\t\tname = static_name;\n\t\t\tvalue = static_value;\n\t\t} else if ((data[offset] & 0x40) != 0) {\n\t\t\t/* Literal with incremental indexing */\n\t\t\tuint64_t index;\n\t\t\tint ret = hpack_decode_integer(data + offset, data_len - offset, 6, &index);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\toffset += ret;\n\n\t\t\tif (index > 0) {\n\t\t\t\tconst char *static_name, *static_value;\n\t\t\t\tif (hpack_get_entry(hpack, index, &static_name, &static_value) < 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tname = static_name;\n\t\t\t} else {\n\t\t\t\tret = hpack_decode_string(data + offset, data_len - offset, &allocated_name);\n\t\t\t\tif (ret < 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\toffset += ret;\n\t\t\t\tname = allocated_name;\n\t\t\t}\n\n\t\t\tret = hpack_decode_string(data + offset, data_len - offset, &allocated_value);\n\t\t\tif (ret < 0) {\n\t\t\t\tfree(allocated_name);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\toffset += ret;\n\t\t\tvalue = allocated_value;\n\n\t\t\tif (name && value) {\n\t\t\t\thpack_add_dynamic_entry(hpack, name, value);\n\t\t\t}\n\t\t} else if ((data[offset] & 0x20) != 0) {\n\t\t\t/* Dynamic Table Size Update */\n\t\t\tuint64_t new_size;\n\t\t\tint ret = hpack_decode_integer(data + offset, data_len - offset, 5, &new_size);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\toffset += ret;\n\t\t\thpack_resize_dynamic_table(hpack, new_size);\n\t\t\tcontinue; /* Continue to next field */\n\t\t} else {\n\t\t\t/* Literal without indexing or never indexed */\n\t\t\tuint64_t index;\n\t\t\tint prefix = 4; /* Both types use 4-bit prefix */\n\t\t\tint ret = hpack_decode_integer(data + offset, data_len - offset, prefix, &index);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\toffset += ret;\n\n\t\t\tif (index > 0) {\n\t\t\t\tconst char *static_name, *static_value;\n\t\t\t\tif (hpack_get_entry(hpack, index, &static_name, &static_value) < 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tname = static_name;\n\t\t\t} else {\n\t\t\t\tret = hpack_decode_string(data + offset, data_len - offset, &allocated_name);\n\t\t\t\tif (ret < 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\toffset += ret;\n\t\t\t\tname = allocated_name;\n\t\t\t}\n\n\t\t\tret = hpack_decode_string(data + offset, data_len - offset, &allocated_value);\n\t\t\tif (ret < 0) {\n\t\t\t\tfree(allocated_name);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\toffset += ret;\n\t\t\tvalue = allocated_value;\n\t\t}\n\n\t\t/* Add header to stream */\n\t\tif (on_header(ctx, name, value) < 0) {\n\t\t\tfree(allocated_name);\n\t\t\tfree(allocated_value);\n\t\t\treturn -1;\n\t\t}\n\n\t\t/* Free allocated strings if they were copied */\n\t\tif (allocated_name) {\n\t\t\tfree(allocated_name);\n\t\t}\n\t\tif (allocated_value) {\n\t\t\tfree(allocated_value);\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "src/http_parse/hpack.h",
    "content": "#ifndef _HPACK_H_\n#define _HPACK_H_\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* HPACK dynamic table entry */\nstruct hpack_dynamic_entry {\n\tchar *name;\n\tchar *value;\n\tsize_t size; /* name_len + value_len + 32 */\n\tstruct hpack_dynamic_entry *next;\n};\n\n/* HPACK context */\nstruct hpack_context {\n\tstruct hpack_dynamic_entry *dynamic_table;\n\tsize_t dynamic_table_size;\n\tsize_t max_dynamic_table_size;\n\tint entry_count;\n};\n\n/* Callback function for decoded headers */\ntypedef int (*hpack_on_header_fn)(void *ctx, const char *name, const char *value);\n\n/**\n * Initialize HPACK context\n * @param hpack HPACK context\n */\nvoid hpack_init_context(struct hpack_context *hpack);\n\n/**\n * Free HPACK context\n * @param hpack HPACK context\n */\nvoid hpack_free_context(struct hpack_context *hpack);\n\n/**\n * Resize dynamic table\n * @param hpack HPACK context\n * @param new_size New size\n */\nvoid hpack_resize_dynamic_table(struct hpack_context *hpack, size_t new_size);\n\n/**\n * Encode a header\n * @param hpack HPACK context\n * @param name Header name\n * @param value Header value\n * @param buf Output buffer\n * @param buf_size Output buffer size\n * @return Number of bytes written, or -1 on error\n */\nint hpack_encode_header(struct hpack_context *hpack, const char *name, const char *value, uint8_t *buf, int buf_size);\n\n/**\n * Decode headers\n * @param hpack HPACK context\n * @param data Input data\n * @param data_len Input data length\n * @param on_header Callback function for each decoded header\n * @param ctx User context passed to callback\n * @return 0 on success, -1 on error\n */\nint hpack_decode_headers(struct hpack_context *hpack, const uint8_t *data, int data_len, hpack_on_header_fn on_header,\n\t\t\t\t\t\t void *ctx);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* _HPACK_H_ */\n"
  },
  {
    "path": "src/http_parse/http1_parse.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"http1_parse.h\"\n#include \"http_parse.h\"\n#include \"smartdns/util.h\"\n\n#include <stdio.h>\n#include <unistd.h>\n\nstatic int _http_head_parse_response(struct http_head *http_head, char *key, char *value)\n{\n\tchar *field_start = NULL;\n\tchar *tmp_ptr = NULL;\n\tchar *ret_msg = NULL;\n\tchar *ret_code = NULL;\n\n\tif (strstr(key, \"HTTP/\") == NULL) {\n\t\treturn -1;\n\t}\n\n\tfor (tmp_ptr = value; *tmp_ptr != 0; tmp_ptr++) {\n\t\tif (field_start == NULL) {\n\t\t\tfield_start = tmp_ptr;\n\t\t}\n\n\t\tif (*tmp_ptr != ' ') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t*tmp_ptr = '\\0';\n\t\tret_code = field_start;\n\t\tret_msg = tmp_ptr + 1;\n\t\tfield_start = NULL;\n\t\tbreak;\n\t}\n\n\tif (ret_code == NULL || ret_msg == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (is_numeric(ret_code) != 0) {\n\t\treturn -1;\n\t}\n\n\thttp_head->code = atol(ret_code);\n\thttp_head->code_msg = ret_msg;\n\thttp_head->version = key;\n\thttp_head->head_type = HTTP_HEAD_RESPONSE;\n\n\treturn 0;\n}\n\nstatic int _http_head_parse_request(struct http_head *http_head, char *key, char *value)\n{\n\tint method = HTTP_METHOD_INVALID;\n\tchar *url = NULL;\n\tchar *version = NULL;\n\tchar *tmp_ptr = value;\n\tchar *field_start = NULL;\n\n\tmethod = _http_method_parse(key);\n\tif (method == HTTP_METHOD_INVALID) {\n\t\treturn _http_head_parse_response(http_head, key, value);\n\t}\n\n\tfor (tmp_ptr = value; *tmp_ptr != 0; tmp_ptr++) {\n\t\tif (field_start == NULL) {\n\t\t\tfield_start = tmp_ptr;\n\t\t}\n\t\tif (*tmp_ptr == ' ') {\n\t\t\t*tmp_ptr = '\\0';\n\t\t\tif (url == NULL) {\n\t\t\t\turl = field_start;\n\t\t\t}\n\n\t\t\tfield_start = NULL;\n\t\t}\n\t}\n\n\tif (field_start && version == NULL) {\n\t\tversion = field_start;\n\t\ttmp_ptr = field_start;\n\t}\n\n\tif (_http_head_parse_params(http_head, url, tmp_ptr - url) != 0) {\n\t\treturn -2;\n\t}\n\n\thttp_head->method = method;\n\thttp_head->url = url;\n\thttp_head->version = version;\n\thttp_head->head_type = HTTP_HEAD_REQUEST;\n\n\treturn 0;\n}\n\nstatic int _http_head_parse(struct http_head *http_head)\n{\n\tint i = 0;\n\tchar *key = NULL;\n\tchar *value = NULL;\n\tchar *data;\n\tint has_first_line = 0;\n\n\tint inkey = 1;\n\tint invalue = 0;\n\n\tdata = (char *)http_head->buff;\n\tfor (i = 0; i < http_head->head_len; i++, data++) {\n\t\tif (inkey) {\n\t\t\tif (key == NULL && *data != ' ' && *data != '\\r' && *data != '\\n') {\n\t\t\t\tkey = data;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (*data == ':' || *data == ' ') {\n\t\t\t\t*data = '\\0';\n\t\t\t\tinkey = 0;\n\t\t\t\tinvalue = 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (invalue) {\n\t\t\tif (value == NULL && *data != ' ') {\n\t\t\t\tvalue = data;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (*data == '\\r' || *data == '\\n') {\n\t\t\t\t*data = '\\0';\n\t\t\t\tinkey = 1;\n\t\t\t\tinvalue = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (key && value && invalue == 0) {\n\t\t\tif (has_first_line == 0) {\n\t\t\t\tif (_http_head_parse_request(http_head, key, value) != 0) {\n\t\t\t\t\treturn -2;\n\t\t\t\t}\n\n\t\t\t\thas_first_line = 1;\n\t\t\t} else {\n\t\t\t\tif (http_head_add_fields(http_head, key, value) != 0) {\n\t\t\t\t\treturn -2;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tkey = NULL;\n\t\t\tvalue = NULL;\n\t\t\tinkey = 1;\n\t\t\tinvalue = 0;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _http1_get_chunk_len(const uint8_t *data, int data_len, int32_t *chunk_len)\n{\n\tint offset = 0;\n\tint32_t chunk_value = 0;\n\tint is_num_start = 0;\n\n\tfor (offset = 0; offset < data_len; offset++) {\n\t\tif (data[offset] == ' ') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (data[offset] == '\\r') {\n\t\t\tif (offset + 1 < data_len && data[offset + 1] == '\\n') {\n\t\t\t\toffset += 2;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (is_num_start == 0) {\n\t\t\t\treturn -2;\n\t\t\t}\n\n\t\t\treturn -2;\n\t\t}\n\t\tint value = decode_hex(data[offset]);\n\t\tif (value < 0) {\n\t\t\treturn -2;\n\t\t}\n\n\t\tif (is_num_start == 0) {\n\t\t\tis_num_start = 1;\n\t\t}\n\n\t\tchunk_value = (chunk_value << 4) + value;\n\t}\n\n\tif (offset >= data_len) {\n\t\treturn -1;\n\t}\n\n\t*chunk_len = chunk_value;\n\treturn offset;\n}\n\nint http_head_parse_http1_1(struct http_head *http_head, const uint8_t *data, int in_data_len)\n{\n\tint i = 0;\n\tuint8_t *buff_end = NULL;\n\tint left_size = 0;\n\tint process_data_len = 0;\n\tint data_len = in_data_len;\n\tint is_chunked = 0;\n\n\tleft_size = http_head->buff_size - http_head->buff_len;\n\n\tif (left_size < data_len) {\n\t\treturn -3;\n\t}\n\n\tbuff_end = http_head->buff + http_head->buff_len;\n\tif (http_head->head_ok == 0) {\n\t\tfor (i = 0; i < in_data_len; i++, data++) {\n\t\t\t*(buff_end + i) = *data;\n\t\t\tif (isprint(*data) == 0 && isspace(*data) == 0) {\n\t\t\t\treturn -2;\n\t\t\t}\n\n\t\t\tif (*data == '\\n') {\n\t\t\t\tif (http_head->buff_len + i < 2) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (*(buff_end + i - 2) == '\\n') {\n\t\t\t\t\thttp_head->head_ok = 1;\n\t\t\t\t\thttp_head->head_len = http_head->buff_len + i - 2;\n\t\t\t\t\ti++;\n\t\t\t\t\tbuff_end += i;\n\t\t\t\t\tdata_len -= i;\n\t\t\t\t\tdata++;\n\t\t\t\t\tif (_http_head_parse(http_head) != 0) {\n\t\t\t\t\t\treturn -2;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst char *content_len = NULL;\n\t\t\t\t\tcontent_len = http_head_get_fields_value(http_head, \"Content-Length\");\n\t\t\t\t\tif (content_len) {\n\t\t\t\t\t\thttp_head->expect_data_len = atol(content_len);\n\t\t\t\t\t} else {\n\t\t\t\t\t\thttp_head->expect_data_len = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (http_head->expect_data_len < 0) {\n\t\t\t\t\t\treturn -2;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tprocess_data_len += i;\n\t\tif (process_data_len >= http_head->buff_size) {\n\t\t\treturn -3;\n\t\t}\n\n\t\tif (http_head->head_ok == 0) {\n\t\t\t// Read data again */\n\t\t\thttp_head->buff_len += process_data_len;\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tconst char *transfer_encoding = http_head_get_fields_value(http_head, \"Transfer-Encoding\");\n\tif (transfer_encoding != NULL && strncasecmp(transfer_encoding, \"chunked\", sizeof(\"chunked\")) == 0) {\n\t\tis_chunked = 1;\n\t}\n\n\tif (http_head->head_ok == 1) {\n\t\tif (is_chunked == 0) {\n\t\t\tint get_data_len = (http_head->expect_data_len > data_len) ? data_len : http_head->expect_data_len;\n\t\t\tif (get_data_len == 0 && data_len > 0) {\n\t\t\t\tget_data_len = data_len;\n\t\t\t}\n\n\t\t\tif (http_head->data == NULL) {\n\t\t\t\thttp_head->data = buff_end;\n\t\t\t}\n\n\t\t\tmemcpy(buff_end, data, get_data_len);\n\t\t\tprocess_data_len += get_data_len;\n\t\t\thttp_head->data_len += get_data_len;\n\t\t\tbuff_end += get_data_len;\n\t\t} else {\n\t\t\tconst uint8_t *body_data = buff_end;\n\t\t\tuint32_t body_data_len = 0;\n\n\t\t\twhile (true) {\n\t\t\t\tint32_t chunk_len = 0;\n\t\t\t\tint offset = 0;\n\t\t\t\toffset = _http1_get_chunk_len(data, data_len, &chunk_len);\n\t\t\t\tif (offset < 0) {\n\t\t\t\t\treturn offset;\n\t\t\t\t}\n\n\t\t\t\tdata += offset;\n\t\t\t\tdata_len -= offset;\n\t\t\t\tprocess_data_len += offset;\n\n\t\t\t\tif (chunk_len == 0) {\n\t\t\t\t\thttp_head->data = body_data;\n\t\t\t\t\thttp_head->data_len = body_data_len;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif (data_len < chunk_len) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\tif (data_len < chunk_len + 2) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\tif (data[chunk_len] != '\\r' || data[chunk_len + 1] != '\\n') {\n\t\t\t\t\treturn -2;\n\t\t\t\t}\n\n\t\t\t\t/* Check buffer space */\n\t\t\t\tif (http_head->buff_len + process_data_len + chunk_len >= http_head->buff_size) {\n\t\t\t\t\treturn -3;\n\t\t\t\t}\n\n\t\t\t\tmemcpy(buff_end, data, chunk_len);\n\t\t\t\tbody_data_len += chunk_len;\n\t\t\t\tbuff_end += chunk_len;\n\t\t\t\tdata_len -= chunk_len;\n\t\t\t\tdata += chunk_len + 2;\n\t\t\t\tdata_len -= 2;\n\t\t\t\tprocess_data_len += chunk_len + 2;\n\t\t\t}\n\t\t}\n\n\t\t/* try append null byte */\n\t\tif (process_data_len < http_head->buff_size - 1) {\n\t\t\tbuff_end[0] = '\\0';\n\t\t}\n\t}\n\n\tif (process_data_len >= http_head->buff_size) {\n\t\treturn -3;\n\t}\n\n\thttp_head->buff_len += process_data_len;\n\tif (http_head->data_len < http_head->expect_data_len) {\n\t\treturn -1;\n\t}\n\n\treturn process_data_len;\n}\n\nint http_head_serialize_http1_1(struct http_head *http_head, char *buffer, int buffer_len)\n{\n\tint len = 0;\n\tchar *buff_start = buffer;\n\tstruct http_head_fields *fields = NULL;\n\tstruct http_params *params = NULL;\n\n\tif (http_head->head_type == HTTP_HEAD_INVALID) {\n\t\treturn -1;\n\t}\n\n\tif (http_head->head_type == HTTP_HEAD_REQUEST) {\n\t\tif (http_head->method == HTTP_METHOD_INVALID || http_head->url == NULL || http_head->version == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tlen = snprintf(buffer, buffer_len, \"%s %s\", http_method_str(http_head->method), http_head->url);\n\t\tif (len < 0) {\n\t\t\treturn -2;\n\t\t}\n\n\t\tbuffer += len;\n\t\tbuffer_len -= len;\n\n\t\tif (buffer_len < 2) {\n\t\t\treturn -3;\n\t\t}\n\n\t\tint count = 0;\n\t\tlist_for_each_entry(params, &http_head->params.list, list)\n\t\t{\n\t\t\tif (count == 0) {\n\t\t\t\tlen = snprintf(buffer, buffer_len, \"?%s=%s\", params->name, params->value);\n\t\t\t} else {\n\t\t\t\tlen = snprintf(buffer, buffer_len, \"&%s=%s\", params->name, params->value);\n\t\t\t}\n\n\t\t\tcount++;\n\t\t\tbuffer += len;\n\t\t\tbuffer_len -= len;\n\n\t\t\tif (buffer_len < 2) {\n\t\t\t\treturn -3;\n\t\t\t}\n\t\t}\n\n\t\tif (buffer_len < 2) {\n\t\t\treturn -3;\n\t\t}\n\n\t\tlen = snprintf(buffer, buffer_len, \" %s\\r\\n\", http_head->version);\n\t\tif (len < 0) {\n\t\t\treturn -2;\n\t\t}\n\t\tbuffer += len;\n\t\tbuffer_len -= len;\n\t}\n\n\tif (http_head->head_type == HTTP_HEAD_RESPONSE) {\n\t\tif (http_head->code < 0 || http_head->code_msg == NULL || http_head->version == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tlen = snprintf(buffer, buffer_len, \"%s %d %s\\r\\n\", http_head->version, http_head->code, http_head->code_msg);\n\t\tif (len < 0) {\n\t\t\treturn -2;\n\t\t}\n\n\t\tbuffer += len;\n\t\tbuffer_len -= len;\n\t\tif (buffer_len < 2) {\n\t\t\treturn -3;\n\t\t}\n\t}\n\n\tlist_for_each_entry(fields, &http_head->field_head.list, list)\n\t{\n\t\tlen = snprintf(buffer, buffer_len, \"%s: %s\\r\\n\", fields->name, fields->value);\n\t\tif (len < 0) {\n\t\t\treturn -2;\n\t\t}\n\n\t\tbuffer += len;\n\t\tbuffer_len -= len;\n\t\tif (buffer_len < 2) {\n\t\t\treturn -3;\n\t\t}\n\t}\n\n\tif (buffer_len < 2) {\n\t\treturn -3;\n\t}\n\n\t*(buffer) = '\\r';\n\t*(buffer + 1) = '\\n';\n\tbuffer += 2;\n\tbuffer_len -= 2;\n\n\tif (http_head->data_len > buffer_len) {\n\t\treturn -3;\n\t}\n\n\tif (http_head->data && http_head->data_len > 0) {\n\t\tmemcpy(buffer, http_head->data, http_head->data_len);\n\t\tbuffer += http_head->data_len;\n\t\tbuffer_len -= http_head->data_len;\n\t}\n\n\treturn buffer - buff_start;\n}\n"
  },
  {
    "path": "src/http_parse/http1_parse.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _HTTP_PARSE_HTTP1_H_\n#define _HTTP_PARSE_HTTP1_H_\n\n#include \"http_parse.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint http_head_serialize_http1_1(struct http_head *http_head, char *buffer, int buffer_len);\n\nint http_head_parse_http1_1(struct http_head *http_head, const uint8_t *data, int data_len);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/http_parse/http2.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/http2.h\"\n#include \"smartdns/util.h\"\n#include \"smartdns/tlog.h\"\n\n#include \"hpack.h\"\n#include \"http_parse.h\"\n#include <errno.h>\n#include <limits.h>\n#include <pthread.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef WITH_ZLIB\n#include <zlib.h>\n#endif\n\nconst char *http2_error_to_string(int ret)\n{\n\tswitch (ret) {\n\tcase HTTP2_ERR_NONE:\n\t\treturn \"no error\";\n\tcase HTTP2_ERR_EAGAIN:\n\t\treturn \"operation would block\";\n\tcase HTTP2_ERR_EOF:\n\t\treturn \"connection closed by client\";\n\tcase HTTP2_ERR_IO:\n\t\treturn \"I/O error\";\n\tcase HTTP2_ERR_PROTOCOL:\n\t\treturn \"protocol error\";\n\tcase HTTP2_ERR_HTTP1:\n\t\treturn \"client sent HTTP/1.1 after ALPN h2\";\n\tdefault:\n\t\treturn \"unknown error\";\n\t}\n}\n\n/* HTTP/2 Frame Types */\n#define HTTP2_FRAME_DATA 0x00\n#define HTTP2_FRAME_HEADERS 0x01\n#define HTTP2_FRAME_PRIORITY 0x02\n#define HTTP2_FRAME_RST_STREAM 0x03\n#define HTTP2_FRAME_SETTINGS 0x04\n#define HTTP2_FRAME_PUSH_PROMISE 0x05\n#define HTTP2_FRAME_PING 0x06\n#define HTTP2_FRAME_GOAWAY 0x07\n#define HTTP2_FRAME_WINDOW_UPDATE 0x08\n#define HTTP2_FRAME_CONTINUATION 0x09\n\n/* HTTP/2 Error Codes for RST_STREAM and GOAWAY */\n#define HTTP2_RST_NO_ERROR 0\n#define HTTP2_RST_PROTOCOL_ERROR 1\n#define HTTP2_RST_INTERNAL_ERROR 2\n#define HTTP2_RST_FLOW_CONTROL_ERROR 3\n#define HTTP2_RST_COMPRESSION_ERROR 9\n\n/* HTTP/2 Frame Flags */\n#define HTTP2_FLAG_END_STREAM 0x01\n#define HTTP2_FLAG_END_HEADERS 0x04\n#define HTTP2_FLAG_PADDED 0x08\n#define HTTP2_FLAG_PRIORITY 0x20\n#define HTTP2_FLAG_ACK 0x01\n\n/* HTTP/2 Settings */\n#define HTTP2_SETTINGS_HEADER_TABLE_SIZE 0x01\n#define HTTP2_SETTINGS_ENABLE_PUSH 0x02\n#define HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS 0x03\n#define HTTP2_SETTINGS_INITIAL_WINDOW_SIZE 0x04\n#define HTTP2_SETTINGS_MAX_FRAME_SIZE 0x05\n#define HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE 0x06\n\n/* Default values */\n#define HTTP2_DEFAULT_WINDOW_SIZE 65535\n#define HTTP2_DEFAULT_MAX_FRAME_SIZE 16384\n#define HTTP2_FRAME_HEADER_SIZE 9\n#define HTTP2_MAX_HEADER_TABLE_SIZE 65536\n#define HTTP2_CONNECTION_PREFACE \"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\"\n#define HTTP2_CONNECTION_PREFACE_LEN 24\n\n/* Stream states */\ntypedef enum {\n\tHTTP2_STREAM_IDLE,\n\tHTTP2_STREAM_OPEN,\n\tHTTP2_STREAM_HALF_CLOSED_LOCAL,\n\tHTTP2_STREAM_HALF_CLOSED_REMOTE,\n\tHTTP2_STREAM_CLOSED\n} http2_stream_state_t;\n\n/* Stream structure */\nstruct http2_stream {\n\tstruct http2_ctx *ctx;\n\tint refcount; /* Atomic reference count */\n\tuint32_t stream_id;\n\thttp2_stream_state_t state;\n\n\t/* Headers using hashmap like http_head */\n\tstruct http_head_fields header_list;\n\tDECLARE_HASHTABLE(header_map, 4);\n\n\tuint8_t *body_buffer;\n\tint body_buffer_size;\n\tint body_buffer_len;\n\tint body_read_offset;\n\tint end_stream_received;\n\tint end_stream_sent;\n\tint end_stream_read_handled; /* Flag to track if EOF has been reported to app */\n\tint accepted;                /* Flag to track if stream has been accepted by app */\n\tint window_size;\n\tint body_decompressed; /* Flag to track if body has been decompressed */\n\tvoid *ex_data;\n\tstruct hlist_node hash_node;\n\tstruct list_head node;\n};\n\n/* HTTP/2 context */\nstruct http2_ctx {\n\tpthread_mutex_t mutex;\n\tint refcount; /* Atomic reference count */\n\tint is_client;\n\tchar *server;\n\thttp2_bio_read_fn bio_read;\n\thttp2_bio_write_fn bio_write;\n\tvoid *private_data;\n\n\t/* Connection state */\n\tint status; /* 0: connected, <0: error code */\n\tint handshake_complete;\n\tint settings_received;\n\tint preface_received; /* Server: has received client preface */\n\tuint32_t next_stream_id;\n\tint connection_window_size;\n\tuint32_t peer_max_frame_size;\n\tint peer_initial_window_size;\n\tint active_streams;\n\tstruct http2_settings settings; /* HTTP/2 settings */\n\n\t/* I/O state */\n\tint want_read;\n\tint want_write;\n\tuint8_t *pending_write_buffer;\n\tint pending_write_len;\n\tint pending_write_capacity;\n\n\t/* HPACK */\n\tstruct hpack_context encoder;\n\tstruct hpack_context decoder;\n\n\t/* Streams */\n\tDECLARE_HASHTABLE(stream_map, 8);\n\tstruct list_head streams;\n\n\t/* Frame buffers */\n\tuint8_t read_buffer[HTTP2_DEFAULT_MAX_FRAME_SIZE + HTTP2_FRAME_HEADER_SIZE];\n\tint read_buffer_len;\n\tuint8_t write_buffer[HTTP2_DEFAULT_MAX_FRAME_SIZE + HTTP2_FRAME_HEADER_SIZE];\n\tint write_buffer_len;\n};\n\n/* Public API implementation */\nstruct http2_ctx_init_params {\n\tconst char *server;\n\thttp2_bio_read_fn bio_read;\n\thttp2_bio_write_fn bio_write;\n\tvoid *private_data;\n\tconst struct http2_settings *settings;\n\tint is_client;\n\tuint32_t next_stream_id;\n};\n\n/* Forward declarations */\nstatic int _http2_send_settings(struct http2_ctx *ctx, int ack);\nstatic int _http2_send_window_update(struct http2_ctx *ctx, uint32_t stream_id, int increment);\nstatic int _http2_process_frames(struct http2_ctx *ctx);\nstatic int http2_send_rst_stream(struct http2_ctx *ctx, uint32_t stream_id, uint32_t error_code);\n\nstatic void _http2_free_headers(struct http2_stream *stream);\nstatic struct http2_stream *_http2_find_stream(struct http2_ctx *ctx, uint32_t stream_id);\nstatic int _http2_stream_add_header(struct http2_stream *stream, const char *name, const char *value);\n\n/* Utility functions */\n\nstatic uint32_t read_uint32(const uint8_t *data)\n{\n\treturn ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8) | (uint32_t)data[3];\n}\n\nstatic void write_uint32(uint8_t *data, uint32_t value)\n{\n\tdata[0] = (value >> 24) & 0xFF;\n\tdata[1] = (value >> 16) & 0xFF;\n\tdata[2] = (value >> 8) & 0xFF;\n\tdata[3] = value & 0xFF;\n}\n\nstatic uint32_t read_uint24(const uint8_t *data)\n{\n\treturn ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | (uint32_t)data[2];\n}\n\nstatic void write_uint24(uint8_t *data, uint32_t value)\n{\n\tdata[0] = (value >> 16) & 0xFF;\n\tdata[1] = (value >> 8) & 0xFF;\n\tdata[2] = value & 0xFF;\n}\n\n/* Safe buffer size calculation to prevent integer overflow */\nstatic int safe_buffer_size(int current, int factor)\n{\n\tif (current < 0 || factor <= 0) {\n\t\treturn -1; /* Invalid parameters */\n\t}\n\n\tif ((size_t)current > (size_t)INT_MAX / (size_t)factor) {\n\t\treturn -1; /* Overflow */\n\t}\n\treturn current * factor;\n}\n\n/* HPACK callback */\nstatic int _http2_on_header(void *ctx, const char *name, const char *value)\n{\n\tstruct http2_stream *stream = (struct http2_stream *)ctx;\n\tif (!stream) {\n\t\treturn -1;\n\t}\n\treturn _http2_stream_add_header(stream, name, value);\n}\n\nstatic void _http2_free_headers(struct http2_stream *stream)\n{\n\tstruct http_head_fields *fields = NULL, *tmp;\n\n\tlist_for_each_entry_safe(fields, tmp, &stream->header_list.list, list)\n\t{\n\t\tlist_del(&fields->list);\n\t\tfree((void *)fields->name);\n\t\tfree((void *)fields->value);\n\t\tfree(fields);\n\t}\n\n\thash_init(stream->header_map);\n}\n\nstatic int _http2_stream_add_header(struct http2_stream *stream, const char *name, const char *value)\n{\n\tuint32_t key = 0;\n\tstruct http_head_fields *fields = NULL;\n\n\tif (name == NULL || value == NULL) {\n\t\treturn -1;\n\t}\n\n\tfields = zalloc(1, sizeof(*fields));\n\tif (fields == NULL) {\n\t\treturn -1;\n\t}\n\n\tfields->name = strdup(name);\n\tfields->value = strdup(value);\n\tif (!fields->name || !fields->value) {\n\t\tfree((void *)fields->name);\n\t\tfree((void *)fields->value);\n\t\tfree(fields);\n\t\treturn -1;\n\t}\n\n\tlist_add_tail(&fields->list, &stream->header_list.list);\n\tkey = hash_string_case(name);\n\thash_add(stream->header_map, &fields->node, key);\n\n\treturn 0;\n}\n\nstatic const char *_http2_stream_get_header_value(struct http2_stream *stream, const char *name)\n{\n\tuint32_t key;\n\tstruct http_head_fields *field = NULL;\n\n\tkey = hash_string_case(name);\n\thash_for_each_possible(stream->header_map, field, node, key)\n\t{\n\t\tif (strncasecmp(field->name, name, 128) == 0) {\n\t\t\treturn field->value;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nvoid http2_stream_headers_walk(struct http2_stream *stream, header_walk_fn fn, void *arg)\n{\n\tstruct list_head *pos;\n\tif (!stream || !fn) {\n\t\treturn;\n\t}\n\n\tlist_for_each(pos, &stream->header_list.list)\n\t{\n\t\tstruct http_head_fields *pair = list_entry(pos, struct http_head_fields, list);\n\t\tfn(arg, pair->name, pair->value);\n\t}\n}\n\n/* Frame handling */\nstatic int _http2_write_frame_header(uint8_t *buf, int length, uint8_t type, uint8_t flags, uint32_t stream_id)\n{\n\twrite_uint24(buf, length);\n\tbuf[3] = type;\n\tbuf[4] = flags;\n\twrite_uint32(buf + 5, stream_id & 0x7FFFFFFF);\n\treturn HTTP2_FRAME_HEADER_SIZE;\n}\n\nstatic int _http2_send_frame(struct http2_ctx *ctx, const uint8_t *data, int len)\n{\n\t/* Check if connection is already closed */\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\tif (ctx->status < 0) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\treturn -1;\n\t}\n\n\tint total_sent = 0;\n\tint unsent = 0;\n\n\t/* First, try to flush any pending writes */\n\tif (ctx->pending_write_len > 0) {\n\t\tint ret = ctx->bio_write(ctx->private_data, ctx->pending_write_buffer, ctx->pending_write_len);\n\t\tif (ret < 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\tctx->want_write = 1;\n\t\t\t\t/* Still have pending data, buffer new data too */\n\t\t\t\tgoto buffer_new_data;\n\t\t\t}\n\t\t\t/* Real error */\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (ret > 0) {\n\t\t\t/* Partial or complete write */\n\t\t\tif (ret < ctx->pending_write_len) {\n\t\t\t\t/* Partial write, move remaining data */\n\t\t\t\tmemmove(ctx->pending_write_buffer, ctx->pending_write_buffer + ret, ctx->pending_write_len - ret);\n\t\t\t\tctx->pending_write_len -= ret;\n\t\t\t\tctx->want_write = 1;\n\t\t\t\tgoto buffer_new_data;\n\t\t\t} else {\n\t\t\t\t/* Complete write of pending data */\n\t\t\t\tctx->pending_write_len = 0;\n\t\t\t\tctx->want_write = 0;\n\t\t\t}\n\t\t} else if (ret == 0) {\n\t\t\t/* Connection closed */\n\t\t\tctx->status = HTTP2_ERR_EOF;\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/* Now try to send the new data */\n\twhile (total_sent < len) {\n\t\tint ret = ctx->bio_write(ctx->private_data, data + total_sent, len - total_sent);\n\t\tif (ret < 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\tctx->want_write = 1;\n\t\t\t\t/* Buffer remaining data */\n\t\t\t\tgoto buffer_new_data;\n\t\t\t}\n\t\t\t/* Real error */\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (ret == 0) {\n\t\t\t/* Connection closed */\n\t\t\tctx->status = HTTP2_ERR_EOF;\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\treturn -1;\n\t\t}\n\n\t\ttotal_sent += ret;\n\t}\n\n\tctx->want_write = 0;\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn len;\n\nbuffer_new_data:\n\t/* Buffer the unsent data */\n\tunsent = len - total_sent;\n\tif (unsent > 0) {\n\t\t/* Ensure buffer capacity */\n\t\tint needed = ctx->pending_write_len + unsent;\n\t\tif (needed > ctx->pending_write_capacity) {\n\t\t\tint new_capacity = ctx->pending_write_capacity ? safe_buffer_size(ctx->pending_write_capacity, 2) : 8192;\n\t\t\tif (new_capacity < 0) {\n\t\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\twhile (new_capacity < needed) {\n\t\t\t\tint temp = safe_buffer_size(new_capacity, 2);\n\t\t\t\tif (temp < 0) {\n\t\t\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tnew_capacity = temp;\n\t\t\t}\n\t\t\tuint8_t *new_buffer = realloc(ctx->pending_write_buffer, new_capacity);\n\t\t\tif (!new_buffer) {\n\t\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tctx->pending_write_buffer = new_buffer;\n\t\t\tctx->pending_write_capacity = new_capacity;\n\t\t}\n\n\t\t/* Append unsent data to buffer */\n\t\tmemcpy(ctx->pending_write_buffer + ctx->pending_write_len, data + total_sent, unsent);\n\t\tctx->pending_write_len += unsent;\n\t}\n\n\tctx->want_write = 1;\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn len; /* Return success - data is buffered */\n}\n\nstatic int _http2_send_settings(struct http2_ctx *ctx, int ack)\n{\n\tuint8_t frame[HTTP2_FRAME_HEADER_SIZE + 256]; /* Increased size for ENABLE_PUSH */\n\tint offset = HTTP2_FRAME_HEADER_SIZE;\n\tuint8_t flags = ack ? HTTP2_FLAG_ACK : 0;\n\n\tif (!ack) {\n\t\t/* Client: Disable Server Push */\n\t\tif (ctx->is_client) {\n\t\t\twrite_uint32(frame + offset, (HTTP2_SETTINGS_ENABLE_PUSH << 16) | 0);\n\t\t\twrite_uint32(frame + offset + 2, 0); /* 0 = disabled */\n\t\t\toffset += 6;\n\t\t}\n\n\t\t/* SETTINGS_HEADER_TABLE_SIZE */\n\t\twrite_uint32(frame + offset, (HTTP2_SETTINGS_HEADER_TABLE_SIZE << 16) | 0);\n\t\twrite_uint32(frame + offset + 2, HTTP2_MAX_HEADER_TABLE_SIZE);\n\t\toffset += 6;\n\n\t\t/* SETTINGS_INITIAL_WINDOW_SIZE */\n\t\twrite_uint32(frame + offset, (HTTP2_SETTINGS_INITIAL_WINDOW_SIZE << 16) | 0);\n\t\twrite_uint32(frame + offset + 2, HTTP2_DEFAULT_WINDOW_SIZE);\n\t\toffset += 6;\n\n\t\t/* SETTINGS_MAX_FRAME_SIZE */\n\t\twrite_uint32(frame + offset, (HTTP2_SETTINGS_MAX_FRAME_SIZE << 16) | 0);\n\t\twrite_uint32(frame + offset + 2, HTTP2_DEFAULT_MAX_FRAME_SIZE);\n\t\toffset += 6;\n\n\t\tif (ctx->settings.max_concurrent_streams > 0) {\n\t\t\t/* SETTINGS_MAX_CONCURRENT_STREAMS */\n\t\t\twrite_uint32(frame + offset, (HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS << 16) | 0);\n\t\t\twrite_uint32(frame + offset + 2, ctx->settings.max_concurrent_streams);\n\t\t\toffset += 6;\n\t\t}\n\t}\n\n\t_http2_write_frame_header(frame, offset - HTTP2_FRAME_HEADER_SIZE, HTTP2_FRAME_SETTINGS, flags, 0);\n\treturn _http2_send_frame(ctx, frame, offset);\n}\n\nstatic int _http2_send_window_update(struct http2_ctx *ctx, uint32_t stream_id, int increment)\n{\n\tuint8_t frame[HTTP2_FRAME_HEADER_SIZE + 4];\n\n\t_http2_write_frame_header(frame, 4, HTTP2_FRAME_WINDOW_UPDATE, 0, stream_id);\n\twrite_uint32(frame + HTTP2_FRAME_HEADER_SIZE, increment & 0x7FFFFFFF);\n\n\treturn _http2_send_frame(ctx, frame, sizeof(frame));\n}\n\nstatic int _http2_send_goaway(struct http2_ctx *ctx, uint32_t last_stream_id, uint32_t error_code,\n\t\t\t\t\t\t\t  const uint8_t *debug_data, int debug_len)\n{\n\tuint8_t frame[HTTP2_FRAME_HEADER_SIZE + 8 + 256];\n\tint offset = HTTP2_FRAME_HEADER_SIZE;\n\n\twrite_uint32(frame + offset, last_stream_id & 0x7FFFFFFF);\n\twrite_uint32(frame + offset + 4, error_code);\n\tif (debug_len > 0 && debug_len <= 256) {\n\t\tmemcpy(frame + offset + 8, debug_data, debug_len);\n\t} else {\n\t\tdebug_len = 0;\n\t}\n\n\t_http2_write_frame_header(frame, 8 + debug_len, HTTP2_FRAME_GOAWAY, 0, 0);\n\treturn _http2_send_frame(ctx, frame, HTTP2_FRAME_HEADER_SIZE + 8 + debug_len);\n}\n\nstatic int _http2_send_data_frame(struct http2_ctx *ctx, uint32_t stream_id, const uint8_t *data, int len,\n\t\t\t\t\t\t\t\t  int end_stream)\n{\n\tuint8_t *frame = zalloc(1, HTTP2_FRAME_HEADER_SIZE + len);\n\tif (!frame) {\n\t\treturn -1;\n\t}\n\n\tuint8_t flags = end_stream ? HTTP2_FLAG_END_STREAM : 0;\n\t_http2_write_frame_header(frame, len, HTTP2_FRAME_DATA, flags, stream_id);\n\tif (len > 0) {\n\t\tmemcpy(frame + HTTP2_FRAME_HEADER_SIZE, data, len);\n\t}\n\n\tint ret = _http2_send_frame(ctx, frame, HTTP2_FRAME_HEADER_SIZE + len);\n\tfree(frame);\n\treturn ret;\n}\n\nstatic int _http2_send_headers_frame(struct http2_ctx *ctx, uint32_t stream_id, struct http2_stream *stream,\n\t\t\t\t\t\t\t\t\t int end_stream, int end_headers)\n{\n\tuint8_t *header_block = zalloc(1, 4096);\n\tint header_block_size = 4096;\n\tint header_block_len = 0;\n\tstruct http_head_fields *field;\n\tuint8_t flags = 0;\n\tint ret = -1;\n\n\tif (!header_block) {\n\t\treturn -1;\n\t}\n\n\tif (end_stream) {\n\t\tflags |= HTTP2_FLAG_END_STREAM;\n\t}\n\tif (end_headers) {\n\t\tflags |= HTTP2_FLAG_END_HEADERS;\n\t}\n\n\t/* Encode headers */\n\tlist_for_each_entry(field, &stream->header_list.list, list)\n\t{\n\t\tif (header_block_len + 4096 > header_block_size) {\n\t\t\tint new_size = safe_buffer_size(header_block_size, 2);\n\t\t\tif (new_size < 0) {\n\t\t\t\tgoto cleanup;\n\t\t\t}\n\t\t\tuint8_t *new_block = realloc(header_block, new_size);\n\t\t\tif (!new_block) {\n\t\t\t\tgoto cleanup;\n\t\t\t}\n\t\t\theader_block = new_block;\n\t\t\theader_block_size = new_size;\n\t\t}\n\t\tint encode_ret = hpack_encode_header(&ctx->encoder, field->name, field->value, header_block + header_block_len,\n\t\t\t\t\t\t\t\t\t\t\t header_block_size - header_block_len);\n\t\tif (encode_ret < 0) {\n\t\t\tgoto cleanup;\n\t\t}\n\t\theader_block_len += encode_ret;\n\t}\n\n\t/* Send HEADERS frame */\n\tuint8_t frame[HTTP2_FRAME_HEADER_SIZE];\n\t_http2_write_frame_header(frame, header_block_len, HTTP2_FRAME_HEADERS, flags, stream_id);\n\n\tif (_http2_send_frame(ctx, frame, HTTP2_FRAME_HEADER_SIZE) < 0) {\n\t\tgoto cleanup;\n\t}\n\n\tif (header_block_len > 0 && _http2_send_frame(ctx, header_block, header_block_len) < 0) {\n\t\tgoto cleanup;\n\t}\n\n\tret = 0;\n\ncleanup:\n\tfree(header_block);\n\treturn ret;\n}\n\nstatic struct http2_stream *_http2_find_stream(struct http2_ctx *ctx, uint32_t stream_id)\n{\n\tstruct http2_stream *stream;\n\n\tif (!ctx) {\n\t\treturn NULL;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\thash_for_each_possible(ctx->stream_map, stream, hash_node, stream_id)\n\t{\n\t\tif (stream->stream_id == stream_id) {\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\treturn stream;\n\t\t}\n\t}\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn NULL;\n}\n\nstatic struct http2_stream *_http2_create_stream(struct http2_ctx *ctx, uint32_t stream_id)\n{\n\t/* Check concurrent streams limit */\n\tif (ctx->active_streams >= ctx->settings.max_concurrent_streams && ctx->settings.max_concurrent_streams > 0) {\n\t\ttlog(TLOG_DEBUG, \"HTTP/2: Max concurrent streams limit reached (%d)\", ctx->settings.max_concurrent_streams);\n\t\terrno = ENOSPC;\n\t\treturn NULL;\n\t}\n\n\tstruct http2_stream *stream = zalloc(1, sizeof(*stream));\n\tif (!stream) {\n\t\treturn NULL;\n\t}\n\n\tstream->ctx = ctx;\n\tstream->refcount = 0; /* Initial reference count */\n\tstream->stream_id = stream_id;\n\tstream->state = HTTP2_STREAM_IDLE;\n\n\t/* Determine if stream is accepted (locally initiated) or needs accept (peer initiated) */\n\tif (ctx->is_client) {\n\t\t/* Client: Odd IDs are local (accepted), Even IDs are remote (need accept) */\n\t\tstream->accepted = (stream_id % 2) != 0;\n\t} else {\n\t\t/* Server: Even IDs are local (accepted), Odd IDs are remote (need accept) */\n\t\tstream->accepted = (stream_id % 2) == 0;\n\t}\n\n\tstream->window_size = ctx->peer_initial_window_size;\n\tstream->body_buffer_size = 8192;\n\tstream->body_buffer = zalloc(1, stream->body_buffer_size);\n\tif (!stream->body_buffer) {\n\t\tfree(stream);\n\t\treturn NULL;\n\t}\n\n\t/* Initialize header structures */\n\tINIT_LIST_HEAD(&stream->header_list.list);\n\thash_init(stream->header_map);\n\n\thttp2_stream_get(stream); /* Hold ownership for ctx */\n\tpthread_mutex_lock(&ctx->mutex);\n\thash_add(ctx->stream_map, &stream->hash_node, stream->stream_id);\n\tlist_add(&stream->node, &ctx->streams);\n\tctx->active_streams++;\n\tpthread_mutex_unlock(&ctx->mutex);\n\n\treturn stream;\n}\n\nstatic int _http2_remove_stream(struct http2_stream *stream, int do_put)\n{\n\tstruct http2_ctx *ctx = NULL;\n\tif (!stream) {\n\t\treturn -1;\n\t}\n\n\tctx = stream->ctx;\n\tif (ctx) {\n\t\tpthread_mutex_lock(&ctx->mutex);\n\t}\n\n\t/* Try to remove from hash map */\n\tif (!hlist_unhashed(&stream->hash_node)) {\n\t\thash_del(&stream->hash_node);\n\t}\n\n\t/* Try to remove from list */\n\tif (!list_empty(&stream->node)) {\n\t\tlist_del_init(&stream->node);\n\t\tstream->ctx = NULL; /* Break link to ctx to prevent UAF if ctx dies first */\n\t\tif (ctx) {\n\t\t\tctx->active_streams--;\n\t\t}\n\t\t\n\t\t/* Only release ownership if we successfully removed it from the list.\n\t\t   This prevents double-free if called concurrently or recursively. */\n\t\tif (do_put) {\n\t\t\thttp2_stream_put(stream);\n\t\t}\n\t} else {\n\t\t/* If already removed from list, we assume we don't own the list reference anymore */\n\t}\n\n\tif (ctx) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t}\n\n\treturn 0;\n}\n\nstatic int _http2_process_data_frame(struct http2_ctx *ctx, int stream_id, const uint8_t *data, int len, uint8_t flags)\n{\n\tstruct http2_stream *stream = _http2_find_stream(ctx, stream_id);\n\tif (!stream) {\n\t\treturn -1;\n\t}\n\n\t/* Stream ID 0 is invalid for DATA frames */\n\tif (stream_id == 0) {\n\t\t_http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0);\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn -1;\n\t}\n\n\t/* Check for invalid length */\n\tif (len < 0) {\n\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_PROTOCOL_ERROR);\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn -1;\n\t}\n\n\t/* Check for integer overflow in buffer size */\n\tif (stream->body_buffer_len > INT_MAX - len) {\n\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR);\n\t\treturn -1;\n\t}\n\n\t/* Limit body buffer to 1MB to prevent OOM DOS */\n\tif (stream->body_buffer_len + len > 1024 * 1024) {\n\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR);\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn -1;\n\t}\n\n\t/* Check flow control */\n\tif (len < 0 || stream->window_size <= 0 || ctx->connection_window_size <= 0) {\n\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_FLOW_CONTROL_ERROR);\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn -1;\n\t}\n\tif (len > stream->window_size || len > ctx->connection_window_size) {\n\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_FLOW_CONTROL_ERROR);\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn -1;\n\t}\n\n\t/* Append to body buffer */\n\tif (stream->body_buffer_len + len > stream->body_buffer_size) {\n\t\tint new_size = safe_buffer_size(stream->body_buffer_size, 2);\n\t\tif (new_size < 0) {\n\t\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR);\n\t\t\treturn -1;\n\t\t}\n\t\twhile (new_size < stream->body_buffer_len + len) {\n\t\t\tint temp = safe_buffer_size(new_size, 2);\n\t\t\tif (temp < 0) {\n\t\t\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tnew_size = temp;\n\t\t}\n\t\tuint8_t *new_buffer = realloc(stream->body_buffer, new_size);\n\t\tif (!new_buffer) {\n\t\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_INTERNAL_ERROR);\n\t\t\treturn -1;\n\t\t}\n\t\tstream->body_buffer = new_buffer;\n\t\tstream->body_buffer_size = new_size;\n\t}\n\n\tmemcpy(stream->body_buffer + stream->body_buffer_len, data, len);\n\tstream->body_buffer_len += len;\n\n\tif (flags & HTTP2_FLAG_END_STREAM) {\n\t\tstream->end_stream_received = 1;\n\t\tif (stream->state == HTTP2_STREAM_OPEN) {\n\t\t\tstream->state = HTTP2_STREAM_HALF_CLOSED_REMOTE;\n\t\t} else if (stream->state == HTTP2_STREAM_HALF_CLOSED_LOCAL) {\n\t\t\tstream->state = HTTP2_STREAM_CLOSED;\n\t\t}\n\t}\n\n\t/* Update flow control */\n\t/* Send WINDOW_UPDATE immediately to prevent flow control deadlock */\n\t/* Connection-level WINDOW_UPDATE */\n\tif (_http2_send_window_update(ctx, 0, len) < 0) {\n\t\treturn -1;\n\t}\n\tctx->connection_window_size += len;\n\n\t/* Stream-level WINDOW_UPDATE */\n\tif (_http2_send_window_update(ctx, stream_id, len) < 0) {\n\t\tctx->connection_window_size -= len;\n\t\treturn -1;\n\t}\n\tstream->window_size += len;\n\n\treturn 0;\n}\n\nstatic int _http2_process_headers_frame(struct http2_ctx *ctx, int stream_id, const uint8_t *data, int len,\n\t\t\t\t\t\t\t\t\t\tuint8_t flags, uint8_t frame_type)\n{\n\t/* Handle Padding and Priority fields (only for HEADERS frame) */\n\tint pad_len = 0;\n\tif (frame_type == HTTP2_FRAME_HEADERS) {\n\t\tif (flags & HTTP2_FLAG_PADDED) {\n\t\t\tif (len < 1) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tpad_len = data[0];\n\t\t\tdata++;\n\t\t\tlen--;\n\t\t}\n\n\t\tif (flags & HTTP2_FLAG_PRIORITY) {\n\t\t\tif (len < 5) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tdata += 5;\n\t\t\tlen -= 5;\n\t\t}\n\t} else if (frame_type == HTTP2_FRAME_CONTINUATION) {\n\t\t/* CONTINUATION frames must not have PADDED or PRIORITY flags */\n\t\tif (flags & (HTTP2_FLAG_PADDED | HTTP2_FLAG_PRIORITY)) {\n\t\t\t_http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0);\n\t\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (len < pad_len) {\n\t\treturn -1;\n\t}\n\tlen -= pad_len;\n\n\tstruct http2_stream *stream = _http2_find_stream(ctx, stream_id);\n\tif (!stream) {\n\t\tstream = _http2_create_stream(ctx, stream_id);\n\t\tif (!stream) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/* Clear old headers only if this is a new HEADERS frame */\n\tif (frame_type == HTTP2_FRAME_HEADERS) {\n\t\t_http2_free_headers(stream);\n\t}\n\n\tif (len > 0) { /* Only decode if there's data */\n\t\tif (hpack_decode_headers(&ctx->decoder, data, len, _http2_on_header, stream) < 0) {\n\t\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_COMPRESSION_ERROR);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (stream->state == HTTP2_STREAM_IDLE) {\n\t\tstream->state = HTTP2_STREAM_OPEN;\n\t}\n\n\tif (flags & HTTP2_FLAG_END_STREAM) {\n\t\tstream->end_stream_received = 1;\n\t\tif (stream->state == HTTP2_STREAM_OPEN) {\n\t\t\tstream->state = HTTP2_STREAM_HALF_CLOSED_REMOTE;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _http2_process_settings_frame(struct http2_ctx *ctx, const uint8_t *data, int len, uint8_t flags)\n{\n\tif (!ctx || !data) {\n\t\treturn -1;\n\t}\n\n\tif (flags & HTTP2_FLAG_ACK) {\n\t\treturn 0;\n\t}\n\n\tif (len < 0 || len % 6 != 0) {\n\t\t_http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0);\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn -1;\n\t}\n\n\t/* Process settings */\n\tint offset = 0;\n\twhile (offset + 6 <= len) {\n\t\tuint16_t id = (data[offset] << 8) | data[offset + 1];\n\t\tuint32_t value = read_uint32(data + offset + 2);\n\t\toffset += 6;\n\n\t\tswitch (id) {\n\t\tcase HTTP2_SETTINGS_HEADER_TABLE_SIZE:\n\t\t\tctx->encoder.max_dynamic_table_size = value;\n\t\t\tbreak;\n\t\tcase HTTP2_SETTINGS_ENABLE_PUSH:\n\t\t\t/* Server: must ignore this setting */\n\t\t\tbreak;\n\t\tcase HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:\n\t\t\tctx->settings.max_concurrent_streams = value;\n\t\t\tbreak;\n\t\tcase HTTP2_SETTINGS_INITIAL_WINDOW_SIZE:\n\t\t\tctx->peer_initial_window_size = value > INT_MAX ? INT_MAX : value;\n\t\t\tbreak;\n\t\tcase HTTP2_SETTINGS_MAX_FRAME_SIZE:\n\t\t\tctx->peer_max_frame_size = value > HTTP2_DEFAULT_MAX_FRAME_SIZE ? HTTP2_DEFAULT_MAX_FRAME_SIZE : value;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Send SETTINGS ACK */\n\tctx->settings_received = 1;\n\treturn _http2_send_settings(ctx, 1);\n}\n\nstatic int _http2_process_window_update_frame(struct http2_ctx *ctx, int stream_id, const uint8_t *data, int len)\n{\n\tif (!ctx || !data) {\n\t\treturn -1;\n\t}\n\n\tif (len != 4) {\n\t\t_http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0);\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn -1;\n\t}\n\n\tuint32_t increment = read_uint32(data) & 0x7FFFFFFF;\n\n\tif (increment == 0) {\n\t\t/* RFC 9113: A receiver MUST treat the receipt of a WINDOW_UPDATE frame with an\n\t\t   increment of 0 as a stream error of type PROTOCOL_ERROR */\n\t\tif (stream_id == 0) {\n\t\t\t_http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0);\n\t\t} else {\n\t\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_PROTOCOL_ERROR);\n\t\t}\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn -1;\n\t}\n\n\tif (stream_id == 0) {\n\t\tif (ctx->connection_window_size > INT_MAX - (int)increment) {\n\t\t\t_http2_send_goaway(ctx, 0, HTTP2_RST_FLOW_CONTROL_ERROR, NULL, 0);\n\t\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\t\treturn -1;\n\t\t}\n\t\tctx->connection_window_size += (int)increment;\n\t} else {\n\t\tstruct http2_stream *stream = _http2_find_stream(ctx, stream_id);\n\t\tif (stream) {\n\t\t\tif (stream->window_size > INT_MAX - (int)increment) {\n\t\t\t\thttp2_send_rst_stream(ctx, stream_id, HTTP2_RST_FLOW_CONTROL_ERROR);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tstream->window_size += (int)increment;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/* Verify HTTP/2 connection preface (server side) */\nstatic int http2_verify_connection_preface(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\n\t/* Server: first read and verify connection preface */\n\tif (ctx->is_client || ctx->preface_received) {\n\t\treturn 0; /* Not applicable or already verified */\n\t}\n\n\t/* Need to read 24-byte connection preface */\n\tif (ctx->read_buffer_len < HTTP2_CONNECTION_PREFACE_LEN) {\n\t\tint to_read = HTTP2_CONNECTION_PREFACE_LEN - ctx->read_buffer_len;\n\t\tint ret = ctx->bio_read(ctx->private_data, ctx->read_buffer + ctx->read_buffer_len, to_read);\n\t\tif (ret < 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\tctx->want_read = 1;\n\t\t\t\treturn HTTP2_ERR_EAGAIN;\n\t\t\t}\n\t\t\tctx->status = HTTP2_ERR_IO;\n\t\t\treturn HTTP2_ERR_IO;\n\t\t}\n\t\tif (ret == 0) {\n\t\t\tctx->status = HTTP2_ERR_EOF;\n\t\t\treturn HTTP2_ERR_EOF;\n\t\t}\n\n\t\tctx->read_buffer_len += ret;\n\t\tif (ctx->read_buffer_len < HTTP2_CONNECTION_PREFACE_LEN) {\n\t\t\tctx->want_read = 1;\n\t\t\treturn HTTP2_ERR_EAGAIN;\n\t\t}\n\t}\n\n\t/* Verify preface */\n\tif (memcmp(ctx->read_buffer, HTTP2_CONNECTION_PREFACE, HTTP2_CONNECTION_PREFACE_LEN) != 0) {\n\t\t/* Check if it looks like HTTP/1.1 */\n\t\tif (ctx->read_buffer_len >= 4 &&\n\t\t\t(memcmp(ctx->read_buffer, \"GET \", 4) == 0 || memcmp(ctx->read_buffer, \"POST \", 5) == 0 ||\n\t\t\t memcmp(ctx->read_buffer, \"HEAD \", 5) == 0 || memcmp(ctx->read_buffer, \"PUT \", 4) == 0 ||\n\t\t\t memcmp(ctx->read_buffer, \"DELETE \", 7) == 0 || memcmp(ctx->read_buffer, \"OPTIONS \", 8) == 0 ||\n\t\t\t memcmp(ctx->read_buffer, \"PATCH \", 6) == 0 || memcmp(ctx->read_buffer, \"CONNECT \", 8) == 0 ||\n\t\t\t memcmp(ctx->read_buffer, \"TRACE \", 6) == 0)) {\n\t\t\tctx->status = HTTP2_ERR_HTTP1;\n\t\t\treturn HTTP2_ERR_HTTP1;\n\t\t}\n\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\treturn HTTP2_ERR_PROTOCOL;\n\t}\n\n\t/* Preface verified, remove it from buffer */\n\tctx->read_buffer_len -= HTTP2_CONNECTION_PREFACE_LEN;\n\tif (ctx->read_buffer_len > 0) {\n\t\tmemmove(ctx->read_buffer, ctx->read_buffer + HTTP2_CONNECTION_PREFACE_LEN, ctx->read_buffer_len);\n\t}\n\tctx->preface_received = 1;\n\n\treturn 0;\n}\n\nstatic int _http2_process_frames(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\n\tctx->want_read = 0;\n\n\t/* Server: verify connection preface */\n\tint ret = http2_verify_connection_preface(ctx);\n\tif (ret != 0) {\n\t\treturn ret;\n\t}\n\n\twhile (1) {\n\t\t/* Try to read frame header */\n\t\tif (ctx->read_buffer_len < HTTP2_FRAME_HEADER_SIZE) {\n\t\t\tret = ctx->bio_read(ctx->private_data, ctx->read_buffer + ctx->read_buffer_len,\n\t\t\t\t\t\t\t\tHTTP2_FRAME_HEADER_SIZE - ctx->read_buffer_len);\n\t\t\tif (ret < 0) {\n\t\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\t\t/* Normal for async I/O - just wait for more data */\n\t\t\t\t\tctx->want_read = 1;\n\t\t\t\t\treturn HTTP2_ERR_EAGAIN;\n\t\t\t\t}\n\t\t\t\t/* Real error */\n\t\t\t\tctx->status = HTTP2_ERR_IO;\n\t\t\t\treturn HTTP2_ERR_IO;\n\t\t\t}\n\t\t\tif (ret == 0) {\n\t\t\t\t/* Connection closed */\n\t\t\t\tctx->status = HTTP2_ERR_EOF;\n\t\t\t\treturn HTTP2_ERR_EOF;\n\t\t\t}\n\n\t\t\tctx->read_buffer_len += ret;\n\t\t\tif (ctx->read_buffer_len < HTTP2_FRAME_HEADER_SIZE) {\n\t\t\t\t/* Need more data */\n\t\t\t\tctx->want_read = 1;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\t/* Parse frame header */\n\t\tuint32_t length = read_uint24(ctx->read_buffer);\n\t\tuint8_t type = ctx->read_buffer[3];\n\t\tuint8_t flags = ctx->read_buffer[4];\n\t\tuint32_t stream_id = read_uint32(ctx->read_buffer + 5) & 0x7FFFFFFF;\n\n\t\t/* Validate frame length */\n\t\tif (length > ctx->peer_max_frame_size || length > INT_MAX) {\n\t\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\t\treturn HTTP2_ERR_PROTOCOL;\n\t\t}\n\n\t\t/* Read frame payload */\n\t\tif (ctx->read_buffer_len < (int)(HTTP2_FRAME_HEADER_SIZE + length)) {\n\t\t\tint to_read = HTTP2_FRAME_HEADER_SIZE + length - ctx->read_buffer_len;\n\t\t\tret = ctx->bio_read(ctx->private_data, ctx->read_buffer + ctx->read_buffer_len, to_read);\n\t\t\tif (ret < 0) {\n\t\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\t\t/* Normal for async I/O - just wait for more data */\n\t\t\t\t\tctx->want_read = 1;\n\t\t\t\t\treturn HTTP2_ERR_EAGAIN;\n\t\t\t\t}\n\t\t\t\t/* Real error */\n\t\t\t\tctx->status = HTTP2_ERR_IO;\n\t\t\t\treturn HTTP2_ERR_IO;\n\t\t\t}\n\t\t\tif (ret == 0) {\n\t\t\t\t/* Connection closed */\n\t\t\t\tctx->status = HTTP2_ERR_EOF;\n\t\t\t\treturn HTTP2_ERR_EOF;\n\t\t\t}\n\n\t\t\tctx->read_buffer_len += ret;\n\t\t\tif (ctx->read_buffer_len < (int)(HTTP2_FRAME_HEADER_SIZE + length)) {\n\t\t\t\t/* Need more data */\n\t\t\t\tctx->want_read = 1;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\t/* Process frame */\n\t\tconst uint8_t *payload = ctx->read_buffer + HTTP2_FRAME_HEADER_SIZE;\n\n\t\tswitch (type) {\n\t\tcase HTTP2_FRAME_DATA:\n\t\t\t_http2_process_data_frame(ctx, stream_id, payload, length, flags);\n\t\t\tbreak;\n\t\tcase HTTP2_FRAME_HEADERS:\n\t\t\t_http2_process_headers_frame(ctx, stream_id, payload, length, flags, HTTP2_FRAME_HEADERS);\n\t\t\tbreak;\n\t\tcase HTTP2_FRAME_CONTINUATION:\n\t\t\t/* CONTINUATION frames continue a HEADERS frame */\n\t\t\t_http2_process_headers_frame(ctx, stream_id, payload, length, flags, HTTP2_FRAME_CONTINUATION);\n\t\t\tbreak;\n\t\tcase HTTP2_FRAME_SETTINGS:\n\t\t\t_http2_process_settings_frame(ctx, payload, length, flags);\n\t\t\tbreak;\n\t\tcase HTTP2_FRAME_WINDOW_UPDATE:\n\t\t\t_http2_process_window_update_frame(ctx, stream_id, payload, length);\n\t\t\tbreak;\n\t\tcase HTTP2_FRAME_PING:\n\t\t\t/* Echo PING */\n\t\t\tif (!(flags & HTTP2_FLAG_ACK)) {\n\t\t\t\tuint8_t pong[HTTP2_FRAME_HEADER_SIZE + 8];\n\t\t\t\t_http2_write_frame_header(pong, 8, HTTP2_FRAME_PING, HTTP2_FLAG_ACK, 0);\n\t\t\t\tmemcpy(pong + HTTP2_FRAME_HEADER_SIZE, payload, 8);\n\t\t\t\t_http2_send_frame(ctx, pong, sizeof(pong));\n\t\t\t}\n\t\t\tbreak;\n\t\tcase HTTP2_FRAME_RST_STREAM:\n\t\t\tif (length != 4) {\n\t\t\t\t_http2_send_goaway(ctx, 0, HTTP2_RST_PROTOCOL_ERROR, NULL, 0);\n\t\t\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\t/* Close the stream */\n\t\t\t{\n\t\t\t\tstruct http2_stream *rst_stream = _http2_find_stream(ctx, stream_id);\n\t\t\t\tif (rst_stream) {\n\t\t\t\t\trst_stream->state = HTTP2_STREAM_CLOSED;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase HTTP2_FRAME_GOAWAY:\n\t\t\tif (length < 8) {\n\t\t\t\tctx->status = HTTP2_ERR_PROTOCOL;\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tctx->status = HTTP2_ERR_EOF;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t/* Ignore unknown frames */\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Move remaining data to beginning of buffer */\n\t\tint frame_size = HTTP2_FRAME_HEADER_SIZE + length;\n\t\tif (ctx->read_buffer_len > frame_size) {\n\t\t\tmemmove(ctx->read_buffer, ctx->read_buffer + frame_size, ctx->read_buffer_len - frame_size);\n\t\t\tctx->read_buffer_len -= frame_size;\n\t\t} else {\n\t\t\tctx->read_buffer_len = 0;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/* Reference counting functions */\n\nstruct http2_ctx *http2_ctx_get(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn NULL;\n\t}\n\t__sync_add_and_fetch(&ctx->refcount, 1);\n\treturn ctx;\n}\n\nvoid http2_ctx_put(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn;\n\t}\n\n\tint refcnt = __sync_sub_and_fetch(&ctx->refcount, 1);\n\tif (refcnt > 0) {\n\t\treturn; /* Still has references */\n\t}\n\n\tif (refcnt < 0) {\n\t\tBUG(\"http2_ctx_put: negative reference count %d\", refcnt);\n\t\treturn;\n\t}\n\n\t/* Reference count reached zero, free the context */\n\tpthread_mutex_lock(&ctx->mutex);\n\n\t/* Free all streams - each stream will call http2_stream_put */\n\tstruct http2_stream *stream, *tmp;\n\tlist_for_each_entry_safe(stream, tmp, &ctx->streams, node)\n\t{\n\t\t_http2_remove_stream(stream, 1);\n\t}\n\n\thpack_free_context(&ctx->encoder);\n\thpack_free_context(&ctx->decoder);\n\n\t/* Free pending write buffer */\n\tif (ctx->pending_write_buffer) {\n\t\tfree(ctx->pending_write_buffer);\n\t}\n\n\tif (ctx->server) {\n\t\tfree(ctx->server);\n\t}\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\tpthread_mutex_destroy(&ctx->mutex);\n\n\tfree(ctx);\n}\n\nvoid http2_ctx_close(struct http2_ctx *ctx)\n{\n\tstruct list_head streams_to_free;\n\n\tif (!ctx) {\n\t\treturn;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\n\t/* Detach all streams from context */\n\tINIT_LIST_HEAD(&streams_to_free);\n\tlist_splice_init(&ctx->streams, &streams_to_free);\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\n\t/* Now free streams outside the lock - just break circular references */\n\tstruct http2_stream *stream, *tmp;\n\tlist_for_each_entry_safe(stream, tmp, &streams_to_free, node)\n\t{\n\t\t_http2_remove_stream(stream, 1);\n\t}\n\n\t/* release context reference held by caller */\n\thttp2_ctx_put(ctx);\n}\n\nstruct http2_stream *http2_stream_get(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn NULL;\n\t}\n\t__sync_add_and_fetch(&stream->refcount, 1);\n\treturn stream;\n}\n\nvoid http2_stream_put(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn;\n\t}\n\n\tint refcnt = __sync_sub_and_fetch(&stream->refcount, 1);\n\tif (refcnt > 0) {\n\t\treturn; /* Still has references */\n\t}\n\n\tif (refcnt < 0) {\n\t\tBUG(\"http2_stream_put: negative reference count %d\", refcnt);\n\t\treturn;\n\t}\n\n\tif (!list_empty(&stream->node)) {\n\t\t_http2_remove_stream(stream, 0);\n\t}\n\t_http2_free_headers(stream);\n\tfree(stream->body_buffer);\n\tfree(stream);\n}\n\nstatic void _http2_ctx_init_common(struct http2_ctx *ctx, const struct http2_ctx_init_params *params)\n{\n\tpthread_mutexattr_t attr;\n\tpthread_mutexattr_init(&attr);\n\tpthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);\n\tpthread_mutex_init(&ctx->mutex, &attr);\n\tpthread_mutexattr_destroy(&attr);\n\tctx->refcount = 1; /* Initial reference count */\n\tctx->is_client = params->is_client;\n\tctx->server = strdup(params->server ? params->server : \"\");\n\tctx->bio_read = params->bio_read;\n\tctx->bio_write = params->bio_write;\n\tctx->private_data = params->private_data;\n\tctx->next_stream_id = params->next_stream_id;\n\tctx->connection_window_size = HTTP2_DEFAULT_WINDOW_SIZE;\n\tctx->peer_max_frame_size = HTTP2_DEFAULT_MAX_FRAME_SIZE;\n\tctx->peer_initial_window_size = HTTP2_DEFAULT_WINDOW_SIZE;\n\tctx->active_streams = 0;\n\n\t/* Initialize settings with defaults or provided values */\n\tif (params->settings) {\n\t\tmemcpy(&ctx->settings, params->settings, sizeof(struct http2_settings));\n\t}\n\n\t/* Initialize I/O state */\n\tctx->want_read = 0;\n\tctx->want_write = 0;\n\tctx->pending_write_buffer = NULL;\n\tctx->pending_write_len = 0;\n\tctx->pending_write_capacity = 0;\n\n\thpack_init_context(&ctx->encoder);\n\thpack_init_context(&ctx->decoder);\n\n\thash_init(ctx->stream_map);\n\tINIT_LIST_HEAD(&ctx->streams);\n}\n\nstatic struct http2_ctx *http2_ctx_new(int is_client, const char *server, http2_bio_read_fn bio_read,\n\t\t\t\t\t\t\t\t\t   http2_bio_write_fn bio_write, void *private_data,\n\t\t\t\t\t\t\t\t\t   const struct http2_settings *settings)\n{\n\tstruct http2_ctx *ctx = zalloc(1, sizeof(*ctx));\n\tif (!ctx) {\n\t\treturn NULL;\n\t}\n\n\tstruct http2_ctx_init_params params = {.server = server,\n\t\t\t\t\t\t\t\t\t\t   .bio_read = bio_read,\n\t\t\t\t\t\t\t\t\t\t   .bio_write = bio_write,\n\t\t\t\t\t\t\t\t\t\t   .private_data = private_data,\n\t\t\t\t\t\t\t\t\t\t   .settings = settings,\n\t\t\t\t\t\t\t\t\t\t   .is_client = is_client,\n\t\t\t\t\t\t\t\t\t\t   .next_stream_id = is_client ? 1 : 2};\n\t_http2_ctx_init_common(ctx, &params);\n\n\tif (is_client) {\n\t\t/* Send connection preface - may return EAGAIN, will be buffered */\n\t\t_http2_send_frame(ctx, (const uint8_t *)HTTP2_CONNECTION_PREFACE, HTTP2_CONNECTION_PREFACE_LEN);\n\t}\n\n\t/* Send initial SETTINGS - may return EAGAIN, will be buffered */\n\t_http2_send_settings(ctx, 0);\n\n\treturn ctx;\n}\n\nstruct http2_ctx *http2_ctx_client_new(const char *server, http2_bio_read_fn bio_read, http2_bio_write_fn bio_write,\n\t\t\t\t\t\t\t\t\t   void *private_data, const struct http2_settings *settings)\n{\n\tif (!server || !bio_read || !bio_write) {\n\t\treturn NULL;\n\t}\n\treturn http2_ctx_new(1, server, bio_read, bio_write, private_data, settings);\n}\n\nstruct http2_ctx *http2_ctx_server_new(const char *server, http2_bio_read_fn bio_read, http2_bio_write_fn bio_write,\n\t\t\t\t\t\t\t\t\t   void *private_data, const struct http2_settings *settings)\n{\n\tif (!server || !bio_read || !bio_write) {\n\t\treturn NULL;\n\t}\n\treturn http2_ctx_new(0, server, bio_read, bio_write, private_data, settings);\n}\n\nint http2_ctx_handshake(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\n\tif (ctx->handshake_complete) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\treturn 1;\n\t}\n\n\t/* Try to flush any pending writes (e.g., connection preface, SETTINGS) */\n\tif (ctx->pending_write_len > 0) {\n\t\t/* Trigger flush by calling send_frame with empty data */\n\t\tuint8_t dummy = 0;\n\t\t_http2_send_frame(ctx, &dummy, 0);\n\t}\n\n\t/* Process incoming frames */\n\tint ret = _http2_process_frames(ctx);\n\n\t/* Handshake is complete after receiving SETTINGS */\n\tif (ctx->settings_received) {\n\t\tctx->handshake_complete = 1;\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\treturn 1;\n\t}\n\n\tif (ret == HTTP2_ERR_EAGAIN) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\treturn 0; /* In progress */\n\t}\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn ret;\n}\n\nstruct http2_stream *http2_ctx_accept_stream(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn NULL;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\n\t/* Try to flush any pending writes first */\n\tif (ctx->pending_write_len > 0) {\n\t\tuint8_t dummy = 0;\n\t\t_http2_send_frame(ctx, &dummy, 0);\n\t}\n\n\t/* Process frames to get new streams */\n\t_http2_process_frames(ctx);\n\n\t/* Find a stream that was initiated by peer */\n\tstruct http2_stream *stream;\n\tlist_for_each_entry(stream, &ctx->streams, node)\n\t{\n\t\tif ((ctx->is_client && (stream->stream_id % 2) == 0) || (!ctx->is_client && (stream->stream_id % 2) == 1)) {\n\t\t\tif (!stream->accepted && !list_empty(&stream->header_list.list) && !stream->end_stream_sent) {\n\t\t\t\tstream->accepted = 1;\n\t\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\t\tif (stream) {\n\t\t\t\t\t/* take ownership */\n\t\t\t\t\thttp2_stream_get(stream);\n\t\t\t\t}\n\t\t\t\treturn stream;\n\t\t\t}\n\t\t}\n\t}\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn NULL;\n}\n\nstatic int _http2_ctx_io_process(struct http2_ctx *ctx)\n{\n\t/* Try to flush any pending writes first */\n\tif (ctx->pending_write_len > 0) {\n\t\tuint8_t dummy = 0;\n\t\t_http2_send_frame(ctx, &dummy, 0);\n\t}\n\n\t/* Process frames */\n\treturn _http2_process_frames(ctx);\n}\n\nstatic int _http2_ctx_check_new_streams(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *count)\n{\n\tif (ctx->is_client || *count >= max_items) {\n\t\treturn 0;\n\t}\n\n\tstruct http2_stream *stream;\n\tint has_new_stream = 0;\n\n\tlist_for_each_entry(stream, &ctx->streams, node)\n\t{\n\t\t/* Server accepts odd stream IDs (client-initiated) */\n\t\t/* Stream is ready to accept when it has received headers */\n\t\tif ((stream->stream_id % 2) == 1 && !stream->accepted && !list_empty(&stream->header_list.list) &&\n\t\t\t!stream->end_stream_sent) {\n\t\t\thas_new_stream = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (has_new_stream) {\n\t\t/* Return server context item (stream = NULL) to indicate new connection */\n\t\titems[*count].stream = NULL;\n\t\titems[*count].readable = 1;\n\t\titems[*count].writable = 0;\n\t\t(*count)++;\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic void _http2_ctx_collect_ready_streams(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items,\n\t\t\t\t\t\t\t\t\t\t\t int *count, int check_writable)\n{\n\tstruct http2_stream *stream, *tmp;\n\tstruct list_head ready_list;\n\tINIT_LIST_HEAD(&ready_list);\n\n\tlist_for_each_entry_safe(stream, tmp, &ctx->streams, node)\n\t{\n\t\t/* Only return streams that have been accepted */\n\t\tif (!stream->accepted) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Stream is readable if:\n\t\t * 1. Has unread body data in buffer, OR\n\t\t * 2. Stream has ended (all data including headers received)\n\t\t */\n\t\tint has_body_data = stream->body_buffer_len > stream->body_read_offset;\n\t\tint stream_ended = (stream->end_stream_received || stream->state == HTTP2_STREAM_CLOSED) && !stream->end_stream_read_handled;\n\n\t\tint readable = has_body_data || stream_ended;\n\t\tint writable = stream->state == HTTP2_STREAM_OPEN || stream->state == HTTP2_STREAM_HALF_CLOSED_REMOTE;\n\n\t\tif (readable || (check_writable && writable)) {\n\t\t\tif (*count < max_items) {\n\t\t\t\titems[*count].stream = http2_stream_get(stream);\n\t\t\t\titems[*count].readable = readable;\n\t\t\t\titems[*count].writable = writable;\n\t\t\t\t(*count)++;\n\t\t\t\t/* Move to ready list */\n\t\t\t\tlist_move_tail(&stream->node, &ready_list);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Append ready list to the end of ctx->streams */\n\tlist_splice_tail(&ready_list, &ctx->streams);\n}\n\nstatic int _http2_ctx_poll(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *ret_count,\n\t\t\t\t\t\t   int check_writable)\n{\n\tpthread_mutex_lock(&ctx->mutex);\n\tif (ret_count) {\n\t\t*ret_count = 0;\n\t}\n\n\tint ret = _http2_ctx_io_process(ctx);\n\n\tif (items == NULL || max_items <= 0) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\treturn ret;\n\t}\n\n\t/* Note: We continue even if http2_process_frames returns error (like EOF),\n\t   because we might have received data that made streams readable.\n\t   We will return the error at the end if no streams are ready. */\n\n\tint count = 0;\n\n\t_http2_ctx_check_new_streams(ctx, items, max_items, &count);\n\t_http2_ctx_collect_ready_streams(ctx, items, max_items, &count, check_writable);\n\n\tif (ret_count) {\n\t\t*ret_count = count;\n\t}\n\n\t/* If we have an error, return it even if there ready items.\n\t   BUT we must release references collected in items before returning. */\n\tif (ret < 0 && ret != HTTP2_ERR_EAGAIN) {\n\t\tint i;\n\t\tfor (i = 0; i < count; i++) {\n\t\t\tif (items[i].stream) {\n\t\t\t\thttp2_stream_put(items[i].stream);\n\t\t\t\titems[i].stream = NULL;\n\t\t\t}\n\t\t}\n\t\tif (ret_count) {\n\t\t\t*ret_count = 0;\n\t\t}\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\treturn ret;\n\t}\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\n\tif (count > 0) {\n\t\treturn 0;\n\t}\n\n\tif (ctx->status < 0) {\n\t\treturn ctx->status;\n\t}\n\n\treturn 0;\n}\n\nint http2_ctx_poll(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *ret_count)\n{\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\n\tif (items != NULL && (max_items <= 0 || !ret_count)) {\n\t\treturn -1;\n\t}\n\treturn _http2_ctx_poll(ctx, items, max_items, ret_count, 1);\n}\n\nint http2_ctx_poll_readable(struct http2_ctx *ctx, struct http2_poll_item *items, int max_items, int *ret_count)\n{\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\n\tif (items != NULL && (max_items <= 0 || !ret_count)) {\n\t\treturn -1;\n\t}\n\treturn _http2_ctx_poll(ctx, items, max_items, ret_count, 0);\n}\n\nstruct http2_stream *http2_stream_new(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn NULL;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\n\tint stream_id = ctx->next_stream_id;\n\tctx->next_stream_id += 2;\n\n\tstruct http2_stream *stream = _http2_create_stream(ctx, stream_id);\n\tif (stream) {\n\t\t/* take ownership */\n\t\thttp2_stream_get(stream);\n\t}\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn stream;\n}\n\nstatic int http2_send_rst_stream(struct http2_ctx *ctx, uint32_t stream_id, uint32_t error_code)\n{\n\tuint8_t frame[HTTP2_FRAME_HEADER_SIZE + 4];\n\n\t_http2_write_frame_header(frame, 4, HTTP2_FRAME_RST_STREAM, 0, stream_id);\n\twrite_uint32(frame + HTTP2_FRAME_HEADER_SIZE, error_code);\n\n\treturn _http2_send_frame(ctx, frame, sizeof(frame));\n}\n\nvoid http2_stream_close(struct http2_stream *stream)\n{\n\tif (stream == NULL) {\n\t\treturn;\n\t}\n\n\thttp2_stream_get(stream); /* Ensure stream survives during close */\n\t/* Mark stream as closed */\n\tstream->state = HTTP2_STREAM_CLOSED;\n\tstream->ex_data = NULL;\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (ctx) {\n\t\tpthread_mutex_lock(&ctx->mutex);\n\t\t/* Send RST_STREAM to close the stream */\n\t\thttp2_send_rst_stream(ctx, stream->stream_id, 0); /* NO_ERROR */\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\n\t\t_http2_remove_stream(stream, 1);\n\t}\n\n\thttp2_stream_put(stream);\n\thttp2_stream_put(stream);\n}\n\nint http2_stream_get_id(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn -1;\n\t}\n\treturn stream->stream_id;\n}\n\nint http2_stream_set_request(struct http2_stream *stream, const char *method, const char *path, const char *scheme,\n\t\t\t\t\t\t\t const struct http2_header_pair *headers)\n{\n\tif (!stream || !method || !path) {\n\t\treturn -1;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\n\t/* Clear old headers */\n\t_http2_free_headers(stream);\n\n\t/* Add pseudo-headers */\n\t_http2_stream_add_header(stream, \":method\", method);\n\t_http2_stream_add_header(stream, \":path\", path);\n\t_http2_stream_add_header(stream, \":scheme\", scheme ? scheme : \"https\");\n\t_http2_stream_add_header(stream, \":authority\", ctx->server ? ctx->server : \"localhost\");\n\n\t/* Add additional headers from array */\n\tif (headers) {\n\t\tfor (int i = 0; headers[i].name != NULL; i++) {\n\t\t\tif (headers[i].value == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (headers[i].name == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t_http2_stream_add_header(stream, headers[i].name, headers[i].value);\n\t\t}\n\t}\n\n\t/* Send HEADERS frame with END_STREAM for GET/HEAD requests (no body) */\n\tint end_stream = (strcmp(method, \"GET\") == 0 || strcmp(method, \"HEAD\") == 0) ? 1 : 0;\n\tint ret = _http2_send_headers_frame(ctx, stream->stream_id, stream, end_stream, 1);\n\n\tif (end_stream) {\n\t\tstream->end_stream_sent = 1;\n\t}\n\n\tif (stream->state == HTTP2_STREAM_IDLE) {\n\t\tstream->state = HTTP2_STREAM_OPEN;\n\t}\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn ret;\n}\n\nint http2_stream_set_response(struct http2_stream *stream, int status, const struct http2_header_pair *headers,\n\t\t\t\t\t\t\t  int header_count)\n{\n\tif (!stream || status < 100 || status > 599 || header_count < 0 || (header_count > 0 && !headers)) {\n\t\treturn -1;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\n\t/* Clear old headers */\n\t_http2_free_headers(stream);\n\n\t/* Add :status pseudo-header */\n\tchar status_str[16];\n\tsnprintf(status_str, sizeof(status_str), \"%d\", status);\n\t_http2_stream_add_header(stream, \":status\", status_str);\n\n\t/* Add additional headers from array */\n\tif (headers && header_count > 0) {\n\t\tfor (int i = 0; i < header_count; i++) {\n\t\t\tif (headers[i].name == NULL || headers[i].value == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t_http2_stream_add_header(stream, headers[i].name, headers[i].value);\n\t\t}\n\t}\n\n\t/* Send HEADERS frame */\n\tint ret = _http2_send_headers_frame(ctx, stream->stream_id, stream, 0, 1);\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn ret;\n}\n\nconst char *http2_stream_get_method(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn NULL;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (!ctx) {\n\t\treturn NULL;\n\t}\n\tpthread_mutex_lock(&ctx->mutex);\n\n\tconst char *method = _http2_stream_get_header_value(stream, \":method\");\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn method;\n}\n\nconst char *http2_stream_get_path(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn NULL;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (!ctx) {\n\t\treturn NULL;\n\t}\n\tpthread_mutex_lock(&ctx->mutex);\n\n\tconst char *path = _http2_stream_get_header_value(stream, \":path\");\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn path;\n}\n\nint http2_stream_get_status(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn -1;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\tpthread_mutex_lock(&ctx->mutex);\n\n\tconst char *status_str = _http2_stream_get_header_value(stream, \":status\");\n\tint status = status_str ? atoi(status_str) : -1;\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn status;\n}\n\nconst char *http2_stream_get_header(struct http2_stream *stream, const char *name)\n{\n\tif (!stream || !name) {\n\t\treturn NULL;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (!ctx) {\n\t\treturn NULL;\n\t}\n\tpthread_mutex_lock(&ctx->mutex);\n\n\tconst char *value = _http2_stream_get_header_value(stream, name);\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn value;\n}\n\nint http2_stream_write_body(struct http2_stream *stream, const uint8_t *data, int len, int end_stream)\n{\n\tif (!stream || len <= 0 || !data) {\n\t\treturn -1;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (!ctx) {\n\t\treturn -1;\n\t}\n\tpthread_mutex_lock(&ctx->mutex);\n\n\tint total_sent = 0;\n\twhile (len > 0) {\n\t\t/* Check flow control */\n\t\tif (stream->window_size <= 0 || ctx->connection_window_size <= 0) {\n\t\t\tif (total_sent > 0) {\n\t\t\t\tbreak; /* Partial send */\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\terrno = EAGAIN;\n\t\t\treturn -1;\n\t\t}\n\t\tint to_send = len;\n\t\tif (to_send > stream->window_size) {\n\t\t\tto_send = stream->window_size;\n\t\t}\n\t\tif (to_send > ctx->connection_window_size) {\n\t\t\tto_send = ctx->connection_window_size;\n\t\t}\n\t\tif ((uint32_t)to_send > (uint32_t)ctx->peer_max_frame_size) {\n\t\t\tto_send = ctx->peer_max_frame_size;\n\t\t}\n\n\t\tif (to_send <= 0) {\n\t\t\tif (total_sent > 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\terrno = EAGAIN;\n\t\t\treturn -1;\n\t\t}\n\n\t\tint ret =\n\t\t\t_http2_send_data_frame(ctx, stream->stream_id, data + total_sent, to_send, end_stream && to_send == len);\n\t\tif (ret < 0) {\n\t\t\tif (total_sent > 0) {\n\t\t\t\tbreak; /* Partial send */\n\t\t\t}\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\treturn ret;\n\t\t}\n\t\ttotal_sent += ret;\n\t\tlen -= ret;\n\t\tstream->window_size -= ret;\n\t\tctx->connection_window_size -= ret;\n\n\t\tif (end_stream && len == 0) {\n\t\t\tstream->end_stream_sent = 1;\n\t\t\tif (stream->state == HTTP2_STREAM_OPEN) {\n\t\t\t\tstream->state = HTTP2_STREAM_HALF_CLOSED_LOCAL;\n\t\t\t} else if (stream->state == HTTP2_STREAM_HALF_CLOSED_REMOTE) {\n\t\t\t\tstream->state = HTTP2_STREAM_CLOSED;\n\t\t\t}\n\t\t}\n\t}\n\n\tpthread_mutex_unlock(&ctx->mutex);\n\treturn total_sent;\n}\n\n/* Helper function to decompress gzip/deflate data */\nstatic int http2_decompress_data(const uint8_t *compressed, int compressed_len, uint8_t **decompressed,\n\t\t\t\t\t\t\t\t int *decompressed_len, int is_gzip)\n{\n#ifdef WITH_ZLIB\n\tz_stream strm;\n\tint ret;\n\tint window_bits = is_gzip ? (15 + 16) : 15;        /* 15+16 for gzip, 15 for deflate */\n\tconst int MAX_DECOMPRESSED_SIZE = 1 * 1024 * 1024; /* 10MB limit to prevent DOS */\n\n\t/* Allocate initial output buffer */\n\tint out_size = safe_buffer_size(compressed_len, 4);\n\tif (out_size < 0 || out_size < 8192) {\n\t\tout_size = 8192;\n\t}\n\tif (out_size > MAX_DECOMPRESSED_SIZE) {\n\t\tout_size = MAX_DECOMPRESSED_SIZE;\n\t}\n\tuint8_t *out_buf = zalloc(1, out_size);\n\tif (!out_buf) {\n\t\treturn -1;\n\t}\n\n\t/* Initialize zlib */\n\tstrm.zalloc = Z_NULL;\n\tstrm.zfree = Z_NULL;\n\tstrm.opaque = Z_NULL;\n\tstrm.avail_in = compressed_len;\n\tstrm.next_in = (Bytef *)compressed;\n\n\tret = inflateInit2(&strm, window_bits);\n\tif (ret != Z_OK) {\n\t\tfree(out_buf);\n\t\treturn -1;\n\t}\n\n\t/* Decompress */\n\tint total_out = 0;\n\tdo {\n\t\t/* Ensure we have space in output buffer */\n\t\tif (total_out + 4096 > out_size) {\n\t\t\tint new_size = safe_buffer_size(out_size, 2);\n\t\t\tif (new_size < 0 || new_size > MAX_DECOMPRESSED_SIZE) {\n\t\t\t\tinflateEnd(&strm);\n\t\t\t\tfree(out_buf);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tuint8_t *new_buf = realloc(out_buf, new_size);\n\t\t\tif (!new_buf) {\n\t\t\t\tinflateEnd(&strm);\n\t\t\t\tfree(out_buf);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tout_buf = new_buf;\n\t\t\tout_size = new_size;\n\t\t}\n\n\t\tstrm.avail_out = out_size - total_out;\n\t\tstrm.next_out = out_buf + total_out;\n\n\t\tret = inflate(&strm, Z_NO_FLUSH);\n\t\tif (ret != Z_OK && ret != Z_STREAM_END) {\n\t\t\tinflateEnd(&strm);\n\t\t\tfree(out_buf);\n\t\t\treturn -1;\n\t\t}\n\n\t\ttotal_out = strm.total_out;\n\t} while (ret != Z_STREAM_END && strm.avail_in > 0);\n\n\tinflateEnd(&strm);\n\n\t*decompressed = out_buf;\n\t*decompressed_len = total_out;\n\treturn 0;\n#else\n\treturn -1;\n#endif\n}\n\nstatic int http2_try_decompress_body(struct http2_stream *stream)\n{\n\tif (!stream || stream->body_decompressed || stream->body_buffer_len == 0) {\n\t\treturn 0;\n\t}\n\n\t/* Only decompress when the stream is fully received or connection is closed */\n\tif (!stream->end_stream_received && stream->ctx && stream->ctx->status >= 0) {\n\t\treturn 0;\n\t}\n\n\tconst char *content_encoding = _http2_stream_get_header_value(stream, \"content-encoding\");\n\tint is_gzip = 0;\n\tint should_decompress = 0;\n\n\tif (content_encoding) {\n\t\tis_gzip = (strcasecmp(content_encoding, \"gzip\") == 0);\n\t\tint is_deflate = (strcasecmp(content_encoding, \"deflate\") == 0);\n\t\tshould_decompress = (is_gzip || is_deflate);\n\t} else if (stream->body_buffer_len > 2) {\n\t\t/* Fallback: check for gzip magic number (0x1f 0x8b) */\n\t\tif (stream->body_buffer[0] == 0x1f && stream->body_buffer[1] == 0x8b) {\n\t\t\tis_gzip = 1;\n\t\t\tshould_decompress = 1;\n\t\t}\n\t}\n\n\tif (should_decompress) {\n\t\tuint8_t *decompressed = NULL;\n\t\tint decompressed_len = 0;\n\n\t\tif (http2_decompress_data(stream->body_buffer, stream->body_buffer_len, &decompressed, &decompressed_len,\n\t\t\t\t\t\t\t\t  is_gzip) == 0) {\n\t\t\t/* Replace compressed buffer with decompressed data */\n\t\t\tfree(stream->body_buffer);\n\t\t\tstream->body_buffer = decompressed;\n\t\t\tstream->body_buffer_len = decompressed_len;\n\t\t\tstream->body_buffer_size = decompressed_len;\n\t\t\tstream->body_decompressed = 1;\n\t\t\treturn 1; /* Decompression successful */\n\t\t} else {\n\t\t\t/* Decompression failed, set an error flag or log */\n\t\t\t/* For now, leave body_decompressed = 0, and let read_body handle error */\n\t\t\treturn -1; /* Indicate failure */\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint http2_stream_read_body(struct http2_stream *stream, uint8_t *data, int len)\n{\n\tif (!stream || len <= 0 || !data) {\n\t\treturn -1;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (ctx) {\n\t\tpthread_mutex_lock(&ctx->mutex);\n\t}\n\n\t/* NOTE: We do NOT call http2_process_frames here!\n\t * The caller should use http2_ctx_poll to process frames for all streams.\n\t * This function only reads from the stream's buffer. */\n\n\t/* Try to decompress if needed */\n\tint decompress_ret = http2_try_decompress_body(stream);\n\tif (decompress_ret < 0) {\n\t\t/* Decompression failed, return error */\n\t\tif (ctx) {\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t}\n\t\terrno = EINVAL; /* Invalid data */\n\t\treturn -1;\n\t}\n\n\t/* If content is compressed but not yet decompressed (because stream not ended),\n\t   we must not return raw data. */\n\tconst char *content_encoding = _http2_stream_get_header_value(stream, \"content-encoding\");\n\tif (content_encoding && !stream->body_decompressed) {\n\t\t/* Check if it's a compression format we handle */\n\t\tif (strcasecmp(content_encoding, \"gzip\") == 0 || strcasecmp(content_encoding, \"deflate\") == 0) {\n\t\t\t/* If stream not ended and connection is healthy, return EAGAIN */\n\t\t\tif (!stream->end_stream_received && (!ctx || ctx->status >= 0)) {\n\t\t\t\tif (ctx) {\n\t\t\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\t\t}\n\t\t\t\terrno = EAGAIN;\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\t/* If stream ended but decompression failed earlier, error already returned */\n\t\t}\n\t}\n\n\tint available = stream->body_buffer_len - stream->body_read_offset;\n\tif (available <= 0) {\n\t\tif (ctx) {\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t}\n\n\t\t/* If stream ended or connection has error, return 0 (EOF) */\n\t\tif (stream->end_stream_received || stream->state == HTTP2_STREAM_CLOSED || (!ctx || ctx->status < 0)) {\n\t\t\tstream->end_stream_read_handled = 1;\n\t\t\treturn 0;\n\t\t}\n\n\t\t/* No data available yet, return EAGAIN */\n\t\terrno = EAGAIN;\n\t\treturn -1;\n\t}\n\n\tint to_read = available < len ? available : len;\n\tmemcpy(data, stream->body_buffer + stream->body_read_offset, to_read);\n\tstream->body_read_offset += to_read;\n\n\tif (ctx) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t}\n\treturn to_read;\n}\n\nint http2_stream_body_available(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn 0;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (ctx) {\n\t\tpthread_mutex_lock(&ctx->mutex);\n\t}\n\n\t/* Try to decompress if needed */\n\thttp2_try_decompress_body(stream);\n\n\t/* If content is compressed but not yet decompressed, pretend no data available */\n\tconst char *content_encoding = _http2_stream_get_header_value(stream, \"content-encoding\");\n\tif (content_encoding && !stream->body_decompressed) {\n\t\tif (strcasecmp(content_encoding, \"gzip\") == 0 || strcasecmp(content_encoding, \"deflate\") == 0) {\n\t\t\tif (!stream->end_stream_received && (!ctx || ctx->status >= 0)) {\n\t\t\t\tif (ctx) {\n\t\t\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tint available = stream->body_buffer_len - stream->body_read_offset;\n\n\tif (ctx) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t}\n\treturn available > 0 ? 1 : 0;\n}\n\nint http2_stream_is_end(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn 1;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (ctx) {\n\t\tpthread_mutex_lock(&ctx->mutex);\n\t}\n\n\t/* Try to decompress if needed - this might change body_buffer_len */\n\thttp2_try_decompress_body(stream);\n\n\tint is_end = (stream->end_stream_received || stream->state == HTTP2_STREAM_CLOSED) && (stream->body_read_offset >= stream->body_buffer_len);\n\n\t/* If connection is closed/error, and we have read all buffered data, consider stream ended */\n\tif (!is_end && (!ctx || ctx->status < 0) && stream->body_read_offset >= stream->body_buffer_len) {\n\t\tis_end = 1;\n\t}\n\n\tif (ctx) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t}\n\treturn is_end;\n}\n\nvoid http2_stream_set_ex_data(struct http2_stream *stream, void *data)\n{\n\tif (!stream) {\n\t\treturn;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (ctx) {\n\t\tpthread_mutex_lock(&ctx->mutex);\n\t}\n\n\tstream->ex_data = data;\n\n\tif (ctx) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t}\n}\n\nvoid *http2_stream_get_ex_data(struct http2_stream *stream)\n{\n\tif (!stream) {\n\t\treturn NULL;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (ctx) {\n\t\tpthread_mutex_lock(&ctx->mutex);\n\t}\n\n\tvoid *data = stream->ex_data;\n\n\tif (ctx) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t}\n\treturn data;\n}\n\nint http2_ctx_want_read(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn 0;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\tint want_read = ctx->want_read;\n\tpthread_mutex_unlock(&ctx->mutex);\n\n\treturn want_read;\n}\n\nint http2_ctx_want_write(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn 0;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\tint want_write = ctx->want_write;\n\tpthread_mutex_unlock(&ctx->mutex);\n\n\treturn want_write;\n}\n\nint http2_ctx_is_closed(struct http2_ctx *ctx)\n{\n\tif (!ctx) {\n\t\treturn 1;\n\t}\n\n\tpthread_mutex_lock(&ctx->mutex);\n\tint is_closed = (ctx->status < 0);\n\tpthread_mutex_unlock(&ctx->mutex);\n\n\treturn is_closed;\n}\n\nchar *http2_stream_get_query_param(struct http2_stream *stream, const char *name)\n{\n\tconst char *path = NULL;\n\tconst char *q = NULL;\n\tconst char *val_start = NULL;\n\tint name_len = 0;\n\tchar *ret = NULL;\n\tconst int MAX_VAL_LEN = 4096; /* Limit to prevent excessive memory use */\n\n\tif (stream == NULL || name == NULL) {\n\t\treturn NULL;\n\t}\n\n\tstruct http2_ctx *ctx = stream->ctx;\n\tif (ctx) {\n\t\tpthread_mutex_lock(&ctx->mutex);\n\t}\n\n\tpath = _http2_stream_get_header_value(stream, \":path\");\n\tif (path == NULL) {\n\t\tif (ctx) {\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tq = strstr(path, \"?\");\n\tif (q == NULL) {\n\t\tif (ctx) {\n\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t}\n\t\treturn NULL;\n\t}\n\tq++;\n\n\tname_len = strlen(name);\n\n\twhile (*q) {\n\t\tif (strncmp(q, name, name_len) == 0 && q[name_len] == '=') {\n\t\t\tval_start = q + name_len + 1;\n\t\t\tbreak;\n\t\t}\n\t\tq = strchr(q, '&');\n\t\tif (q == NULL) {\n\t\t\tbreak;\n\t\t}\n\t\tq++;\n\t}\n\n\tif (val_start) {\n\t\tconst char *end = strchr(val_start, '&');\n\t\tsize_t val_len = end ? (size_t)(end - val_start) : strlen(val_start);\n\t\tif (val_len > (size_t)MAX_VAL_LEN) {\n\t\t\tif (ctx) {\n\t\t\t\tpthread_mutex_unlock(&ctx->mutex);\n\t\t\t}\n\t\t\treturn NULL; /* Too long, reject */\n\t\t}\n\t\tchar *encoded_val = strndup(val_start, val_len);\n\t\tif (encoded_val) {\n\t\t\tsize_t decoded_len = val_len * 2 + 1; // Estimate\n\t\t\tchar *decoded_val = zalloc(1, decoded_len);\n\t\t\tif (decoded_val) {\n\t\t\t\tint ret_len = urldecode(decoded_val, decoded_len, encoded_val);\n\t\t\t\tif (ret_len >= 0 && (size_t)ret_len < decoded_len) {\n\t\t\t\t\tret = decoded_val;\n\t\t\t\t} else {\n\t\t\t\t\tfree(decoded_val);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfree(encoded_val);\n\t\t}\n\t}\n\n\tif (ctx) {\n\t\tpthread_mutex_unlock(&ctx->mutex);\n\t}\n\n\treturn ret;\n}\n"
  },
  {
    "path": "src/http_parse/http3_parse.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"http3_parse.h\"\n#include \"http_parse.h\"\n#include \"qpack.h\"\n\n#include <stdio.h>\n\n#define HTTP3_HEADER_FRAME 1\n#define HTTP3_DATA_FRAME 0\n\nstatic int _quicvarint_encode(uint64_t value, uint8_t *buffer, int buffer_size)\n{\n\tif (value <= 63) {\n\t\tif (buffer_size < 1) {\n\t\t\treturn -1;\n\t\t}\n\t\tbuffer[0] = (uint8_t)value;\n\t\treturn 1;\n\t} else if (value <= 16383) {\n\t\tif (buffer_size < 2) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tbuffer[0] = (uint8_t)((value >> 8) | 0x40);\n\t\tbuffer[1] = (uint8_t)value;\n\t\treturn 2;\n\t} else if (value <= 1073741823) {\n\t\tif (buffer_size < 4) {\n\t\t\treturn -1;\n\t\t}\n\t\tbuffer[0] = (uint8_t)((value >> 24) | 0x80);\n\t\tbuffer[1] = (uint8_t)(value >> 16);\n\t\tbuffer[2] = (uint8_t)(value >> 8);\n\t\tbuffer[3] = (uint8_t)value;\n\t\treturn 4;\n\t} else {\n\t\tif (buffer_size < 8) {\n\t\t\treturn -1;\n\t\t}\n\t\tbuffer[0] = (uint8_t)((value >> 56) | 0xC0);\n\t\tbuffer[1] = (uint8_t)(value >> 48);\n\t\tbuffer[2] = (uint8_t)(value >> 40);\n\t\tbuffer[3] = (uint8_t)(value >> 32);\n\t\tbuffer[4] = (uint8_t)(value >> 24);\n\t\tbuffer[5] = (uint8_t)(value >> 16);\n\t\tbuffer[6] = (uint8_t)(value >> 8);\n\t\tbuffer[7] = (uint8_t)value;\n\t\treturn 8;\n\t}\n}\n\nstatic int _qpack_build_header(const char *name, const char *value, uint8_t *buffer, int buffer_size)\n{\n\tint offset = 0;\n\tint offset_ret = 0;\n\tint name_len = strlen(name);\n\tint value_len = strlen(value);\n\n\tif (buffer_size - offset < 2) {\n\t\treturn -1;\n\t}\n\n\tif (name_len < 7) {\n\t\tbuffer[offset++] = 0x20 | name_len;\n\t} else {\n\t\tbuffer[offset++] = 0x20 | 7;\n\t\tbuffer[offset++] = name_len - 7;\n\t}\n\n\tif (buffer_size - offset < name_len) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(buffer + offset, name, name_len);\n\toffset += name_len;\n\n\tif (buffer_size - offset < 2) {\n\t\treturn -1;\n\t}\n\n\toffset_ret = _quicvarint_encode(value_len, buffer + offset, buffer_size - offset);\n\tif (offset_ret < 0) {\n\t\treturn -1;\n\t}\n\toffset += offset_ret;\n\n\tif (buffer_size - offset < value_len) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(buffer + offset, value, value_len);\n\toffset += value_len;\n\n\treturn offset;\n}\n\nstatic int _http3_build_headers_payload(struct http_head *http_head, uint8_t *buffer, int buffer_len)\n{\n\tint offset = 0;\n\tint offset_ret = 0;\n\tstruct http_head_fields *fields = NULL;\n\tstruct http_params *params = NULL;\n\n\t/* Insert count and delta base */\n\tif (buffer_len - offset < 2) {\n\t\treturn -1;\n\t}\n\n\tbuffer[offset++] = 0;\n\tbuffer[offset++] = 0;\n\n\tif (http_head->head_type == HTTP_HEAD_REQUEST) {\n\t\tchar request_path[1024];\n\t\tchar *request_path_buffer = request_path;\n\t\tint request_path_buffer_len = sizeof(request_path);\n\n\t\tint request_path_len = snprintf(request_path, sizeof(request_path), \"%s\", http_head->url);\n\t\tif (request_path_len < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\trequest_path_buffer += request_path_len;\n\t\trequest_path_buffer_len -= request_path_len;\n\n\t\tint count = 0;\n\t\tlist_for_each_entry(params, &http_head->params.list, list)\n\t\t{\n\t\t\tif (count == 0) {\n\t\t\t\trequest_path_len =\n\t\t\t\t\tsnprintf(request_path_buffer, request_path_buffer_len, \"?%s=%s\", params->name, params->value);\n\t\t\t} else {\n\t\t\t\trequest_path_len =\n\t\t\t\t\tsnprintf(request_path_buffer, request_path_buffer_len, \"&%s=%s\", params->name, params->value);\n\t\t\t}\n\n\t\t\tcount++;\n\t\t\trequest_path_buffer += request_path_len;\n\t\t\trequest_path_buffer_len -= request_path_len;\n\n\t\t\tif (request_path_buffer_len < 2) {\n\t\t\t\treturn -3;\n\t\t\t}\n\t\t}\n\n\t\toffset_ret =\n\t\t\t_qpack_build_header(\":method\", http_method_str(http_head->method), buffer + offset, buffer_len - offset);\n\t\tif (offset_ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\toffset += offset_ret;\n\n\t\toffset_ret = _qpack_build_header(\":path\", request_path, buffer + offset, buffer_len - offset);\n\t\tif (offset_ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\toffset += offset_ret;\n\t\toffset_ret = _qpack_build_header(\":scheme\", \"https\", buffer + offset, buffer_len - offset);\n\t\tif (offset_ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\toffset += offset_ret;\n\t} else if (http_head->head_type == HTTP_HEAD_RESPONSE) {\n\t\tchar status_str[12];\n\t\tsnprintf(status_str, sizeof(status_str), \"%d\", http_head->code);\n\t\toffset_ret = _qpack_build_header(\":status\", status_str, buffer + offset, buffer_len - offset);\n\t\tif (offset_ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\toffset += offset_ret;\n\t}\n\n\tlist_for_each_entry(fields, &http_head->field_head.list, list)\n\t{\n\t\toffset_ret = _qpack_build_header(fields->name, fields->value, buffer + offset, buffer_len - offset);\n\t\tif (offset_ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\toffset += offset_ret;\n\t}\n\n\tif (http_head->data_len > 0 && http_head->data) {\n\t\tchar len_str[12];\n\t\tsnprintf(len_str, sizeof(len_str), \"%d\", http_head->data_len);\n\t\toffset_ret = _qpack_build_header(\"content-length\", len_str, buffer + offset, buffer_len - offset);\n\t\tif (offset_ret < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\toffset += offset_ret;\n\t}\n\treturn offset;\n}\n\nstatic int http3_build_body_payload(const uint8_t *body, int body_len, uint8_t *buffer, int buffer_len)\n{\n\tint offset = 0;\n\tint offset_ret = 0;\n\n\toffset_ret = _quicvarint_encode(body_len, buffer + offset, buffer_len - offset);\n\tif (offset_ret < 0) {\n\t\treturn -1;\n\t}\n\toffset += offset_ret;\n\n\tif (buffer_len - offset < body_len) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(buffer + offset, body, body_len);\n\toffset += body_len;\n\n\treturn offset;\n}\n\nstatic int _quicvarint_decode(const uint8_t *buffer, int buffer_len, uint64_t *value)\n{\n\tif ((buffer[0] & 0xC0) == 0x00) {\n\t\tif (buffer_len < 1) {\n\t\t\treturn -1;\n\t\t}\n\n\t\t*value = buffer[0];\n\t\treturn 1;\n\t} else if ((buffer[0] & 0xC0) == 0x40) {\n\t\tif (buffer_len < 2) {\n\t\t\treturn -1;\n\t\t}\n\t\t*value = ((uint64_t)(buffer[0] & 0x3F) << 8) | buffer[1];\n\t\treturn 2;\n\t} else if ((buffer[0] & 0xC0) == 0x80) {\n\t\tif (buffer_len < 4) {\n\t\t\treturn -1;\n\t\t}\n\t\t*value =\n\t\t\t((uint64_t)(buffer[0] & 0x3F) << 24) | ((uint64_t)buffer[1] << 16) | ((uint64_t)buffer[2] << 8) | buffer[3];\n\t\treturn 4;\n\t} else {\n\t\tif (buffer_len < 8) {\n\t\t\treturn -1;\n\t\t}\n\t\t*value = ((uint64_t)(buffer[0] & 0x3F) << 56) | ((uint64_t)buffer[1] << 48) | ((uint64_t)buffer[2] << 40) |\n\t\t\t\t ((uint64_t)buffer[3] << 32) | ((uint64_t)buffer[4] << 24) | ((uint64_t)buffer[5] << 16) |\n\t\t\t\t ((uint64_t)buffer[6] << 8) | buffer[7];\n\t\treturn 8;\n\t}\n}\n\nstatic int _quic_read_varint(const uint8_t *buffer, int buffer_len, uint64_t *value, int n)\n{\n\tuint64_t i;\n\tif (n < 1 || n > 8) {\n\t\treturn -2;\n\t}\n\tif (buffer_len == 0) {\n\t\treturn -1;\n\t}\n\n\tconst uint8_t *p = buffer;\n\ti = *p;\n\tif (n < 8) {\n\t\ti &= (1 << (uint64_t)n) - 1;\n\t}\n\n\tif (i < (uint64_t)(1 << (uint64_t)n) - 1) {\n\t\t*value = i;\n\t\treturn 1;\n\t}\n\n\tp++;\n\tuint64_t m = 0;\n\n\twhile (p < buffer + buffer_len) {\n\t\tuint8_t b = *p;\n\t\ti += (uint64_t)(b & 127) << m;\n\t\tif ((b & 128) == 0) {\n\t\t\t*value = i;\n\t\t\treturn p - buffer + 1;\n\t\t}\n\t\tm += 7;\n\t\tif (m >= 63) {\n\t\t\treturn -1;\n\t\t}\n\t\tp++;\n\t}\n\n\treturn -1;\n}\n\nstatic int _quic_read_string(const uint8_t *buffer, int buffer_len, char *str, int max_str_len, size_t *str_len, int n,\n\t\t\t\t\t\t\t int huffman)\n{\n\tuint64_t len = 0;\n\tint offset = 0;\n\tint offset_ret = 0;\n\n\toffset_ret = _quic_read_varint(buffer, buffer_len, &len, n);\n\tif (offset_ret < 0) {\n\t\treturn -1;\n\t}\n\toffset += offset_ret;\n\n\tif ((uint64_t)(buffer_len - offset) < len) {\n\t\treturn -1;\n\t}\n\n\tif ((uint64_t)max_str_len < len) {\n\t\treturn -3;\n\t}\n\n\tif (huffman) {\n\t\tsize_t char_len = 0;\n\t\tif (qpack_huffman_decode(buffer + offset, buffer + offset + len, (uint8_t *)str, max_str_len, &char_len) < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tstr[char_len] = '\\0';\n\t\t*str_len = char_len;\n\t} else {\n\t\tmemcpy(str, buffer + offset, len);\n\t\tstr[len] = '\\0';\n\t\t*str_len = len;\n\t}\n\n\treturn offset + len;\n}\n\nstatic int _http3_parse_headers_payload(struct http_head *http_head, const uint8_t *data, int data_len)\n{\n\tint offset = 0;\n\tint offset_ret = 0;\n\tint insert_count = 0;\n\tint delta_base = 0;\n\tstruct qpack_header_field *header = NULL;\n\tconst char *name = NULL;\n\tconst char *value = NULL;\n\tuint64_t index = -1;\n\tint use_huffman = 0;\n\tuint8_t b = 0;\n\tsize_t str_len = 0;\n\tint buffer_left_len = 0;\n\n\tif (data_len < 2) {\n\t\treturn -1;\n\t}\n\n\tinsert_count = data[0];\n\tdelta_base = data[1];\n\n\tif (insert_count != 0 || delta_base != 0) {\n\t\treturn -2;\n\t}\n\n\toffset += 2;\n\n\twhile (offset < data_len) {\n\t\tindex = -1;\n\t\tuse_huffman = 0;\n\t\tname = NULL;\n\t\tvalue = NULL;\n\n\t\tchar *buffer_name = NULL;\n\t\tchar *buffer_value = NULL;\n\n\t\tstr_len = 0;\n\t\tb = data[offset];\n\n\t\tif (b & 0x80) {\n\t\t\t/* indexed header*/\n\t\t\tif ((b & 0x40) == 0) {\n\t\t\t\treturn -2;\n\t\t\t}\n\n\t\t\toffset_ret = _quic_read_varint(data + offset, data_len - offset, &index, 6);\n\t\t\tif (offset_ret < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\toffset += offset_ret;\n\n\t\t\theader = qpack_get_static_header_field(index);\n\t\t\tif (header == NULL) {\n\t\t\t\treturn -2;\n\t\t\t}\n\n\t\t\tname = header->name;\n\t\t\tvalue = header->value;\n\t\t} else if (b & 0x40) {\n\t\t\t/* literal header with indexing */\n\t\t\tif ((b & 0x10) == 0) {\n\t\t\t\treturn -2;\n\t\t\t}\n\t\t\toffset_ret = _quic_read_varint(data + offset, data_len - offset, &index, 4);\n\t\t\tif (offset_ret < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\toffset += offset_ret;\n\n\t\t\theader = qpack_get_static_header_field(index);\n\t\t\tif (header == NULL) {\n\t\t\t\treturn -2;\n\t\t\t}\n\n\t\t\tname = header->name;\n\n\t\t\tb = data[offset];\n\t\t\tif ((b & 0x80) > 0) {\n\t\t\t\tuse_huffman = 1;\n\t\t\t}\n\n\t\t\tbuffer_value = (char *)_http_head_buffer_get_end(http_head);\n\t\t\tbuffer_left_len = _http_head_buffer_left_len(http_head);\n\n\t\t\toffset_ret = _quic_read_string(data + offset, data_len - offset, buffer_value, buffer_left_len - 1,\n\t\t\t\t\t\t\t\t\t\t   &str_len, 7, use_huffman);\n\t\t\tif (offset_ret < 0) {\n\t\t\t\treturn offset_ret;\n\t\t\t}\n\n\t\t\toffset += offset_ret;\n\t\t\tbuffer_value[str_len] = '\\0';\n\t\t\tif (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) {\n\t\t\t\treturn -3;\n\t\t\t}\n\t\t\tvalue = buffer_value;\n\t\t} else if (b & 0x20) {\n\t\t\t/* literal header without indexing */\n\t\t\tb = data[offset];\n\t\t\tif ((b & 0x8) > 0) {\n\t\t\t\tuse_huffman = 1;\n\t\t\t}\n\n\t\t\tbuffer_name = (char *)_http_head_buffer_get_end(http_head);\n\t\t\tbuffer_left_len = _http_head_buffer_left_len(http_head);\n\n\t\t\toffset_ret = _quic_read_string(data + offset, data_len - offset, buffer_name, buffer_left_len - 1, &str_len,\n\t\t\t\t\t\t\t\t\t\t   3, use_huffman);\n\t\t\tif (offset_ret < 0) {\n\t\t\t\treturn offset_ret;\n\t\t\t}\n\t\t\toffset += offset_ret;\n\t\t\tbuffer_name[str_len] = '\\0';\n\t\t\tif (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) {\n\t\t\t\treturn -3;\n\t\t\t}\n\t\t\tname = buffer_name;\n\n\t\t\tb = data[offset];\n\t\t\tif ((b & 0x80) > 0) {\n\t\t\t\tuse_huffman = 1;\n\t\t\t}\n\n\t\t\tbuffer_value = (char *)_http_head_buffer_get_end(http_head);\n\t\t\tbuffer_left_len = _http_head_buffer_left_len(http_head);\n\t\t\toffset_ret = _quic_read_string(data + offset, data_len - offset, buffer_value, buffer_left_len - 1,\n\t\t\t\t\t\t\t\t\t\t   &str_len, 7, use_huffman);\n\t\t\tif (offset_ret < 0) {\n\t\t\t\treturn offset_ret;\n\t\t\t}\n\t\t\toffset += offset_ret;\n\t\t\tbuffer_value[str_len] = '\\0';\n\t\t\tif (_http_head_buffer_append(http_head, NULL, str_len + 1) == NULL) {\n\t\t\t\treturn -3;\n\t\t\t}\n\t\t\tvalue = buffer_value;\n\t\t} else {\n\t\t\treturn -2;\n\t\t}\n\n\t\tif (http_head_add_fields(http_head, name, value) != 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _http_head_setup_http3_0_httpcode(struct http_head *http_head)\n{\n\tconst char *status = NULL;\n\tint status_code = 0;\n\tconst char *method = NULL;\n\tconst char *url = NULL;\n\n\tmethod = http_head_get_fields_value(http_head, \":method\");\n\tif (method) {\n\t\thttp_head->method = _http_method_parse(method);\n\t\tif (http_head->method == HTTP_METHOD_INVALID) {\n\t\t\treturn -1;\n\t\t}\n\n\t\turl = http_head_get_fields_value(http_head, \":path\");\n\t\tif (url == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\thttp_head->url = url;\n\t\thttp_head->head_type = HTTP_HEAD_REQUEST;\n\n\t\tif (_http_head_parse_params(http_head, (char *)url, strlen(url) + 1) != 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tstatus = http_head_get_fields_value(http_head, \":status\");\n\tif (status == NULL) {\n\t\treturn 0;\n\t}\n\n\thttp_head->head_type = HTTP_HEAD_RESPONSE;\n\n\tstatus_code = atoi(status);\n\tif (status_code < 100 || status_code > 999) {\n\t\treturn -1;\n\t}\n\n\thttp_head->code = status_code;\n\tif (status_code == 200) {\n\t\treturn 0;\n\t}\n\n\treturn 0;\n}\n\nint http_head_parse_http3_0(struct http_head *http_head, const uint8_t *data, int data_len)\n{\n\tuint64_t frame_type = 0;\n\tuint64_t frame_len = 0;\n\tint offset = 0;\n\tint offset_ret = 0;\n\n\thttp_head->data_len = 0;\n\twhile (offset < data_len) {\n\t\toffset_ret = _quicvarint_decode((uint8_t *)data + offset, data_len - offset, &frame_type);\n\t\tif (offset_ret < 0) {\n\t\t\treturn offset_ret;\n\t\t}\n\t\toffset += offset_ret;\n\n\t\toffset_ret = _quicvarint_decode((uint8_t *)data + offset, data_len - offset, &frame_len);\n\t\tif (offset_ret < 0) {\n\t\t\treturn offset_ret;\n\t\t}\n\t\toffset += offset_ret;\n\n\t\tif (offset >= http_head->buff_size) {\n\t\t\treturn -3;\n\t\t}\n\n\t\tif ((uint64_t)(data_len - offset) < frame_len) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (frame_type == HTTP3_HEADER_FRAME) {\n\t\t\tint header_len = 0;\n\t\t\theader_len = _http3_parse_headers_payload(http_head, data + offset, frame_len);\n\t\t\tif (header_len < 0) {\n\t\t\t\treturn header_len;\n\t\t\t}\n\n\t\t\tif (_http_head_setup_http3_0_httpcode(http_head) != 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t} else if (frame_type == HTTP3_DATA_FRAME) {\n\t\t\tif (http_head->code != 200 && http_head->head_type == HTTP_HEAD_RESPONSE) {\n\t\t\t\tif (frame_len > (uint64_t)(http_head->buff_size - http_head->buff_len)) {\n\t\t\t\t\thttp_head->code_msg = \"Unknow Error\";\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tmemcpy(http_head->buff + http_head->buff_len, data + offset, frame_len);\n\t\t\t\thttp_head->code_msg = (const char *)(http_head->buff + http_head->buff_len);\n\t\t\t\thttp_head->buff_len += frame_len;\n\t\t\t} else if (frame_len > 0) {\n\t\t\t\tif (http_head->data == NULL) {\n\t\t\t\t\thttp_head->data = _http_head_buffer_get_end(http_head);\n\t\t\t\t}\n\n\t\t\t\tif (_http_head_buffer_append(http_head, data + offset, frame_len) == NULL) {\n\t\t\t\t\thttp_head->code_msg = \"Receive Buffer Insufficient\";\n\t\t\t\t\thttp_head->code = 500;\n\t\t\t\t\thttp_head->data_len = 0;\n\t\t\t\t\thttp_head->buff_len = 0;\n\t\t\t\t\treturn -3;\n\t\t\t\t}\n\t\t\t\t/* Check buffer space before memcpy */\n\t\t\t\tif ((uint64_t)http_head->buff_len + frame_len > (uint64_t)http_head->buff_size) {\n\t\t\t\t\thttp_head->code_msg = \"Receive Buffer Insufficient\";\n\t\t\t\t\thttp_head->code = 500;\n\t\t\t\t\thttp_head->data_len = 0;\n\t\t\t\t\thttp_head->buff_len = 0;\n\t\t\t\t\treturn -3;\n\t\t\t\t}\n\t\t\t\tmemcpy(http_head->buff + http_head->buff_len, data + offset, frame_len);\n\t\t\t\thttp_head->data_len += frame_len;\n\t\t\t}\n\t\t} else {\n\t\t\t/* skip unknown frame. e.g. GREASE  */\n\t\t\toffset += frame_len;\n\t\t\tcontinue;\n\t\t}\n\t\toffset += frame_len;\n\t}\n\n\tif (offset >= http_head->buff_size) {\n\t\treturn -3;\n\t}\n\n\thttp_head->version = \"HTTP/3.0\";\n\n\treturn offset;\n}\n\nint http_head_serialize_http3_0(struct http_head *http_head, uint8_t *buffer, int buffer_len)\n{\n\tint offset = 0;\n\tint offset_ret = 0;\n\tuint8_t *header_data = NULL;\n\tint header_data_size = 1024;\n\tint header_data_len = 0;\n\tint result = -1;\n\n\theader_data = malloc(header_data_size);\n\tif (!header_data) {\n\t\tgoto cleanup;\n\t}\n\n\t/* serialize header frame. */\n\theader_data_len = _http3_build_headers_payload(http_head, header_data, header_data_size);\n\tif (header_data_len < 0) {\n\t\tgoto cleanup;\n\t}\n\n\t/* If header_data_len > header_data_size, realloc */\n\tif (header_data_len > header_data_size) {\n\t\tuint8_t *new_header_data = realloc(header_data, header_data_len);\n\t\tif (!new_header_data) {\n\t\t\tgoto cleanup;\n\t\t}\n\t\theader_data = new_header_data;\n\t\theader_data_size = header_data_len;\n\t\theader_data_len = _http3_build_headers_payload(http_head, header_data, header_data_size);\n\t\tif (header_data_len < 0) {\n\t\t\tgoto cleanup;\n\t\t}\n\t}\n\n\t/* Frame Type: Header*/\n\toffset_ret = _quicvarint_encode(HTTP3_HEADER_FRAME, buffer + offset, buffer_len - offset);\n\tif (offset_ret < 0) {\n\t\tgoto cleanup;\n\t}\n\toffset += offset_ret;\n\n\t/* Header Frame Length */\n\toffset_ret = _quicvarint_encode(header_data_len, buffer + offset, buffer_len - offset);\n\tif (offset_ret < 0) {\n\t\tgoto cleanup;\n\t}\n\toffset += offset_ret;\n\n\tif (buffer_len - offset < header_data_len) {\n\t\tgoto cleanup;\n\t}\n\tmemcpy(buffer + offset, header_data, header_data_len);\n\toffset += header_data_len;\n\n\t/* Frame Type: Data */\n\tif (http_head->data_len > 0 && http_head->data) {\n\t\t/* Data Frame Length */\n\t\toffset_ret = _quicvarint_encode(HTTP3_DATA_FRAME, buffer + offset, buffer_len - offset);\n\t\tif (offset_ret < 0) {\n\t\t\tgoto cleanup;\n\t\t}\n\t\toffset += offset_ret;\n\n\t\toffset_ret =\n\t\t\thttp3_build_body_payload(http_head->data, http_head->data_len, buffer + offset, buffer_len - offset);\n\t\tif (offset_ret < 0) {\n\t\t\tgoto cleanup;\n\t\t}\n\t\toffset += offset_ret;\n\t}\n\n\tresult = offset;\n\ncleanup:\n\tif (header_data) {\n\t\tfree(header_data);\n\t}\n\treturn result;\n}"
  },
  {
    "path": "src/http_parse/http3_parse.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _HTTP_PARSE_HTTP3_H_\n#define _HTTP_PARSE_HTTP3_H_\n\n#include \"http_parse.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nint http_head_parse_http3_0(struct http_head *http_head, const uint8_t *data, int data_len);\n\nint http_head_serialize_http3_0(struct http_head *http_head, uint8_t *buffer, int buffer_len);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/http_parse/http_parse.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/http_parse.h\"\n#include \"smartdns/util.h\"\n#include \"http1_parse.h\"\n#include \"http3_parse.h\"\n#include \"http_parse.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\n/*\n * Returns:\n *  >=0  - success http data len\n *  -1   - Incomplete request\n *  -2   - parse failed\n */\nstruct http_head *http_head_init(int buffsize, HTTP_VERSION version)\n{\n\tstruct http_head *http_head = NULL;\n\tunsigned char *buffer = NULL;\n\n\thttp_head = zalloc(1, sizeof(*http_head));\n\tif (http_head == NULL) {\n\t\tgoto errout;\n\t}\n\tINIT_LIST_HEAD(&http_head->field_head.list);\n\thash_init(http_head->field_map);\n\tINIT_LIST_HEAD(&http_head->params.list);\n\thash_init(http_head->params_map);\n\n\tbuffer = zalloc(1, buffsize);\n\tif (buffer == NULL) {\n\t\tgoto errout;\n\t}\n\n\thttp_head->buff = buffer;\n\thttp_head->buff_size = buffsize;\n\thttp_head->http_version = version;\n\n\treturn http_head;\n\nerrout:\n\tif (buffer) {\n\t\tfree(buffer);\n\t}\n\n\tif (http_head) {\n\t\tfree(http_head);\n\t}\n\n\treturn NULL;\n}\n\nstruct http_head_fields *http_head_first_fields(struct http_head *http_head)\n{\n\tstruct http_head_fields *first = NULL;\n\tfirst = list_first_entry(&http_head->field_head.list, struct http_head_fields, list);\n\n\tif (first->name == NULL && first->value == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn first;\n}\n\nconst char *http_head_get_fields_value(struct http_head *http_head, const char *name)\n{\n\tuint32_t key;\n\tstruct http_head_fields *filed = NULL;\n\n\tkey = hash_string_case(name);\n\thash_for_each_possible(http_head->field_map, filed, node, key)\n\t{\n\t\tif (strncasecmp(filed->name, name, 128) == 0) {\n\t\t\treturn filed->value;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstruct http_head_fields *http_head_next_fields(struct http_head_fields *fields)\n{\n\tstruct http_head_fields *next = NULL;\n\tnext = list_next_entry(fields, list);\n\n\tif (next->name == NULL && next->value == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn next;\n}\n\nconst char *http_head_fields_get_name(struct http_head_fields *fields)\n{\n\tif (fields == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn fields->name;\n}\n\nconst char *http_head_fields_get_value(struct http_head_fields *fields)\n{\n\tif (fields == NULL) {\n\t\treturn NULL;\n\t}\n\n\treturn fields->value;\n}\n\nint http_head_lookup_fields(struct http_head_fields *fields, const char **name, const char **value)\n{\n\tif (fields == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (name) {\n\t\t*name = fields->name;\n\t}\n\n\tif (value) {\n\t\t*value = fields->value;\n\t}\n\n\treturn 0;\n}\n\nconst char *http_head_get_params_value(struct http_head *http_head, const char *name)\n{\n\tuint32_t key;\n\tstruct http_params *params = NULL;\n\n\tkey = hash_string_case(name);\n\thash_for_each_possible(http_head->params_map, params, node, key)\n\t{\n\t\tif (strncasecmp(params->name, name, 128) == 0) {\n\t\t\treturn params->value;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nHTTP_METHOD http_head_get_method(struct http_head *http_head)\n{\n\treturn http_head->method;\n}\n\nconst char *http_head_get_url(struct http_head *http_head)\n{\n\treturn http_head->url;\n}\n\nconst char *http_head_get_httpversion(struct http_head *http_head)\n{\n\treturn http_head->version;\n}\n\nint http_head_get_httpcode(struct http_head *http_head)\n{\n\treturn http_head->code;\n}\n\nconst char *http_head_get_httpcode_msg(struct http_head *http_head)\n{\n\treturn http_head->code_msg;\n}\n\nHTTP_HEAD_TYPE http_head_get_head_type(struct http_head *http_head)\n{\n\treturn http_head->head_type;\n}\n\nconst unsigned char *http_head_get_data(struct http_head *http_head)\n{\n\treturn http_head->data;\n}\n\nint http_head_get_data_len(struct http_head *http_head)\n{\n\treturn http_head->data_len;\n}\n\nint _http_head_buffer_left_len(struct http_head *http_head)\n{\n\treturn http_head->buff_size - http_head->buff_len;\n}\n\nuint8_t *_http_head_buffer_get_end(struct http_head *http_head)\n{\n\treturn http_head->buff + http_head->buff_len;\n}\n\nuint8_t *_http_head_buffer_append(struct http_head *http_head, const uint8_t *data, int data_len)\n{\n\tif (http_head == NULL || data_len < 0) {\n\t\treturn NULL;\n\t}\n\n\tif (http_head->buff_len + data_len > http_head->buff_size) {\n\t\treturn NULL;\n\t}\n\n\tif (data != NULL) {\n\t\tmemcpy(http_head->buff + http_head->buff_len, data, data_len);\n\t}\n\thttp_head->buff_len += data_len;\n\n\treturn (http_head->buff + http_head->buff_len);\n}\n\nint _http_head_add_param(struct http_head *http_head, const char *name, const char *value)\n{\n\tuint32_t key = 0;\n\tstruct http_params *params = NULL;\n\tparams = zalloc(1, sizeof(*params));\n\tif (params == NULL) {\n\t\treturn -1;\n\t}\n\n\tparams->name = name;\n\tparams->value = value;\n\n\tlist_add_tail(&params->list, &http_head->params.list);\n\tkey = hash_string_case(name);\n\thash_add(http_head->params_map, &params->node, key);\n\n\treturn 0;\n}\n\nint http_head_add_param(struct http_head *http_head, const char *name, const char *value)\n{\n\tif (http_head == NULL || name == NULL || value == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn _http_head_add_param(http_head, name, value);\n}\n\nint http_head_set_url(struct http_head *http_head, const char *url)\n{\n\tif (http_head == NULL || url == NULL) {\n\t\treturn -1;\n\t}\n\n\thttp_head->url = url;\n\n\treturn 0;\n}\n\nint http_head_set_httpversion(struct http_head *http_head, const char *version)\n{\n\tif (http_head == NULL || version == NULL) {\n\t\treturn -1;\n\t}\n\n\thttp_head->version = version;\n\n\treturn 0;\n}\n\nint http_head_set_httpcode(struct http_head *http_head, int code, const char *msg)\n{\n\tif (http_head == NULL || code < 0 || msg == NULL) {\n\t\treturn -1;\n\t}\n\n\thttp_head->code = code;\n\thttp_head->code_msg = msg;\n\n\treturn 0;\n}\n\nint http_head_set_head_type(struct http_head *http_head, HTTP_HEAD_TYPE head_type)\n{\n\tif (http_head == NULL || head_type == HTTP_HEAD_INVALID) {\n\t\treturn -1;\n\t}\n\n\thttp_head->head_type = head_type;\n\n\treturn 0;\n}\n\nint http_head_set_method(struct http_head *http_head, HTTP_METHOD method)\n{\n\tif (http_head == NULL || method == HTTP_METHOD_INVALID) {\n\t\treturn -1;\n\t}\n\n\thttp_head->method = method;\n\n\treturn 0;\n}\n\nint http_head_set_data(struct http_head *http_head, const void *data, int len)\n{\n\tif (http_head == NULL || data == NULL || len < 0) {\n\t\treturn -1;\n\t}\n\n\thttp_head->data = (unsigned char *)data;\n\thttp_head->data_len = len;\n\n\treturn 0;\n}\n\nstatic int _http_head_add_fields(struct http_head *http_head, const char *name, const char *value)\n{\n\tuint32_t key = 0;\n\tstruct http_head_fields *fields = NULL;\n\tfields = zalloc(1, sizeof(*fields));\n\tif (fields == NULL) {\n\t\treturn -1;\n\t}\n\n\tfields->name = name;\n\tfields->value = value;\n\n\tlist_add_tail(&fields->list, &http_head->field_head.list);\n\tkey = hash_string_case(name);\n\thash_add(http_head->field_map, &fields->node, key);\n\n\treturn 0;\n}\n\nint http_head_add_fields(struct http_head *http_head, const char *name, const char *value)\n{\n\tif (http_head == NULL || name == NULL || value == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn _http_head_add_fields(http_head, name, value);\n}\n\nint _http_head_parse_params(struct http_head *http_head, char *url, int url_len)\n{\n\tchar *tmp_ptr = NULL;\n\tchar *field_start = NULL;\n\tchar *param_start = NULL;\n\tchar *field = NULL;\n\tchar *value = NULL;\n\n\tif (url == NULL) {\n\t\treturn -1;\n\t}\n\n\tparam_start = strstr(url, \"?\");\n\tif (param_start == NULL) {\n\t\treturn 0;\n\t}\n\n\t*param_start = '\\0';\n\tparam_start++;\n\n\tfor (tmp_ptr = param_start; tmp_ptr < url + url_len; tmp_ptr++) {\n\t\tif (field_start == NULL) {\n\t\t\tfield_start = tmp_ptr;\n\t\t}\n\n\t\tif (field == NULL) {\n\t\t\tif (*tmp_ptr == '=') {\n\t\t\t\t*tmp_ptr = '\\0';\n\t\t\t\tfield = field_start;\n\t\t\t\tfield_start = NULL;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (value == NULL) {\n\t\t\tif (*tmp_ptr == '&' || tmp_ptr == url + url_len - 1) {\n\t\t\t\t*tmp_ptr = '\\0';\n\t\t\t\tvalue = field_start;\n\t\t\t\tfield_start = NULL;\n\n\t\t\t\tif (_http_head_add_param(http_head, field, value) != 0) {\n\t\t\t\t\treturn -2;\n\t\t\t\t}\n\t\t\t\tfield = NULL;\n\t\t\t\tvalue = NULL;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t}\n\treturn 0;\n}\n\nconst char *http_method_str(HTTP_METHOD method)\n{\n\tswitch (method) {\n\tcase HTTP_METHOD_GET:\n\t\treturn \"GET\";\n\tcase HTTP_METHOD_POST:\n\t\treturn \"POST\";\n\tcase HTTP_METHOD_PUT:\n\t\treturn \"PUT\";\n\tcase HTTP_METHOD_DELETE:\n\t\treturn \"DELETE\";\n\tcase HTTP_METHOD_TRACE:\n\t\treturn \"TRACE\";\n\tcase HTTP_METHOD_CONNECT:\n\t\treturn \"CONNECT\";\n\tdefault:\n\t\treturn \"INVALID\";\n\t}\n}\n\nHTTP_METHOD _http_method_parse(const char *method)\n{\n\tif (method == NULL) {\n\t\treturn HTTP_METHOD_INVALID;\n\t}\n\n\tif (strncmp(method, \"GET\", sizeof(\"GET\")) == 0) {\n\t\treturn HTTP_METHOD_GET;\n\t} else if (strncmp(method, \"POST\", sizeof(\"POST\")) == 0) {\n\t\treturn HTTP_METHOD_POST;\n\t} else if (strncmp(method, \"PUT\", sizeof(\"PUT\")) == 0) {\n\t\treturn HTTP_METHOD_PUT;\n\t} else if (strncmp(method, \"DELETE\", sizeof(\"DELETE\")) == 0) {\n\t\treturn HTTP_METHOD_DELETE;\n\t} else if (strncmp(method, \"TRACE\", sizeof(\"TRACE\")) == 0) {\n\t\treturn HTTP_METHOD_TRACE;\n\t} else if (strncmp(method, \"CONNECT\", sizeof(\"CONNECT\")) == 0) {\n\t\treturn HTTP_METHOD_CONNECT;\n\t}\n\n\treturn HTTP_METHOD_INVALID;\n}\n\nint http_head_parse(struct http_head *http_head, const unsigned char *data, int data_len)\n{\n\tif (http_head->http_version == HTTP_VERSION_1_1) {\n\t\treturn http_head_parse_http1_1(http_head, data, data_len);\n\t} else if (http_head->http_version == HTTP_VERSION_3_0) {\n\t\treturn http_head_parse_http3_0(http_head, data, data_len);\n\t}\n\n\treturn -2;\n}\n\nint http_head_serialize(struct http_head *http_head, void *buffer, int buffer_len)\n{\n\tif (http_head == NULL || buffer == NULL || buffer_len <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (http_head->http_version == HTTP_VERSION_1_1) {\n\t\treturn http_head_serialize_http1_1(http_head, buffer, buffer_len);\n\t} else if (http_head->http_version == HTTP_VERSION_3_0) {\n\t\treturn http_head_serialize_http3_0(http_head, buffer, buffer_len);\n\t}\n\n\treturn -2;\n}\n\nvoid http_head_destroy(struct http_head *http_head)\n{\n\tstruct http_head_fields *fields = NULL, *tmp;\n\tstruct http_params *params = NULL, *tmp_params;\n\n\tlist_for_each_entry_safe(fields, tmp, &http_head->field_head.list, list)\n\t{\n\t\tlist_del(&fields->list);\n\t\tfree(fields);\n\t}\n\n\tlist_for_each_entry_safe(params, tmp_params, &http_head->params.list, list)\n\t{\n\t\tlist_del(&params->list);\n\t\tfree(params);\n\t}\n\n\tif (http_head->buff) {\n\t\tfree(http_head->buff);\n\t}\n\n\tfree(http_head);\n}\n"
  },
  {
    "path": "src/http_parse/http_parse.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _HTTP_PARSE_HTTP_H_\n#define _HTTP_PARSE_HTTP_H_\n\n#include \"smartdns/lib/hash.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/lib/jhash.h\"\n#include \"smartdns/lib/list.h\"\n\n#include \"smartdns/http_parse.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /*__cplusplus */\n\nstruct http_head_fields {\n\tstruct hlist_node node;\n\tstruct list_head list;\n\n\tconst char *name;\n\tconst char *value;\n};\n\nstruct http_params {\n\tstruct hlist_node node;\n\tstruct list_head list;\n\n\tconst char *name;\n\tconst char *value;\n};\n\nstruct http_head {\n\tHTTP_VERSION http_version;\n\tHTTP_HEAD_TYPE head_type;\n\tHTTP_METHOD method;\n\tconst char *url;\n\tconst char *version;\n\tint code;\n\tconst char *code_msg;\n\tint buff_size;\n\tint buff_len;\n\tuint8_t *buff;\n\tint head_ok;\n\tint head_len;\n\tconst uint8_t *data;\n\tint data_len;\n\tint expect_data_len;\n\tstruct http_head_fields field_head;\n\tstruct http_params params;\n\tDECLARE_HASHTABLE(field_map, 4);\n\tDECLARE_HASHTABLE(params_map, 4);\n};\n\nint _http_head_buffer_left_len(struct http_head *http_head);\nuint8_t *_http_head_buffer_get_end(struct http_head *http_head);\nuint8_t *_http_head_buffer_append(struct http_head *http_head, const uint8_t *data, int data_len);\n\nint _http_head_add_param(struct http_head *http_head, const char *name, const char *value);\nint _http_head_parse_params(struct http_head *http_head, char *url, int url_len);\n\nHTTP_METHOD _http_method_parse(const char *method);\n\n#ifdef __cplusplus\n}\n#endif /*__cplusplus */\n#endif\n"
  },
  {
    "path": "src/http_parse/qpack.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"qpack.h\"\n\nstatic const uint8_t _qpack_huffman_bit[64] = {\n\t249, 50,  115, 39,  38,  79, 147, 39, 38,  100, 249, 50,  114, 100, 242, 100, 228, 228, 206, 77,  52, 228,\n\t204, 203, 202, 114, 102, 79, 147, 39, 76,  156, 153, 62,  76,  156, 156, 153, 62,  76,  156, 153, 60, 154,\n\t100, 242, 102, 79,  147, 39, 38,  83, 228, 201, 201, 147, 211, 39,  38,  79,  38,  78,  76,  178};\n\nstatic const uint8_t _qpack_huffman_val[512] = {\n\t/* 0: |  X: 44 */ 44,\n\t/* 1: |0  X: 17 */ 16,\n\t/* 2: |00  X: 10 */ 8,\n\t/* 3: |000  X: 7 */ 4,\n\t/* 4: |0000  X: 6 */ 2,\n\t/* 5: |00000  V: 48 */ 48,\n\t/* 6: |00001  V: 49 */ 49,\n\t/* 7: |0001  X: 9 */ 2,\n\t/* 8: |00010  V: 50 */ 50,\n\t/* 9: |00011  V: 97 */ 97,\n\t/* 10: |001  X: 14 */ 4,\n\t/* 11: |0010  X: 13 */ 2,\n\t/* 12: |00100  V: 99 */ 99,\n\t/* 13: |00101  V: 101 */ 101,\n\t/* 14: |0011  X: 16 */ 2,\n\t/* 15: |00110  V: 105 */ 105,\n\t/* 16: |00111  V: 111 */ 111,\n\t/* 17: |01  X: 29 */ 12,\n\t/* 18: |010  X: 22 */ 4,\n\t/* 19: |0100  X: 21 */ 2,\n\t/* 20: |01000  V: 115 */ 115,\n\t/* 21: |01001  V: 116 */ 116,\n\t/* 22: |0101  X: 26 */ 4,\n\t/* 23: |01010  X: 25 */ 2,\n\t/* 24: |010100  V: 32 */ 32,\n\t/* 25: |010101  V: 37 */ 37,\n\t/* 26: |01011  X: 28 */ 2,\n\t/* 27: |010110  V: 45 */ 45,\n\t/* 28: |010111  V: 46 */ 46,\n\t/* 29: |011  X: 37 */ 8,\n\t/* 30: |0110  X: 34 */ 4,\n\t/* 31: |01100  X: 33 */ 2,\n\t/* 32: |011000  V: 47 */ 47,\n\t/* 33: |011001  V: 51 */ 51,\n\t/* 34: |01101  X: 36 */ 2,\n\t/* 35: |011010  V: 52 */ 52,\n\t/* 36: |011011  V: 53 */ 53,\n\t/* 37: |0111  X: 41 */ 4,\n\t/* 38: |01110  X: 40 */ 2,\n\t/* 39: |011100  V: 54 */ 54,\n\t/* 40: |011101  V: 55 */ 55,\n\t/* 41: |01111  X: 43 */ 2,\n\t/* 42: |011110  V: 56 */ 56,\n\t/* 43: |011111  V: 57 */ 57,\n\t/* 44: |1  X: 80 */ 36,\n\t/* 45: |10  X: 61 */ 16,\n\t/* 46: |100  X: 54 */ 8,\n\t/* 47: |1000  X: 51 */ 4,\n\t/* 48: |10000  X: 50 */ 2,\n\t/* 49: |100000  V: 61 */ 61,\n\t/* 50: |100001  V: 65 */ 65,\n\t/* 51: |10001  X: 53 */ 2,\n\t/* 52: |100010  V: 95 */ 95,\n\t/* 53: |100011  V: 98 */ 98,\n\t/* 54: |1001  X: 58 */ 4,\n\t/* 55: |10010  X: 57 */ 2,\n\t/* 56: |100100  V: 100 */ 100,\n\t/* 57: |100101  V: 102 */ 102,\n\t/* 58: |10011  X: 60 */ 2,\n\t/* 59: |100110  V: 103 */ 103,\n\t/* 60: |100111  V: 104 */ 104,\n\t/* 61: |101  X: 69 */ 8,\n\t/* 62: |1010  X: 66 */ 4,\n\t/* 63: |10100  X: 65 */ 2,\n\t/* 64: |101000  V: 108 */ 108,\n\t/* 65: |101001  V: 109 */ 109,\n\t/* 66: |10101  X: 68 */ 2,\n\t/* 67: |101010  V: 110 */ 110,\n\t/* 68: |101011  V: 112 */ 112,\n\t/* 69: |1011  X: 73 */ 4,\n\t/* 70: |10110  X: 72 */ 2,\n\t/* 71: |101100  V: 114 */ 114,\n\t/* 72: |101101  V: 117 */ 117,\n\t/* 73: |10111  X: 77 */ 4,\n\t/* 74: |101110  X: 76 */ 2,\n\t/* 75: |1011100  V: 58 */ 58,\n\t/* 76: |1011101  V: 66 */ 66,\n\t/* 77: |101111  X: 79 */ 2,\n\t/* 78: |1011110  V: 67 */ 67,\n\t/* 79: |1011111  V: 68 */ 68,\n\t/* 80: |11  X: 112 */ 32,\n\t/* 81: |110  X: 97 */ 16,\n\t/* 82: |1100  X: 90 */ 8,\n\t/* 83: |11000  X: 87 */ 4,\n\t/* 84: |110000  X: 86 */ 2,\n\t/* 85: |1100000  V: 69 */ 69,\n\t/* 86: |1100001  V: 70 */ 70,\n\t/* 87: |110001  X: 89 */ 2,\n\t/* 88: |1100010  V: 71 */ 71,\n\t/* 89: |1100011  V: 72 */ 72,\n\t/* 90: |11001  X: 94 */ 4,\n\t/* 91: |110010  X: 93 */ 2,\n\t/* 92: |1100100  V: 73 */ 73,\n\t/* 93: |1100101  V: 74 */ 74,\n\t/* 94: |110011  X: 96 */ 2,\n\t/* 95: |1100110  V: 75 */ 75,\n\t/* 96: |1100111  V: 76 */ 76,\n\t/* 97: |1101  X: 105 */ 8,\n\t/* 98: |11010  X: 102 */ 4,\n\t/* 99: |110100  X: 101 */ 2,\n\t/* 100: |1101000  V: 77 */ 77,\n\t/* 101: |1101001  V: 78 */ 78,\n\t/* 102: |110101  X: 104 */ 2,\n\t/* 103: |1101010  V: 79 */ 79,\n\t/* 104: |1101011  V: 80 */ 80,\n\t/* 105: |11011  X: 109 */ 4,\n\t/* 106: |110110  X: 108 */ 2,\n\t/* 107: |1101100  V: 81 */ 81,\n\t/* 108: |1101101  V: 82 */ 82,\n\t/* 109: |110111  X: 111 */ 2,\n\t/* 110: |1101110  V: 83 */ 83,\n\t/* 111: |1101111  V: 84 */ 84,\n\t/* 112: |111  X: 128 */ 16,\n\t/* 113: |1110  X: 121 */ 8,\n\t/* 114: |11100  X: 118 */ 4,\n\t/* 115: |111000  X: 117 */ 2,\n\t/* 116: |1110000  V: 85 */ 85,\n\t/* 117: |1110001  V: 86 */ 86,\n\t/* 118: |111001  X: 120 */ 2,\n\t/* 119: |1110010  V: 87 */ 87,\n\t/* 120: |1110011  V: 89 */ 89,\n\t/* 121: |11101  X: 125 */ 4,\n\t/* 122: |111010  X: 124 */ 2,\n\t/* 123: |1110100  V: 106 */ 106,\n\t/* 124: |1110101  V: 107 */ 107,\n\t/* 125: |111011  X: 127 */ 2,\n\t/* 126: |1110110  V: 113 */ 113,\n\t/* 127: |1110111  V: 118 */ 118,\n\t/* 128: |1111  X: 136 */ 8,\n\t/* 129: |11110  X: 133 */ 4,\n\t/* 130: |111100  X: 132 */ 2,\n\t/* 131: |1111000  V: 119 */ 119,\n\t/* 132: |1111001  V: 120 */ 120,\n\t/* 133: |111101  X: 135 */ 2,\n\t/* 134: |1111010  V: 121 */ 121,\n\t/* 135: |1111011  V: 122 */ 122,\n\t/* 136: |11111  X: 144 */ 8,\n\t/* 137: |111110  X: 141 */ 4,\n\t/* 138: |1111100  X: 140 */ 2,\n\t/* 139: |11111000  V: 38 */ 38,\n\t/* 140: |11111001  V: 42 */ 42,\n\t/* 141: |1111101  X: 143 */ 2,\n\t/* 142: |11111010  V: 44 */ 44,\n\t/* 143: |11111011  V: 59 */ 59,\n\t/* 144: |111111  X: 148 */ 4,\n\t/* 145: |1111110  X: 147 */ 2,\n\t/* 146: |11111100  V: 88 */ 88,\n\t/* 147: |11111101  V: 90 */ 90,\n\t/* 148: |1111111  X: 156 */ 8,\n\t/* 149: |11111110  X: 153 */ 4,\n\t/* 150: |11111110|0  X: 152 */ 2,\n\t/* 151: |11111110|00  V: 33 */ 33,\n\t/* 152: |11111110|01  V: 34 */ 34,\n\t/* 153: |11111110|1  X: 155 */ 2,\n\t/* 154: |11111110|10  V: 40 */ 40,\n\t/* 155: |11111110|11  V: 41 */ 41,\n\t/* 156: |11111111|  X: 162 */ 6,\n\t/* 157: |11111111|0  X: 159 */ 2,\n\t/* 158: |11111111|00  V: 63 */ 63,\n\t/* 159: |11111111|01  X: 161 */ 2,\n\t/* 160: |11111111|010  V: 39 */ 39,\n\t/* 161: |11111111|011  V: 43 */ 43,\n\t/* 162: |11111111|1  X: 168 */ 6,\n\t/* 163: |11111111|10  X: 165 */ 2,\n\t/* 164: |11111111|100  V: 124 */ 124,\n\t/* 165: |11111111|101  X: 167 */ 2,\n\t/* 166: |11111111|1010  V: 35 */ 35,\n\t/* 167: |11111111|1011  V: 62 */ 62,\n\t/* 168: |11111111|11  X: 176 */ 8,\n\t/* 169: |11111111|110  X: 173 */ 4,\n\t/* 170: |11111111|1100  X: 172 */ 2,\n\t/* 171: |11111111|11000  V: 0 */ 0,\n\t/* 172: |11111111|11001  V: 36 */ 36,\n\t/* 173: |11111111|1101  X: 175 */ 2,\n\t/* 174: |11111111|11010  V: 64 */ 64,\n\t/* 175: |11111111|11011  V: 91 */ 91,\n\t/* 176: |11111111|111  X: 180 */ 4,\n\t/* 177: |11111111|1110  X: 179 */ 2,\n\t/* 178: |11111111|11100  V: 93 */ 93,\n\t/* 179: |11111111|11101  V: 126 */ 126,\n\t/* 180: |11111111|1111  X: 184 */ 4,\n\t/* 181: |11111111|11110  X: 183 */ 2,\n\t/* 182: |11111111|111100  V: 94 */ 94,\n\t/* 183: |11111111|111101  V: 125 */ 125,\n\t/* 184: |11111111|11111  X: 188 */ 4,\n\t/* 185: |11111111|111110  X: 187 */ 2,\n\t/* 186: |11111111|1111100  V: 60 */ 60,\n\t/* 187: |11111111|1111101  V: 96 */ 96,\n\t/* 188: |11111111|111111  X: 190 */ 2,\n\t/* 189: |11111111|1111110  V: 123 */ 123,\n\t/* 190: |11111111|1111111  X: 220 */ 30,\n\t/* 191: |11111111|11111110|  X: 201 */ 10,\n\t/* 192: |11111111|11111110|0  X: 196 */ 4,\n\t/* 193: |11111111|11111110|00  X: 195 */ 2,\n\t/* 194: |11111111|11111110|000  V: 92 */ 92,\n\t/* 195: |11111111|11111110|001  V: 195 */ 195,\n\t/* 196: |11111111|11111110|01  X: 198 */ 2,\n\t/* 197: |11111111|11111110|010  V: 208 */ 208,\n\t/* 198: |11111111|11111110|011  X: 200 */ 2,\n\t/* 199: |11111111|11111110|0110  V: 128 */ 128,\n\t/* 200: |11111111|11111110|0111  V: 130 */ 130,\n\t/* 201: |11111111|11111110|1  X: 209 */ 8,\n\t/* 202: |11111111|11111110|10  X: 206 */ 4,\n\t/* 203: |11111111|11111110|100  X: 205 */ 2,\n\t/* 204: |11111111|11111110|1000  V: 131 */ 131,\n\t/* 205: |11111111|11111110|1001  V: 162 */ 162,\n\t/* 206: |11111111|11111110|101  X: 208 */ 2,\n\t/* 207: |11111111|11111110|1010  V: 184 */ 184,\n\t/* 208: |11111111|11111110|1011  V: 194 */ 194,\n\t/* 209: |11111111|11111110|11  X: 213 */ 4,\n\t/* 210: |11111111|11111110|110  X: 212 */ 2,\n\t/* 211: |11111111|11111110|1100  V: 224 */ 224,\n\t/* 212: |11111111|11111110|1101  V: 226 */ 226,\n\t/* 213: |11111111|11111110|111  X: 217 */ 4,\n\t/* 214: |11111111|11111110|1110  X: 216 */ 2,\n\t/* 215: |11111111|11111110|11100  V: 153 */ 153,\n\t/* 216: |11111111|11111110|11101  V: 161 */ 161,\n\t/* 217: |11111111|11111110|1111  X: 219 */ 2,\n\t/* 218: |11111111|11111110|11110  V: 167 */ 167,\n\t/* 219: |11111111|11111110|11111  V: 172 */ 172,\n\t/* 220: |11111111|11111111|  X: 266 */ 46,\n\t/* 221: |11111111|11111111|0  X: 237 */ 16,\n\t/* 222: |11111111|11111111|00  X: 230 */ 8,\n\t/* 223: |11111111|11111111|000  X: 227 */ 4,\n\t/* 224: |11111111|11111111|0000  X: 226 */ 2,\n\t/* 225: |11111111|11111111|00000  V: 176 */ 176,\n\t/* 226: |11111111|11111111|00001  V: 177 */ 177,\n\t/* 227: |11111111|11111111|0001  X: 229 */ 2,\n\t/* 228: |11111111|11111111|00010  V: 179 */ 179,\n\t/* 229: |11111111|11111111|00011  V: 209 */ 209,\n\t/* 230: |11111111|11111111|001  X: 234 */ 4,\n\t/* 231: |11111111|11111111|0010  X: 233 */ 2,\n\t/* 232: |11111111|11111111|00100  V: 216 */ 216,\n\t/* 233: |11111111|11111111|00101  V: 217 */ 217,\n\t/* 234: |11111111|11111111|0011  X: 236 */ 2,\n\t/* 235: |11111111|11111111|00110  V: 227 */ 227,\n\t/* 236: |11111111|11111111|00111  V: 229 */ 229,\n\t/* 237: |11111111|11111111|01  X: 251 */ 14,\n\t/* 238: |11111111|11111111|010  X: 244 */ 6,\n\t/* 239: |11111111|11111111|0100  X: 241 */ 2,\n\t/* 240: |11111111|11111111|01000  V: 230 */ 230,\n\t/* 241: |11111111|11111111|01001  X: 243 */ 2,\n\t/* 242: |11111111|11111111|010010  V: 129 */ 129,\n\t/* 243: |11111111|11111111|010011  V: 132 */ 132,\n\t/* 244: |11111111|11111111|0101  X: 248 */ 4,\n\t/* 245: |11111111|11111111|01010  X: 247 */ 2,\n\t/* 246: |11111111|11111111|010100  V: 133 */ 133,\n\t/* 247: |11111111|11111111|010101  V: 134 */ 134,\n\t/* 248: |11111111|11111111|01011  X: 250 */ 2,\n\t/* 249: |11111111|11111111|010110  V: 136 */ 136,\n\t/* 250: |11111111|11111111|010111  V: 146 */ 146,\n\t/* 251: |11111111|11111111|011  X: 259 */ 8,\n\t/* 252: |11111111|11111111|0110  X: 256 */ 4,\n\t/* 253: |11111111|11111111|01100  X: 255 */ 2,\n\t/* 254: |11111111|11111111|011000  V: 154 */ 154,\n\t/* 255: |11111111|11111111|011001  V: 156 */ 156,\n\t/* 256: |11111111|11111111|01101  X: 257 */ 2,\n\t/* 257: |11111111|11111111|011010  V: 160 */ 160,\n\t/* 258: |11111111|11111111|011011  V: 163 */ 163,\n\t/* 259: |11111111|11111111|0111  X: 263 */ 4,\n\t/* 260: |11111111|11111111|01110  X: 262 */ 2,\n\t/* 261: |11111111|11111111|011100  V: 164 */ 164,\n\t/* 262: |11111111|11111111|011101  V: 169 */ 169,\n\t/* 263: |11111111|11111111|01111  X: 265 */ 2,\n\t/* 264: |11111111|11111111|011110  V: 170 */ 170,\n\t/* 265: |11111111|11111111|011111  V: 173 */ 173,\n\t/* 266: |11111111|11111111|1  X: 306 */ 40,\n\t/* 267: |11111111|11111111|10  X: 283 */ 16,\n\t/* 268: |11111111|11111111|100  X: 276 */ 8,\n\t/* 269: |11111111|11111111|1000  X: 273 */ 4,\n\t/* 270: |11111111|11111111|10000  X: 272 */ 2,\n\t/* 271: |11111111|11111111|100000  V: 178 */ 178,\n\t/* 272: |11111111|11111111|100001  V: 181 */ 181,\n\t/* 273: |11111111|11111111|10001  X: 275 */ 2,\n\t/* 274: |11111111|11111111|100010  V: 185 */ 185,\n\t/* 275: |11111111|11111111|100011  V: 186 */ 186,\n\t/* 276: |11111111|11111111|1001  X: 280 */ 4,\n\t/* 277: |11111111|11111111|10010  X: 279 */ 2,\n\t/* 278: |11111111|11111111|100100  V: 187 */ 187,\n\t/* 279: |11111111|11111111|100101  V: 189 */ 189,\n\t/* 280: |11111111|11111111|10011  X: 282 */ 2,\n\t/* 281: |11111111|11111111|100110  V: 190 */ 190,\n\t/* 282: |11111111|11111111|100111  V: 196 */ 196,\n\t/* 283: |11111111|11111111|101  X: 291 */ 8,\n\t/* 284: |11111111|11111111|1010  X: 288 */ 4,\n\t/* 285: |11111111|11111111|10100  X: 287 */ 2,\n\t/* 286: |11111111|11111111|101000  V: 198 */ 198,\n\t/* 287: |11111111|11111111|101001  V: 228 */ 228,\n\t/* 288: |11111111|11111111|10101  X: 290 */ 2,\n\t/* 289: |11111111|11111111|101010  V: 232 */ 232,\n\t/* 290: |11111111|11111111|101011  V: 233 */ 233,\n\t/* 291: |11111111|11111111|1011  X: 299 */ 8,\n\t/* 292: |11111111|11111111|10110  X: 296 */ 4,\n\t/* 293: |11111111|11111111|101100  X: 295 */ 2,\n\t/* 294: |11111111|11111111|1011000  V: 1 */ 1,\n\t/* 295: |11111111|11111111|1011001  V: 135 */ 135,\n\t/* 296: |11111111|11111111|101101  X: 298 */ 2,\n\t/* 297: |11111111|11111111|1011010  V: 137 */ 137,\n\t/* 298: |11111111|11111111|1011011  V: 138 */ 138,\n\t/* 299: |11111111|11111111|10111  X: 303 */ 4,\n\t/* 300: |11111111|11111111|101110  X: 302 */ 2,\n\t/* 301: |11111111|11111111|1011100  V: 139 */ 139,\n\t/* 302: |11111111|11111111|1011101  V: 140 */ 140,\n\t/* 303: |11111111|11111111|101111  X: 305 */ 2,\n\t/* 304: |11111111|11111111|1011110  V: 141 */ 141,\n\t/* 305: |11111111|11111111|1011111  V: 143 */ 143,\n\t/* 306: |11111111|11111111|11  X: 338 */ 32,\n\t/* 307: |11111111|11111111|110  X: 323 */ 16,\n\t/* 308: |11111111|11111111|1100  X: 316 */ 8,\n\t/* 309: |11111111|11111111|11000  X: 313 */ 4,\n\t/* 310: |11111111|11111111|110000  X: 312 */ 2,\n\t/* 311: |11111111|11111111|1100000  V: 147 */ 147,\n\t/* 312: |11111111|11111111|1100001  V: 149 */ 149,\n\t/* 313: |11111111|11111111|110001  X: 315 */ 2,\n\t/* 314: |11111111|11111111|1100010  V: 150 */ 150,\n\t/* 315: |11111111|11111111|1100011  V: 151 */ 151,\n\t/* 316: |11111111|11111111|11001  X: 320 */ 4,\n\t/* 317: |11111111|11111111|110010  X: 319 */ 2,\n\t/* 318: |11111111|11111111|1100100  V: 152 */ 152,\n\t/* 319: |11111111|11111111|1100101  V: 155 */ 155,\n\t/* 320: |11111111|11111111|110011  X: 322 */ 2,\n\t/* 321: |11111111|11111111|1100110  V: 157 */ 157,\n\t/* 322: |11111111|11111111|1100111  V: 158 */ 158,\n\t/* 323: |11111111|11111111|1101  X: 331 */ 8,\n\t/* 324: |11111111|11111111|11010  X: 328 */ 4,\n\t/* 325: |11111111|11111111|110100  X: 327 */ 2,\n\t/* 326: |11111111|11111111|1101000  V: 165 */ 165,\n\t/* 327: |11111111|11111111|1101001  V: 166 */ 166,\n\t/* 328: |11111111|11111111|110101  X: 330 */ 2,\n\t/* 329: |11111111|11111111|1101010  V: 168 */ 168,\n\t/* 330: |11111111|11111111|1101011  V: 174 */ 174,\n\t/* 331: |11111111|11111111|11011  X: 335 */ 4,\n\t/* 332: |11111111|11111111|110110  X: 334 */ 2,\n\t/* 333: |11111111|11111111|1101100  V: 175 */ 175,\n\t/* 334: |11111111|11111111|1101101  V: 180 */ 180,\n\t/* 335: |11111111|11111111|110111  X: 337 */ 2,\n\t/* 336: |11111111|11111111|1101110  V: 182 */ 182,\n\t/* 337: |11111111|11111111|1101111  V: 183 */ 183,\n\t/* 338: |11111111|11111111|111  X: 360 */ 22,\n\t/* 339: |11111111|11111111|1110  X: 347 */ 8,\n\t/* 340: |11111111|11111111|11100  X: 344 */ 4,\n\t/* 341: |11111111|11111111|111000  X: 343 */ 2,\n\t/* 342: |11111111|11111111|1110000  V: 188 */ 188,\n\t/* 343: |11111111|11111111|1110001  V: 191 */ 191,\n\t/* 344: |11111111|11111111|111001  X: 346 */ 2,\n\t/* 345: |11111111|11111111|1110010  V: 197 */ 197,\n\t/* 346: |11111111|11111111|1110011  V: 231 */ 231,\n\t/* 347: |11111111|11111111|11101  X: 353 */ 6,\n\t/* 348: |11111111|11111111|111010  X: 350 */ 2,\n\t/* 349: |11111111|11111111|1110100  V: 239 */ 239,\n\t/* 350: |11111111|11111111|1110101  X: 352 */ 2,\n\t/* 351: |11111111|11111111|11101010  V: 9 */ 9,\n\t/* 352: |11111111|11111111|11101011  V: 142 */ 142,\n\t/* 353: |11111111|11111111|111011  X: 357 */ 4,\n\t/* 354: |11111111|11111111|1110110  X: 356 */ 2,\n\t/* 355: |11111111|11111111|11101100  V: 144 */ 144,\n\t/* 356: |11111111|11111111|11101101  V: 145 */ 145,\n\t/* 357: |11111111|11111111|1110111  X: 359 */ 2,\n\t/* 358: |11111111|11111111|11101110  V: 148 */ 148,\n\t/* 359: |11111111|11111111|11101111  V: 159 */ 159,\n\t/* 360: |11111111|11111111|1111  X: 380 */ 20,\n\t/* 361: |11111111|11111111|11110  X: 369 */ 8,\n\t/* 362: |11111111|11111111|111100  X: 366 */ 4,\n\t/* 363: |11111111|11111111|1111000  X: 365 */ 2,\n\t/* 364: |11111111|11111111|11110000  V: 171 */ 171,\n\t/* 365: |11111111|11111111|11110001  V: 206 */ 206,\n\t/* 366: |11111111|11111111|1111001  X: 368 */ 2,\n\t/* 367: |11111111|11111111|11110010  V: 215 */ 215,\n\t/* 368: |11111111|11111111|11110011  V: 225 */ 225,\n\t/* 369: |11111111|11111111|111101  X: 373 */ 4,\n\t/* 370: |11111111|11111111|1111010  X: 372 */ 2,\n\t/* 371: |11111111|11111111|11110100  V: 236 */ 236,\n\t/* 372: |11111111|11111111|11110101  V: 237 */ 237,\n\t/* 373: |11111111|11111111|1111011  X: 377 */ 4,\n\t/* 374: |11111111|11111111|11110110|  X: 376 */ 2,\n\t/* 375: |11111111|11111111|11110110|0  V: 199 */ 199,\n\t/* 376: |11111111|11111111|11110110|1  V: 207 */ 207,\n\t/* 377: |11111111|11111111|11110111|  X: 379 */ 2,\n\t/* 378: |11111111|11111111|11110111|0  V: 234 */ 234,\n\t/* 379: |11111111|11111111|11110111|1  V: 235 */ 235,\n\t/* 380: |11111111|11111111|11111  X: 414 */ 34,\n\t/* 381: |11111111|11111111|111110  X: 397 */ 16,\n\t/* 382: |11111111|11111111|1111100  X: 390 */ 8,\n\t/* 383: |11111111|11111111|11111000|  X: 387 */ 4,\n\t/* 384: |11111111|11111111|11111000|0  X: 386 */ 2,\n\t/* 385: |11111111|11111111|11111000|00  V: 192 */ 192,\n\t/* 386: |11111111|11111111|11111000|01  V: 193 */ 193,\n\t/* 387: |11111111|11111111|11111000|1  X: 389 */ 2,\n\t/* 388: |11111111|11111111|11111000|10  V: 200 */ 200,\n\t/* 389: |11111111|11111111|11111000|11  V: 201 */ 201,\n\t/* 390: |11111111|11111111|11111001|  X: 394 */ 4,\n\t/* 391: |11111111|11111111|11111001|0  X: 393 */ 2,\n\t/* 392: |11111111|11111111|11111001|00  V: 202 */ 202,\n\t/* 393: |11111111|11111111|11111001|01  V: 205 */ 205,\n\t/* 394: |11111111|11111111|11111001|1  X: 396 */ 2,\n\t/* 395: |11111111|11111111|11111001|10  V: 210 */ 210,\n\t/* 396: |11111111|11111111|11111001|11  V: 213 */ 213,\n\t/* 397: |11111111|11111111|1111101  X: 405 */ 8,\n\t/* 398: |11111111|11111111|11111010|  X: 402 */ 4,\n\t/* 399: |11111111|11111111|11111010|0  X: 401 */ 2,\n\t/* 400: |11111111|11111111|11111010|00  V: 218 */ 218,\n\t/* 401: |11111111|11111111|11111010|01  V: 219 */ 219,\n\t/* 402: |11111111|11111111|11111010|1  X: 404 */ 2,\n\t/* 403: |11111111|11111111|11111010|10  V: 238 */ 238,\n\t/* 404: |11111111|11111111|11111010|11  V: 240 */ 240,\n\t/* 405: |11111111|11111111|11111011|  X: 409 */ 4,\n\t/* 406: |11111111|11111111|11111011|0  X: 408 */ 2,\n\t/* 407: |11111111|11111111|11111011|00  V: 242 */ 242,\n\t/* 408: |11111111|11111111|11111011|01  V: 243 */ 243,\n\t/* 409: |11111111|11111111|11111011|1  X: 411 */ 2,\n\t/* 410: |11111111|11111111|11111011|10  V: 255 */ 255,\n\t/* 411: |11111111|11111111|11111011|11  X: 413 */ 2,\n\t/* 412: |11111111|11111111|11111011|110  V: 203 */ 203,\n\t/* 413: |11111111|11111111|11111011|111  V: 204 */ 204,\n\t/* 414: |11111111|11111111|111111  X: 446 */ 32,\n\t/* 415: |11111111|11111111|1111110  X: 431 */ 16,\n\t/* 416: |11111111|11111111|11111100|  X: 424 */ 8,\n\t/* 417: |11111111|11111111|11111100|0  X: 421 */ 4,\n\t/* 418: |11111111|11111111|11111100|00  X: 420 */ 2,\n\t/* 419: |11111111|11111111|11111100|000  V: 211 */ 211,\n\t/* 420: |11111111|11111111|11111100|001  V: 212 */ 212,\n\t/* 421: |11111111|11111111|11111100|01  X: 423 */ 2,\n\t/* 422: |11111111|11111111|11111100|010  V: 214 */ 214,\n\t/* 423: |11111111|11111111|11111100|011  V: 221 */ 221,\n\t/* 424: |11111111|11111111|11111100|1  X: 428 */ 4,\n\t/* 425: |11111111|11111111|11111100|10  X: 427 */ 2,\n\t/* 426: |11111111|11111111|11111100|100  V: 222 */ 222,\n\t/* 427: |11111111|11111111|11111100|101  V: 223 */ 223,\n\t/* 428: |11111111|11111111|11111100|11  X: 430 */ 2,\n\t/* 429: |11111111|11111111|11111100|110  V: 241 */ 241,\n\t/* 430: |11111111|11111111|11111100|111  V: 244 */ 244,\n\t/* 431: |11111111|11111111|11111101|  X: 439 */ 8,\n\t/* 432: |11111111|11111111|11111101|0  X: 436 */ 4,\n\t/* 433: |11111111|11111111|11111101|00  X: 435 */ 2,\n\t/* 434: |11111111|11111111|11111101|000  V: 245 */ 245,\n\t/* 435: |11111111|11111111|11111101|001  V: 246 */ 246,\n\t/* 436: |11111111|11111111|11111101|01  X: 438 */ 2,\n\t/* 437: |11111111|11111111|11111101|010  V: 247 */ 247,\n\t/* 438: |11111111|11111111|11111101|011  V: 248 */ 248,\n\t/* 439: |11111111|11111111|11111101|1  X: 443 */ 4,\n\t/* 440: |11111111|11111111|11111101|10  X: 442 */ 2,\n\t/* 441: |11111111|11111111|11111101|100  V: 250 */ 250,\n\t/* 442: |11111111|11111111|11111101|101  V: 251 */ 251,\n\t/* 443: |11111111|11111111|11111101|11  X: 445 */ 2,\n\t/* 444: |11111111|11111111|11111101|110  V: 252 */ 252,\n\t/* 445: |11111111|11111111|11111101|111  V: 253 */ 253,\n\t/* 446: |11111111|11111111|1111111  X: 476 */ 30,\n\t/* 447: |11111111|11111111|11111110|  X: 461 */ 14,\n\t/* 448: |11111111|11111111|11111110|0  X: 454 */ 6,\n\t/* 449: |11111111|11111111|11111110|00  X: 451 */ 2,\n\t/* 450: |11111111|11111111|11111110|000  V: 254 */ 254,\n\t/* 451: |11111111|11111111|11111110|001  X: 453 */ 2,\n\t/* 452: |11111111|11111111|11111110|0010  V: 2 */ 2,\n\t/* 453: |11111111|11111111|11111110|0011  V: 3 */ 3,\n\t/* 454: |11111111|11111111|11111110|01  X: 458 */ 4,\n\t/* 455: |11111111|11111111|11111110|010  X: 457 */ 2,\n\t/* 456: |11111111|11111111|11111110|0100  V: 4 */ 4,\n\t/* 457: |11111111|11111111|11111110|0101  V: 5 */ 5,\n\t/* 458: |11111111|11111111|11111110|011  X: 460 */ 2,\n\t/* 459: |11111111|11111111|11111110|0110  V: 6 */ 6,\n\t/* 460: |11111111|11111111|11111110|0111  V: 7 */ 7,\n\t/* 461: |11111111|11111111|11111110|1  X: 469 */ 8,\n\t/* 462: |11111111|11111111|11111110|10  X: 466 */ 4,\n\t/* 463: |11111111|11111111|11111110|100  X: 465 */ 2,\n\t/* 464: |11111111|11111111|11111110|1000  V: 8 */ 8,\n\t/* 465: |11111111|11111111|11111110|1001  V: 11 */ 11,\n\t/* 466: |11111111|11111111|11111110|101  X: 468 */ 2,\n\t/* 467: |11111111|11111111|11111110|1010  V: 12 */ 12,\n\t/* 468: |11111111|11111111|11111110|1011  V: 14 */ 14,\n\t/* 469: |11111111|11111111|11111110|11  X: 473 */ 4,\n\t/* 470: |11111111|11111111|11111110|110  X: 472 */ 2,\n\t/* 471: |11111111|11111111|11111110|1100  V: 15 */ 15,\n\t/* 472: |11111111|11111111|11111110|1101  V: 16 */ 16,\n\t/* 473: |11111111|11111111|11111110|111  X: 475 */ 2,\n\t/* 474: |11111111|11111111|11111110|1110  V: 17 */ 17,\n\t/* 475: |11111111|11111111|11111110|1111  V: 18 */ 18,\n\t/* 476: |11111111|11111111|11111111|  X: 492 */ 16,\n\t/* 477: |11111111|11111111|11111111|0  X: 485 */ 8,\n\t/* 478: |11111111|11111111|11111111|00  X: 482 */ 4,\n\t/* 479: |11111111|11111111|11111111|000  X: 481 */ 2,\n\t/* 480: |11111111|11111111|11111111|0000  V: 19 */ 19,\n\t/* 481: |11111111|11111111|11111111|0001  V: 20 */ 20,\n\t/* 482: |11111111|11111111|11111111|001  X: 484 */ 2,\n\t/* 483: |11111111|11111111|11111111|0010  V: 21 */ 21,\n\t/* 484: |11111111|11111111|11111111|0011  V: 23 */ 23,\n\t/* 485: |11111111|11111111|11111111|01  X: 489 */ 4,\n\t/* 486: |11111111|11111111|11111111|010  X: 488 */ 2,\n\t/* 487: |11111111|11111111|11111111|0100  V: 24 */ 24,\n\t/* 488: |11111111|11111111|11111111|0101  V: 25 */ 25,\n\t/* 489: |11111111|11111111|11111111|011  X: 491 */ 2,\n\t/* 490: |11111111|11111111|11111111|0110  V: 26 */ 26,\n\t/* 491: |11111111|11111111|11111111|0111  V: 27 */ 27,\n\t/* 492: |11111111|11111111|11111111|1  X: 500 */ 8,\n\t/* 493: |11111111|11111111|11111111|10  X: 497 */ 4,\n\t/* 494: |11111111|11111111|11111111|100  X: 496 */ 2,\n\t/* 495: |11111111|11111111|11111111|1000  V: 28 */ 28,\n\t/* 496: |11111111|11111111|11111111|1001  V: 29 */ 29,\n\t/* 497: |11111111|11111111|11111111|101  X: 499 */ 2,\n\t/* 498: |11111111|11111111|11111111|1010  V: 30 */ 30,\n\t/* 499: |11111111|11111111|11111111|1011  V: 31 */ 31,\n\t/* 500: |11111111|11111111|11111111|11  X: 504 */ 4,\n\t/* 501: |11111111|11111111|11111111|110  X: 503 */ 2,\n\t/* 502: |11111111|11111111|11111111|1100  V: 127 */ 127,\n\t/* 503: |11111111|11111111|11111111|1101  V: 220 */ 220,\n\t/* 504: |11111111|11111111|11111111|111  X: 506 */ 2,\n\t/* 505: |11111111|11111111|11111111|1110  V: 249 */ 249,\n\t/* 506: |11111111|11111111|11111111|1111  X: 510 */ 4,\n\t/* 507: |11111111|11111111|11111111|11110  X: 509 */ 2,\n\t/* 508: |11111111|11111111|11111111|111100  V: 10 */ 10,\n\t/* 509: |11111111|11111111|11111111|111101  V: 13 */ 13,\n\t/* 510: |11111111|11111111|11111111|11111  X: 512 */ 2,\n\t/* 511: |11111111|11111111|11111111|111110  V: 22 */ 22};\n\nint qpack_huffman_decode(const uint8_t *bytes, const uint8_t *bytes_max, uint8_t *decoded, size_t max_decoded,\n\t\t\t\t\t\t size_t *nb_decoded)\n{\n\tint ret = 0;\n\tuint64_t val_in = 0;\n\tint bits_in = 0;\n\tsize_t decoded_index = 0;\n\tint index = 0;\n\tint was_all_ones = 1;\n\n\twhile (1) {\n\t\tint bit;\n\t\tint index_64 = index >> 3;\n\t\tint b_index = 7 - (index & 7);\n\n\t\twhile (bits_in < 57 && bytes < bytes_max) {\n\t\t\tuint64_t added = *bytes++;\n\t\t\tint shift = 64 - bits_in - 8;\n\t\t\tadded <<= shift;\n\t\t\tval_in |= added;\n\t\t\tbits_in += 8;\n\t\t}\n\n\t\tif ((_qpack_huffman_bit[index_64] >> b_index) & 1) {\n\t\t\tif (bits_in <= 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbit = (val_in >> 63) & 1;\n\t\t\tval_in <<= 1;\n\t\t\tbits_in--;\n\t\t\tif (bit) {\n\t\t\t\tindex += _qpack_huffman_val[index];\n\t\t\t} else {\n\t\t\t\tindex++;\n\t\t\t\twas_all_ones = 0;\n\t\t\t}\n\t\t\tif (index >= 512) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else if (decoded_index < max_decoded) {\n\t\t\tdecoded[decoded_index++] = _qpack_huffman_val[index];\n\t\t\tindex = 0;\n\t\t\twas_all_ones = 1;\n\t\t} else {\n\t\t\twas_all_ones = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!was_all_ones) {\n\t\tret = -1;\n\t}\n\n\t*nb_decoded = decoded_index;\n\n\treturn ret;\n}\n\n/* clang-format off */\nstatic struct qpack_header_field _http3_static_header_table[] = {\n    {.name = \":authority\", .value = \"\"},\n    {.name = \":path\", .value = \"/\"},\n    {.name = \"age\", .value = \"0\"},\n    {.name = \"content-disposition\", .value = \"\"},\n    {.name = \"content-length\", .value = \"0\"},\n    {.name = \"cookie\", .value = \"\"},\n    {.name = \"date\", .value = \"\"},\n    {.name = \"etag\", .value = \"\"},\n    {.name = \"if-modified-since\", .value = \"\"},\n    {.name = \"if-none-match\", .value = \"\"},\n    {.name = \"last-modified\", .value = \"\"},\n    {.name = \"link\", .value = \"\"},\n    {.name = \"location\", .value = \"\"},\n    {.name = \"referer\", .value = \"\"},\n    {.name = \"set-cookie\", .value = \"\"},\n    {.name = \":method\", .value = \"CONNECT\"},\n    {.name = \":method\", .value = \"DELETE\"},\n    {.name = \":method\", .value = \"GET\"},\n    {.name = \":method\", .value = \"HEAD\"},\n    {.name = \":method\", .value = \"OPTIONS\"},\n    {.name = \":method\", .value = \"POST\"},\n    {.name = \":method\", .value = \"PUT\"},\n    {.name = \":scheme\", .value = \"http\"},\n    {.name = \":scheme\", .value = \"https\"},\n    {.name = \":status\", .value = \"103\"},\n    {.name = \":status\", .value = \"200\"},\n    {.name = \":status\", .value = \"304\"},\n    {.name = \":status\", .value = \"404\"},\n    {.name = \":status\", .value = \"503\"},\n    {.name = \"accept\", .value = \"*/*\"},\n    {.name = \"accept\", .value = \"application/dns-message\"},\n    {.name = \"accept-encoding\", .value = \"gzip, .value = deflate, .value = br\"},\n    {.name = \"accept-ranges\", .value = \"bytes\"},\n    {.name = \"access-control-allow-headers\", .value = \"cache-control\"},\n    {.name = \"access-control-allow-headers\", .value = \"content-type\"},\n    {.name = \"access-control-allow-origin\", .value = \"*\"},\n    {.name = \"cache-control\", .value = \"max-age=0\"},\n    {.name = \"cache-control\", .value = \"max-age=2592000\"},\n    {.name = \"cache-control\", .value = \"max-age=604800\"},\n    {.name = \"cache-control\", .value = \"no-cache\"},\n    {.name = \"cache-control\", .value = \"no-store\"},\n    {.name = \"cache-control\", .value = \"public, .value = max-age=31536000\"},\n    {.name = \"content-encoding\", .value = \"br\"},\n    {.name = \"content-encoding\", .value = \"gzip\"},\n    {.name = \"content-type\", .value = \"application/dns-message\"},\n    {.name = \"content-type\", .value = \"application/javascript\"},\n    {.name = \"content-type\", .value = \"application/json\"},\n    {.name = \"content-type\", .value = \"application/x-www-form-urlencoded\"},\n    {.name = \"content-type\", .value = \"image/gif\"},\n    {.name = \"content-type\", .value = \"image/jpeg\"},\n    {.name = \"content-type\", .value = \"image/png\"},\n    {.name = \"content-type\", .value = \"text/css\"},\n    {.name = \"content-type\", .value = \"text/html; charset=utf-8\"},\n    {.name = \"content-type\", .value = \"text/plain\"},\n    {.name = \"content-type\", .value = \"text/plain;charset=utf-8\"},\n    {.name = \"range\", .value = \"bytes=0-\"},\n    {.name = \"strict-transport-security\", .value = \"max-age=31536000\"},\n    {.name = \"strict-transport-security\", .value = \"max-age=31536000; includesubdomains\"},\n    {.name = \"strict-transport-security\", .value = \"max-age=31536000; includesubdomains; preload\"},\n    {.name = \"vary\", .value = \"accept-encoding\"},\n    {.name = \"vary\", .value = \"origin\"},\n    {.name = \"x-content-type-options\", .value = \"nosniff\"},\n    {.name = \"x-xss-protection\", .value = \"1; mode=block\"},\n    {.name = \":status\", .value = \"100\"},\n    {.name = \":status\", .value = \"204\"},\n    {.name = \":status\", .value = \"206\"},\n    {.name = \":status\", .value = \"302\"},\n    {.name = \":status\", .value = \"400\"},\n    {.name = \":status\", .value = \"403\"},\n    {.name = \":status\", .value = \"421\"},\n    {.name = \":status\", .value = \"425\"},\n    {.name = \":status\", .value = \"500\"},\n    {.name = \"accept-language\", .value = \"\"},\n    {.name = \"access-control-allow-credentials\", .value = \"FALSE\"},\n    {.name = \"access-control-allow-credentials\", .value = \"TRUE\"},\n    {.name = \"access-control-allow-headers\", .value = \"*\"},\n    {.name = \"access-control-allow-methods\", .value = \"get\"},\n    {.name = \"access-control-allow-methods\", .value = \"get, .value = post, .value = options\"},\n    {.name = \"access-control-allow-methods\", .value = \"options\"},\n    {.name = \"access-control-expose-headers\", .value = \"content-length\"},\n    {.name = \"access-control-request-headers\", .value = \"content-type\"},\n    {.name = \"access-control-request-method\", .value = \"get\"},\n    {.name = \"access-control-request-method\", .value = \"post\"},\n    {.name = \"alt-svc\", .value = \"clear\"},\n    {.name = \"authorization\"},\n    {.name = \"content-security-policy\", .value = \"script-src 'none'; object-src 'none'; base-uri 'none'\"},\n    {.name = \"early-data\", .value = \"1\"},\n    {.name = \"expect-ct\", .value = \"\"},\n    {.name = \"forwarded\", .value = \"\"},\n    {.name = \"if-range\", .value = \"\"},\n    {.name = \"origin\", .value = \"\"},\n    {.name = \"purpose\", .value = \"prefetch\"},\n    {.name = \"server\", .value = \"\"},\n    {.name = \"timing-allow-origin\", .value = \"*\"},\n    {.name = \"upgrade-insecure-requests\", .value = \"1\"},\n    {.name = \"user-agent\", .value = \"\"},\n    {.name = \"x-forwarded-for\", .value = \"\"},\n    {.name = \"x-frame-options\", .value = \"deny\"},\n    {.name = \"x-frame-options\", .value = \"sameorigin\"},\n };\n static int _http3_static_header_table_len = sizeof(_http3_static_header_table) / sizeof(struct qpack_header_field);\n/* clang-format on */\n\nstruct qpack_header_field *qpack_get_static_header_field(int index)\n{\n\tif (index < 0 || index >= _http3_static_header_table_len) {\n\t\treturn NULL;\n\t}\n\treturn &_http3_static_header_table[index];\n}"
  },
  {
    "path": "src/http_parse/qpack.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _QPACK_H\n#define _QPACK_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct qpack_header_field {\n\tconst char *name;\n\tconst char *value;\n};\n\nstruct qpack_header_field *qpack_get_static_header_field(int index);\n\nint qpack_huffman_decode(const uint8_t *bytes, const uint8_t *bytes_max, uint8_t *decoded, size_t max_decoded,\n\t\t\t\t\t\t size_t *nb_decoded);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // !_QPACK_H\n"
  },
  {
    "path": "src/lib/art.c",
    "content": "/*\nCopyright (c) 2012, Armon Dadgar\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the organization nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL ARMON DADGAR BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n#include <stdlib.h>\n#include <string.h>\n#include <strings.h>\n#include <stdio.h>\n#include <assert.h>\n#include \"smartdns/lib/art.h\"\n#include \"smartdns/util.h\"\n\n// #ifdef __i386__\n//     #include <emmintrin.h>\n// #else\n#ifdef __amd64__\n    #include <emmintrin.h>\n#endif\n// #endif\n\n/**\n * Macros to manipulate pointer tags\n */\n#define IS_LEAF(x) (((uintptr_t)x & 1))\n#define SET_LEAF(x) ((void*)((uintptr_t)x | 1))\n#define LEAF_RAW(x) ((art_leaf*)((void*)((uintptr_t)x & ~1)))\n\n/**\n * Forward declarations\n */\nstatic void add_child32(art_node32 *n, art_node **ref, unsigned char c, void *child);\nstatic void add_child48(art_node48 *n, art_node **ref, unsigned char c, void *child);\n\n/**\n * Allocates a node of the given type,\n * initializes to zero and sets the type.\n */\nstatic art_node* alloc_node(uint8_t type) {\n    art_node* n;\n\tvoid *mem = NULL;\n\tswitch (type) {\n        case NODE4:\n            mem = (art_node*)calloc(1, sizeof(art_node4));\n            break;\n        case NODE16:\n            mem = (art_node*)calloc(1, sizeof(art_node16));\n            break;\n        case NODE32:\n            mem = (art_node*)calloc(1, sizeof(art_node32));\n            break;\n        case NODE48:\n            mem = (art_node*)calloc(1, sizeof(art_node48));\n            break;\n        case NODE256:\n            mem = (art_node*)calloc(1, sizeof(art_node256));\n            break;\n        default:\n            BUG(\"alloc_node: invalid type\");\n    }\n    if (mem == NULL) {\n\t\tBUG(\"alloc_node: calloc failed\");\n\t}\n\tn = mem;\n\tn->type = type;\n    return n;\n}\n\n/**\n * Initializes an ART tree\n * @return 0 on success.\n */\nint art_tree_init(art_tree *t) {\n    t->root = NULL;\n    t->size = 0;\n    return 0;\n}\n\n// Recursively destroys the tree\nstatic void destroy_node(art_node *n) {\n    // Break if null\n    if (!n) return;\n\n    // Special case leafs\n    if (IS_LEAF(n)) {\n        free(LEAF_RAW(n));\n        return;\n    }\n\n    // Handle each node type\n    int i, idx;\n    union {\n        art_node4 *p1;\n        art_node16 *p2;\n        art_node32 *p3;\n        art_node48 *p4;\n        art_node256 *p5;\n    } p;\n    switch (n->type) {\n        case NODE4:\n            p.p1 = (art_node4*)n;\n            for (i=0;i<n->num_children;i++) {\n                destroy_node(p.p1->children[i]);\n            }\n            break;\n\n        case NODE16:\n            p.p2 = (art_node16*)n;\n            for (i=0;i<n->num_children;i++) {\n                destroy_node(p.p2->children[i]);\n            }\n            break;\n\n        case NODE32:\n            p.p3 = (art_node32*)n;\n            for (i=0;i<n->num_children;i++) {\n                destroy_node(p.p3->children[i]);\n            }\n            break;\n\n        case NODE48:\n            p.p4 = (art_node48*)n;\n            for (i=0;i<256;i++) {\n                idx = ((art_node48*)n)->keys[i]; \n                if (!idx) continue; \n                destroy_node(p.p4->children[idx-1]);\n            }\n            break;\n\n        case NODE256:\n            p.p5 = (art_node256*)n;\n            for (i=0;i<256;i++) {\n                if (p.p5->children[i])\n                    destroy_node(p.p5->children[i]);\n            }\n            break;\n\n        default:\n            BUG(\"destroy_node: invalid type\");\n    }\n\n    // Free ourself on the way up\n    free(n);\n}\n\n/**\n * Destroys an ART tree\n * @return 0 on success.\n */\nint art_tree_destroy(art_tree *t) {\n    destroy_node(t->root);\n    return 0;\n}\n\n/**\n * Returns the size of the ART tree.\n */\n\n#ifndef BROKEN_GCC_C99_INLINE\nextern inline uint64_t art_size(art_tree *t);\n#endif\n\nstatic art_node** find_child(art_node *n, unsigned char c) {\n    int i, mask, bitfield;\n    union {\n        art_node4 *p1;\n        art_node16 *p2;\n        art_node32 *p3;\n        art_node48 *p4;\n        art_node256 *p5;\n    } p;\n    switch (n->type) {\n        case NODE4:\n            p.p1 = (art_node4*)n;\n            for (i=0 ; i < n->num_children; i++) {\n        /* this cast works around a bug in gcc 5.1 when unrolling loops\n         * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124\n         */\n                if (((unsigned char*)p.p1->keys)[i] == c)\n                    return &p.p1->children[i];\n            }\n            break;\n\n        {\n        case NODE16:\n            p.p2 = (art_node16*)n;\n\n            // support non-86 architectures\n\n            // #ifdef __i386__\n            //     // Compare the key to all 16 stored keys\n            //     __m128i cmp;\n            //     cmp = _mm_cmpeq_epi8(_mm_set1_epi8(c),\n            //             _mm_loadu_si128((__m128i*)p.p2->keys));\n                \n            //     // Use a mask to ignore children that don't exist\n            //     mask = (1 << n->num_children) - 1;\n            //     bitfield = _mm_movemask_epi8(cmp) & mask;\n            // #else\n            #ifdef __amd64__\n                // Compare the key to all 16 stored keys\n                __m128i cmp;\n                cmp = _mm_cmpeq_epi8(_mm_set1_epi8(c),\n                        _mm_loadu_si128((__m128i*)p.p2->keys));\n\n                // Use a mask to ignore children that don't exist\n                mask = (1 << n->num_children) - 1;\n                bitfield = _mm_movemask_epi8(cmp) & mask;\n            #else\n                // Compare the key to all 16 stored keys\n                bitfield = 0;\n                for (i = 0; i < 16; ++i) {\n                    if (p.p2->keys[i] == c)\n                        bitfield |= (1 << i);\n                }\n\n                // Use a mask to ignore children that don't exist\n                mask = (1 << n->num_children) - 1;\n                bitfield &= mask;\n            #endif\n            // #endif\n\n            /*\n             * If we have a match (any bit set) then we can\n             * return the pointer match using ctz to get\n             * the index.\n             */\n            if (bitfield)\n                return &p.p2->children[__builtin_ctz(bitfield)];\n            break;\n        }\n\n        case NODE32:\n            p.p3 = (art_node32*)n;\n            // Linear search for NODE32\n            for (i = 0; i < n->num_children; i++) {\n                if (p.p3->keys[i] == c)\n                    return &p.p3->children[i];\n            }\n            break;\n\n        case NODE48:\n            p.p4 = (art_node48*)n;\n            i = p.p4->keys[c];\n            if (i)\n                return &p.p4->children[i-1];\n            break;\n\n        case NODE256:\n            p.p5 = (art_node256*)n;\n            if (p.p5->children[c])\n                return &p.p5->children[c];\n            break;\n\n        default:\n            BUG(\"find_child: invalid type\");\n    }\n    return NULL;\n}\n\n// Simple inlined if\nstatic inline int min(int a, int b) {\n    return (a < b) ? a : b;\n}\n\n/**\n * Returns the number of prefix characters shared between\n * the key and node.\n */\nstatic int check_prefix(const art_node *n, const unsigned char *key, int key_len, int depth) {\n    int max_cmp = min(min(n->partial_len, MAX_PREFIX_LEN), key_len - depth);\n    int idx;\n    for (idx=0; idx < max_cmp; idx++) {\n        if (n->partial[idx] != key[depth+idx])\n            return idx;\n    }\n    return idx;\n}\n\n/**\n * Checks if a leaf matches\n * @return 0 on success.\n */\nstatic int leaf_matches(const art_leaf *n, const unsigned char *key, int key_len, int depth) {\n    (void)depth;\n    // Fail if the key lengths are different\n    if (n->key_len != (uint32_t)key_len) return 1;\n\n    // Compare the keys starting at the depth\n    return memcmp(n->key, key, key_len);\n}\n\n/**\n * Searches for a value in the ART tree\n * @arg t The tree\n * @arg key The key\n * @arg key_len The length of the key\n * @return NULL if the item was not found, otherwise\n * the value pointer is returned.\n */\nvoid* art_search(const art_tree *t, const unsigned char *key, int key_len) {\n    art_node **child;\n    art_node *n = t->root;\n    int prefix_len, depth = 0;\n    while (n) {\n        // Might be a leaf\n        if (IS_LEAF(n)) {\n            n = (art_node*)LEAF_RAW(n);\n            // Check if the expanded path matches\n            if (!leaf_matches((art_leaf*)n, key, key_len, depth)) {\n                return ((art_leaf*)n)->value;\n            }\n            return NULL;\n        }\n\n        // Bail if the prefix does not match\n        if (n->partial_len) {\n            prefix_len = check_prefix(n, key, key_len, depth);\n            if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len))\n                return NULL;\n            depth = depth + n->partial_len;\n        }\n\n        // Recursively search\n        child = find_child(n, key[depth]);\n        n = (child) ? *child : NULL;\n        depth++;\n    }\n    return NULL;\n}\n\n// Find the minimum leaf under a node\nstatic art_leaf* minimum(const art_node *n) {\n    // Handle base cases\n    if (!n) return NULL;\n    if (IS_LEAF(n)) return LEAF_RAW(n);\n\n    int idx;\n    switch (n->type) {\n        case NODE4:\n            return minimum(((const art_node4*)n)->children[0]);\n        case NODE16:\n            return minimum(((const art_node16*)n)->children[0]);\n        case NODE32:\n            return minimum(((const art_node32*)n)->children[0]);\n        case NODE48:\n            idx=0;\n            while (!((const art_node48*)n)->keys[idx]) idx++;\n            idx = ((const art_node48*)n)->keys[idx] - 1;\n            return minimum(((const art_node48*)n)->children[idx]);\n        case NODE256:\n            idx=0;\n            while (!((const art_node256*)n)->children[idx]) idx++;\n            return minimum(((const art_node256*)n)->children[idx]);\n        default:\n            BUG(\"minimum: invalid type\");\n            return NULL;\n    }\n}\n\n// Find the maximum leaf under a node\nstatic art_leaf* maximum(const art_node *n) {\n    // Handle base cases\n    if (!n) return NULL;\n    if (IS_LEAF(n)) return LEAF_RAW(n);\n\n    int idx;\n    switch (n->type) {\n        case NODE4:\n            return maximum(((const art_node4*)n)->children[n->num_children-1]);\n        case NODE16:\n            return maximum(((const art_node16*)n)->children[n->num_children-1]);\n        case NODE32:\n            return maximum(((const art_node32*)n)->children[n->num_children-1]);\n        case NODE48:\n            idx=255;\n            while (!((const art_node48*)n)->keys[idx]) idx--;\n            idx = ((const art_node48*)n)->keys[idx] - 1;\n            return maximum(((const art_node48*)n)->children[idx]);\n        case NODE256:\n            idx=255;\n            while (!((const art_node256*)n)->children[idx]) idx--;\n            return maximum(((const art_node256*)n)->children[idx]);\n        default:\n            BUG(\"maximum: invalid type\");\n            return NULL;\n    }\n}\n\n/**\n * Returns the minimum valued leaf\n */\nart_leaf* art_minimum(art_tree *t) {\n    return minimum((art_node*)t->root);\n}\n\n/**\n * Returns the maximum valued leaf\n */\nart_leaf* art_maximum(art_tree *t) {\n    return maximum((art_node*)t->root);\n}\n\nstatic art_leaf* make_leaf(const unsigned char *key, int key_len, void *value) {\n    art_leaf *l = (art_leaf*)calloc(1, sizeof(art_leaf)+key_len+1);\n    if (l == NULL) {\n\t\treturn NULL;\n\t}\n    \n    l->value = value;\n    l->key_len = key_len;\n    memcpy(l->key, key, key_len);\n    return l;\n}\n\nstatic int longest_common_prefix(art_leaf *l1, art_leaf *l2, int depth) {\n    int max_cmp = min(l1->key_len, l2->key_len) - depth;\n    int idx;\n    for (idx=0; idx < max_cmp; idx++) {\n        if (l1->key[depth+idx] != l2->key[depth+idx])\n            return idx;\n    }\n    return idx;\n}\n\nstatic void copy_header(art_node *dest, art_node *src) {\n    dest->num_children = src->num_children;\n    dest->partial_len = src->partial_len;\n    memcpy(dest->partial, src->partial, min(MAX_PREFIX_LEN, src->partial_len));\n}\n\nstatic void add_child256(art_node256 *n, art_node **ref, unsigned char c, void *child) {\n    (void)ref;\n    n->n.num_children++;\n    n->children[c] = (art_node*)child;\n}\n\nstatic void add_child48(art_node48 *n, art_node **ref, unsigned char c, void *child) {\n    if (n->n.num_children < 48) {\n        int pos = 0;\n        while (n->children[pos]) pos++;\n        n->children[pos] = (art_node*)child;\n        n->keys[c] = pos + 1;\n        n->n.num_children++;\n    } else {\n        art_node256 *new_node = (art_node256*)alloc_node(NODE256);\n        int i;\n        for (i=0;i<256;i++) {\n            if (n->keys[i]) {\n                new_node->children[i] = n->children[n->keys[i] - 1];\n            }\n        }\n        copy_header((art_node*)new_node, (art_node*)n);\n        *ref = (art_node*)new_node;\n        free(n);\n        add_child256(new_node, ref, c, child);\n    }\n}\n\nstatic void add_child32(art_node32 *n, art_node **ref, unsigned char c, void *child) {\n    if (n->n.num_children < 32) {\n        unsigned idx;\n        // Find insertion point\n        for (idx = 0; idx < n->n.num_children; idx++) {\n            if (c < n->keys[idx]) break;\n        }\n\n        // Shift to make room\n        memmove(n->keys+idx+1, n->keys+idx, n->n.num_children - idx);\n        memmove(n->children+idx+1, n->children+idx,\n                (n->n.num_children - idx)*sizeof(void*));\n\n        // Insert element\n        n->keys[idx] = c;\n        n->children[idx] = (art_node*)child;\n        n->n.num_children++;\n\n    } else {\n        art_node48 *new_node = (art_node48*)alloc_node(NODE48);\n        int i;\n\n        // Copy the child pointers and populate the key map\n        memcpy(new_node->children, n->children,\n                sizeof(void*)*n->n.num_children);\n        for (i=0;i<n->n.num_children;i++) {\n            new_node->keys[n->keys[i]] = i + 1;\n        }\n        copy_header((art_node*)new_node, (art_node*)n);\n        *ref = (art_node*)new_node;\n        free(n);\n        add_child48(new_node, ref, c, child);\n    }\n}\n\nstatic void add_child16(art_node16 *n, art_node **ref, unsigned char c, void *child) {\n    if (n->n.num_children < 16) {\n        unsigned mask = (1 << n->n.num_children) - 1;\n        \n        // support non-x86 architectures\n        // #ifdef __i386__\n        //     __m128i cmp;\n\n        //     // Compare the key to all 16 stored keys\n        //     cmp = _mm_cmplt_epi8(_mm_set1_epi8(c),\n        //             _mm_loadu_si128((__m128i*)n->keys));\n\n        //     // Use a mask to ignore children that don't exist\n        //     unsigned bitfield = _mm_movemask_epi8(cmp) & mask;\n        // #else\n        #ifdef __amd64__\n            __m128i cmp;\n\n            // Compare the key to all 16 stored keys\n            cmp = _mm_cmplt_epi8(_mm_set1_epi8(c),\n                    _mm_loadu_si128((__m128i*)n->keys));\n\n            // Use a mask to ignore children that don't exist\n            unsigned bitfield = _mm_movemask_epi8(cmp) & mask;\n        #else\n            // Compare the key to all 16 stored keys\n            unsigned bitfield = 0;\n            int i;\n            for (i = 0; i < 16; ++i) {\n                if (c < n->keys[i])\n                    bitfield |= (1 << i);\n            }\n\n            // Use a mask to ignore children that don't exist\n            bitfield &= mask;    \n        #endif\n        // #endif\n\n        // Check if less than any\n        unsigned idx;\n        if (bitfield) {\n            idx = __builtin_ctz(bitfield);\n            memmove(n->keys+idx+1,n->keys+idx,n->n.num_children-idx);\n            memmove(n->children+idx+1,n->children+idx,\n                    (n->n.num_children-idx)*sizeof(void*));\n        } else\n            idx = n->n.num_children;\n\n        // Set the child\n        n->keys[idx] = c;\n        n->children[idx] = (art_node*)child;\n        n->n.num_children++;\n\n    } else {\n        art_node32 *new_node = (art_node32*)alloc_node(NODE32);\n\n        // Copy the child pointers and keys\n        memcpy(new_node->children, n->children,\n                sizeof(void*)*n->n.num_children);\n        memcpy(new_node->keys, n->keys,\n                sizeof(unsigned char)*n->n.num_children);\n        copy_header((art_node*)new_node, (art_node*)n);\n        *ref = (art_node*)new_node;\n        free(n);\n        add_child32(new_node, ref, c, child);\n    }\n}\n\nstatic void add_child4(art_node4 *n, art_node **ref, unsigned char c, void *child) {\n    if (n->n.num_children < 4) {\n        int idx;\n        for (idx=0; idx < n->n.num_children; idx++) {\n            if (c < n->keys[idx]) break;\n        }\n\n        // Shift to make room\n        memmove(n->keys+idx+1, n->keys+idx, n->n.num_children - idx);\n        memmove(n->children+idx+1, n->children+idx,\n                (n->n.num_children - idx)*sizeof(void*));\n\n        // Insert element\n        n->keys[idx] = c;\n        n->children[idx] = (art_node*)child;\n        n->n.num_children++;\n\n    } else {\n        art_node16 *new_node = (art_node16*)alloc_node(NODE16);\n\n        // Copy the child pointers and the key map\n        memcpy(new_node->children, n->children,\n                sizeof(void*)*n->n.num_children);\n        memcpy(new_node->keys, n->keys,\n                sizeof(unsigned char)*n->n.num_children);\n        copy_header((art_node*)new_node, (art_node*)n);\n        *ref = (art_node*)new_node;\n        free(n);\n        add_child16(new_node, ref, c, child);\n    }\n}\n\nstatic void add_child(art_node *n, art_node **ref, unsigned char c, void *child) {\n    switch (n->type) {\n        case NODE4:\n            return add_child4((art_node4*)n, ref, c, child);\n        case NODE16:\n            return add_child16((art_node16*)n, ref, c, child);\n        case NODE32:\n            return add_child32((art_node32*)n, ref, c, child);\n        case NODE48:\n            return add_child48((art_node48*)n, ref, c, child);\n        case NODE256:\n            return add_child256((art_node256*)n, ref, c, child);\n        default:\n            BUG(\"add_child: invalid type\");\n    }\n}\n\n/**\n * Calculates the index at which the prefixes mismatch\n */\nstatic int prefix_mismatch(const art_node *n, const unsigned char *key, int key_len, int depth) {\n    int max_cmp = min(min(MAX_PREFIX_LEN, n->partial_len), key_len - depth);\n    int idx;\n    for (idx=0; idx < max_cmp; idx++) {\n        if (n->partial[idx] != key[depth+idx])\n            return idx;\n    }\n\n    // If the prefix is short we can avoid finding a leaf\n    if (n->partial_len > MAX_PREFIX_LEN) {\n        // Prefix is longer than what we've checked, find a leaf\n        art_leaf *l = minimum(n);\n        max_cmp = min(l->key_len, key_len)- depth;\n        for (; idx < max_cmp; idx++) {\n            if (l->key[idx+depth] != key[depth+idx])\n                return idx;\n        }\n    }\n    return idx;\n}\n\nstatic void* recursive_insert(art_node *n, art_node **ref, const unsigned char *key, int key_len, void *value, int depth, int *old) {\n    // If we are at a NULL node, inject a leaf\n    if (!n) {\n        *ref = (art_node*)SET_LEAF(make_leaf(key, key_len, value));\n        return NULL;\n    }\n\n    // If we are at a leaf, we need to replace it with a node\n    if (IS_LEAF(n)) {\n        art_leaf *l = LEAF_RAW(n);\n\n        // Check if we are updating an existing value\n        if (!leaf_matches(l, key, key_len, depth)) {\n            *old = 1;\n            void *old_val = l->value;\n            l->value = value;\n            return old_val;\n        }\n\n        // New value, we must split the leaf into a node4\n        art_node4 *new_node = (art_node4*)alloc_node(NODE4);\n\n        // Create a new leaf\n        art_leaf *l2 = make_leaf(key, key_len, value);\n\n        // Determine longest prefix\n        int longest_prefix = longest_common_prefix(l, l2, depth);\n        new_node->n.partial_len = longest_prefix;\n        memcpy(new_node->n.partial, key+depth, min(MAX_PREFIX_LEN, longest_prefix));\n        // Add the leafs to the new node4\n        *ref = (art_node*)new_node;\n        add_child4(new_node, ref, l->key[depth+longest_prefix], SET_LEAF(l));\n        add_child4(new_node, ref, l2->key[depth+longest_prefix], SET_LEAF(l2));\n        return NULL;\n    }\n\n    // Check if given node has a prefix\n    if (n->partial_len) {\n        // Determine if the prefixes differ, since we need to split\n        int prefix_diff = prefix_mismatch(n, key, key_len, depth);\n        if ((uint32_t)prefix_diff >= n->partial_len) {\n            depth += n->partial_len;\n            goto RECURSE_SEARCH;\n        }\n\n        // Create a new node\n        art_node4 *new_node = (art_node4*)alloc_node(NODE4);\n        *ref = (art_node*)new_node;\n        new_node->n.partial_len = prefix_diff;\n        memcpy(new_node->n.partial, n->partial, min(MAX_PREFIX_LEN, prefix_diff));\n\n        // Adjust the prefix of the old node\n        if (n->partial_len <= MAX_PREFIX_LEN) {\n            add_child4(new_node, ref, n->partial[prefix_diff], n);\n            n->partial_len -= (prefix_diff+1);\n            memmove(n->partial, n->partial+prefix_diff+1,\n                    min(MAX_PREFIX_LEN, n->partial_len));\n        } else {\n            n->partial_len -= (prefix_diff+1);\n            art_leaf *l = minimum(n);\n            add_child4(new_node, ref, l->key[depth+prefix_diff], n);\n            memcpy(n->partial, l->key+depth+prefix_diff+1,\n                    min(MAX_PREFIX_LEN, n->partial_len));\n        }\n\n        // Insert the new leaf\n        art_leaf *l = make_leaf(key, key_len, value);\n        add_child4(new_node, ref, key[depth+prefix_diff], SET_LEAF(l));\n        return NULL;\n    }\n\nRECURSE_SEARCH:;\n\n    // Find a child to recurse to\n    art_node **child = find_child(n, key[depth]);\n    if (child) {\n        return recursive_insert(*child, child, key, key_len, value, depth+1, old);\n    }\n\n    // No child, node goes within us\n    art_leaf *l = make_leaf(key, key_len, value);\n    add_child(n, ref, key[depth], SET_LEAF(l));\n    return NULL;\n}\n\n/**\n * Inserts a new value into the ART tree\n * @arg t The tree\n * @arg key The key\n * @arg key_len The length of the key\n * @arg value Opaque value.\n * @return NULL if the item was newly inserted, otherwise\n * the old value pointer is returned.\n */\nvoid* art_insert(art_tree *t, const unsigned char *key, int key_len, void *value) {\n    int old_val = 0;\n    void *old = recursive_insert(t->root, &t->root, key, key_len, value, 0, &old_val);\n    if (!old_val) t->size++;\n    return old;\n}\n\nstatic void remove_child256(art_node256 *n, art_node **ref, unsigned char c) {\n    n->children[c] = NULL;\n    n->n.num_children--;\n\n    // Resize to a node48 on underflow, not immediately to prevent\n    // trashing if we sit on the 48/49 boundary\n    if (n->n.num_children == 37) {\n        art_node48 *new_node = (art_node48*)alloc_node(NODE48);\n        *ref = (art_node*)new_node;\n        copy_header((art_node*)new_node, (art_node*)n);\n\n        int pos = 0;\n        int i;\n        for (i=0;i<256;i++) {\n            if (n->children[i]) {\n                new_node->children[pos] = n->children[i];\n                new_node->keys[i] = pos + 1;\n                pos++;\n            }\n        }\n        free(n);\n    }\n}\n\nstatic void remove_child48(art_node48 *n, art_node **ref, unsigned char c) {\n    int pos = n->keys[c];\n    n->keys[c] = 0;\n    n->children[pos-1] = NULL;\n    n->n.num_children--;\n\n    if (n->n.num_children == 31) {\n        art_node32 *new_node = (art_node32*)alloc_node(NODE32);\n        *ref = (art_node*)new_node;\n        copy_header((art_node*)new_node, (art_node*)n);\n\n        int child = 0;\n        int i;\n        for (i=0;i<256;i++) {\n            pos = n->keys[i];\n            if (pos) {\n                new_node->keys[child] = i;\n                new_node->children[child] = n->children[pos - 1];\n                child++;\n            }\n        }\n        free(n);\n    }\n}\n\nstatic void remove_child32(art_node32 *n, art_node **ref, art_node **l) {\n    int pos = l - n->children;\n    memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos);\n    memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*));\n    n->n.num_children--;\n\n    if (n->n.num_children == 15) {\n        art_node16 *new_node = (art_node16*)alloc_node(NODE16);\n        *ref = (art_node*)new_node;\n        copy_header((art_node*)new_node, (art_node*)n);\n        memcpy(new_node->keys, n->keys, 16);\n        memcpy(new_node->children, n->children, 16*sizeof(void*));\n        free(n);\n    }\n}\n\nstatic void remove_child16(art_node16 *n, art_node **ref, art_node **l) {\n    int pos = l - n->children;\n    memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos);\n    memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*));\n    n->n.num_children--;\n\n    if (n->n.num_children == 3) {\n        art_node4 *new_node = (art_node4*)alloc_node(NODE4);\n        *ref = (art_node*)new_node;\n        copy_header((art_node*)new_node, (art_node*)n);\n        memcpy(new_node->keys, n->keys, 4);\n        memcpy(new_node->children, n->children, 4*sizeof(void*));\n        free(n);\n    }\n}\n\nstatic void remove_child4(art_node4 *n, art_node **ref, art_node **l) {\n    int pos = l - n->children;\n    memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos);\n    memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*));\n    n->n.num_children--;\n\n    // Remove nodes with only a single child\n    if (n->n.num_children == 1) {\n        art_node *child = n->children[0];\n        if (!IS_LEAF(child)) {\n            // Concatenate the prefixes\n            int prefix = n->n.partial_len;\n            if (prefix < MAX_PREFIX_LEN) {\n                n->n.partial[prefix] = n->keys[0];\n                prefix++;\n            }\n            if (prefix < MAX_PREFIX_LEN) {\n                int sub_prefix = min(child->partial_len, MAX_PREFIX_LEN - prefix);\n                memcpy(n->n.partial+prefix, child->partial, sub_prefix);\n                prefix += sub_prefix;\n            }\n\n            // Store the prefix in the child\n            memcpy(child->partial, n->n.partial, min(prefix, MAX_PREFIX_LEN));\n            child->partial_len += n->n.partial_len + 1;\n        }\n        *ref = child;\n        free(n);\n    }\n}\n\nstatic void remove_child(art_node *n, art_node **ref, unsigned char c, art_node **l) {\n    switch (n->type) {\n        case NODE4:\n            return remove_child4((art_node4*)n, ref, l);\n        case NODE16:\n            return remove_child16((art_node16*)n, ref, l);\n        case NODE32:\n            return remove_child32((art_node32*)n, ref, l);\n        case NODE48:\n            return remove_child48((art_node48*)n, ref, c);\n        case NODE256:\n            return remove_child256((art_node256*)n, ref, c);\n        default:\n            BUG(\"remove_child: invalid type\");\n    }\n}\n\nstatic art_leaf* recursive_delete(art_node *n, art_node **ref, const unsigned char *key, int key_len, int depth) {\n    // Search terminated\n    if (!n) return NULL;\n\n    // Handle hitting a leaf node\n    if (IS_LEAF(n)) {\n        art_leaf *l = LEAF_RAW(n);\n        if (!leaf_matches(l, key, key_len, depth)) {\n            *ref = NULL;\n            return l;\n        }\n        return NULL;\n    }\n\n    // Bail if the prefix does not match\n    if (n->partial_len) {\n        int prefix_len = check_prefix(n, key, key_len, depth);\n        if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len)) {\n            return NULL;\n        }\n        depth = depth + n->partial_len;\n    }\n\n    // Find child node\n    art_node **child = find_child(n, key[depth]);\n    if (!child) return NULL;\n\n    // If the child is leaf, delete from this node\n    if (IS_LEAF(*child)) {\n        art_leaf *l = LEAF_RAW(*child);\n        if (!leaf_matches(l, key, key_len, depth)) {\n            remove_child(n, ref, key[depth], child);\n            return l;\n        }\n        return NULL;\n\n    // Recurse\n    } else {\n        return recursive_delete(*child, child, key, key_len, depth+1);\n    }\n}\n\n/**\n * Deletes a value from the ART tree\n * @arg t The tree\n * @arg key The key\n * @arg key_len The length of the key\n * @return NULL if the item was not found, otherwise\n * the value pointer is returned.\n */\nvoid* art_delete(art_tree *t, const unsigned char *key, int key_len) {\n    art_leaf *l = recursive_delete(t->root, &t->root, key, key_len, 0);\n    if (l) {\n        t->size--;\n        void *old = l->value;\n        free(l);\n        return old;\n    }\n    return NULL;\n}\n\n// Recursively iterates over the tree\nstatic int recursive_iter(art_node *n, art_callback cb, void *data) {\n    // Handle base cases\n    if (!n) return 0;\n    if (IS_LEAF(n)) {\n        art_leaf *l = LEAF_RAW(n);\n        return cb(data, (const unsigned char*)l->key, l->key_len, l->value);\n    }\n\n    int idx, res;\n    int i;\n    switch (n->type) {\n        case NODE4:\n            for (i=0; i < n->num_children; i++) {\n                res = recursive_iter(((art_node4*)n)->children[i], cb, data);\n                if (res) return res;\n            }\n            break;\n\n        case NODE16:\n            for (i=0; i < n->num_children; i++) {\n                res = recursive_iter(((art_node16*)n)->children[i], cb, data);\n                if (res) return res;\n            }\n            break;\n\n        case NODE32:\n            for (i=0; i < n->num_children; i++) {\n                res = recursive_iter(((art_node32*)n)->children[i], cb, data);\n                if (res) return res;\n            }\n            break;\n\n        case NODE48:\n            for (i=0; i < 256; i++) {\n                idx = ((art_node48*)n)->keys[i];\n                if (!idx) continue;\n\n                res = recursive_iter(((art_node48*)n)->children[idx-1], cb, data);\n                if (res) return res;\n            }\n            break;\n\n        case NODE256:\n            for (i=0; i < 256; i++) {\n                if (!((art_node256*)n)->children[i]) continue;\n                res = recursive_iter(((art_node256*)n)->children[i], cb, data);\n                if (res) return res;\n            }\n            break;\n\n        default:\n            BUG(\"recursive_iter: invalid type\");\n    }\n    return 0;\n}\n\n/**\n * Iterates through the entries pairs in the map,\n * invoking a callback for each. The call back gets a\n * key, value for each and returns an integer stop value.\n * If the callback returns non-zero, then the iteration stops.\n * @arg t The tree to iterate over\n * @arg cb The callback function to invoke\n * @arg data Opaque handle passed to the callback\n * @return 0 on success, or the return of the callback.\n */\nint art_iter(art_tree *t, art_callback cb, void *data) {\n    return recursive_iter(t->root, cb, data);\n}\n\n/**\n * Checks if a leaf prefix matches\n * @return 0 on success.\n */\nstatic int leaf_prefix_matches(const art_leaf *n, const unsigned char *prefix, int prefix_len) {\n    // Fail if the key length is too short\n    if (n->key_len < (uint32_t)prefix_len) return 1;\n\n    // Compare the keys\n    return memcmp(n->key, prefix, prefix_len);\n}\n\n/**\n * Iterates through the entries pairs in the map,\n * invoking a callback for each that matches a given prefix.\n * The call back gets a key, value for each and returns an integer stop value.\n * If the callback returns non-zero, then the iteration stops.\n * @arg t The tree to iterate over\n * @arg prefix The prefix of keys to read\n * @arg prefix_len The length of the prefix\n * @arg cb The callback function to invoke\n * @arg data Opaque handle passed to the callback\n * @return 0 on success, or the return of the callback.\n */\nint art_iter_prefix(art_tree *t, const unsigned char *key, int key_len, art_callback cb, void *data) {\n    art_node **child;\n    art_node *n = t->root;\n    int prefix_len, depth = 0;\n    while (n) {\n        // Might be a leaf\n        if (IS_LEAF(n)) {\n            n = (art_node*)LEAF_RAW(n);\n            // Check if the expanded path matches\n            if (!leaf_prefix_matches((art_leaf*)n, key, key_len)) {\n                art_leaf *l = (art_leaf*)n;\n                return cb(data, (const unsigned char*)l->key, l->key_len, l->value);\n            }\n            return 0;\n        }\n\n        // If the depth matches the prefix, we need to handle this node\n        if (depth == key_len) {\n            art_leaf *l = minimum(n);\n            if (!leaf_prefix_matches(l, key, key_len))\n               return recursive_iter(n, cb, data);\n            return 0;\n        }\n\n        // Bail if the prefix does not match\n        if (n->partial_len) {\n            prefix_len = prefix_mismatch(n, key, key_len, depth);\n\n            // Guard if the mis-match is longer than the MAX_PREFIX_LEN\n            if ((uint32_t)prefix_len > n->partial_len) {\n                prefix_len = n->partial_len;\n            }\n\n            // If there is no match, search is terminated\n            if (!prefix_len) {\n                return 0;\n\n            // If we've matched the prefix, iterate on this node\n            } else if (depth + prefix_len == key_len) {\n                return recursive_iter(n, cb, data);\n            }\n\n            // if there is a full match, go deeper\n            depth = depth + n->partial_len;\n        }\n\n        // Recursively search\n        child = find_child(n, key[depth]);\n        n = (child) ? *child : NULL;\n        depth++;\n    }\n    return 0;\n}\n\nstatic int str_prefix_matches(const art_leaf *n, const unsigned char *str, int str_len) {\n    // Fail if the key length is too short\n    if (n->key_len > (uint32_t)str_len) return 1;\n\n    // Compare the keys\n    return memcmp(str, n->key, n->key_len);\n}\n\nstatic void art_copy_key(art_leaf *leaf, unsigned char *key, int *key_len)\n{\n\tint len;\n\n    if (key == NULL || key_len == NULL) {\n\t\treturn;\n\t}\n\n\tlen = (int)leaf->key_len > *key_len ? *key_len : (int)leaf->key_len;\n\tmemcpy(key, leaf->key, len);\n\t*key_len = len;\n}\n\nvoid *art_substring(const art_tree *t, const unsigned char *str, int str_len, unsigned char *key, int *key_len)\n{\n    art_node **child;\n    art_node *n = t->root;\n    art_node *m;    \n    art_leaf *found = NULL;\n    int prefix_len, depth = 0;\n\n    while (n) {\n        // Might be a leaf\n        if (IS_LEAF(n)) {\n            n = (art_node*)LEAF_RAW(n);\n            // Check if the expanded path matches\n            if (!str_prefix_matches((art_leaf*)n, str, str_len)) {\n                found = (art_leaf*)n;\n\t\t\t}\n            break;\n        }\n\n        // Check if current is leaf\n    \tchild = find_child(n, 0);\n    \tm = (child) ? *child : NULL;\n    \tif (m && IS_LEAF(m)) {\n            m = (art_node*)LEAF_RAW(m);\n            // Check if the expanded path matches\n            if (!str_prefix_matches((art_leaf*)m, str, str_len)) {\n                found = (art_leaf*)m;\n            }\n    \t}\n\n        // Bail if the prefix does not match\n        if (n->partial_len) {\n            prefix_len = check_prefix(n, str, str_len, depth);\n            if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len))\n                break;\n            depth = depth + n->partial_len;\n        }\n\n        // Recursively search\n        child = find_child(n, str[depth]);\n        n = (child) ? *child : NULL;\n        depth++;\n    }\n\n    if (found == NULL) {\n        return NULL;\n    }\n\n    art_copy_key(found, key, key_len);\n\n    return found->value;\n}\n\nvoid art_substring_walk(const art_tree *t, const unsigned char *str, int str_len, walk_func func, void *arg)\n{\n    art_node **child;\n    art_node *n = t->root;\n    art_node *m;    \n    art_leaf *found = NULL;\n    int prefix_len, depth = 0;\n\tint stop_search = 0;\n\n\twhile (n && stop_search == 0) {\n        // Might be a leaf\n        if (IS_LEAF(n)) {\n            n = (art_node*)LEAF_RAW(n);\n            // Check if the expanded path matches\n            if (!str_prefix_matches((art_leaf*)n, str, str_len)) {\n                found = (art_leaf*)n;\n\t\t\t\tfunc(found->key, found->key_len, found->key_len != (uint32_t)str_len, found->value, arg);\n\t\t\t}\n            break;\n        }\n\n        // Check if current is leaf\n    \tchild = find_child(n, 0);\n    \tm = (child) ? *child : NULL;\n    \tif (m && IS_LEAF(m)) {\n            m = (art_node*)LEAF_RAW(m);\n            // Check if the expanded path matches\n            if (!str_prefix_matches((art_leaf*)m, str, str_len)) {\n                found = (art_leaf*)m;\n                stop_search = func(found->key, found->key_len, found->key_len != (uint32_t)str_len, found->value, arg);\n            }\n    \t}\n\n        // Bail if the prefix does not match\n        if (n->partial_len) {\n            prefix_len = check_prefix(n, str, str_len, depth);\n            if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len))\n                break;\n            depth = depth + n->partial_len;\n        }\n\n        // Recursively search\n        child = find_child(n, str[depth]);\n        n = (child) ? *child : NULL;\n        depth++;\n    }\n\n    if (found == NULL) {\n        return ;\n    }\n\n    return ;\n}\n"
  },
  {
    "path": "src/lib/bitops.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/lib/bitmap.h\"\n#include \"smartdns/lib/bitops.h\"\n\n/*\n * This is a common helper function for find_next_bit, find_next_zero_bit, and\n * find_next_and_bit. The differences are:\n *  - The \"invert\" argument, which is XORed with each fetched word before\n *    searching it for one bits.\n *  - The optional \"addr2\", which is addr2 with \"addr1\" if present.\n */\nstatic inline unsigned long _find_next_bit(const unsigned long *addr1,\n\t\tconst unsigned long *addr2, unsigned long nbits,\n\t\tunsigned long start, unsigned long invert)\n{\n\tunsigned long tmp;\n\n\tif (unlikely(start >= nbits))\n\t\treturn nbits;\n\n\ttmp = addr1[start / BITS_PER_LONG];\n\tif (addr2)\n\t\ttmp &= addr2[start / BITS_PER_LONG];\n\ttmp ^= invert;\n\n\t/* Handle 1st word. */\n\ttmp &= BITMAP_FIRST_WORD_MASK(start);\n\tstart = round_down(start, BITS_PER_LONG);\n\n\twhile (!tmp) {\n\t\tstart += BITS_PER_LONG;\n\t\tif (start >= nbits)\n\t\t\treturn nbits;\n\n\t\ttmp = addr1[start / BITS_PER_LONG];\n\t\tif (addr2)\n\t\t\ttmp &= addr2[start / BITS_PER_LONG];\n\t\ttmp ^= invert;\n\t}\n\n\treturn min(start + __ffs(tmp), nbits);\n}\n\n/*\n * Find the next set bit in a memory region.\n */\nunsigned long find_next_bit(const unsigned long *addr, unsigned long size,\n\t\t\t    unsigned long offset)\n{\n\treturn _find_next_bit(addr, NULL, size, offset, 0UL);\n}\n\n/*\n * Find the first set bit in a memory region.\n */\nunsigned long find_first_bit(const unsigned long *addr, unsigned long size)\n{\n\tunsigned long idx;\n\n\tfor (idx = 0; idx * BITS_PER_LONG < size; idx++) {\n\t\tif (addr[idx])\n\t\t\treturn min(idx * BITS_PER_LONG + __ffs(addr[idx]), size);\n\t}\n\n\treturn size;\n}\n\n/*\n * Find the first cleared bit in a memory region.\n */\nunsigned long find_first_zero_bit(const unsigned long *addr, unsigned long size)\n{\n\tunsigned long idx;\n\n\tfor (idx = 0; idx * BITS_PER_LONG < size; idx++) {\n\t\tif (addr[idx] != ~0UL)\n\t\t\treturn min(idx * BITS_PER_LONG + ffz(addr[idx]), size);\n\t}\n\n\treturn size;\n}\n\nunsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size,\n\t\t\t\t unsigned long offset)\n{\n\treturn _find_next_bit(addr, NULL, size, offset, ~0UL);\n}\n\nunsigned long find_next_and_bit(const unsigned long *addr1,\n\t\tconst unsigned long *addr2, unsigned long size,\n\t\tunsigned long offset)\n{\n\treturn _find_next_bit(addr1, addr2, size, offset, 0UL);\n}\n\n/**\n * hweightN - returns the hamming weight of a N-bit word\n * @x: the word to weigh\n *\n * The Hamming Weight of a number is the total number of bits set in it.\n */\n\nunsigned int __sw_hweight32(unsigned int w)\n{\n#ifdef CONFIG_ARCH_HAS_FAST_MULTIPLIER\n\tw -= (w >> 1) & 0x55555555;\n\tw =  (w & 0x33333333) + ((w >> 2) & 0x33333333);\n\tw =  (w + (w >> 4)) & 0x0f0f0f0f;\n\treturn (w * 0x01010101) >> 24;\n#else\n\tunsigned int res = w - ((w >> 1) & 0x55555555);\n\tres = (res & 0x33333333) + ((res >> 2) & 0x33333333);\n\tres = (res + (res >> 4)) & 0x0F0F0F0F;\n\tres = res + (res >> 8);\n\treturn (res + (res >> 16)) & 0x000000FF;\n#endif\n}\n\nunsigned int __sw_hweight16(unsigned int w)\n{\n\tunsigned int res = w - ((w >> 1) & 0x5555);\n\tres = (res & 0x3333) + ((res >> 2) & 0x3333);\n\tres = (res + (res >> 4)) & 0x0F0F;\n\treturn (res + (res >> 8)) & 0x00FF;\n}\n\nunsigned int __sw_hweight8(unsigned int w)\n{\n\tunsigned int res = w - ((w >> 1) & 0x55);\n\tres = (res & 0x33) + ((res >> 2) & 0x33);\n\treturn (res + (res >> 4)) & 0x0F;\n}\n\nunsigned long __sw_hweight64(uint64_t w)\n{\n#if BITS_PER_LONG == 32\n\treturn __sw_hweight32((unsigned int)(w >> 32)) +\n\t       __sw_hweight32((unsigned int)w);\n#elif BITS_PER_LONG == 64\n#ifdef CONFIG_ARCH_HAS_FAST_MULTIPLIER\n\tw -= (w >> 1) & 0x5555555555555555ul;\n\tw =  (w & 0x3333333333333333ul) + ((w >> 2) & 0x3333333333333333ul);\n\tw =  (w + (w >> 4)) & 0x0f0f0f0f0f0f0f0ful;\n\treturn (w * 0x0101010101010101ul) >> 56;\n#else\n\tuint64_t res = w - ((w >> 1) & 0x5555555555555555ul);\n\tres = (res & 0x3333333333333333ul) + ((res >> 2) & 0x3333333333333333ul);\n\tres = (res + (res >> 4)) & 0x0F0F0F0F0F0F0F0Ful;\n\tres = res + (res >> 8);\n\tres = res + (res >> 16);\n\treturn (res + (res >> 32)) & 0x00000000000000FFul;\n#endif\n#endif\n\treturn 0;\n}"
  },
  {
    "path": "src/lib/conf.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/lib/conf.h\"\n#include <errno.h>\n#include <getopt.h>\n#include <libgen.h>\n#include <linux/limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\nstatic const char *current_conf_file = NULL;\nstatic int current_conf_lineno = 0;\n\nconst char *conf_get_conf_file(void)\n{\n\treturn current_conf_file;\n}\n\nint conf_get_current_lineno(void)\n{\n\treturn current_conf_lineno;\n}\n\nstatic char *get_dir_name(char *path)\n{\n\tif (strstr(path, \"/\") == NULL) {\n\t\tstrncpy(path, \"./\", PATH_MAX);\n\t\treturn path;\n\t}\n\n\treturn dirname(path);\n}\n\nconst char *conf_get_conf_fullpath(const char *path, char *fullpath, size_t path_len)\n{\n\tchar file_path_dir[PATH_MAX];\n\tconst char *conf_file = NULL;\n\n\tif (path_len < 1) {\n\t\treturn NULL;\n\t}\n\n\tif (path[0] == '/') {\n\t\tstrncpy(fullpath, path, path_len);\n\t\treturn fullpath;\n\t}\n\n\tconf_file = conf_get_conf_file();\n\tif (conf_file == NULL) {\n\t\tstrncpy(fullpath, path, path_len);\n\t\treturn fullpath;\n\t}\n\n\tstrncpy(file_path_dir, conf_file, PATH_MAX - 1);\n\tfile_path_dir[PATH_MAX - 1] = 0;\n\tget_dir_name(file_path_dir);\n\tif (file_path_dir[0] == '\\0') {\n\t\tstrncpy(fullpath, path, path_len);\n\t\treturn fullpath;\n\t}\n\n\tif (snprintf(fullpath, PATH_MAX, \"%s/%s\", file_path_dir, path) < 0) {\n\t\treturn NULL;\n\t}\n\n\treturn fullpath;\n}\n\nint conf_custom(const char *item, void *data, int argc, char *argv[])\n{\n\tstruct config_item_custom *item_custom = data;\n\treturn item_custom->custom_func(item_custom->custom_data, argc, argv);\n}\n\nint conf_int(const char *item, void *data, int argc, char *argv[])\n{\n\tstruct config_item_int *item_int = data;\n\tint value = 0;\n\tif (argc < 2) {\n\t\treturn -1;\n\t}\n\n\tvalue = atoi(argv[1]);\n\n\tif (value < item_int->min) {\n\t\tvalue = item_int->min;\n\t} else if (value > item_int->max) {\n\t\tvalue = item_int->max;\n\t}\n\n\tif (item_int->func) {\n\t\treturn item_int->func(value, item_int->data);\n\t}\n\n\t*(item_int->data) = value;\n\n\treturn 0;\n}\n\nint conf_int_base(const char *item, void *data, int argc, char *argv[])\n{\n\tstruct config_item_int_base *item_int = data;\n\tint value = 0;\n\tif (argc < 2) {\n\t\treturn -1;\n\t}\n\n\tvalue = strtol(argv[1], NULL, item_int->base);\n\n\tif (value < item_int->min) {\n\t\tvalue = item_int->min;\n\t} else if (value > item_int->max) {\n\t\tvalue = item_int->max;\n\t}\n\n\tif (item_int->func) {\n\t\treturn item_int->func(value, item_int->data);\n\t}\n\n\t*(item_int->data) = value;\n\n\treturn 0;\n}\n\nint conf_string(const char *item, void *data, int argc, char *argv[])\n{\n\tstruct config_item_string *item_string = data;\n\n\tif (argc < 2) {\n\t\treturn -1;\n\t}\n\n\tif (item_string->func) {\n\t\treturn item_string->func(argv[1], item_string->data);\n\t}\n\n\tstrncpy(item_string->data, argv[1], item_string->size);\n\n\treturn 0;\n}\n\nint conf_yesno(const char *item, void *data, int argc, char *argv[])\n{\n\tstruct config_item_yesno *item_yesno = data;\n\tint yes = 0;\n\n\tif (argc < 2) {\n\t\treturn -1;\n\t}\n\n\tchar *value = argv[1];\n\tif (strncmp(\"auto\", value, sizeof(\"auto\")) == 0 || strncmp(\"AUTO\", value, sizeof(\"AUTO\")) == 0) {\n\t\treturn 0;\n\t}\n\n\tif (strncmp(\"yes\", value, sizeof(\"yes\")) == 0 || strncmp(\"YES\", value, sizeof(\"YES\")) == 0) {\n\t\tyes = 1;\n\t} else if (strncmp(\"no\", value, sizeof(\"no\")) == 0 || strncmp(\"NO\", value, sizeof(\"NO\")) == 0) {\n\t\tyes = 0;\n\t}\n\n\tif (item_yesno->func) {\n\t\treturn item_yesno->func(yes, item_yesno->data);\n\t}\n\n\t*(item_yesno->data) = yes;\n\n\treturn 0;\n}\n\nint conf_size(const char *item, void *data, int argc, char *argv[])\n{\n\tint base = 1;\n\tsize_t size = 0;\n\tint num = 0;\n\tstruct config_item_size *item_size = data;\n\tchar *value = argv[1];\n\n\tif (strstr(value, \"k\") || strstr(value, \"K\")) {\n\t\tbase = 1024;\n\t} else if (strstr(value, \"m\") || strstr(value, \"M\")) {\n\t\tbase = 1024 * 1024;\n\t} else if (strstr(value, \"g\") || strstr(value, \"G\")) {\n\t\tbase = 1024 * 1024 * 1024;\n\t}\n\n\tnum = atoi(value);\n\tif (num < 0) {\n\t\treturn -1;\n\t}\n\n\tsize = (size_t)num * base;\n\tif (size > item_size->max) {\n\t\tsize = item_size->max;\n\t} else if (size < item_size->min) {\n\t\tsize = item_size->min;\n\t}\n\n\tif (item_size->func) {\n\t\treturn item_size->func(size, item_size->data);\n\t}\n\n\t*(item_size->data) = size;\n\n\treturn 0;\n}\n\nint conf_ssize(const char *item, void *data, int argc, char *argv[])\n{\n\tint base = 1;\n\tssize_t size = 0;\n\tint num = 0;\n\tstruct config_item_ssize *item_size = data;\n\tchar *value = argv[1];\n\n\tif (strstr(value, \"k\") || strstr(value, \"K\")) {\n\t\tbase = 1024;\n\t} else if (strstr(value, \"m\") || strstr(value, \"M\")) {\n\t\tbase = 1024 * 1024;\n\t} else if (strstr(value, \"g\") || strstr(value, \"G\")) {\n\t\tbase = 1024 * 1024 * 1024;\n\t}\n\n\tnum = atoi(value);\n\tsize = (ssize_t)num * base;\n\tif (size > item_size->max) {\n\t\tsize = item_size->max;\n\t} else if (size < item_size->min) {\n\t\tsize = item_size->min;\n\t}\n\n\tif (item_size->func) {\n\t\treturn item_size->func(size, item_size->data);\n\t}\n\n\t*(item_size->data) = size;\n\n\treturn 0;\n}\n\nint conf_enum(const char *item, void *data, int argc, char *argv[])\n{\n\tstruct config_enum *item_enum = data;\n\tchar *enum_name = argv[1];\n\tint i = 0;\n\n\tif (argc <= 0) {\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; item_enum->list[i].name != NULL; i++) {\n\t\tif (strcmp(enum_name, item_enum->list[i].name) == 0) {\n\t\t\tif (item_enum->func) {\n\t\t\t\treturn item_enum->func(item_enum->list[i].id, item_enum->data);\n\t\t\t}\n\t\t\t*(item_enum->data) = item_enum->list[i].id;\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tprintf(\"Not found config value '%s', valid value is:\\n\", enum_name);\n\tfor (i = 0; item_enum->list[i].name != NULL; i++) {\n\t\tprintf(\" %s\\n\", item_enum->list[i].name);\n\t}\n\n\treturn -1;\n}\n\nvoid conf_getopt_reset(void)\n{\n\tstatic struct option long_options[] = {{\"-\", 0, NULL, 0}, {NULL, 0, NULL, 0}};\n\tint argc = 2;\n\tchar *argv[3] = {\"reset\", \"\", NULL};\n\n\toptind = 0;\n\topterr = 0;\n\toptopt = 0;\n\tgetopt_long(argc, argv, \"\", long_options, NULL);\n\toptind = 0;\n\topterr = 0;\n\toptopt = 0;\n}\n\nint conf_parse_key_values(char *line, int *key_num, char **keys, char **values)\n{\n\tint count = 0;\n\tchar *ptr = line;\n\tchar *key = NULL;\n\tchar *value = NULL;\n\tchar *field_start = NULL;\n\tint filed_stage = 0;\n\tint inquote = 0;\n\tint end = 0;\n\n\tif (line == NULL || key_num == NULL || keys == NULL || values == NULL) {\n\t\treturn -1;\n\t}\n\n\twhile (1) {\n\t\tif (*ptr == '\\'' || *ptr == '\"') {\n\t\t\tif (inquote == 0) {\n\t\t\t\tinquote = *ptr;\n\t\t\t\tptr++;\n\t\t\t\tcontinue;\n\t\t\t} else if (inquote == *ptr) {\n\t\t\t\tinquote = 0;\n\t\t\t\t*ptr = '\\0';\n\t\t\t}\n\t\t}\n\n\t\tif (field_start == NULL) {\n\t\t\tfield_start = ptr;\n\t\t}\n\n\t\tif (inquote != 0) {\n\t\t\tptr++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (*ptr == ',' || *ptr == '=' || *ptr == '\\0') {\n\t\t\tif (filed_stage == 0) {\n\t\t\t\tkey = field_start;\n\t\t\t\tif (*key == '\\0' || *key == ',') {\n\t\t\t\t\tfield_start = NULL;\n\t\t\t\t\tif (end == 1) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tptr++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tvalue = ptr;\n\t\t\t\tfiled_stage = 1;\n\t\t\t\tkeys[count] = key;\n\t\t\t\tvalues[count] = value;\n\t\t\t\tif (*ptr == '\\0' || *ptr == ',') {\n\t\t\t\t\tcount++;\n\t\t\t\t\tkey = NULL;\n\t\t\t\t\tvalue = NULL;\n\t\t\t\t\tfiled_stage = 0;\n\t\t\t\t}\n\t\t\t\t*ptr = '\\0';\n\t\t\t} else if (filed_stage == 1) {\n\t\t\t\tvalue = field_start;\n\t\t\t\tif (*ptr == '=') {\n\t\t\t\t\tgoto errout;\n\t\t\t\t}\n\t\t\t\tfiled_stage = 0;\n\t\t\t\tkeys[count] = key;\n\t\t\t\tvalues[count] = value;\n\t\t\t\tcount++;\n\t\t\t\t*ptr = '\\0';\n\t\t\t\tkey = NULL;\n\t\t\t\tvalue = NULL;\n\t\t\t}\n\n\t\t\tfield_start = NULL;\n\t\t}\n\n\t\tif (end == 1) {\n\t\t\tbreak;\n\t\t}\n\n\t\tptr++;\n\t\tif (*ptr == '\\0') {\n\t\t\tend = 1;\n\t\t}\n\t}\n\n\t*key_num = count;\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstatic int conf_parse_args(char *key, char *value, int *argc, char **argv)\n{\n\tchar *start = NULL;\n\tchar *ptr = value;\n\tint count = 0;\n\tint sep_flag = ' ';\n\n\targv[0] = key;\n\tcount++;\n\n\twhile (*ptr != '\\0') {\n\t\tif (*ptr == '\\\\') {\n\t\t\tchar *tmp = ptr + 1;\n\t\t\twhile (*tmp != '\\0') {\n\t\t\t\t*(tmp - 1) = *tmp;\n\t\t\t\ttmp++;\n\t\t\t}\n\t\t\t*(tmp - 1) = '\\0';\n\t\t\tptr++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ((*ptr == '\"' || *ptr == '\\'') && start == NULL) {\n\t\t\tsep_flag = *ptr;\n\t\t\tstart = NULL;\n\t\t}\n\n\t\tif (*ptr != sep_flag && start == NULL) {\n\t\t\tstart = ptr;\n\t\t\tptr++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (*ptr == sep_flag && start == NULL) {\n\t\t\tptr++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (*ptr == sep_flag && start) {\n\t\t\targv[count] = start;\n\t\t\t*ptr = '\\0';\n\t\t\tptr++;\n\t\t\tcount++;\n\t\t\tsep_flag = ' ';\n\t\t\tstart = NULL;\n\t\t\tcontinue;\n\t\t}\n\n\t\tptr++;\n\t}\n\n\tif (start != ptr && start) {\n\t\targv[count] = start;\n\t\tcount++;\n\t}\n\n\t*argc = count;\n\targv[count] = NULL;\n\n\treturn 0;\n}\n\nvoid load_exit(void) {}\n\nstatic int load_conf_printf(const char *key, const char *value, const char *file, int lineno, int ret)\n{\n\tif (ret != CONF_RET_OK) {\n\t\tprintf(\"process config file '%s' failed at line %d.\", file, lineno);\n\t\tif (ret == CONF_RET_ERR || ret == CONF_RET_NOENT) {\n\t\t\treturn -1;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\treturn 0;\n}\n\nstatic int load_conf_file(const char *file, const struct config_item *items, conf_error_handler handler)\n{\n\tFILE *fp = NULL;\n\tchar line[MAX_LINE_LEN + MAX_KEY_LEN];\n\tchar key[MAX_KEY_LEN];\n\tchar value[MAX_LINE_LEN];\n\tint value_begin = 0, value_end = 0;\n\tint value_len = 0;\n\tint filed_num = 0;\n\tint i = 0;\n\tint last_item_index = -1;\n\tint argc = 0;\n\tchar *argv[1024];\n\tint ret = 0;\n\tint call_ret = 0;\n\tint line_no = 0;\n\tint line_len = 0;\n\tint read_len = 0;\n\tint is_last_line_wrap = 0;\n\tint current_line_wrap = 0;\n\tint is_func_found = 0;\n\tconst char *last_file = NULL;\n\n\tif (handler == NULL) {\n\t\thandler = load_conf_printf;\n\t}\n\n\tfp = fopen(file, \"r\");\n\tif (fp == NULL) {\n\t\tfprintf(stderr, \"open config file '%s' failed, %s\\n\", file, strerror(errno));\n\t\treturn -1;\n\t}\n\n\tline_no = 0;\n\twhile (fgets(line + line_len, MAX_LINE_LEN - line_len, fp)) {\n\t\tcurrent_line_wrap = 0;\n\t\tline_no++;\n\t\tread_len = strnlen(line + line_len, sizeof(line));\n\t\tif (read_len >= 2 && *(line + line_len + read_len - 2) == '\\\\') {\n\t\t\tread_len -= 1;\n\t\t\tcurrent_line_wrap = 1;\n\t\t}\n\n\t\t/* comment in wrap line, skip */\n\t\tif (*(line + line_len) == '#') {\n\t\t\tline_len = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* trim prefix spaces in wrap line */\n\t\tif ((current_line_wrap == 1 || is_last_line_wrap == 1) && read_len > 0) {\n\t\t\tis_last_line_wrap = current_line_wrap;\n\t\t\tread_len -= 1;\n\t\t\tfor (i = 0; i < read_len; i++) {\n\t\t\t\tchar *ptr = line + line_len + i;\n\t\t\t\tif (*ptr == ' ' || *ptr == '\\t') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tmemmove(line + line_len, ptr, read_len - i + 1);\n\t\t\t\tline_len += read_len - i;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tline[line_len] = '\\0';\n\t\t\tif (current_line_wrap) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tline_len = 0;\n\t\tis_last_line_wrap = 0;\n\t\tkey[0] = '\\0';\n\t\tvalue[0] = '\\0';\n\t\tfiled_num = sscanf(line, \"%63s %n%4095[^\\r\\n]%n\", key, &value_begin, value, &value_end);\n\t\tif (filed_num <= 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* remove suffix space of value */\n\t\tvalue_len = value_end - value_begin;\n\t\tif (value_len < 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (i = value_len - 1; i >= 0; i--) {\n\t\t\tif (value[i] == ' ' || value[i] == '\\t') {\n\t\t\t\tvalue[i] = '\\0';\n\t\t\t\tvalue_len--;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t/* comment, skip */\n\t\tif (key[0] == '#') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* if field format is not key = value, error */\n\t\tif (filed_num != 2 && filed_num != 1) {\n\t\t\thandler(NULL, NULL, file, line_no, CONF_RET_BADCONF);\n\t\t\tgoto errout;\n\t\t}\n\n\t\tis_func_found = 0;\n\n\t\tfor (i = last_item_index;; i++) {\n\t\t\tif (i < 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (items[i].item == NULL) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (strncmp(items[i].item, key, MAX_KEY_LEN) != 0) {\n\t\t\t\tif (last_item_index >= 0) {\n\t\t\t\t\ti = -1;\n\t\t\t\t\tlast_item_index = -1;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (conf_parse_args(key, value, &argc, argv) != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconf_getopt_reset();\n\t\t\t/* call item function */\n\t\t\tlast_file = current_conf_file;\n\t\t\tcurrent_conf_file = file;\n\t\t\tcurrent_conf_lineno = line_no;\n\t\t\tcall_ret = items[i].item_func(items[i].item, items[i].data, argc, argv);\n\t\t\tret = handler(key, value, file, line_no, call_ret);\n\t\t\tif (ret != 0) {\n\t\t\t\tconf_getopt_reset();\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tconf_getopt_reset();\n\t\t\tif (last_file) {\n\t\t\t\tcurrent_conf_file = last_file;\n\t\t\t}\n\n\t\t\tlast_item_index = i;\n\t\t\tis_func_found = 1;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (is_func_found == 0) {\n\t\t\tif (handler(key, value, file, line_no, CONF_RET_NOENT) != 0) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t}\n\t}\n\n\tfclose(fp);\n\n\treturn 0;\nerrout:\n\tif (fp) {\n\t\tfclose(fp);\n\t}\n\treturn -1;\n}\n\nint load_conf(const char *file, const struct config_item items[], conf_error_handler handler)\n{\n\treturn load_conf_file(file, items, handler);\n}\n"
  },
  {
    "path": "src/lib/idna.c",
    "content": "\n/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n#include \"smartdns/lib/idna.h\"\n#include <limits.h>\n\nstatic unsigned _utf8_decode_slow(const char **p, const char *pe, unsigned a)\n{\n\tunsigned b;\n\tunsigned c;\n\tunsigned d;\n\tunsigned min;\n\n\tif (a > 0xF7) {\n\t\treturn -1;\n\t}\n\n\tswitch (pe - *p) {\n\tdefault:\n\t\tif (a > 0xEF) {\n\t\t\tmin = 0x10000;\n\t\t\ta = a & 7;\n\t\t\tb = (unsigned char)*(*p)++;\n\t\t\tc = (unsigned char)*(*p)++;\n\t\t\td = (unsigned char)*(*p)++;\n\t\t\tbreak;\n\t\t}\n\tcase 2:\n\t\tif (a > 0xDF) {\n\t\t\tmin = 0x800;\n\t\t\tb = 0x80 | (a & 15);\n\t\t\tc = (unsigned char)*(*p)++;\n\t\t\td = (unsigned char)*(*p)++;\n\t\t\ta = 0;\n\t\t\tbreak;\n\t\t}\n\tcase 1:\n\t\tif (a > 0xBF) {\n\t\t\tmin = 0x80;\n\t\t\tb = 0x80;\n\t\t\tc = 0x80 | (a & 31);\n\t\t\td = (unsigned char)*(*p)++;\n\t\t\ta = 0;\n\t\t\tbreak;\n\t\t}\n\tcase 0:\n\t\treturn -1;\n\t}\n\n\tif (0x80 != (0xC0 & (b ^ c ^ d))) {\n\t\treturn -1;\n\t}\n\n\tb &= 63;\n\tc &= 63;\n\td &= 63;\n\ta = (a << 18) | (b << 12) | (c << 6) | d;\n\n\tif (a < min) {\n\t\treturn -1;\n\t}\n\n\tif (a > 0x10FFFF) {\n\t\treturn -1;\n\t}\n\n\tif (a >= 0xD800 && a <= 0xDFFF) {\n\t\treturn -1;\n\t}\n\n\treturn a;\n}\n\nstatic unsigned _utf8_decode(const char **p, const char *pe)\n{\n\tunsigned a;\n\n\ta = (unsigned char)*(*p)++;\n\n\tif (a < 128) {\n\t\treturn a;\n\t}\n\n\treturn _utf8_decode_slow(p, pe, a);\n}\n\nstatic int _utf8_to_punycode_label(const char *s, const char *se, char **d, const char *de)\n{\n\tstatic const char alphabet[] = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n\tconst char *ss;\n\tunsigned c;\n\tunsigned h;\n\tunsigned k;\n\tunsigned n;\n\tunsigned m;\n\tunsigned q;\n\tunsigned t;\n\tunsigned x;\n\tunsigned y;\n\tunsigned bias;\n\tunsigned delta;\n\tunsigned todo;\n\tint first;\n\n\th = 0;\n\tss = s;\n\ttodo = 0;\n\n\twhile (s < se) {\n\t\tc = _utf8_decode(&s, se);\n\t\tif (c == UINT_MAX) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (c < 128) {\n\t\t\th++;\n\t\t} else {\n\t\t\ttodo++;\n\t\t}\n\t}\n\n\tif (todo > 0) {\n\t\tif (*d < de) {\n\t\t\t*(*d)++ = 'x';\n\t\t}\n\t\tif (*d < de) {\n\t\t\t*(*d)++ = 'n';\n\t\t}\n\t\tif (*d < de) {\n\t\t\t*(*d)++ = '-';\n\t\t}\n\t\tif (*d < de) {\n\t\t\t*(*d)++ = '-';\n\t\t}\n\t}\n\n\tx = 0;\n\ts = ss;\n\twhile (s < se) {\n\t\tc = _utf8_decode(&s, se);\n\n\t\tif (c > 127) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (*d < de) {\n\t\t\t*(*d)++ = c;\n\t\t}\n\n\t\tif (++x == h) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (todo == 0) {\n\t\treturn h;\n\t}\n\n\tif (h > 0) {\n\t\tif (*d < de) {\n\t\t\t*(*d)++ = '-';\n\t\t}\n\t}\n\n\tn = 128;\n\tbias = 72;\n\tdelta = 0;\n\tfirst = 1;\n\n\twhile (todo > 0) {\n\t\tm = -1;\n\t\ts = ss;\n\n\t\twhile (s < se) {\n\t\t\tc = _utf8_decode(&s, se);\n\n\t\t\tif (c >= n) {\n\t\t\t\tif (c < m) {\n\t\t\t\t\tm = c;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tx = m - n;\n\t\ty = h + 1;\n\n\t\tif (x > ~delta / y) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tdelta += x * y;\n\t\tn = m;\n\n\t\ts = ss;\n\t\twhile (s < se) {\n\t\t\tc = _utf8_decode(&s, se);\n\n\t\t\tif (c < n) {\n\t\t\t\tif (++delta == 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (c != n) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (k = 36, q = delta;; k += 36) {\n\t\t\t\tt = 1;\n\n\t\t\t\tif (k > bias) {\n\t\t\t\t\tt = k - bias;\n\t\t\t\t}\n\n\t\t\t\tif (t > 26) {\n\t\t\t\t\tt = 26;\n\t\t\t\t}\n\n\t\t\t\tif (q < t) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tx = q - t;\n\t\t\t\ty = 36 - t;\n\t\t\t\tq = x / y;\n\t\t\t\tt = t + x % y;\n\n\t\t\t\tif (*d < de) {\n\t\t\t\t\t*(*d)++ = alphabet[t];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (*d < de) {\n\t\t\t\t*(*d)++ = alphabet[q];\n\t\t\t}\n\n\t\t\tdelta /= 2;\n\n\t\t\tif (first) {\n\t\t\t\tdelta /= 350;\n\t\t\t\tfirst = 0;\n\t\t\t}\n\n\t\t\th++;\n\t\t\tdelta += delta / h;\n\n\t\t\tfor (bias = 0; delta > 35 * 26 / 2; bias += 36) {\n\t\t\t\tdelta /= 35;\n\t\t\t}\n\n\t\t\tbias += 36 * delta / (delta + 38);\n\t\t\tdelta = 0;\n\t\t\ttodo--;\n\t\t}\n\n\t\tdelta++;\n\t\tn++;\n\t}\n\n\treturn 0;\n}\n\nint utf8_to_punycode(const char *src, int src_len, char *dst, int dst_len)\n{\n\tconst char *si;\n\tconst char *se;\n\tconst char *st;\n\tunsigned c;\n\tchar *ds;\n\tchar *de;\n\tint rc;\n\n\tds = dst;\n\tsi = src;\n\tse = src + src_len;\n\tde = dst + dst_len;\n\n\twhile (si < se) {\n\t\tst = si;\n\t\tc = _utf8_decode(&si, se);\n\n\t\tif (c == UINT_MAX) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (c != '.') {\n\t\t\tif (c != 0x3002) {\n\t\t\t\tif (c != 0xFF0E) {\n\t\t\t\t\tif (c != 0xFF61) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trc = _utf8_to_punycode_label(src, st, &dst, de);\n\n\t\tif (rc < 0) {\n\t\t\treturn rc;\n\t\t}\n\n\t\tif (dst < de) {\n\t\t\t*dst++ = '.';\n\t\t}\n\n\t\tsrc = si;\n\t}\n\n\tif (src < se) {\n\t\trc = _utf8_to_punycode_label(src, se, &dst, de);\n\n\t\tif (rc < 0) {\n\t\t\treturn rc;\n\t\t}\n\t}\n\n\tif (dst < de) {\n\t\t*dst++ = '\\0';\n\t}\n\n\treturn dst - ds;\n}"
  },
  {
    "path": "src/lib/radix.c",
    "content": "/*\n * Copyright (c) 1999-2000\n *\n * The Regents of the University of Michigan (\"The Regents\") and\n * Merit Network, Inc. All rights reserved.  Redistribution and use\n * in source and binary forms, with or without modification, are\n * permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above\n * copyright notice, this list of conditions and the\n * following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the\n * following disclaimer in the documentation and/or other\n * materials provided with the distribution.\n *\n * 3. All advertising materials mentioning features or use of\n * this software must display the following acknowledgement:\n *\n *   This product includes software developed by the University of\n *   Michigan, Merit Network, Inc., and their contributors.\n *\n * 4. Neither the name of the University, Merit Network, nor the\n * names of their contributors may be used to endorse or\n * promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TH E REGENTS\n * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\n * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HO WEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n/*\n * Portions Copyright (c) 2004,2005 Damien Miller <djm@mindrot.org>\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#define _GNU_SOURCE\n\n#include <sys/types.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"smartdns/lib/radix.h\"\n\n/* $Id: radix.c,v 1.17 2007/10/24 06:04:31 djm Exp $ */\n\n/*\n * Originally from MRT include/defs.h\n * $MRTId: defs.h,v 1.1.1.1 2000/08/14 18:46:10 labovit Exp $\n */\n#define BIT_TEST(f, b)  ((f) & (b))\n\n/*\n * Originally from MRT include/mrt.h\n * $MRTId: mrt.h,v 1.1.1.1 2000/08/14 18:46:10 labovit Exp $\n */\n#define prefix_tochar(prefix)\t\t((char *)&(prefix)->add)\n#define prefix_touchar(prefix)\t\t((unsigned char *)&(prefix)->add)\n\n/*\n * Originally from MRT lib/mrt/prefix.c\n * $MRTId: prefix.c,v 1.1.1.1 2000/08/14 18:46:11 labovit Exp $\n */\n\nstatic int\ncomp_with_mask(unsigned char *addr, unsigned char *dest, unsigned int mask)\n{\n\tif (memcmp(addr, dest, mask / 8) == 0) {\n\t\tunsigned int n = mask / 8;\n\t\tunsigned int m = ((unsigned int)(~0) << (8 - (mask % 8)));\n\n\t\tif (mask % 8 == 0 || (addr[n] & m) == (dest[n] & m))\n\t\t\treturn (1);\n\t}\n\treturn (0);\n}\n\nstatic prefix_t \n*New_Prefix2(int family, void *dest, int bitlen, prefix_t *prefix)\n{\n\tint dynamic_allocated = 0;\n\tint default_bitlen = 32;\n\n\tif (family == AF_INET6) {\n\t\tdefault_bitlen = 128;\n\t\tif (prefix == NULL) {\n\t\t\tif ((prefix = calloc(1, sizeof(*prefix))) == NULL)\n\t\t\t\treturn (NULL);\n\t\t\tdynamic_allocated++;\n\t\t}\n\t\tmemcpy(&prefix->add.sin6, dest, 16);\n\t} else if (family == AF_INET) {\n\t\tif (prefix == NULL) {\n\t\t\tif ((prefix = calloc(1, sizeof(*prefix))) == NULL)\n\t\t\t\treturn (NULL);\n\t\t\tdynamic_allocated++;\n\t\t}\n\t\tmemcpy(&prefix->add.sin, dest, 4);\n\t} else\n\t\treturn (NULL);\n\n\tprefix->bitlen = (bitlen >= 0) ? bitlen : default_bitlen;\n\tprefix->family = family;\n\tprefix->ref_count = 0;\n\tif (dynamic_allocated)\n\t\tprefix->ref_count++;\n\treturn (prefix);\n}\n\n\nstatic prefix_t \n*Ref_Prefix(prefix_t *prefix)\n{\n\tif (prefix == NULL)\n\t\treturn (NULL);\n\tif (prefix->ref_count == 0) {\n\t\t/* make a copy in case of a static prefix */\n\t\treturn (New_Prefix2(prefix->family, &prefix->add,\n\t\t    prefix->bitlen, NULL));\n\t}\n\tprefix->ref_count++;\n\treturn (prefix);\n}\n\n\nvoid\nDeref_Prefix(prefix_t *prefix)\n{\n\tif (prefix == NULL)\n\t\treturn;\n\tprefix->ref_count--;\n\tif (prefix->ref_count <= 0) {\n\t\tfree(prefix);\n\t\treturn;\n\t}\n}\n\n/*\n * Originally from MRT lib/radix/radix.c\n * $MRTId: radix.c,v 1.1.1.1 2000/08/14 18:46:13 labovit Exp $\n */\n\n/* these routines support continuous mask only */\n\nradix_tree_t\n*New_Radix(void)\n{\n\tradix_tree_t *radix;\n\n\tif ((radix = calloc(1, sizeof(*radix))) == NULL)\n\t\treturn (NULL);\n\n\tradix->maxbits = 128;\n\tradix->head = NULL;\n\tradix->num_active_node = 0;\n\treturn (radix);\n}\n\n/*\n * if func is supplied, it will be called as func(node->data)\n * before deleting the node\n */\nstatic void\nClear_Radix(radix_tree_t *radix, rdx_cb_t func, void *cbctx)\n{\n\tif (radix->head) {\n\t\tradix_node_t *Xstack[RADIX_MAXBITS + 1];\n\t\tradix_node_t **Xsp = Xstack;\n\t\tradix_node_t *Xrn = radix->head;\n\n\t\twhile (Xrn) {\n\t\t\tradix_node_t *l = Xrn->l;\n\t\t\tradix_node_t *r = Xrn->r;\n\n\t\t\tif (Xrn->prefix) {\n\t\t\t\tDeref_Prefix(Xrn->prefix);\n\t\t\t\tif (Xrn->data && func)\n\t\t\t\t\tfunc(Xrn, cbctx);\n\t\t\t}\n\t\t\tfree(Xrn);\n\t\t\tradix->num_active_node--;\n\n\t\t\tif (l) {\n\t\t\t\tif (r)\n\t\t\t\t\t*Xsp++ = r;\n\t\t\t\tXrn = l;\n\t\t\t} else if (r) {\n\t\t\t\tXrn = r;\n\t\t\t} else if (Xsp != Xstack) {\n\t\t\t\tXrn = *(--Xsp);\n\t\t\t} else {\n\t\t\t\tXrn = (radix_node_t *) 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid\nDestroy_Radix(radix_tree_t *radix, rdx_cb_t func, void *cbctx)\n{\n\tif (radix == NULL) {\n\t\treturn;\n\t}\n\tClear_Radix(radix, func, cbctx);\n\tfree(radix);\n}\n\n/*\n * if func is supplied, it will be called as func(node->prefix, node->data)\n */\nvoid\nradix_process(radix_tree_t *radix, rdx_cb_t func, void *cbctx)\n{\n\tradix_node_t *node;\n\n\tRADIX_WALK(radix->head, node) {\n\t\tfunc(node, cbctx);\n\t} RADIX_WALK_END;\n}\n\nradix_node_t\n*radix_search_exact(radix_tree_t *radix, prefix_t *prefix)\n{\n\tradix_node_t *node;\n\tunsigned char *addr;\n\tunsigned int bitlen;\n\n\tif (radix->head == NULL)\n\t\treturn (NULL);\n\n\tnode = radix->head;\n\taddr = prefix_touchar(prefix);\n\tbitlen = prefix->bitlen;\n\n\twhile (node->bit < bitlen) {\n\t\tif (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07)))\n\t\t\tnode = node->r;\n\t\telse\n\t\t\tnode = node->l;\n\n\t\tif (node == NULL)\n\t\t\treturn (NULL);\n\t}\n\n\tif (node->bit > bitlen || node->prefix == NULL)\n\t\treturn (NULL);\n\n\tif (comp_with_mask(prefix_touchar(node->prefix),\n\t    prefix_touchar(prefix), bitlen))\n\t\treturn (node);\n\n\treturn (NULL);\n}\n\n\n/* if inclusive != 0, \"best\" may be the given prefix itself */\nstatic radix_node_t\n*radix_search_best2(radix_tree_t *radix, prefix_t *prefix, int inclusive)\n{\n\tradix_node_t *node;\n\tradix_node_t *stack[RADIX_MAXBITS + 1] = {0};\n\tunsigned char *addr;\n\tunsigned int bitlen;\n\tint cnt = 0;\n\n\tif (radix->head == NULL)\n\t\treturn (NULL);\n\n\tnode = radix->head;\n\taddr = prefix_touchar(prefix);\n\tbitlen = prefix->bitlen;\n\n\twhile (node->bit < bitlen) {\n\t\tif (node->prefix)\n\t\t\tstack[cnt++] = node;\n\t\tif (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07)))\n\t\t\tnode = node->r;\n\t\telse\n\t\t\tnode = node->l;\n\n\t\tif (node == NULL)\n\t\t\tbreak;\n\t}\n\n\tif (inclusive && node && node->prefix)\n\t\tstack[cnt++] = node;\n\n\n\tif (cnt <= 0)\n\t\treturn (NULL);\n\n\twhile (--cnt >= 0) {\n\t\tnode = stack[cnt];\n\t\tif (comp_with_mask(prefix_touchar(node->prefix),\n\t\t    prefix_touchar(prefix), node->prefix->bitlen))\n\t\t\treturn (node);\n\t}\n\treturn (NULL);\n}\n\n\nradix_node_t\n*radix_search_best(radix_tree_t *radix, prefix_t *prefix)\n{\n\treturn (radix_search_best2(radix, prefix, 1));\n}\n\n\nradix_node_t\n*radix_lookup(radix_tree_t *radix, prefix_t *prefix)\n{\n\tradix_node_t *node, *new_node, *parent, *glue;\n\tunsigned char *addr, *test_addr;\n\tunsigned int bitlen, check_bit, differ_bit;\n\tunsigned int i, j, r;\n\n\tif (radix->head == NULL) {\n\t\tif ((node = calloc(1, sizeof(*node))) == NULL)\n\t\t\treturn (NULL);\n\t\tnode->bit = prefix->bitlen;\n\t\tnode->prefix = Ref_Prefix(prefix);\n\t\tnode->parent = NULL;\n\t\tnode->l = node->r = NULL;\n\t\tnode->data = NULL;\n\t\tradix->head = node;\n\t\tradix->num_active_node++;\n\t\treturn (node);\n\t}\n\taddr = prefix_touchar(prefix);\n\tbitlen = prefix->bitlen;\n\tnode = radix->head;\n\n\twhile (node->bit < bitlen || node->prefix == NULL) {\n\t\tif (node->bit < radix->maxbits && BIT_TEST(addr[node->bit >> 3],\n\t\t    0x80 >> (node->bit & 0x07))) {\n\t\t\tif (node->r == NULL)\n\t\t\t\tbreak;\n\t\t\tnode = node->r;\n\t\t} else {\n\t\t\tif (node->l == NULL)\n\t\t\t\tbreak;\n\t\t\tnode = node->l;\n\t\t}\n\t}\n\n\ttest_addr = prefix_touchar(node->prefix);\n\t/* find the first bit different */\n\tcheck_bit = (node->bit < bitlen) ? node->bit : bitlen;\n\tdiffer_bit = 0;\n\tfor (i = 0; i * 8 < check_bit; i++) {\n\t\tif ((r = (addr[i] ^ test_addr[i])) == 0) {\n\t\t\tdiffer_bit = (i + 1) * 8;\n\t\t\tcontinue;\n\t\t}\n\t\t/* I know the better way, but for now */\n\t\tfor (j = 0; j < 8; j++) {\n\t\t\tif (BIT_TEST(r, (0x80 >> j)))\n\t\t\t\tbreak;\n\t\t}\n\t\t/* must be found */\n\t\tdiffer_bit = i * 8 + j;\n\t\tbreak;\n\t}\n\tif (differ_bit > check_bit)\n\t\tdiffer_bit = check_bit;\n\n\tparent = node->parent;\n\twhile (parent && parent->bit >= differ_bit) {\n\t\tnode = parent;\n\t\tparent = node->parent;\n\t}\n\n\tif (differ_bit == bitlen && node->bit == bitlen) {\n\t\tif (node->prefix == NULL)\n\t\t\tnode->prefix = Ref_Prefix(prefix);\n\t\treturn (node);\n\t}\n\tif ((new_node = calloc(1, sizeof(*new_node))) == NULL)\n\t\treturn (NULL);\n\tnew_node->bit = prefix->bitlen;\n\tnew_node->prefix = Ref_Prefix(prefix);\n\tnew_node->parent = NULL;\n\tnew_node->l = new_node->r = NULL;\n\tnew_node->data = NULL;\n\tradix->num_active_node++;\n\n\tif (node->bit == differ_bit) {\n\t\tnew_node->parent = node;\n\t\tif (node->bit < radix->maxbits && BIT_TEST(addr[node->bit >> 3],\n\t\t    0x80 >> (node->bit & 0x07)))\n\t\t\tnode->r = new_node;\n\t\telse\n\t\t\tnode->l = new_node;\n\n\t\treturn (new_node);\n\t}\n\tif (bitlen == differ_bit) {\n\t\tif (bitlen < radix->maxbits && BIT_TEST(test_addr[bitlen >> 3],\n\t\t    0x80 >> (bitlen & 0x07)))\n\t\t\tnew_node->r = node;\n\t\telse\n\t\t\tnew_node->l = node;\n\n\t\tnew_node->parent = node->parent;\n\t\tif (node->parent == NULL)\n\t\t\tradix->head = new_node;\n\t\telse if (node->parent->r == node)\n\t\t\tnode->parent->r = new_node;\n\t\telse\n\t\t\tnode->parent->l = new_node;\n\n\t\tnode->parent = new_node;\n\t} else {\n\t\tif ((glue = calloc(1, sizeof(*glue))) == NULL) {\n\t\t\tfree(new_node);\n\t\t\treturn (NULL);\n\t\t}\n\t\tglue->bit = differ_bit;\n\t\tglue->prefix = NULL;\n\t\tglue->parent = node->parent;\n\t\tglue->data = NULL;\n\t\tradix->num_active_node++;\n\t\tif (differ_bit < radix->maxbits &&\n\t\t    BIT_TEST(addr[differ_bit >> 3],\n\t\t    0x80 >> (differ_bit & 0x07))) {\n\t\t\tglue->r = new_node;\n\t\t\tglue->l = node;\n\t\t} else {\n\t\t\tglue->r = node;\n\t\t\tglue->l = new_node;\n\t\t}\n\t\tnew_node->parent = glue;\n\n\t\tif (node->parent == NULL)\n\t\t\tradix->head = glue;\n\t\telse if (node->parent->r == node)\n\t\t\tnode->parent->r = glue;\n\t\telse\n\t\t\tnode->parent->l = glue;\n\n\t\tnode->parent = glue;\n\t}\n\treturn (new_node);\n}\n\n\nvoid\nradix_remove(radix_tree_t *radix, radix_node_t *node)\n{\n\tradix_node_t *parent, *child;\n\n\tif (node->r && node->l) {\n\t\t/*\n\t\t * this might be a placeholder node -- have to check and make\n\t\t * sure there is a prefix aossciated with it !\n\t\t */\n\t\tif (node->prefix != NULL)\n\t\t\tDeref_Prefix(node->prefix);\n\t\tnode->prefix = NULL;\n\t\t/* Also I needed to clear data pointer -- masaki */\n\t\tnode->data = NULL;\n\t\treturn;\n\t}\n\tif (node->r == NULL && node->l == NULL) {\n\t\tparent = node->parent;\n\t\tDeref_Prefix(node->prefix);\n\t\tfree(node);\n\t\tradix->num_active_node--;\n\n\t\tif (parent == NULL) {\n\t\t\tradix->head = NULL;\n\t\t\treturn;\n\t\t}\n\t\tif (parent->r == node) {\n\t\t\tparent->r = NULL;\n\t\t\tchild = parent->l;\n\t\t} else {\n\t\t\tparent->l = NULL;\n\t\t\tchild = parent->r;\n\t\t}\n\n\t\tif (parent->prefix)\n\t\t\treturn;\n\n\t\t/* we need to remove parent too */\n\t\tif (parent->parent == NULL)\n\t\t\tradix->head = child;\n\t\telse if (parent->parent->r == parent)\n\t\t\tparent->parent->r = child;\n\t\telse\n\t\t\tparent->parent->l = child;\n\n\t\tchild->parent = parent->parent;\n\t\tfree(parent);\n\t\tradix->num_active_node--;\n\t\treturn;\n\t}\n\tif (node->r)\n\t\tchild = node->r;\n\telse\n\t\tchild = node->l;\n\n\tparent = node->parent;\n\tchild->parent = parent;\n\n\tDeref_Prefix(node->prefix);\n\tfree(node);\n\tradix->num_active_node--;\n\n\tif (parent == NULL) {\n\t\tradix->head = child;\n\t\treturn;\n\t}\n\tif (parent->r == node)\n\t\tparent->r = child;\n\telse\n\t\tparent->l = child;\n}\n\n/* Local additions */\nstatic void\nsanitise_mask(unsigned char *addr, unsigned int masklen, unsigned int maskbits)\n{\n\tunsigned int i = masklen / 8;\n\tunsigned int j = masklen % 8;\n\n\tif (j != 0) {\n\t\taddr[i] &= (unsigned int)(~0) << (8 - j);\n\t\ti++;\n\t}\n\tfor (; i < maskbits / 8; i++)\n\t\taddr[i] = 0;\n}\n\nstatic void\nupdate_addr(const char **addr_ptr, const char *addr, size_t len)\n{\n\tconst char *ipv6_prefix = \"::ffff:0:0\";\n\n\tif (strncmp(addr, \"::ffff\", len) == 0) \n\t\t*addr_ptr = ipv6_prefix;\n\telse\n\t\t*addr_ptr = (char *)addr;\n}\n\nprefix_t\n*prefix_pton(const char *string, long len, prefix_t *prefix, const char **errmsg)\n{\n\tstatic char save[256];\n\tchar *cp, *ep;\n\tstruct addrinfo hints, *ai;\n\tvoid *addr;\n\tconst char *addr_str = NULL;\n\tprefix_t *ret;\n\tsize_t slen;\n\tint r;\n\n\tret = NULL;\n\n\t/* Copy the string to parse, because we modify it */\n\tif ((slen = strlen(string) + 1) > sizeof(save)) {\n\t\t*errmsg = \"string too long\";\n\t\treturn (NULL);\n\t}\n\tmemcpy(save, string, slen);\n\n\tif ((cp = strchr(save, '/')) != NULL) {\n\t\tif (len != -1 ) {\n\t\t\t*errmsg = \"masklen specified twice\";\n\t\t\treturn (NULL);\n\t\t}\n\t\t*cp++ = '\\0';\n\t\tlen = strtol(cp, &ep, 10);\n\t\tif (*cp == '\\0' || *ep != '\\0' || len < 0) {\n\t\t\t*errmsg = \"could not parse masklen\";\n\t\t\treturn (NULL);\n\t\t}\n\t\t/* More checks below */\n\t}\n\tmemset(&hints, '\\0', sizeof(hints));\n\thints.ai_flags = AI_NUMERICHOST;\n\n\tupdate_addr(&addr_str, save, slen);\n\n\tif ((r = getaddrinfo(addr_str, NULL, &hints, &ai)) != 0) {\n\t\tsnprintf(save, sizeof(save), \"getaddrinfo: %s:\",\n\t\t    gai_strerror(r));\n\t\t*errmsg = save;\n\t\treturn NULL;\n\t}\n\tif (ai == NULL || ai->ai_addr == NULL) {\n\t\t*errmsg = \"getaddrinfo returned no result\";\n\t\tgoto out;\n\t}\n\tswitch (ai->ai_addr->sa_family) {\n\tcase AF_INET:\n\t\tif (len == -1)\n\t\t\tlen = 32;\n\t\telse if (len < 0 || len > 32)\n\t\t\tgoto out;\n\t\taddr = &((struct sockaddr_in *) ai->ai_addr)->sin_addr;\n\t\tsanitise_mask(addr, len, 32);\n\t\tbreak;\n\tcase AF_INET6:\n\t\tif (len == -1)\n\t\t\tlen = 128;\n\t\telse if (len < 0 || len > 128)\n\t\t\tgoto out;\n\t\taddr = &((struct sockaddr_in6 *) ai->ai_addr)->sin6_addr;\n\t\tsanitise_mask(addr, len, 128);\n\t\tbreak;\n\tdefault:\n\t\tgoto out;\n\t}\n\n\tret = New_Prefix2(ai->ai_addr->sa_family, addr, len, prefix);\n\tif (ret == NULL)\n\t\t*errmsg = \"New_Prefix2 failed\";\nout:\n\tfreeaddrinfo(ai);\n\treturn (ret);\n}\n\nprefix_t\n*prefix_from_blob(unsigned char *blob, int len, int prefixlen, prefix_t *prefix)\n{\n\tint family, maxprefix;\n\n\tswitch (len) {\n\tcase 4:\n\t\t/* Assume AF_INET */\n\t\tfamily = AF_INET;\n\t\tmaxprefix = 32;\n\t\tbreak;\n\tcase 16:\n\t\t/* Assume AF_INET6 */\n\t\tfamily = AF_INET6;\n\t\tmaxprefix = 128;\n\t\tbreak;\n\tdefault:\n\t\t/* Who knows? */\n\t\treturn NULL;\n\t}\n\tif (prefixlen == -1)\n\t\tprefixlen = maxprefix;\n\tif (prefixlen < 0 || prefixlen > maxprefix)\n\t\treturn NULL;\n\treturn (New_Prefix2(family, blob, prefixlen, prefix));\n}\n\nconst char *\nprefix_addr_ntop(prefix_t *prefix, char *buf, size_t len)\n{\n\treturn (inet_ntop(prefix->family, &prefix->add, buf, len));\n}\n\nconst char *\nprefix_ntop(prefix_t *prefix, char *buf, size_t len)\n{\n\tchar addrbuf[128];\n\n\tif (prefix_addr_ntop(prefix, addrbuf, sizeof(addrbuf)) == NULL)\n\t\treturn (NULL);\n\tsnprintf(buf, len, \"%s/%d\", addrbuf, prefix->bitlen);\n\n\treturn (buf);\n}\n"
  },
  {
    "path": "src/lib/rbtree.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/lib/rbtree.h\"\n#include <stdbool.h>\n\nstatic inline void rb_set_black(struct rb_node *rb)\n{\n\trb->__rb_parent_color |= RB_BLACK;\n}\n\nstatic inline struct rb_node *rb_red_parent(struct rb_node *red)\n{\n\treturn (struct rb_node *)red->__rb_parent_color;\n}\n\n/*\n * Helper function for rotations:\n * - old's parent and color get assigned to new\n * - old gets assigned new as a parent and 'color' as a color.\n */\nstatic inline void\n__rb_rotate_set_parents(struct rb_node *old, struct rb_node *new,\n\t\t\tstruct rb_root *root, int color)\n{\n\tstruct rb_node *parent = rb_parent(old);\n\tnew->__rb_parent_color = old->__rb_parent_color;\n\trb_set_parent_color(old, new, color);\n\t__rb_change_child(old, new, parent, root);\n}\n\nstatic inline void\n__rb_insert(struct rb_node *node, struct rb_root *root,\n\t    void (*augment_rotate)(struct rb_node *old, struct rb_node *new))\n{\n\tstruct rb_node *parent = rb_red_parent(node), *gparent, *tmp;\n\n\twhile (true) {\n\t\t/*\n\t\t * Loop invariant: node is red\n\t\t *\n\t\t * If there is a black parent, we are done.\n\t\t * Otherwise, take some corrective action as we don't\n\t\t * want a red root or two consecutive red nodes.\n\t\t */\n\t\tif (!parent) {\n\t\t\trb_set_parent_color(node, NULL, RB_BLACK);\n\t\t\tbreak;\n\t\t} else if (rb_is_black(parent))\n\t\t\tbreak;\n\n\t\tgparent = rb_red_parent(parent);\n\n\t\ttmp = gparent->rb_right;\n\t\tif (parent != tmp) {\t/* parent == gparent->rb_left */\n\t\t\tif (tmp && rb_is_red(tmp)) {\n\t\t\t\t/*\n\t\t\t\t * Case 1 - color flips\n\t\t\t\t *\n\t\t\t\t *       G            g\n\t\t\t\t *      / \\          / \\\n\t\t\t\t *     p   u  -->   P   U\n\t\t\t\t *    /            /\n\t\t\t\t *   n            n\n\t\t\t\t *\n\t\t\t\t * However, since g's parent might be red, and\n\t\t\t\t * 4) does not allow this, we need to recurse\n\t\t\t\t * at g.\n\t\t\t\t */\n\t\t\t\trb_set_parent_color(tmp, gparent, RB_BLACK);\n\t\t\t\trb_set_parent_color(parent, gparent, RB_BLACK);\n\t\t\t\tnode = gparent;\n\t\t\t\tparent = rb_parent(node);\n\t\t\t\trb_set_parent_color(node, parent, RB_RED);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttmp = parent->rb_right;\n\t\t\tif (node == tmp) {\n\t\t\t\t/*\n\t\t\t\t * Case 2 - left rotate at parent\n\t\t\t\t *\n\t\t\t\t *      G             G\n\t\t\t\t *     / \\           / \\\n\t\t\t\t *    p   U  -->    n   U\n\t\t\t\t *     \\           /\n\t\t\t\t *      n         p\n\t\t\t\t *\n\t\t\t\t * This still leaves us in violation of 4), the\n\t\t\t\t * continuation into Case 3 will fix that.\n\t\t\t\t */\n\t\t\t\tparent->rb_right = tmp = node->rb_left;\n\t\t\t\tnode->rb_left = parent;\n\t\t\t\tif (tmp)\n\t\t\t\t\trb_set_parent_color(tmp, parent,\n\t\t\t\t\t\t\t    RB_BLACK);\n\t\t\t\trb_set_parent_color(parent, node, RB_RED);\n\t\t\t\taugment_rotate(parent, node);\n\t\t\t\tparent = node;\n\t\t\t\ttmp = node->rb_right;\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Case 3 - right rotate at gparent\n\t\t\t *\n\t\t\t *        G           P\n\t\t\t *       / \\         / \\\n\t\t\t *      p   U  -->  n   g\n\t\t\t *     /                 \\\n\t\t\t *    n                   U\n\t\t\t */\n\t\t\tgparent->rb_left = tmp;  /* == parent->rb_right */\n\t\t\tparent->rb_right = gparent;\n\t\t\tif (tmp)\n\t\t\t\trb_set_parent_color(tmp, gparent, RB_BLACK);\n\t\t\t__rb_rotate_set_parents(gparent, parent, root, RB_RED);\n\t\t\taugment_rotate(gparent, parent);\n\t\t\tbreak;\n\t\t} else {\n\t\t\ttmp = gparent->rb_left;\n\t\t\tif (tmp && rb_is_red(tmp)) {\n\t\t\t\t/* Case 1 - color flips */\n\t\t\t\trb_set_parent_color(tmp, gparent, RB_BLACK);\n\t\t\t\trb_set_parent_color(parent, gparent, RB_BLACK);\n\t\t\t\tnode = gparent;\n\t\t\t\tparent = rb_parent(node);\n\t\t\t\trb_set_parent_color(node, parent, RB_RED);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttmp = parent->rb_left;\n\t\t\tif (node == tmp) {\n\t\t\t\t/* Case 2 - right rotate at parent */\n\t\t\t\tparent->rb_left = tmp = node->rb_right;\n\t\t\t\tnode->rb_right = parent;\n\t\t\t\tif (tmp)\n\t\t\t\t\trb_set_parent_color(tmp, parent,\n\t\t\t\t\t\t\t    RB_BLACK);\n\t\t\t\trb_set_parent_color(parent, node, RB_RED);\n\t\t\t\taugment_rotate(parent, node);\n\t\t\t\tparent = node;\n\t\t\t\ttmp = node->rb_left;\n\t\t\t}\n\n\t\t\t/* Case 3 - left rotate at gparent */\n\t\t\tgparent->rb_right = tmp;  /* == parent->rb_left */\n\t\t\tparent->rb_left = gparent;\n\t\t\tif (tmp)\n\t\t\t\trb_set_parent_color(tmp, gparent, RB_BLACK);\n\t\t\t__rb_rotate_set_parents(gparent, parent, root, RB_RED);\n\t\t\taugment_rotate(gparent, parent);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n * Inline version for rb_erase() use - we want to be able to inline\n * and eliminate the dummy_rotate callback there\n */\nstatic inline void\n____rb_erase_color(struct rb_node *parent, struct rb_root *root,\n\tvoid (*augment_rotate)(struct rb_node *old, struct rb_node *new))\n{\n\tstruct rb_node *node = NULL, *sibling, *tmp1, *tmp2;\n\n\twhile (true) {\n\t\t/*\n\t\t * Loop invariants:\n\t\t * - node is black (or NULL on first iteration)\n\t\t * - node is not the root (parent is not NULL)\n\t\t * - All leaf paths going through parent and node have a\n\t\t *   black node count that is 1 lower than other leaf paths.\n\t\t */\n\t\tsibling = parent->rb_right;\n\t\tif (node != sibling) {\t/* node == parent->rb_left */\n\t\t\tif (rb_is_red(sibling)) {\n\t\t\t\t/*\n\t\t\t\t * Case 1 - left rotate at parent\n\t\t\t\t *\n\t\t\t\t *     P               S\n\t\t\t\t *    / \\             / \\\n\t\t\t\t *   N   s    -->    p   Sr\n\t\t\t\t *      / \\         / \\\n\t\t\t\t *     Sl  Sr      N   Sl\n\t\t\t\t */\n\t\t\t\tparent->rb_right = tmp1 = sibling->rb_left;\n\t\t\t\tsibling->rb_left = parent;\n\t\t\t\trb_set_parent_color(tmp1, parent, RB_BLACK);\n\t\t\t\t__rb_rotate_set_parents(parent, sibling, root,\n\t\t\t\t\t\t\tRB_RED);\n\t\t\t\taugment_rotate(parent, sibling);\n\t\t\t\tsibling = tmp1;\n\t\t\t}\n\t\t\ttmp1 = sibling->rb_right;\n\t\t\tif (!tmp1 || rb_is_black(tmp1)) {\n\t\t\t\ttmp2 = sibling->rb_left;\n\t\t\t\tif (!tmp2 || rb_is_black(tmp2)) {\n\t\t\t\t\t/*\n\t\t\t\t\t * Case 2 - sibling color flip\n\t\t\t\t\t * (p could be either color here)\n\t\t\t\t\t *\n\t\t\t\t\t *    (p)           (p)\n\t\t\t\t\t *    / \\           / \\\n\t\t\t\t\t *   N   S    -->  N   s\n\t\t\t\t\t *      / \\           / \\\n\t\t\t\t\t *     Sl  Sr        Sl  Sr\n\t\t\t\t\t *\n\t\t\t\t\t * This leaves us violating 5) which\n\t\t\t\t\t * can be fixed by flipping p to black\n\t\t\t\t\t * if it was red, or by recursing at p.\n\t\t\t\t\t * p is red when coming from Case 1.\n\t\t\t\t\t */\n\t\t\t\t\trb_set_parent_color(sibling, parent,\n\t\t\t\t\t\t\t    RB_RED);\n\t\t\t\t\tif (rb_is_red(parent))\n\t\t\t\t\t\trb_set_black(parent);\n\t\t\t\t\telse {\n\t\t\t\t\t\tnode = parent;\n\t\t\t\t\t\tparent = rb_parent(node);\n\t\t\t\t\t\tif (parent)\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t/*\n\t\t\t\t * Case 3 - right rotate at sibling\n\t\t\t\t * (p could be either color here)\n\t\t\t\t *\n\t\t\t\t *   (p)           (p)\n\t\t\t\t *   / \\           / \\\n\t\t\t\t *  N   S    -->  N   Sl\n\t\t\t\t *     / \\             \\\n\t\t\t\t *    sl  Sr            s\n\t\t\t\t *                       \\\n\t\t\t\t *                        Sr\n\t\t\t\t */\n\t\t\t\tsibling->rb_left = tmp1 = tmp2->rb_right;\n\t\t\t\ttmp2->rb_right = sibling;\n\t\t\t\tparent->rb_right = tmp2;\n\t\t\t\tif (tmp1)\n\t\t\t\t\trb_set_parent_color(tmp1, sibling,\n\t\t\t\t\t\t\t    RB_BLACK);\n\t\t\t\taugment_rotate(sibling, tmp2);\n\t\t\t\ttmp1 = sibling;\n\t\t\t\tsibling = tmp2;\n\t\t\t}\n\t\t\t/*\n\t\t\t * Case 4 - left rotate at parent + color flips\n\t\t\t * (p and sl could be either color here.\n\t\t\t *  After rotation, p becomes black, s acquires\n\t\t\t *  p's color, and sl keeps its color)\n\t\t\t *\n\t\t\t *      (p)             (s)\n\t\t\t *      / \\             / \\\n\t\t\t *     N   S     -->   P   Sr\n\t\t\t *        / \\         / \\\n\t\t\t *      (sl) sr      N  (sl)\n\t\t\t */\n\t\t\tparent->rb_right = tmp2 = sibling->rb_left;\n\t\t\tsibling->rb_left = parent;\n\t\t\trb_set_parent_color(tmp1, sibling, RB_BLACK);\n\t\t\tif (tmp2)\n\t\t\t\trb_set_parent(tmp2, parent);\n\t\t\t__rb_rotate_set_parents(parent, sibling, root,\n\t\t\t\t\t\tRB_BLACK);\n\t\t\taugment_rotate(parent, sibling);\n\t\t\tbreak;\n\t\t} else {\n\t\t\tsibling = parent->rb_left;\n\t\t\tif (rb_is_red(sibling)) {\n\t\t\t\t/* Case 1 - right rotate at parent */\n\t\t\t\tparent->rb_left = tmp1 = sibling->rb_right;\n\t\t\t\tsibling->rb_right = parent;\n\t\t\t\trb_set_parent_color(tmp1, parent, RB_BLACK);\n\t\t\t\t__rb_rotate_set_parents(parent, sibling, root,\n\t\t\t\t\t\t\tRB_RED);\n\t\t\t\taugment_rotate(parent, sibling);\n\t\t\t\tsibling = tmp1;\n\t\t\t}\n\t\t\ttmp1 = sibling->rb_left;\n\t\t\tif (!tmp1 || rb_is_black(tmp1)) {\n\t\t\t\ttmp2 = sibling->rb_right;\n\t\t\t\tif (!tmp2 || rb_is_black(tmp2)) {\n\t\t\t\t\t/* Case 2 - sibling color flip */\n\t\t\t\t\trb_set_parent_color(sibling, parent,\n\t\t\t\t\t\t\t    RB_RED);\n\t\t\t\t\tif (rb_is_red(parent))\n\t\t\t\t\t\trb_set_black(parent);\n\t\t\t\t\telse {\n\t\t\t\t\t\tnode = parent;\n\t\t\t\t\t\tparent = rb_parent(node);\n\t\t\t\t\t\tif (parent)\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t/* Case 3 - right rotate at sibling */\n\t\t\t\tsibling->rb_right = tmp1 = tmp2->rb_left;\n\t\t\t\ttmp2->rb_left = sibling;\n\t\t\t\tparent->rb_left = tmp2;\n\t\t\t\tif (tmp1)\n\t\t\t\t\trb_set_parent_color(tmp1, sibling,\n\t\t\t\t\t\t\t    RB_BLACK);\n\t\t\t\taugment_rotate(sibling, tmp2);\n\t\t\t\ttmp1 = sibling;\n\t\t\t\tsibling = tmp2;\n\t\t\t}\n\t\t\t/* Case 4 - left rotate at parent + color flips */\n\t\t\tparent->rb_left = tmp2 = sibling->rb_right;\n\t\t\tsibling->rb_right = parent;\n\t\t\trb_set_parent_color(tmp1, sibling, RB_BLACK);\n\t\t\tif (tmp2)\n\t\t\t\trb_set_parent(tmp2, parent);\n\t\t\t__rb_rotate_set_parents(parent, sibling, root,\n\t\t\t\t\t\tRB_BLACK);\n\t\t\taugment_rotate(parent, sibling);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/* Non-inline version for rb_erase_augmented() use */\nvoid __rb_erase_color(struct rb_node *parent, struct rb_root *root,\n\tvoid (*augment_rotate)(struct rb_node *old, struct rb_node *new))\n{\n\t____rb_erase_color(parent, root, augment_rotate);\n}\n\n/*\n * Non-augmented rbtree manipulation functions.\n *\n * We use dummy augmented callbacks here, and have the compiler optimize them\n * out of the rb_insert_color() and rb_erase() function definitions.\n */\n\nstatic inline void dummy_propagate(struct rb_node *node, struct rb_node *stop) {}\nstatic inline void dummy_copy(struct rb_node *old, struct rb_node *new) {}\nstatic inline void dummy_rotate(struct rb_node *old, struct rb_node *new) {}\n\nstatic const struct rb_augment_callbacks dummy_callbacks = {\n\tdummy_propagate, dummy_copy, dummy_rotate\n};\n\nvoid rb_insert_color(struct rb_node *node, struct rb_root *root)\n{\n\t__rb_insert(node, root, dummy_rotate);\n}\n\nvoid rb_erase(struct rb_node *node, struct rb_root *root)\n{\n\tstruct rb_node *rebalance;\n\trebalance = __rb_erase_augmented(node, root, &dummy_callbacks);\n\tif (rebalance)\n\t\t____rb_erase_color(rebalance, root, dummy_rotate);\n}\n\n/*\n * Augmented rbtree manipulation functions.\n *\n * This instantiates the same inline functions as in the non-augmented\n * case, but this time with user-defined callbacks.\n */\n\nvoid __rb_insert_augmented(struct rb_node *node, struct rb_root *root,\n\tvoid (*augment_rotate)(struct rb_node *old, struct rb_node *new))\n{\n\t__rb_insert(node, root, augment_rotate);\n}\n\n/*\n * This function returns the first node (in sort order) of the tree.\n */\nstruct rb_node *rb_first(const struct rb_root *root)\n{\n\tstruct rb_node\t*n;\n\n\tn = root->rb_node;\n\tif (!n)\n\t\treturn NULL;\n\twhile (n->rb_left)\n\t\tn = n->rb_left;\n\treturn n;\n}\n\nstruct rb_node *rb_last(const struct rb_root *root)\n{\n\tstruct rb_node\t*n;\n\n\tn = root->rb_node;\n\tif (!n)\n\t\treturn NULL;\n\twhile (n->rb_right)\n\t\tn = n->rb_right;\n\treturn n;\n}\n\nstruct rb_node *rb_next(const struct rb_node *node)\n{\n\tstruct rb_node *parent;\n\n\tif (RB_EMPTY_NODE(node))\n\t\treturn NULL;\n\n\t/*\n\t * If we have a right-hand child, go down and then left as far\n\t * as we can.\n\t */\n\tif (node->rb_right) {\n\t\tnode = node->rb_right;\n\t\twhile (node->rb_left)\n\t\t\tnode=node->rb_left;\n\t\treturn (struct rb_node *)node;\n\t}\n\n\t/*\n\t * No right-hand children. Everything down and left is smaller than us,\n\t * so any 'next' node must be in the general direction of our parent.\n\t * Go up the tree; any time the ancestor is a right-hand child of its\n\t * parent, keep going up. First time it's a left-hand child of its\n\t * parent, said parent is our 'next' node.\n\t */\n\twhile ((parent = rb_parent(node)) && node == parent->rb_right)\n\t\tnode = parent;\n\n\treturn parent;\n}\n\nstruct rb_node *rb_prev(const struct rb_node *node)\n{\n\tstruct rb_node *parent;\n\n\tif (RB_EMPTY_NODE(node))\n\t\treturn NULL;\n\n\t/*\n\t * If we have a left-hand child, go down and then right as far\n\t * as we can.\n\t */\n\tif (node->rb_left) {\n\t\tnode = node->rb_left;\n\t\twhile (node->rb_right)\n\t\t\tnode=node->rb_right;\n\t\treturn (struct rb_node *)node;\n\t}\n\n\t/*\n\t * No left-hand children. Go up till we find an ancestor which\n\t * is a right-hand child of its parent.\n\t */\n\twhile ((parent = rb_parent(node)) && node == parent->rb_left)\n\t\tnode = parent;\n\n\treturn parent;\n}\n\nvoid rb_replace_node(struct rb_node *victim, struct rb_node *new,\n\t\t     struct rb_root *root)\n{\n\tstruct rb_node *parent = rb_parent(victim);\n\n\t/* Set the surrounding nodes to point to the replacement */\n\t__rb_change_child(victim, new, parent, root);\n\tif (victim->rb_left)\n\t\trb_set_parent(victim->rb_left, new);\n\tif (victim->rb_right)\n\t\trb_set_parent(victim->rb_right, new);\n\n\t/* Copy the pointers/colour from the victim to the replacement */\n\t*new = *victim;\n}\n\nstatic struct rb_node *rb_left_deepest_node(const struct rb_node *node)\n{\n\tfor (;;) {\n\t\tif (node->rb_left)\n\t\t\tnode = node->rb_left;\n\t\telse if (node->rb_right)\n\t\t\tnode = node->rb_right;\n\t\telse\n\t\t\treturn (struct rb_node *)node;\n\t}\n}\n\nstruct rb_node *rb_next_postorder(const struct rb_node *node)\n{\n\tconst struct rb_node *parent;\n\tif (!node)\n\t\treturn NULL;\n\tparent = rb_parent(node);\n\n\t/* If we're sitting on node, we've already seen our children */\n\tif (parent && node == parent->rb_left && parent->rb_right) {\n\t\t/* If we are the parent's left node, go to the parent's right\n\t\t * node then all the way down to the left */\n\t\treturn rb_left_deepest_node(parent->rb_right);\n\t} else\n\t\t/* Otherwise we are the parent's right node, and the parent\n\t\t * should be next */\n\t\treturn (struct rb_node *)parent;\n}\n\nstruct rb_node *rb_first_postorder(const struct rb_root *root)\n{\n\tif (!root->rb_node)\n\t\treturn NULL;\n\n\treturn rb_left_deepest_node(root->rb_node);\n}\n"
  },
  {
    "path": "src/lib/stringutil.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */"
  },
  {
    "path": "src/lib/timer_wheel.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/lib/bitops.h\"\n#include <pthread.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/select.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#include \"smartdns/lib/timer_wheel.h\"\n\n#define TVR_BITS 10\n#define TVN_BITS 6\n#define TVR_SIZE (1 << TVR_BITS)\n#define TVN_SIZE (1 << TVN_BITS)\n#define TVR_MASK (TVR_SIZE - 1)\n#define TVN_MASK (TVN_SIZE - 1)\n#define INDEX(N) ((base->jiffies >> (TVR_BITS + N * TVN_BITS)) & TVN_MASK)\n#define MAX_TVAL ((unsigned long)((1ULL << (TVR_BITS + 4 * TVN_BITS)) - 1))\n\nstruct tvec {\n\tstruct list_head vec[TVN_SIZE];\n};\n\nstruct tvec_root {\n\tstruct list_head vec[TVR_SIZE];\n};\n\nstruct tw_base {\n\tpthread_spinlock_t lock;\n\n\tpthread_t runner;\n\n\tunsigned long jiffies;\n\n\tstruct tvec_root tv1;\n\tstruct tvec tv2;\n\tstruct tvec tv3;\n\tstruct tvec tv4;\n\tstruct tvec tv5;\n};\n\nstatic inline void _tw_add_timer(struct tw_base *base, struct tw_timer_list *timer)\n{\n\tint i;\n\tunsigned long idx;\n\tunsigned long expires;\n\tstruct list_head *vec;\n\n\texpires = timer->expires;\n\tidx = expires - base->jiffies;\n\n\tif (idx < TVR_SIZE) {\n\t\ti = expires & TVR_MASK;\n\t\tvec = base->tv1.vec + i;\n\t} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {\n\t\ti = (expires >> TVR_BITS) & TVN_MASK;\n\t\tvec = base->tv2.vec + i;\n\t} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {\n\t\ti = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;\n\t\tvec = base->tv3.vec + i;\n\t} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {\n\t\ti = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;\n\t\tvec = base->tv4.vec + i;\n\t} else if ((signed long)idx < 0) {\n\t\tvec = base->tv1.vec + (base->jiffies & TVR_MASK);\n\t} else {\n\t\tif (idx > MAX_TVAL) {\n\t\t\tidx = MAX_TVAL;\n\t\t\texpires = idx + base->jiffies;\n\t\t}\n\t\ti = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;\n\t\tvec = base->tv5.vec + i;\n\t}\n\n\tlist_add_tail(&timer->entry, vec);\n}\n\nstatic inline void _tw_detach_timer(struct tw_timer_list *timer)\n{\n\tstruct list_head *entry = &timer->entry;\n\n\tlist_del(entry);\n\tentry->next = NULL;\n}\n\nstatic inline int _tw_cascade(struct tw_base *base, struct tvec *tv, int index)\n{\n\tstruct tw_timer_list *timer, *tmp;\n\tstruct list_head tv_list;\n\n\tlist_replace_init(tv->vec + index, &tv_list);\n\n\tlist_for_each_entry_safe(timer, tmp, &tv_list, entry)\n\t{\n\t\t_tw_add_timer(base, timer);\n\t}\n\n\treturn index;\n}\n\nstatic inline int timer_pending(struct tw_timer_list *timer)\n{\n\tstruct list_head *entry = &timer->entry;\n\n\treturn (entry->next != NULL);\n}\n\nstatic inline int __detach_if_pending(struct tw_timer_list *timer)\n{\n\tif (!timer_pending(timer)) {\n\t\treturn 0;\n\t}\n\n\t_tw_detach_timer(timer);\n\treturn 1;\n}\n\nstatic inline int __mod_timer(struct tw_base *base, struct tw_timer_list *timer, int pending_only)\n{\n\tint ret = 0;\n\n\tret = __detach_if_pending(timer);\n\tif (!ret && pending_only) {\n\t\tgoto done;\n\t}\n\n\tret = 1;\n\t_tw_add_timer(base, timer);\n\ndone:\n\treturn ret;\n}\n\nvoid tw_add_timer(struct tw_base *base, struct tw_timer_list *timer)\n{\n\tif (timer->function == NULL) {\n\t\treturn;\n\t}\n\n\tpthread_spin_lock(&base->lock);\n\t{\n\t\ttimer->expires += base->jiffies - 1;\n\t\t_tw_add_timer(base, timer);\n\t}\n\tpthread_spin_unlock(&base->lock);\n}\n\nint tw_del_timer(struct tw_base *base, struct tw_timer_list *timer)\n{\n\tint ret = 0;\n\n\tpthread_spin_lock(&base->lock);\n\t{\n\t\tif (timer_pending(timer)) {\n\t\t\tret = 1;\n\t\t\t_tw_detach_timer(timer);\n\t\t}\n\t}\n\tpthread_spin_unlock(&base->lock);\n\n\treturn ret;\n}\n\nint tw_mod_timer_pending(struct tw_base *base, struct tw_timer_list *timer, unsigned long expires)\n{\n\tint ret = 1;\n\n\tpthread_spin_lock(&base->lock);\n\t{\n\t\ttimer->expires = expires + base->jiffies - 1;\n\t\tret = __mod_timer(base, timer, 1);\n\t}\n\tpthread_spin_unlock(&base->lock);\n\n\treturn ret;\n}\n\nint tw_mod_timer(struct tw_base *base, struct tw_timer_list *timer, unsigned long expires)\n{\n\tint ret = 1;\n\n\tpthread_spin_lock(&base->lock);\n\t{\n\t\tif (timer_pending(timer) && timer->expires == expires) {\n\t\t\tgoto unblock;\n\t\t}\n\n\t\ttimer->expires = expires + base->jiffies - 1;\n\n\t\tret = __mod_timer(base, timer, 0);\n\t}\nunblock:\n\tpthread_spin_unlock(&base->lock);\n\n\treturn ret;\n}\n\nint tw_cleanup_timers(struct tw_base *base)\n{\n\tint ret = 0;\n\tvoid *res = NULL;\n\n\tret = pthread_cancel(base->runner);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\tret = pthread_join(base->runner, &res);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\tif (res != PTHREAD_CANCELED) {\n\t\tgoto errout;\n\t}\n\n\tret = pthread_spin_destroy(&base->lock);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\tfree(base);\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nstatic inline void run_timers(struct tw_base *base)\n{\n\tunsigned long index, call_time;\n\tstruct tw_timer_list *timer;\n\n\tstruct list_head work_list;\n\tstruct list_head *head = &work_list;\n\n\tpthread_spin_lock(&base->lock);\n\t{\n\t\tindex = base->jiffies & TVR_MASK;\n\n\t\tif (!index && (!_tw_cascade(base, &base->tv2, INDEX(0))) && (!_tw_cascade(base, &base->tv3, INDEX(1))) &&\n\t\t\t(!_tw_cascade(base, &base->tv4, INDEX(2))))\n\t\t\t_tw_cascade(base, &base->tv5, INDEX(3));\n\n\t\tcall_time = base->jiffies++;\n\t\tlist_replace_init(base->tv1.vec + index, head);\n\t\twhile (!list_empty(head)) {\n\t\t\ttw_func fn;\n\t\t\tvoid *data;\n\n\t\t\ttimer = list_first_entry(head, struct tw_timer_list, entry);\n\t\t\tfn = timer->function;\n\t\t\tdata = timer->data;\n\n\t\t\t_tw_detach_timer(timer);\n\t\t\tpthread_spin_unlock(&base->lock);\n\t\t\t{\n\t\t\t\tfn(base, timer, data, call_time);\n\t\t\t}\n\n\t\t\tpthread_spin_lock(&base->lock);\n\t\t}\n\t}\n\tpthread_spin_unlock(&base->lock);\n}\n\nstatic unsigned long _tw_tick_count(void)\n{\n\tstruct timespec ts;\n\n\tclock_gettime(CLOCK_MONOTONIC, &ts);\n\n\treturn (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);\n}\n\nstatic void *timer_work(void *arg)\n{\n\tstruct tw_base *base = arg;\n\tint sleep = 1000; \n\tint sleep_time = 0;\n\tunsigned long now = {0};\n\tunsigned long expect_time = 0;\n\tunsigned long start_time = 0;\n\n\tnow = _tw_tick_count();\n\tstart_time = now;\n\texpect_time = now + sleep;\n\t\n\twhile (1) {\n\t\trun_timers(base);\n\n\t\tnow = _tw_tick_count();\n\t\t\n\t\tif (now >= expect_time) {\n\t\t\tunsigned long elapsed_from_start = now - start_time;\n\t\t\tunsigned long next_period = (elapsed_from_start / sleep) + 1;\n\t\t\texpect_time = start_time + next_period * sleep;\n\t\t}\n\t\t\n\t\tsleep_time = (int)(expect_time - now);\n\t\tif (sleep_time < 0) {\n\t\t\tsleep_time = 0;\n\t\t}\n\n\t\tusleep(sleep_time * 1000);\n\t}\n\n\treturn NULL;\n}\n\nstruct tw_base *tw_init_timers(void)\n{\n\tint j = 0;\n\tint ret = 0;\n\tstruct timeval tv = {\n\t\t0,\n\t};\n\tstruct tw_base *base = NULL;\n\n\tbase = calloc(1, sizeof(*base));\n\tif (!base) {\n\t\tgoto errout;\n\t}\n\n\tret = pthread_spin_init(&base->lock, 0);\n\tif (ret != 0) {\n\t\tgoto errout2;\n\t}\n\n\tfor (j = 0; j < TVN_SIZE; j++) {\n\t\tINIT_LIST_HEAD(base->tv5.vec + j);\n\t\tINIT_LIST_HEAD(base->tv4.vec + j);\n\t\tINIT_LIST_HEAD(base->tv3.vec + j);\n\t\tINIT_LIST_HEAD(base->tv2.vec + j);\n\t}\n\n\tfor (j = 0; j < TVR_SIZE; j++) {\n\t\tINIT_LIST_HEAD(base->tv1.vec + j);\n\t}\n\n\tret = gettimeofday(&tv, NULL);\n\tif (ret < 0) {\n\t\tgoto errout1;\n\t}\n\tbase->jiffies = tv.tv_sec;\n\n\tret = pthread_create(&base->runner, NULL, timer_work, base);\n\tif (ret != 0) {\n\t\tgoto errout1;\n\t}\n\treturn base;\n\nerrout1:\n\t(void)pthread_spin_destroy(&base->lock);\nerrout2:\n\tfree(base);\nerrout:\n\treturn NULL;\n}\n"
  },
  {
    "path": "src/main.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/smartdns.h\"\n#include <errno.h>\n#include <stdio.h>\n#include <string.h>\n\nint main(int argc, char *argv[])\n{\n\treturn smartdns_main(argc, argv);\n}\n"
  },
  {
    "path": "src/proxy.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n#include \"smartdns/proxy.h\"\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/http_parse.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/lib/list.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n#include <arpa/inet.h>\n#include <errno.h>\n#include <pthread.h>\n#include <stdio.h>\n#include <sys/epoll.h>\n#include <time.h>\n\n#define PROXY_SOCKS5_VERSION 0x05\n#define PROXY_SOCKS5_NO_AUTH 0x00\n#define PROXY_SOCKS5_AUTH_USER_PASS 0x02\n#define PROXY_SOCKS5_AUTH_NONE 0xFF\n\n#define PROXY_SOCKS5_TYPE_IPV4 0x01\n#define PROXY_SOCKS5_TYPE_DOMAIN 0x03\n#define PROXY_SOCKS5_TYPE_IPV6 0x04\n\n#define PROXY_SOCKS5_CONNECT_TCP 0x01\n#define PROXY_SOCKS5_CONNECT_UDP 0x03\n\n#define PROXY_MAX_EVENTS 64\n#define PROXY_BUFFER_SIZE (1024 * 4)\n#define PROXY_MAX_HOSTNAME_LEN 256\n\n#define PROXY_ERROR_LOG_THROTTLE_SEC 60\n\n#define PROXY_THROTTLED_ERROR_LOG(last_time, ...)                                                                      \\\n\tdo {                                                                                                               \\\n\t\ttime_t now = time(NULL);                                                                                       \\\n\t\tif (now - (last_time) >= PROXY_ERROR_LOG_THROTTLE_SEC) {                                                       \\\n\t\t\ttlog(TLOG_ERROR, __VA_ARGS__);                                                                             \\\n\t\t\t(last_time) = now;                                                                                         \\\n\t\t}                                                                                                              \\\n\t} while (0)\n\ntypedef enum PROXY_CONN_STATE {\n\tPROXY_CONN_INIT = 0,\n\tPROXY_CONN_INIT_ACK = 1,\n\tPROXY_CONN_AUTH = 2,\n\tPROXY_CONN_AUTH_ACK = 3,\n\tPROXY_CONN_CONNECTING = 4,\n\tPROXY_CONN_CONNECTED = 5,\n} PROXY_CONN_STATE;\n\nstruct proxy_conn_buffer {\n\tint len;\n\tuint8_t buffer[PROXY_BUFFER_SIZE];\n};\n\nstruct proxy_conn {\n\tproxy_type_t type;\n\tPROXY_CONN_STATE state;\n\tchar host[DNS_MAX_CNAME_LEN];\n\tunsigned short port;\n\tint fd;\n\tint udp_fd;\n\tint buffer_len;\n\tint is_udp;\n\tint non_block;\n\tstruct sockaddr_storage udp_dest_addr;\n\tsocklen_t udp_dest_addrlen;\n\tstruct proxy_conn_buffer buffer;\n\tstruct proxy_server_info *server_info;\n};\n\n/* upstream server groups */\nstruct proxy_server_info {\n\tstruct hlist_node node;\n\tchar proxy_name[PROXY_NAME_LEN];\n\tstruct sockaddr_storage server_addr;\n\tsocklen_t server_addrlen;\n\tstruct proxy_info info;\n};\n\nstruct proxy_struct {\n\tint run;\n\tint epoll_fd;\n\tpthread_t tid;\n\tpthread_mutex_t proxy_lock;\n\tDECLARE_HASHTABLE(proxy_server, 4);\n};\n\nstatic struct proxy_struct proxy;\nstatic int is_proxy_init;\n\nstatic const char *proxy_socks5_status_code[] = {\n\t\"success\",\n\t\"general SOCKS server failure\",\n\t\"connection not allowed by ruleset\",\n\t\"Network unreachable\",\n\t\"Host unreachable\",\n\t\"Connection refused\",\n\t\"TTL expired\",\n\t\"Command not supported\",\n\t\"Address type not supported\",\n};\n\n/* get server group by name */\nstatic struct proxy_server_info *_proxy_get_server_info(const char *proxy_name)\n{\n\tunsigned long key;\n\tstruct proxy_server_info *server_info = NULL;\n\tstruct hlist_node *tmp = NULL;\n\n\tif (proxy_name == NULL) {\n\t\treturn NULL;\n\t}\n\n\tkey = hash_string(proxy_name);\n\thash_for_each_possible_safe(proxy.proxy_server, server_info, tmp, node, key)\n\t{\n\t\tif (strncmp(server_info->proxy_name, proxy_name, DNS_GROUP_NAME_LEN) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn server_info;\n\t}\n\n\treturn NULL;\n}\n\nstatic struct addrinfo *_proxy_getaddr(const char *host, int port, int type, int protocol)\n{\n\tstruct addrinfo hints;\n\tstruct addrinfo *result = NULL;\n\tint ret = 0;\n\tchar port_str[32];\n\n\tsnprintf(port_str, sizeof(port_str), \"%d\", port);\n\n\tmemset(&hints, 0, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = type;\n\thints.ai_protocol = protocol;\n\n\tret = getaddrinfo(host, port_str, &hints, &result);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"get addr info failed. %s\\n\", gai_strerror(ret));\n\t\ttlog(TLOG_ERROR, \"host: %s, port: %d, type: %d, protocol: %d\", host, port, type, protocol);\n\t\tgoto errout;\n\t}\n\n\treturn result;\nerrout:\n\tif (result) {\n\t\tfreeaddrinfo(result);\n\t}\n\treturn NULL;\n}\n\nint proxy_add(const char *proxy_name, struct proxy_info *info)\n{\n\tunsigned long key;\n\tchar ip_str[PROXY_MAX_IPLEN];\n\tint port = 0;\n\tstruct addrinfo *gai = NULL;\n\tstruct proxy_server_info *server_info = _proxy_get_server_info(proxy_name);\n\n\tif (server_info) {\n\t\treturn -1;\n\t}\n\n\tserver_info = zalloc(1, sizeof(*server_info));\n\tif (server_info == NULL) {\n\t\tgoto errout;\n\t}\n\n\tmemcpy(&server_info->info, info, sizeof(struct proxy_info));\n\n\tif (parse_ip(info->server, ip_str, &port) != 0) {\n\t\tgoto errout;\n\t}\n\n\tport = info->port;\n\tgai = _proxy_getaddr(info->server, port, SOCK_STREAM, 0);\n\tif (gai == NULL) {\n\t\tgoto errout;\n\t}\n\n\tserver_info->server_addrlen = gai->ai_addrlen;\n\tmemcpy(&server_info->server_addr, gai->ai_addr, gai->ai_addrlen);\n\n\tsafe_strncpy(server_info->proxy_name, proxy_name, PROXY_NAME_LEN);\n\tkey = hash_string(server_info->proxy_name);\n\thash_add(proxy.proxy_server, &server_info->node, key);\n\n\tfreeaddrinfo(gai);\n\treturn 0;\nerrout:\n\tif (server_info) {\n\t\tfree(server_info);\n\t\tserver_info = NULL;\n\t}\n\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\treturn -1;\n}\n\nstatic int _proxy_remove(struct proxy_server_info *server_info)\n{\n\thash_del(&server_info->node);\n\tfree(server_info);\n\n\treturn 0;\n}\n\nint proxy_remove(const char *proxy_name)\n{\n\tstruct proxy_server_info *server_info = _proxy_get_server_info(proxy_name);\n\tif (server_info == NULL) {\n\t\treturn 0;\n\t}\n\n\t_proxy_remove(server_info);\n\n\treturn 0;\n}\n\nstatic void _proxy_remove_all(void)\n{\n\tstruct proxy_server_info *server_info = NULL;\n\tstruct hlist_node *tmp = NULL;\n\tunsigned int i = 0;\n\n\thash_for_each_safe(proxy.proxy_server, i, tmp, server_info, node)\n\t{\n\t\t_proxy_remove(server_info);\n\t}\n}\n\nstruct proxy_conn *proxy_conn_new(const char *proxy_name, const char *host, int port, int is_udp, int non_block)\n{\n\tstruct proxy_conn *proxy_conn = NULL;\n\tstruct proxy_server_info *server_info = NULL;\n\tstruct addrinfo *gai = NULL;\n\tint fd = -1;\n\n\tserver_info = _proxy_get_server_info(proxy_name);\n\tif (server_info == NULL) {\n\t\ttlog(TLOG_WARN, \"proxy server %s not found\", proxy_name);\n\t\tgoto errout;\n\t}\n\n\tif (is_udp == 1 && server_info->info.type != PROXY_SOCKS5) {\n\t\ttlog(TLOG_WARN, \"only socks5 support udp\");\n\t\tgoto errout;\n\t}\n\n\tfd = socket(server_info->server_addr.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0);\n\tif (fd < 0) {\n\t\tgoto errout;\n\t}\n\n\tproxy_conn = zalloc(1, sizeof(*proxy_conn));\n\tif (proxy_conn == NULL) {\n\t\tgoto errout;\n\t}\n\n\tsafe_strncpy(proxy_conn->host, host, DNS_MAX_CNAME_LEN);\n\tproxy_conn->port = port;\n\tproxy_conn->type = server_info->info.type;\n\tproxy_conn->state = PROXY_CONN_INIT;\n\tproxy_conn->server_info = server_info;\n\tproxy_conn->fd = fd;\n\tproxy_conn->udp_fd = -1;\n\tproxy_conn->is_udp = is_udp;\n\tproxy_conn->non_block = non_block;\n\n\tif (non_block) {\n\t\tset_fd_nonblock(fd, 1);\n\t}\n\n\treturn proxy_conn;\nerrout:\n\tif (proxy_conn) {\n\t\tfree(proxy_conn);\n\t\tproxy_conn = NULL;\n\t}\n\n\tif (fd >= 0) {\n\t\tclose(fd);\n\t}\n\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\treturn NULL;\n}\n\nvoid proxy_conn_free(struct proxy_conn *proxy_conn)\n{\n\tif (proxy_conn == NULL) {\n\t\treturn;\n\t}\n\n\tif (proxy_conn->fd >= 0) {\n\t\tclose(proxy_conn->fd);\n\t}\n\n\tif (proxy_conn->udp_fd >= 0) {\n\t\tclose(proxy_conn->udp_fd);\n\t}\n\n\tfree(proxy_conn);\n}\n\nint proxy_conn_connect(struct proxy_conn *proxy_conn)\n{\n\tif (proxy_conn == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn connect(proxy_conn->fd, (struct sockaddr *)&proxy_conn->server_info->server_addr,\n\t\t\t\t   proxy_conn->server_info->server_addrlen);\n}\n\nstatic int _proxy_handshake_socks5_create_udp_fd(struct proxy_conn *proxy_conn)\n{\n\tint ret = 0;\n\tchar *gai_host = NULL;\n\tint udp_fd = -1;\n\tstruct addrinfo *gai = NULL;\n\n\tswitch (proxy_conn->udp_dest_addr.ss_family) {\n\tcase AF_INET:\n\t\tgai_host = \"0.0.0.0\";\n\t\tbreak;\n\tcase AF_INET6:\n\t\tgai_host = \"::\";\n\t\tbreak;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\tgai = _proxy_getaddr(gai_host, 0, SOCK_DGRAM, 0);\n\tudp_fd = socket(gai->ai_family, gai->ai_socktype | SOCK_CLOEXEC, 0);\n\tif (udp_fd < 0) {\n\t\tgoto errout;\n\t}\n\n\tret = bind(udp_fd, gai->ai_addr, gai->ai_addrlen);\n\tif (ret < 0) {\n\t\tgoto errout;\n\t}\n\n\tif (proxy_conn->non_block) {\n\t\tset_fd_nonblock(udp_fd, 1);\n\t}\n\n\tfreeaddrinfo(gai);\n\treturn udp_fd;\nerrout:\n\n\tif (udp_fd >= 0) {\n\t\tclose(udp_fd);\n\t}\n\n\tif (gai) {\n\t\tfreeaddrinfo(gai);\n\t}\n\n\treturn -1;\n}\n\nstatic int _proxy_handshake_socks5_connect_udp(struct proxy_conn *proxy_conn)\n{\n\tint udp_fd = -1;\n\n\tif (proxy_conn->is_udp == 0) {\n\t\treturn 0;\n\t}\n\n\tif (proxy_conn->udp_fd < 0) {\n\t\tudp_fd = _proxy_handshake_socks5_create_udp_fd(proxy_conn);\n\t\tif (udp_fd < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tproxy_conn->udp_fd = udp_fd;\n\t}\n\n\treturn connect(proxy_conn->udp_fd, (struct sockaddr *)&proxy_conn->udp_dest_addr, proxy_conn->udp_dest_addrlen);\n}\n\nstatic proxy_handshake_state _proxy_handshake_socks5_reply_connect_addr(struct proxy_conn *proxy_conn)\n{\n\tchar buff[DNS_MAX_CNAME_LEN * 2];\n\tint len = 0;\n\tmemset(buff, 0, sizeof(buff));\n\tstruct sockaddr_storage addr;\n\tchar *ptr = NULL;\n\tsocklen_t addr_len = sizeof(addr);\n\n\tbuff[0] = PROXY_SOCKS5_VERSION;\n\tif (proxy_conn->is_udp) {\n\t\tbuff[1] = PROXY_SOCKS5_CONNECT_UDP;\n\t} else {\n\t\tbuff[1] = PROXY_SOCKS5_CONNECT_TCP;\n\t}\n\n\tbuff[2] = 0x0;\n\tptr = buff + 3;\n\tif (proxy_conn->server_info->info.use_domain) {\n\t\t*ptr = PROXY_SOCKS5_TYPE_DOMAIN;\n\t\tptr++;\n\n\t\tint domainlen = strnlen(proxy_conn->host, DNS_MAX_CNAME_LEN);\n\t\t*ptr = domainlen;\n\t\tptr++;\n\t\tmemcpy(ptr, proxy_conn->host, domainlen);\n\t\tptr += domainlen;\n\t} else {\n\t\tif (proxy_conn->is_udp) {\n\t\t\tmemset(&addr, 0, proxy_conn->server_info->server_addrlen);\n\t\t\taddr_len = proxy_conn->server_info->server_addrlen;\n\t\t\taddr.ss_family = proxy_conn->server_info->server_addr.ss_family;\n\t\t} else {\n\t\t\tgetaddr_by_host(proxy_conn->host, (struct sockaddr *)&addr, &addr_len);\n\t\t}\n\n\t\tswitch (addr.ss_family) {\n\t\tcase AF_INET: {\n\t\t\tstruct sockaddr_in *addr_in = NULL;\n\t\t\taddr_in = (struct sockaddr_in *)&addr;\n\t\t\t*ptr = PROXY_SOCKS5_TYPE_IPV4;\n\t\t\tptr++;\n\t\t\tmemcpy(ptr, &addr_in->sin_addr.s_addr, 4);\n\t\t\tptr += 4;\n\t\t} break;\n\t\tcase AF_INET6: {\n\t\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\t\taddr_in6 = (struct sockaddr_in6 *)&addr;\n\t\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\t\t*ptr = PROXY_SOCKS5_TYPE_IPV4;\n\t\t\t\tptr++;\n\t\t\t\tmemcpy(ptr, addr_in6->sin6_addr.s6_addr + 12, 4);\n\t\t\t\tptr += 4;\n\t\t\t} else {\n\t\t\t\t*ptr = PROXY_SOCKS5_TYPE_IPV6;\n\t\t\t\tptr++;\n\t\t\t\tmemcpy(ptr, addr_in6->sin6_addr.s6_addr, 16);\n\t\t\t\tptr += 16;\n\t\t\t}\n\t\t} break;\n\t\tdefault:\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\t}\n\t*((short *)(ptr)) = htons(proxy_conn->port);\n\tptr += 2;\n\n\tlen = send(proxy_conn->fd, buff, ptr - buff, MSG_NOSIGNAL);\n\tif (len != ptr - buff) {\n\t\ttlog(TLOG_ERROR, \"Send proxy request failed.\");\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t}\n\tproxy_conn->state = PROXY_CONN_CONNECTING;\n\treturn PROXY_HANDSHAKE_WANT_READ;\n}\n\nstatic proxy_handshake_state _proxy_handshake_socks5_send_auth(struct proxy_conn *proxy_conn)\n{\n\tchar buff[DNS_MAX_CNAME_LEN * 2];\n\tint len = 0;\n\tint offset = 0;\n\tmemset(buff, 0, sizeof(buff));\n\n\tbuff[0] = 0x1;\n\tbuff[1] = strnlen(proxy_conn->server_info->info.username, PROXY_MAX_NAMELEN);\n\tsafe_strncpy(buff + 2, proxy_conn->server_info->info.username, buff[1] + 1);\n\toffset = buff[1] + 2;\n\tbuff[offset] = strnlen(proxy_conn->server_info->info.password, PROXY_MAX_NAMELEN);\n\tsafe_strncpy(buff + offset + 1, proxy_conn->server_info->info.password, buff[offset] + 1);\n\toffset += buff[offset] + 1;\n\tlen = send(proxy_conn->fd, buff, offset, MSG_NOSIGNAL);\n\tif (len != offset) {\n\t\ttlog(TLOG_ERROR, \"send auth failed, len: %d, %s\", len, strerror(errno));\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t}\n\n\tproxy_conn->state = PROXY_CONN_AUTH_ACK;\n\treturn PROXY_HANDSHAKE_WANT_READ;\n}\n\nstatic proxy_handshake_state _proxy_handshake_socks5(struct proxy_conn *proxy_conn)\n{\n\tint len = 0;\n\tchar buff[DNS_MAX_CNAME_LEN * 2];\n\tstatic time_t last_error_log_time = 0;\n\n\tmemset(buff, 0, sizeof(buff));\n\n\tif (proxy_conn == NULL) {\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t}\n\n\tif (proxy_conn->fd < 0) {\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t}\n\n\tswitch (proxy_conn->state) {\n\tcase PROXY_CONN_INIT: {\n\t\tbuff[0] = PROXY_SOCKS5_VERSION;\n\t\tbuff[1] = 0x2; // 2 auth methods\n\t\tbuff[2] = PROXY_SOCKS5_NO_AUTH;\n\t\tbuff[3] = PROXY_SOCKS5_AUTH_USER_PASS;\n\t\tlen = send(proxy_conn->fd, buff, 4, MSG_NOSIGNAL);\n\t\tif (len != 4) {\n\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"connect socks5 server %s failed, %s\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name, strerror(errno));\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tproxy_conn->state = PROXY_CONN_INIT_ACK;\n\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t} break;\n\tcase PROXY_CONN_INIT_ACK:\n\t\tlen = recv(proxy_conn->fd, proxy_conn->buffer.buffer + proxy_conn->buffer.len,\n\t\t\t\t   sizeof(proxy_conn->buffer.buffer), 0);\n\t\tif (len <= 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t\t\t}\n\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"recv socks5 init ack from %s failed, %s\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name, strerror(errno));\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tproxy_conn->buffer.len += len;\n\t\tif (proxy_conn->buffer.len < 2) {\n\t\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t\t}\n\n\t\tif (proxy_conn->buffer.len > 2) {\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"recv socks5 init ack from %s failed\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name);\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tproxy_conn->buffer.len = 0;\n\n\t\tif (proxy_conn->buffer.buffer[0] != PROXY_SOCKS5_VERSION) {\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"server %s not support socks5\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name);\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tif ((unsigned char)proxy_conn->buffer.buffer[1] == PROXY_SOCKS5_AUTH_NONE) {\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"server %s not support auth methods\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name);\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"server %s select auth method is %d\", proxy_conn->server_info->proxy_name,\n\t\t\t proxy_conn->buffer.buffer[1]);\n\t\tif (proxy_conn->buffer.buffer[1] == PROXY_SOCKS5_AUTH_USER_PASS) {\n\t\t\treturn _proxy_handshake_socks5_send_auth(proxy_conn);\n\t\t}\n\n\t\tif (proxy_conn->buffer.buffer[1] == PROXY_SOCKS5_NO_AUTH) {\n\t\t\treturn _proxy_handshake_socks5_reply_connect_addr(proxy_conn);\n\t\t}\n\n\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"server %s select invalid auth method %d\",\n\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name, proxy_conn->buffer.buffer[1]);\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t\tbreak;\n\tcase PROXY_CONN_AUTH_ACK:\n\t\tlen = recv(proxy_conn->fd, proxy_conn->buffer.buffer + proxy_conn->buffer.len,\n\t\t\t\t   sizeof(proxy_conn->buffer.buffer), 0);\n\t\tif (len <= 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t\t\t}\n\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"recv socks5 auth ack from %s failed, %s\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name, strerror(errno));\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tproxy_conn->buffer.len += len;\n\t\tif (proxy_conn->buffer.len < 2) {\n\t\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t\t}\n\n\t\tif (proxy_conn->buffer.len != 2) {\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"recv socks5 auth ack from %s failed\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name);\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tproxy_conn->buffer.len = 0;\n\n\t\tif (proxy_conn->buffer.buffer[0] != 0x1) {\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"server %s not support socks5\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name);\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tif (proxy_conn->buffer.buffer[1] != 0x0) {\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time,\n\t\t\t\t\t\t\t\t\t  \"server %s auth failed, incorrect user or password, code: %d\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name, proxy_conn->buffer.buffer[1]);\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"server %s auth success\", proxy_conn->server_info->proxy_name);\n\t\tproxy_conn->state = PROXY_CONN_CONNECTING;\n\t\treturn _proxy_handshake_socks5_reply_connect_addr(proxy_conn);\n\tcase PROXY_CONN_CONNECTING: {\n\t\tunsigned char addr[16];\n\t\tunsigned short port = 0;\n\t\tint use_dest_ip = 0;\n\t\tunsigned char *recv_buff = NULL;\n\n\t\tint addr_len = 0;\n\t\tlen = recv(proxy_conn->fd, proxy_conn->buffer.buffer + proxy_conn->buffer.len,\n\t\t\t\t   sizeof(proxy_conn->buffer.buffer), 0);\n\t\tif (len <= 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t\t\t}\n\n\t\t\tif (len == 0) {\n\t\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"server %s closed connection\",\n\t\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name);\n\t\t\t} else {\n\t\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"recv socks5 connect ack from %s failed, %s\",\n\t\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name, strerror(errno));\n\t\t\t}\n\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tproxy_conn->buffer.len += len;\n\t\tif (proxy_conn->buffer.len < 10) {\n\t\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t\t}\n\t\trecv_buff = proxy_conn->buffer.buffer;\n\n\t\tif (recv_buff[0] != PROXY_SOCKS5_VERSION) {\n\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"server %s not support socks5\",\n\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name);\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tif (recv_buff[1] != 0) {\n\t\t\tif (recv_buff[1] <= (sizeof(proxy_socks5_status_code) / sizeof(proxy_socks5_status_code[0]))) {\n\t\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"server %s reply failed, error-code: %s\",\n\t\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name,\n\t\t\t\t\t\t\t\t\t\t  proxy_socks5_status_code[(int)recv_buff[1]]);\n\t\t\t} else {\n\t\t\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"server %s reply failed, error-code: %x\",\n\t\t\t\t\t\t\t\t\t\t  proxy_conn->server_info->proxy_name, recv_buff[1]);\n\t\t\t}\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tswitch (recv_buff[3]) {\n\t\tcase PROXY_SOCKS5_TYPE_IPV4: {\n\t\t\tstruct sockaddr_in *addr_in = NULL;\n\t\t\taddr_in = (struct sockaddr_in *)&proxy_conn->udp_dest_addr;\n\t\t\tproxy_conn->udp_dest_addrlen = sizeof(struct sockaddr_in);\n\t\t\tif (proxy_conn->buffer.len < 10) {\n\t\t\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t\t\t}\n\n\t\t\taddr_len = 4;\n\t\t\tmemcpy(addr, recv_buff + 4, addr_len);\n\t\t\tport = ntohs(*((short *)(recv_buff + 4 + addr_len)));\n\t\t\taddr_in->sin_family = AF_INET;\n\t\t\taddr_in->sin_addr.s_addr = *((int *)addr);\n\t\t\taddr_in->sin_port = *((short *)(recv_buff + 4 + addr_len));\n\t\t\tif (addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] == 0) {\n\t\t\t\tuse_dest_ip = 1;\n\t\t\t}\n\n\t\t\ttlog(TLOG_DEBUG, \"server %s proxy dest: %d.%d.%d.%d:%d\\n\", proxy_conn->server_info->proxy_name, addr[0],\n\t\t\t\t addr[1], addr[2], addr[3], port);\n\t\t} break;\n\t\tcase PROXY_SOCKS5_TYPE_IPV6: {\n\t\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\t\taddr_in6 = (struct sockaddr_in6 *)&proxy_conn->udp_dest_addr;\n\t\t\tproxy_conn->udp_dest_addrlen = sizeof(struct sockaddr_in6);\n\t\t\tif (proxy_conn->buffer.len < 22) {\n\t\t\t\treturn PROXY_HANDSHAKE_WANT_READ;\n\t\t\t}\n\n\t\t\taddr_len = 16;\n\t\t\tmemcpy(addr, recv_buff + 4, addr_len);\n\t\t\tport = ntohs(*((short *)(recv_buff + 4 + addr_len)));\n\t\t\taddr_in6->sin6_family = AF_INET6;\n\t\t\tmemcpy(addr_in6->sin6_addr.s6_addr, addr, addr_len);\n\t\t\taddr_in6->sin6_port = *((short *)(recv_buff + 4 + addr_len));\n\n\t\t\tif (addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] == 0 && addr[4] == 0 && addr[5] == 0 &&\n\t\t\t\taddr[6] == 0 && addr[7] == 0 && addr[8] == 0 && addr[9] == 0 && addr[10] == 0 && addr[11] == 0 &&\n\t\t\t\taddr[12] == 0 && addr[13] == 0 && addr[14] == 0 && addr[15] == 0) {\n\t\t\t\tuse_dest_ip = 1;\n\t\t\t}\n\n\t\t\ttlog(TLOG_DEBUG, \"server %s proxy dest: [%x:%x:%x:%x:%x:%x:%x:%x]:%d\\n\",\n\t\t\t\t proxy_conn->server_info->proxy_name, ntohs(*((short *)addr)), ntohs(*((short *)(addr + 2))),\n\t\t\t\t ntohs(*((short *)(addr + 4))), ntohs(*((short *)(addr + 6))), ntohs(*((short *)(addr + 8))),\n\t\t\t\t ntohs(*((short *)(addr + 10))), ntohs(*((short *)(addr + 12))), ntohs(*((short *)(addr + 14))), port);\n\t\t} break;\n\t\tdefault:\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tif (use_dest_ip && proxy_conn->is_udp) {\n\t\t\tmemcpy(&proxy_conn->udp_dest_addr, &proxy_conn->server_info->server_addr,\n\t\t\t\t   proxy_conn->server_info->server_addrlen);\n\t\t\tproxy_conn->udp_dest_addrlen = proxy_conn->server_info->server_addrlen;\n\t\t\tswitch (proxy_conn->udp_dest_addr.ss_family) {\n\t\t\tcase AF_INET: {\n\t\t\t\tstruct sockaddr_in *addr_in = NULL;\n\t\t\t\taddr_in = (struct sockaddr_in *)&proxy_conn->udp_dest_addr;\n\t\t\t\taddr_in->sin_port = *((short *)(recv_buff + 4 + addr_len));\n\t\t\t} break;\n\t\t\tcase AF_INET6: {\n\t\t\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\t\t\taddr_in6 = (struct sockaddr_in6 *)&proxy_conn->udp_dest_addr;\n\t\t\t\taddr_in6->sin6_port = *((short *)(recv_buff + 4 + addr_len));\n\t\t\t} break;\n\t\t\tdefault:\n\t\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (_proxy_handshake_socks5_connect_udp(proxy_conn) != 0) {\n\t\t\treturn PROXY_HANDSHAKE_ERR;\n\t\t}\n\n\t\tproxy_conn->state = PROXY_CONN_CONNECTED;\n\t\ttlog(TLOG_DEBUG, \"success connect to socks5 proxy server %s, type: %s\", proxy_conn->server_info->proxy_name,\n\t\t\t proxy_conn->is_udp ? \"udp\" : \"tcp\");\n\t\treturn PROXY_HANDSHAKE_CONNECTED;\n\t} break;\n\tdefault:\n\t\tPROXY_THROTTLED_ERROR_LOG(last_error_log_time, \"client socks5 status %d is invalid\", proxy_conn->state);\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t}\n\n\treturn PROXY_HANDSHAKE_ERR;\n}\n\nstatic int _proxy_handshake_http(struct proxy_conn *proxy_conn)\n{\n\tint len = 0;\n\tproxy_handshake_state ret = PROXY_HANDSHAKE_ERR;\n\tchar buff[4096];\n\tstruct http_head *http_head = NULL;\n\n\tif (proxy_conn == NULL) {\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t}\n\n\tif (proxy_conn->fd < 0) {\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t}\n\n\tswitch (proxy_conn->state) {\n\tcase PROXY_CONN_INIT: {\n\t\tchar connecthost[DNS_MAX_CNAME_LEN * 2];\n\t\tstruct sockaddr_storage addr;\n\n\t\tsocklen_t addr_len = sizeof(addr);\n\t\tgetaddr_by_host(proxy_conn->host, (struct sockaddr *)&addr, &addr_len);\n\n\t\tif (proxy_conn->server_info->info.use_domain) {\n\t\t\tsnprintf(connecthost, sizeof(connecthost), \"%s:%d\", proxy_conn->host, proxy_conn->port);\n\t\t} else {\n\t\t\tstruct sockaddr_in *addr_in;\n\t\t\taddr_in = (struct sockaddr_in *)&addr;\n\t\t\tunsigned char *paddr = (unsigned char *)&addr_in->sin_addr.s_addr;\n\t\t\tsnprintf(connecthost, sizeof(connecthost), \"%d.%d.%d.%d:%d\", paddr[0], paddr[1], paddr[2], paddr[3],\n\t\t\t\t\t proxy_conn->port);\n\t\t}\n\n\t\tint msglen = 0;\n\n\t\tif (proxy_conn->server_info->info.username[0] == '\\0') {\n\t\t\tmsglen = snprintf(buff, sizeof(buff),\n\t\t\t\t\t\t\t  \"CONNECT %s HTTP/1.1\\r\\n\"\n\t\t\t\t\t\t\t  \"Host: %s\\r\\n\"\n\t\t\t\t\t\t\t  \"Proxy-Connection: Keep-Alive\\r\\n\\r\\n\",\n\t\t\t\t\t\t\t  connecthost, connecthost);\n\t\t} else {\n\t\t\tchar auth[256];\n\t\t\tchar base64_auth[256 * 2];\n\t\t\tsnprintf(auth, sizeof(auth), \"%s:%s\", proxy_conn->server_info->info.username,\n\t\t\t\t\t proxy_conn->server_info->info.password);\n\t\t\tSSL_base64_encode(auth, strlen(auth), base64_auth);\n\n\t\t\tmsglen = snprintf(buff, sizeof(buff),\n\t\t\t\t\t\t\t  \"CONNECT %s HTTP/1.1\\r\\n\"\n\t\t\t\t\t\t\t  \"Host: %s\\r\\n\"\n\t\t\t\t\t\t\t  \"Proxy-Authorization: Basic %s\\r\\n\"\n\t\t\t\t\t\t\t  \"Proxy-Connection: Keep-Alive\\r\\n\\r\\n\",\n\t\t\t\t\t\t\t  connecthost, connecthost, base64_auth);\n\t\t}\n\n\t\tlen = send(proxy_conn->fd, buff, msglen, MSG_NOSIGNAL);\n\t\tif (len != msglen) {\n\t\t\ttlog(TLOG_ERROR, \"connect to https proxy server %s failed, %s\", proxy_conn->server_info->proxy_name,\n\t\t\t\t strerror(errno));\n\t\t\tgoto out;\n\t\t}\n\n\t\tproxy_conn->state = PROXY_CONN_CONNECTING;\n\t\tret = PROXY_HANDSHAKE_WANT_READ;\n\t\tgoto out;\n\t} break;\n\tcase PROXY_CONN_CONNECTING: {\n\t\thttp_head = http_head_init(4096, HTTP_VERSION_1_1);\n\t\tif (http_head == NULL) {\n\t\t\tgoto out;\n\t\t}\n\n\t\tlen = recv(proxy_conn->fd, proxy_conn->buffer.buffer + proxy_conn->buffer.len,\n\t\t\t\t   sizeof(proxy_conn->buffer.buffer), 0);\n\t\tif (len <= 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\t\tret = PROXY_HANDSHAKE_WANT_READ;\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\tif (len == 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"remote server %s closed.\", proxy_conn->server_info->proxy_name);\n\t\t\t} else {\n\t\t\t\ttlog(TLOG_ERROR, \"recv from %s failed, %s\", proxy_conn->server_info->proxy_name, strerror(errno));\n\t\t\t}\n\t\t\tgoto out;\n\t\t}\n\t\tproxy_conn->buffer.len += len;\n\n\t\tlen = http_head_parse(http_head, proxy_conn->buffer.buffer, proxy_conn->buffer.len);\n\t\tif (len < 0) {\n\t\t\tif (len == -1) {\n\t\t\t\tret = PROXY_HANDSHAKE_WANT_READ;\n\t\t\t\tgoto out;\n\t\t\t}\n\n\t\t\ttlog(TLOG_DEBUG, \"remote server %s not supported.\", proxy_conn->server_info->proxy_name);\n\t\t\tgoto out;\n\t\t}\n\n\t\tif (http_head_get_httpcode(http_head) != 200) {\n\t\t\ttlog(TLOG_WARN, \"http server %s query failed, server return http code : %d, %s\",\n\t\t\t\t proxy_conn->server_info->proxy_name, http_head_get_httpcode(http_head),\n\t\t\t\t http_head_get_httpcode_msg(http_head));\n\t\t\tgoto out;\n\t\t}\n\n\t\tproxy_conn->buffer.len -= len;\n\t\tif (proxy_conn->buffer.len > 0) {\n\t\t\tmemmove(proxy_conn->buffer.buffer, proxy_conn->buffer.buffer + len, proxy_conn->buffer.len);\n\t\t}\n\n\t\tif (proxy_conn->buffer.len < 0) {\n\t\t\tproxy_conn->buffer.len = 0;\n\t\t}\n\t\ttlog(TLOG_DEBUG, \"success connect to http proxy server %s\", proxy_conn->server_info->proxy_name);\n\t\tproxy_conn->state = PROXY_CONN_CONNECTED;\n\t\tret = PROXY_HANDSHAKE_CONNECTED;\n\t\tgoto out;\n\t} break;\n\tdefault:\n\t\tgoto out;\n\t\tbreak;\n\t}\n\nout:\n\tif (http_head) {\n\t\thttp_head_destroy(http_head);\n\t}\n\n\treturn ret;\n}\n\nproxy_handshake_state proxy_conn_handshake(struct proxy_conn *proxy_conn)\n{\n\tif (proxy_conn == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (proxy_conn->state == PROXY_CONN_CONNECTED) {\n\t\treturn PROXY_HANDSHAKE_OK;\n\t}\n\n\tswitch (proxy_conn->type) {\n\tcase PROXY_SOCKS5:\n\t\treturn _proxy_handshake_socks5(proxy_conn);\n\tcase PROXY_HTTP:\n\t\treturn _proxy_handshake_http(proxy_conn);\n\tdefault:\n\t\treturn PROXY_HANDSHAKE_ERR;\n\t}\n\n\treturn PROXY_HANDSHAKE_ERR;\n}\n\nstatic int _proxy_is_tcp_connected(struct proxy_conn *proxy_conn)\n{\n\tchar buff[1];\n\tint ret = 0;\n\n\tif (proxy_conn == NULL) {\n\t\treturn 0;\n\t}\n\n\tret = recv(proxy_conn->fd, buff, 1, MSG_PEEK | MSG_DONTWAIT);\n\tif (ret < 0) {\n\t\tif (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nint proxy_conn_sendto(struct proxy_conn *proxy_conn, const void *buf, size_t len, int flags,\n\t\t\t\t\t  const struct sockaddr *dest_addr, socklen_t addrlen)\n{\n\tchar buffer[PROXY_BUFFER_SIZE];\n\tint buffer_len = 0;\n\tint ret = 0;\n\n\tif (proxy_conn == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (proxy_conn->udp_fd < 0) {\n\t\treturn -1;\n\t}\n\n\tif (_proxy_is_tcp_connected(proxy_conn) == 0) {\n\t\terrno = ECONNRESET;\n\t\treturn -1;\n\t}\n\n\tbuffer[0] = 0x00;\n\tbuffer[1] = 0x00;\n\tbuffer[2] = 0x00;\n\tbuffer_len += 3;\n\n\tswitch (dest_addr->sa_family) {\n\tcase AF_INET:\n\t\tbuffer[3] = PROXY_SOCKS5_TYPE_IPV4;\n\t\tmemcpy(buffer + 4, &((struct sockaddr_in *)dest_addr)->sin_addr.s_addr, 4);\n\t\tmemcpy(buffer + 8, &((struct sockaddr_in *)dest_addr)->sin_port, 2);\n\t\tbuffer_len += 7;\n\t\tbreak;\n\tcase AF_INET6:\n\t\tbuffer[3] = PROXY_SOCKS5_TYPE_IPV6;\n\t\tmemcpy(buffer + 4, &((struct sockaddr_in6 *)dest_addr)->sin6_addr.s6_addr, 16);\n\t\tmemcpy(buffer + 20, &((struct sockaddr_in6 *)dest_addr)->sin6_port, 2);\n\t\tbuffer_len += 19;\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t}\n\n\tif (sizeof(buffer) - buffer_len <= len) {\n\t\terrno = ENOSPC;\n\t\treturn -1;\n\t}\n\n\tmemcpy(buffer + buffer_len, buf, len);\n\tbuffer_len += len;\n\n\tret = sendto(proxy_conn->udp_fd, buffer, buffer_len, MSG_NOSIGNAL, (struct sockaddr *)&proxy_conn->udp_dest_addr,\n\t\t\t\t proxy_conn->udp_dest_addrlen);\n\tif (ret != buffer_len) {\n\t\treturn -1;\n\t}\n\n\treturn len;\n}\n\nint proxy_conn_recvfrom(struct proxy_conn *proxy_conn, void *buf, size_t len, int flags, struct sockaddr *src_addr,\n\t\t\t\t\t\tsocklen_t *addrlen)\n{\n\tchar buffer[PROXY_BUFFER_SIZE];\n\tint buffer_len = 0;\n\tint ret = 0;\n\n\tif (proxy_conn == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (proxy_conn->udp_fd < 0) {\n\t\treturn -1;\n\t}\n\n\tret = recvfrom(proxy_conn->udp_fd, buffer, sizeof(buffer), MSG_NOSIGNAL, NULL, NULL);\n\tif (ret <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (buffer[0] != 0x00 || buffer[1] != 0x00 || buffer[2] != 0x00) {\n\t\treturn -1;\n\t}\n\n\tswitch (buffer[3]) {\n\tcase PROXY_SOCKS5_TYPE_IPV4:\n\t\tif (ret < 10) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (src_addr) {\n\t\t\tmemset(src_addr, 0, sizeof(struct sockaddr_in));\n\t\t\t((struct sockaddr_in *)src_addr)->sin_family = AF_INET;\n\t\t\tmemcpy(&((struct sockaddr_in *)src_addr)->sin_addr.s_addr, buffer + 4, 4);\n\t\t\tmemcpy(&((struct sockaddr_in *)src_addr)->sin_port, buffer + 8, 2);\n\t\t}\n\n\t\tif (addrlen) {\n\t\t\t*addrlen = sizeof(struct sockaddr_in);\n\t\t}\n\n\t\tbuffer_len = 10;\n\t\tbreak;\n\tcase PROXY_SOCKS5_TYPE_IPV6:\n\t\tif (ret < 22) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (src_addr) {\n\t\t\tmemset(src_addr, 0, sizeof(struct sockaddr_in6));\n\t\t\t((struct sockaddr_in6 *)src_addr)->sin6_family = AF_INET6;\n\t\t\tmemcpy(&((struct sockaddr_in6 *)src_addr)->sin6_addr.s6_addr, buffer + 4, 16);\n\t\t\tmemcpy(&((struct sockaddr_in6 *)src_addr)->sin6_port, buffer + 20, 2);\n\t\t}\n\n\t\tif (addrlen) {\n\t\t\t*addrlen = sizeof(struct sockaddr_in6);\n\t\t}\n\n\t\tbuffer_len = 22;\n\t\tbreak;\n\tdefault:\n\n\t\treturn -1;\n\t}\n\n\tif (ret - buffer_len > (int)len) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(buf, buffer + buffer_len, ret - buffer_len);\n\treturn ret - buffer_len;\n}\n\nint proxy_conn_get_fd(struct proxy_conn *proxy_conn)\n{\n\tif (proxy_conn == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn proxy_conn->fd;\n}\n\nint proxy_conn_get_udpfd(struct proxy_conn *proxy_conn)\n{\n\tif (proxy_conn == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn proxy_conn->udp_fd;\n}\n\nint proxy_conn_is_udp(struct proxy_conn *proxy_conn)\n{\n\tif (proxy_conn == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn proxy_conn->is_udp;\n}\n\nint proxy_init(void)\n{\n\tif (is_proxy_init == 1) {\n\t\treturn -1;\n\t}\n\n\tmemset(&proxy, 0, sizeof(proxy));\n\thash_init(proxy.proxy_server);\n\tis_proxy_init = 1;\n\treturn 0;\n}\n\nvoid proxy_exit(void)\n{\n\tif (is_proxy_init == 0) {\n\t\treturn;\n\t}\n\t_proxy_remove_all();\n\n\tis_proxy_init = 0;\n\n\treturn;\n}\n"
  },
  {
    "path": "src/smartdns.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/smartdns.h\"\n\n#include \"smartdns/lib/art.h\"\n#include \"smartdns/lib/atomic.h\"\n#include \"smartdns/lib/hashtable.h\"\n#include \"smartdns/lib/list.h\"\n#include \"smartdns/lib/rbtree.h\"\n#include \"smartdns/timer.h\"\n#include \"smartdns/tlog.h\"\n\n#include <errno.h>\n#include <fcntl.h>\n#include <getopt.h>\n#include <libgen.h>\n#include <openssl/err.h>\n#include <openssl/ssl.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/prctl.h>\n#include <sys/resource.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <syslog.h>\n#include <ucontext.h>\n\n#define MAX_KEY_LEN 64\n#define SMARTDNS_PID_FILE \"/run/smartdns.pid\"\n#define SMARTDNS_LEGACY_PID_FILE \"/var/run/smartdns.pid\"\n#define TMP_BUFF_LEN_32 32\n#define SMARTDNS_CRASH_CODE 254\n\ntypedef enum {\n\tSMARTDNS_RUN_MONITOR_OK = 0,\n\tSMARTDNS_RUN_MONITOR_ERROR = 1,\n\tSMARTDNS_RUN_MONITOR_EXIT = 2,\n} smartdns_run_monitor_ret;\n\nstatic int verbose_screen;\nstatic int exit_status;\nstatic int exit_restart;\n\nstatic void _help(void)\n{\n\t/* clang-format off */\n\tchar *help = \"\"\n\t\t\"Usage: smartdns [OPTION]...\\n\"\n\t\t\"Start smartdns server.\\n\"\n\t\t\"  -f            run foreground.\\n\"\n\t\t\"  -c [conf]     config file.\\n\"\n\t\t\"  -p [pid]      pid file path, '-' means don't create pid file.\\n\"\n\t\t\"  -R            restart smartdns when crash.\\n\"\n\t\t\"  -S            ignore segment fault signal.\\n\"\n\t\t\"  -x            verbose screen.\\n\"\n\t\t\"  -v            display version.\\n\"\n\t\t\"  -h            show this help message.\\n\"\n\t\t\"\"\n\t\t\"Debug options:\\n\"\n#ifdef DEBUG\n\t\t\"  -N [file]     dump dns packet to file.\\n\"\n#endif\n\t\t\"  --cache-print [file]  print cache.\\n\"\n\t\t\"  --is-quic-supported   is quic http3 supported.\\n\"\n\t\t\"\"\n\n\t\t\"Online help: https://pymumu.github.io/smartdns\\n\"\n\t\t\"Copyright (C) Nick Peng <pymumu@gmail.com>\\n\"\n\t\t;\n\t/* clang-format on */\n\tprintf(\"%s\", help);\n}\n\nstatic void _smartdns_get_version(char *str_ver, int str_ver_len)\n{\n\tchar commit_ver[TMP_BUFF_LEN_32 * 2] = {0};\n#ifdef COMMIT_VERION\n\tsnprintf(commit_ver, sizeof(commit_ver), \" (%s)\", COMMIT_VERION);\n#endif\n\n#ifdef SMARTDNS_VERION\n\tconst char *ver = SMARTDNS_VERION;\n\tsnprintf(str_ver, str_ver_len, \"%s%s\", ver, commit_ver);\n#else\n\tstruct tm tm;\n\tget_compiled_time(&tm);\n\tsnprintf(str_ver, str_ver_len, \"1.%.4d%.2d%.2d-%.2d%.2d%s\", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,\n\t\t\t tm.tm_hour, tm.tm_min, commit_ver);\n#endif\n}\n\nconst char *smartdns_version(void)\n{\n\tstatic char str_ver[256] = {0};\n\tif (str_ver[0] == 0) {\n\t\t_smartdns_get_version(str_ver, sizeof(str_ver));\n\t}\n\treturn str_ver;\n}\n\nstatic void _show_version(void)\n{\n\tchar str_ver[256] = {0};\n\t_smartdns_get_version(str_ver, sizeof(str_ver));\n\tprintf(\"smartdns %s\\n\", str_ver);\n}\n\nstatic int _smartdns_load_from_resolv_file(const char *resolv_file)\n{\n\tFILE *fp = NULL;\n\tchar line[MAX_LINE_LEN];\n\tchar key[MAX_KEY_LEN] = {0};\n\tchar value[MAX_LINE_LEN];\n\tchar ns_ip[DNS_MAX_IPLEN];\n\tint port = PORT_NOT_DEFINED;\n\tint ret = -1;\n\n\tint filed_num = 0;\n\n\tfp = fopen(resolv_file, \"r\");\n\tif (fp == NULL) {\n\t\ttlog(TLOG_ERROR, \"open %s failed, %s\", resolv_file, strerror(errno));\n\t\treturn -1;\n\t}\n\n\twhile (fgets(line, MAX_LINE_LEN, fp)) {\n\t\tfiled_num = sscanf(line, \"%63s %1023[^\\r\\n]s\", key, value);\n\n\t\tif (filed_num != 2) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (strncmp(key, \"nameserver\", MAX_KEY_LEN - 1) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (parse_ip(value, ns_ip, &port) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (port == PORT_NOT_DEFINED) {\n\t\t\tport = DEFAULT_DNS_PORT;\n\t\t}\n\n\t\tsafe_strncpy(dns_conf.servers[dns_conf.server_num].server, ns_ip, DNS_MAX_IPLEN);\n\t\tdns_conf.servers[dns_conf.server_num].port = port;\n\t\tdns_conf.servers[dns_conf.server_num].type = DNS_SERVER_UDP;\n\t\tdns_conf.servers[dns_conf.server_num].set_mark = -1;\n\t\tdns_conf.server_num++;\n\t\tret = 0;\n\t}\n\n\tfclose(fp);\n\n\treturn ret;\n}\n\nstatic int _smartdns_load_from_resolv(void)\n{\n\treturn _smartdns_load_from_resolv_file(dns_conf.dns_resolv_file);\n}\n\nstatic int _smartdns_load_from_default_resolv(void)\n{\n\treturn _smartdns_load_from_resolv_file(DNS_RESOLV_FILE);\n}\n\nstatic int _smartdns_prepare_server_flags(struct client_dns_server_flags *flags, struct dns_servers *server)\n{\n\tmemset(flags, 0, sizeof(*flags));\n\tswitch (server->type) {\n\tcase DNS_SERVER_UDP: {\n\t\tstruct client_dns_server_flag_udp *flag_udp = &flags->udp;\n\t\tflag_udp->ttl = server->ttl;\n\t} break;\n\tcase DNS_SERVER_HTTP3:\n\tcase DNS_SERVER_HTTPS: {\n\t\tstruct client_dns_server_flag_https *flag_http = &flags->https;\n\t\tif (server->spki[0] != 0) {\n\t\t\tflag_http->spi_len =\n\t\t\t\tdns_client_spki_decode(server->spki, (unsigned char *)flag_http->spki, sizeof(flag_http->spki));\n\t\t\tif (flag_http->spi_len <= 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"decode spki failed, %s:%d\", server->server, server->port);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t\tsafe_strncpy(flag_http->hostname, server->hostname, sizeof(flag_http->hostname));\n\t\tsafe_strncpy(flag_http->path, server->path, sizeof(flag_http->path));\n\t\tsafe_strncpy(flag_http->httphost, server->httphost, sizeof(flag_http->httphost));\n\t\tsafe_strncpy(flag_http->tls_host_verify, server->tls_host_verify, sizeof(flag_http->tls_host_verify));\n\t\tsafe_strncpy(flag_http->alpn, server->alpn, DNS_MAX_ALPN_LEN);\n\t\tflag_http->skip_check_cert = server->skip_check_cert;\n\t} break;\n\tcase DNS_SERVER_QUIC:\n\tcase DNS_SERVER_TLS: {\n\t\tstruct client_dns_server_flag_tls *flag_tls = &flags->tls;\n\t\tif (server->spki[0] != 0) {\n\t\t\tflag_tls->spi_len =\n\t\t\t\tdns_client_spki_decode(server->spki, (unsigned char *)flag_tls->spki, sizeof(flag_tls->spki));\n\t\t\tif (flag_tls->spi_len <= 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"decode spki failed, %s:%d\", server->server, server->port);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t\tsafe_strncpy(flag_tls->hostname, server->hostname, sizeof(flag_tls->hostname));\n\t\tsafe_strncpy(flag_tls->tls_host_verify, server->tls_host_verify, sizeof(flag_tls->tls_host_verify));\n\t\tsafe_strncpy(flag_tls->alpn, server->alpn, DNS_MAX_ALPN_LEN);\n\t\tflag_tls->skip_check_cert = server->skip_check_cert;\n\t} break;\n\tcase DNS_SERVER_TCP:\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t\tbreak;\n\t}\n\n\tflags->type = server->type;\n\tflags->server_flag = server->server_flag;\n\tflags->result_flag = server->result_flag;\n\tflags->set_mark = server->set_mark;\n\tflags->drop_packet_latency_ms = server->drop_packet_latency_ms;\n\tflags->tcp_keepalive = server->tcp_keepalive;\n\tflags->subnet_all_query_types = server->subnet_all_query_types;\n\tflags->fallback = server->fallback;\n\tsafe_strncpy(flags->proxyname, server->proxyname, sizeof(flags->proxyname));\n\tsafe_strncpy(flags->ifname, server->ifname, sizeof(flags->ifname));\n\tif (server->ipv4_ecs.enable) {\n\t\tflags->ipv4_ecs.enable = 1;\n\t\tsafe_strncpy(flags->ipv4_ecs.ip, server->ipv4_ecs.ip, sizeof(flags->ipv4_ecs.ip));\n\t\tflags->ipv4_ecs.subnet = server->ipv4_ecs.subnet;\n\t}\n\n\tif (server->ipv6_ecs.enable) {\n\t\tflags->ipv6_ecs.enable = 1;\n\t\tsafe_strncpy(flags->ipv6_ecs.ip, server->ipv6_ecs.ip, sizeof(flags->ipv6_ecs.ip));\n\t\tflags->ipv6_ecs.subnet = server->ipv6_ecs.subnet;\n\t}\n\n\treturn 0;\n}\n\nstatic int _smartdns_add_servers(void)\n{\n\tunsigned long i = 0;\n\tint j = 0;\n\tint ret = 0;\n\tstruct dns_server_groups *group = NULL;\n\tstruct dns_servers *server = NULL;\n\tstruct client_dns_server_flags flags;\n\n\tfor (i = 0; i < (unsigned int)dns_conf.server_num; i++) {\n\t\tif (_smartdns_prepare_server_flags(&flags, &dns_conf.servers[i]) != 0) {\n\t\t\ttlog(TLOG_ERROR, \"prepare server flags failed, %s:%d\", dns_conf.servers[i].server,\n\t\t\t\t dns_conf.servers[i].port);\n\t\t\treturn -1;\n\t\t}\n\n\t\tret = dns_client_add_server(dns_conf.servers[i].server, dns_conf.servers[i].port, dns_conf.servers[i].type,\n\t\t\t\t\t\t\t\t\t&flags);\n\t\tif (ret != 0) {\n\t\t\ttlog(TLOG_ERROR, \"add server failed, %s:%d\", dns_conf.servers[i].server, dns_conf.servers[i].port);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\thash_for_each(dns_group_table.group, i, group, node)\n\t{\n\t\tret = dns_client_add_group(group->group_name);\n\t\tif (ret != 0) {\n\t\t\ttlog(TLOG_ERROR, \"add group failed, %s\", group->group_name);\n\t\t\treturn -1;\n\t\t}\n\n\t\tfor (j = 0; j < group->server_num; j++) {\n\t\t\tserver = group->servers[j];\n\t\t\tif (server == NULL) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (_smartdns_prepare_server_flags(&flags, server) != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"prepare server flags failed, %s:%d\", server->server, server->port);\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tret = dns_client_add_to_group(group->group_name, server->server, server->port, server->type, &flags);\n\t\t\tif (ret != 0) {\n\t\t\t\ttlog(TLOG_ERROR, \"add server %s to group %s failed\", server->server, group->group_name);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _proxy_add_servers(void)\n{\n\tunsigned long i = 0;\n\tstruct hlist_node *tmp = NULL;\n\tstruct dns_proxy_names *proxy = NULL;\n\tstruct dns_proxy_servers *server = NULL;\n\tstruct dns_proxy_servers *server_tmp = NULL;\n\n\thash_for_each_safe(dns_proxy_table.proxy, i, tmp, proxy, node)\n\t{\n\t\tlist_for_each_entry_safe(server, server_tmp, &proxy->server_list, list)\n\t\t{\n\t\t\tstruct proxy_info info;\n\t\t\tmemset(&info, 0, sizeof(info));\n\t\t\tinfo.type = server->type;\n\t\t\tinfo.port = server->port;\n\t\t\tsafe_strncpy(info.server, server->server, PROXY_MAX_IPLEN);\n\t\t\tsafe_strncpy(info.username, server->username, PROXY_MAX_NAMELEN);\n\t\t\tsafe_strncpy(info.password, server->password, PROXY_MAX_NAMELEN);\n\t\t\tinfo.use_domain = server->use_domain;\n\t\t\tproxy_add(proxy->proxy_name, &info);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _smartdns_plugin_init(void)\n{\n\tint ret = 0;\n\tunsigned long i = 0;\n\tstruct dns_conf_plugin *plugin = NULL;\n\tstruct hlist_node *tmp = NULL;\n\n\tret = dns_server_plugin_init();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"init plugin failed.\");\n\t\tgoto errout;\n\t}\n\n\thash_for_each_safe(dns_conf_plugin_table.plugins, i, tmp, plugin, node)\n\t{\n\t\tret = dns_plugin_add(plugin->file, plugin->argc, plugin->args, plugin->args_len);\n\t\tif (ret != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstatic int _smartdns_plugin_exit(void)\n{\n\tdns_server_plugin_exit();\n\treturn 0;\n}\n\nstatic int _smartdns_create_cert(void)\n{\n\tuid_t uid = 0;\n\tgid_t gid = 0;\n\tchar san[PATH_MAX] = {0};\n\t/* 13 month */\n\tint validity_days = 13 * 30;\n\tchar ddns_san[DNS_MAX_CNAME_LEN] = {0};\n\n\tif (dns_conf.need_cert == 0) {\n\t\treturn 0;\n\t}\n\n\tif (dns_conf.bind_ca_file[0] != 0 && dns_conf.bind_ca_key_file[0] != 0) {\n\t\treturn 0;\n\t}\n\n\tconf_get_conf_fullpath(\"smartdns-cert.pem\", dns_conf.bind_ca_file, sizeof(dns_conf.bind_ca_file));\n\tconf_get_conf_fullpath(\"smartdns-key.pem\", dns_conf.bind_ca_key_file, sizeof(dns_conf.bind_ca_key_file));\n\tconf_get_conf_fullpath(\"smartdns-root-key.pem\", dns_conf.bind_root_ca_key_file,\n\t\t\t\t\t\t   sizeof(dns_conf.bind_root_ca_key_file));\n\tif (access(dns_conf.bind_ca_file, F_OK) == 0 && access(dns_conf.bind_ca_key_file, F_OK) == 0) {\n\t\tif (is_cert_valid(dns_conf.bind_ca_file)) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (access(dns_conf.bind_root_ca_key_file, R_OK) != 0) {\n\t\t\ttlog(TLOG_WARN, \"root ca key file %s is not found, can not regenerate cert file.\",\n\t\t\t\t dns_conf.bind_root_ca_key_file);\n\t\t\treturn 0;\n\t\t}\n\t\tunlink(dns_conf.bind_ca_file);\n\t\tunlink(dns_conf.bind_ca_key_file);\n\t\ttlog(TLOG_WARN, \"regenerate cert with root ca key %s\", dns_conf.bind_root_ca_key_file);\n\t}\n\n\tif (dns_conf_get_ddns_domain()[0] != 0) {\n\t\tsnprintf(ddns_san, sizeof(ddns_san), \"DNS:%s\", dns_conf_get_ddns_domain());\n\t}\n\n\tif (generate_cert_san(san, sizeof(san), ddns_san) != 0) {\n\t\ttlog(TLOG_WARN, \"generate cert san failed.\");\n\t\treturn -1;\n\t}\n\n\tif (dns_conf.bind_ca_validity_days > 0) {\n\t\tvalidity_days = dns_conf.bind_ca_validity_days;\n\t}\n\n\tif (generate_cert_key(dns_conf.bind_ca_key_file, dns_conf.bind_ca_file, dns_conf.bind_root_ca_key_file, san,\n\t\t\t\t\t\t  validity_days) != 0) {\n\t\ttlog(TLOG_WARN, \"Generate default ssl cert and key file failed. %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\tint unused __attribute__((unused)) = 0;\n\n\tif (get_uid_gid(&uid, &gid) != 0) {\n\t\treturn 0;\n\t}\n\n\tunused = chown(dns_conf.bind_ca_file, uid, gid);\n\tunused = chown(dns_conf.bind_ca_key_file, uid, gid);\n\n\treturn 0;\n}\n\nint smartdns_get_cert(char *key, char *cert)\n{\n\tif (dns_conf.need_cert == 0) {\n\t\tdns_conf.need_cert = 1;\n\t}\n\n\tif (_smartdns_create_cert() != 0) {\n\t\ttlog(TLOG_WARN, \"generate ssl cert and key file failed. %s\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\tif (key != NULL) {\n\t\tsafe_strncpy(key, dns_conf.bind_ca_key_file, PATH_MAX);\n\t}\n\n\tif (cert != NULL) {\n\t\tsafe_strncpy(cert, dns_conf.bind_ca_file, PATH_MAX);\n\t}\n\n\treturn 0;\n}\n\nstatic int _smartdns_init_ssl(void)\n{\n#if OPENSSL_API_COMPAT < 0x10100000L\n\tSSL_load_error_strings();\n\tSSL_library_init();\n\tOpenSSL_add_all_algorithms();\n\tSSL_CRYPTO_thread_setup();\n#endif\n\treturn 0;\n}\n\nstatic int _smartdns_destroy_ssl(void)\n{\n#if OPENSSL_API_COMPAT < 0x10100000L\n\tSSL_CRYPTO_thread_cleanup();\n\tERR_free_strings();\n\tEVP_cleanup();\n#endif\n\treturn 0;\n}\n\nstatic const char *_smartdns_log_path(void)\n{\n\tchar *logfile = SMARTDNS_LOG_FILE;\n\n\tif (dns_conf.log_file[0] != 0) {\n\t\tlogfile = dns_conf.log_file;\n\t}\n\n\treturn logfile;\n}\n\nstatic int _smartdns_tlog_output_syslog_callback(struct tlog_loginfo *info, const char *buff, int bufflen,\n\t\t\t\t\t\t\t\t\t\t\t\t void *private_data)\n{\n\tint syslog_level = LOG_INFO;\n\tswitch (info->level) {\n\tcase TLOG_ERROR:\n\t\tsyslog_level = LOG_ERR;\n\t\tbreak;\n\tcase TLOG_WARN:\n\t\tsyslog_level = LOG_WARNING;\n\t\tbreak;\n\tcase TLOG_NOTICE:\n\t\tsyslog_level = LOG_NOTICE;\n\t\tbreak;\n\tcase TLOG_INFO:\n\t\tsyslog_level = LOG_INFO;\n\t\tbreak;\n\tcase TLOG_DEBUG:\n\t\tsyslog_level = LOG_DEBUG;\n\t\tbreak;\n\tdefault:\n\t\tsyslog_level = LOG_INFO;\n\t\tbreak;\n\t}\n\n\tsyslog(syslog_level, \"%.*s\", bufflen, buff);\n\treturn bufflen;\n}\n\nstatic int _smartdns_tlog_output_callback(struct tlog_loginfo *info, const char *buff, int bufflen, void *private_data)\n{\n\tsmartdns_plugin_func_server_log_callback((smartdns_log_level)info->level, buff, bufflen);\n\n\tif (dns_conf.log_syslog) {\n\t\treturn _smartdns_tlog_output_syslog_callback(info, buff, bufflen, private_data);\n\t}\n\n\treturn tlog_write_log(buff, bufflen);\n}\n\nstatic int _smartdns_init_log(void)\n{\n\tconst char *logfile = _smartdns_log_path();\n\tchar logdir[PATH_MAX] = {0};\n\tint logbuffersize = 0;\n\tint enable_log_screen = 0;\n\tint ret = 0;\n\n\tif (get_system_mem_size() > 1024 * 1024 * 1024) {\n\t\tlogbuffersize = 1024 * 1024;\n\t}\n\n\tsafe_strncpy(logdir, _smartdns_log_path(), PATH_MAX);\n\tif (verbose_screen != 0 || dns_conf.log_console != 0 || access(dir_name(logdir), W_OK) != 0) {\n\t\tenable_log_screen = 1;\n\t}\n\n\tunsigned int tlog_flag = TLOG_NONBLOCK;\n\tif (enable_log_screen == 1) {\n\t\ttlog_flag |= TLOG_SCREEN;\n\t}\n\n\tif (dns_conf.log_color_mode) {\n\t\ttlog_flag |= TLOG_SEGMENT;\n\t\tif (enable_log_screen) {\n\t\t\ttlog_flag |= TLOG_SCREEN_COLOR;\n\t\t}\n\t}\n\n\tif (dns_conf.log_syslog) {\n\t\ttlog_flag |= TLOG_SEGMENT;\n\t\ttlog_flag |= TLOG_FORMAT_NO_PREFIX;\n\t}\n\n\tret = tlog_init(logfile, dns_conf.log_size, dns_conf.log_num, logbuffersize, tlog_flag);\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"start tlog failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tif (enable_log_screen) {\n\t\ttlog_setlogscreen(1);\n\t\tverbose_screen = 1;\n\t}\n\n\ttlog_reg_log_output_func(_smartdns_tlog_output_callback, NULL);\n\n\ttlog_setlevel(dns_conf.log_level);\n\tif (dns_conf.log_file_mode > 0) {\n\t\ttlog_set_permission(tlog_get_root(), dns_conf.log_file_mode, dns_conf.log_file_mode);\n\t}\n\n\treturn 0;\n\nerrout:\n\treturn -1;\n}\n\nstatic int _smartdns_init_load_from_resolv(void)\n{\n\tint ret = 0;\n\tint i = 0;\n\n\tfor (i = 0; i < 180 && dns_conf.server_num <= 0; i++) {\n\t\tret = _smartdns_load_from_resolv();\n\t\tif (ret == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* try load from default resolv.conf file */\n\t\tif (i > 30 && strncmp(dns_conf.dns_resolv_file, DNS_RESOLV_FILE, MAX_LINE_LEN) != 0) {\n\t\t\tret = _smartdns_load_from_default_resolv();\n\t\t\tif (ret == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\ttlog(TLOG_DEBUG, \"load dns from resolv failed, retry after 1s, retry times %d.\", i + 1);\n\t\tsleep(1);\n\t}\n\n\tif (dns_conf.server_num <= 0) {\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nstatic int _smartdns_init(void)\n{\n\tint ret = 0;\n\tchar str_ver[256] = {0};\n\n\tif (_smartdns_init_log() != 0) {\n\t\ttlog(TLOG_ERROR, \"init log failed.\");\n\t\tgoto errout;\n\t}\n\n\t_smartdns_get_version(str_ver, sizeof(str_ver));\n\n\ttlog(TLOG_NOTICE, \"smartdns starting...(Copyright (C) Nick Peng <pymumu@gmail.com>, build: %s)\", str_ver);\n\n\tif (dns_timer_init() != 0) {\n\t\ttlog(TLOG_ERROR, \"init timer failed.\");\n\t\tgoto errout;\n\t}\n\n\tif (_smartdns_init_ssl() != 0) {\n\t\ttlog(TLOG_ERROR, \"init ssl failed.\");\n\t\tgoto errout;\n\t}\n\n\tif (_smartdns_init_load_from_resolv() != 0) {\n\t\ttlog(TLOG_ERROR, \"no dns server found, exit...\");\n\t\tgoto errout;\n\t}\n\n\tret = fast_ping_init();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"start ping failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tret = proxy_init();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"start proxy failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tret = _proxy_add_servers();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"add proxy servers failed.\");\n\t}\n\n\tret = dns_stats_init();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"start dns stats failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tret = dns_server_init();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"start dns server failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tret = dns_client_init();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"start dns client failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tret = _smartdns_add_servers();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"add servers failed.\");\n\t\tgoto errout;\n\t}\n\n\tret = _smartdns_plugin_init();\n\tif (ret != 0) {\n\t\ttlog(TLOG_ERROR, \"init plugin failed.\");\n\t\tgoto errout;\n\t}\n\n\treturn 0;\nerrout:\n\n\treturn -1;\n}\n\nstatic int _smartdns_run(void)\n{\n\treturn dns_server_run();\n}\n\nstatic void _smartdns_exit(void)\n{\n\t_smartdns_plugin_exit();\n\tproxy_exit();\n\tfast_ping_exit();\n\tdns_server_exit();\n\tdns_client_exit();\n\tdns_stats_exit();\n\t_smartdns_destroy_ssl();\n\tdns_timer_destroy();\n\ttlog_exit();\n\tdns_server_load_exit();\n}\n\nstatic void _sig_exit(int signo)\n{\n\ttlog(TLOG_INFO, \"stop smartdns by signal %d\", signo);\n\tdns_server_stop();\n}\n\nstatic void _sig_error_exit(int signo, siginfo_t *siginfo, void *ct)\n{\n\tunsigned long PC = 0;\n\tucontext_t *context = ct;\n\tconst char *arch = NULL;\n\tconst char *build_info = \"\";\n#ifdef SMARTDNS_VERION\n\tbuild_info = SMARTDNS_VERION;\n#else\n\tbuild_info = __DATE__ \" \" __TIME__;\n#endif\n#if defined(__i386__)\n\tint *pgregs = (int *)(&(context->uc_mcontext.gregs));\n\tPC = pgregs[REG_EIP];\n\tarch = \"i386\";\n#elif defined(__x86_64__)\n\tint *pgregs = (int *)(&(context->uc_mcontext.gregs));\n\tPC = pgregs[REG_RIP];\n\tarch = \"x86_64\";\n#elif defined(__arm__)\n\tPC = context->uc_mcontext.arm_pc;\n\tarch = \"arm\";\n#elif defined(__aarch64__)\n\tPC = context->uc_mcontext.pc;\n\tarch = \"arm64\";\n#elif defined(__mips__)\n\tPC = context->uc_mcontext.pc;\n\tarch = \"mips\";\n#endif\n\ttlog(TLOG_FATAL,\n\t\t \"process exit with signal %d, code = %d, errno = %d, pid = %d, self = %d, pc = %#lx, addr = %#lx, build(\"\n\t\t \"%s %s)\\n\",\n\t\t signo, siginfo->si_code, siginfo->si_errno, siginfo->si_pid, getpid(), PC, (unsigned long)siginfo->si_addr,\n\t\t build_info, arch);\n\tprint_stack();\n\tsleep(1);\n\t_exit(SMARTDNS_CRASH_CODE);\n}\n\nstatic int sig_list[] = {SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE};\n\nstatic int sig_num = sizeof(sig_list) / sizeof(int);\n\nstatic void _reg_signal(void)\n{\n\tstruct sigaction act;\n\tstruct sigaction old;\n\tint i = 0;\n\tact.sa_sigaction = _sig_error_exit;\n\tsigemptyset(&act.sa_mask);\n\tact.sa_flags = SA_RESTART | SA_SIGINFO;\n\n\tfor (i = 0; i < sig_num; i++) {\n\t\tsigaction(sig_list[i], &act, &old);\n\t}\n}\n\nstatic int _smartdns_create_logdir(void)\n{\n\tint ret = create_dir_with_perm(_smartdns_log_path());\n\tif (ret == -2) {\n\t\ttlog_set_maxlog_count(0);\n\t} else if (ret != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _smartdns_create_cache_dir(void)\n{\n\tint ret = create_dir_with_perm(dns_conf_get_cache_dir());\n\tif (ret == -2) {\n\t\tif (dns_conf.cache_file[0] == '\\0') {\n\t\t\tsafe_strncpy(dns_conf.cache_file, SMARTDNS_TMP_CACHE_FILE, sizeof(dns_conf.cache_file));\n\t\t}\n\t} else if (ret != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _smartdns_create_datadir(void)\n{\n\tuid_t uid = 0;\n\tgid_t gid = 0;\n\tstruct stat sb;\n\tchar data_dir[PATH_MAX] = {0};\n\tint unused __attribute__((unused)) = 0;\n\n\tsafe_strncpy(data_dir, dns_conf_get_data_dir(), PATH_MAX);\n\n\tif (get_uid_gid(&uid, &gid) != 0) {\n\t\treturn -1;\n\t}\n\n\tmkdir(data_dir, 0750);\n\tif (stat(data_dir, &sb) != 0) {\n\t\ttlog(TLOG_DEBUG, \"create dir %s failed, %s\", data_dir, strerror(errno));\n\t\treturn -1;\n\t}\n\n\tif (sb.st_uid == uid && sb.st_gid == gid && (sb.st_mode & 0700) == 0700) {\n\t\treturn 0;\n\t}\n\n\tif (chown(data_dir, uid, gid) != 0) {\n\t\tif (dns_conf.cache_file[0] == '\\0') {\n\t\t\tsafe_strncpy(dns_conf.cache_file, SMARTDNS_DATA_DIR, sizeof(dns_conf.cache_file));\n\t\t}\n\t}\n\n\tunused = chmod(data_dir, 0750);\n\tunused = chown(dns_conf_get_data_dir(), uid, gid);\n\treturn 0;\n}\n\nstatic int _set_rlimit(void)\n{\n\tstruct rlimit value;\n\tvalue.rlim_cur = 40;\n\tvalue.rlim_max = 40;\n\tsetrlimit(RLIMIT_NICE, &value);\n\n\tvalue.rlim_cur = 1024 * 10;\n\tvalue.rlim_max = 1024 * 10;\n\tsetrlimit(RLIMIT_NOFILE, &value);\n\treturn 0;\n}\n\nstatic int _smartdns_init_pre(void)\n{\n\tint ret = -1;\n\t_smartdns_create_logdir();\n\t_smartdns_create_cache_dir();\n\tret = _smartdns_create_datadir();\n\tif (ret != 0) {\n\t\ttlog(TLOG_DEBUG, \"create data dir failed.\");\n\t}\n\n\t_set_rlimit();\n\n\tif (_smartdns_create_cert() != 0) {\n\t\ttlog(TLOG_ERROR, \"create cert failed.\");\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _smartdns_child_pid = 0;\nstatic int _smartdns_child_restart = 0;\n\nstatic void _smartdns_run_monitor_sig(int sig)\n{\n\tif (_smartdns_child_pid > 0) {\n\t\tif (sig == SIGHUP) {\n\t\t\t_smartdns_child_restart = 1;\n\t\t\tkill(_smartdns_child_pid, SIGTERM);\n\t\t\treturn;\n\t\t}\n\t\tkill(_smartdns_child_pid, SIGTERM);\n\t}\n\twaitpid(_smartdns_child_pid, NULL, 0);\n\n\t_exit(0);\n}\n\nstatic smartdns_run_monitor_ret _smartdns_run_monitor(int restart_when_crash, int is_run_as_daemon)\n{\n\tpid_t pid;\n\tint status;\n\n\tif (restart_when_crash == 0) {\n\t\treturn SMARTDNS_RUN_MONITOR_OK;\n\t}\n\n\tif (is_run_as_daemon) {\n\t\tswitch (daemon_run(NULL)) {\n\t\tcase DAEMON_RET_CHILD_OK:\n\t\t\tbreak;\n\t\tcase DAEMON_RET_PARENT_OK:\n\t\t\treturn SMARTDNS_RUN_MONITOR_EXIT;\n\t\tdefault:\n\t\t\treturn SMARTDNS_RUN_MONITOR_ERROR;\n\t\t}\n\t}\n\n\tdaemon_kickoff(0, 1);\n\nrestart:\n\tpid = fork();\n\tif (pid < 0) {\n\t\tfprintf(stderr, \"fork failed, %s\\n\", strerror(errno));\n\t\treturn SMARTDNS_RUN_MONITOR_ERROR;\n\t} else if (pid == 0) {\n\t\treturn SMARTDNS_RUN_MONITOR_OK;\n\t}\n\n\t_smartdns_child_pid = pid;\n\n\tsignal(SIGTERM, _smartdns_run_monitor_sig);\n\tsignal(SIGHUP, _smartdns_run_monitor_sig);\n\twhile (true) {\n\t\tpid = waitpid(-1, &status, 0);\n\t\tif (pid == _smartdns_child_pid) {\n\t\t\tint need_restart = 0;\n\t\t\tchar signalmsg[64] = {0};\n\n\t\t\tif (_smartdns_child_restart == 1) {\n\t\t\t\t_smartdns_child_restart = 0;\n\t\t\t\tgoto restart;\n\t\t\t}\n\n\t\t\tif (WEXITSTATUS(status) == SMARTDNS_CRASH_CODE) {\n\t\t\t\tneed_restart = 1;\n\t\t\t} else if (WEXITSTATUS(status) == 255) {\n\t\t\t\tfprintf(stderr, \"run daemon failed, please check log.\\n\");\n\t\t\t} else if (WIFSIGNALED(status)) {\n\t\t\t\tswitch (WTERMSIG(status)) {\n\t\t\t\tcase SIGSEGV:\n\t\t\t\tcase SIGABRT:\n\t\t\t\tcase SIGBUS:\n\t\t\t\tcase SIGILL:\n\t\t\t\tcase SIGFPE:\n\t\t\t\t\tsnprintf(signalmsg, sizeof(signalmsg), \" with signal %d\", WTERMSIG(status));\n\t\t\t\t\tneed_restart = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (need_restart == 1) {\n\t\t\t\tprintf(\"smartdns crashed%s, restart...\\n\", signalmsg);\n\t\t\t\tgoto restart;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tif (pid < 0) {\n\t\t\tsleep(1);\n\t\t}\n\t}\n\n\treturn SMARTDNS_RUN_MONITOR_ERROR;\n}\n\nstatic void _smartdns_print_error_tip(void)\n{\n\tchar buff[4096];\n\tchar *log_path = realpath(_smartdns_log_path(), buff);\n\n\tif (log_path != NULL && access(log_path, F_OK) == 0) {\n\t\tfprintf(stderr, \"run daemon failed, please check log at %s\\n\", log_path);\n\t}\n}\n\nvoid smartdns_exit(int status)\n{\n\tdns_server_stop();\n\texit_status = status;\n}\n\nvoid smartdns_restart(void)\n{\n\texit_restart = 1;\n\tdns_server_stop();\n}\n\nstatic const char *smartdns_exec_dir(void)\n{\n\tstatic char start_dir[PATH_MAX] = {0};\n\tif (start_dir[0] == 0) {\n\t\tif (getcwd(start_dir, sizeof(start_dir)) == NULL) {\n\t\t\tsnprintf(start_dir, sizeof(start_dir), \".\");\n\t\t}\n\t}\n\treturn start_dir;\n}\n\nstatic int smartdns_enter_monitor_mode(int argc, char *argv[], int no_deamon)\n{\n\tchar exec_path[PATH_MAX] = {0};\n\n\tsetenv(\"SMARTDNS_RESTART_ON_CRASH\", \"1\", 1);\n\tif (no_deamon == 1) {\n\t\tsetenv(\"SMARTDNS_NO_DAEMON\", \"1\", 1);\n\t}\n\n\tchdir(smartdns_exec_dir());\n\tif (readlink(\"/proc/self/exe\", exec_path, sizeof(exec_path) - 1) > 0) {\n\t\texecv(exec_path, argv);\n\t} else {\n\t\tsafe_strncpy(exec_path, argv[0], sizeof(exec_path));\n\t\texecvp(exec_path, argv);\n\t}\n\n\ttlog(TLOG_ERROR, \"execv failed, %s, %s\", exec_path, strerror(errno));\n\treturn -1;\n}\n\nstatic int smartdns_init_workdir(void)\n{\n\tsmartdns_exec_dir();\n\tconst char *smartdns_workdir = getenv(\"SMARTDNS_WORKDIR\");\n\n\tif (smartdns_workdir != NULL) {\n\t\tif (chdir(smartdns_workdir) != 0) {\n\t\t\tfprintf(stderr, \"chdir to %s failed: %s\\n\", smartdns_workdir, strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n#ifdef TEST\n\nstatic smartdns_post_func _smartdns_post = NULL;\nstatic void *_smartdns_post_arg = NULL;\n\nint smartdns_reg_post_func(smartdns_post_func func, void *arg)\n{\n\t_smartdns_post = func;\n\t_smartdns_post_arg = arg;\n\treturn 0;\n}\n\n#define smartdns_test_notify(retval) smartdns_test_notify_func(fd_notify, retval)\nstatic void smartdns_test_notify_func(int fd_notify, uint64_t retval)\n{\n\tint unused __attribute__((unused));\n\t/* notify parent kickoff */\n\tif (fd_notify > 0) {\n\t\tunused = write(fd_notify, &retval, sizeof(retval));\n\t}\n\n\tif (_smartdns_post != NULL) {\n\t\t_smartdns_post(_smartdns_post_arg);\n\t}\n}\n\n#define smartdns_close_allfds()                                                                                        \\\n\tif (no_close_allfds == 0) {                                                                                        \\\n\t\tclose_all_fd(fd_notify);                                                                                       \\\n\t}\n\nint smartdns_test_main(int argc, char *argv[], int fd_notify, int no_close_allfds)\n#else\n#define smartdns_test_notify(retval)\n#define smartdns_close_allfds() close_all_fd(-1)\nint smartdns_main(int argc, char *argv[])\n#endif\n{\n\tint ret = 0;\n\tint is_run_as_daemon = 1;\n\tint opt = 0;\n\tchar config_file[MAX_LINE_LEN];\n\tchar pid_file[MAX_LINE_LEN];\n\tint is_pid_file_set = 0;\n\tint signal_ignore = 0;\n\tint restart_when_crash = getpid() == 1 ? 1 : 0;\n\tsigset_t empty_sigblock;\n\tstruct stat sb;\n\n\tstatic struct option long_options[] = {{\"cache-print\", required_argument, NULL, 256},\n\t\t\t\t\t\t\t\t\t\t   {\"is-quic-supported\", no_argument, NULL, 257},\n\t\t\t\t\t\t\t\t\t\t   {\"help\", no_argument, NULL, 'h'},\n\t\t\t\t\t\t\t\t\t\t   {NULL, 0, NULL, 0}};\n\n\tif (smartdns_init_workdir() != 0) {\n\t\treturn 1;\n\t}\n\n\tsafe_strncpy(config_file, SMARTDNS_CONF_FILE, MAX_LINE_LEN);\n\n\tif (stat(\"/run\", &sb) == 0 && S_ISDIR(sb.st_mode)) {\n\t\tsafe_strncpy(pid_file, SMARTDNS_PID_FILE, MAX_LINE_LEN);\n\t} else {\n\t\tsafe_strncpy(pid_file, SMARTDNS_LEGACY_PID_FILE, MAX_LINE_LEN);\n\t}\n\n\t/* patch for Asus router:  unblock all signal*/\n\tsigemptyset(&empty_sigblock);\n\tsigprocmask(SIG_SETMASK, &empty_sigblock, NULL);\n\tsmartdns_close_allfds();\n\n\twhile ((opt = getopt_long(argc, argv, \"fhc:p:SvxN:R\", long_options, NULL)) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'f':\n\t\t\tis_run_as_daemon = 0;\n\t\t\tbreak;\n\t\tcase 'c':\n\t\t\tif (full_path(config_file, sizeof(config_file), optarg) != 0) {\n\t\t\t\tsnprintf(config_file, sizeof(config_file), \"%s\", optarg);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'p':\n\t\t\tif (strncmp(optarg, \"-\", 2) == 0 || full_path(pid_file, sizeof(pid_file), optarg) != 0) {\n\t\t\t\tsnprintf(pid_file, sizeof(pid_file), \"%s\", optarg);\n\t\t\t\tis_pid_file_set = 1;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'R':\n\t\t\trestart_when_crash = 1;\n\t\t\tbreak;\n\t\tcase 'S':\n\t\t\tsignal_ignore = 1;\n\t\t\tbreak;\n\t\tcase 'x':\n\t\t\tverbose_screen = 1;\n\t\t\tbreak;\n\t\tcase 'v':\n\t\t\t_show_version();\n\t\t\treturn 0;\n\t\t\tbreak;\n#ifdef DEBUG\n\t\tcase 'N':\n\t\t\treturn dns_packet_debug(optarg);\n#endif\n\t\tcase 'h':\n\t\t\t_help();\n\t\t\treturn 0;\n\t\tcase 256:\n\t\t\ttlog_set_early_printf(1, 1, 1);\n\t\t\treturn dns_cache_print(optarg);\n\t\t\tbreak;\n\t\tcase 257:\n\t\t\tif (dns_is_quic_supported() == 0) {\n\t\t\t\tfprintf(stdout, \"quic is not supported.\\n\");\n\t\t\t\treturn 1;\n\t\t\t} else {\n\t\t\t\tfprintf(stdout, \"quic is supported.\\n\");\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn 0;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"unknown option, please run %s -h for help.\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (getenv(\"SMARTDNS_RESTART_ON_CRASH\") != NULL) {\n\t\trestart_when_crash = 1;\n\t\tunsetenv(\"SMARTDNS_RESTART_ON_CRASH\");\n\t}\n\n\tif (getenv(\"SMARTDNS_NO_DAEMON\") != NULL) {\n\t\tis_run_as_daemon = 0;\n\t\tunsetenv(\"SMARTDNS_NO_DAEMON\");\n\t}\n\n\t/* started by systemd, do not restart when crash */\n\tif (getenv(\"INVOCATION_ID\") != NULL) {\n\t\trestart_when_crash = 0;\n\t}\n\n\tsmartdns_run_monitor_ret init_ret = _smartdns_run_monitor(restart_when_crash, is_run_as_daemon);\n\tif (init_ret != SMARTDNS_RUN_MONITOR_OK) {\n\t\tif (init_ret == SMARTDNS_RUN_MONITOR_EXIT) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn 1;\n\t}\n\n\tsrand(time(NULL));\n\n\ttlog_set_early_printf(1, 1, 1);\n\ttlog_reg_early_printf_output_callback(_smartdns_tlog_output_syslog_callback, 1, NULL);\n\n\tret = dns_server_load_conf(config_file);\n\tif (ret != 0) {\n\t\tfprintf(stderr, \"load config failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\t/* started by systemd, do not restart when crash */\n\tif (getenv(\"INVOCATION_ID\") != NULL) {\n\t\tdns_conf.dns_restart_on_crash = 0;\n\t}\n\n\tif (dns_conf.dns_restart_on_crash && restart_when_crash == 0) {\n\t\treturn smartdns_enter_monitor_mode(argc, argv, dns_conf.dns_no_daemon || !is_run_as_daemon);\n\t}\n\n\tif (dns_conf.dns_no_daemon || restart_when_crash) {\n\t\tis_run_as_daemon = 0;\n\t}\n\n\tif (is_run_as_daemon) {\n\t\tint child_status = -1;\n\t\tswitch (daemon_run(&child_status)) {\n\t\tcase DAEMON_RET_CHILD_OK:\n\t\t\tbreak;\n\t\tcase DAEMON_RET_PARENT_OK: {\n\t\t\tif (child_status != 0 && child_status != -3) {\n\t\t\t\t_smartdns_print_error_tip();\n\t\t\t}\n\n\t\t\treturn child_status;\n\t\t} break;\n\t\tcase DAEMON_RET_ERR:\n\t\tdefault:\n\t\t\tfprintf(stderr, \"run daemon failed.\\n\");\n\t\t\tgoto errout;\n\t\t}\n\t}\n\n\tif (signal_ignore == 0) {\n\t\t_reg_signal();\n\t}\n\n\tif (is_pid_file_set == 0) {\n\t\tchar pid_file_path[MAX_LINE_LEN];\n\t\tsafe_strncpy(pid_file_path, pid_file, MAX_LINE_LEN);\n\t\tdir_name(pid_file_path);\n\n\t\tif (access(pid_file_path, W_OK) != 0) {\n\t\t\tdns_conf.dns_no_pidfile = 1;\n\t\t}\n\t}\n\n\tif (strncmp(pid_file, \"-\", 2) != 0 && dns_conf.dns_no_pidfile == 0 && create_pid_file(pid_file) != 0) {\n\t\tret = -3;\n\t\tgoto errout;\n\t}\n\n\tsignal(SIGPIPE, SIG_IGN);\n\tsignal(SIGINT, _sig_exit);\n\tsignal(SIGTERM, _sig_exit);\n\n\tret = _smartdns_init_pre();\n\tif (ret != 0) {\n\t\tfprintf(stderr, \"smartdns init failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tdrop_root_privilege();\n\n\tret = _smartdns_init();\n\tif (ret != 0) {\n\t\tusleep(100000);\n\t\tgoto errout;\n\t}\n\n\tif (is_run_as_daemon) {\n\t\tret = daemon_kickoff(0, dns_conf.log_console | dns_conf.audit_console | verbose_screen);\n\t\tif (ret != 0) {\n\t\t\tgoto errout;\n\t\t}\n\t} else if (dns_conf.log_console == 0 && dns_conf.audit_console == 0 && verbose_screen == 0) {\n\t\tdaemon_close_stdfds();\n\t}\n\n\tsmartdns_test_notify(1);\n\tret = _smartdns_run();\n\tif (ret == 0 && exit_status != 0) {\n\t\tret = exit_status;\n\t}\n\n\tif (exit_restart == 0) {\n\t\ttlog(TLOG_INFO, \"smartdns exit...\");\n\t\t_smartdns_exit();\n\t} else {\n\t\ttlog(TLOG_INFO, \"smartdns restart...\");\n\t\t_smartdns_exit();\n\t\tif (restart_when_crash == 0) {\n\t\t\texecve(argv[0], argv, environ);\n\t\t}\n\t}\n\treturn ret;\nerrout:\n\tif (is_run_as_daemon) {\n\t\tdaemon_kickoff(ret, dns_conf.log_console | dns_conf.audit_console | verbose_screen);\n\t} else if (dns_conf.log_console == 0 && dns_conf.audit_console == 0 && verbose_screen == 0) {\n\t\t_smartdns_print_error_tip();\n\t}\n\tsmartdns_test_notify(2);\n\t_smartdns_exit();\n\treturn ret;\n}\n\nint smartdns_server_run(const char *config_file)\n{\n\tint ret = -1;\n\n\tret = dns_server_load_conf(config_file);\n\tif (ret != 0) {\n\t\tfprintf(stderr, \"load config failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tret = _smartdns_init_pre();\n\tif (ret != 0) {\n\t\tfprintf(stderr, \"init failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tret = _smartdns_init();\n\tif (ret != 0) {\n\t\tfprintf(stderr, \"init failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\tret = _smartdns_run();\n\tif (ret != 0) {\n\t\tfprintf(stderr, \"run failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\t_smartdns_exit();\n\ttlog(TLOG_INFO, \"smartdns exit...\");\n\treturn ret;\nerrout:\n\t_smartdns_exit();\n\treturn -1;\n}\n\nint smartdns_server_stop(void)\n{\n\tdns_server_stop();\n\treturn 0;\n}"
  },
  {
    "path": "src/timer.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/timer.h\"\n#include \"smartdns/lib/timer_wheel.h\"\n\nstatic struct tw_base *dns_timer_base = NULL;\n\nint dns_timer_init(void)\n{\n\tstruct tw_base *tw = tw_init_timers();\n\tif (tw == NULL) {\n\t\treturn -1;\n\t}\n\n\tdns_timer_base = tw;\n\n\treturn 0;\n}\n\nvoid dns_timer_destroy(void)\n{\n\tif (dns_timer_base != NULL) {\n\t\ttw_cleanup_timers(dns_timer_base);\n\t\tdns_timer_base = NULL;\n\t}\n}\n\nvoid dns_timer_add(struct tw_timer_list *timer)\n{\n\tif (dns_timer_base == NULL) {\n\t\treturn;\n\t}\n\n\ttw_add_timer(dns_timer_base, timer);\n}\n\nint dns_timer_del(struct tw_timer_list *timer)\n{\n\tif (dns_timer_base == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn tw_del_timer(dns_timer_base, timer);\n}\n\nint dns_timer_mod(struct tw_timer_list *timer, unsigned long expires)\n{\n\tif (dns_timer_base == NULL) {\n\t\treturn 0;\n\t}\n\n\treturn tw_mod_timer_pending(dns_timer_base, timer, expires);\n}\n"
  },
  {
    "path": "src/tlog.c",
    "content": "/*\n * tinylog\n * Copyright (C) 2018-2025 Nick Peng <pymumu@gmail.com>\n * https://github.com/pymumu/tinylog\n */\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n#include \"smartdns/tlog.h\"\n#include <dirent.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <libgen.h>\n#include <limits.h>\n#include <pthread.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/resource.h>\n#include <sys/stat.h>\n#include <sys/syscall.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n\n#ifndef likely\n#define likely(x) __builtin_expect(!!(x), 1)\n#endif\n\n#ifndef unlikely\n#define unlikely(x) __builtin_expect(!!(x), 0)\n#endif\n\n#define TLOG_BUFF_SIZE (1024 * 128)\n#define TLOG_TMP_LEN 128\n#define TLOG_LOG_SIZE (1024 * 1024 * 50)\n#define TLOG_LOG_COUNT 32\n#define TLOG_LOG_NAME_LEN 256\n#define TLOG_BUFF_LEN (PATH_MAX + TLOG_LOG_NAME_LEN * 3)\n#define TLOG_SUFFIX_GZ \".gz\"\n#define TLOG_SUFFIX_LOG \"\"\n#define TLOG_MAX_LINE_SIZE_SET (1024 * 8)\n#define TLOG_MIN_LINE_SIZE_SET (128)\n\n#define TLOG_SEGMENT_MAGIC 0xFF446154\n\nstruct linux_dirent64 {\n    unsigned long long d_ino;\n    long long d_off;\n    unsigned short d_reclen;\n    unsigned char d_type;\n    char d_name[256];\n};\n\nstruct tlog_log {\n    char *buff;\n    int buffsize;\n    int start;\n    int end;\n    int ext_end;\n\n    int fd;\n    int fd_lock;\n\n    off_t filesize;\n    char logdir[PATH_MAX];\n    char logname[TLOG_LOG_NAME_LEN];\n    char suffix[TLOG_LOG_NAME_LEN];\n    char pending_logfile[PATH_MAX];\n    char logfile[PATH_MAX * 2];\n    int rename_pending;\n    int fail;\n    int logsize;\n    int logcount;\n    int block;\n    int dropped;\n    int nocompress;\n    int zip_pid;\n    int multi_log;\n    int logscreen;\n    int logscreen_color;\n    int segment_log;\n    int max_line_size;\n    int print_errmsg;\n\n    tlog_output_func output_func;\n    void *private_data;\n    int set_custom_output_func;\n\n    time_t last_try;\n    time_t last_waitpid;\n    mode_t file_perm;\n    mode_t archive_perm;\n    int mode_changed;\n\n    int waiters;\n    int is_exit;\n    struct tlog_log *next;\n    pthread_mutex_t lock;\n    pthread_cond_t client_cond;\n};\n\nstruct tlog {\n    struct tlog_log *root;\n    struct tlog_log *log;\n    struct tlog_log *notify_log;\n    int run;\n    pthread_t tid;\n    pthread_mutex_t lock;\n    pthread_cond_t cond;\n    tlog_log_output_func output_func;\n    struct tlog_log *wait_on_log;\n    int is_wait;\n    int output_no_prefix;\n    char gzip_cmd[PATH_MAX];\n\n    tlog_format_func root_format;\n\n    tlog_early_print_func tlog_early_print;\n    tlog_log_output_func early_print_output;\n    int early_print_disable;\n    int early_print_with_screen;\n    int early_print_no_prefix;\n    int early_print_color;\n    void *early_print_userptr;\n};\n\nstruct tlog_segment_log_head {\n    struct tlog_loginfo info;\n    unsigned short len;\n    char data[0];\n} __attribute__((packed));\n\nstruct tlog_segment_head {\n    unsigned int magic;\n    unsigned short len;\n    char data[0];\n} __attribute__((packed));\n\nstruct oldest_log {\n    char name[TLOG_LOG_NAME_LEN];\n    time_t mtime;\n    struct tlog_log *log;\n};\n\nstruct count_log {\n    int lognum;\n    struct tlog_log *log;\n};\n\nstruct tlog_info_inter {\n    struct tlog_loginfo info;\n    void *userptr;\n};\n\ntypedef int (*list_callback)(const char *name, struct dirent *entry, void *user);\ntypedef int (*vprint_callback)(char *buff, int maxlen, void *userptr, const char *format, va_list ap);\n\nstatic struct tlog tlog;\nstatic tlog_level tlog_set_level = TLOG_INFO;\nunsigned int tlog_localtime_lock;\n\nstatic const char *tlog_level_str[] = {\n    \"DEBUG\",\n    \"INFO\",\n    \"NOTICE\",\n    \"WARN\",\n    \"ERROR\",\n    \"FATAL\",\n};\n\nstatic inline void _tlog_spin_lock(unsigned int *lock)\n{\n    while (1) {\n        int i;\n        for (i = 0; i < 10000; i++) {\n            if (__sync_bool_compare_and_swap(lock, 0, 1)) {\n                return;\n            }\n        }\n        sched_yield();\n    }\n}\n\nstatic inline void _tlog_spin_unlock(unsigned int *lock)\n{\n    __sync_bool_compare_and_swap(lock, 1, 0);\n}\n\nstatic int _tlog_mkdir(const char *path)\n{\n    char path_c[PATH_MAX + 1];\n    char *path_end;\n    char str;\n    int len;\n    if (access(path, F_OK) == 0) {\n        return 0;\n    }\n\n    while (*path == ' ') {\n        path++;\n    }\n\n    strncpy(path_c, path, sizeof(path_c) - 1);\n    path_c[sizeof(path_c) - 1] = '\\0';\n    len = strnlen(path_c, sizeof(path_c) - 1);\n    path_c[len] = '/';\n    path_c[len + 1] = '\\0';\n    path_end = path_c;\n\n    /* create directory recursively */\n    while (*path_end != 0) {\n        if (*path_end != '/') {\n            path_end++;\n            continue;\n        }\n\n        if (path_end == path_c) {\n            path_end++;\n            continue;\n        }\n\n        str = *path_end;\n        *path_end = '\\0';\n        if (access(path_c, F_OK) == 0) {\n            *path_end = str;\n            path_end++;\n            continue;\n        }\n\n        if (mkdir(path_c, 0750) != 0) {\n            return -1;\n        }\n\n        *path_end = str;\n        path_end++;\n    }\n\n    return 0;\n}\n\nstatic struct tm *_tlog_localtime(time_t *timep, struct tm *tm)\n{\n    static time_t last_time;\n    static struct tm last_tm;\n\n    /* localtime_r has a global timezone lock, it's about 8 times slower than gmtime\n     * this code is used to speed up localtime_r call.\n     */\n    _tlog_spin_lock(&tlog_localtime_lock);\n    if (*timep == last_time) {\n        *tm = last_tm;\n    } else {\n        _tlog_spin_unlock(&tlog_localtime_lock);\n        tm = localtime_r(timep, tm);\n        _tlog_spin_lock(&tlog_localtime_lock);\n        if (tm) {\n            last_time = *timep;\n            last_tm = *tm;\n        }\n    }\n    _tlog_spin_unlock(&tlog_localtime_lock);\n\n    return tm;\n}\n\nstatic int _tlog_getmtime(struct tlog_time *log_mtime, const char *file)\n{\n    struct tm tm;\n    struct stat sb;\n\n    if (stat(file, &sb) != 0) {\n        return -1;\n    }\n\n    if (_tlog_localtime(&sb.st_mtime, &tm) == NULL) {\n        return -1;\n    }\n\n    log_mtime->year = tm.tm_year + 1900;\n    log_mtime->mon = tm.tm_mon + 1;\n    log_mtime->mday = tm.tm_mday;\n    log_mtime->hour = tm.tm_hour;\n    log_mtime->min = tm.tm_min;\n    log_mtime->sec = tm.tm_sec;\n    log_mtime->usec = 0;\n\n    return 0;\n}\n\nstatic int _tlog_gettime(struct tlog_time *cur_time)\n{\n    struct tm tm;\n    struct timeval tmval;\n\n    if (gettimeofday(&tmval, NULL) != 0) {\n        return -1;\n    }\n\n    if (_tlog_localtime(&tmval.tv_sec, &tm) == NULL) {\n        return -1;\n    }\n\n    cur_time->year = tm.tm_year + 1900;\n    cur_time->mon = tm.tm_mon + 1;\n    cur_time->mday = tm.tm_mday;\n    cur_time->hour = tm.tm_hour;\n    cur_time->min = tm.tm_min;\n    cur_time->sec = tm.tm_sec;\n    cur_time->usec = tmval.tv_usec;\n\n    return 0;\n}\n\nvoid tlog_set_maxline_size(struct tlog_log *log, int size)\n{\n    if (log == NULL) {\n        return;\n    }\n\n    if (size < TLOG_MIN_LINE_SIZE_SET) {\n        size = TLOG_MIN_LINE_SIZE_SET;\n    } else if (size > TLOG_MAX_LINE_SIZE_SET) {\n        size = TLOG_MAX_LINE_SIZE_SET;\n    }\n\n    log->max_line_size = size;\n}\n\nvoid tlog_logcount(struct tlog_log *log, int count)\n{\n    if (log == NULL) {\n        return;\n    }\n\n    if (count < 0) {\n        count = 0;\n    }\n\n    log->logcount = count;\n}\n\nvoid tlog_set_permission(struct tlog_log *log, mode_t file, mode_t archive)\n{\n    log->file_perm = file;\n    log->archive_perm = archive;\n    log->mode_changed = 1;\n}\n\nint tlog_localtime(struct tlog_time *tm)\n{\n    return _tlog_gettime(tm);\n}\n\ntlog_log *tlog_get_root(void)\n{\n    return tlog.root;\n}\n\nvoid tlog_set_private(tlog_log *log, void *private_data)\n{\n    if (log == NULL) {\n        return;\n    }\n\n    log->private_data = private_data;\n}\n\nvoid *tlog_get_private(tlog_log *log)\n{\n    if (log == NULL) {\n        return NULL;\n    }\n\n    return log->private_data;\n}\n\nstatic int _tlog_root_default_format(char *buff, int maxlen, struct tlog_loginfo *info, void *userptr, const char *format, va_list ap)\n{\n    int len = 0;\n    int total_len = 0;\n    struct tlog_time *tm = &info->time;\n    void *unused __attribute__((unused));\n\n    unused = userptr;\n\n    if (tlog.output_no_prefix == 0) {\n        if (tlog.root->multi_log) {\n            /* format prefix */\n            len = snprintf(buff, maxlen, \"[%.4d-%.2d-%.2d %.2d:%.2d:%.2d,%.3d][%5d][%4s][%17s:%-4d] \",\n                tm->year, tm->mon, tm->mday, tm->hour, tm->min, tm->sec, tm->usec / 1000, getpid(),\n                tlog_get_level_string(info->level), info->file, info->line);\n        } else {\n            /* format prefix */\n            len = snprintf(buff, maxlen, \"[%.4d-%.2d-%.2d %.2d:%.2d:%.2d,%.3d][%5s][%17s:%-4d] \",\n                tm->year, tm->mon, tm->mday, tm->hour, tm->min, tm->sec, tm->usec / 1000,\n                tlog_get_level_string(info->level), info->file, info->line);\n        }\n    }\n\n    if (len < 0 || len >= maxlen) {\n        return -1;\n    }\n    buff += len;\n    total_len += len;\n    maxlen -= len;\n\n    /* format log message */\n    len = vsnprintf(buff, maxlen, format, ap);\n    if (len < 0 || len == maxlen) {\n        return -1;\n    }\n    buff += len;\n    total_len += len;\n\n    /* return total length */\n    return total_len;\n}\n\nstatic int _tlog_root_log_buffer(char *buff, int maxlen, void *userptr, const char *format, va_list ap)\n{\n    int len = 0;\n    int log_len = 0;\n    struct tlog_info_inter *info_inter = (struct tlog_info_inter *)userptr;\n    struct tlog_segment_log_head *log_head = NULL;\n    int max_format_len = 0;\n\n    if (tlog.root_format == NULL) {\n        return -1;\n    }\n\n    if (tlog.root->segment_log) {\n        log_head = (struct tlog_segment_log_head *)buff;\n        len += sizeof(*log_head);\n        memcpy(&log_head->info, &info_inter->info, sizeof(log_head->info));\n    }\n\n    max_format_len = maxlen - len - 2;\n    buff[maxlen - 1] = 0;\n    log_len = tlog.root_format(buff + len, max_format_len, &info_inter->info, info_inter->userptr, format, ap);\n    if (log_len < 0) {\n        return -1;\n    } else if (log_len >= max_format_len) {\n        buff[len + max_format_len - 2] = '.';\n        buff[len + max_format_len - 3] = '.';\n        buff[len + max_format_len - 4] = '.';\n        log_len = max_format_len - 1;\n    }\n    len += log_len;\n\n    /* add new line character*/\n    if (*(buff + len - 1) != '\\n' && len + 1 < maxlen - 1) {\n        *(buff + len) = '\\n';\n        len++;\n        log_len++;\n    }\n\n    if (tlog.root->segment_log && log_head != NULL) {\n        if (len + 1 < maxlen - 1) {\n            *(buff + len) = '\\0';\n            len++;\n        }\n        log_head->len = log_len;\n    }\n\n    return len;\n}\n\nstatic int _tlog_print_buffer(char *buff, int maxlen, void *userptr, const char *format, va_list ap)\n{\n    int len;\n    int total_len = 0;\n    void *unused __attribute__((unused));\n\n    unused = userptr;\n\n    /* format log message */\n    len = vsnprintf(buff, maxlen, format, ap);\n    if (len < 0 || len == maxlen) {\n        return -1;\n    }\n    buff += len;\n    total_len += len;\n\n    /* return total length */\n    return total_len;\n}\n\nstatic int _tlog_need_drop(struct tlog_log *log)\n{\n    int maxlen = 0;\n    int ret = -1;\n    if (log->block) {\n        return -1;\n    }\n\n    pthread_mutex_lock(&tlog.lock);\n    if (log->end == log->start) {\n        if (log->ext_end == 0) {\n            /* if buffer is empty */\n            maxlen = log->buffsize - log->end;\n        }\n    } else if (log->end > log->start) {\n        maxlen = log->buffsize - log->end;\n    } else {\n        /* if reverse */\n        maxlen = log->start - log->end;\n    }\n\n    /* if free buffer length is less than min line length */\n    if (maxlen < log->max_line_size) {\n        log->dropped++;\n        ret = 0;\n    }\n    pthread_mutex_unlock(&tlog.lock);\n    return ret;\n}\n\nstatic int _tlog_write_screen(struct tlog_log *log, struct tlog_loginfo *info, const char *buff, int bufflen)\n{\n    if (bufflen <= 0) {\n        return 0;\n    }\n\n    if (log->logscreen == 0) {\n        return 0;\n    }\n\n    if (info == NULL) {\n        return write(STDOUT_FILENO, buff, bufflen);;\n    }\n\n    return tlog_stdout_with_color(info->level, buff, bufflen);\n}\n\nstatic int _tlog_write_output_func(struct tlog_log *log, char *buff, int bufflen)\n{\n    if (log->logscreen && log != tlog.root) {\n        _tlog_write_screen(log, NULL, buff, bufflen);\n    }\n\n    if (log->output_func == NULL) {\n        return -1;\n    }\n\n    return log->output_func(log, buff, bufflen);\n}\n\nstatic void _tlog_output_warning(void)\n{\n    static int printed = 0;\n    int unused __attribute__((unused));\n\n    if (printed) {\n        return;\n    }\n    printed = 1;\n    tlog_log *root = tlog.root;\n\n    const char warning_msg[] = \"\"\n    \"TLOG ERROR: \\n\"\n    \"  Do not call the tlog output function from within a registered tlog log output callback function.\\n\"\n    \"  Recursively calling the log output function will cause tlog to fail to output logs and deadlock.\\n\";\n\n    if (root->logcount > 0 && root->logsize > 0 && root->logfile[0] != 0) {\n        int fd = open(root->logfile, O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, root->file_perm);\n        if (fd >= 0) {\n            unused = write(fd, warning_msg, sizeof(warning_msg) - 1);\n            close(fd);\n        }\n    }\n\n    /* if open log file failed, print to stderr */\n    fprintf(stderr, \"\\033[31;1m%s\\033[0m\\n\", warning_msg);\n\n    return;\n}\n\nstatic int _tlog_vprintf(struct tlog_log *log, vprint_callback print_callback, void *userptr, const char *format, va_list ap)\n{\n    int len;\n    int maxlen = 0;\n    struct tlog_segment_head *segment_head = NULL;\n\n    if (log == NULL || format == NULL) {\n        return -1;\n    }\n\n    char buff[log->max_line_size];\n\n    if (log->buff == NULL) {\n        return -1;\n    }\n\n    if (unlikely(log->logcount <= 0 && log->logscreen == 0 && log->set_custom_output_func == 0)) {\n        return 0;\n    }\n\n    if (_tlog_need_drop(log) == 0) {\n        return -1;\n    }\n\n    len = print_callback(buff, sizeof(buff), userptr, format, ap);\n    if (len <= 0) {\n        return -1;\n    } else if (len >= log->max_line_size) {\n        len = log->max_line_size;\n        buff[len - 1] = '\\0';\n        buff[len - 2] = '\\n';\n        buff[len - 3] = '.';\n        buff[len - 4] = '.';\n        buff[len - 5] = '.';\n    }\n\n    /* \n     Output log from tlog_worker thread context? this may crash from upper-level function.\n     Try call printf output log. \n     */\n    if (tlog.tid == pthread_self()) {\n        _tlog_output_warning();\n        vprintf(format, ap);\n        printf(\"\\n\");\n        return -1;\n    }\n\n    pthread_mutex_lock(&tlog.lock);\n    do {\n        if (log->end == log->start) {\n            if (log->ext_end == 0) {\n                /* if buffer is empty */\n                maxlen = log->buffsize - log->end;\n            }\n        } else if (log->end > log->start) {\n            maxlen = log->buffsize - log->end;\n        } else {\n            /* if reverse */\n            maxlen = log->start - log->end;\n        }\n\n        /* if free buffer length is less than min line length */\n        if (maxlen < log->max_line_size) {\n            if (log->end != log->start) {\n                tlog.notify_log = log;\n                pthread_cond_signal(&tlog.cond);\n            }\n\n            /* if drop message, increase statistics and return */\n            if (log->block == 0) {\n                log->dropped++;\n                pthread_mutex_unlock(&tlog.lock);\n                return -1;\n            }\n\n            pthread_mutex_unlock(&tlog.lock);\n            pthread_mutex_lock(&log->lock);\n            log->waiters++;\n            /* block wait for free buffer */\n            int ret = pthread_cond_wait(&log->client_cond, &log->lock);\n            log->waiters--;\n            pthread_mutex_unlock(&log->lock);\n            if (ret < 0) {\n                return -1;\n            }\n\n            pthread_mutex_lock(&tlog.lock);\n        }\n    } while (maxlen < log->max_line_size);\n\n    if (log->segment_log) {\n        segment_head = (struct tlog_segment_head *)(log->buff + log->end);\n        memcpy(segment_head->data, buff, len);\n        log->end += len + sizeof(*segment_head) + 1;\n        segment_head->len = len + 1;\n        segment_head->data[len] = '\\0';\n        segment_head->magic = TLOG_SEGMENT_MAGIC;\n    } else {\n        /* write log to buffer */\n        memcpy(log->buff + log->end, buff, len);\n        log->end += len;\n    }\n\n    /* if remain buffer is not enough for a line, move end to start of buffer. */\n    if (log->end > log->buffsize - log->max_line_size) {\n        log->ext_end = log->end;\n        log->end = 0;\n    }\n    if (tlog.is_wait) {\n        tlog.notify_log = log;\n        pthread_cond_signal(&tlog.cond);\n    }\n    pthread_mutex_unlock(&tlog.lock);\n\n    return len;\n}\n\nint tlog_vprintf(struct tlog_log *log, const char *format, va_list ap)\n{\n    return _tlog_vprintf(log, _tlog_print_buffer, NULL, format, ap);\n}\n\nint tlog_printf(struct tlog_log *log, const char *format, ...)\n{\n    int len;\n    va_list ap;\n\n    va_start(ap, format);\n    len = tlog_vprintf(log, format, ap);\n    va_end(ap);\n\n    return len;\n}\n\nint tlog_stdout_with_color(tlog_level level, const char *buff, int bufflen)\n{\n    int unused __attribute__((unused));\n    const char *color = NULL;\n\n    switch (level) {\n    case TLOG_DEBUG:\n        color = \"\\033[0;94m\";\n        break;\n    case TLOG_NOTICE:\n        color = \"\\033[0;97m\";\n        break;\n    case TLOG_WARN:\n        color = \"\\033[0;33m\";\n        break;\n    case TLOG_ERROR:\n        color = \"\\033[0;31m\";\n        break;\n    case TLOG_FATAL:\n        color = \"\\033[31;1m\";\n        break;\n    default:\n        unused = write(STDOUT_FILENO, buff, bufflen);\n        return bufflen;\n    }\n\n    if (color != NULL) {\n        fprintf(stdout, \"%s%.*s\\033[0m\\n\", color, bufflen - 1, buff);\n    } else {\n        fprintf(stdout, \"%s\", buff);\n    }\n\n    return bufflen;    \n}\n\nstatic int _tlog_early_print(struct tlog_info_inter *info_inter, const char *format, va_list ap)\n{\n    char log_buf[TLOG_MAX_LINE_LEN];\n    size_t len = 0;\n    size_t out_len = 0;\n    struct tlog_time cur_time;\n    int unused __attribute__((unused));\n\n    if (tlog.early_print_disable) {\n        return 0;\n    }\n\n    if (_tlog_gettime(&cur_time) != 0) {\n        return -1;\n    }\n\n    if (tlog.tlog_early_print != NULL) {\n        tlog.tlog_early_print(&info_inter->info, format, ap);\n        return out_len;\n    }\n\n    if (tlog.early_print_no_prefix == 0) {\n        len = snprintf(log_buf, sizeof(log_buf), \"[%.4d-%.2d-%.2d %.2d:%.2d:%.2d,%.3d][%5s][%17s:%-4d] \",\n            cur_time.year, cur_time.mon, cur_time.mday, cur_time.hour, cur_time.min, cur_time.sec, cur_time.usec / 1000,\n            tlog_get_level_string(info_inter->info.level), info_inter->info.file, info_inter->info.line);\n    }\n\n    out_len = len;\n    len = vsnprintf(log_buf + out_len, sizeof(log_buf) - out_len - 1, format, ap);\n    out_len += len;\n    if (len <= 0) {\n        return -1;\n    } else if (len >= sizeof(log_buf) - 1) {\n        out_len = sizeof(log_buf) - 1;\n    }\n\n    if (log_buf[out_len - 1] != '\\n') {\n        log_buf[out_len] = '\\n';\n        out_len++;\n    }\n\n    if (out_len + 1 < sizeof(log_buf) - out_len - 1) {\n        log_buf[out_len] = '\\0';\n    }\n\n    if (tlog.early_print_output != NULL) {\n        len = tlog.early_print_output(&info_inter->info, log_buf, out_len, tlog.early_print_userptr);\n        if (tlog.early_print_with_screen == 0) {\n            return len;\n        }\n    }\n\n    if (tlog.early_print_color) {\n        unused = tlog_stdout_with_color(info_inter->info.level, log_buf, out_len);\n    } else {\n        unused = write(STDOUT_FILENO, log_buf, out_len);\n    }\n\n    return out_len;\n}\n\nint tlog_vext(tlog_level level, const char *file, int line, const char *func, void *userptr, const char *format, va_list ap)\n{\n    struct tlog_info_inter info_inter;\n\n    if (level < tlog_set_level) {\n        return 0;\n    }\n\n    if (level >= TLOG_END) {\n        return -1;\n    }\n\n    info_inter.info.file = file;\n    info_inter.info.line = line;\n    info_inter.info.func = func;\n    info_inter.info.level = level;\n    info_inter.userptr = userptr;\n    if (_tlog_gettime(&info_inter.info.time) != 0) {\n        return -1;\n    }\n\n    if (tlog.root == NULL) {\n        return _tlog_early_print(&info_inter, format, ap);\n    }\n\n    if (unlikely(tlog.root->logsize <= 0 && tlog.root->logscreen == 0 && tlog.root->set_custom_output_func == 0)) {\n        return 0;\n    }\n\n    return _tlog_vprintf(tlog.root, _tlog_root_log_buffer, &info_inter, format, ap);\n}\n\nint tlog_ext(tlog_level level, const char *file, int line, const char *func, void *userptr, const char *format, ...)\n{\n    int len;\n    va_list ap;\n\n    va_start(ap, format);\n    len = tlog_vext(level, file, line, func, userptr, format, ap);\n    va_end(ap);\n\n    return len;\n}\n\nstatic int _tlog_rename_logfile(struct tlog_log *log, const char *log_file)\n{\n    char archive_file[TLOG_BUFF_LEN];\n    struct tlog_time logtime;\n    int i = 0;\n\n    if (_tlog_getmtime(&logtime, log_file) != 0) {\n        return -1;\n    }\n\n    snprintf(archive_file, sizeof(archive_file), \"%s/%s-%.4d%.2d%.2d-%.2d%.2d%.2d%s\",\n        log->logdir, log->logname, logtime.year, logtime.mon, logtime.mday,\n        logtime.hour, logtime.min, logtime.sec, log->suffix);\n\n    while (access(archive_file, F_OK) == 0) {\n        i++;\n        snprintf(archive_file, sizeof(archive_file), \"%s/%s-%.4d%.2d%.2d-%.2d%.2d%.2d-%d%s\",\n            log->logdir, log->logname, logtime.year, logtime.mon,\n            logtime.mday, logtime.hour, logtime.min, logtime.sec, i, log->suffix);\n    }\n\n    if (rename(log_file, archive_file) != 0) {\n        return -1;\n    }\n\n    chmod(archive_file, log->archive_perm);\n\n    return 0;\n}\n\nstatic int _tlog_list_dir(const char *path, list_callback callback, void *userptr)\n{\n    DIR *dir = NULL;\n    struct dirent *ent;\n    int ret = 0;\n    const char *unused __attribute__((unused)) = path;\n\n    dir = opendir(path);\n    if (dir == NULL) {\n        fprintf(stderr, \"tlog: open directory failed, %s\\n\", strerror(errno));\n        goto errout;\n    }\n\n    while ((ent = readdir(dir)) != NULL) {\n        if (strncmp(\".\", ent->d_name, 2) == 0 || strncmp(\"..\", ent->d_name, 3) == 0) {\n            continue;\n        }\n        ret = callback(path, ent, userptr);\n        if (ret != 0) {\n            goto errout;\n        }\n    }\n\n    closedir(dir);\n    return 0;\nerrout:\n    if (dir) {\n        closedir(dir);\n        dir = NULL;\n    }\n    return -1;\n}\n\nstatic int _tlog_count_log_callback(const char *path, struct dirent *entry, void *userptr)\n{\n    struct count_log *count_log = (struct count_log *)userptr;\n    struct tlog_log *log = count_log->log;\n    char logname[TLOG_LOG_NAME_LEN * 2];\n    const char *unused __attribute__((unused)) = path;\n\n    if (strstr(entry->d_name, log->suffix) == NULL) {\n        return 0;\n    }\n\n    snprintf(logname, sizeof(logname), \"%s-\", log->logname);\n    int len = strnlen(logname, sizeof(logname));\n    if (strncmp(logname, entry->d_name, len) != 0) {\n        return 0;\n    }\n\n    count_log->lognum++;\n    return 0;\n}\n\nstatic int _tlog_get_oldest_callback(const char *path, struct dirent *entry, void *userptr)\n{\n    struct stat sb;\n    char filename[TLOG_BUFF_LEN];\n    struct oldest_log *oldestlog = (struct oldest_log *)userptr;\n    struct tlog_log *log = oldestlog->log;\n    char logname[TLOG_LOG_NAME_LEN * 2];\n\n    /* if not a log file, skip */\n    if (strstr(entry->d_name, log->suffix) == NULL) {\n        return 0;\n    }\n\n    /* if not tlog log file, skip */\n    snprintf(logname, sizeof(logname), \"%s-\", log->logname);\n    int len = strnlen(logname, sizeof(logname));\n    if (strncmp(logname, entry->d_name, len) != 0) {\n        return 0;\n    }\n\n    /* get log file mtime */\n    snprintf(filename, sizeof(filename), \"%s/%s\", path, entry->d_name);\n    if (stat(filename, &sb) != 0) {\n        return -1;\n    }\n\n    if (oldestlog->mtime == 0 || oldestlog->mtime > sb.st_mtime) {\n        oldestlog->mtime = sb.st_mtime;\n        strncpy(oldestlog->name, entry->d_name, sizeof(oldestlog->name) - 1);\n        oldestlog->name[sizeof(oldestlog->name) - 1] = '\\0';\n        return 0;\n    }\n\n    return 0;\n}\n\nstatic int _tlog_remove_oldestlog(struct tlog_log *log)\n{\n    struct oldest_log oldestlog;\n    oldestlog.name[0] = 0;\n    oldestlog.mtime = 0;\n    oldestlog.log = log;\n\n    /* get oldest log file name */\n    if (_tlog_list_dir(log->logdir, _tlog_get_oldest_callback, &oldestlog) != 0) {\n        return -1;\n    }\n\n    char filename[PATH_MAX * 2];\n    snprintf(filename, sizeof(filename), \"%s/%s\", log->logdir, oldestlog.name);\n\n    /* delete */\n    unlink(filename);\n\n    return 0;\n}\n\nstatic int _tlog_remove_oldlog(struct tlog_log *log)\n{\n    struct count_log count_log;\n    int i = 0;\n    count_log.lognum = 0;\n    count_log.log = log;\n\n    /* get total log file number */\n    if (_tlog_list_dir(log->logdir, _tlog_count_log_callback, &count_log) != 0) {\n        fprintf(stderr, \"tlog: get log file count failed.\\n\");\n        return -1;\n    }\n\n    /* remove last N log files */\n    for (i = 0; i < count_log.lognum - log->logcount; i++) {\n        _tlog_remove_oldestlog(log);\n    }\n\n    return 0;\n}\n\nstatic void _tlog_log_unlock(struct tlog_log *log)\n{\n    char lock_file[PATH_MAX * 2];\n    if (log->fd_lock <= 0) {\n        return;\n    }\n\n    snprintf(lock_file, sizeof(lock_file), \"%s/%s.lock\", log->logdir, log->logname);\n    unlink(lock_file);\n    close(log->fd_lock);\n    log->fd_lock = -1;\n}\n\nstatic int _tlog_log_lock(struct tlog_log *log)\n{\n    char lock_file[PATH_MAX * 2];\n    int fd;\n\n    if (log->multi_log == 0) {\n        return 0;\n    }\n\n    snprintf(lock_file, sizeof(lock_file), \"%s/%s.lock\", log->logdir, log->logname);\n    fd = open(lock_file, O_RDWR | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);\n    if (fd == -1) {\n        fprintf(stderr, \"tlog: create lock file failed, %s\", strerror(errno));\n        return -1;\n    }\n\n    if (lockf(fd, F_TLOCK, 0) < 0) {\n        goto errout;\n    }\n\n    log->fd_lock = fd;\n    return 0;\n\nerrout:\n    if (fd > 0) {\n        close(fd);\n    }\n    return -1;\n}\n\nstatic void _tlog_wait_pid(struct tlog_log *log, int wait_hang)\n{\n    int status;\n    if (log->zip_pid <= 0) {\n        return;\n    }\n\n    int option = (wait_hang == 0) ? WNOHANG : 0;\n    /* check and obtain gzip process status*/\n    if (waitpid(log->zip_pid, &status, option) <= 0) {\n        if (errno != ECHILD || errno == EINTR) {\n            return;\n        }\n    }\n\n    /* gzip process exited */\n    log->zip_pid = -1;\n    char gzip_file[PATH_MAX * 2];\n\n    /* rename zipped file */\n    snprintf(gzip_file, sizeof(gzip_file), \"%s/%s.pending.gz\", log->logdir, log->logname);\n    if (_tlog_rename_logfile(log, gzip_file) != 0) {\n        _tlog_log_unlock(log);\n        return;\n    }\n\n    /* remove oldest file */\n    _tlog_remove_oldlog(log);\n    _tlog_log_unlock(log);\n}\n\nstatic void _tlog_close_all_fd_by_res(void)\n{\n    struct rlimit lim;\n    int maxfd = 0;\n    int i = 0;\n\n    getrlimit(RLIMIT_NOFILE, &lim);\n\n    maxfd = lim.rlim_cur;\n    if (maxfd > 4096) {\n        maxfd = 4096;\n    }\n\n    for (i = 3; i < maxfd; i++) {\n        close(i);\n    }\n}\n\nstatic int _tlog_str_to_int(const char *str)\n{\n    int num = 0;\n\n    while (*str >= '0' && *str <= '9') {\n        num = num * 10 + (*str - '0');\n        ++str;\n    }\n\n    if (*str) {\n        return -1;\n    }\n\n    return num;\n}\n\nstatic void _tlog_close_all_fd(void)\n{\n#if defined(__linux__)\n    int dir_fd = -1;\n\n    dir_fd = open(\"/proc/self/fd/\", O_RDONLY | O_DIRECTORY);\n    if (dir_fd < 0) {\n        goto errout;\n    }\n\n    char buffer[sizeof(struct linux_dirent64)];\n    int bytes;\n    while ((bytes = syscall(SYS_getdents64, dir_fd,\n                (struct linux_dirent64 *)buffer,\n                sizeof(buffer)))\n        > 0) {\n        struct linux_dirent64 *entry;\n        int offset;\n\n        for (offset = 0; offset < bytes; offset += entry->d_reclen) {\n            int fd;\n            entry = (struct linux_dirent64 *)(buffer + offset);\n            if ((fd = _tlog_str_to_int(entry->d_name)) < 0) {\n                continue;\n            }\n\n            if (fd == dir_fd || fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) {\n                continue;\n            }\n            close(fd);\n        }\n    }\n\n\n    if (bytes < 0) {\n        goto errout;\n    }\n\n    close(dir_fd);\n    return;\nerrout:\n    if (dir_fd > 0) {\n        close(dir_fd);\n        dir_fd = -1;\n    }\n#endif\n    _tlog_close_all_fd_by_res();\n    return;\n}\n\nstatic int _tlog_archive_log_compressed(struct tlog_log *log)\n{\n    char gzip_file[TLOG_BUFF_LEN];\n    char log_file[TLOG_BUFF_LEN];\n    char pending_file[TLOG_BUFF_LEN];\n\n    snprintf(gzip_file, sizeof(gzip_file), \"%s/%s.pending.gz\", log->logdir, log->logname);\n    snprintf(pending_file, sizeof(pending_file), \"%s/%s.pending\", log->logdir, log->logname);\n\n    if (_tlog_log_lock(log) != 0) {\n        return -1;\n    }\n\n    /* if pending.zip exists */\n    if (access(gzip_file, F_OK) == 0) {\n        /* rename it to standard name */\n        if (_tlog_rename_logfile(log, gzip_file) != 0) {\n            goto errout;\n        }\n    }\n\n    if (access(pending_file, F_OK) != 0) {\n        /* rename current log file to pending */\n        snprintf(log_file, sizeof(log_file), \"%s/%s\", log->logdir, log->logname);\n        if (rename(log_file, pending_file) != 0) {\n            goto errout;\n        }\n    }\n\n    /* start gzip process to compress log file */\n    if (log->zip_pid <= 0) {\n        // NOLINTNEXTLINE(bugprone-unsafe-functions): vfork is safe here as we immediately exec\n        int pid = vfork();\n        if (pid == 0) {\n            _tlog_close_all_fd();\n            execl(tlog.gzip_cmd, tlog.gzip_cmd, \"-1\", pending_file, NULL);\n            fprintf(stderr, \"tlog: execl gzip failed, no compress\\n\");\n            log->nocompress = 1;\n            _exit(1);\n        } else if (pid < 0) {\n            if (errno == EPERM || errno == EACCES) {\n                fprintf(stderr, \"tlog: vfork failed, errno: %d, no compress\\n\", errno);\n                log->nocompress = 1;\n            }\n            goto errout;\n        }\n        log->zip_pid = pid;\n    }\n\n    return 0;\n\nerrout:\n    _tlog_log_unlock(log);\n    return -1;\n}\n\nstatic int _tlog_archive_log_nocompress(struct tlog_log *log)\n{\n    char log_file[TLOG_BUFF_LEN];\n    char pending_file[TLOG_BUFF_LEN];\n\n    snprintf(pending_file, sizeof(pending_file), \"%s/%s.pending\", log->logdir, log->logname);\n\n    if (_tlog_log_lock(log) != 0) {\n        return -1;\n    }\n\n    if (access(pending_file, F_OK) != 0) {\n        /* rename current log file to pending */\n        snprintf(log_file, sizeof(log_file), \"%s/%s\", log->logdir, log->logname);\n        if (rename(log_file, pending_file) != 0) {\n            goto errout;\n        }\n    }\n\n    /* rename pending file */\n    if (_tlog_rename_logfile(log, pending_file) != 0) {\n        goto errout;\n    }\n\n    /* remove oldest file */\n    _tlog_remove_oldlog(log);\n    _tlog_log_unlock(log);\n\n    return 0;\n\nerrout:\n    _tlog_log_unlock(log);\n    return -1;\n}\n\nstatic int _tlog_archive_log(struct tlog_log *log)\n{\n    if (log->nocompress) {\n        return _tlog_archive_log_nocompress(log);\n    } else {\n        return _tlog_archive_log_compressed(log);\n    }\n}\n\nstatic void _tlog_get_log_name_dir(struct tlog_log *log)\n{\n    char log_file[PATH_MAX + 1];\n    if (log->fd > 0) {\n        close(log->fd);\n        log->fd = -1;\n    }\n\n    pthread_mutex_lock(&tlog.lock);\n    strncpy(log_file, log->pending_logfile, sizeof(log_file) - 1);\n    log_file[sizeof(log_file) - 1] = '\\0';\n    strncpy(log->logdir, dirname(log_file), sizeof(log->logdir) - 1);\n    log->logdir[sizeof(log->logdir) - 1] = '\\0';\n    strncpy(log_file, log->pending_logfile, PATH_MAX);\n    log_file[sizeof(log_file) - 1] = '\\0';\n    strncpy(log->logname, basename(log_file), sizeof(log->logname) - 1);\n    log->logname[sizeof(log->logname) - 1] = '\\0';\n    snprintf(log->logfile, sizeof(log->logfile), \"%s/%s\", log->logdir, log->logname);\n    pthread_mutex_unlock(&tlog.lock);\n}\n\nstatic int _tlog_write(struct tlog_log *log, const char *buff, int bufflen)\n{\n    int len;\n    int unused __attribute__((unused));\n    struct stat sb = { 0 };\n\n    if (bufflen <= 0 || log->fail) {\n        return 0;\n    }\n\n    if (log->rename_pending) {\n        _tlog_get_log_name_dir(log);\n        log->rename_pending = 0;\n    }\n\n    if (log->logcount <= 0 || log->logsize <= 0) {\n        return 0;\n    }\n\n    /* if log file size exceeds threshold, start to compress */\n    if (log->multi_log && log->fd > 0) {\n        log->filesize = lseek(log->fd, 0, SEEK_END);\n    }\n\n    if (log->filesize > log->logsize && log->zip_pid <= 0) {\n        if (log->filesize < lseek(log->fd, 0, SEEK_END) && log->multi_log == 0) {\n            const char *msg = \"[Auto enable multi-process write mode, log may be lost, please enable multi-process write mode manually]\\n\";\n            log->multi_log = 1;\n            unused = write(log->fd, msg, strlen(msg));\n        }\n        close(log->fd);\n        log->fd = -1;\n        log->filesize = 0;\n        _tlog_archive_log(log);\n    }\n\n\n    if ((log->fd <= 0 && log->logsize > 0)\n        || ((0 == fstat(log->fd, &sb))\n            && (0 == sb.st_nlink))      // log file was deleted\n    ) {\n        /* open a new log file to write */\n        time_t now;\n        \n        if (log->fd > 0) {\n                close(log->fd);\n                log->fd = -1;\n        }\n\n        time(&now);\n        if (now == log->last_try) {\n            return -1;\n        }\n        log->last_try = now;\n\n        if (_tlog_mkdir(log->logdir) != 0) {\n            if (log->print_errmsg == 0) {\n                return -1;\n            }\n            log->print_errmsg = 0;\n            fprintf(stderr, \"tlog: create log dir %s failed, %s\\n\", log->logdir, strerror(errno));\n            if (errno == EACCES && log->logscreen == 0) {\n                fprintf(stderr, \"tlog: no permission to write log file, output log to console\\n\");\n                tlog_logscreen(log, 1);\n                tlog_logcount(log, 0);\n            }\n            return -1;\n        }\n\n        log->filesize = 0;\n        log->fd = open(log->logfile, O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, log->file_perm);\n        if (log->fd < 0) {\n            if (log->print_errmsg == 0) {\n                return -1;\n            }\n\n            fprintf(stderr, \"tlog: open log file %s failed, %s\\n\", log->logfile, strerror(errno));\n            log->print_errmsg = 0;\n            return -1;\n        }\n\n        if (log->mode_changed != 0) {\n            fchmod(log->fd, log->file_perm);\n        }\n\n        log->last_try = 0;\n        log->print_errmsg = 1;\n        /* get log file size */\n        log->filesize = lseek(log->fd, 0, SEEK_END);\n    }\n\n    /* write log to file */\n    len = write(log->fd, buff, bufflen);\n    if (len > 0) {\n        log->filesize += len;\n    } else {\n        if (log->fd > 0 && errno == ENOSPC) {\n            close(log->fd);\n            log->fd = -1;\n        }\n    }\n    return len;\n}\n\nint tlog_write(struct tlog_log *log, const char *buff, int bufflen)\n{\n    return _tlog_write(log, buff, bufflen);\n}\n\nstatic int _tlog_has_data(struct tlog_log *log)\n{\n    if (log->end != log->start || log->ext_end > 0) {\n        return 1;\n    }\n\n    return 0;\n}\n\nstatic int _tlog_any_has_data_locked(void)\n{\n    struct tlog_log *next = NULL;\n\n    next = tlog.log;\n    while (next) {\n        if (_tlog_has_data(next) == 1) {\n            return 1;\n        }\n        next = next->next;\n    }\n\n    return 0;\n}\n\nstatic int _tlog_any_has_data(void)\n{\n    int ret = 0;\n    pthread_mutex_lock(&tlog.lock);\n    ret = _tlog_any_has_data_locked();\n    pthread_mutex_unlock(&tlog.lock);\n\n    return ret;\n}\n\nstatic int _tlog_wait_pids(void)\n{\n    time_t now = time(NULL);\n    struct tlog_log *next = NULL;\n    static struct tlog_log *last_log = NULL;\n\n    pthread_mutex_lock(&tlog.lock);\n    for (next = tlog.log; next != NULL; next = next->next) {\n        if (next->zip_pid <= 0) {\n            continue;\n        }\n\n        if (next == last_log) {\n            continue;\n        }\n\n        if (next->last_waitpid == now) {\n            continue;\n        }\n\n        last_log = next;\n        next->last_waitpid = now;\n        pthread_mutex_unlock(&tlog.lock);\n        _tlog_wait_pid(next, 0);\n        return 0;\n    }\n    last_log = NULL;\n    pthread_mutex_unlock(&tlog.lock);\n\n    return 0;\n}\n\nstatic int _tlog_close(struct tlog_log *log, int wait_hang)\n{\n    struct tlog_log *next = tlog.log;\n\n    if (log == NULL) {\n        return -1;\n    }\n\n    if (log->zip_pid > 0) {\n        _tlog_wait_pid(log, wait_hang);\n        if (log->zip_pid > 0) {\n            return -1;\n        }\n    }\n\n    if (log->fd > 0) {\n        close(log->fd);\n        log->fd = -1;\n    }\n\n    _tlog_log_unlock(log);\n\n    if (log->buff != NULL) {\n        free(log->buff);\n        log->buff = NULL;\n    }\n\n    if (next == log) {\n        tlog.log = next->next;\n        free(log);\n        return 0;\n    }\n\n    while (next) {\n        if (next->next == log) {\n            next->next = log->next;\n            free(log);\n            return -1;\n        }\n        next = next->next;\n    }\n\n    pthread_cond_destroy(&log->client_cond);\n    pthread_mutex_destroy(&log->lock);\n\n    return 0;\n}\n\nstatic struct tlog_log *_tlog_next_log(struct tlog_log *last_log)\n{\n    if (last_log == NULL) {\n        return tlog.log;\n    }\n\n    return last_log->next;\n}\n\nstatic struct tlog_log *_tlog_wait_log_locked(struct tlog_log *last_log)\n{\n    int ret = 0;\n    struct timespec tm;\n    struct tlog_log *log = NULL;\n    struct tlog_log *next = NULL;\n    int need_wait_pid = 0;\n\n    for (next = tlog.log; next != NULL; next = next->next) {\n        if (next->zip_pid > 0) {\n            need_wait_pid = 1;\n            break;\n        }\n    }\n\n    clock_gettime(CLOCK_REALTIME, &tm);\n    tm.tv_sec += 2;\n    tlog.is_wait = 1;\n    tlog.wait_on_log = last_log;\n    if (need_wait_pid != 0) {\n        ret = pthread_cond_timedwait(&tlog.cond, &tlog.lock, &tm);\n    } else {\n        ret = pthread_cond_wait(&tlog.cond, &tlog.lock);\n    }\n\n    tlog.is_wait = 0;\n    tlog.wait_on_log = NULL;\n    errno = ret;\n    if (ret == 0 || ret == ETIMEDOUT) {\n        log = tlog.notify_log;\n        tlog.notify_log = NULL;\n    }\n\n    return log;\n}\n\nstatic void _tlog_wakeup_waiters(struct tlog_log *log)\n{\n    pthread_mutex_lock(&log->lock);\n    if (log->waiters > 0) {\n        /* if there are waiters, wakeup */\n        pthread_cond_broadcast(&log->client_cond);\n    }\n    pthread_mutex_unlock(&log->lock);\n}\n\nstatic void _tlog_write_one_segment_log(struct tlog_log *log, char *buff, int bufflen)\n{\n    struct tlog_segment_head *segment_head = NULL;\n    int write_len = 0;\n\n    segment_head = (struct tlog_segment_head *)buff;\n    for (write_len = 0; write_len < bufflen;) {\n        if (segment_head->magic != TLOG_SEGMENT_MAGIC) {\n            return;\n        }\n        \n        _tlog_write_output_func(log, segment_head->data, segment_head->len - 1);\n        write_len += segment_head->len + sizeof(*segment_head);\n        segment_head = (struct tlog_segment_head *)(buff + write_len);\n    }\n}\n\nstatic void _tlog_write_segments_log(struct tlog_log *log, int log_len, int log_extlen)\n{\n    _tlog_write_one_segment_log(log, log->buff + log->start, log_len);\n    if (log_extlen > 0) {\n        /* write extend buffer log */\n        _tlog_write_one_segment_log(log, log->buff, log_extlen);\n    }\n}\n\nstatic void _tlog_write_buff_log(struct tlog_log *log, int log_len, int log_extlen)\n{\n    _tlog_write_output_func(log, log->buff + log->start, log_len);\n    if (log_extlen > 0) {\n        /* write extend buffer log */\n        _tlog_write_output_func(log, log->buff, log_extlen);\n    }\n}\n\nstatic void _tlog_work_write(struct tlog_log *log, int log_len, int log_extlen, int log_dropped)\n{\n    /* write log */\n    if (log->segment_log) {\n        _tlog_write_segments_log(log, log_len, log_extlen);\n    } else {\n        _tlog_write_buff_log(log, log_len, log_extlen);\n    }\n\n    if (log_dropped > 0) {\n        /* if there is dropped log, record dropped log number */\n        char dropmsg[TLOG_TMP_LEN];\n        char *msg = dropmsg;\n        struct tlog_segment_log_head *log_head = NULL;\n        if (log->segment_log) {\n            memset(dropmsg, 0, sizeof(struct tlog_segment_log_head));\n            log_head = (struct tlog_segment_log_head *)dropmsg;\n            msg += sizeof(struct tlog_segment_log_head);\n            log_head->info.level = TLOG_WARN;\n        }\n\n        int len = snprintf(msg, msg - dropmsg, \"[Total Dropped %d Messages]\\n\", log_dropped);\n        if (log_head) {\n            log_head->len = len;\n        }\n        _tlog_write_output_func(log, dropmsg, strnlen(dropmsg, sizeof(dropmsg)));\n    }\n}\n\nstatic int _tlog_root_write_screen_log(struct tlog_log *log, struct tlog_loginfo *info, const char *buff, int bufflen)\n{\n    if (log->logscreen == 0) {\n        return 0;\n    }\n\n    return _tlog_write_screen(log, info, buff, bufflen);\n}\n\nstatic int _tlog_root_write_log(struct tlog_log *log, const char *buff, int bufflen)\n{\n    struct tlog_segment_log_head *head = NULL;\n    static struct tlog_segment_log_head empty_info = { .info.level = TLOG_INFO };\n    if (tlog.output_func == NULL) {\n        if (log->segment_log) {\n            head = (struct tlog_segment_log_head *)buff;\n            _tlog_root_write_screen_log(log, &head->info, head->data, head->len);\n            return _tlog_write(log, head->data, head->len);\n        }\n        _tlog_root_write_screen_log(log, NULL, buff, bufflen);\n        return _tlog_write(log, buff, bufflen);\n    }\n\n    if (log->segment_log && tlog.root == log) {\n        head = (struct tlog_segment_log_head *)buff;\n        _tlog_root_write_screen_log(log, &head->info, head->data, head->len);\n        return tlog.output_func(&head->info, head->data, head->len, tlog_get_private(log));\n    }\n\n    _tlog_root_write_screen_log(log, NULL, buff, bufflen);\n    return tlog.output_func(&empty_info.info, buff, bufflen, tlog_get_private(log));\n}\n\nstatic void tlog_wait_zip_fini(void)\n{\n    tlog_log *next;\n    if (tlog.root == NULL) {\n        return;\n    }\n\n    int wait_zip = 1;\n    int time_out = 0;\n    while (wait_zip) {\n        wait_zip = 0;\n        time_out++;\n        next = tlog.log;\n        while (next) {\n            if (next->zip_pid > 0 && wait_zip == 0) {\n                wait_zip = 1;\n                usleep(1000);\n            }\n\n            if (kill(next->zip_pid, 0) != 0 || time_out >= 5000) {\n                next->zip_pid = -1;\n            }\n            next = next->next;\n        }\n    }\n\n    return;\n}\n\nstatic void *_tlog_work(void *arg)\n{\n    int log_len = 0;\n    int log_extlen = 0;\n    int log_end = 0;\n    int log_extend = 0;\n    int log_dropped = 0;\n    struct tlog_log *log = NULL;\n    struct tlog_log *loop_log = NULL;\n    void *unused __attribute__((unused));\n\n    unused = arg;\n\n    // for child process\n    tlog_wait_zip_fini();\n\n    while (1) {\n        log_len = 0;\n        log_extlen = 0;\n        log_extend = 0;\n        if (__atomic_load_n(&tlog.run, __ATOMIC_RELAXED) == 0) {\n            if (_tlog_any_has_data() == 0) {\n                break;\n            }\n        }\n\n        _tlog_wait_pids();\n\n        pthread_mutex_lock(&tlog.lock);\n        if (loop_log == NULL) {\n            loop_log = log;\n        }\n\n        log = _tlog_next_log(log);\n        if (log == NULL) {\n            pthread_mutex_unlock(&tlog.lock);\n            continue;\n        }\n\n        /* if buffer is empty, wait */\n        if (_tlog_any_has_data_locked() == 0 && __atomic_load_n(&tlog.run, __ATOMIC_RELAXED)) {\n            log = _tlog_wait_log_locked(log);\n            if (log == NULL) {\n                pthread_mutex_unlock(&tlog.lock);\n                if (errno != ETIMEDOUT && __atomic_load_n(&tlog.run, __ATOMIC_RELAXED)) {\n                    sleep(1);\n                }\n                continue;\n            }\n        }\n\n        if (_tlog_has_data(log) == 0) {\n            if (log->is_exit) {\n                if (_tlog_close(log, 0) == 0) {\n                    log = NULL;\n                    loop_log = NULL;\n                };\n            }\n            pthread_mutex_unlock(&tlog.lock);\n            continue;\n        }\n\n        loop_log = NULL;\n\n        if (log->ext_end > 0) {\n            log_len = log->ext_end - log->start;\n            log_extend = log->ext_end;\n        }\n        if (log->end < log->start) {\n            log_extlen = log->end;\n        } else if (log->end > log->start) {\n            log_len = log->end - log->start;\n        }\n        log_end = log->end;\n        log_dropped = log->dropped;\n        log->dropped = 0;\n        pthread_mutex_unlock(&tlog.lock);\n\n        /* start write log work */\n        _tlog_work_write(log, log_len, log_extlen, log_dropped);\n\n        pthread_mutex_lock(&tlog.lock);\n        /* release finished buffer */\n        log->start = log_end;\n        if (log_extend > 0) {\n            log->ext_end = 0;\n        }\n        pthread_mutex_unlock(&tlog.lock);\n\n        _tlog_wakeup_waiters(log);\n    }\n\n    return NULL;\n}\n\nvoid tlog_set_early_printf(int enable, int no_prefix, int color)\n{\n    tlog.early_print_disable = (enable == 0) ? 1 : 0;\n    tlog.early_print_no_prefix = (no_prefix == 0) ? 0 : 1;\n    tlog.early_print_color = (color == 0 || isatty(STDOUT_FILENO) ==  0) ? 0 : 1;\n}\n\nvoid tlog_reg_early_printf_callback(tlog_early_print_func callback)\n{\n    tlog.tlog_early_print = callback;\n}\n\nvoid tlog_reg_early_printf_output_callback(tlog_log_output_func callback, int log_screen, void *private_data)\n{\n    tlog.early_print_output = callback;\n    tlog.early_print_userptr = private_data;\n    tlog.early_print_with_screen = (log_screen == 0) ? 0 : 1;\n}\n\nconst char *tlog_get_level_string(tlog_level level)\n{\n    if (level >= TLOG_END) {\n        return NULL;\n    }\n\n    return tlog_level_str[level];\n}\n\nvoid tlog_set_maxlog_count(int count)\n{\n    tlog_logcount(tlog.root, count);\n}\n\nstatic void _tlog_log_setlogscreen(struct tlog_log *log, int enable)\n{\n    if (log == NULL) {\n        return;\n    }\n\n    log->logscreen = (enable != 0) ? 1 : 0;\n}\n\nvoid tlog_setlogscreen(int enable)\n{\n    _tlog_log_setlogscreen(tlog.root, enable);\n}\n\nint tlog_write_log(const char *buff, int bufflen)\n{\n    if (unlikely(tlog.root == NULL)) {\n        return -1;\n    }\n\n    return _tlog_write(tlog.root, buff, bufflen);\n}\n\nvoid tlog_logscreen(tlog_log *log, int enable)\n{\n    if (log == NULL) {\n        return;\n    }\n\n    _tlog_log_setlogscreen(log, enable);\n}\n\nstatic int _tlog_reg_output_func(tlog_log *log, tlog_output_func output)\n{\n    if (log == NULL) {\n        return -1;\n    }\n\n    if (output == NULL) {\n        log->output_func = _tlog_write;\n        return 0;\n    }\n\n    log->output_func = output;\n\n    return 0;\n}\n\nint tlog_reg_output_func(tlog_log *log, tlog_output_func output)\n{\n    if (log == tlog.root) {\n        return -1;\n    }\n\n    int ret =  _tlog_reg_output_func(log, output);\n    if (ret == 0) {\n        log->set_custom_output_func = 1;\n    }\n\n    return ret;\n}\n\nint tlog_reg_format_func(tlog_format_func callback)\n{\n    tlog.root_format = callback;\n    return 0;\n}\n\nint tlog_reg_log_output_func(tlog_log_output_func output, void *private_data)\n{\n    tlog.output_func = output;\n    tlog_set_private(tlog.root, private_data);\n    tlog.log->set_custom_output_func = 1;\n    return 0;\n}\n\nint tlog_setlevel(tlog_level level)\n{\n    if (level >= TLOG_END) {\n        return -1;\n    }\n\n    tlog_set_level = level;\n    return 0;\n}\n\nint tlog_log_enabled(tlog_level level)\n{\n    if (level >= TLOG_END) {\n        return 0;\n    }\n\n    return (tlog_set_level >= level) ? 1 : 0;\n}\n\ntlog_level tlog_getlevel(void)\n{\n    return tlog_set_level;\n}\n\nvoid tlog_set_logfile(const char *logfile)\n{\n    tlog_rename_logfile(tlog.root, logfile);\n}\n\nstatic void _tlog_get_gzip_cmd_path(void)\n{\n    char *copy_path = NULL;\n    char gzip_cmd_path[PATH_MAX];\n    const char *env_path = getenv(\"PATH\");\n    char *save_ptr = NULL;\n\n    if (env_path == NULL) {\n        env_path = \"/bin:/usr/bin:/usr/local/bin\";\n    }\n\n    copy_path = strdup(env_path);\n    if (copy_path == NULL) {\n        return;\n    }\n\n    for (char *tok = strtok_r(copy_path, \":\", &save_ptr); tok; tok = strtok_r(NULL, \":\", &save_ptr)) {\n        snprintf(gzip_cmd_path, sizeof(gzip_cmd_path), \"%s/gzip\", tok);\n        if (access(gzip_cmd_path, X_OK) != 0) {\n            continue;\n        }\n\n        snprintf(tlog.gzip_cmd, sizeof(tlog.gzip_cmd), \"%s\", gzip_cmd_path);\n        break;\n    }\n\n    free(copy_path);\n}\n\ntlog_log *tlog_open(const char *logfile, int maxlogsize, int maxlogcount, int buffsize, unsigned int flag)\n{\n    struct tlog_log *log = NULL;\n\n    if (__atomic_load_n(&tlog.run, __ATOMIC_RELAXED) == 0) {\n        fprintf(stderr, \"tlog: tlog is not initialized.\\n\");\n        return NULL;\n    }\n\n    log = (struct tlog_log *)calloc(1, sizeof(*log));\n    if (log == NULL) {\n        fprintf(stderr, \"tlog: malloc log failed.\\n\");\n        return NULL;\n    }\n\n    log->start = 0;\n    log->end = 0;\n    log->ext_end = 0;\n    log->dropped = 0;\n    log->buffsize = (buffsize > 0) ? buffsize : TLOG_BUFF_SIZE;\n    log->logsize = (maxlogsize >= 0) ? maxlogsize : TLOG_LOG_SIZE;\n    log->logcount = (maxlogcount <= 0) ? 0 : maxlogcount;\n    log->fd = -1;\n    log->filesize = 0;\n    log->zip_pid = -1;\n    log->is_exit = 0;\n    log->fail = 0;\n    log->print_errmsg = 1;\n    log->waiters = 0;\n    log->block = ((flag & TLOG_NONBLOCK) == 0) ? 1 : 0;\n    log->nocompress = ((flag & TLOG_NOCOMPRESS) == 0) ? 0 : 1;\n    log->logscreen = ((flag & TLOG_SCREEN) == 0) ? 0 : 1;\n    log->logscreen_color = ((flag & TLOG_SCREEN_COLOR) == 0 || isatty(STDOUT_FILENO) == 0) ? 0 : 1;\n    log->multi_log = ((flag & TLOG_MULTI_WRITE) == 0) ? 0 : 1;\n    log->segment_log = ((flag & TLOG_SEGMENT) == 0) ? 0 : 1;\n    log->max_line_size = TLOG_MAX_LINE_LEN;\n    log->output_func = _tlog_write;\n    log->file_perm = S_IRUSR | S_IWUSR | S_IRGRP;\n    log->archive_perm = S_IRUSR | S_IRGRP;\n    \n    if (log->nocompress == 0 && tlog.gzip_cmd[0] == '\\0') {\n        log->nocompress = 1;\n    }\n\n    if (log->logscreen_color == 1) {\n        log->logscreen = 1;\n        log->segment_log = 1;\n    }\n\n    tlog_rename_logfile(log, logfile);\n    if (log->nocompress) {\n        strncpy(log->suffix, TLOG_SUFFIX_LOG, sizeof(log->suffix));\n    } else {\n        strncpy(log->suffix, TLOG_SUFFIX_GZ, sizeof(log->suffix));\n    }\n\n    log->buff = (char *)malloc(log->buffsize);\n    if (log->buff == NULL) {\n        fprintf(stderr, \"tlog: malloc log buffer failed, %s\\n\", strerror(errno));\n        goto errout;\n    }\n\n    pthread_mutex_lock(&tlog.lock);\n    if (tlog.log == NULL) {\n        tlog.log = log;\n    } else {\n        log->next = tlog.log;\n        tlog.log = log;\n    }\n    pthread_mutex_unlock(&tlog.lock);\n\n    return log;\n\nerrout:\n    if (log) {\n        pthread_cond_destroy(&log->client_cond);\n        pthread_mutex_destroy(&log->lock);\n        free(log);\n        log = NULL;\n    }\n\n    return NULL;\n}\n\nvoid tlog_close(tlog_log *log)\n{\n    if (log == NULL) {\n        return;\n    }\n\n    log->is_exit = 1;\n}\n\nvoid tlog_rename_logfile(struct tlog_log *log, const char *logfile)\n{\n    pthread_mutex_lock(&tlog.lock);\n    strncpy(log->pending_logfile, logfile, sizeof(log->pending_logfile) - 1);\n    pthread_mutex_unlock(&tlog.lock);\n    log->rename_pending = 1;\n}\n\nstatic void tlog_fork_prepare(void)\n{\n    if (tlog.root == NULL) {\n        return;\n    }\n\n    pthread_mutex_lock(&tlog.lock);\n    tlog_log *next;\n    next = tlog.log;\n    while (next) {\n        next->multi_log = 1;\n        next = next->next;\n    }\n}\n\nstatic void tlog_fork_parent(void)\n{\n    if (tlog.root == NULL) {\n        return;\n    }\n\n    pthread_mutex_unlock(&tlog.lock);\n}\n\nstatic void tlog_fork_child(void)\n{\n    pthread_attr_t attr;\n    tlog_log *next;\n    if (tlog.root == NULL) {\n        return;\n    }\n\n    next = tlog.log;\n    while (next) {\n        next->start = 0;\n        next->end = 0;\n        next->ext_end = 0;\n        next->dropped = 0;\n        next->filesize = 0;\n        next = next->next;\n    }\n\n    pthread_attr_init(&attr);\n    int ret = pthread_create(&tlog.tid, &attr, _tlog_work, NULL);\n    if (ret != 0) {\n        fprintf(stderr, \"tlog: create tlog work thread failed, %s\\n\", strerror(errno));\n        goto errout;\n    }\n\n    goto out;\nerrout:\n    next = tlog.log;\n    while (next) {\n        next->fail = 1;\n        next = next->next;\n    }\nout:\n    pthread_mutex_unlock(&tlog.lock);\n}\n\nint tlog_init(const char *logfile, int maxlogsize, int maxlogcount, int buffsize, unsigned int flag)\n{\n    pthread_attr_t attr;\n    int ret;\n    struct tlog_log *log = NULL;\n\n    if (tlog.root_format != NULL) {\n        fprintf(stderr, \"tlog: already initialized.\\n\");\n        return -1;\n    }\n\n    if (buffsize > 0 && buffsize < TLOG_MAX_LINE_SIZE_SET * 2) {\n        fprintf(stderr, \"tlog: buffer size is invalid.\\n\");\n        return -1;\n    }\n\n    memset(&tlog, 0, sizeof(tlog));\n    tlog.is_wait = 0;\n\n    _tlog_get_gzip_cmd_path();\n    pthread_attr_init(&attr);\n    pthread_cond_init(&tlog.cond, NULL);\n    pthread_mutex_init(&tlog.lock, NULL);\n    __atomic_store_n(&tlog.run, 1, __ATOMIC_RELAXED);\n\n    log = tlog_open(logfile, maxlogsize, maxlogcount, buffsize, flag);\n    if (log == NULL) {\n        fprintf(stderr, \"tlog: init tlog root failed.\\n\");\n        goto errout;\n    }\n    _tlog_reg_output_func(log, _tlog_root_write_log);\n\n    if ((flag & TLOG_NOCOMPRESS) == 0 && tlog.gzip_cmd[0] == '\\0') {\n        fprintf(stderr, \"tlog: can not find gzip command, disable compress.\\n\");\n    }\n\n    tlog.output_no_prefix = ((flag & TLOG_FORMAT_NO_PREFIX) == 0) ? 0 : 1;\n    tlog.root = log;\n    tlog.root_format = _tlog_root_default_format;\n\n    ret = pthread_create(&tlog.tid, &attr, _tlog_work, NULL);\n    if (ret != 0) {\n        fprintf(stderr, \"tlog: create tlog work thread failed, %s\\n\", strerror(errno));\n        goto errout;\n    }\n\n    if (flag & TLOG_SUPPORT_FORK) {\n        pthread_atfork(&tlog_fork_prepare, &tlog_fork_parent, &tlog_fork_child);\n    }\n    return 0;\nerrout:\n    if (tlog.tid) {\n        void *retval = NULL;\n        __atomic_store_n(&tlog.run, 0, __ATOMIC_RELAXED);\n        pthread_join(tlog.tid, &retval);\n        tlog.tid = 0;\n    }\n\n    pthread_cond_destroy(&tlog.cond);\n    pthread_mutex_destroy(&tlog.lock);\n    __atomic_store_n(&tlog.run, 0, __ATOMIC_RELAXED);\n    tlog.root = NULL;\n    tlog.root_format = NULL;\n\n    _tlog_close(log, 1);\n\n    return -1;\n}\n\nvoid tlog_exit(void)\n{\n    if (tlog.root_format == NULL) {\n        return;\n    }\n\n    if (tlog.tid) {\n        void *ret = NULL;\n        __atomic_store_n(&tlog.run, 0, __ATOMIC_RELAXED);\n        pthread_mutex_lock(&tlog.lock);\n        pthread_cond_signal(&tlog.cond);\n        pthread_mutex_unlock(&tlog.lock);\n        pthread_join(tlog.tid, &ret);\n        tlog.tid = 0;\n    }\n\n    tlog.root = NULL;\n    while (tlog.log) {\n        _tlog_close(tlog.log, 1);\n    }\n\n    pthread_cond_destroy(&tlog.cond);\n    pthread_mutex_destroy(&tlog.lock);\n\n    tlog.root_format = NULL;\n    tlog.is_wait = 0;\n}\n"
  },
  {
    "path": "src/utils/alpn.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n\n#include <stdint.h>\n#include <string.h>\n#include <unistd.h>\n\nint encode_alpn_protos(const char *alpn, uint8_t *alpn_data, int alpn_data_max)\n{\n\tint alpn_data_len = 0;\n\tconst char *alpn_str = alpn;\n\n\tif (alpn == NULL || alpn[0] == 0 || alpn_data == NULL || alpn_data_max <= 0) {\n\t\treturn 0;\n\t}\n\n\t/* Parse comma-separated ALPN protocols and encode in wire format */\n\twhile (*alpn_str && alpn_data_len < alpn_data_max - 1) {\n\t\tconst char *comma = strchr(alpn_str, ',');\n\t\tint proto_len;\n\n\t\tif (comma) {\n\t\t\tproto_len = comma - alpn_str;\n\t\t} else {\n\t\t\tproto_len = strnlen(alpn_str, alpn_data_max - alpn_data_len - 1);\n\t\t}\n\n\t\t/* Skip empty protocols */\n\t\tif (proto_len == 0) {\n\t\t\talpn_str = comma ? comma + 1 : alpn_str + proto_len;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Check if we have space for length byte + protocol */\n\t\tif (alpn_data_len + 1 + proto_len > alpn_data_max) {\n\t\t\ttlog(TLOG_WARN, \"ALPN string too long, truncating.\");\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Write length-prefixed protocol */\n\t\talpn_data[alpn_data_len++] = (uint8_t)proto_len;\n\t\tmemcpy(alpn_data + alpn_data_len, alpn_str, proto_len);\n\t\talpn_data_len += proto_len;\n\n\t\t/* Move to next protocol */\n\t\talpn_str = comma ? comma + 1 : alpn_str + proto_len;\n\t\tif (!comma) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn alpn_data_len;\n}\n"
  },
  {
    "path": "src/utils/capbility.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/util.h\"\n\n#include \"smartdns/dns_conf.h\"\n#include <linux/capability.h>\n#include <linux/limits.h>\n#include <pwd.h>\n#include <sys/prctl.h>\n#include <sys/resource.h>\n#include <sys/types.h>\n\nint get_uid_gid(uid_t *uid, gid_t *gid)\n{\n\tstruct passwd *result = NULL;\n\tstruct passwd pwd;\n\tchar *buf = NULL;\n\tssize_t bufsize = 0;\n\tint ret = -1;\n\n\tif (dns_conf.user[0] == '\\0') {\n\t\t*uid = getuid();\n\t\t*gid = getgid();\n\t\treturn 0;\n\t}\n\n\tbufsize = sysconf(_SC_GETPW_R_SIZE_MAX);\n\tif (bufsize == -1) {\n\t\tbufsize = 1024 * 16;\n\t}\n\n\tbuf = malloc(bufsize);\n\tif (buf == NULL) {\n\t\tgoto out;\n\t}\n\n\tret = getpwnam_r(dns_conf.user, &pwd, buf, bufsize, &result);\n\tif (ret != 0) {\n\t\tgoto out;\n\t}\n\n\tif (result == NULL) {\n\t\tret = -1;\n\t\tgoto out;\n\t}\n\n\t*uid = result->pw_uid;\n\t*gid = result->pw_gid;\n\nout:\n\tif (buf) {\n\t\tfree(buf);\n\t}\n\n\treturn ret;\n}\n\nint capget(struct __user_cap_header_struct *header, struct __user_cap_data_struct *cap);\nint capset(struct __user_cap_header_struct *header, struct __user_cap_data_struct *cap);\n\nint drop_root_privilege(void)\n{\n\tstruct __user_cap_data_struct cap[2];\n\tstruct __user_cap_header_struct header;\n#ifdef _LINUX_CAPABILITY_VERSION_3\n\theader.version = _LINUX_CAPABILITY_VERSION_3;\n#else\n\theader.version = _LINUX_CAPABILITY_VERSION;\n#endif\n\theader.pid = 0;\n\tuid_t uid = 0;\n\tgid_t gid = 0;\n\tint unused __attribute__((unused)) = 0;\n\n\tif (get_uid_gid(&uid, &gid) != 0) {\n\t\treturn -1;\n\t}\n\n\tmemset(cap, 0, sizeof(cap));\n\tif (capget(&header, cap) < 0) {\n\t\treturn -1;\n\t}\n\n\tprctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);\n\tfor (int i = 0; i < 2; i++) {\n\t\tcap[i].effective = (1 << CAP_NET_RAW | 1 << CAP_NET_ADMIN | 1 << CAP_NET_BIND_SERVICE);\n\t\tcap[i].permitted = (1 << CAP_NET_RAW | 1 << CAP_NET_ADMIN | 1 << CAP_NET_BIND_SERVICE);\n\t}\n\n\tunused = setgid(gid);\n\tunused = setuid(uid);\n\tif (capset(&header, cap) < 0) {\n\t\treturn -1;\n\t}\n\n\tprctl(PR_SET_KEEPCAPS, 0, 0, 0, 0);\n\treturn 0;\n}\n"
  },
  {
    "path": "src/utils/daemon.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/util.h\"\n\n#include <dirent.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <poll.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/resource.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\nenum daemon_msg_type {\n\tDAEMON_MSG_KICKOFF,\n\tDAEMON_MSG_KEEPALIVE,\n\tDAEMON_MSG_DAEMON_PID,\n};\n\nstruct daemon_msg {\n\tenum daemon_msg_type type;\n\tint value;\n};\n\nstatic int pidfile_fd;\nstatic int daemon_fd;\n\nstatic void _close_all_fd_by_res(void)\n{\n\tstruct rlimit lim;\n\tint maxfd = 0;\n\tint i = 0;\n\n\tgetrlimit(RLIMIT_NOFILE, &lim);\n\n\tmaxfd = lim.rlim_cur;\n\tif (maxfd > 4096) {\n\t\tmaxfd = 4096;\n\t}\n\n\tfor (i = 3; i < maxfd; i++) {\n\t\tclose(i);\n\t}\n}\n\nvoid close_all_fd(int keepfd)\n{\n\tDIR *dirp;\n\tint dir_fd = -1;\n\tstruct dirent *dentp;\n\n\tdirp = opendir(\"/proc/self/fd\");\n\tif (dirp == NULL) {\n\t\tgoto errout;\n\t}\n\n\tdir_fd = dirfd(dirp);\n\n\twhile ((dentp = readdir(dirp)) != NULL) {\n\t\tint fd = atol(dentp->d_name);\n\t\tif (fd < 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (fd == dir_fd || fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO || fd == keepfd) {\n\t\t\tcontinue;\n\t\t}\n\t\tclose(fd);\n\t}\n\n\tclosedir(dirp);\n\treturn;\nerrout:\n\tif (dirp) {\n\t\tclosedir(dirp);\n\t}\n\t_close_all_fd_by_res();\n\treturn;\n}\n\nvoid daemon_close_stdfds(void)\n{\n\tint fd_null = open(\"/dev/null\", O_RDWR);\n\tif (fd_null < 0) {\n\t\tfprintf(stderr, \"open /dev/null failed, %s\\n\", strerror(errno));\n\t\treturn;\n\t}\n\n\tdup2(fd_null, STDIN_FILENO);\n\tdup2(fd_null, STDOUT_FILENO);\n\tdup2(fd_null, STDERR_FILENO);\n\n\tif (fd_null > 2) {\n\t\tclose(fd_null);\n\t}\n}\n\nint daemon_kickoff(int status, int no_close)\n{\n\tstruct daemon_msg msg;\n\n\tif (daemon_fd <= 0) {\n\t\treturn -1;\n\t}\n\n\tmsg.type = DAEMON_MSG_KICKOFF;\n\tmsg.value = status;\n\n\tint ret = write(daemon_fd, &msg, sizeof(msg));\n\tif (ret != sizeof(msg)) {\n\t\tfprintf(stderr, \"notify parent process failed, %s\\n\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\tif (no_close == 0) {\n\t\tdaemon_close_stdfds();\n\t}\n\n\tclose(daemon_fd);\n\tdaemon_fd = -1;\n\n\treturn 0;\n}\n\nint daemon_keepalive(void)\n{\n\tstruct daemon_msg msg;\n\tstatic time_t last = 0;\n\ttime_t now = time(NULL);\n\n\tif (daemon_fd <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (now == last) {\n\t\treturn 0;\n\t}\n\n\tlast = now;\n\n\tmsg.type = DAEMON_MSG_KEEPALIVE;\n\tmsg.value = 0;\n\n\tint ret = write(daemon_fd, &msg, sizeof(msg));\n\tif (ret != sizeof(msg)) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\ndaemon_ret daemon_run(int *wstatus)\n{\n\tpid_t pid = 0;\n\tint fds[2] = {0};\n\n\tif (pipe(fds) != 0) {\n\t\tfprintf(stderr, \"run daemon process failed, pipe failed, %s\\n\", strerror(errno));\n\t\treturn -1;\n\t}\n\n\tpid = fork();\n\tif (pid < 0) {\n\t\tfprintf(stderr, \"run daemon process failed, fork failed, %s\\n\", strerror(errno));\n\t\tclose(fds[0]);\n\t\tclose(fds[1]);\n\t\treturn -1;\n\t} else if (pid > 0) {\n\t\tstruct pollfd pfd;\n\t\tint ret = 0;\n\n\t\tclose(fds[1]);\n\n\t\tpfd.fd = fds[0];\n\t\tpfd.events = POLLIN;\n\t\tpfd.revents = 0;\n\n\t\tdo {\n\t\t\tret = poll(&pfd, 1, 3000);\n\t\t\tif (ret <= 0) {\n\t\t\t\tfprintf(stderr, \"run daemon process failed, wait child timeout, kill child.\\n\");\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (!(pfd.revents & POLLIN)) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tstruct daemon_msg msg;\n\n\t\t\tret = read(fds[0], &msg, sizeof(msg));\n\t\t\tif (ret != sizeof(msg)) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\n\t\t\tif (msg.type == DAEMON_MSG_KEEPALIVE) {\n\t\t\t\tcontinue;\n\t\t\t} else if (msg.type == DAEMON_MSG_DAEMON_PID) {\n\t\t\t\tpid = msg.value;\n\t\t\t\tcontinue;\n\t\t\t} else if (msg.type == DAEMON_MSG_KICKOFF) {\n\t\t\t\tif (wstatus != NULL) {\n\t\t\t\t\t*wstatus = msg.value;\n\t\t\t\t}\n\t\t\t\treturn DAEMON_RET_PARENT_OK;\n\t\t\t} else {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t} while (1);\n\n\t\treturn DAEMON_RET_ERR;\n\t}\n\n\tsetsid();\n\n\tpid = fork();\n\tif (pid < 0) {\n\t\tfprintf(stderr, \"double fork failed, %s\\n\", strerror(errno));\n\t\t_exit(1);\n\t} else if (pid > 0) {\n\t\tstruct daemon_msg msg;\n\t\tint unused __attribute__((unused));\n\t\tmsg.type = DAEMON_MSG_DAEMON_PID;\n\t\tmsg.value = pid;\n\t\tunused = write(fds[1], &msg, sizeof(msg));\n\t\t_exit(0);\n\t}\n\n\tumask(0);\n\tif (chdir(\"/\") != 0) {\n\t\tgoto errout;\n\t}\n\tclose(fds[0]);\n\n\tdaemon_fd = fds[1];\n\treturn DAEMON_RET_CHILD_OK;\nerrout:\n\tkill(pid, SIGKILL);\n\tif (wstatus != NULL) {\n\t\t*wstatus = -1;\n\t}\n\treturn DAEMON_RET_ERR;\n}\n\nint create_pid_file(const char *pid_file)\n{\n\tint fd = 0;\n\tint flags = 0;\n\tchar buff[TMP_BUFF_LEN_32];\n\tint unused __attribute__((unused));\n\n\t/*  create pid file, and lock this file */\n\tfd = open(pid_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);\n\tif (fd == -1) {\n\t\tfprintf(stderr, \"create pid file %s failed, %s\\n\", pid_file, strerror(errno));\n\t\treturn -1;\n\t}\n\n\tflags = fcntl(fd, F_GETFD);\n\tif (flags < 0) {\n\t\tfprintf(stderr, \"Could not get flags for PID file %s\\n\", pid_file);\n\t\tgoto errout;\n\t}\n\n\tflags |= FD_CLOEXEC;\n\tif (fcntl(fd, F_SETFD, flags) == -1) {\n\t\tfprintf(stderr, \"Could not set flags for PID file %s\\n\", pid_file);\n\t\tgoto errout;\n\t}\n\n\tif (lockf(fd, F_TLOCK, 0) < 0) {\n\t\tmemset(buff, 0, TMP_BUFF_LEN_32);\n\t\tif (read(fd, buff, TMP_BUFF_LEN_32) <= 0) {\n\t\t\tbuff[0] = '\\0';\n\t\t}\n\t\tfprintf(stderr, \"Server is already running, pid is %s\", buff);\n\t\tgoto errout;\n\t}\n\n\tunused = ftruncate(fd, 0);\n\tsnprintf(buff, TMP_BUFF_LEN_32, \"%d\\n\", getpid());\n\n\tif (write(fd, buff, strnlen(buff, TMP_BUFF_LEN_32)) < 0) {\n\t\tfprintf(stderr, \"write pid to file failed, %s.\\n\", strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tif (pidfile_fd > 0) {\n\t\tclose(pidfile_fd);\n\t}\n\n\tpidfile_fd = fd;\n\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\treturn -1;\n}"
  },
  {
    "path": "src/utils/dns_debug.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/dns.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n\n#include <arpa/inet.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <netinet/in.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/socket.h>\n#include <sys/time.h>\n#include <unistd.h>\n\n#define BUFF_SZ 1024\n#define PACKET_BUF_SIZE 8192\n#define PACKET_MAGIC 0X11040918\n\nint write_file(const char *filename, void *data, int data_len)\n{\n\tint fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644);\n\tif (fd < 0) {\n\t\treturn -1;\n\t}\n\n\tint len = write(fd, data, data_len);\n\tif (len < 0) {\n\t\tgoto errout;\n\t}\n\n\tclose(fd);\n\treturn 0;\nerrout:\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\treturn -1;\n}\n\nint dns_packet_save(const char *dir, const char *type, const char *from, const void *packet, int packet_len)\n{\n\tchar *data = NULL;\n\tint data_len = 0;\n\tchar filename[BUFF_SZ];\n\tchar time_s[BUFF_SZ];\n\tint ret = -1;\n\n\tstruct tm *ptm;\n\tstruct tm tm;\n\tstruct timeval tm_val;\n\tstruct stat sb;\n\n\tif (stat(dir, &sb) != 0) {\n\t\tmkdir(dir, 0750);\n\t}\n\n\tif (gettimeofday(&tm_val, NULL) != 0) {\n\t\treturn -1;\n\t}\n\n\tptm = localtime_r(&tm_val.tv_sec, &tm);\n\tif (ptm == NULL) {\n\t\treturn -1;\n\t}\n\n\tsnprintf(time_s, sizeof(time_s) - 1, \"%.4d-%.2d-%.2d %.2d:%.2d:%.2d.%.3d\", ptm->tm_year + 1900, ptm->tm_mon + 1,\n\t\t\t ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, (int)(tm_val.tv_usec / 1000));\n\tsnprintf(filename, sizeof(filename) - 1, \"%s/%s-%.4d%.2d%.2d-%.2d%.2d%.2d%.3d.packet\", dir, type,\n\t\t\t ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec,\n\t\t\t (int)(tm_val.tv_usec / 1000));\n\n\tdata = malloc(PACKET_BUF_SIZE);\n\tif (data == NULL) {\n\t\treturn -1;\n\t}\n\n\tdata_len = snprintf(data, PACKET_BUF_SIZE,\n\t\t\t\t\t\t\"type: %s\\n\"\n\t\t\t\t\t\t\"from: %s\\n\"\n\t\t\t\t\t\t\"time: %s\\n\"\n\t\t\t\t\t\t\"packet-len: %d\\n\",\n\t\t\t\t\t\ttype, from, time_s, packet_len);\n\tif (data_len <= 0 || data_len >= PACKET_BUF_SIZE) {\n\t\tgoto out;\n\t}\n\n\tdata[data_len] = 0;\n\tdata_len++;\n\tuint32_t magic = htonl(PACKET_MAGIC);\n\tmemcpy(data + data_len, &magic, sizeof(magic));\n\tdata_len += sizeof(magic);\n\tint len_in_h = htonl(packet_len);\n\tmemcpy(data + data_len, &len_in_h, sizeof(len_in_h));\n\tdata_len += 4;\n\tmemcpy(data + data_len, packet, packet_len);\n\tdata_len += packet_len;\n\n\tret = write_file(filename, data, data_len);\n\tif (ret != 0) {\n\t\tgoto out;\n\t}\n\n\tret = 0;\nout:\n\tif (data) {\n\t\tfree(data);\n\t}\n\n\treturn ret;\n}\n\n#if defined(DEBUG) || defined(TEST)\nstruct _dns_read_packet_info {\n\tint data_len;\n\tint message_len;\n\tchar *message;\n\tint packet_len;\n\tuint8_t *packet;\n\tuint8_t data[0];\n};\n\nstatic struct _dns_read_packet_info *_dns_read_packet_file(const char *packet_file)\n{\n\tstruct _dns_read_packet_info *info = NULL;\n\tint fd = -1;\n\tint len = 0;\n\tint message_len = 0;\n\tuint8_t *ptr = NULL;\n\n\tinfo = malloc(sizeof(struct _dns_read_packet_info) + PACKET_BUF_SIZE);\n\tfd = open(packet_file, O_RDONLY);\n\tif (fd < 0) {\n\t\tprintf(\"open file %s failed, %s\\n\", packet_file, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tlen = read(fd, info->data, PACKET_BUF_SIZE);\n\tif (len < 0) {\n\t\tprintf(\"read file %s failed, %s\\n\", packet_file, strerror(errno));\n\t\tgoto errout;\n\t}\n\n\tmessage_len = strnlen((char *)info->data, PACKET_BUF_SIZE);\n\tif (message_len >= 512 || message_len >= len) {\n\t\tprintf(\"invalid packet file, bad message len\\n\");\n\t\tgoto errout;\n\t}\n\n\tinfo->message_len = message_len;\n\tinfo->message = (char *)info->data;\n\n\tptr = info->data + message_len + 1;\n\tuint32_t magic = 0;\n\tif (ptr - (uint8_t *)info + sizeof(magic) >= (size_t)len) {\n\t\tprintf(\"invalid packet file, magic length is invalid.\\n\");\n\t\tgoto errout;\n\t}\n\n\tmemcpy(&magic, ptr, sizeof(magic));\n\tif (magic != htonl(PACKET_MAGIC)) {\n\t\tprintf(\"invalid packet file, bad magic\\n\");\n\t\tgoto errout;\n\t}\n\tptr += sizeof(magic);\n\n\tuint32_t packet_len = 0;\n\tif (ptr - info->data + sizeof(packet_len) >= (size_t)len) {\n\t\tprintf(\"invalid packet file, packet length is invalid.\\n\");\n\t\tgoto errout;\n\t}\n\n\tmemcpy(&packet_len, ptr, sizeof(packet_len));\n\tpacket_len = ntohl(packet_len);\n\tptr += sizeof(packet_len);\n\tif (packet_len != (size_t)len - (ptr - info->data)) {\n\t\tprintf(\"invalid packet file, packet length is invalid\\n\");\n\t\tgoto errout;\n\t}\n\n\tinfo->packet_len = packet_len;\n\tinfo->packet = ptr;\n\n\tclose(fd);\n\treturn info;\nerrout:\n\n\tif (fd > 0) {\n\t\tclose(fd);\n\t}\n\n\tif (info) {\n\t\tfree(info);\n\t}\n\n\treturn NULL;\n}\n\nstatic int _dns_debug_display(struct dns_packet *packet)\n{\n\tint i = 0;\n\tint j = 0;\n\tint ttl = 0;\n\tstruct dns_rrs *rrs = NULL;\n\tint rr_count = 0;\n\tchar req_host[MAX_IP_LEN];\n\tint ret;\n\n\tfor (j = 1; j < DNS_RRS_OPT; j++) {\n\t\trrs = dns_get_rrs_start(packet, j, &rr_count);\n\t\tprintf(\"section: %d\\n\", j);\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\t\tswitch (rrs->type) {\n\t\t\tcase DNS_T_A: {\n\t\t\t\tunsigned char addr[4];\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\t/* get A result */\n\t\t\t\tdns_get_A(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);\n\t\t\t\treq_host[0] = '\\0';\n\t\t\t\tinet_ntop(AF_INET, addr, req_host, sizeof(req_host));\n\t\t\t\tprintf(\"domain: %s A: %s TTL: %d\\n\", name, req_host, ttl);\n\t\t\t} break;\n\t\t\tcase DNS_T_AAAA: {\n\t\t\t\tunsigned char addr[16];\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tdns_get_AAAA(rrs, name, DNS_MAX_CNAME_LEN, &ttl, addr);\n\t\t\t\treq_host[0] = '\\0';\n\t\t\t\tinet_ntop(AF_INET6, addr, req_host, sizeof(req_host));\n\t\t\t\tprintf(\"domain: %s AAAA: %s TTL:%d\\n\", name, req_host, ttl);\n\t\t\t} break;\n\t\t\tcase DNS_T_SRV: {\n\t\t\t\tunsigned short priority = 0;\n\t\t\t\tunsigned short weight = 0;\n\t\t\t\tunsigned short port = 0;\n\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tchar target[DNS_MAX_CNAME_LEN];\n\n\t\t\t\tret = dns_get_SRV(rrs, name, DNS_MAX_CNAME_LEN, &ttl, &priority, &weight, &port, target,\n\t\t\t\t\t\t\t\t  DNS_MAX_CNAME_LEN);\n\t\t\t\tif (ret < 0) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"decode SRV failed, %s\", name);\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\tprintf(\"domain: %s SRV: %s TTL: %d priority: %d weight: %d port: %d\\n\", name, target, ttl, priority,\n\t\t\t\t\t   weight, port);\n\t\t\t} break;\n\t\t\tcase DNS_T_SVCB:\n\t\t\tcase DNS_T_HTTPS: {\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tchar target[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tstruct dns_svcparam *p = NULL;\n\t\t\t\tint priority = 0;\n\n\t\t\t\tret = dns_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,\n\t\t\t\t\t\t\t\t\t\t\t\t  DNS_MAX_CNAME_LEN);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\tprintf(\"get HTTPS svcparm failed\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tprintf(\"domain: %s HTTPS: %s TTL: %d priority: %d\\n\", name, target, ttl, priority);\n\n\t\t\t\tfor (; p; p = dns_svcparm_next(rrs, p)) {\n\t\t\t\t\tswitch (p->key) {\n\t\t\t\t\tcase DNS_HTTPS_T_MANDATORY: {\n\t\t\t\t\t\tprintf(\"  HTTPS: mandatory: %s\\n\", p->value);\n\t\t\t\t\t} break;\n\t\t\t\t\tcase DNS_HTTPS_T_ALPN: {\n\t\t\t\t\t\tchar alph[64] = {0};\n\t\t\t\t\t\tint total_alph_len = 0;\n\t\t\t\t\t\tchar *ptr = (char *)p->value;\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\tint alphlen = *ptr;\n\t\t\t\t\t\t\tmemcpy(alph + total_alph_len, ptr + 1, alphlen);\n\t\t\t\t\t\t\ttotal_alph_len += alphlen;\n\t\t\t\t\t\t\tptr += alphlen + 1;\n\t\t\t\t\t\t\talph[total_alph_len] = ',';\n\t\t\t\t\t\t\ttotal_alph_len++;\n\t\t\t\t\t\t\talph[total_alph_len] = ' ';\n\t\t\t\t\t\t\ttotal_alph_len++;\n\t\t\t\t\t\t} while (ptr - (char *)p->value < p->len);\n\t\t\t\t\t\tif (total_alph_len > 2) {\n\t\t\t\t\t\t\talph[total_alph_len - 2] = '\\0';\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprintf(\"  HTTPS: alpn: %s\\n\", alph);\n\t\t\t\t\t} break;\n\t\t\t\t\tcase DNS_HTTPS_T_NO_DEFAULT_ALPN: {\n\t\t\t\t\t\tprintf(\"  HTTPS: no_default_alpn: %s\\n\", p->value);\n\t\t\t\t\t} break;\n\t\t\t\t\tcase DNS_HTTPS_T_PORT: {\n\t\t\t\t\t\tint port = *(unsigned short *)(p->value);\n\t\t\t\t\t\tprintf(\"  HTTPS: port: %d\\n\", port);\n\t\t\t\t\t} break;\n\t\t\t\t\tcase DNS_HTTPS_T_IPV4HINT: {\n\t\t\t\t\t\tprintf(\"  HTTPS: ipv4hint: %d\\n\", p->len / 4);\n\t\t\t\t\t\tfor (int k = 0; k < p->len / 4; k++) {\n\t\t\t\t\t\t\tchar ip[16] = {0};\n\t\t\t\t\t\t\tinet_ntop(AF_INET, p->value + k * 4, ip, sizeof(ip));\n\t\t\t\t\t\t\tprintf(\"    ipv4: %s\\n\", ip);\n\t\t\t\t\t\t}\n\t\t\t\t\t} break;\n\t\t\t\t\tcase DNS_HTTPS_T_ECH: {\n\t\t\t\t\t\tprintf(\"  HTTPS: ech: \");\n\t\t\t\t\t\tfor (int k = 0; k < p->len; k++) {\n\t\t\t\t\t\t\tprintf(\"%02x \", p->value[k]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprintf(\"\\n\");\n\t\t\t\t\t} break;\n\t\t\t\t\tcase DNS_HTTPS_T_IPV6HINT: {\n\t\t\t\t\t\tprintf(\"  HTTPS: ipv6hint: %d\\n\", p->len / 16);\n\t\t\t\t\t\tfor (int k = 0; k < p->len / 16; k++) {\n\t\t\t\t\t\t\tchar ip[64] = {0};\n\t\t\t\t\t\t\tinet_ntop(AF_INET6, p->value + k * 16, ip, sizeof(ip));\n\t\t\t\t\t\t\tprintf(\"    ipv6: %s\\n\", ip);\n\t\t\t\t\t\t}\n\t\t\t\t\t} break;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} break;\n\t\t\tcase DNS_T_NS: {\n\t\t\t\tchar cname[DNS_MAX_CNAME_LEN];\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tdns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN);\n\t\t\t\tprintf(\"domain: %s TTL: %d NS: %s\\n\", name, ttl, cname);\n\t\t\t} break;\n\t\t\tcase DNS_T_CNAME: {\n\t\t\t\tchar cname[DNS_MAX_CNAME_LEN];\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tdns_get_CNAME(rrs, name, DNS_MAX_CNAME_LEN, &ttl, cname, DNS_MAX_CNAME_LEN);\n\t\t\t\tprintf(\"domain: %s TTL: %d CNAME: %s\\n\", name, ttl, cname);\n\t\t\t} break;\n\t\t\tcase DNS_T_SOA: {\n\t\t\t\tchar name[DNS_MAX_CNAME_LEN] = {0};\n\t\t\t\tstruct dns_soa soa;\n\t\t\t\tdns_get_SOA(rrs, name, 128, &ttl, &soa);\n\t\t\t\tprintf(\"domain: %s SOA: mname: %s, rname: %s, serial: %d, refresh: %d, retry: %d, expire: \"\n\t\t\t\t\t   \"%d, minimum: %d\",\n\t\t\t\t\t   name, soa.mname, soa.rname, soa.serial, soa.refresh, soa.retry, soa.expire, soa.minimum);\n\t\t\t} break;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tprintf(\"\\n\");\n\t}\n\n\trr_count = 0;\n\trrs = dns_get_rrs_start(packet, DNS_RRS_OPT, &rr_count);\n\tif (rr_count <= 0) {\n\t\treturn 0;\n\t}\n\n\tprintf(\"section opt:\\n\");\n\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\tswitch (rrs->type) {\n\t\tcase DNS_OPT_T_TCP_KEEPALIVE: {\n\t\t\tunsigned short idle_timeout = 0;\n\t\t\tret = dns_get_OPT_TCP_KEEPALIVE(rrs, &idle_timeout);\n\t\t\tif (idle_timeout == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tprintf(\"tcp keepalive: %d\\n\", idle_timeout);\n\t\t} break;\n\t\tcase DNS_OPT_T_ECS: {\n\t\t\tstruct dns_opt_ecs ecs;\n\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\tif (ret != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tprintf(\"ecs family: %d, src_prefix: %d, scope_prefix: %d, \", ecs.family, ecs.source_prefix,\n\t\t\t\t   ecs.scope_prefix);\n\t\t\tif (ecs.family == 1) {\n\t\t\t\tchar ip[16] = {0};\n\t\t\t\tinet_ntop(AF_INET, ecs.addr, ip, sizeof(ip));\n\t\t\t\tprintf(\"ecs address: %s\\n\", ip);\n\t\t\t} else if (ecs.family == 2) {\n\t\t\t\tchar ip[64] = {0};\n\t\t\t\tinet_ntop(AF_INET6, ecs.addr, ip, sizeof(ip));\n\t\t\t\tprintf(\"ecs address: %s\\n\", ip);\n\t\t\t}\n\t\t} break;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint dns_packet_debug(const char *packet_file)\n{\n\tstruct _dns_read_packet_info *info = NULL;\n\tchar buff[DNS_PACKSIZE];\n\n\ttlog_set_maxlog_count(0);\n\ttlog_setlogscreen(1);\n\ttlog_setlevel(TLOG_DEBUG);\n\n\tinfo = _dns_read_packet_file(packet_file);\n\tif (info == NULL) {\n\t\tgoto errout;\n\t}\n\n\tconst char *send_env = getenv(\"SMARTDNS_DEBUG_SEND\");\n\tif (send_env != NULL) {\n\t\tchar ip[32];\n\t\tint port = 53;\n\t\tif (parse_ip(send_env, ip, &port) == 0) {\n\t\t\tint sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n\t\t\tif (sockfd > 0) {\n\t\t\t\tstruct sockaddr_in server;\n\t\t\t\tserver.sin_family = AF_INET;\n\t\t\t\tserver.sin_port = htons(port);\n\t\t\t\tserver.sin_addr.s_addr = inet_addr(ip);\n\t\t\t\tsendto(sockfd, info->packet, info->packet_len, 0, (struct sockaddr *)&server, sizeof(server));\n\t\t\t\tclose(sockfd);\n\t\t\t}\n\t\t}\n\t}\n\n\tstruct dns_packet *packet = (struct dns_packet *)buff;\n\tif (dns_decode(packet, DNS_PACKSIZE, info->packet, info->packet_len) != 0) {\n\t\tprintf(\"decode failed.\\n\");\n\t\tgoto errout;\n\t}\n\n\t_dns_debug_display(packet);\n\n\tfree(info);\n\treturn 0;\n\nerrout:\n\tif (info) {\n\t\tfree(info);\n\t}\n\n\treturn -1;\n}\n\n#endif\n"
  },
  {
    "path": "src/utils/ipset.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <linux/netlink.h>\n#include <linux/rtnetlink.h>\n\n#define NFNL_SUBSYS_IPSET 6\n\n#define IPSET_ATTR_DATA 7\n#define IPSET_ATTR_IP 1\n#define IPSET_ATTR_IPADDR_IPV4 1\n#define IPSET_ATTR_IPADDR_IPV6 2\n#define IPSET_ATTR_PROTOCOL 1\n#define IPSET_ATTR_SETNAME 2\n#define IPSET_ATTR_TIMEOUT 6\n#define IPSET_ADD 9\n#define IPSET_DEL 10\n#define IPSET_MAXNAMELEN 32\n#define IPSET_PROTOCOL 6\n\n#ifndef NFNETLINK_V0\n#define NFNETLINK_V0 0\n#endif\n\n#ifndef NLA_F_NESTED\n#define NLA_F_NESTED (1 << 15)\n#endif\n\n#ifndef NLA_F_NET_BYTEORDER\n#define NLA_F_NET_BYTEORDER (1 << 14)\n#endif\n\n#define NETLINK_ALIGN(len) (((len) + 3) & ~(3))\n\n#define BUFF_SZ 1024\n\nstruct ipset_netlink_attr {\n\tunsigned short len;\n\tunsigned short type;\n};\n\nstruct ipset_netlink_msg {\n\tunsigned char family;\n\tunsigned char version;\n\t__be16 res_id;\n};\n\nstatic int ipset_fd;\n\nstatic inline void _ipset_add_attr(struct nlmsghdr *netlink_head, uint16_t type, size_t len, const void *data)\n{\n\tstruct ipset_netlink_attr *attr = (void *)netlink_head + NETLINK_ALIGN(netlink_head->nlmsg_len);\n\tuint16_t payload_len = NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)) + len;\n\tattr->type = type;\n\tattr->len = payload_len;\n\tmemcpy((void *)attr + NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)), data, len);\n\tnetlink_head->nlmsg_len += NETLINK_ALIGN(payload_len);\n}\n\nstatic int _ipset_socket_init(void)\n{\n\tif (ipset_fd > 0) {\n\t\treturn 0;\n\t}\n\n\tipset_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_NETFILTER);\n\n\tif (ipset_fd < 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _ipset_operate(const char *ipset_name, const unsigned char addr[], int addr_len, unsigned long timeout,\n\t\t\t\t\t\t  int operate)\n{\n\tstruct nlmsghdr *netlink_head = NULL;\n\tstruct ipset_netlink_msg *netlink_msg = NULL;\n\tstruct ipset_netlink_attr *nested[3];\n\tchar buffer[BUFF_SZ];\n\tuint8_t proto = 0;\n\tssize_t rc = 0;\n\tint af = 0;\n\tstatic const struct sockaddr_nl snl = {.nl_family = AF_NETLINK};\n\tuint32_t expire = 0;\n\n\tif (addr_len != IPV4_ADDR_LEN && addr_len != IPV6_ADDR_LEN) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tif (addr_len == IPV4_ADDR_LEN) {\n\t\taf = AF_INET;\n\t} else if (addr_len == IPV6_ADDR_LEN) {\n\t\taf = AF_INET6;\n\t} else {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tif (_ipset_socket_init() != 0) {\n\t\treturn -1;\n\t}\n\n\tif (strlen(ipset_name) >= IPSET_MAXNAMELEN) {\n\t\terrno = ENAMETOOLONG;\n\t\treturn -1;\n\t}\n\n\tmemset(buffer, 0, BUFF_SZ);\n\n\tnetlink_head = (struct nlmsghdr *)buffer;\n\tnetlink_head->nlmsg_len = NETLINK_ALIGN(sizeof(struct nlmsghdr));\n\tnetlink_head->nlmsg_type = operate | (NFNL_SUBSYS_IPSET << 8);\n\tnetlink_head->nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE;\n\n\tnetlink_msg = (struct ipset_netlink_msg *)(buffer + netlink_head->nlmsg_len);\n\tnetlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_msg));\n\tnetlink_msg->family = af;\n\tnetlink_msg->version = NFNETLINK_V0;\n\tnetlink_msg->res_id = htons(NFNL_SUBSYS_IPSET);\n\n\tproto = IPSET_PROTOCOL;\n\t_ipset_add_attr(netlink_head, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto);\n\t_ipset_add_attr(netlink_head, IPSET_ATTR_SETNAME, strlen(ipset_name) + 1, ipset_name);\n\n\tnested[0] = (struct ipset_netlink_attr *)(buffer + NETLINK_ALIGN(netlink_head->nlmsg_len));\n\tnetlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_attr));\n\tnested[0]->type = NLA_F_NESTED | IPSET_ATTR_DATA;\n\tnested[1] = (struct ipset_netlink_attr *)(buffer + NETLINK_ALIGN(netlink_head->nlmsg_len));\n\tnetlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_attr));\n\tnested[1]->type = NLA_F_NESTED | IPSET_ATTR_IP;\n\n\t_ipset_add_attr(netlink_head,\n\t\t\t\t\t(af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER, addr_len,\n\t\t\t\t\taddr);\n\tnested[1]->len = (void *)buffer + NETLINK_ALIGN(netlink_head->nlmsg_len) - (void *)nested[1];\n\n\tif (timeout > 0) {\n\t\texpire = htonl(timeout);\n\t\t_ipset_add_attr(netlink_head, IPSET_ATTR_TIMEOUT | NLA_F_NET_BYTEORDER, sizeof(expire), &expire);\n\t}\n\n\tnested[0]->len = (void *)buffer + NETLINK_ALIGN(netlink_head->nlmsg_len) - (void *)nested[0];\n\n\tfor (;;) {\n\t\trc = sendto(ipset_fd, buffer, netlink_head->nlmsg_len, 0, (const struct sockaddr *)&snl, sizeof(snl));\n\t\tif (rc >= 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {\n\t\t\tstruct timespec waiter;\n\t\t\twaiter.tv_sec = 0;\n\t\t\twaiter.tv_nsec = 10000;\n\t\t\tnanosleep(&waiter, NULL);\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\treturn rc;\n}\n\nint ipset_add(const char *ipset_name, const unsigned char addr[], int addr_len, unsigned long timeout)\n{\n\treturn _ipset_operate(ipset_name, addr, addr_len, timeout, IPSET_ADD);\n}\n\nint ipset_del(const char *ipset_name, const unsigned char addr[], int addr_len)\n{\n\treturn _ipset_operate(ipset_name, addr, addr_len, 0, IPSET_DEL);\n}"
  },
  {
    "path": "src/utils/misc.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"smartdns/dns.h\"\n#include \"smartdns/util.h\"\n\n#include <libgen.h>\n#include <linux/limits.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <sys/statvfs.h>\n#include <sys/sysinfo.h>\n#include <sys/time.h>\n#include <time.h>\n#include <unistd.h>\n\nunsigned long get_tick_count(void)\n{\n\tstruct timespec ts;\n\n\tclock_gettime(CLOCK_MONOTONIC, &ts);\n\n\treturn (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);\n}\n\nunsigned long long get_utc_time_ms(void)\n{\n\tstruct timeval tv;\n\tgettimeofday(&tv, NULL);\n\n\tunsigned long long millisecondsSinceEpoch =\n\t\t(unsigned long long)(tv.tv_sec) * 1000 + (unsigned long long)(tv.tv_usec) / 1000;\n\n\treturn millisecondsSinceEpoch;\n}\n\nchar *dir_name(char *path)\n{\n\tif (strstr(path, \"/\") == NULL) {\n\t\tsafe_strncpy(path, \"./\", PATH_MAX);\n\t\treturn path;\n\t}\n\n\treturn dirname(path);\n}\n\nint create_dir_with_perm(const char *dir_path)\n{\n\tuid_t uid = 0;\n\tgid_t gid = 0;\n\tstruct stat sb;\n\tchar data_dir[PATH_MAX] = {0};\n\tint unused __attribute__((unused)) = 0;\n\n\tsafe_strncpy(data_dir, dir_path, PATH_MAX);\n\tdir_name(data_dir);\n\n\tif (get_uid_gid(&uid, &gid) != 0) {\n\t\treturn -1;\n\t}\n\n\tif (stat(data_dir, &sb) == 0) {\n\t\tif (sb.st_uid == uid && sb.st_gid == gid && (sb.st_mode & 0700) == 0700) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (sb.st_gid == gid && (sb.st_mode & 0070) == 0070) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (sb.st_uid != uid && sb.st_gid != gid && (sb.st_mode & 0007) == 0007) {\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tmkdir(data_dir, 0750);\n\tif (chown(data_dir, uid, gid) != 0) {\n\t\treturn -2;\n\t}\n\n\tunused = chmod(data_dir, 0750);\n\tunused = chown(dir_path, uid, gid);\n\n\treturn 0;\n}\n\nchar *reverse_string(char *output, const char *input, int len, int to_lower_case)\n{\n\tchar *begin = output;\n\tif (len <= 0) {\n\t\t*output = 0;\n\t\treturn output;\n\t}\n\n\tlen--;\n\twhile (len >= 0) {\n\t\t*output = *(input + len);\n\t\tif (to_lower_case) {\n\t\t\tif (*output >= 'A' && *output <= 'Z') {\n\t\t\t\t/* To lower case */\n\t\t\t\t*output = *output + 32;\n\t\t\t}\n\t\t}\n\t\toutput++;\n\t\tlen--;\n\t}\n\n\t*output = 0;\n\n\treturn begin;\n}\n\nchar *to_lower_case(char *output, const char *input, int len)\n{\n\tchar *begin = output;\n\tint i = 0;\n\tif (len <= 0) {\n\t\t*output = 0;\n\t\treturn output;\n\t}\n\n\tlen--;\n\twhile (i < len && *(input + i) != '\\0') {\n\t\t*output = *(input + i);\n\t\tif (*output >= 'A' && *output <= 'Z') {\n\t\t\t/* To lower case */\n\t\t\t*output = *output + 32;\n\t\t}\n\t\toutput++;\n\t\ti++;\n\t}\n\n\t*output = 0;\n\n\treturn begin;\n}\n\nint full_path(char *normalized_path, int normalized_path_len, const char *path)\n{\n\tconst char *p = path;\n\n\tif (path == NULL || normalized_path == NULL) {\n\t\treturn -1;\n\t}\n\n\twhile (*p == ' ') {\n\t\tp++;\n\t}\n\n\tif (*p == '\\0' || *p == '/') {\n\t\treturn -1;\n\t}\n\n\tchar buf[PATH_MAX];\n\tsnprintf(normalized_path, normalized_path_len, \"%s/%s\", getcwd(buf, sizeof(buf)), path);\n\treturn 0;\n}\n\nvoid get_compiled_time(struct tm *tm)\n{\n\tchar s_month[5];\n\tint month = 0;\n\tint day = 0;\n\tint year = 0;\n\tint hour = 0;\n\tint min = 0;\n\tint sec = 0;\n\tstatic const char *month_names = \"JanFebMarAprMayJunJulAugSepOctNovDec\";\n\n\tsscanf(__DATE__, \"%4s %d %d\", s_month, &day, &year);\n\tmonth = (strstr(month_names, s_month) - month_names) / 3;\n\tsscanf(__TIME__, \"%d:%d:%d\", &hour, &min, &sec);\n\ttm->tm_year = year - 1900;\n\ttm->tm_mon = month;\n\ttm->tm_mday = day;\n\ttm->tm_isdst = -1;\n\ttm->tm_hour = hour;\n\ttm->tm_min = min;\n\ttm->tm_sec = sec;\n}\n\nunsigned long get_system_mem_size(void)\n{\n\tstruct sysinfo memInfo;\n\tsysinfo(&memInfo);\n\tlong long totalMem = memInfo.totalram;\n\ttotalMem *= memInfo.mem_unit;\n\n\treturn totalMem;\n}\n\nint is_numeric(const char *str)\n{\n\twhile (*str != '\\0') {\n\t\tif (*str < '0' || *str > '9') {\n\t\t\treturn -1;\n\t\t}\n\t\tstr++;\n\t}\n\treturn 0;\n}\n\nint has_network_raw_cap(void)\n{\n\tint fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);\n\tif (fd < 0) {\n\t\treturn 0;\n\t}\n\n\tclose(fd);\n\treturn 1;\n}\n\nuint64_t get_free_space(const char *path)\n{\n\tuint64_t size = 0;\n\tstruct statvfs buf;\n\tif (statvfs(path, &buf) != 0) {\n\t\treturn 0;\n\t}\n\n\tsize = (uint64_t)buf.f_frsize * buf.f_bavail;\n\n\treturn size;\n}\n\nint parser_mac_address(const char *in_mac, uint8_t mac[6])\n{\n\tint fileld_num = 0;\n\n\tif (in_mac == NULL) {\n\t\treturn -1;\n\t}\n\n\tfileld_num =\n\t\tsscanf(in_mac, \"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx\", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);\n\tif (fileld_num == 6) {\n\t\treturn 0;\n\t}\n\n\tfileld_num =\n\t\tsscanf(in_mac, \"%2hhx-%2hhx-%2hhx-%2hhx-%2hhx-%2hhx\", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);\n\tif (fileld_num == 6) {\n\t\treturn 0;\n\t}\n\n\treturn -1;\n}\n\nint set_http_host(const char *uri_host, int port, int default_port, char *host)\n{\n\tint is_ipv6;\n\n\tif (uri_host == NULL || port <= 0 || host == NULL) {\n\t\treturn -1;\n\t}\n\n\tis_ipv6 = check_is_ipv6(uri_host);\n\tif (port == default_port) {\n\t\tsnprintf(host, DNS_MAX_CNAME_LEN, \"%s%s%s\", is_ipv6 == 0 ? \"[\" : \"\", uri_host, is_ipv6 == 0 ? \"]\" : \"\");\n\t} else {\n\t\tsnprintf(host, DNS_MAX_CNAME_LEN, \"%s%s%s:%d\", is_ipv6 == 0 ? \"[\" : \"\", uri_host, is_ipv6 == 0 ? \"]\" : \"\",\n\t\t\t\t port);\n\t}\n\treturn 0;\n}\n\nint decode_hex(int ch)\n{\n\tif ('0' <= ch && ch <= '9') {\n\t\treturn ch - '0';\n\t} else if ('A' <= ch && ch <= 'F') {\n\t\treturn ch - 'A' + 0xa;\n\t} else if ('a' <= ch && ch <= 'f') {\n\t\treturn ch - 'a' + 0xa;\n\t} else {\n\t\treturn -1;\n\t}\n}\n"
  },
  {
    "path": "src/utils/neighbors.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/util.h\"\n\n#include <errno.h>\n#include <linux/netlink.h>\n#include <linux/rtnetlink.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nstatic int netlink_neighbor_fd;\n\nint netlink_get_neighbors(int family, const uint8_t *target_ip, int target_ip_len,\n\t\t\t\t\t\t  int (*callback)(const uint8_t *net_addr, int net_addr_len, const uint8_t mac[6], void *arg),\n\t\t\t\t\t\t  void *arg)\n{\n\tif (netlink_neighbor_fd <= 0) {\n\t\tnetlink_neighbor_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_ROUTE);\n\t\tif (netlink_neighbor_fd < 0) {\n\t\t\terrno = EINVAL;\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tstruct nlmsghdr *nlh;\n\tstruct ndmsg *ndm;\n\tchar buf[1024 * 16];\n\tstruct iovec iov = {buf, sizeof(buf)};\n\tstruct sockaddr_nl sa;\n\tstruct msghdr msg;\n\tint len;\n\tint ret = 0;\n\tint send_count = 0;\n\n\tmemset(buf, 0, sizeof(buf));\n\tmemset(&sa, 0, sizeof(sa));\n\tmemset(&msg, 0, sizeof(msg));\n\t\n\tsa.nl_family = AF_NETLINK;\n\tmsg.msg_name = &sa;\n\tmsg.msg_namelen = sizeof(sa);\n\tmsg.msg_iov = &iov;\n\tmsg.msg_iovlen = 1;\n\n\tnlh = (struct nlmsghdr *)buf;\n\tnlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg));\n\tnlh->nlmsg_type = RTM_GETNEIGH;\n\tnlh->nlmsg_flags = NLM_F_REQUEST;\n\tnlh->nlmsg_seq = time(NULL);\n\tnlh->nlmsg_pid = getpid();\n\n\tndm = NLMSG_DATA(nlh);\n\tmemset(ndm, 0, sizeof(struct ndmsg));\n\tndm->ndm_family = family;\n\n\tif (target_ip_len > 0 && target_ip != NULL) {\n\t\tstruct rtattr *rta = (struct rtattr *)(((char *)nlh) + NLMSG_ALIGN(nlh->nlmsg_len));\n\t\trta->rta_type = NDA_DST;\n\t\trta->rta_len = RTA_LENGTH(target_ip_len);\n\t\tmemcpy(RTA_DATA(rta), target_ip, target_ip_len);\n\t\tnlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len);\n\t}\n\n\twhile (1) {\n\t\tif (send_count > 5) {\n\t\t\terrno = ETIMEDOUT;\n\t\t\treturn -1;\n\t\t}\n\n\t\tsend_count++;\n\t\tif (send(netlink_neighbor_fd, buf, NLMSG_SPACE(sizeof(struct ndmsg)), 0) < 0) {\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {\n\t\t\t\tstruct timespec waiter;\n\t\t\t\twaiter.tv_sec = 0;\n\t\t\t\twaiter.tv_nsec = 500000;\n\t\t\t\tnanosleep(&waiter, NULL);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tclose(netlink_neighbor_fd);\n\t\t\tnetlink_neighbor_fd = -1;\n\t\t\treturn -1;\n\t\t}\n\n\t\tbreak;\n\t}\n\n\tint is_received = 0;\n\tint recv_count = 0;\n\twhile (1) {\n\t\trecv_count++;\n\t\tlen = recvmsg(netlink_neighbor_fd, &msg, 0);\n\t\tif (len < 0) {\n\t\t\tif (recv_count > 5 && is_received == 0) {\n\t\t\t\terrno = ETIMEDOUT;\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tif (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {\n\t\t\t\tif (is_received) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tstruct timespec waiter;\n\t\t\t\twaiter.tv_sec = 0;\n\t\t\t\twaiter.tv_nsec = 500000;\n\t\t\t\tnanosleep(&waiter, NULL);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (ret != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tis_received = 1;\n\t\tuint32_t nlh_len = len;\n\t\tfor (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, nlh_len); nlh = NLMSG_NEXT(nlh, nlh_len)) {\n\t\t\tndm = NLMSG_DATA(nlh);\n\t\t\tstruct rtattr *rta = RTM_RTA(ndm);\n\t\t\tconst uint8_t *mac = NULL;\n\t\t\tconst uint8_t *net_addr = NULL;\n\t\t\tint net_addr_len = 0;\n\t\t\tunsigned int rta_len = RTM_PAYLOAD(nlh);\n\n\t\t\tif (rta_len > (sizeof(buf) - ((char *)rta - buf))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (; RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) {\n\t\t\t\tif (rta->rta_type == NDA_DST) {\n\t\t\t\t\tif (ndm->ndm_family == AF_INET) {\n\t\t\t\t\t\tstruct in_addr *addr = RTA_DATA(rta);\n\t\t\t\t\t\tif (IN_MULTICAST(ntohl(addr->s_addr))) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (ntohl(addr->s_addr) == 0) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tnet_addr = (uint8_t *)&addr->s_addr;\n\t\t\t\t\t\tnet_addr_len = IPV4_ADDR_LEN;\n\t\t\t\t\t} else if (ndm->ndm_family == AF_INET6) {\n\t\t\t\t\t\tstruct in6_addr *addr = RTA_DATA(rta);\n\t\t\t\t\t\tif (IN6_IS_ADDR_MC_NODELOCAL(addr)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (IN6_IS_ADDR_MC_LINKLOCAL(addr)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (IN6_IS_ADDR_MC_SITELOCAL(addr)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (IN6_IS_ADDR_UNSPECIFIED(addr)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tnet_addr = addr->s6_addr;\n\t\t\t\t\t\tnet_addr_len = IPV6_ADDR_LEN;\n\t\t\t\t\t}\n\t\t\t\t} else if (rta->rta_type == NDA_LLADDR) {\n\t\t\t\t\tmac = RTA_DATA(rta);\n\t\t\t\t\tif (mac[0] == 0 && mac[1] == 0 && mac[2] == 0 && mac[3] == 0 && mac[4] == 0 && mac[5] == 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (net_addr != NULL && mac != NULL) {\n\t\t\t\tret = callback(net_addr, net_addr_len, mac, arg);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ret;\n}"
  },
  {
    "path": "src/utils/net.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n#define _GNU_SOURCE\n\n#include \"smartdns/dns.h\"\n#include \"smartdns/lib/jhash.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n\n#include <arpa/inet.h>\n#include <ctype.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <linux/limits.h>\n#include <netdb.h>\n#include <netinet/in.h>\n#include <netinet/tcp.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/socket.h>\n#include <sys/types.h>\n\nchar *get_host_by_addr(char *host, int maxsize, const struct sockaddr *addr)\n{\n\tstruct sockaddr_storage *addr_store = (struct sockaddr_storage *)addr;\n\thost[0] = 0;\n\tswitch (addr_store->ss_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)addr;\n\t\tinet_ntop(AF_INET, &addr_in->sin_addr, host, maxsize);\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tstruct sockaddr_in addr_in4;\n\t\t\tmemset(&addr_in4, 0, sizeof(addr_in4));\n\t\t\tmemcpy(&addr_in4.sin_addr.s_addr, addr_in6->sin6_addr.s6_addr + 12, sizeof(addr_in4.sin_addr.s_addr));\n\t\t\tinet_ntop(AF_INET, &addr_in4.sin_addr, host, maxsize);\n\t\t} else {\n\t\t\tinet_ntop(AF_INET6, &addr_in6->sin6_addr, host, maxsize);\n\t\t}\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\treturn host;\nerrout:\n\treturn NULL;\n}\n\nint generate_random_addr(unsigned char *addr, int addr_len, int mask)\n{\n\tif (mask / 8 > addr_len) {\n\t\treturn -1;\n\t}\n\n\tint offset = mask / 8;\n\tint bit = 0;\n\n\tfor (int i = offset; i < addr_len; i++) {\n\t\tbit = 0xFF;\n\t\tif (i == offset) {\n\t\t\tbit = ~(0xFF << (8 - mask % 8)) & 0xFF;\n\t\t}\n\t\taddr[i] = jhash(&addr[i], 1, 0) & bit;\n\t}\n\n\treturn 0;\n}\n\nint generate_addr_map(const unsigned char *addr_from, const unsigned char *addr_to, unsigned char *addr_out,\n\t\t\t\t\t  int addr_len, int mask)\n{\n\tif ((mask / 8) >= addr_len) {\n\t\tif (mask % 8 != 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tint offset = mask / 8;\n\tint bit = mask % 8;\n\tfor (int i = 0; i < offset; i++) {\n\t\taddr_out[i] = addr_to[i];\n\t}\n\n\tif (bit != 0) {\n\t\tint mask1 = 0xFF >> bit;\n\t\tint mask2 = (0xFF << (8 - bit)) & 0xFF;\n\t\taddr_out[offset] = addr_from[offset] & mask1;\n\t\taddr_out[offset] |= addr_to[offset] & mask2;\n\t\toffset = offset + 1;\n\t}\n\n\tfor (int i = offset; i < addr_len; i++) {\n\t\taddr_out[i] = addr_from[i];\n\t}\n\n\treturn 0;\n}\n\nint is_private_addr(const unsigned char *addr, int addr_len)\n{\n\tif (addr_len == IPV4_ADDR_LEN) {\n\t\tif (addr[0] == 10) {\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (addr[0] == 172 && addr[1] >= 16 && addr[1] <= 31) {\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (addr[0] == 192 && addr[1] == 168) {\n\t\t\treturn 1;\n\t\t}\n\t} else if (addr_len == IPV6_ADDR_LEN) {\n\t\tif (addr[0] == 0xFD) {\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (addr[0] == 0xFE && addr[1] == 0x80) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint is_private_addr_sockaddr(const struct sockaddr *addr, socklen_t addr_len)\n{\n\tswitch (addr->sa_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)addr;\n\t\treturn is_private_addr((const unsigned char *)&addr_in->sin_addr.s_addr, IPV4_ADDR_LEN);\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\treturn is_private_addr(addr_in6->sin6_addr.s6_addr + 12, IPV4_ADDR_LEN);\n\t\t} else {\n\t\t\treturn is_private_addr(addr_in6->sin6_addr.s6_addr, IPV6_ADDR_LEN);\n\t\t}\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\nerrout:\n\treturn 0;\n}\n\nint getaddr_by_host(const char *host, struct sockaddr *addr, socklen_t *addr_len)\n{\n\tstruct addrinfo hints;\n\tstruct addrinfo *result = NULL;\n\tint ret = 0;\n\n\tmemset(&hints, 0, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = SOCK_STREAM;\n\n\tret = getaddrinfo(host, \"53\", &hints, &result);\n\tif (ret != 0) {\n\t\tgoto errout;\n\t}\n\n\tif (result->ai_addrlen > *addr_len) {\n\t\tresult->ai_addrlen = *addr_len;\n\t}\n\n\taddr->sa_family = result->ai_family;\n\tmemcpy(addr, result->ai_addr, result->ai_addrlen);\n\t*addr_len = result->ai_addrlen;\n\n\tfreeaddrinfo(result);\n\n\treturn 0;\nerrout:\n\tif (result) {\n\t\tfreeaddrinfo(result);\n\t}\n\treturn -1;\n}\n\nint get_raw_addr_by_sockaddr(const struct sockaddr_storage *addr, int addr_len, unsigned char *raw_addr,\n\t\t\t\t\t\t\t int *raw_addr_len)\n{\n\tswitch (addr->ss_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)addr;\n\t\tif (*raw_addr_len < DNS_RR_A_LEN) {\n\t\t\tgoto errout;\n\t\t}\n\t\tmemcpy(raw_addr, &addr_in->sin_addr.s_addr, DNS_RR_A_LEN);\n\t\t*raw_addr_len = DNS_RR_A_LEN;\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)addr;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tif (*raw_addr_len < DNS_RR_A_LEN) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tmemcpy(raw_addr, addr_in6->sin6_addr.s6_addr + 12, DNS_RR_A_LEN);\n\t\t\t*raw_addr_len = DNS_RR_A_LEN;\n\t\t} else {\n\t\t\tif (*raw_addr_len < DNS_RR_AAAA_LEN) {\n\t\t\t\tgoto errout;\n\t\t\t}\n\t\t\tmemcpy(raw_addr, addr_in6->sin6_addr.s6_addr, DNS_RR_AAAA_LEN);\n\t\t\t*raw_addr_len = DNS_RR_AAAA_LEN;\n\t\t}\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint get_raw_addr_by_ip(const char *ip, unsigned char *raw_addr, int *raw_addr_len)\n{\n\tstruct sockaddr_storage addr;\n\tsocklen_t addr_len = sizeof(addr);\n\n\tif (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) {\n\t\tgoto errout;\n\t}\n\n\treturn get_raw_addr_by_sockaddr(&addr, addr_len, raw_addr, raw_addr_len);\nerrout:\n\treturn -1;\n}\n\nint getsocket_inet(int fd, struct sockaddr *addr, socklen_t *addr_len)\n{\n\tstruct sockaddr_storage addr_store;\n\tsocklen_t addr_store_len = sizeof(addr_store);\n\tif (getsockname(fd, (struct sockaddr *)&addr_store, &addr_store_len) != 0) {\n\t\tgoto errout;\n\t}\n\n\tswitch (addr_store.ss_family) {\n\tcase AF_INET: {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr_in = (struct sockaddr_in *)&addr_store;\n\t\taddr_in->sin_family = AF_INET;\n\t\t*addr_len = sizeof(struct sockaddr_in);\n\t\tmemcpy(addr, addr_in, sizeof(struct sockaddr_in));\n\t} break;\n\tcase AF_INET6: {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr_in6 = (struct sockaddr_in6 *)&addr_store;\n\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\tstruct sockaddr_in addr_in4;\n\t\t\tmemset(&addr_in4, 0, sizeof(addr_in4));\n\t\t\tmemcpy(&addr_in4.sin_addr.s_addr, addr_in6->sin6_addr.s6_addr + 12, sizeof(addr_in4.sin_addr.s_addr));\n\t\t\taddr_in4.sin_family = AF_INET;\n\t\t\taddr_in4.sin_port = 0;\n\t\t\t*addr_len = sizeof(struct sockaddr_in);\n\t\t\tmemcpy(addr, &addr_in4, sizeof(struct sockaddr_in));\n\t\t} else {\n\t\t\taddr_in6->sin6_family = AF_INET6;\n\t\t\t*addr_len = sizeof(struct sockaddr_in6);\n\t\t\tmemcpy(addr, addr_in6, sizeof(struct sockaddr_in6));\n\t\t}\n\t} break;\n\tdefault:\n\t\tgoto errout;\n\t\tbreak;\n\t}\n\treturn 0;\nerrout:\n\treturn -1;\n}\n\nint fill_sockaddr_by_ip(unsigned char *ip, int ip_len, int port, struct sockaddr *addr, socklen_t *addr_len)\n{\n\tif (ip == NULL || addr == NULL || addr_len == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (ip_len == IPV4_ADDR_LEN) {\n\t\tstruct sockaddr_in *addr_in = NULL;\n\t\taddr->sa_family = AF_INET;\n\t\taddr_in = (struct sockaddr_in *)addr;\n\t\taddr_in->sin_port = htons(port);\n\t\taddr_in->sin_family = AF_INET;\n\t\tmemcpy(&addr_in->sin_addr.s_addr, ip, ip_len);\n\t\t*addr_len = 16;\n\t} else if (ip_len == IPV6_ADDR_LEN) {\n\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\taddr->sa_family = AF_INET6;\n\t\taddr_in6 = (struct sockaddr_in6 *)addr;\n\t\taddr_in6->sin6_port = htons(port);\n\t\taddr_in6->sin6_family = AF_INET6;\n\t\tmemcpy(addr_in6->sin6_addr.s6_addr, ip, ip_len);\n\t\t*addr_len = 28;\n\t}\n\n\treturn -1;\n}\n\nint check_is_ipv4(const char *ip)\n{\n\tconst char *ptr = ip;\n\tchar c = 0;\n\tint dot_num = 0;\n\tint dig_num = 0;\n\n\twhile ((c = *ptr++) != '\\0') {\n\t\tif (c == '.') {\n\t\t\tdot_num++;\n\t\t\tdig_num = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* check number count of one field */\n\t\tif (dig_num >= 4) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (c >= '0' && c <= '9') {\n\t\t\tdig_num++;\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn -1;\n\t}\n\n\t/* check field number */\n\tif (dot_num != 3) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint check_is_ipv6(const char *ip)\n{\n\tconst char *ptr = ip;\n\tchar c = 0;\n\tint colon_num = 0;\n\tint dig_num = 0;\n\n\twhile ((c = *ptr++) != '\\0') {\n\t\tif (c == '[' || c == ']') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* scope id, end of ipv6 address*/\n\t\tif (c == '%') {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (c == ':') {\n\t\t\tcolon_num++;\n\t\t\tdig_num = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* check number count of one field */\n\t\tif (dig_num >= 5) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tdig_num++;\n\t\tif (c >= '0' && c <= '9') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c >= 'a' && c <= 'f') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c >= 'A' && c <= 'F') {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn -1;\n\t}\n\n\t/* check field number */\n\tif (colon_num > 7) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint check_is_ipaddr(const char *ip)\n{\n\tif (strstr(ip, \".\")) {\n\t\t/* IPV4 */\n\t\treturn check_is_ipv4(ip);\n\t} else if (strstr(ip, \":\")) {\n\t\t/* IPV6 */\n\t\treturn check_is_ipv6(ip);\n\t}\n\treturn -1;\n}\n\nint set_fd_nonblock(int fd, int nonblock)\n{\n\tint ret = 0;\n\tint flags = fcntl(fd, F_GETFL);\n\n\tif (flags == -1) {\n\t\treturn -1;\n\t}\n\n\tflags = (nonblock) ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK);\n\tret = fcntl(fd, F_SETFL, flags);\n\tif (ret == -1) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint set_sock_keepalive(int fd, int keepidle, int keepinterval, int keepcnt)\n{\n\tconst int yes = 1;\n\tif (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) != 0) {\n\t\treturn -1;\n\t}\n\n\tsetsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));\n\tsetsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepinterval, sizeof(keepinterval));\n\tsetsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));\n\n\treturn 0;\n}\n\nint set_sock_lingertime(int fd, int time)\n{\n\tstruct linger l;\n\n\tl.l_onoff = 1;\n\tl.l_linger = 0;\n\n\tif (setsockopt(fd, SOL_SOCKET, SO_LINGER, (const char *)&l, sizeof(l)) != 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint has_unprivileged_ping(void)\n{\n\tint fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);\n\tif (fd < 0) {\n\t\treturn 0;\n\t}\n\n\tclose(fd);\n\n\tfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);\n\tif (fd < 0) {\n\t\treturn 0;\n\t}\n\n\tclose(fd);\n\n\treturn 1;\n}\n"
  },
  {
    "path": "src/utils/nftset.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/lib/nftset.h\"\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/tlog.h\"\n\n#include <errno.h>\n#include <linux/netfilter.h>\n#include <linux/netfilter/nfnetlink.h>\n#include <linux/netlink.h>\n#include <linux/rtnetlink.h>\n#include <memory.h>\n#include <poll.h>\n#include <signal.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/socket.h>\n#include <sys/types.h>\n#include <time.h>\n#include <unistd.h>\n#ifdef NFNL_SUBSYS_NFTABLES\n#include <linux/netfilter/nf_tables.h>\n\nstruct nlmsgreq {\n\tstruct nlmsghdr h;\n\tstruct nfgenmsg m;\n};\n\nenum { PAYLOAD_MAX = 2048 };\n\nstatic int nftset_fd;\n\nstatic int _nftset_get_nffamily_from_str(const char *family)\n{\n\tif (strncmp(family, \"inet\", sizeof(\"inet\")) == 0) {\n\t\treturn NFPROTO_INET;\n\t} else if (strncmp(family, \"ip\", sizeof(\"ip\")) == 0) {\n\t\treturn NFPROTO_IPV4;\n\t} else if (strncmp(family, \"ip6\", sizeof(\"ip6\")) == 0) {\n\t\treturn NFPROTO_IPV6;\n\t} else if (strncmp(family, \"arp\", sizeof(\"arp\")) == 0) {\n\t\treturn NFPROTO_ARP;\n\t} else if (strncmp(family, \"netdev\", sizeof(\"netdev\")) == 0) {\n\t\treturn NFPROTO_NETDEV;\n\t} else if (strncmp(family, \"bridge\", sizeof(\"bridge\")) == 0) {\n\t\treturn NFPROTO_BRIDGE;\n\t} else if (strncmp(family, \"decnet\", sizeof(\"decnet\")) == 0) {\n\t\treturn NFPROTO_DECNET;\n\t} else {\n\t\treturn NFPROTO_UNSPEC;\n\t}\n}\n\nstatic struct rtattr *_nftset_nlmsg_tail(struct nlmsghdr *n)\n{\n\treturn (struct rtattr *)((uint8_t *)n + NLMSG_ALIGN(n->nlmsg_len));\n}\n\nstatic int _nftset_addattr(struct nlmsghdr *n, int maxlen, __u16 type, const void *data, __u16 alen)\n{\n\tconst __u16 len = RTA_LENGTH(alen);\n\tconst ssize_t newlen = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);\n\n\tif (newlen > maxlen) {\n\t\terrno = ENOSPC;\n\t\treturn -1;\n\t}\n\n\tstruct rtattr *attr = _nftset_nlmsg_tail(n);\n\tattr->rta_len = len;\n\tattr->rta_type = type;\n\n\tvoid *rta_data = RTA_DATA(attr);\n\n\tif ((data != NULL) && (alen > 0)) {\n\t\tmemcpy(rta_data, data, alen);\n\t}\n\tmemset((uint8_t *)rta_data + alen, 0, RTA_ALIGN(len) - len);\n\n\tn->nlmsg_len = newlen;\n\n\treturn 0;\n}\n\nstatic int _nftset_addattr_string(struct nlmsghdr *n, int maxlen, __u16 type, const char *s)\n{\n\treturn _nftset_addattr(n, maxlen, type, s, strlen(s) + 1);\n}\n\nstatic int __attribute__((unused)) _nftset_addattr_uint32(struct nlmsghdr *n, int maxlen, __u16 type, const uint32_t v)\n{\n\treturn _nftset_addattr(n, maxlen, type, &v, sizeof(uint32_t));\n}\n\nstatic int __attribute__((unused)) _nftset_addattr_uint16(struct nlmsghdr *n, int maxlen, __u16 type, const uint16_t v)\n{\n\treturn _nftset_addattr(n, maxlen, type, &v, sizeof(uint16_t));\n}\n\nstatic int __attribute__((unused)) _nftset_addattr_uint8(struct nlmsghdr *n, int maxlen, __u16 type, const uint8_t v)\n{\n\treturn _nftset_addattr(n, maxlen, type, &v, sizeof(uint8_t));\n}\n\nstatic struct rtattr *_nftset_addattr_nest(struct nlmsghdr *n, int maxlen, __u16 type)\n{\n\tstruct rtattr *attr = _nftset_nlmsg_tail(n);\n\n\tif (-1 == _nftset_addattr(n, maxlen, type, NULL, 0)) {\n\t\treturn NULL;\n\t}\n\n\treturn attr;\n}\n\nstatic void _nftset_addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest)\n{\n\tconst void *tail = _nftset_nlmsg_tail(n);\n\tnest->rta_len = (uint8_t *)tail - (uint8_t *)nest;\n}\n\nstatic int _nftset_start_batch(void *buf, void **nextbuf)\n{\n\tstruct nlmsgreq *req = (struct nlmsgreq *)buf;\n\tmemset(buf, 0, sizeof(struct nlmsgreq));\n\n\treq->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));\n\treq->h.nlmsg_flags = NLM_F_REQUEST;\n\treq->h.nlmsg_type = NFNL_MSG_BATCH_BEGIN;\n\treq->h.nlmsg_seq = time(NULL);\n\n\treq->m.res_id = htons(NFNL_SUBSYS_NFTABLES);\n\n\tif (nextbuf) {\n\t\t*nextbuf = (uint8_t *)buf + req->h.nlmsg_len;\n\t}\n\treturn 0;\n}\n\nstatic int _nftset_end_batch(void *buf, void **nextbuf)\n{\n\tstruct nlmsgreq *req = (struct nlmsgreq *)buf;\n\tmemset(buf, 0, sizeof(struct nlmsgreq));\n\n\treq->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));\n\treq->h.nlmsg_flags = NLM_F_REQUEST;\n\treq->h.nlmsg_type = NFNL_MSG_BATCH_END;\n\treq->h.nlmsg_seq = time(NULL);\n\n\treq->m.res_id = htons(NFNL_SUBSYS_NFTABLES);\n\n\tif (nextbuf) {\n\t\t*nextbuf = (uint8_t *)buf + req->h.nlmsg_len;\n\t}\n\n\treturn 0;\n}\n\nstatic int _nftset_socket_init(void)\n{\n\tstruct sockaddr_nl addr = {0};\n\taddr.nl_family = AF_NETLINK;\n\taddr.nl_pid = 0;\n\tint fd = -1;\n\n\tif (nftset_fd > 0) {\n\t\treturn 0;\n\t}\n\n\tfd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_NETFILTER);\n\tif (fd < 0) {\n\t\treturn -1;\n\t}\n\n\tif (bind(fd, (struct sockaddr *)(&addr), sizeof(addr)) < 0) {\n\t\tclose(fd);\n\t\treturn -2;\n\t}\n\n\tnftset_fd = fd;\n\n\treturn 0;\n}\n\nstatic int _nftset_socket_request(void *msg, int msg_len, void *ret_msg, int ret_msg_len)\n{\n\tint ret = -1;\n\tstruct pollfd pfds;\n\tint do_recv = 0;\n\tint len = 0;\n\tconst int max_retries = 3;\n\tint retry_count = 0;\n\n\tif (_nftset_socket_init() != 0) {\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\ttlog(TLOG_DEBUG, \"nftset socket init failed, errno=%d\", errno);\n\t\t}\n\t\treturn -1;\n\t}\n\n\t/* clear pending error message*/\n\tfor (;;) {\n\t\tuint8_t buff[1024];\n\t\tret = recv(nftset_fd, buff, sizeof(buff), MSG_DONTWAIT);\n\t\tif (ret < 0) {\n\t\t\tif (errno != EAGAIN && errno != EWOULDBLOCK) {\n\t\t\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"nftset clear pending msg error, errno=%d\", errno);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* send message with retry */\n\twhile (retry_count < max_retries) {\n\t\tlen = send(nftset_fd, msg, msg_len, 0);\n\t\tif (len == msg_len) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {\n\t\t\tretry_count++;\n\t\t\tstruct timespec waiter;\n\t\t\twaiter.tv_sec = 0;\n\t\t\twaiter.tv_nsec = 10000 * retry_count;\n\t\t\tnanosleep(&waiter, NULL);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\ttlog(TLOG_DEBUG, \"nftset send msg failed, errno=%d, retries=%d\", errno, retry_count);\n\t\t}\n\t\treturn -1;\n\t}\n\n\tif (retry_count >= max_retries) {\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\ttlog(TLOG_DEBUG, \"nftset send msg failed after max retries\");\n\t\t}\n\t\treturn -1;\n\t}\n\n\tif (ret_msg == NULL || ret_msg_len <= 0) {\n\t\treturn 0;\n\t}\n\n\tpfds.fd = nftset_fd;\n\tpfds.events = POLLIN;\n\tpfds.revents = 0;\n\tret = poll(&pfds, 1, 100);\n\tif (ret <= 0) {\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\ttlog(TLOG_DEBUG, \"nftset poll timeout or error, ret=%d, errno=%d\", ret, errno);\n\t\t}\n\t\treturn -1;\n\t}\n\n\tif ((pfds.revents & POLLIN) == 0) {\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\ttlog(TLOG_DEBUG, \"nftset poll no data, revents=0x%x\", pfds.revents);\n\t\t}\n\t\treturn -1;\n\t}\n\n\tmemset(ret_msg, 0, ret_msg_len);\n\tlen = 0;\n\tfor (;;) {\n\t\tret = recv(nftset_fd, ret_msg + len, ret_msg_len - len, 0);\n\t\tif (ret < 0) {\n\t\t\tif (errno == EAGAIN && do_recv == 1) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\t\ttlog(TLOG_DEBUG, \"nftset recv msg failed, errno=%d\", errno);\n\t\t\t}\n\t\t\treturn -1;\n\t\t}\n\n\t\tdo_recv = 1;\n\t\tlen += ret;\n\n\t\tif (len >= ret_msg_len) {\n\t\t\tbreak;\n\t\t}\n\n\t\tstruct nlmsghdr *nlh = (struct nlmsghdr *)ret_msg;\n\t\tif (nlh->nlmsg_type == NLMSG_ERROR) {\n\t\t\tstruct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);\n\t\t\tif (err->error != 0) {\n\t\t\t\terrno = -err->error;\n\t\t\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\t\t\ttlog(TLOG_DEBUG, \"nftset recv error msg, error=%d\", err->error);\n\t\t\t\t}\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Correctly handle the NFT_MSG_GETSETELEM response */\n\t\tif (nlh->nlmsg_type == (NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_NEWSETELEM)) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (nlh->nlmsg_type == (NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_GETSETELEM)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (nlh->nlmsg_type & (NFNL_SUBSYS_NFTABLES << 8)) {\n\t\t\tif (nlh->nlmsg_type & NLMSG_DONE) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\terrno = ENOTSUP;\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\ttlog(TLOG_DEBUG, \"nftset unsupported msg type, type=0x%x\", nlh->nlmsg_type);\n\t\t}\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int _nftset_socket_send(void *msg, int msg_len)\n{\n\tchar recvbuff[1024];\n\n\tif (dns_conf.nftset_debug_enable == 0) {\n\t\treturn _nftset_socket_request(msg, msg_len, NULL, 0);\n\t}\n\n\treturn _nftset_socket_request(msg, msg_len, recvbuff, sizeof(recvbuff));\n}\n\nstatic int _nftset_get_nftset(int nffamily, const char *table_name, const char *setname, void *buf, void **nextbuf)\n{\n\tstruct nlmsgreq *req = (struct nlmsgreq *)buf;\n\tmemset(buf, 0, sizeof(struct nlmsgreq));\n\n\treq->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));\n\treq->h.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;\n\treq->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_GETSET;\n\treq->h.nlmsg_seq = time(NULL);\n\n\treq->m.nfgen_family = nffamily;\n\treq->m.res_id = htons(NFNL_SUBSYS_NFTABLES);\n\treq->m.version = 0;\n\n\tstruct nlmsghdr *n = &req->h;\n\n\t_nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname);\n\t_nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, table_name);\n\n\tif (nextbuf) {\n\t\t*nextbuf = (uint8_t *)buf + req->h.nlmsg_len;\n\t}\n\n\treturn 0;\n}\n\nstatic int _nftset_get_flags(int nffamily, const char *tablename, const char *setname, uint32_t *flags)\n{\n\tuint8_t buf[PAYLOAD_MAX];\n\tuint8_t result[PAYLOAD_MAX];\n\tvoid *next = buf;\n\tint buffer_len = 0;\n\n\tif (flags == NULL) {\n\t\treturn -1;\n\t}\n\n\t_nftset_get_nftset(nffamily, tablename, setname, next, &next);\n\tbuffer_len = (uint8_t *)next - buf;\n\tint ret = _nftset_socket_request(buf, buffer_len, result, sizeof(result));\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\tstruct nlmsghdr *nlh = (struct nlmsghdr *)result;\n\tstruct nfgenmsg *nfmsg = (struct nfgenmsg *)NLMSG_DATA(nlh);\n\tstruct nfattr *nfa = (struct nfattr *)NFM_NFA(nfmsg);\n\t*flags = 0;\n\tfor (; NFA_OK(nfa, nlh->nlmsg_len); nfa = NFA_NEXT(nfa, nlh->nlmsg_len)) {\n\t\tif (nfa->nfa_type == NFTA_SET_FLAGS) {\n\t\t\t*flags = ntohl(*(uint32_t *)NFA_DATA(nfa));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int _nftset_del_element(int nffamily, const char *table_name, const char *setname, const void *data,\n\t\t\t\t\t\t\t   int data_len, const void *data_interval, int data_interval_len, void *buf,\n\t\t\t\t\t\t\t   void **nextbuf)\n{\n\tstruct nlmsgreq *req = (struct nlmsgreq *)buf;\n\tmemset(buf, 0, sizeof(struct nlmsgreq));\n\n\treq->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));\n\treq->h.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;\n\treq->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_DELSETELEM;\n\treq->h.nlmsg_seq = time(NULL);\n\n\tif (dns_conf.nftset_debug_enable) {\n\t\treq->h.nlmsg_flags |= NLM_F_ACK;\n\t}\n\n\treq->m.nfgen_family = nffamily;\n\n\tstruct nlmsghdr *n = &req->h;\n\n\t_nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, table_name);\n\t_nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname);\n\tstruct rtattr *nest_list = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_LIST_ELEMENTS);\n\tstruct rtattr *nest_elem = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED);\n\n\tstruct rtattr *nest_elem_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY);\n\t_nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data, data_len);\n\t_nftset_addattr_nest_end(n, nest_elem_key);\n\t_nftset_addattr_nest_end(n, nest_elem);\n\n\t/* interval attribute */\n\tif (data_interval && data_interval_len > 0) {\n\t\tstruct rtattr *nest_interval_end = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_LIST_ELEM);\n\t\t_nftset_addattr_uint32(n, PAYLOAD_MAX, NFTA_SET_ELEM_FLAGS, htonl(NFT_SET_ELEM_INTERVAL_END));\n\t\tstruct rtattr *nest_elem_interval_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY);\n\n\t\t_nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data_interval, data_interval_len);\n\t\t_nftset_addattr_nest_end(n, nest_elem_interval_key);\n\t\t_nftset_addattr_nest_end(n, nest_interval_end);\n\t}\n\n\t_nftset_addattr_nest_end(n, nest_list);\n\n\tif (nextbuf) {\n\t\t*nextbuf = (uint8_t *)buf + req->h.nlmsg_len;\n\t}\n\n\treturn 0;\n}\n\nstatic int _nftset_add_element(int nffamily, const char *table_name, const char *setname, const void *data,\n\t\t\t\t\t\t\t   int data_len, const void *data_interval, int data_interval_len, unsigned long timeout,\n\t\t\t\t\t\t\t   void *buf, void **nextbuf)\n{\n\tstruct nlmsgreq *req = (struct nlmsgreq *)buf;\n\tmemset(buf, 0, sizeof(struct nlmsgreq));\n\n\treq->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));\n\treq->h.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;\n\treq->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_NEWSETELEM;\n\treq->h.nlmsg_seq = time(NULL);\n\n\tif (dns_conf.nftset_debug_enable) {\n\t\treq->h.nlmsg_flags |= NLM_F_ACK;\n\t}\n\n\treq->m.nfgen_family = nffamily;\n\n\tstruct nlmsghdr *n = &req->h;\n\n\t_nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, table_name);\n\t_nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname);\n\tstruct rtattr *nest_list = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_LIST_ELEMENTS);\n\n\tstruct rtattr *nest_elem = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_LIST_ELEM);\n\tstruct rtattr *nest_elem_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY);\n\t_nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data, data_len);\n\t_nftset_addattr_nest_end(n, nest_elem_key);\n\tif (timeout > 0) {\n\t\tuint64_t timeout_value = htobe64(timeout * 1000);\n\t\t_nftset_addattr(n, PAYLOAD_MAX, NFTA_SET_ELEM_TIMEOUT, &timeout_value, sizeof(timeout_value));\n\t}\n\t_nftset_addattr_nest_end(n, nest_elem);\n\n\t/* interval attribute */\n\tif (data_interval && data_interval_len > 0) {\n\t\tstruct rtattr *nest_interval_end = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_LIST_ELEM);\n\t\t_nftset_addattr_uint32(n, PAYLOAD_MAX, NFTA_SET_ELEM_FLAGS, htonl(NFT_SET_ELEM_INTERVAL_END));\n\t\tstruct rtattr *nest_elem_interval_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY);\n\n\t\t_nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data_interval, data_interval_len);\n\t\t_nftset_addattr_nest_end(n, nest_elem_interval_key);\n\t\t_nftset_addattr_nest_end(n, nest_interval_end);\n\t}\n\n\t_nftset_addattr_nest_end(n, nest_list);\n\n\tif (nextbuf) {\n\t\t*nextbuf = (uint8_t *)buf + req->h.nlmsg_len;\n\t}\n\n\treturn 0;\n}\n\nstatic int _nftset_process_setflags(uint32_t flags, const unsigned char addr[], int addr_len, unsigned long *timeout,\n\t\t\t\t\t\t\t\t\tuint8_t **interval_addr, int *interval_addr_len)\n{\n\tuint8_t *addr_end = *interval_addr;\n\n\tif ((flags & NFT_SET_TIMEOUT) == 0 && timeout != NULL) {\n\t\t*timeout = 0;\n\t}\n\n\tif ((flags & NFT_SET_INTERVAL) && addr_end != NULL) {\n\t\tif (addr_len == 4) {\n\t\t\taddr_end[0] = addr[0];\n\t\t\taddr_end[1] = addr[1];\n\t\t\taddr_end[2] = addr[2];\n\t\t\taddr_end[3] = addr[3] + 1;\n\t\t\tif (addr_end[3] == 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t*interval_addr_len = 4;\n\t\t} else if (addr_len == 16) {\n\t\t\tmemcpy(addr_end, addr, 16);\n\t\t\taddr_end[15] = addr[15] + 1;\n\t\t\tif (addr_end[15] == 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\t*interval_addr_len = 16;\n\t\t}\n\t} else {\n\t\t*interval_addr = NULL;\n\t\t*interval_addr_len = 0;\n\t}\n\n\treturn 0;\n}\n\n/* Check whether the IP address already exists in the nftables collection */\nstatic int _nftset_test_ip_exists(int nffamily, const char *tablename, const char *setname, const unsigned char addr[],\n\t\t\t\t\t\t\t\t  int addr_len)\n{\n\tuint8_t buf[PAYLOAD_MAX];\n\tuint8_t result[PAYLOAD_MAX];\n\tvoid *next = buf;\n\tint buffer_len = 0;\n\n\t/* Initialize test message */\n\tstruct nlmsgreq *req = (struct nlmsgreq *)next;\n\tmemset(next, 0, sizeof(struct nlmsgreq));\n\n\treq->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));\n\treq->h.nlmsg_flags = NLM_F_REQUEST;\n\treq->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_GETSETELEM;\n\treq->h.nlmsg_seq = time(NULL);\n\treq->m.nfgen_family = nffamily;\n\treq->m.res_id = htons(NFNL_SUBSYS_NFTABLES);\n\treq->m.version = 0;\n\n\tstruct nlmsghdr *n = &req->h;\n\n\t_nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, tablename);\n\t_nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname);\n\n\tstruct rtattr *nest_list = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_LIST_ELEMENTS);\n\tstruct rtattr *nest_elem = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_LIST_ELEM);\n\tstruct rtattr *nest_elem_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY);\n\t_nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, addr, addr_len);\n\t_nftset_addattr_nest_end(n, nest_elem_key);\n\t_nftset_addattr_nest_end(n, nest_elem);\n\t_nftset_addattr_nest_end(n, nest_list);\n\n\tnext = (uint8_t *)next + req->h.nlmsg_len;\n\tbuffer_len = (uint8_t *)next - buf;\n\n\tif (dns_conf.nftset_debug_enable) {\n\t\tchar ip_str[INET6_ADDRSTRLEN];\n\t\tif (addr_len == 4) {\n\t\t\tinet_ntop(AF_INET, addr, ip_str, sizeof(ip_str));\n\t\t} else if (addr_len == 16) {\n\t\t\tinet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str));\n\t\t} else {\n\t\t\tsnprintf(ip_str, sizeof(ip_str), \"unknown\");\n\t\t}\n\t\ttlog(TLOG_DEBUG, \"nftset test ip: family=%d, table=%s, set=%s, ip=%s\", nffamily, tablename, setname, ip_str);\n\t}\n\n\t/* Send a test message and receive a response */\n\tint ret = _nftset_socket_request(buf, buffer_len, result, sizeof(result));\n\n\tif (ret < 0) {\n\t\t/* errorno=-2 */\n\t\tif (errno == ENOENT) {\n\t\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\t\ttlog(TLOG_DEBUG, \"nftset test ip: table/set not found, assuming ip not exists\");\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\t/* errorno=11 */\n\t\telse if (errno == EAGAIN || errno == EWOULDBLOCK) {\n\t\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\t\ttlog(TLOG_DEBUG, \"nftset test ip: EAGAIN error, assuming ip exists\");\n\t\t\t}\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\tchar ip_str[INET6_ADDRSTRLEN];\n\t\t\tif (addr_len == 4) {\n\t\t\t\tinet_ntop(AF_INET, addr, ip_str, sizeof(ip_str));\n\t\t\t} else if (addr_len == 16) {\n\t\t\t\tinet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str));\n\t\t\t} else {\n\t\t\t\tsnprintf(ip_str, sizeof(ip_str), \"unknown\");\n\t\t\t}\n\t\t\ttlog(TLOG_DEBUG,\n\t\t\t\t \"nftset test ip communication failed, assuming ip not exists: family=%d, table=%s, set=%s, ip=%s, \"\n\t\t\t\t \"errorno:%d\",\n\t\t\t\t nffamily, tablename, setname, ip_str, errno);\n\t\t}\n\t\treturn 0;\n\t}\n\n\tint ip_exists = 0;\n\n\tstruct nlmsghdr *nlh = (struct nlmsghdr *)result;\n\n\t/* If a successful response is received, it indicates that the IP already exists */\n\tif (nlh->nlmsg_type == (NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_NEWSETELEM)) {\n\t\tip_exists = 1;\n\t}\n\n\t/* Received an error message */\n\tif (nlh->nlmsg_type == NLMSG_ERROR) {\n\t\tstruct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);\n\n\t\tif (err->error == -ENOENT) {\n\t\t\tip_exists = 0;\n\t\t} else if (dns_conf.nftset_debug_enable) {\n\t\t\ttlog(TLOG_DEBUG, \"nftset test ip error: family=%d, table=%s, set=%s, error=%d\", nffamily, tablename,\n\t\t\t\t setname, -err->error);\n\t\t}\n\t}\n\n\tif (dns_conf.nftset_debug_enable) {\n\t\tchar ip_str[INET6_ADDRSTRLEN];\n\t\tif (addr_len == 4) {\n\t\t\tinet_ntop(AF_INET, addr, ip_str, sizeof(ip_str));\n\t\t} else if (addr_len == 16) {\n\t\t\tinet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str));\n\t\t} else {\n\t\t\tsnprintf(ip_str, sizeof(ip_str), \"unknown\");\n\t\t}\n\t\ttlog(TLOG_DEBUG, \"nftset test ip result: family=%d, table=%s, set=%s, ip=%s, exists=%d\", nffamily, tablename,\n\t\t\t setname, ip_str, ip_exists);\n\t}\n\n\treturn ip_exists;\n}\n\nstatic int _nftset_del(int nffamily, const char *tablename, const char *setname, const unsigned char addr[],\n\t\t\t\t\t   int addr_len, const unsigned char addr_end[], int addr_end_len)\n{\n\tuint8_t buf[PAYLOAD_MAX];\n\tvoid *next = buf;\n\tint buffer_len = 0;\n\n\t_nftset_start_batch(next, &next);\n\t_nftset_del_element(nffamily, tablename, setname, addr, addr_len, addr_end, addr_end_len, next, &next);\n\t_nftset_end_batch(next, &next);\n\tbuffer_len = (uint8_t *)next - buf;\n\treturn _nftset_socket_send(buf, buffer_len);\n}\n\nint nftset_del(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[],\n\t\t\t   int addr_len)\n{\n\tint nffamily = _nftset_get_nffamily_from_str(familyname);\n\n\tuint8_t addr_end_buff[16] = {0};\n\tuint8_t *addr_end = addr_end_buff;\n\tuint32_t flags = 0;\n\tint addr_end_len = 0;\n\tint ret = -1;\n\n\tret = _nftset_get_flags(nffamily, tablename, setname, &flags);\n\tif (ret == 0) {\n\t\tret = _nftset_process_setflags(flags, addr, addr_len, NULL, &addr_end, &addr_end_len);\n\t\tif (ret != 0) {\n\t\t\treturn -1;\n\t\t}\n\t} else {\n\t\taddr_end = NULL;\n\t\taddr_end_len = 0;\n\t}\n\n\tret = _nftset_del(nffamily, tablename, setname, addr, addr_len, addr_end, addr_end_len);\n\tif (ret != 0 && errno != ENOENT) {\n\t\ttlog(TLOG_ERROR, \"nftset delete failed, family:%s, table:%s, set:%s, error:%s\", familyname, tablename, setname,\n\t\t\t strerror(errno));\n\t}\n\n\treturn ret;\n}\n\nint nftset_add(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[],\n\t\t\t   int addr_len, unsigned long timeout)\n{\n\tint nffamily = _nftset_get_nffamily_from_str(familyname);\n\tint ip_exists = _nftset_test_ip_exists(nffamily, tablename, setname, addr, addr_len);\n\n\tif (ip_exists) {\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\tchar ip_str[INET6_ADDRSTRLEN];\n\t\t\tif (addr_len == 4) {\n\t\t\t\tinet_ntop(AF_INET, addr, ip_str, sizeof(ip_str));\n\t\t\t} else if (addr_len == 16) {\n\t\t\t\tinet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str));\n\t\t\t} else {\n\t\t\t\tsnprintf(ip_str, sizeof(ip_str), \"unknown\");\n\t\t\t}\n\t\t\ttlog(TLOG_DEBUG, \"nftset skip adding existing ip: family=%d, table=%s, set=%s, ip=%s\", nffamily, tablename,\n\t\t\t\t setname, ip_str);\n\t\t}\n\t\treturn 0;\n\t}\n\n\tuint8_t buf[PAYLOAD_MAX];\n\tuint8_t addr_end_buff[16] = {0};\n\tuint8_t *addr_end = addr_end_buff;\n\tuint32_t flags = 0;\n\tint addr_end_len = 0;\n\tvoid *next = buf;\n\tint buffer_len = 0;\n\tint ret = -1;\n\n\tret = _nftset_get_flags(nffamily, tablename, setname, &flags);\n\tif (ret == 0) {\n\t\tret = _nftset_process_setflags(flags, addr, addr_len, &timeout, &addr_end, &addr_end_len);\n\t\tif (ret != 0) {\n\t\t\tchar ip_str[INET6_ADDRSTRLEN];\n\t\t\tif (addr_len == 4) {\n\t\t\t\tinet_ntop(AF_INET, addr, ip_str, sizeof(ip_str));\n\t\t\t} else if (addr_len == 16) {\n\t\t\t\tinet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str));\n\t\t\t} else {\n\t\t\t\tsnprintf(ip_str, sizeof(ip_str), \"unknown\");\n\t\t\t}\n\n\t\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\t\ttlog(TLOG_ERROR, \"nftset setflags failed, family:%s, table:%s, set:%s, ip:%s, error:%s\", familyname,\n\t\t\t\t\t tablename, setname, ip_str, \"ip is invalid\");\n\t\t\t}\n\t\t\treturn -1;\n\t\t}\n\t} else {\n\t\taddr_end = NULL;\n\t\taddr_end_len = 0;\n\t}\n\n\tif (timeout > 0) {\n\t\t_nftset_del(nffamily, tablename, setname, addr, addr_len, addr_end, addr_end_len);\n\t}\n\n\t_nftset_start_batch(next, &next);\n\t_nftset_add_element(nffamily, tablename, setname, addr, addr_len, addr_end, addr_end_len, timeout, next, &next);\n\t_nftset_end_batch(next, &next);\n\tbuffer_len = (uint8_t *)next - buf;\n\n\tret = _nftset_socket_send(buf, buffer_len);\n\tif (ret != 0) {\n\t\tchar ip_str[INET6_ADDRSTRLEN];\n\t\tif (addr_len == 4) {\n\t\t\tinet_ntop(AF_INET, addr, ip_str, sizeof(ip_str));\n\t\t} else if (addr_len == 16) {\n\t\t\tinet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str));\n\t\t} else {\n\t\t\tsnprintf(ip_str, sizeof(ip_str), \"unknown\");\n\t\t}\n\n\t\ttlog(TLOG_ERROR, \"nftset add failed, family:%s, table:%s, set:%s, ip:%s, error:%s\", familyname, tablename,\n\t\t\t setname, ip_str, strerror(errno));\n\t} else {\n\t\tif (dns_conf.nftset_debug_enable) {\n\t\t\tchar ip_str[INET6_ADDRSTRLEN];\n\t\t\tif (addr_len == 4) {\n\t\t\t\tinet_ntop(AF_INET, addr, ip_str, sizeof(ip_str));\n\t\t\t} else if (addr_len == 16) {\n\t\t\t\tinet_ntop(AF_INET6, addr, ip_str, sizeof(ip_str));\n\t\t\t} else {\n\t\t\t\tsnprintf(ip_str, sizeof(ip_str), \"unknown\");\n\t\t\t}\n\n\t\t\tif (timeout > 0) {\n\t\t\t\ttlog(TLOG_DEBUG, \"nftset add success, family:%s, table:%s, set:%s, ip:%s, timeout:%lu\", familyname,\n\t\t\t\t\t tablename, setname, ip_str, timeout);\n\t\t\t} else {\n\t\t\t\ttlog(TLOG_DEBUG, \"nftset add success, family:%s, table:%s, set:%s, ip:%s\", familyname, tablename,\n\t\t\t\t\t setname, ip_str);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ret;\n}\n\n#else\n\nint nftset_add(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[],\n\t\t\t   int addr_len, unsigned long timeout)\n{\n\treturn 0;\n}\n\nint nftset_del(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[],\n\t\t\t   int addr_len)\n{\n\treturn 0;\n}\n\n#endif"
  },
  {
    "path": "src/utils/ssl.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/dns_conf.h\"\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n\n#include <ifaddrs.h>\n#include <linux/limits.h>\n#include <openssl/crypto.h>\n#include <openssl/ssl.h>\n#include <openssl/x509v3.h>\n#include <pthread.h>\n#include <sys/stat.h>\n\n#define DNS_MAX_HOSTNAME_LEN 256\n\nstruct DNS_EVP_PKEY_CTX {\n\tEVP_PKEY *pkey;\n#if (OPENSSL_VERSION_NUMBER < 0x30000000L)\n\tRSA *rsa;\n\tBIGNUM *bn;\n#endif\n};\n\nint is_cert_valid(const char *cert_file_path)\n{\n\tstruct stat st;\n\tBIO *cert_file = NULL;\n\tX509 *cert = NULL;\n\tint ret = 0;\n\n\tif (stat(cert_file_path, &st) != 0) {\n\t\treturn 0;\n\t}\n\n\tif (st.st_size <= 0) {\n\t\treturn 0;\n\t}\n\n\tcert_file = BIO_new_file(cert_file_path, \"r\");\n\tif (cert_file == NULL) {\n\t\treturn 0;\n\t}\n\n\tcert = PEM_read_bio_X509_AUX(cert_file, NULL, NULL, NULL);\n\tif (cert == NULL) {\n\t\tgoto out;\n\t}\n\n\tif (X509_get_notAfter(cert) == NULL) {\n\t\tgoto out;\n\t}\n\n\tif (X509_get_notBefore(cert) == NULL) {\n\t\tgoto out;\n\t}\n\n\tif (X509_cmp_current_time(X509_get_notAfter(cert)) < 0) {\n\t\ttlog(TLOG_WARN, \"cert %s expired\", cert_file_path);\n\t\tgoto out;\n\t}\n\n\tif (X509_cmp_current_time(X509_get_notBefore(cert)) > 0) {\n\t\ttlog(TLOG_WARN, \"cert %s not valid yet\", cert_file_path);\n\t\tgoto out;\n\t}\n\n\tret = 1;\nout:\n\tif (cert) {\n\t\tX509_free(cert);\n\t}\n\n\tif (cert_file) {\n\t\tBIO_free(cert_file);\n\t}\n\n\treturn ret;\n}\n\nint generate_cert_san(char *san, int max_san_len, const char *append_san)\n{\n\tchar hostname[DNS_MAX_HOSTNAME_LEN];\n\tchar domainname[DNS_MAX_HOSTNAME_LEN];\n\tint san_len = 0;\n\tstruct ifaddrs *ifaddr = NULL;\n\tstruct ifaddrs *ifa = NULL;\n\tuint8_t addr[16] = {0};\n\tint addr_len = 0;\n\n\thostname[0] = '\\0';\n\tdomainname[0] = '\\0';\n\n\tif (san == NULL || max_san_len <= 0) {\n\t\treturn -1;\n\t}\n\n\tint len = snprintf(san, max_san_len - san_len, \"DNS:%s\", \"smartdns\");\n\tif (len < 0 || len >= max_san_len - san_len) {\n\t\treturn -1;\n\t}\n\tsan_len += len;\n\n\tif (append_san != NULL && append_san[0] != '\\0') {\n\t\tlen = snprintf(san + san_len, max_san_len - san_len, \",%s\", append_san);\n\t\tif (len < 0 || len >= max_san_len - san_len) {\n\t\t\treturn -1;\n\t\t}\n\t\tsan_len += len;\n\t}\n\n\t/* get local domain name */\n\tif (getdomainname(domainname, DNS_MAX_HOSTNAME_LEN - 1) == 0) {\n\t\t/* check domain is valid */\n\t\tif (strncmp(domainname, \"(none)\", DNS_MAX_HOSTNAME_LEN - 1) == 0) {\n\t\t\tdomainname[0] = '\\0';\n\t\t}\n\n\t\tif (domainname[0] != '\\0') {\n\t\t\tlen = snprintf(san + san_len, max_san_len - san_len, \",DNS:%s\", domainname);\n\t\t\tif (len < 0 || len >= max_san_len - san_len) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tsan_len += len;\n\t\t}\n\t}\n\n\tif (gethostname(hostname, DNS_MAX_HOSTNAME_LEN - 1) == 0) {\n\t\t/* check hostname is valid */\n\t\tif (strncmp(hostname, \"(none)\", DNS_MAX_HOSTNAME_LEN - 1) == 0) {\n\t\t\thostname[0] = '\\0';\n\t\t}\n\n\t\tif (hostname[0] != '\\0') {\n\t\t\tlen = snprintf(san + san_len, max_san_len - san_len, \",DNS:%s\", hostname);\n\t\t\tif (len < 0 || len >= max_san_len - san_len) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tsan_len += len;\n\t\t}\n\t}\n\n\tif (dns_conf.server_name[0] != '\\0' &&\n\t\tstrncmp(dns_conf.server_name, \"smartdns\", DNS_MAX_SERVER_NAME_LEN - 1) != 0) {\n\t\tlen = snprintf(san + san_len, max_san_len - san_len, \",DNS:%s\", dns_conf.server_name);\n\t\tif (len < 0 || len >= max_san_len - san_len) {\n\t\t\treturn -1;\n\t\t}\n\t\tsan_len += len;\n\t}\n\n\tif (getifaddrs(&ifaddr) == -1) {\n\t\treturn -1;\n\t}\n\n\tfor (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {\n\t\tif (ifa->ifa_addr == NULL) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (ifa->ifa_addr->sa_family) {\n\t\tcase AF_INET: {\n\t\t\tstruct sockaddr_in *addr_in = NULL;\n\t\t\taddr_in = (struct sockaddr_in *)ifa->ifa_addr;\n\t\t\tmemcpy(addr, &(addr_in->sin_addr.s_addr), 4);\n\t\t\taddr_len = 4;\n\t\t} break;\n\t\tcase AF_INET6: {\n\t\t\tstruct sockaddr_in6 *addr_in6 = NULL;\n\t\t\taddr_in6 = (struct sockaddr_in6 *)ifa->ifa_addr;\n\t\t\tif (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) {\n\t\t\t\tmemcpy(addr, &(addr_in6->sin6_addr.s6_addr[12]), 4);\n\t\t\t\taddr_len = 4;\n\t\t\t} else {\n\t\t\t\tmemcpy(addr, addr_in6->sin6_addr.s6_addr, 16);\n\t\t\t\taddr_len = 16;\n\t\t\t\t// TODO\n\t\t\t\t// SKIP local IPV6;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} break;\n\t\tdefault:\n\t\t\tcontinue;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (is_private_addr(addr, addr_len) == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (addr_len == 4) {\n\t\t\tlen = snprintf(san + san_len, max_san_len - san_len, \",IP:%d.%d.%d.%d\", addr[0], addr[1], addr[2], addr[3]);\n\t\t} else if (addr_len == 16) {\n\t\t\tlen = snprintf(san + san_len, max_san_len - san_len, \",IP:%x:%x:%x:%x:%x:%x:%x:%x\",\n\t\t\t\t\t\t   ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[0]),\n\t\t\t\t\t\t   ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[1]),\n\t\t\t\t\t\t   ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[2]),\n\t\t\t\t\t\t   ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[3]),\n\t\t\t\t\t\t   ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[4]),\n\t\t\t\t\t\t   ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[5]),\n\t\t\t\t\t\t   ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[6]),\n\t\t\t\t\t\t   ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.s6_addr[7]));\n\t\t} else {\n\t\t\tcontinue;\n\t\t}\n\t\tif (len < 0 || len >= max_san_len - san_len) {\n\t\t\tgoto errout;\n\t\t}\n\t\tsan_len += len;\n\t}\n\n\tfreeifaddrs(ifaddr);\n\treturn 0;\n\nerrout:\n\tif (ifaddr) {\n\t\tfreeifaddrs(ifaddr);\n\t}\n\treturn -1;\n}\n\nstatic void _free_key(struct DNS_EVP_PKEY_CTX *ctx)\n{\n\tif (ctx) {\n\t\tif (ctx->pkey) {\n\t\t\tEVP_PKEY_free(ctx->pkey);\n\t\t}\n#if (OPENSSL_VERSION_NUMBER < 0x30000000L)\n\t\tif (ctx->rsa) {\n\t\t\tRSA_free(ctx->rsa);\n\t\t}\n\t\tif (ctx->bn) {\n\t\t\tBN_free(ctx->bn);\n\t\t}\n\t\tfree(ctx);\n#endif\n\t}\n}\n\nstatic struct DNS_EVP_PKEY_CTX *_read_key_from_file(const char *key_path)\n{\n\tstruct DNS_EVP_PKEY_CTX *ctx = NULL;\n\tEVP_PKEY *pkey = NULL;\n\tBIO *key_file = NULL;\n\n\tctx = zalloc(1, sizeof(struct DNS_EVP_PKEY_CTX));\n\tif (ctx == NULL) {\n\t\treturn NULL;\n\t}\n\n\tkey_file = BIO_new_file(key_path, \"rb\");\n\tif (key_file == NULL) {\n\t\ttlog(TLOG_ERROR, \"read root key file %s failed.\", key_path);\n\t\tgoto errout;\n\t}\n\n\tpkey = PEM_read_bio_PrivateKey(key_file, NULL, NULL, NULL);\n\tif (pkey == NULL) {\n\t\ttlog(TLOG_ERROR, \"read root key data failed.\");\n\t\tgoto errout;\n\t}\n\n\tBIO_free(key_file);\n\tctx->pkey = pkey;\n\treturn ctx;\nerrout:\n\tif (key_file) {\n\t\tBIO_free(key_file);\n\t}\n\tif (ctx) {\n\t\t_free_key(ctx);\n\t}\n\treturn NULL;\n}\n\nstatic struct DNS_EVP_PKEY_CTX *_generate_key(void)\n{\n\tstruct DNS_EVP_PKEY_CTX *ctx = NULL;\n\tctx = zalloc(1, sizeof(struct DNS_EVP_PKEY_CTX));\n\tif (ctx == NULL) {\n\t\treturn NULL;\n\t}\n\n\tconst int RSA_KEY_LENGTH = 2048;\n#if (OPENSSL_VERSION_NUMBER >= 0x30000000L)\n\tctx->pkey = EVP_RSA_gen(RSA_KEY_LENGTH);\n#else\n\tctx->pkey = EVP_PKEY_new();\n\tctx->rsa = RSA_new();\n\tctx->bn = BN_new();\n\n\tBN_set_word(ctx->bn, RSA_F4);\n\tRSA_generate_key_ex(ctx->rsa, RSA_KEY_LENGTH, ctx->bn, NULL);\n\tEVP_PKEY_assign_RSA(ctx->pkey, ctx->rsa);\n#endif\n\treturn ctx;\n}\n\nstatic X509 *_generate_smartdns_cert(EVP_PKEY *pkey, X509 *issuer_cert, EVP_PKEY *issuer_key, const char *san, int days)\n{\n\tX509 *cert = NULL;\n\tX509_EXTENSION *cert_ext = NULL;\n\tint is_ca = 0;\n\n\tif (pkey == NULL) {\n\t\tgoto errout;\n\t}\n\n\tcert = X509_new();\n\tif (cert == NULL) {\n\t\tgoto errout;\n\t}\n\n\tif (issuer_cert == NULL || issuer_key == NULL) {\n\t\tis_ca = 1;\n\t}\n\n\tX509_set_version(cert, 2);\n\tASN1_INTEGER_set(X509_get_serialNumber(cert), rand());\n\tX509_gmtime_adj(X509_get_notBefore(cert), 0);\n\tX509_gmtime_adj(X509_get_notAfter(cert), days * 24 * 3600);\n\n\tX509_set_pubkey(cert, pkey);\n\n\tX509_NAME *name = X509_get_subject_name(cert);\n\tX509_NAME *issuer_name = name;\n\n\tconst unsigned char *country = (unsigned char *)\"smartdns\";\n\tconst unsigned char *company = (unsigned char *)\"smartdns\";\n\tconst unsigned char *common_name = (unsigned char *)(is_ca ? \"SmartDNS Root\" : \"smartdns\");\n\tconst char *CA_BASIC_CONSTRAINTS = is_ca ? \"CA:TRUE\" : \"CA:FALSE\";\n\tconst char *KEY_USAGE = is_ca ? \"keyCertSign,cRLSign\" : \"digitalSignature,keyEncipherment\";\n\tconst char *EXT_KEY_USAGE = is_ca ? \"clientAuth,serverAuth,codeSigning,timeStamping\" : \"serverAuth\";\n\n\tX509_NAME_add_entry_by_txt(name, \"C\", MBSTRING_ASC, country, -1, -1, 0);\n\tX509_NAME_add_entry_by_txt(name, \"CN\", MBSTRING_ASC, common_name, -1, -1, 0);\n\tif (is_ca) {\n\t\tX509_NAME_add_entry_by_txt(name, \"O\", MBSTRING_ASC, company, -1, -1, 0);\n\t} else {\n\t\tissuer_name = X509_get_subject_name(issuer_cert);\n\t}\n\tX509_set_subject_name(cert, name);\n\tX509_set_issuer_name(cert, issuer_name);\n\n\tif (san != NULL) {\n\t\tcert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, san);\n\t\tif (cert_ext == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\t\tX509_add_ext(cert, cert_ext, -1);\n\t\tX509_EXTENSION_free(cert_ext);\n\t}\n\n\t// Add X509v3 extensions\n\tcert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, CA_BASIC_CONSTRAINTS);\n\tX509_add_ext(cert, cert_ext, -1);\n\tX509_EXTENSION_free(cert_ext);\n\n\tcert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, KEY_USAGE);\n\tX509_add_ext(cert, cert_ext, -1);\n\tX509_EXTENSION_free(cert_ext);\n\n\tif (EXT_KEY_USAGE != NULL) {\n\t\tcert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, EXT_KEY_USAGE);\n\t\tX509_add_ext(cert, cert_ext, -1);\n\t\tX509_EXTENSION_free(cert_ext);\n\t}\n\n\tcert_ext = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_key_identifier, \"hash\");\n\tX509_add_ext(cert, cert_ext, -1);\n\tX509_EXTENSION_free(cert_ext);\n\n\tX509_sign(cert, is_ca ? pkey : issuer_key, EVP_sha256());\n\treturn cert;\n\nerrout:\n\tif (cert) {\n\t\tX509_free(cert);\n\t}\n\n\treturn NULL;\n}\n\nint generate_cert_key(const char *key_path, const char *cert_path, const char *root_key_path, const char *san, int days)\n{\n\tchar root_key_path_buff[PATH_MAX * 2] = {0};\n\tchar server_key_path[PATH_MAX] = {0};\n\tBIO *server_key_file = NULL;\n\tBIO *server_cert_file = NULL;\n\tBIO *root_key_file = NULL;\n\tint create_root_key = 0;\n\tX509 *ca_cert = NULL;\n\tX509 *server_cert = NULL;\n\tstruct DNS_EVP_PKEY_CTX *server_key_ctx = NULL;\n\tstruct DNS_EVP_PKEY_CTX *ca_key_ctx = NULL;\n\n\tif (key_path == NULL || cert_path == NULL) {\n\t\treturn -1;\n\t}\n\n\tif (root_key_path == NULL || root_key_path[0] == '\\0') {\n\t\tsafe_strncpy(server_key_path, key_path, sizeof(server_key_path));\n\t\tif (dir_name(server_key_path) == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"get server key path failed.\");\n\t\t\treturn -1;\n\t\t}\n\n\t\tsnprintf(root_key_path_buff, sizeof(root_key_path_buff), \"%s/root-ca.key\", server_key_path);\n\t\troot_key_path = root_key_path_buff;\n\t}\n\n\tif (access(root_key_path, F_OK) == 0) {\n\t\tca_key_ctx = _read_key_from_file(root_key_path);\n\t\tif (ca_key_ctx == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"read root ca key failed.\");\n\t\t\tgoto errout;\n\t\t}\n\t\tcreate_root_key = 1;\n\t} else {\n\t\tca_key_ctx = _generate_key();\n\t\tcreate_root_key = 0;\n\t}\n\n\tif (ca_key_ctx == NULL) {\n\t\ttlog(TLOG_ERROR, \"generate root ca key failed.\");\n\t\tgoto errout;\n\t}\n\n\tca_cert = _generate_smartdns_cert(ca_key_ctx->pkey, NULL, NULL, NULL, 365 * 10);\n\tif (ca_cert == NULL) {\n\t\ttlog(TLOG_ERROR, \"generate root ca cert failed.\");\n\t\tgoto errout;\n\t}\n\n\tserver_key_ctx = _generate_key();\n\tif (server_key_ctx == NULL) {\n\t\ttlog(TLOG_ERROR, \"generate server key failed.\");\n\t\tgoto errout;\n\t}\n\tserver_cert = _generate_smartdns_cert(server_key_ctx->pkey, ca_cert, ca_key_ctx->pkey, san, days);\n\tif (server_cert == NULL) {\n\t\ttlog(TLOG_ERROR, \"generate server cert failed.\");\n\t\tgoto errout;\n\t}\n\n\tserver_key_file = BIO_new_file(key_path, \"wb\");\n\tserver_cert_file = BIO_new_file(cert_path, \"wb\");\n\tif (server_key_file == NULL || server_cert_file == NULL) {\n\t\ttlog(TLOG_ERROR, \"create key/cert file failed.\");\n\t\treturn -1;\n\t}\n\n\tif (PEM_write_bio_PrivateKey(server_key_file, server_key_ctx->pkey, NULL, NULL, 0, NULL, NULL) != 1) {\n\t\treturn -1;\n\t}\n\n\tif (PEM_write_bio_X509(server_cert_file, server_cert) != 1) {\n\t\treturn -1;\n\t}\n\n\tif (PEM_write_bio_X509(server_cert_file, ca_cert) != 1) {\n\t\treturn -1;\n\t}\n\n\tif (create_root_key == 0) {\n\t\troot_key_file = BIO_new_file(root_key_path, \"wb\");\n\t\tif (root_key_file == NULL) {\n\t\t\ttlog(TLOG_ERROR, \"create root ca key file failed.\");\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (PEM_write_bio_PrivateKey(root_key_file, ca_key_ctx->pkey, NULL, NULL, 0, NULL, NULL) != 1) {\n\t\t\tgoto errout;\n\t\t}\n\t\tBIO_free_all(root_key_file);\n\t\tchmod(root_key_path, S_IRUSR);\n\t}\n\n\tchmod(key_path, S_IRUSR);\n\tchmod(cert_path, S_IRUSR);\n\n\tBIO_free_all(server_key_file);\n\tBIO_free_all(server_cert_file);\n\n\tX509_free(ca_cert);\n\tX509_free(server_cert);\n\t_free_key(ca_key_ctx);\n\t_free_key(server_key_ctx);\n\treturn 0;\n\nerrout:\n\tif (server_key_file) {\n\t\tBIO_free_all(server_key_file);\n\t}\n\n\tif (server_cert_file) {\n\t\tBIO_free_all(server_cert_file);\n\t}\n\n\tif (root_key_file) {\n\t\tBIO_free_all(root_key_file);\n\t}\n\n\tif (ca_cert) {\n\t\tX509_free(ca_cert);\n\t}\n\n\tif (server_cert) {\n\t\tX509_free(server_cert);\n\t}\n\n\tif (ca_key_ctx) {\n\t\t_free_key(ca_key_ctx);\n\t}\n\n\tif (server_key_ctx) {\n\t\t_free_key(server_key_ctx);\n\t}\n\n\treturn -1;\n}\n\n#if OPENSSL_API_COMPAT < 0x10100000\n#define THREAD_STACK_SIZE (16 * 1024)\nstatic pthread_mutex_t *lock_cs;\nstatic long *lock_count;\n\nstatic __attribute__((unused)) void _pthreads_locking_callback(int mode, int type, const char *file, int line)\n{\n\tif (mode & CRYPTO_LOCK) {\n\t\tpthread_mutex_lock(&(lock_cs[type]));\n\t\tlock_count[type]++;\n\t} else {\n\t\tpthread_mutex_unlock(&(lock_cs[type]));\n\t}\n}\n\nstatic __attribute__((unused)) unsigned long _pthreads_thread_id(void)\n{\n\tunsigned long ret = 0;\n\n\tret = (unsigned long)pthread_self();\n\treturn (ret);\n}\n\nvoid SSL_CRYPTO_thread_setup(void)\n{\n\tint i = 0;\n\n\tif (lock_cs != NULL) {\n\t\treturn;\n\t}\n\n\tlock_cs = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));\n\tlock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));\n\tif (!lock_cs || !lock_count) {\n\t\t/* Nothing we can do about this...void function! */\n\t\tif (lock_cs) {\n\t\t\tOPENSSL_free(lock_cs);\n\t\t}\n\t\tif (lock_count) {\n\t\t\tOPENSSL_free(lock_count);\n\t\t}\n\t\treturn;\n\t}\n\tfor (i = 0; i < CRYPTO_num_locks(); i++) {\n\t\tlock_count[i] = 0;\n\t\tpthread_mutex_init(&(lock_cs[i]), NULL);\n\t}\n\n#if OPENSSL_API_COMPAT < 0x10000000\n\tCRYPTO_set_id_callback(_pthreads_thread_id);\n#else\n\tCRYPTO_THREADID_set_callback(_pthreads_thread_id);\n#endif\n\tCRYPTO_set_locking_callback(_pthreads_locking_callback);\n}\n\nvoid SSL_CRYPTO_thread_cleanup(void)\n{\n\tint i = 0;\n\n\tif (lock_cs == NULL) {\n\t\treturn;\n\t}\n\n\tCRYPTO_set_locking_callback(NULL);\n\tfor (i = 0; i < CRYPTO_num_locks(); i++) {\n\t\tpthread_mutex_destroy(&(lock_cs[i]));\n\t}\n\tOPENSSL_free(lock_cs);\n\tOPENSSL_free(lock_count);\n\tlock_cs = NULL;\n\tlock_count = NULL;\n}\n#endif\n\nunsigned char *SSL_SHA256(const unsigned char *d, size_t n, unsigned char *md)\n{\n\tstatic unsigned char m[SHA256_DIGEST_LENGTH];\n\n\tif (md == NULL) {\n\t\tmd = m;\n\t}\n\n\tEVP_MD_CTX *ctx = EVP_MD_CTX_create();\n\tif (ctx == NULL) {\n\t\treturn NULL;\n\t}\n\n\tEVP_MD_CTX_init(ctx);\n\tEVP_DigestInit_ex(ctx, EVP_sha256(), NULL);\n\tEVP_DigestUpdate(ctx, d, n);\n\tEVP_DigestFinal_ex(ctx, m, NULL);\n\tEVP_MD_CTX_destroy(ctx);\n\n\treturn (md);\n}\n\nint SSL_base64_decode_ext(const char *in, unsigned char *out, int max_outlen, int url_safe, int auto_padding)\n{\n\tsize_t inlen = strlen(in);\n\tchar *in_padding_data = NULL;\n\tint padding_len = 0;\n\tconst char *in_data = in;\n\tint outlen = 0;\n\n\tif (inlen == 0) {\n\t\treturn 0;\n\t}\n\n\tif (inlen % 4 == 0) {\n\t\tauto_padding = 0;\n\t}\n\n\tif (auto_padding == 1 || url_safe == 1) {\n\t\tpadding_len = 4 - inlen % 4;\n\t\tin_padding_data = (char *)malloc(inlen + padding_len + 1);\n\t\tif (in_padding_data == NULL) {\n\t\t\tgoto errout;\n\t\t}\n\n\t\tif (url_safe) {\n\t\t\tfor (size_t i = 0; i < inlen; i++) {\n\t\t\t\tif (in[i] == '-') {\n\t\t\t\t\tin_padding_data[i] = '+';\n\t\t\t\t} else if (in[i] == '_') {\n\t\t\t\t\tin_padding_data[i] = '/';\n\t\t\t\t} else {\n\t\t\t\t\tin_padding_data[i] = in[i];\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tmemcpy(in_padding_data, in, inlen);\n\t\t}\n\n\t\tif (auto_padding) {\n\t\t\tmemset(in_padding_data + inlen, '=', padding_len);\n\t\t} else {\n\t\t\tpadding_len = 0;\n\t\t}\n\n\t\tin_padding_data[inlen + padding_len] = '\\0';\n\t\tin_data = in_padding_data;\n\t\tinlen += padding_len;\n\t}\n\n\tif (max_outlen < (int)inlen / 4 * 3) {\n\t\tgoto errout;\n\t}\n\n\toutlen = EVP_DecodeBlock(out, (unsigned char *)in_data, inlen);\n\tif (outlen < 0) {\n\t\tgoto errout;\n\t}\n\n\t/* Subtract padding bytes from |outlen| */\n\twhile (in[--inlen] == '=') {\n\t\t--outlen;\n\t}\n\n\tif (in_padding_data) {\n\t\tfree(in_padding_data);\n\t}\n\n\toutlen -= padding_len;\n\n\treturn outlen;\nerrout:\n\n\tif (in_padding_data) {\n\t\tfree(in_padding_data);\n\t}\n\n\treturn -1;\n}\n\nint SSL_base64_decode(const char *in, unsigned char *out, int max_outlen)\n{\n\treturn SSL_base64_decode_ext(in, out, max_outlen, 0, 0);\n}\n\nint SSL_base64_encode(const void *in, int in_len, char *out)\n{\n\tint outlen = 0;\n\n\tif (in_len == 0) {\n\t\treturn 0;\n\t}\n\n\toutlen = EVP_EncodeBlock((unsigned char *)out, in, in_len);\n\tif (outlen < 0) {\n\t\tgoto errout;\n\t}\n\n\treturn outlen;\nerrout:\n\treturn -1;\n}\n\nint dns_is_quic_supported(void)\n{\n#if defined(OSSL_QUIC1_VERSION) && !defined (OPENSSL_NO_QUIC)\n\treturn 1;\n#else\n\treturn 0;\n#endif\n}"
  },
  {
    "path": "src/utils/stack.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define _GNU_SOURCE\n\n#include \"smartdns/tlog.h\"\n#include \"smartdns/util.h\"\n\n#include <dlfcn.h>\n#include <signal.h>\n#include <stdint.h>\n#include <unistd.h>\n\nvoid bug_ext(const char *file, int line, const char *func, const char *errfmt, ...)\n{\n\tva_list ap;\n\n\tva_start(ap, errfmt);\n\ttlog_vext(TLOG_FATAL, file, line, func, NULL, errfmt, ap);\n\tva_end(ap);\n\n\tprint_stack();\n\t/* trigger BUG */\n\tsleep(1);\n\traise(SIGSEGV);\n\n\twhile (1) {\n\t\tsleep(1);\n\t};\n}\n\n#ifdef HAVE_UNWIND_BACKTRACE\n\n#include <unwind.h>\n\nstruct backtrace_state {\n\tvoid **current;\n\tvoid **end;\n};\n\nstatic _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context, void *arg)\n{\n\tstruct backtrace_state *state = (struct backtrace_state *)(arg);\n\tuintptr_t pc = _Unwind_GetIP(context);\n\tif (pc) {\n\t\tif (state->current == state->end) {\n\t\t\treturn _URC_END_OF_STACK;\n\t\t}\n\n\t\t*state->current++ = (void *)(pc);\n\t}\n\treturn _URC_NO_REASON;\n}\n\nvoid print_stack(void)\n{\n\tconst size_t max_buffer = 30;\n\tvoid *buffer[max_buffer];\n\tint idx = 0;\n\n\tstruct backtrace_state state = {buffer, buffer + max_buffer};\n\t_Unwind_Backtrace(unwind_callback, &state);\n\tint frame_num = state.current - buffer;\n\tif (frame_num == 0) {\n\t\treturn;\n\t}\n\n\ttlog(TLOG_FATAL, \"Stack:\");\n\tfor (idx = 0; idx < frame_num; ++idx) {\n\t\tconst void *addr = buffer[idx];\n\t\tconst char *symbol = \"\";\n\n\t\tDl_info info;\n\t\tmemset(&info, 0, sizeof(info));\n\t\tif (dladdr(addr, &info) && info.dli_sname) {\n\t\t\tsymbol = info.dli_sname;\n\t\t}\n\n\t\tvoid *offset = (void *)((char *)(addr) - (char *)(info.dli_fbase));\n\t\ttlog(TLOG_FATAL, \"#%.2d: %p %s() from %s+%p\", idx + 1, addr, symbol, info.dli_fname, offset);\n\t}\n}\n#else\nvoid print_stack(void) {}\n#endif"
  },
  {
    "path": "src/utils/tls_header_parse.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/util.h\"\n\n#define SERVER_NAME_LEN 256\n#define TLS_HEADER_LEN 5\n#define TLS_HANDSHAKE_CONTENT_TYPE 0x16\n#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01\n#ifndef MIN\n#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))\n#endif\n\nstatic int parse_extensions(const char *, size_t, char *, const char **);\nstatic int parse_server_name_extension(const char *, size_t, char *, const char **);\n\n/* Parse a TLS packet for the Server Name Indication extension in the client\n * hello handshake, returning the first server name found (pointer to static\n * array)\n *\n * Returns:\n *  >=0  - length of the hostname and updates *hostname\n *         caller is responsible for freeing *hostname\n *  -1   - Incomplete request\n *  -2   - No Host header included in this request\n *  -3   - Invalid hostname pointer\n *  -4   - malloc failure\n *  < -4 - Invalid TLS client hello\n */\nint parse_tls_header(const char *data, size_t data_len, char *hostname, const char **hostname_ptr)\n{\n\tchar tls_content_type = 0;\n\tchar tls_version_major = 0;\n\tchar tls_version_minor = 0;\n\tsize_t pos = TLS_HEADER_LEN;\n\tsize_t len = 0;\n\n\tif (hostname == NULL) {\n\t\treturn -3;\n\t}\n\n\t/* Check that our TCP payload is at least large enough for a TLS header */\n\tif (data_len < TLS_HEADER_LEN) {\n\t\treturn -1;\n\t}\n\n\t/* SSL 2.0 compatible Client Hello\n\t *\n\t * High bit of first byte (length) and content type is Client Hello\n\t *\n\t * See RFC5246 Appendix E.2\n\t */\n\tif (data[0] & 0x80 && data[2] == 1) {\n\t\treturn -2;\n\t}\n\n\ttls_content_type = data[0];\n\tif (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {\n\t\treturn -5;\n\t}\n\n\ttls_version_major = data[1];\n\ttls_version_minor = data[2];\n\tif (tls_version_major < 3) {\n\t\treturn -2;\n\t}\n\n\t/* TLS record length */\n\tlen = ((unsigned char)data[3] << 8) + (unsigned char)data[4] + TLS_HEADER_LEN;\n\tdata_len = MIN(data_len, len);\n\n\t/* Check we received entire TLS record length */\n\tif (data_len < len) {\n\t\treturn -1;\n\t}\n\n\t/*\n\t * Handshake\n\t */\n\tif (pos + 1 > data_len) {\n\t\treturn -5;\n\t}\n\tif (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {\n\t\treturn -5;\n\t}\n\n\t/* Skip past fixed length records:\n\t * 1\tHandshake Type\n\t * 3\tLength\n\t * 2\tVersion (again)\n\t * 32\tRandom\n\t * to\tSession ID Length\n\t */\n\tpos += 38;\n\n\t/* Session ID */\n\tif (pos + 1 > data_len) {\n\t\treturn -5;\n\t}\n\tlen = (unsigned char)data[pos];\n\tpos += 1 + len;\n\n\t/* Cipher Suites */\n\tif (pos + 2 > data_len) {\n\t\treturn -5;\n\t}\n\tlen = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];\n\tpos += 2 + len;\n\n\t/* Compression Methods */\n\tif (pos + 1 > data_len) {\n\t\treturn -5;\n\t}\n\tlen = (unsigned char)data[pos];\n\tpos += 1 + len;\n\n\tif (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {\n\t\treturn -2;\n\t}\n\n\t/* Extensions */\n\tif (pos + 2 > data_len) {\n\t\treturn -5;\n\t}\n\tlen = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];\n\tpos += 2;\n\n\tif (pos + len > data_len) {\n\t\treturn -5;\n\t}\n\treturn parse_extensions(data + pos, len, hostname, hostname_ptr);\n}\n\nstatic int parse_extensions(const char *data, size_t data_len, char *hostname, const char **hostname_ptr)\n{\n\tsize_t pos = 0;\n\tsize_t len = 0;\n\n\t/* Parse each 4 bytes for the extension header */\n\twhile (pos + 4 <= data_len) {\n\t\t/* Extension Length */\n\t\tlen = ((unsigned char)data[pos + 2] << 8) + (unsigned char)data[pos + 3];\n\n\t\t/* Check if it's a server name extension */\n\t\tif (data[pos] == 0x00 && data[pos + 1] == 0x00) {\n\t\t\t/* There can be only one extension of each type, so we break\n\t\t\t * our state and move p to beginning of the extension here */\n\t\t\tif (pos + 4 + len > data_len) {\n\t\t\t\treturn -5;\n\t\t\t}\n\t\t\treturn parse_server_name_extension(data + pos + 4, len, hostname, hostname_ptr);\n\t\t}\n\t\tpos += 4 + len; /* Advance to the next extension header */\n\t}\n\t/* Check we ended where we expected to */\n\tif (pos != data_len) {\n\t\treturn -5;\n\t}\n\n\treturn -2;\n}\n\nstatic int parse_server_name_extension(const char *data, size_t data_len, char *hostname, const char **hostname_ptr)\n{\n\tsize_t pos = 2; /* skip server name list length */\n\tsize_t len = 0;\n\n\twhile (pos + 3 < data_len) {\n\t\tlen = ((unsigned char)data[pos + 1] << 8) + (unsigned char)data[pos + 2];\n\n\t\tif (pos + 3 + len > data_len) {\n\t\t\treturn -5;\n\t\t}\n\n\t\tswitch (data[pos]) { /* name type */\n\t\tcase 0x00:           /* host_name */\n\t\t\tstrncpy(hostname, data + pos + 3, len);\n\t\t\tif (hostname_ptr) {\n\t\t\t\t*hostname_ptr = data + pos + 3;\n\t\t\t}\n\t\t\thostname[len] = '\\0';\n\n\t\t\treturn len;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t\tpos += 3 + len;\n\t}\n\t/* Check we ended where we expected to */\n\tif (pos != data_len) {\n\t\treturn -5;\n\t}\n\n\treturn -2;\n}"
  },
  {
    "path": "src/utils/url.c",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"smartdns/util.h\"\n\n#include <ctype.h>\n#include <linux/limits.h>\n#include <stdlib.h>\n\nint parse_uri(const char *value, char *scheme, char *host, int *port, char *path)\n{\n\treturn parse_uri_ext(value, scheme, NULL, NULL, host, port, path);\n}\n\nint urldecode(char *dst, int dst_maxlen, const char *src)\n{\n\tchar a, b;\n\tint len = 0;\n\twhile (*src) {\n\t\tif ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) {\n\t\t\tif (a >= 'a') {\n\t\t\t\ta -= 'a' - 'A';\n\t\t\t}\n\n\t\t\tif (a >= 'A') {\n\t\t\t\ta -= ('A' - 10);\n\t\t\t} else {\n\t\t\t\ta -= '0';\n\t\t\t}\n\n\t\t\tif (b >= 'a') {\n\t\t\t\tb -= 'a' - 'A';\n\t\t\t}\n\n\t\t\tif (b >= 'A') {\n\t\t\t\tb -= ('A' - 10);\n\t\t\t} else {\n\t\t\t\tb -= '0';\n\t\t\t}\n\t\t\t*dst++ = 16 * a + b;\n\t\t\tsrc += 3;\n\t\t} else if (*src == '+') {\n\t\t\t*dst++ = ' ';\n\t\t\tsrc++;\n\t\t} else {\n\t\t\t*dst++ = *src++;\n\t\t}\n\n\t\tlen++;\n\t\tif (len >= dst_maxlen - 1) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\t*dst++ = '\\0';\n\n\treturn len;\n}\n\nint parse_uri_ext(const char *value, char *scheme, char *user, char *password, char *host, int *port, char *path)\n{\n\tchar *scheme_end = NULL;\n\tint field_len = 0;\n\tconst char *process_ptr = value;\n\tchar user_pass_host_part[PATH_MAX];\n\tchar *user_password = NULL;\n\tchar *host_part = NULL;\n\n\tconst char *host_end = NULL;\n\n\tscheme_end = strstr(value, \"://\");\n\tif (scheme_end) {\n\t\tfield_len = scheme_end - value;\n\t\tif (scheme) {\n\t\t\tmemcpy(scheme, value, field_len);\n\t\t\tscheme[field_len] = 0;\n\t\t}\n\t\tprocess_ptr += field_len + 3;\n\t} else {\n\t\tif (scheme) {\n\t\t\tscheme[0] = '\\0';\n\t\t}\n\t}\n\n\thost_end = strstr(process_ptr, \"/\");\n\tif (host_end == NULL) {\n\t\thost_end = process_ptr + strlen(process_ptr);\n\t};\n\n\tfield_len = host_end - process_ptr;\n\tif (field_len >= (int)sizeof(user_pass_host_part)) {\n\t\treturn -1;\n\t}\n\tmemcpy(user_pass_host_part, process_ptr, field_len);\n\tuser_pass_host_part[field_len] = 0;\n\n\thost_part = strstr(user_pass_host_part, \"@\");\n\tif (host_part != NULL) {\n\t\t*host_part = '\\0';\n\t\thost_part = host_part + 1;\n\t\tuser_password = user_pass_host_part;\n\t\tchar *sep = strstr(user_password, \":\");\n\t\tif (sep != NULL) {\n\t\t\t*sep = '\\0';\n\t\t\tsep = sep + 1;\n\t\t\tif (password) {\n\t\t\t\tif (urldecode(password, 128, sep) < 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (user) {\n\t\t\tif (urldecode(user, 128, user_password) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t} else {\n\t\thost_part = user_pass_host_part;\n\t}\n\n\tif (host != NULL && parse_ip(host_part, host, port) != 0) {\n\t\treturn -1;\n\t}\n\n\tprocess_ptr += field_len;\n\n\tif (path) {\n\t\tstrcpy(path, process_ptr);\n\t}\n\treturn 0;\n}\n\nint parse_ip(const char *value, char *ip, int *port)\n{\n\tint offset = 0;\n\tchar *colon = NULL;\n\n\tcolon = strstr(value, \":\");\n\n\tif (strstr(value, \"[\")) {\n\t\t/* ipv6 with port */\n\t\tchar *bracket_end = strstr(value, \"]\");\n\t\tif (bracket_end == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\toffset = bracket_end - value - 1;\n\t\tmemcpy(ip, value + 1, offset);\n\t\tip[offset] = 0;\n\n\t\tcolon = strstr(bracket_end, \":\");\n\t\tif (colon) {\n\t\t\tcolon++;\n\t\t}\n\t} else if (colon && strstr(colon + 1, \":\")) {\n\t\t/* ipv6 without port */\n\t\tstrncpy(ip, value, MAX_IP_LEN);\n\t\tcolon = NULL;\n\t} else {\n\t\t/* ipv4 */\n\t\tcolon = strstr(value, \":\");\n\t\tif (colon == NULL) {\n\t\t\t/* without port */\n\t\t\tstrncpy(ip, value, MAX_IP_LEN);\n\t\t} else {\n\t\t\t/* with port */\n\t\t\toffset = colon - value;\n\t\t\tcolon++;\n\t\t\tmemcpy(ip, value, offset);\n\t\t\tip[offset] = 0;\n\t\t}\n\t}\n\n\tif (colon) {\n\t\t/* get port num */\n\t\t*port = atoi(colon);\n\t} else {\n\t\t*port = PORT_NOT_DEFINED;\n\t}\n\n\tif (ip[0] == 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "systemd/smartdns.service.in",
    "content": "[Unit]\nDescription=SmartDNS Server\nAfter=network.target\nBefore=network-online.target\nBefore=nss-lookup.target\nWants=nss-lookup.target\nStartLimitBurst=0\nStartLimitIntervalSec=60\n\n[Service]\nType=forking\nPIDFile=@RUNSTATEDIR@/smartdns.pid\nEnvironmentFile=@SYSCONFDIR@/default/smartdns\nExecStart=@SBINDIR@/smartdns -p @RUNSTATEDIR@/smartdns.pid $SMART_DNS_OPTS\nRestart=always\nRestartSec=2\nTimeoutStopSec=15\n\n[Install]\nWantedBy=multi-user.target\nAlias=smartdns.service\n"
  },
  {
    "path": "test/Makefile",
    "content": "\n# Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n#\n# smartdns is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# smartdns is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nSMARTDNS_SRC_DIR=../src\n\nBIN=test.bin\nSMARTDNS_TEST_LIB=$(SMARTDNS_SRC_DIR)/libsmartdns-test.a\n\nCXXFLAGS += -g\nCXXFLAGS += -DTEST\nCXXFLAGS += -I./ -I../src -I../src/include\nCXXFLAGS += -fno-omit-frame-pointer -Wstrict-aliasing -funwind-tables\n\nTEST_SOURCES := $(wildcard *.cc) $(wildcard */*.cc) $(wildcard */*/*.cc)\nTEST_OBJECTS := $(patsubst %.cc, %.o, $(TEST_SOURCES))\nOBJS += $(TEST_OBJECTS)\n\nSMARTDNS_SRC_FILES := $(wildcard $(SMARTDNS_SRC_DIR)/*.c) $(wildcard $(SMARTDNS_SRC_DIR)/*.h) $(wildcard $(SMARTDNS_SRC_DIR)/lib/*.c)\n\nifeq ($(filter -j,$(MAKEFLAGS)),)\nMAKEFLAGS += -j$(shell nproc)\nendif\n\nLDFLAGS += -lssl -lcrypto -lpthread -ldl -lgtest -lstdc++ -lm -rdynamic\nLDFLAGS += $(EXTRA_LDFLAGS)\n\n.PHONY: all clean test $(SMARTDNS_TEST_LIB)\n\nall: $(BIN)\n\n$(BIN) : $(OBJS) $(SMARTDNS_TEST_LIB)\n\t$(CC) $^  -o $@ $(LDFLAGS)\n\ntest: $(BIN)\n\t./$(BIN)\n\n$(SMARTDNS_TEST_LIB): \n\t$(MAKE) DEBUG=1 -C $(SMARTDNS_SRC_DIR) libsmartdns-test.a\n\nclean:\n\t$(RM) $(OBJS) $(BIN)\n\t$(MAKE) -C $(SMARTDNS_SRC_DIR) clean\n"
  },
  {
    "path": "test/cases/test-address.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass Address : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Address, soa)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/#4\naddress /b.com/#6\naddress /c.com/#\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetData(),\n\t\t\t  \"a.gtld-servers.net. nstld.verisign-grs.com. 1800 1800 900 604800 86400\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\n\tASSERT_TRUE(client.Query(\"b.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetData(),\n\t\t\t  \"a.gtld-servers.net. nstld.verisign-grs.com. 1800 1800 900 604800 86400\");\n\n\tASSERT_TRUE(client.Query(\"c.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"c.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetData(),\n\t\t\t  \"a.gtld-servers.net. nstld.verisign-grs.com. 1800 1800 900 604800 86400\");\n\n\tASSERT_TRUE(client.Query(\"c.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"c.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetData(),\n\t\t\t  \"a.gtld-servers.net. nstld.verisign-grs.com. 1800 1800 900 604800 86400\");\n}\n\nTEST_F(Address, ip)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/10.10.10.10\naddress /a.com/64:ff9b::1010:1010\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"10.10.10.10\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::1010:1010\");\n}\n\nTEST_F(Address, multiaddress)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/10.10.10.10,11.11.11.11,22.22.22.22\naddress /a.com/64:ff9b::1010:1010,64:ff9b::1111:1111,64:ff9b::2222:2222\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tstd::map<std::string, smartdns::DNSRecord *> result;\n\n\tASSERT_EQ(client.GetAnswerNum(), 3);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tauto answers = client.GetAnswer();\n\tfor (int i = 0; i < client.GetAnswerNum(); i++) {\n\t\tresult[client.GetAnswer()[i].GetData()] = &answers[i];\n\t}\n\n\tASSERT_NE(result.find(\"10.10.10.10\"), result.end());\n\tauto check_result = result[\"10.10.10.10\"];\n\tEXPECT_EQ(check_result->GetName(), \"a.com\");\n\tEXPECT_EQ(check_result->GetTTL(), 600);\n\tEXPECT_EQ(check_result->GetType(), \"A\");\n\tEXPECT_EQ(check_result->GetData(), \"10.10.10.10\");\n\n\tASSERT_NE(result.find(\"11.11.11.11\"), result.end());\n\tcheck_result = result[\"11.11.11.11\"];\n\tEXPECT_EQ(check_result->GetName(), \"a.com\");\n\tEXPECT_EQ(check_result->GetTTL(), 600);\n\tEXPECT_EQ(check_result->GetType(), \"A\");\n\tEXPECT_EQ(check_result->GetData(), \"11.11.11.11\");\n\n\tASSERT_NE(result.find(\"22.22.22.22\"), result.end());\n\tcheck_result = result[\"22.22.22.22\"];\n\tEXPECT_EQ(check_result->GetName(), \"a.com\");\n\tEXPECT_EQ(check_result->GetTTL(), 600);\n\tEXPECT_EQ(check_result->GetType(), \"A\");\n\tEXPECT_EQ(check_result->GetData(), \"22.22.22.22\");\n\n\tresult.clear();\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 3);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tanswers = client.GetAnswer();\n\tfor (int i = 0; i < client.GetAnswerNum(); i++) {\n\t\tresult[client.GetAnswer()[i].GetData()] = &answers[i];\n\t}\n\n\tASSERT_NE(result.find(\"64:ff9b::1010:1010\"), result.end());\n\tcheck_result = result[\"64:ff9b::1010:1010\"];\n\tEXPECT_EQ(check_result->GetName(), \"a.com\");\n\tEXPECT_EQ(check_result->GetTTL(), 600);\n\tEXPECT_EQ(check_result->GetType(), \"AAAA\");\n\tEXPECT_EQ(check_result->GetData(), \"64:ff9b::1010:1010\");\n\n\tASSERT_NE(result.find(\"64:ff9b::1111:1111\"), result.end());\n\tcheck_result = result[\"64:ff9b::1111:1111\"];\n\tEXPECT_EQ(check_result->GetName(), \"a.com\");\n\tEXPECT_EQ(check_result->GetTTL(), 600);\n\tEXPECT_EQ(check_result->GetType(), \"AAAA\");\n\tEXPECT_EQ(check_result->GetData(), \"64:ff9b::1111:1111\");\n\n\tASSERT_NE(result.find(\"64:ff9b::2222:2222\"), result.end());\n\tcheck_result = result[\"64:ff9b::2222:2222\"];\n\tEXPECT_EQ(check_result->GetName(), \"a.com\");\n\tEXPECT_EQ(check_result->GetTTL(), 600);\n\tEXPECT_EQ(check_result->GetType(), \"AAAA\");\n\tEXPECT_EQ(check_result->GetData(), \"64:ff9b::2222:2222\");\n}\n\nTEST_F(Address, soa_sub_ip)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/192.168.1.1\naddress /com/#\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"192.168.1.1\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"b.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n}\n\nTEST_F(Address, set_ipv4_query_ipv6)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/192.168.1.1\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"192.168.1.1\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n\nTEST_F(Address, set_ipv6_query_ipv4)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/64:ff9b::1010:1010\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::1010:1010\");\n}\n\nTEST_F(Address, set_ipv4_allow_ipv6)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/192.168.1.1\naddress /b.a.com/-6\naddress /com/#\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"192.168.1.1\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"c.a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"b.a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\n\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n}\n\nTEST_F(Address, set_both_ipv4_ipv6)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/192.168.1.1\naddress /b.a.com/64:ff9b::1010:1010\naddress /com/#\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"192.168.1.1\");\n\n\tASSERT_TRUE(client.Query(\"c.a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"b.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"192.168.1.1\");\n\n\tASSERT_TRUE(client.Query(\"b.a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::1010:1010\");\n\n\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n}\n\nTEST_F(Address, ipv4_in_Test)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/::ffff:192.168.1.1\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"::ffff:192.168.1.1\");\n\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n\nTEST_F(Address, group_ignore_address)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind [::]:60054 -group ignore-address\nserver 127.0.0.1:61053\naddress 2.3.4.5\nspeed-check-mode none\naddress /a.com/2.3.4.6\ngroup-begin ignore-address\n\taddress -\ngroup-end \n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2.3.4.6\");\n\n\tASSERT_TRUE(client.Query(\"b.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2.3.4.5\");\n\n\tASSERT_TRUE(client.Query(\"b.com A\", 60054));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}"
  },
  {
    "path": "test/cases/test-audit.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/dns.h\"\n#include \"gtest/gtest.h\"\n\nclass Audit : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Audit, log)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tDefer\n\t{\n\t\tunlink(\"/tmp/audit.log\");\n\t};\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\n audit-file /tmp/audit.log\n audit-enable yes\n server 127.0.0.1:61053\n )\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tstd::ifstream audit_file(\"/tmp/audit.log\");\n\tstd::string line;\n\tbool found = false;\n\twhile (std::getline(audit_file, line)) {\n\t\tif (line.find(\"a.com\") != std::string::npos) {\n\t\t\tfound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\taudit_file.close();\n\n\tASSERT_TRUE(found) << \"Audit log for a.com not found\";\n}\n"
  },
  {
    "path": "test/cases/test-bind.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <netinet/in.h>\n#include <openssl/rand.h>\n#include <sys/socket.h>\n\nTEST(Bind, tls)\n{\n\tDefer\n\t{\n\t\tunlink(\"/tmp/smartdns-cert.pem\");\n\t\tunlink(\"/tmp/smartdns-key.pem\");\n\t};\n\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver-tls 127.0.0.1:60053 -no-check-certificate\n)\"\"\");\n\tserver_wrap.Start(R\"\"\"(bind-tls [::]:60053\naddress /example.com/1.2.3.4\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(Bind, https)\n{\n\tDefer\n\t{\n\t\tunlink(\"/tmp/smartdns-cert.pem\");\n\t\tunlink(\"/tmp/smartdns-key.pem\");\n\t};\n\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053 -no-check-certificate\n)\"\"\");\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053\naddress /example.com/1.2.3.4\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(Bind, udp_tcp)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\tdns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr);\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(\nbind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com +tcp\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_GE(client.GetAnswer()[0].GetTTL(), 609);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(Bind, self)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(\nbind [::]:60053 -group self\nserver 127.0.0.1:61053 -group self\nbind [::]:61053 -group upstream  \nserver 127.0.0.1:62053 -group upstream\n)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 100);\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(Bind, nocache)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\tusleep(15 * 1000);\n\t\tdns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr);\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(\nbind [::]:60053 --no-cache\nbind-tcp [::]:60053\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 611);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tEXPECT_GT(client.GetQueryTime(), 10);\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 611);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(Bind, device)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(\nbind [::]:60053@lo\nserver 127.0.0.1:62053\n)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 100);\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(Bind, malformed_packet)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(\nbind [::]:60053@lo\nserver 127.0.0.1:62053\nlog-level info\n)\"\"\");\n\n\tint sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n\tASSERT_NE(sockfd, -1);\n\tDefer\n\t{\n\t\tclose(sockfd);\n\t};\n\n\tstruct sockaddr_in server_addr;\n\tserver_addr.sin_family = AF_INET;\n\tserver_addr.sin_port = htons(60053);\n\tserver_addr.sin_addr.s_addr = inet_addr(\"127.0.0.1\");\n\n\tfor (int i = 0; i < 100000; i++) {\n\t\tchar buf[4096];\n\t\tint len = random() % 4096;\n\t\tif (len <= 0) {\n\t\t\tlen = 1;\n\t\t}\n\n\t\tRAND_bytes((unsigned char *)buf, len);\n\t\tsendto(sockfd, buf, len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr));\n\t}\n\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 100);\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(Bind, group)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"9.10.11.12\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:63053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind [::]:60153 -group g1\nbind [::]:60253 -group g2\nserver 127.0.0.1:61053\nserver 127.0.0.1:62053 -group g1 -exclude-default-group\nserver 127.0.0.1:63053 -group g2 -exclude-default-group\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"9.10.11.12\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60153));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60253));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n}\n\nTEST(Bind, Get)\n{\n    smartdns::Server server;\n\n\tint ret = system(\"which curl > /dev/null 2>&1\");\n\tif (ret != 0) {\n\t\tGTEST_SKIP() << \"curl not found, skip test\";\n\t}\n\n    // Start server with bind-https and logging to file\n    server.Start(R\"\"\"(bind-https [::]:60053 -alpn h2\naddress /example.com/1.2.3.4\nlog-level debug\n)\"\"\");\n\n    // Send GET request using curl\n    std::string cmd = \"curl -k --http2 'https://127.0.0.1:60053/dns-query?dns=AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE=' > /dev/null 2>&1\";\n    ret = system(cmd.c_str());\n    ASSERT_EQ(ret, 0);\n}\n"
  },
  {
    "path": "test/cases/test-bootstrap.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass BootStrap : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(BootStrap, bootstrap)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"127.0.0.1\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:62053 -bootstrap-dns\nserver udp://example.com:61053 -group test\n)\"\"\");\n\tsmartdns::Client client;\n\tusleep(2500000);\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n"
  },
  {
    "path": "test/cases/test-cache.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n#include <fcntl.h>\n#include <fstream>\n#include <sys/stat.h>\n#include <sys/types.h>\n\n/* clang-format off */\n#include \"smartdns/dns_cache.h\"\n/* clang-format on */\n\nclass Cache : public ::testing::Test\n{\n  protected:\n\tvoid SetUp() override {}\n\n\tvoid TearDown() override {}\n};\n\nTEST_F(Cache, min)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ncache-size 1\nrr-ttl-min 1\nspeed-check-mode none\nresponse-mode fastest-response\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Cache, max_reply_ttl)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ncache-size 1\nrr-ttl-min 600\nrr-ttl-reply-max 5\nspeed-check-mode none\nresponse-mode fastest-response\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 5);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tsleep(1);\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 5);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Cache, max_reply_ttl_expired)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ncache-size 1\nrr-ttl-min 600\nrr-ttl-reply-max 6\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GE(client.GetAnswer()[0].GetTTL(), 5);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Cache, prefetch)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"9.10.11.12\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:63053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"9.10.11.12\", 60, 110);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind [::]:60153 -group g1\nserver 127.0.0.1:61053\nserver 127.0.0.1:62053 -group g1 -exclude-default-group\nserver 127.0.0.1:63053 -group g2\nprefetch-domain yes\nrr-ttl-max 2\nserve-expired no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60153));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tsleep(1);\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\n\tsleep(1);\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60153));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Cache, nocache)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tusleep(15000);\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 0, addr);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ncache-size 100\nrr-ttl-min 600\nrr-ttl-reply-max 5\ndomain-rules /a.com/ --no-cache\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 5);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tEXPECT_GT(client.GetQueryTime(), 10);\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 5);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Cache, save_file)\n{\n\tsmartdns::MockServer server_upstream;\n\tauto cache_file = \"/tmp/smartdns_cache.\" + smartdns::GenerateRandomString(10);\n\tstd::string conf = R\"\"\"(\nbind [::]:60053@lo\nserver 127.0.0.1:62053\ncache-persist yes\ndualstack-ip-selection no\n)\"\"\";\n\n\tconf += \"cache-file \" + cache_file;\n\tDefer\n\t{\n\t\tunlink(cache_file.c_str());\n\t};\n\n\tserver_upstream.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\t{\n\t\tsmartdns::Server server;\n\t\tserver.Start(conf);\n\t\tsmartdns::Client client;\n\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_LT(client.GetQueryTime(), 100);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t\tserver.Stop();\n\t\tusleep(200 * 1000);\n\t}\n\n\tASSERT_EQ(access(cache_file.c_str(), F_OK), 0);\n\n\tstd::fstream fs(cache_file, std::ios::in);\n\tstruct dns_cache_file head;\n\tmemset(&head, 0, sizeof(head));\n\tfs.read((char *)&head, sizeof(head));\n\tEXPECT_EQ(head.magic, MAGIC_NUMBER);\n\tEXPECT_EQ(head.cache_number, 1);\n}\n\nTEST_F(Cache, corrupt_file)\n{\n\tsmartdns::MockServer server_upstream;\n\tauto cache_file = \"/tmp/smartdns_cache.\" + smartdns::GenerateRandomString(10);\n\tstd::string conf = R\"\"\"(\nbind [::]:60053@lo\nserver 127.0.0.1:62053\ndualstack-ip-selection no\ncache-persist yes\n)\"\"\";\n\n\tconf += \"cache-file \" + cache_file;\n\tDefer\n\t{\n\t\tunlink(cache_file.c_str());\n\t};\n\n\tserver_upstream.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\t{\n\t\tsmartdns::Server server;\n\t\tserver.Start(conf);\n\t\tsmartdns::Client client;\n\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_LT(client.GetQueryTime(), 100);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t\tserver.Stop();\n\t\tusleep(200 * 1000);\n\t}\n\n\tASSERT_EQ(access(cache_file.c_str(), F_OK), 0);\n\n\tint fd = open(cache_file.c_str(), O_RDWR);\n\tASSERT_NE(fd, -1);\n\tsrandom(time(NULL));\n\toff_t file_size = lseek(fd, 0, SEEK_END);\n\toff_t offset = random() % (file_size - 300);\n\tstd::cout << \"try make corrupt at \" << offset << \", file size: \" << file_size << std::endl;\n\tlseek(fd, offset, SEEK_SET);\n\tfor (int i = 0; i < 300; i++) {\n\t\tunsigned char c = random() % 256;\n\t\twrite(fd, &c, 1);\n\t}\n\tclose(fd);\n\t{\n\t\tsmartdns::Server server;\n\t\tserver.Start(conf);\n\t\tsmartdns::Client client;\n\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_LT(client.GetQueryTime(), 100);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t\tserver.Stop();\n\t\tusleep(200 * 1000);\n\t}\n}\n\nTEST_F(Cache, cname)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tstd::string cname = \"cname.\" + domain;\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\tdns_add_domain(request->response_packet, domain.c_str(), DNS_T_A, DNS_C_IN);\n\t\tdns_add_CNAME(request->response_packet, DNS_RRS_AN, domain.c_str(), 300, cname.c_str());\n\t\tdns_add_A(request->response_packet, DNS_RRS_AN, cname.c_str(), 300, addr);\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ncache-size 100\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GE(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"cname.a.com.\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetName(), \"cname.a.com\");\n\tEXPECT_GE(client.GetAnswer()[1].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"cname.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"cname.a.com\");\n\tEXPECT_GE(client.GetAnswer()[0].GetTTL(), 590);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}"
  },
  {
    "path": "test/cases/test-client-rule.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass ClientRule : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(ClientRule, rule)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -g client -e \nserver udp://127.0.0.1:62053\nclient-rules 127.0.0.1 -g client\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(ClientRule, group_begin_group_end)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:62053\ngroup-begin client\nserver udp://127.0.0.1:61053 -e \nclient-rules 127.0.0.1 \ngroup-end\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(ClientRule, acl)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\n\tauto ipaddr = smartdns::GetAvailableIPAddresses();\n\tif (ipaddr.size() < 0) {\n\t\treturn;\n\t}\n\n\tstd::string confstr = R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nacl-enable yes\n)\"\"\";\n\n\tconfstr += \"client-rules \" + ipaddr[0] + \"\\n\";\n\n\tserver.Start(confstr);\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tEXPECT_EQ(client.GetStatus(), \"REFUSED\");\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60053, ipaddr[0]));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\n\nTEST_F(ClientRule, in_group_nameserver) \n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::MockServer server_upstream3;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream3.Start(\"udp://0.0.0.0:63053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tusleep(10000);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"4.5.6.7\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:62053\nserver udp://127.0.0.1:63053 -g in_group\ngroup-begin client\nserver udp://127.0.0.1:61053 -e \nclient-rules 127.0.0.1 \nnameserver /cn/in_group\ngroup-end\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"b.cn\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.cn\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"7.8.9.10\");\n}"
  },
  {
    "path": "test/cases/test-cname.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass Cname : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Cname, cname)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tEXPECT_EQ(request->domain, \"e.com\");\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\ncname /a.com/b.com\ncname /b.com/c.com\ncname /c.com/d.com\ncname /d.com/e.com\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"b.com.\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Cname, subdomain1)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tif (request->domain == \"s.a.com\") {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\ncname /a.com/s.a.com\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"s.a.com.\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"4.5.6.7\");\n}\n\nTEST_F(Cname, subdomain2)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tif (request->domain == \"a.s.a.com\") {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\ncname /a.com/s.a.com\ncname /s.a.com/a.s.a.com\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"s.a.com.\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"4.5.6.7\");\n}\n\nTEST_F(Cname, loop)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tif (request->domain == \"s.a.com\") {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\ncname /a.com/c.a.com\ncname /c.a.com/s.a.com\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"c.a.com.\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"4.5.6.7\");\n}\n\nTEST_F(Cname, query_cname)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tif (request->domain == \"s.a.com\") {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\ncname /a.com/s.a.com\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"s.a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"s.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"4.5.6.7\");\n}\n"
  },
  {
    "path": "test/cases/test-ddns.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass DDNS : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(DDNS, smartdns)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"smartdns A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"smartdns\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"127.0.0.1\");\n\n\tASSERT_TRUE(client.Query(\"smartdns AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"smartdns\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"::1\");\n}\n\nTEST_F(DDNS, ddns)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nddns-domain test.ddns.com\nddns-domain test.ddns.org\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"test.ddns.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"test.ddns.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"127.0.0.1\");\n\n\tASSERT_TRUE(client.Query(\"test.ddns.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"test.ddns.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"::1\");\n\n\tASSERT_TRUE(client.Query(\"test.ddns.org A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"test.ddns.org\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"127.0.0.1\");\n\n\tASSERT_TRUE(client.Query(\"test.ddns.org AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"test.ddns.org\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"::1\");\n}\n"
  },
  {
    "path": "test/cases/test-ddr.cc",
    "content": "#include \"client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass DDR : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(DDR, DDR_RESPONSE)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.Start(R\"\"\"(bind [::]:60053 -ddr\nbind-tls [::]:60153 -ddr\nbind-https [::]:60253 -ddr -alpn h2\nbind-tcp [::]:60353\nserver 127.0.0.1:61053\nlog-console yes\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"_dns.resolver.arpa SVCB\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tbool has_doq = false;\n\tbool has_dot = false;\n\n\tfor (auto &ans : client.GetAnswer()) {\n\t\tEXPECT_EQ(ans.GetName(), \"_dns.resolver.arpa\");\n\t\tEXPECT_EQ(ans.GetType(), \"SVCB\");\n\t\tif (ans.GetData().find(\"alpn=\\\"h2\\\"\") != std::string::npos) {\n\t\t\thas_doq = true;\n\t\t}\n\t\tif (ans.GetData().find(\"alpn=\\\"dot\\\"\") != std::string::npos) {\n\t\t\thas_dot = true;\n\t\t}\n\t}\n\tEXPECT_TRUE(has_doq);\n\tEXPECT_TRUE(has_dot);\n}\n\nTEST_F(DDR, DDR_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tls [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"_dns.resolver.arpa SVCB\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"_dns.resolver.arpa\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n}\n"
  },
  {
    "path": "test/cases/test-discard-block-ip.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nTEST(DiscardBlockIP, first_ping)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tunsigned char addr[4] = {0, 0, 0, 0};\n\t\tdns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr);\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\tusleep(20000);\n\t\tdns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr);\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nserver 127.0.0.1:62053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 100);\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(DiscardBlockIP, first_response)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tunsigned char addr[4] = {0, 0, 0, 0};\n\t\tdns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr);\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\tusleep(20000);\n\t\tdns_add_A(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 611, addr);\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nserver 127.0.0.1:62053\nresponse-mode fastest-response\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}"
  },
  {
    "path": "test/cases/test-dns64.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass DNS64 : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(DNS64, no_dualstack)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndns64 64:ff9b::/96\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n}\n\nTEST_F(DNS64, with_dualstack)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 200);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndns64 64:ff9b::/96\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\tEXPECT_LT(client.GetQueryTime(), 100);\n\n\tusleep(500000);\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 500);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\tEXPECT_LT(client.GetQueryTime(), 100);\n}\n\nTEST_F(DNS64, with_AAAA_result)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 300);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 90);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 100);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndns64 64:ff9b::/96\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 1200);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n}\n\nTEST_F(DNS64, ipv4_in_ipv6)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"::ffff:1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"::ffff:1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 90);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 1200);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"::ffff:1.2.3.4\");\n}\n\nTEST_F(DNS64, ipv4only_arpa)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"::ffff:1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndns64 64:ff9b::/96\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"ipv4only.arpa AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"ipv4only.arpa A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"ipv4only.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"192.0.0.170\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetName(), \"ipv4only.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"192.0.0.171\");\n}\n\nTEST_F(DNS64, ipv4only_arpa_nodns64)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"::ffff:1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"::ffff:1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 90);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 1200);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 590);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"::ffff:1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 1200);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 590);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n"
  },
  {
    "path": "test/cases/test-domain-rule.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass DomainRule : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(DomainRule, bogus_nxdomain)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tif (request->domain == \"a.com\") {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"10.11.12.13\", 611);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -blacklist-ip\nbogus-nxdomain 10.0.0.0/8\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n"
  },
  {
    "path": "test/cases/test-domain-set.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass DomainSet : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(DomainSet, set_add)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tsmartdns::TempFile file_set;\n\tstd::vector<std::string> domain_list;\n\tint count = 16;\n\tstd::string config = \"domain-set -name test-set -file \" + file_set.GetPath() + \"\\n\";\n\tconfig += R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndomain-rules /domain-set:test-set/ -c none --dualstack-ip-selection no -a 9.9.9.9\n)\"\"\";\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tfor (int i = 0; i < count; i++) {\n\t\tauto domain = smartdns::GenerateRandomString(10) + \".\" + smartdns::GenerateRandomString(3);\n\t\tfile_set.Write(domain);\n\t\tfile_set.Write(\"\\n\");\n\t\tdomain_list.emplace_back(domain);\n\t}\n\n\tstd::cout << config << std::endl;\n\tserver.Start(config);\n\tsmartdns::Client client;\n\n\tfor (auto &domain : domain_list) {\n\t\tASSERT_TRUE(client.Query(domain, 60053));\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), domain);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"9.9.9.9\");\n\t}\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n"
  },
  {
    "path": "test/cases/test-dualstack.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass DualStack : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(DualStack, ipv4_prefer)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 150);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tusleep(220 * 1000);\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 20);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n}\n\nTEST_F(DualStack, ipv6_prefer_allow_force_AAAA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 70);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 75);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping\ndualstack-ip-allow-force-AAAA yes\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tusleep(220 * 1000);\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 20);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"2001:db8::2\");\n}\n\nTEST_F(DualStack, ipv6_prefer_without_ipv4)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 70);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 100);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping\ndualstack-ip-allow-force-AAAA yes\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tusleep(220 * 1000);\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 20);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n}\n\nTEST_F(DualStack, ipv6_no_speed)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 10000);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 10000);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tusleep(220 * 1000);\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAuthority()[0].GetTTL(), 597);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n}\n\nTEST_F(DualStack, ipv4_no_response)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_NO_RESPONSE;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10000);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 10000);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 110);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection yes\nspeed-check-mode ping\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"SERVFAIL\");\n\n\tusleep(220 * 1000);\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 20);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 590);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[1].GetTTL(), 590);\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"2001:db8::2\");\n}\n"
  },
  {
    "path": "test/cases/test-edns.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass EDNS : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(EDNS, client)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstruct dns_opt_ecs ecs;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count > 0) {\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tswitch (rrs->type) {\n\t\t\t\tcase DNS_OPT_T_ECS: {\n\t\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\t\tif (ret != 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tdns_add_OPT_ECS(request->response_packet, &ecs);\n\n\t\t\t\t} break;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053 \nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A +subnet=2.2.2.2/24\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tASSERT_EQ(client.GetOpt().size(), 2);\n\tEXPECT_EQ(client.GetOpt()[1], \"CLIENT-SUBNET: 2.2.2.0/24/0\");\n\tEXPECT_EQ(ecs.family, 1);\n\tEXPECT_EQ(ecs.source_prefix, 24);\n\tEXPECT_EQ(ecs.scope_prefix, 0);\n\tunsigned char edns_addr[4] = {2, 2, 2, 0};\n\tEXPECT_EQ(memcmp(ecs.addr, &edns_addr, 4), 0);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(EDNS, server)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstruct dns_opt_ecs ecs;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count > 0) {\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tswitch (rrs->type) {\n\t\t\t\tcase DNS_OPT_T_ECS: {\n\t\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\t\tif (ret != 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tdns_add_OPT_ECS(request->response_packet, &ecs);\n\n\t\t\t\t} break;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053 -subnet=2.2.2.0/24\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(ecs.family, 1);\n\tEXPECT_EQ(ecs.source_prefix, 24);\n\tEXPECT_EQ(ecs.scope_prefix, 0);\n\tunsigned char edns_addr[4] = {2, 2, 2, 0};\n\tEXPECT_EQ(memcmp(ecs.addr, &edns_addr, 4), 0);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(EDNS, server_v6)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstruct dns_opt_ecs ecs;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count > 0) {\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tswitch (rrs->type) {\n\t\t\t\tcase DNS_OPT_T_ECS: {\n\t\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\t\tif (ret != 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tdns_add_OPT_ECS(request->response_packet, &ecs);\n\n\t\t\t\t} break;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053 -subnet=64:ff9b::/96\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(ecs.family, 2);\n\tEXPECT_EQ(ecs.source_prefix, 96);\n\tEXPECT_EQ(ecs.scope_prefix, 0);\n\tunsigned char edns_addr[16] = {00, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n\tEXPECT_EQ(memcmp(ecs.addr, &edns_addr, 16), 0);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(EDNS, edns_client_subnet)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstruct dns_opt_ecs ecs;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count > 0) {\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tswitch (rrs->type) {\n\t\t\t\tcase DNS_OPT_T_ECS: {\n\t\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\t\tif (ret != 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tdns_add_OPT_ECS(request->response_packet, &ecs);\n\n\t\t\t\t} break;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053 \nspeed-check-mode none\nedns-client-subnet 2.2.2.2/24\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(ecs.family, 1);\n\tEXPECT_EQ(ecs.source_prefix, 24);\n\tEXPECT_EQ(ecs.scope_prefix, 0);\n\tunsigned char edns_addr[4] = {2, 2, 2, 0};\n\tEXPECT_EQ(memcmp(ecs.addr, &edns_addr, 4), 0);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(EDNS, edns_client_subnet_v6)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstruct dns_opt_ecs ecs;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count > 0) {\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tswitch (rrs->type) {\n\t\t\t\tcase DNS_OPT_T_ECS: {\n\t\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\t\tif (ret != 0) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tdns_add_OPT_ECS(request->response_packet, &ecs);\n\n\t\t\t\t} break;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053 \nspeed-check-mode none\nedns-client-subnet 64:ff9b::/96\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(ecs.family, 2);\n\tEXPECT_EQ(ecs.source_prefix, 96);\n\tEXPECT_EQ(ecs.scope_prefix, 0);\n\tunsigned char edns_addr[16] = {00, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n\tEXPECT_EQ(memcmp(ecs.addr, &edns_addr, 16), 0);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n"
  },
  {
    "path": "test/cases/test-group.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass Group : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Group, conf_file)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\tstd::string file = \"/tmp/smartdns_conf_file\" + smartdns::GenerateRandomString(5) + \".conf\";\n\tstd::ofstream ofs(file);\n\tASSERT_TRUE(ofs.is_open());\n\tDefer\n\t{\n\t\tofs.close();\n\t\tunlink(file.c_str());\n\t};\n\n\tofs << R\"\"\"(\nserver udp://127.0.0.1:61053 -e\nclient-rules 127.0.0.1\naddress /a.com/1.1.1.1\ndomain-rules /b.com/ -address 4.5.6.7\n# should pop all groups\ngroup-begin dummy\naddress /a.com/9.9.9.9\n)\"\"\";\n\tofs.flush();\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nconf-file /tmp/smartdns_conf_file*.conf -g client\nserver udp://127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"4.5.6.7\");\n\n\tauto ipaddr = smartdns::GetAvailableIPAddresses();\n\tif (ipaddr.size() > 0) {\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t}\n}\n\nTEST_F(Group, conf_file_ip_rule)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\tstd::string file = \"/tmp/smartdns_conf_file\" + smartdns::GenerateRandomString(5) + \".conf\";\n\tstd::ofstream ofs(file);\n\tASSERT_TRUE(ofs.is_open());\n\tDefer\n\t{\n\t\tofs.close();\n\t\tunlink(file.c_str());\n\t};\n\n\tofs << R\"\"\"(\nserver udp://127.0.0.1:61053 -e\nclient-rules 127.0.0.1\nignore-ip 7.8.9.10\ngroup-begin dummy\nignore-ip 1.2.3.4\nignore-ip 7.8.9.10\n)\"\"\";\n\tofs.flush();\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 20);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nconf-file /tmp/smartdns_conf_file*.conf -g client\nserver udp://127.0.0.1:61053\nignore-ip 1.2.3.4\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tauto ipaddr = smartdns::GetAvailableIPAddresses();\n\tif (ipaddr.size() > 0) {\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"7.8.9.10\");\n\t}\n}\n\nTEST_F(Group, speed_check)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\tstd::string file = \"/tmp/smartdns_conf_file\" + smartdns::GenerateRandomString(5) + \".conf\";\n\tstd::ofstream ofs(file);\n\tASSERT_TRUE(ofs.is_open());\n\tDefer\n\t{\n\t\tofs.close();\n\t\tunlink(file.c_str());\n\t};\n\n\tofs << R\"\"\"(\nserver udp://127.0.0.1:61053 -e\nclient-rules 127.0.0.1\nspeed-check-mode none\n)\"\"\";\n\tofs.flush();\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 20);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nconf-file /tmp/smartdns_conf_file*.conf -g client\nserver udp://127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"7.8.9.10\");\n\n\tauto ipaddr = smartdns::GetAvailableIPAddresses();\n\tif (ipaddr.size() > 0) {\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"7.8.9.10\");\n\t}\n}\n\nTEST_F(Group, conf_inherit)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstd::string file = \"/tmp/smartdns_conf_file\" + smartdns::GenerateRandomString(5) + \".conf\";\n\tstd::ofstream ofs(file);\n\tASSERT_TRUE(ofs.is_open());\n\tDefer\n\t{\n\t\tofs.close();\n\t\tunlink(file.c_str());\n\t};\n\n\tofs << R\"\"\"(\nserver udp://127.0.0.1:61053 -e\nclient-rules 127.0.0.1\n)\"\"\";\n\tofs.flush();\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_TXT) {\n\t\t\tdns_add_TXT(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 6, \"hello world\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 50);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"64:ff9b::102:304\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053\ngroup-begin dummy\nspeed-check-mode none\nforce-AAAA-SOA yes\nforce-qtype-SOA 16\nconf-file /tmp/smartdns_conf_file*.conf -g client\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"7.8.9.10\");\n\n\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"c.com TXT\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tauto ipaddr = smartdns::GetAvailableIPAddresses();\n\tif (ipaddr.size() > 0) {\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"7.8.9.10\");\n\n\t\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\n\t\tASSERT_TRUE(client.Query(\"c.com TXT\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"c.com\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"\\\"hello world\\\"\");\n\t}\n}\n\nTEST_F(Group, dualstack_inherit_ipv4_prefer)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 80);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 200);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping\ngroup-begin dummy\ngroup-begin client\ndualstack-ip-selection no\nclient-rules 127.0.0.1\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tauto ipaddr = smartdns::GetAvailableIPAddresses();\n\tif (ipaddr.size() > 0) {\n\t\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\t\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3);\n\t\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_LT(client.GetQueryTime(), 20);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\t\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t}\n}\n\nTEST_F(Group, group_match_client_ip)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 80);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 200);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping\ngroup-begin client\ndualstack-ip-selection no\ngroup-match -client-ip 127.0.0.1\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tauto ipaddr = smartdns::GetAvailableIPAddresses();\n\tif (ipaddr.size() > 0) {\n\t\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\t\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 3);\n\t\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053, ipaddr[0]));\n\t\tstd::cout << client.GetResult() << std::endl;\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_LT(client.GetQueryTime(), 20);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\t\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t}\n}\n\nTEST_F(Group, group_match_domain)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 80);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 200);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\ngroup-begin client\naddress #6\ngroup-match -domain a.com\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"2001:db8::2\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 20);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n}\n\nTEST_F(Group, group_from_bind)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 80);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 200);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind [::]:60054 -g client\nserver 127.0.0.1:61053\nspeed-check-mode none\ngroup-begin client\naddress 1.1.1.1\ngroup-match -domain b.com\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60054));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n}\n\n\nTEST_F(Group, server_group_exclude_default)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tusleep(50000);\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:62053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001::\", 128, 10000);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\ngroup-begin client\nserver 127.0.0.1:61053\ngroup-end\nserver 127.0.0.1:62053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n}\n\nTEST_F(Group, group_inherit)\n{\n\tsmartdns::Server server;\n\tsmartdns::MockServer server_upstream;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61056\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tusleep(50000);\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 80);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind [::]:60054 -g group1\nbind [::]:60055 -g group1_1\nbind [::]:60056 -g group1_1_1\nbind [::]:60057 -g group1_1_2\nserver 127.0.0.1:61053\naddress 1.1.1.0\nspeed-check-mode none\n\ngroup-begin group1\n\taddress 1.1.1.1\n\tgroup-match -domain b.com\n\taddress /b.com/1.1.1.3\n\t\t\n\tgroup-begin group1_1\n\t\taddress /b.com/1.1.1.2\n\n\t\tgroup-begin group1_1_1 -inherit none\n\t\t\tserver 127.0.0.1:61056\n\t\tgroup-end\n\n\t\tgroup-begin group1_1_2 -inherit default\n\t\tgroup-end\n\tgroup-end\ngroup-end\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.0\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60054));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.3\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60055));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60055));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.2\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60056));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60057));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.0\");\n}"
  },
  {
    "path": "test/cases/test-hosts.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass Hosts : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Hosts, read)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstd::string file = \"/tmp/smartdns_test_hosts\" + smartdns::GenerateRandomString(5) + \".hosts\";\n\tstd::string file2 = \"/tmp/smartdns_test_hosts\" + smartdns::GenerateRandomString(5) + \".hosts\";\n\tstd::string file3 = \"/tmp/smartdns_test_hosts\" + smartdns::GenerateRandomString(5) + \".hosts\";\n\tstd::ofstream ofs(file);\n\tstd::ofstream ofs2(file2);\n\tstd::ofstream ofs3(file3);\n\tASSERT_TRUE(ofs.is_open());\n\tASSERT_TRUE(ofs2.is_open());\n\tASSERT_TRUE(ofs3.is_open());\n\tDefer\n\t{\n\t\tofs.close();\n\t\tunlink(file.c_str());\n\n\t\tofs2.close();\n\t\tunlink(file2.c_str());\n\n\t\tofs3.close();\n\t\tunlink(file3.c_str());\n\t};\n\n\tofs << \"1.2.3.4   b.com\\n\";\n\tofs << \"64:ff9b::102:304   b.com\\n\";\n\tofs.flush();\n\n\tofs2 << \"1.1.1.1 a.com 1.a.com 2.a.com\\n\";\n\tofs2 << \"# 4.5.6.7 c.com\\n\";\n\tofs2.flush();\n\n\tofs3 << \"127.0.0.1 d.com\\n\";\n\tofs3 << \"::1 d.com\\n\";\n\tofs3 << \"0.0.0.0 e.com\\n\";\n\tofs3 << \":: e.com\\n\";\n\tofs3.flush();\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\nhosts-file /tmp/*.hosts\n)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"b.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\n\tASSERT_TRUE(client.Query(\"-x 64:ff9b::102:304\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(),\n\t\t\t  \"4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"PTR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"b.com.\");\n\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n\n\tASSERT_TRUE(client.Query(\"-x 1.1.1.1\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"1.1.1.1.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"PTR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"a.com.\");\n\n\tASSERT_TRUE(client.Query(\"1.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"1.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n\n\tASSERT_TRUE(client.Query(\"2.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"2.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n\n\tASSERT_TRUE(client.Query(\"c.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"c.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"d.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"d.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"127.0.0.1\");\n\n\tASSERT_TRUE(client.Query(\"d.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"d.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"::1\");\n\n\tASSERT_TRUE(client.Query(\"e.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"e.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"0.0.0.0\");\n\n\tASSERT_TRUE(client.Query(\"e.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"e.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"::\");\n}\n"
  },
  {
    "path": "test/cases/test-http.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/http_parse.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass HTTP : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(HTTP, http1_1_request_parse)\n{\n\tconst char *data = \"GET /?q=question&lang=cn HTTP/1.1\\r\\n\"\n\t\t\t\t\t   \"Host: www.example.com\\r\\n\"\n\t\t\t\t\t   \"User-Agent: smartdns/46\\r\\n\"\n\t\t\t\t\t   \"Accept: */*\\r\\n\"\n\t\t\t\t\t   \"\\r\\n\"\n\t\t\t\t\t   \"hello world\";\n\tstruct http_head *http_head = http_head_init(1024, HTTP_VERSION_1_1);\n\tASSERT_NE(http_head, nullptr);\n\tint ret = http_head_parse(http_head, (const unsigned char *)data, strlen(data));\n\tASSERT_GT(ret, 0);\n\tEXPECT_STREQ(http_head_get_httpversion(http_head), \"HTTP/1.1\");\n\tEXPECT_EQ(http_head_get_method(http_head), HTTP_METHOD_GET);\n\tEXPECT_STREQ(http_head_get_url(http_head), \"/\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"Host\"), \"www.example.com\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"User-Agent\"), \"smartdns/46\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"Accept\"), \"*/*\");\n\tEXPECT_STREQ((const char *)http_head_get_data(http_head), \"hello world\");\n\tEXPECT_STREQ(http_head_get_params_value(http_head, \"q\"), \"question\");\n\tEXPECT_STREQ(http_head_get_params_value(http_head, \"lang\"), \"cn\");\n\n\thttp_head_destroy(http_head);\n}\n\nTEST_F(HTTP, http1_1_request_serialize)\n{\n\tconst char *data = \"GET /?q=question&lang=cn HTTP/1.1\\r\\n\"\n\t\t\t\t\t   \"Host: www.example.com\\r\\n\"\n\t\t\t\t\t   \"User-Agent: smartdns/46\\r\\n\"\n\t\t\t\t\t   \"Accept: */*\\r\\n\"\n\t\t\t\t\t   \"\\r\\n\"\n\t\t\t\t\t   \"hello world\";\n\tstruct http_head *http_head = http_head_init(1024, HTTP_VERSION_1_1);\n\tASSERT_NE(http_head, nullptr);\n\thttp_head_set_httpversion(http_head, \"HTTP/1.1\");\n\thttp_head_set_method(http_head, HTTP_METHOD_GET);\n\thttp_head_set_url(http_head, \"/\");\n\thttp_head_add_fields(http_head, \"Host\", \"www.example.com\");\n\thttp_head_add_fields(http_head, \"User-Agent\", \"smartdns/46\");\n\thttp_head_add_fields(http_head, \"Accept\", \"*/*\");\n\thttp_head_set_data(http_head, \"hello world\", strlen(\"hello world\") + 1);\n\thttp_head_set_head_type(http_head, HTTP_HEAD_REQUEST);\n\thttp_head_add_param(http_head, \"q\", \"question\");\n\thttp_head_add_param(http_head, \"lang\", \"cn\");\n\tchar buffer[1024];\n\tint ret = http_head_serialize(http_head, buffer, 1024);\n\tASSERT_GT(ret, 0);\n\tEXPECT_STREQ(buffer, data);\n\thttp_head_destroy(http_head);\n}\n\nTEST_F(HTTP, http1_1_response_parse)\n{\n\tconst char *data = \"HTTP/1.1 200 OK\\r\\n\"\n\t\t\t\t\t   \"Server: smartdns\\r\\n\"\n\t\t\t\t\t   \"Content-Type: text/html\\r\\n\"\n\t\t\t\t\t   \"Content-Length: 11\\r\\n\"\n\t\t\t\t\t   \"\\r\\n\"\n\t\t\t\t\t   \"hello world\";\n\tstruct http_head *http_head = http_head_init(1024, HTTP_VERSION_1_1);\n\tASSERT_NE(http_head, nullptr);\n\tint ret = http_head_parse(http_head, (const unsigned char *)data, strlen(data));\n\tASSERT_GT(ret, 0);\n\tEXPECT_STREQ(http_head_get_httpversion(http_head), \"HTTP/1.1\");\n\tEXPECT_EQ(http_head_get_httpcode(http_head), 200);\n\tEXPECT_STREQ(http_head_get_httpcode_msg(http_head), \"OK\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"Server\"), \"smartdns\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"Content-Type\"), \"text/html\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"Content-Length\"), \"11\");\n\tEXPECT_STREQ((const char *)http_head_get_data(http_head), \"hello world\");\n\n\thttp_head_destroy(http_head);\n}\n\nTEST_F(HTTP, http1_1_response_serialize)\n{\n\tconst char *data = \"HTTP/1.1 200 OK\\r\\n\"\n\t\t\t\t\t   \"Server: smartdns\\r\\n\"\n\t\t\t\t\t   \"Content-Type: text/html\\r\\n\"\n\t\t\t\t\t   \"Content-Length: 11\\r\\n\"\n\t\t\t\t\t   \"\\r\\n\"\n\t\t\t\t\t   \"hello world\";\n\tstruct http_head *http_head = http_head_init(1024, HTTP_VERSION_1_1);\n\tASSERT_NE(http_head, nullptr);\n\n\thttp_head_set_httpversion(http_head, \"HTTP/1.1\");\n\thttp_head_set_httpcode(http_head, 200, \"OK\");\n\thttp_head_add_fields(http_head, \"Server\", \"smartdns\");\n\thttp_head_add_fields(http_head, \"Content-Type\", \"text/html\");\n\thttp_head_add_fields(http_head, \"Content-Length\", \"11\");\n\thttp_head_set_data(http_head, \"hello world\", strlen(\"hello world\") + 1);\n\thttp_head_set_head_type(http_head, HTTP_HEAD_RESPONSE);\n\tchar buffer[1024];\n\n\tint ret = http_head_serialize(http_head, buffer, 1024);\n\tASSERT_GT(ret, 0);\n\tEXPECT_STREQ(buffer, data);\n\thttp_head_destroy(http_head);\n}\n\nTEST_F(HTTP, http3_0_parse)\n{\n\tstruct http_head *http_head = http_head_init(1024, HTTP_VERSION_3_0);\n\tASSERT_NE(http_head, nullptr);\n\thttp_head_set_httpversion(http_head, \"HTTP/3\");\n\thttp_head_set_method(http_head, HTTP_METHOD_GET);\n\thttp_head_set_url(http_head, \"/\");\n\thttp_head_add_fields(http_head, \"Host\", \"www.example.com\");\n\thttp_head_add_fields(http_head, \"User-Agent\", \"smartdns/46\");\n\thttp_head_add_fields(http_head, \"Accept\", \"*/*\");\n\thttp_head_set_data(http_head, \"hello world\", strlen(\"hello world\") + 1);\n\thttp_head_set_head_type(http_head, HTTP_HEAD_REQUEST);\n\thttp_head_add_param(http_head, \"q\", \"question\");\n\thttp_head_add_param(http_head, \"lang\", \"cn\");\n\tunsigned char buffer[1024];\n\tint ret = http_head_serialize(http_head, buffer, 1024);\n\tASSERT_EQ(ret, 149);\n\thttp_head_destroy(http_head);\n\n\thttp_head = http_head_init(1024, HTTP_VERSION_3_0);\n\tASSERT_NE(http_head, nullptr);\n\n\tret = http_head_parse(http_head, buffer, ret);\n\tASSERT_EQ(ret, 149);\n\tEXPECT_STREQ(http_head_get_httpversion(http_head), \"HTTP/3.0\");\n\tEXPECT_EQ(http_head_get_method(http_head), HTTP_METHOD_GET);\n\tEXPECT_STREQ(http_head_get_url(http_head), \"/\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"Host\"), \"www.example.com\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"User-Agent\"), \"smartdns/46\");\n\tEXPECT_STREQ(http_head_get_fields_value(http_head, \"Accept\"), \"*/*\");\n\tEXPECT_STREQ((const char *)http_head_get_data(http_head), \"hello world\");\n\tEXPECT_STREQ(http_head_get_params_value(http_head, \"q\"), \"question\");\n\tEXPECT_STREQ(http_head_get_params_value(http_head, \"lang\"), \"cn\");\n\n\thttp_head_destroy(http_head);\n}\n\nTEST_F(HTTP, http1_1_small_buffer)\n{\n\tconst char *data = \"HTTP/1.1 200 OK\\r\\n\"\n\t\t\t\t\t   \"Server: smartdns\\r\\n\"\n\t\t\t\t\t   \"Content-Type: text/html\\r\\n\"\n\t\t\t\t\t   \"Content-Length: 11\\r\\n\"\n\t\t\t\t\t   \"\\r\\n\"\n\t\t\t\t\t   \"hello world\";\n\tstruct http_head *http_head = http_head_init(5, HTTP_VERSION_1_1);\n\tASSERT_NE(http_head, nullptr);\n\tint ret = http_head_parse(http_head, (const unsigned char *)data, strlen(data));\n\tEXPECT_EQ(ret, -3);\n\thttp_head_destroy(http_head);\n}\n\nTEST_F(HTTP, http3_small_buffer)\n{\n\tstruct http_head *http_head = http_head_init(1024, HTTP_VERSION_3_0);\n\tASSERT_NE(http_head, nullptr);\n\thttp_head_set_httpversion(http_head, \"HTTP/3\");\n\thttp_head_set_method(http_head, HTTP_METHOD_GET);\n\thttp_head_set_url(http_head, \"/\");\n\thttp_head_add_fields(http_head, \"Host\", \"www.example.com\");\n\thttp_head_add_fields(http_head, \"User-Agent\", \"smartdns/46\");\n\thttp_head_add_fields(http_head, \"Accept\", \"*/*\");\n\thttp_head_set_data(http_head, \"hello world\", strlen(\"hello world\") + 1);\n\thttp_head_set_head_type(http_head, HTTP_HEAD_REQUEST);\n\thttp_head_add_param(http_head, \"q\", \"question\");\n\thttp_head_add_param(http_head, \"lang\", \"cn\");\n\tunsigned char buffer[1024];\n\tint buffer_len = http_head_serialize(http_head, buffer, 1024);\n\tASSERT_EQ(buffer_len, 149);\n\thttp_head_destroy(http_head);\n\n\thttp_head = http_head_init(5, HTTP_VERSION_3_0);\n\tASSERT_NE(http_head, nullptr);\n\tint ret = http_head_parse(http_head, (const unsigned char *)buffer, buffer_len);\n\tEXPECT_EQ(ret, -3);\n\thttp_head_destroy(http_head);\n\n\thttp_head = http_head_init(100, HTTP_VERSION_3_0);\n\tASSERT_NE(http_head, nullptr);\n\tret = http_head_parse(http_head, (const unsigned char *)buffer, buffer_len);\n\tEXPECT_EQ(ret, -3);\n\thttp_head_destroy(http_head);\n\n\thttp_head = http_head_init(1024, HTTP_VERSION_3_0);\n\tASSERT_NE(http_head, nullptr);\n\tret = http_head_parse(http_head, (const unsigned char *)buffer, buffer_len);\n\tEXPECT_GT(ret, 0);\n\thttp_head_destroy(http_head);\n}\n"
  },
  {
    "path": "test/cases/test-http2.cc",
    "content": "#include \"client.h\"\n#include \"server.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/http2.h\"\n#include \"gtest/gtest.h\"\n#include <arpa/inet.h>\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <thread>\n\n// Test HTTP/2 with bind-https server (simulating upstream HTTPS server)\nTEST(HTTP2, BindServerHTTP2)\n{\n\tDefer\n\t{\n\t\tunlink(\"/tmp/smartdns-cert.pem\");\n\t\tunlink(\"/tmp/smartdns-key.pem\");\n\t};\n\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\t// Start main SmartDNS instance that queries upstream HTTPS server\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2\nlog-level debug\n)\"\"\");\n\n\t// Start upstream HTTPS server (bind-https)\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053 -alpn h2\naddress /example.com/1.2.3.4\naddress /test.com/5.6.7.8\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\n\t// Test first query\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\t// Test second query to verify connection reuse\n\tASSERT_TRUE(client.Query(\"test.com\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n}\n\nTEST(HTTP2, ServerMultiStream)\n{\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\t// Start main SmartDNS instance\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2\nlog-level debug\n)\"\"\");\n\n\t// Start upstream HTTPS server (bind-https)\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053 -alpn h2\naddress /example.com/1.2.3.4\naddress /test.com/5.6.7.8\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\t\n\t// Send multiple concurrent queries\n\t// Note: The smartdns::Client might be synchronous, so we might need threads or a way to send async.\n\t// But we can verify that multiple queries on the same connection work (multiplexing).\n\t// The previous test already verified connection reuse.\n\t// To verify concurrency, we'd need to delay the response on the server, which is hard with bind-https.\n\t// However, we can at least verify that sending many queries quickly works.\n\t\n\tfor (int i = 0; i < 10; i++) {\n\t\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t}\n}\n\nTEST(HTTP2, ServerALPNConfig)\n{\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\t// Case 1: Server supports h2, client requests h2 -> h2\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2\nlog-level debug\n)\"\"\");\n\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053 -alpn h2\naddress /example.com/1.2.3.4\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n\nTEST(HTTP2, ServerALPNFallback)\n{\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\t// Case 2: Server supports http/1.1 only, client requests h2 -> fallback or fail?\n\t// If client requests h2 only, it should fail.\n\t// If client requests h2,http/1.1, it should fallback.\n\t\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2,http/1.1\nlog-level debug\n)\"\"\");\n\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053 -alpn http/1.1\naddress /example.com/1.2.3.4\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n\n// Test client only supports HTTP/1.1, server supports both\nTEST(HTTP2, ClientHTTP1Only)\n{\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\t// Client only supports http/1.1, server supports both h2 and http/1.1\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn http/1.1\nlog-level debug\n)\"\"\");\n\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053 -alpn h2,http/1.1\naddress /example.com/1.2.3.4\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\n// Test both client and server only support HTTP/1.1\nTEST(HTTP2, BothHTTP1Only)\n{\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\t// Both client and server only support http/1.1\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn http/1.1\nlog-level debug\n)\"\"\");\n\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053 -alpn http/1.1\naddress /example.com/1.2.3.4\naddress /test2.com/9.10.11.12\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\t\n\t// First query\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t\n\t// Second query to verify connection reuse with HTTP/1.1\n\tASSERT_TRUE(client.Query(\"test2.com\", 61053));\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"9.10.11.12\");\n}\n\n// Test concurrent queries from multiple clients\nTEST(HTTP2, ConcurrentClients)\n{\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2\nlog-level debug\n)\"\"\");\n\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053 -alpn h2\naddress /example.com/1.2.3.4\naddress /test.com/5.6.7.8\nlog-level debug\n)\"\"\");\n\n\t// Create multiple threads to query simultaneously\n\tstd::vector<std::thread> threads;\n\tstd::atomic<int> success_count{0};\n\t\n\tfor (int i = 0; i < 5; i++) {\n\t\tthreads.emplace_back([&success_count, i]() {\n\t\t\tsmartdns::Client client;\n\t\t\tconst char* domain = (i % 2 == 0) ? \"example.com\" : \"test.com\";\n\t\t\tconst char* expected_ip = (i % 2 == 0) ? \"1.2.3.4\" : \"5.6.7.8\";\n\t\t\t\n\t\t\tif (client.Query(domain, 61053)) {\n\t\t\t\tif (client.GetStatus() == \"NOERROR\" && \n\t\t\t\t\tclient.GetAnswerNum() > 0 &&\n\t\t\t\t\tclient.GetAnswer()[0].GetData() == expected_ip) {\n\t\t\t\t\tsuccess_count++;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\t\n\tfor (auto& t : threads) {\n\t\tt.join();\n\t}\n\t\n\tEXPECT_EQ(success_count.load(), 5);\n}\n\n// Test mixed HTTP/2 and HTTP/1.1 queries\nTEST(HTTP2, MixedProtocolQueries)\n{\n\tsmartdns::Server server_wrap_h2;\n\tsmartdns::Server server_wrap_http1;\n\tsmartdns::Server server;\n\n\t// Main server supports both protocols\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2,http/1.1\nserver https://127.0.0.1:60054/dns-query -no-check-certificate -alpn http/1.1\nlog-level debug\n)\"\"\");\n\n\t// First upstream supports HTTP/2\n\tserver_wrap_h2.Start(R\"\"\"(bind-https [::]:60053 -alpn h2\naddress /h2-domain.com/1.1.1.1\nlog-level debug\n)\"\"\");\n\n\t// Second upstream supports HTTP/1.1 only\n\tserver_wrap_http1.Start(R\"\"\"(bind-https [::]:60054 -alpn http/1.1\naddress /http1-domain.com/2.2.2.2\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\t\n\t// Query from HTTP/2 server\n\tASSERT_TRUE(client.Query(\"h2-domain.com\", 61053));\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n\t\n\t// Query from HTTP/1.1 server\n\tASSERT_TRUE(client.Query(\"http1-domain.com\", 61053));\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2.2.2.2\");\n}\n\n// Test connection reuse for HTTP/2\nTEST(HTTP2, ConnectionReuse)\n{\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2\nlog-level debug\n)\"\"\");\n\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053 -alpn h2\naddress /domain1.com/1.1.1.1\naddress /domain2.com/2.2.2.2\naddress /domain3.com/3.3.3.3\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\t\n\t// Multiple queries that should reuse the same HTTP/2 connection\n\tfor (int i = 1; i <= 3; i++) {\n\t\tstd::string domain = \"domain\" + std::to_string(i) + \".com\";\n\t\tstd::string expected_ip = std::to_string(i) + \".\" + std::to_string(i) + \".\" + std::to_string(i) + \".\" + std::to_string(i);\n\t\t\n\t\tASSERT_TRUE(client.Query(domain.c_str(), 61053));\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), expected_ip);\n\t}\n}\n\n// Test default ALPN behavior (no explicit -alpn parameter)\nTEST(HTTP2, DefaultALPN)\n{\n\tsmartdns::Server server_wrap;\n\tsmartdns::Server server;\n\n\t// Client doesn't specify ALPN (should default to supporting both)\n\tserver.Start(R\"\"\"(bind [::]:61053\nserver https://127.0.0.1:60053/dns-query -no-check-certificate\nlog-level debug\n)\"\"\");\n\n\t// Server supports both (no explicit -alpn, should default to h2,http/1.1)\n\tserver_wrap.Start(R\"\"\"(bind-https [::]:60053\naddress /example.com/1.2.3.4\nlog-level debug\n)\"\"\");\n\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}"
  },
  {
    "path": "test/cases/test-https.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass HTTPS : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(HTTPS, ipv4_speed_prefer)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"b.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 b.com. alpn=\\\"h2,h3-19\\\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=\");\n}\n\nTEST_F(HTTPS, ipv6_speed_prefer)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"b.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"102:304:506:708:90a:b0c:d0e:f10\", 60, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(),\n\t\t\t  \"1 b.com. alpn=\\\"h2,h3-19\\\" port=443 ech=AEX+DQA= ipv6hint=102:304:506:708:90a:b0c:d0e:f10\");\n}\n\nTEST_F(HTTPS, ipv4_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"a.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\naddress /a.com/#4\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tauto result_check = client.GetAnswer()[0].GetData();\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(),\n\t\t\t  \"1 a.com. alpn=\\\"h2,h3-19\\\" port=443 ech=AEX+DQA= ipv6hint=102:304:506:708:90a:b0c:d0e:f10\");\n}\n\nTEST_F(HTTPS, ipv6_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"a.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\naddress /a.com/#6\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tauto result_check = client.GetAnswer()[0].GetData();\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 a.com. alpn=\\\"h2,h3-19\\\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=\");\n}\n\nTEST_F(HTTPS, UPSTREAM_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\naddress /a.com/#6\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAuthority()[0].GetTTL(), 595);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n}\n\nTEST_F(HTTPS, HTTPS_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"a.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nhttps-record /a.com/#\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(HTTPS, HTTPS_IGN)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"a.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nforce-qtype-SOA 65\nhttps-record /a.com/-\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"b.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tauto result_check = client.GetAnswer()[0].GetData();\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 a.com. alpn=\\\"h2,h3-19\\\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=\");\n}\n\nTEST_F(HTTPS, HTTPS_IGN_WITH_RULE)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"a.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nforce-qtype-SOA 65\nhttps-record /a.com/noipv4hint,noipv6hint,noech\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"b.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tauto result_check = client.GetAnswer()[0].GetData();\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 a.com. alpn=\\\"h2,h3-19\\\" port=443\");\n}\n\nTEST_F(HTTPS, HTTPS_DOMAIN_RULE_IGN)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"a.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\naddress #\ndomain-rules /a.com/ -https-record -\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"b.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tauto result_check = client.GetAnswer()[0].GetData();\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 a.com. alpn=\\\"h2,h3-19\\\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=\");\n}\n\nTEST_F(HTTPS, multi_https_record)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nforce-qtype-SOA 65\nhttps-record /a.com/target=b.com,priority=1,port=1443,alpn=\\\"h2,h3-19\\\",ech=\\\"AEX+DQA=\\\",ipv4hint=1.2.3.4\nhttps-record /a.com/target=b.com,priority=2,port=2443,alpn=\\\"h2,h3-19\\\",ech=\\\"AEX+DQA=\\\",ipv4hint=1.2.3.4\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"b.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 b.com. alpn=\\\"h2,h3-19\\\" port=1443 ipv4hint=1.2.3.4 ech=AEX+DQA=\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"2 b.com. alpn=\\\"h2,h3-19\\\" port=2443 ipv4hint=1.2.3.4 ech=AEX+DQA=\");\n}\n\nTEST_F(HTTPS, https_record)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t  [&](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nforce-qtype-SOA 65\nhttps-record /a.com/target=b.com,port=1443,alpn=\\\"h2,h3-19\\\",ech=\\\"AEX+DQA=\\\",ipv4hint=1.2.3.4\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"b.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 b.com. alpn=\\\"h2,h3-19\\\" port=1443 ipv4hint=1.2.3.4 ech=AEX+DQA=\");\n}\n\nTEST_F(HTTPS, filter_ip)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"b.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nhttps-record noipv4hint,noipv6hint\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 b.com. alpn=\\\"h2,h3-19\\\" port=443 ech=AEX+DQA=\");\n}\n\nTEST_F(HTTPS, multi_filter_ip)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\t{\n\t\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 300, 1, \"b.com\");\n\t\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\t\tint alph_len = sizeof(alph) - 1;\n\t\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\t\tunsigned char *addr[1] = {add_v4};\n\t\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\t\taddr[0] = add_v6;\n\t\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\t\t}\n\n\t\t{\n\n\t\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 300, 2, \"c.com\");\n\t\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\t\tint alph_len = sizeof(alph) - 1;\n\t\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\t\tunsigned char add_v4[] = {5, 6, 7, 8};\n\t\t\tunsigned char *addr[1] = {add_v4};\n\t\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17};\n\t\t\taddr[0] = add_v6;\n\t\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nhttps-record noipv4hint,noipv6hint,noech\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(\n\t\tclient.GetAnswer()[0].GetData(),\n\t\t\"1 b.com. alpn=\\\"h2,h3-19\\\" port=443\");\n\n\tEXPECT_EQ(client.GetAnswer()[1].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[1].GetType(), \"HTTPS\");\n\tEXPECT_EQ(\n\t\tclient.GetAnswer()[1].GetData(),\n\t\t\"2 c.com. alpn=\\\"h2,h3-19\\\" port=443\");\n}\n\nTEST_F(HTTPS, BIND_FORCE_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"a.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind [::]:62053 -force-https-soa\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 62053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"HTTPS\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 a.com. alpn=\\\"h2,h3-19\\\" port=443 ipv4hint=1.2.3.4 ech=AEX+DQA=\");\n}"
  },
  {
    "path": "test/cases/test-idna.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass IDNA : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(IDNA, match)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /中国.com/10.10.10.10\naddress /中国.com/64:ff9b::1010:1010\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"xn--fiqs8s.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"xn--fiqs8s.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"10.10.10.10\");\n\n\tASSERT_TRUE(client.Query(\"xn--fiqs8s.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"xn--fiqs8s.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::1010:1010\");\n}\n"
  },
  {
    "path": "test/cases/test-ip-alias.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass IPAlias : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST(IPAlias, map_multiip_nospeed_check)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[][4] = {{1, 2, 3, 1}, {1, 2, 3, 2}, {1, 2, 3, 3}};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]);\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},\n\t\t\t\t\t\t\t\t\t\t{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2},\n\t\t\t\t\t\t\t\t\t\t{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]);\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"9.10.11.12\", 60, 140);\n\tserver.MockPing(PING_TYPE_ICMP, \"10.10.10.10\", 60, 120);\n\tserver.MockPing(PING_TYPE_ICMP, \"11.11.11.11\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"0102:0304:0500::\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"0506:0708:0900::\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"0a0b:0c0d:0e00::\", 60, 140);\n\tserver.MockPing(PING_TYPE_ICMP, \"ffff::1\", 60, 120);\n\tserver.MockPing(PING_TYPE_ICMP, \"ffff::2\", 60, 150);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nspeed-check-mode none\nip-alias 1.2.3.0/24 10.10.10.10,12.12.12.12,13.13.13.13,15.15.15.15\nip-alias 0102::/16 FFFF::0001,FFFF::0002,FFFF::0003,FFFF::0004\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 4);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"10.10.10.10\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"12.12.12.12\");\n\tEXPECT_EQ(client.GetAnswer()[2].GetData(), \"15.15.15.15\");\n\tEXPECT_EQ(client.GetAnswer()[3].GetData(), \"13.13.13.13\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 4);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"ffff::1\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"ffff::3\");\n\tEXPECT_EQ(client.GetAnswer()[2].GetData(), \"ffff::2\");\n\tEXPECT_EQ(client.GetAnswer()[3].GetData(), \"ffff::4\");\n}\n\nTEST(IPAlias, map_single_ip_nospeed_check)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]);\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\t{5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\t{10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]);\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"9.10.11.12\", 60, 140);\n\tserver.MockPing(PING_TYPE_ICMP, \"10.10.10.10\", 60, 120);\n\tserver.MockPing(PING_TYPE_ICMP, \"11.11.11.11\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"0102:0304:0500::\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"0506:0708:0900::\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"0a0b:0c0d:0e00::\", 60, 140);\n\tserver.MockPing(PING_TYPE_ICMP, \"ffff::1\", 60, 120);\n\tserver.MockPing(PING_TYPE_ICMP, \"ffff::2\", 60, 150);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nspeed-check-mode none\nip-alias 1.2.3.4 10.10.10.10\nip-alias 5.6.7.8/32 11.11.11.11\nip-alias 0102:0304:0500:: ffff::1\nip-alias 0506:0708:0900:: ffff::2\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 3);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"10.10.10.10\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"11.11.11.11\");\n\tEXPECT_EQ(client.GetAnswer()[2].GetData(), \"9.10.11.12\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 3);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"ffff::1\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"a0b:c0d:e00::\");\n\tEXPECT_EQ(client.GetAnswer()[2].GetData(), \"ffff::2\");\n}\n\nTEST(IPAlias, mapip_withspeed_check)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]);\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\t{5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\t{10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]);\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"9.10.11.12\", 60, 140);\n\tserver.MockPing(PING_TYPE_ICMP, \"10.10.10.10\", 60, 120);\n\tserver.MockPing(PING_TYPE_ICMP, \"11.11.11.11\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"0102:0304:0500::\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"0506:0708:0900::\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"0a0b:0c0d:0e00::\", 60, 140);\n\tserver.MockPing(PING_TYPE_ICMP, \"ffff::1\", 60, 120);\n\tserver.MockPing(PING_TYPE_ICMP, \"ffff::2\", 60, 150);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nip-alias 1.2.3.4 10.10.10.10\nip-alias 5.6.7.8/32 11.11.11.11\nip-alias 0102::/16 ffff::1\nip-alias 0506::/16 ffff::2\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"10.10.10.10\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"ffff::1\");\n}\n\nTEST(IPAlias, no_ip_alias)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]);\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\t{5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},\n\t\t\t\t\t\t\t\t\t\t{10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[1]);\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[2]);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"9.10.11.12\", 60, 140);\n\tserver.MockPing(PING_TYPE_ICMP, \"10.10.10.10\", 60, 120);\n\tserver.MockPing(PING_TYPE_ICMP, \"11.11.11.11\", 60, 150);\n\tserver.MockPing(PING_TYPE_ICMP, \"0102:0304:0500::\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"0506:0708:0900::\", 60, 110);\n\tserver.MockPing(PING_TYPE_ICMP, \"0a0b:0c0d:0e00::\", 60, 140);\n\tserver.MockPing(PING_TYPE_ICMP, \"ffff::1\", 60, 120);\n\tserver.MockPing(PING_TYPE_ICMP, \"ffff::2\", 60, 150);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nip-alias 1.2.3.4 10.10.10.10\nip-alias 5.6.7.8/32 11.11.11.11\nip-alias 0102::/16 ffff::1\nip-alias 0506::/16 ffff::2\ndomain-rules /a.com/ -no-ip-alias\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"102:304:500::\");\n}"
  },
  {
    "path": "test/cases/test-ip-rule.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass IPRule : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(IPRule, white_list)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -whitelist-ip\nserver udp://127.0.0.1:62053 -whitelist-ip\nwhitelist-ip 4.5.6.7/24\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"4.5.6.7\");\n}\n\nTEST_F(IPRule, white_list_not_in)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"9.10.11.12\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -whitelist-ip\nserver udp://127.0.0.1:62053 -whitelist-ip\nwhitelist-ip 4.5.6.7/24\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n\nTEST_F(IPRule, black_list)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tusleep(800000);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"4.5.6.7\", 60, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -blacklist-ip\nserver udp://127.0.0.1:62053 -blacklist-ip\nblacklist-ip 4.5.6.7/24\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(IPRule, ignore_ip)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"4.5.6.7\", 60, 90);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 40);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -blacklist-ip\nignore-ip 1.2.3.0/24\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"7.8.9.10\");\n}\n\nTEST_F(IPRule, ignore_ip_set)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\tstd::string file = \"/tmp/smartdns_test_ip_set.list\" + smartdns::GenerateRandomString(5);\n\tstd::ofstream ofs(file);\n\tASSERT_TRUE(ofs.is_open());\n\tDefer\n\t{\n\t\tofs.close();\n\t\tunlink(file.c_str());\n\t};\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"4.5.6.7\", 60, 90);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 40);\n\n\tstd::string ipset_list = R\"\"\"(\n1.2.3.0/24\n4.5.6.0/24\n)\"\"\";\n\tofs.write(ipset_list.c_str(), ipset_list.length());\n\tofs.flush();\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -blacklist-ip\nip-set -name ip-list -file )\"\"\" +\n\t\t\t\t file + R\"\"\"(\nignore-ip ip-set:ip-list\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"7.8.9.10\");\n}\n\n\nTEST_F(IPRule, ignore_all_ip)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"4.5.6.7\", 60, 90);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 40);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -blacklist-ip\nignore-ip 0.0.0.0/0\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n\nTEST_F(IPRule, no_ignore_ip_by_domain)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\t/* this ip will be discard, but is reachable */\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"4.5.6.7\", 60, 90);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 40);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -blacklist-ip\nignore-ip 0.0.0.0/0\ndomain-rules /b.com/ -no-ignore-ip\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(IPRule, ip_alias_ip_set)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\tstd::string file = \"/tmp/smartdns_test_ip_set.list\" + smartdns::GenerateRandomString(5);\n\tstd::string file_ip = \"/tmp/smartdns_test_ip_set_ip.list\" + smartdns::GenerateRandomString(5);\n\tstd::ofstream ofs(file);\n\tstd::ofstream ofs_ip(file_ip);\n\tASSERT_TRUE(ofs.is_open());\n\tASSERT_TRUE(ofs_ip.is_open());\n\tDefer\n\t{\n\t\tofs.close();\n\t\tunlink(file.c_str());\n\t\tofs_ip.close();\n\t\tunlink(file_ip.c_str());\n\t};\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"4.5.6.7\", 611);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"7.8.9.10\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"4.5.6.7\", 60, 90);\n\tserver.MockPing(PING_TYPE_ICMP, \"7.8.9.10\", 60, 40);\n\n\tstd::string ipset_list = R\"\"\"(\n1.2.3.0/24\n4.5.6.0/24\n7.8.9.0/24\n)\"\"\";\n\tofs.write(ipset_list.c_str(), ipset_list.length());\n\tofs.flush();\n\n\tstd::string ipset_list_ip = R\"\"\"(\n1.1.1.1\n)\"\"\";\n\tofs_ip.write(ipset_list_ip.c_str(), ipset_list_ip.length());\n\tofs_ip.flush();\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver udp://127.0.0.1:61053 -blacklist-ip\nip-set -name ip-list -file )\"\"\" +\n\t\t\t\t file + R\"\"\"(\nip-set -name ip-list-ip -file )\"\"\" +\n\t\t\t\t file_ip + R\"\"\"(\nip-alias ip-set:ip-list ip-set:ip-list-ip\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.1.1.1\");\n}\n"
  },
  {
    "path": "test/cases/test-lib-http2.cc",
    "content": "#include \"gtest/gtest.h\"\n#include <algorithm>\n#include <atomic>\n#include <chrono>\n#include <cstring>\n#include <fcntl.h>\n#include <iostream>\n#include <poll.h>\n#include <set>\n#include <string>\n#include <sys/socket.h>\n#include <thread>\n#include <unistd.h>\n#include <vector>\n\n#include \"smartdns/http2.h\"\n\nclass LIBHTTP2 : public ::testing::Test\n{\n  protected:\n\tvoid SetUp() override\n\t{\n\t\t// Create socketpair for communication\n\t\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) < 0) {\n\t\t\tperror(\"socketpair\");\n\t\t\tFAIL() << \"Failed to create socketpair\";\n\t\t}\n\n\t\tclient_sock = socks[0];\n\t\tserver_sock = socks[1];\n\n\t\t// Set non-blocking\n\t\tfcntl(client_sock, F_SETFL, O_NONBLOCK);\n\t\tfcntl(server_sock, F_SETFL, O_NONBLOCK);\n\t}\n\n\tvoid TearDown() override\n\t{\n\t\tif (client_sock != -1)\n\t\t\tclose(client_sock);\n\t\tif (server_sock != -1)\n\t\t\tclose(server_sock);\n\t}\n\n\tint socks[2];\n\tint client_sock = -1;\n\tint server_sock = -1;\n\n\t// BIO callbacks\n\tstatic int bio_read(void *private_data, uint8_t *buf, int len)\n\t{\n\t\tint fd = *(int *)private_data;\n\t\tint ret = read(fd, buf, len);\n\t\tif (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {\n\t\t\terrno = EAGAIN;\n\t\t\treturn -1;\n\t\t}\n\t\treturn ret;\n\t}\n\n\tstatic int bio_write(void *private_data, const uint8_t *buf, int len)\n\t{\n\t\tint fd = *(int *)private_data;\n\t\tint ret = write(fd, buf, len);\n\t\tif (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {\n\t\t\terrno = EAGAIN;\n\t\t\treturn -1;\n\t\t}\n\t\treturn ret;\n\t}\n};\n\nTEST_F(LIBHTTP2, Integrated)\n{\n\tstd::thread server_thread([this]() {\n\t\t// Server logic\n\t\tstruct http2_ctx *ctx = http2_ctx_server_new(\"test-server\", bio_read, bio_write, &server_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tint poll_ret = poll(&pfd, 1, 10);\n\t\t\tif (poll_ret == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tASSERT_EQ(ret, 1) << \"Server handshake failed\";\n\n\t\t// Accept stream\n\t\tstruct http2_stream *stream = nullptr;\n\t\tint max_attempts = 200;\n\t\twhile (max_attempts-- > 0 && !stream) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream == nullptr && items[i].readable) {\n\t\t\t\t\tstream = http2_ctx_accept_stream(ctx);\n\t\t\t\t\tif (stream)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tusleep(20000);\n\t\t}\n\t\tif (!stream) {\n\t\t\tstd::cout << \"Server failed to accept stream after timeout\" << std::endl;\n\t\t}\n\t\tASSERT_NE(stream, nullptr) << \"Server failed to accept stream\";\n\n\t\t// Read request body\n\t\tuint8_t request_body[4096];\n\t\tint request_body_len = 0;\n\t\twhile (!http2_stream_is_end(stream) && request_body_len < (int)sizeof(request_body)) {\n\t\t\tint read_len = http2_stream_read_body(stream, request_body + request_body_len,\n\t\t\t\t\t\t\t\t\t\t\t\t  sizeof(request_body) - request_body_len);\n\t\t\tif (read_len > 0) {\n\t\t\t\trequest_body_len += read_len;\n\t\t\t} else {\n\t\t\t\tusleep(10000);\n\t\t\t}\n\t\t}\n\n\t\t// Send response\n\t\tchar response[8192];\n\t\tint response_len = snprintf(response, sizeof(response), \"Echo Response: %.*s\", request_body_len, request_body);\n\t\tchar content_length[32];\n\t\tsnprintf(content_length, sizeof(content_length), \"%d\", response_len);\n\t\tstruct http2_header_pair headers[] = {{\"content-type\", \"text/plain\"}, {\"content-length\", content_length}};\n\t\thttp2_stream_set_response(stream, 200, headers, 2);\n\t\thttp2_stream_write_body(stream, (const uint8_t *)response, response_len, 1);\n\n\t\thttp2_stream_close(stream);\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tstd::thread client_thread([this]() {\n\t\tusleep(500000); // Wait for server start\n\t\tstruct http2_ctx *ctx = http2_ctx_client_new(\"test-client\", bio_read, bio_write, &client_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 10);\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Client handshake failed\";\n\n\t\t// Create stream\n\t\tstruct http2_stream *stream = http2_stream_new(ctx);\n\t\tASSERT_NE(stream, nullptr);\n\n\t\t// Send request\n\t\tstruct http2_header_pair headers[] = {\n\t\t\t{\"content-type\", \"application/json\"}, {\"content-length\", \"27\"}, {NULL, NULL}};\n\t\thttp2_stream_set_request(stream, \"POST\", \"/echo\", NULL, headers);\n\t\tconst char *request_body = \"{\\\"message\\\":\\\"Hello Echo!\\\"}\";\n\t\thttp2_stream_write_body(stream, (const uint8_t *)request_body, strlen(request_body), 1);\n\n\t\t// Wait for response\n\t\tint max_attempts = 200;\n\t\twhile (max_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\t\t\tif (http2_stream_get_status(stream) > 0)\n\t\t\t\tbreak;\n\n\t\t\tusleep(20000);\n\t\t}\n\n\t\tEXPECT_EQ(http2_stream_get_status(stream), 200);\n\n\t\t// Read response\n\t\tuint8_t response_body[4096];\n\t\tint response_body_len = 0;\n\t\twhile (!http2_stream_is_end(stream) && response_body_len < (int)sizeof(response_body)) {\n\t\t\tint read_len = http2_stream_read_body(stream, response_body + response_body_len,\n\t\t\t\t\t\t\t\t\t\t\t\t  sizeof(response_body) - response_body_len);\n\t\t\tif (read_len > 0) {\n\t\t\t\tresponse_body_len += read_len;\n\t\t\t} else {\n\t\t\t\tusleep(10000);\n\t\t\t}\n\t\t}\n\n\t\tstd::string resp((char *)response_body, response_body_len);\n\t\tEXPECT_NE(resp.find(\"Echo Response\"), std::string::npos);\n\n\t\thttp2_stream_close(stream);\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tserver_thread.join();\n\tclient_thread.join();\n}\n\nTEST_F(LIBHTTP2, MultiStream)\n{\n\tconst int NUM_STREAMS = 3;\n\n\tstd::thread server_thread([this, NUM_STREAMS]() {\n\t\tstruct http2_ctx *ctx = http2_ctx_server_new(\"test-server\", bio_read, bio_write, &server_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 10);\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Server handshake failed\";\n\n\t\tint streams_completed = 0;\n\t\tint max_iterations = 500;\n\t\tstd::set<struct http2_stream *> processed_streams;\n\t\twhile (streams_completed < NUM_STREAMS && max_iterations-- > 0) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream == nullptr && items[i].readable) {\n\t\t\t\t\tstruct http2_stream *s = http2_ctx_accept_stream(ctx);\n\t\t\t\t} else if (items[i].stream && items[i].readable) {\n\t\t\t\t\tstruct http2_stream *stream = items[i].stream;\n\t\t\t\t\tuint8_t buf[1024];\n\t\t\t\t\thttp2_stream_read_body(stream, buf, sizeof(buf));\n\n\t\t\t\t\tif (http2_stream_is_end(stream)) {\n\t\t\t\t\t\tif (processed_streams.find(stream) == processed_streams.end()) {\n\t\t\t\t\t\t\tchar response[256];\n\t\t\t\t\t\t\tint response_len = snprintf(response, sizeof(response), \"Echo from stream %d\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thttp2_stream_get_id(stream));\n\t\t\t\t\t\t\tchar content_length[32];\n\t\t\t\t\t\t\tsnprintf(content_length, sizeof(content_length), \"%d\", response_len);\n\t\t\t\t\t\t\tstruct http2_header_pair headers[] = {{\"content-type\", \"text/plain\"},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  {\"content-length\", content_length}};\n\t\t\t\t\t\t\thttp2_stream_set_response(stream, 200, headers, 2);\n\t\t\t\t\t\t\thttp2_stream_write_body(stream, (const uint8_t *)response, response_len, 1);\n\t\t\t\t\t\t\tstreams_completed++;\n\t\t\t\t\t\t\tprocessed_streams.insert(stream);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tusleep(2000);\n\t\t}\n\n\t\tfor (auto stream : processed_streams) {\n\t\t\thttp2_stream_close(stream);\n\t\t}\n\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tstd::thread client_thread([this, NUM_STREAMS]() {\n\t\tusleep(50000);\n\t\tstruct http2_ctx *ctx = http2_ctx_client_new(\"test-client\", bio_read, bio_write, &client_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 10);\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Client handshake failed\";\n\n\t\tstruct http2_stream *streams[NUM_STREAMS];\n\t\tfor (int i = 0; i < NUM_STREAMS; i++) {\n\t\t\tstreams[i] = http2_stream_new(ctx);\n\t\t\tASSERT_NE(streams[i], nullptr);\n\n\t\t\tchar path[64];\n\t\t\tsnprintf(path, sizeof(path), \"/stream%d\", i);\n\t\t\tchar body[128];\n\t\t\tint body_len = snprintf(body, sizeof(body), \"Request from stream %d\", i);\n\t\t\tchar content_length[32];\n\t\t\tsnprintf(content_length, sizeof(content_length), \"%d\", body_len);\n\n\t\t\tstruct http2_header_pair headers[] = {\n\t\t\t\t{\"content-type\", \"text/plain\"}, {\"content-length\", content_length}, {NULL, NULL}};\n\t\t\thttp2_stream_set_request(streams[i], \"POST\", path, NULL, headers);\n\t\t\thttp2_stream_write_body(streams[i], (const uint8_t *)body, body_len, 1);\n\t\t}\n\n\t\tint streams_completed = 0;\n\t\tint max_iterations = 500;\n\t\tstd::set<int> completed_stream_ids;\n\t\twhile (streams_completed < NUM_STREAMS && max_iterations-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream && items[i].readable) {\n\t\t\t\t\tstruct http2_stream *stream = items[i].stream;\n\t\t\t\t\tuint8_t buf[1024];\n\t\t\t\t\thttp2_stream_read_body(stream, buf, sizeof(buf));\n\t\t\t\t\tif (http2_stream_is_end(stream)) {\n\t\t\t\t\t\tint stream_id = http2_stream_get_id(stream);\n\t\t\t\t\t\tif (completed_stream_ids.find(stream_id) == completed_stream_ids.end()) {\n\t\t\t\t\t\t\tcompleted_stream_ids.insert(stream_id);\n\t\t\t\t\t\t\tstreams_completed++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tusleep(2000);\n\t\t}\n\n\t\tEXPECT_EQ(streams_completed, NUM_STREAMS);\n\n\t\tfor (int i = 0; i < NUM_STREAMS; i++) {\n\t\t\thttp2_stream_close(streams[i]);\n\t\t}\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tserver_thread.join();\n\tclient_thread.join();\n}\n\nTEST_F(LIBHTTP2, EarlyStreamCreation)\n{\n\tstd::thread server_thread([this]() {\n\t\t// Server logic\n\t\tstruct http2_ctx *ctx = http2_ctx_server_new(\"test-server\", bio_read, bio_write, &server_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tint poll_ret = poll(&pfd, 1, 10);\n\t\t\tif (poll_ret == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tASSERT_EQ(ret, 1) << \"Server handshake failed\";\n\n\t\t// Accept stream\n\t\tstruct http2_stream *stream = nullptr;\n\t\tint max_attempts = 200;\n\t\twhile (max_attempts-- > 0 && !stream) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream == nullptr && items[i].readable) {\n\t\t\t\t\tstream = http2_ctx_accept_stream(ctx);\n\t\t\t\t\tif (stream)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tusleep(20000);\n\t\t}\n\t\tASSERT_NE(stream, nullptr) << \"Server failed to accept stream\";\n\n\t\t// Verify we received the request\n\t\tconst char *method = http2_stream_get_method(stream);\n\t\tconst char *path = http2_stream_get_path(stream);\n\t\tEXPECT_STREQ(method, \"POST\");\n\t\tEXPECT_STREQ(path, \"/early-test\");\n\n\t\t// Read request body (should be empty for GET)\n\t\tuint8_t request_body[4096];\n\t\tint request_body_len = 0;\n\t\twhile (!http2_stream_is_end(stream) && request_body_len < (int)sizeof(request_body)) {\n\t\t\tint read_len = http2_stream_read_body(stream, request_body + request_body_len,\n\t\t\t\t\t\t\t\t\t\t\t\t  sizeof(request_body) - request_body_len);\n\t\t\tif (read_len > 0) {\n\t\t\t\trequest_body_len += read_len;\n\t\t\t} else {\n\t\t\t\tusleep(10000);\n\t\t\t}\n\t\t}\n\n\t\t// Send response\n\t\tchar response[8192];\n\t\tint response_len = snprintf(response, sizeof(response), \"Echo Response: %.*s\", request_body_len, request_body);\n\t\tchar content_length[32];\n\t\tsnprintf(content_length, sizeof(content_length), \"%d\", response_len);\n\t\tstruct http2_header_pair headers[] = {\n\t\t\t{\"content-type\", \"text/plain\"}, {\"content-length\", content_length}, {NULL, NULL}};\n\t\thttp2_stream_set_response(stream, 200, headers, 2);\n\t\thttp2_stream_write_body(stream, (const uint8_t *)response, response_len, 1);\n\t\thttp2_stream_close(stream);\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tstd::thread client_thread([this]() {\n\t\tusleep(50000); // Wait for server start\n\n\t\t// Create client context\n\t\tstruct http2_ctx *ctx = http2_ctx_client_new(\"test-client\", bio_read, bio_write, &client_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// IMPORTANT: Create stream and send request BEFORE handshake completes\n\t\t// This tests that the HEADERS frame is buffered and sent after handshake\n\t\tstruct http2_stream *stream = http2_stream_new(ctx);\n\t\tASSERT_NE(stream, nullptr);\n\n\t\t// Send request immediately (before handshake)\n\t\tstruct http2_header_pair headers[] = {{\"user-agent\", \"test-client\"}, {NULL, NULL}};\n\t\tint ret = http2_stream_set_request(stream, \"POST\", \"/early-test\", NULL, headers);\n\t\tEXPECT_EQ(ret, 0) << \"Failed to set request\";\n\t\tconst char *request_body = \"test echo\";\n\t\thttp2_stream_write_body(stream, (const uint8_t *)request_body, strlen(request_body), 1);\n\n\t\t// Now complete handshake\n\t\tint handshake_attempts = 200;\n\t\tret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 10);\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Client handshake failed\";\n\n\t\t// Wait for response\n\t\tint max_attempts = 200;\n\t\twhile (max_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\t\t\tif (http2_stream_get_status(stream) > 0)\n\t\t\t\tbreak;\n\n\t\t\tusleep(20000);\n\t\t}\n\n\t\tEXPECT_EQ(http2_stream_get_status(stream), 200);\n\n\t\t// Read response\n\t\tuint8_t response_body[4096];\n\t\tint response_body_len = 0;\n\t\twhile (!http2_stream_is_end(stream) && response_body_len < (int)sizeof(response_body)) {\n\t\t\tint read_len = http2_stream_read_body(stream, response_body + response_body_len,\n\t\t\t\t\t\t\t\t\t\t\t\t  sizeof(response_body) - response_body_len);\n\t\t\tif (read_len > 0) {\n\t\t\t\tresponse_body_len += read_len;\n\t\t\t} else {\n\t\t\t\tusleep(10000);\n\t\t\t}\n\t\t}\n\n\t\tstd::string resp((char *)response_body, response_body_len);\n\t\tEXPECT_NE(resp.find(\"Echo Response\"), std::string::npos);\n\t\tEXPECT_NE(resp.find(\"test echo\"), std::string::npos);\n\n\t\thttp2_stream_close(stream);\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tserver_thread.join();\n\tclient_thread.join();\n}\n\nTEST_F(LIBHTTP2, ServerLoopTerminationOnDisconnect)\n{\n\tstd::thread server_thread([this]() {\n\t\tstruct http2_ctx *ctx = http2_ctx_server_new(\"test-server\", bio_read, bio_write, &server_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tint poll_ret = poll(&pfd, 1, 10);\n\t\t\tif (poll_ret == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Server handshake failed\";\n\n\t\t// Accept stream\n\t\tstruct http2_stream *stream = nullptr;\n\t\tint max_attempts = 200;\n\t\twhile (max_attempts-- > 0 && !stream) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream == nullptr && items[i].readable) {\n\t\t\t\t\tstream = http2_ctx_accept_stream(ctx);\n\t\t\t\t\tif (stream)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tusleep(20000);\n\t\t}\n\t\tASSERT_NE(stream, nullptr) << \"Server failed to accept stream\";\n\n\t\t// Read request body until EOF\n\t\tuint8_t buf[1024];\n\t\tint loop_count = 0;\n\t\twhile (loop_count++ < 100) {\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\n\t\t\tint data_read = 0;\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream == stream && items[i].readable) {\n\t\t\t\t\tint ret = http2_stream_read_body(stream, buf, sizeof(buf));\n\t\t\t\t\tif (ret > 0) {\n\t\t\t\t\t\tdata_read = 1;\n\t\t\t\t\t} else if (ret == 0) {\n\t\t\t\t\t\t// EOF received\n\t\t\t\t\t\tdata_read = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (items[i].stream) {\n\t\t\t\t\thttp2_stream_put(items[i].stream);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!data_read && http2_stream_is_end(stream)) {\n\t\t\t\t// If we are here, it means poll returned 0 items (or stream not readable),\n\t\t\t\t// which is correct behavior after EOF is consumed.\n\t\t\t\t// If the bug exists, poll would keep returning readable stream, and we would keep reading 0 bytes.\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tusleep(10000);\n\t\t}\n\n\t\tEXPECT_LT(loop_count, 100) << \"Server loop did not terminate (infinite loop detected)\";\n\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tstd::thread client_thread([this]() {\n\t\tusleep(50000);\n\t\tstruct http2_ctx *ctx = http2_ctx_client_new(\"test-client\", bio_read, bio_write, &client_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 10);\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret != 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tASSERT_EQ(ret, 1);\n\n\t\tstruct http2_stream *stream = http2_stream_new(ctx);\n\t\tASSERT_NE(stream, nullptr);\n\n\t\tstruct http2_header_pair headers[] = {{\"content-type\", \"text/plain\"}, {NULL, NULL}};\n\t\thttp2_stream_set_request(stream, \"POST\", \"/test\", NULL, headers);\n\t\thttp2_stream_write_body(stream, (const uint8_t *)\"test\", 4, 1);\n\t\thttp2_stream_close(stream);\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tserver_thread.join();\n\tclient_thread.join();\n}\n\nTEST_F(LIBHTTP2, StreamClose)\n{\n\tstd::thread server_thread([this]() {\n\t\tstruct http2_ctx *ctx = http2_ctx_server_new(\"test-server\", bio_read, bio_write, &server_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tint poll_ret = poll(&pfd, 1, 10);\n\t\t\tif (poll_ret == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Server handshake failed\";\n\n\t\t// Accept stream\n\t\tstruct http2_stream *stream = nullptr;\n\t\tint max_attempts = 200;\n\t\twhile (max_attempts-- > 0 && !stream) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream == nullptr && items[i].readable) {\n\t\t\t\t\tstream = http2_ctx_accept_stream(ctx);\n\t\t\t\t\tif (stream)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tusleep(20000);\n\t\t}\n\t\tASSERT_NE(stream, nullptr) << \"Server failed to accept stream\";\n\n\t\t// Read request and send response\n\t\tuint8_t buf[1024];\n\t\thttp2_stream_read_body(stream, buf, sizeof(buf));\n\t\thttp2_stream_set_response(stream, 200, NULL, 0);\n\t\thttp2_stream_write_body(stream, (const uint8_t *)\"OK\", 2, 1);\n\n\t\thttp2_stream_close(stream);\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tstd::thread client_thread([this]() {\n\t\tusleep(50000);\n\t\tstruct http2_ctx *ctx = http2_ctx_client_new(\"test-client\", bio_read, bio_write, &client_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tint handshake_attempts = 200;\n\t\tint ret = 0;\n\t\twhile (handshake_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 10);\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Client handshake failed\";\n\n\t\t// Create stream\n\t\tstruct http2_stream *stream = http2_stream_new(ctx);\n\t\tASSERT_NE(stream, nullptr);\n\n\t\t// Send request\n\t\thttp2_stream_set_request(stream, \"GET\", \"/test\", NULL, NULL);\n\t\thttp2_stream_write_body(stream, NULL, 0, 1);\n\n\t\t// Wait for response\n\t\tint max_attempts = 200;\n\t\twhile (max_attempts-- > 0) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 100);\n\t\t\tstruct http2_poll_item items[10];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 10, &count);\n\t\t\tif (http2_stream_get_status(stream) > 0)\n\t\t\t\tbreak;\n\t\t\tusleep(20000);\n\t\t}\n\n\t\t// Close the stream explicitly\n\t\thttp2_stream_get(stream); // Keep reference for reading after close\n\t\thttp2_stream_close(stream);\n\n\t\t// Verify stream is marked as closed (should still be able to read)\n\t\t// After close, the stream should still be readable until all data is consumed\n\t\tEXPECT_FALSE(http2_stream_is_end(stream)); // Should not be end yet since we haven't read response\n\n\t\t// Read response (should still work after close)\n\t\tuint8_t buf[1024];\n\t\tint read_len = http2_stream_read_body(stream, buf, sizeof(buf));\n\t\tEXPECT_GE(read_len, 0); // Should be able to read\n\n\t\t// After reading all data, stream should be end\n\t\twhile (!http2_stream_is_end(stream)) {\n\t\t\tread_len = http2_stream_read_body(stream, buf, sizeof(buf));\n\t\t\tif (read_len <= 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tEXPECT_TRUE(http2_stream_is_end(stream)); // Should be end after reading all data\n\n\t\thttp2_stream_put(stream);\n\t\thttp2_ctx_put(ctx);\n\t});\n\n\tserver_thread.join();\n\tclient_thread.join();\n}\n\nTEST_F(LIBHTTP2, ReferenceCountingNormal)\n{\n\t// Test normal reference counting: ctx normal, stream released by business\n\tstruct http2_ctx *ctx = http2_ctx_client_new(\"test-client\", bio_read, bio_write, &client_sock, NULL);\n\tASSERT_NE(ctx, nullptr);\n\n\t// Create a stream (already has refcount = 1)\n\tstruct http2_stream *stream = http2_stream_new(ctx);\n\tASSERT_NE(stream, nullptr);\n\n\t// Close context (should not free stream because business still holds reference)\n\thttp2_ctx_close(ctx);\n\n\t// Business releases reference\n\thttp2_stream_close(stream);\n\n\t// Now stream should be freed\n\t// We can't directly check, but no crash should occur\n}\n\nTEST_F(LIBHTTP2, ReferenceCountingContextError)\n{\n\t// Test reference counting when ctx has error but stream is still referenced by business\n\tstruct http2_ctx *ctx = http2_ctx_client_new(\"test-client\", bio_read, bio_write, &client_sock, NULL);\n\tASSERT_NE(ctx, nullptr);\n\n\t// Create a stream\n\tstruct http2_stream *stream = http2_stream_new(ctx);\n\tASSERT_NE(stream, nullptr);\n\n\t// Simulate context error by closing the socket (connection broken)\n\tclose(client_sock);\n\tclient_sock = -1;\n\n\t// Close context (should handle error gracefully)\n\thttp2_ctx_close(ctx);\n\n\t// Business still holds reference, should be able to release it\n\thttp2_stream_close(stream);\n\n\t// No crash should occur\n}\n\nTEST_F(LIBHTTP2, StressTest)\n{\n\tconst int NUM_STREAMS = 1024;\n\tstd::atomic<int> server_processed(0);\n\tstd::atomic<int> client_completed(0);\n\tstd::atomic<bool> test_completed(false);\n\n\tstd::thread server_thread([this, NUM_STREAMS, &server_processed, &test_completed]() {\n\t\tstruct http2_ctx *ctx = http2_ctx_server_new(\"test-server\", bio_read, bio_write, &server_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tauto start_time = std::chrono::steady_clock::now();\n\t\tint ret = 0;\n\t\twhile (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(5)) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tint poll_ret = poll(&pfd, 1, 10);\n\t\t\tif (poll_ret == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Server handshake failed\";\n\n\t\tstd::vector<struct http2_stream *> streams;\n\t\tstart_time = std::chrono::steady_clock::now();\n\t\twhile (!test_completed && std::chrono::steady_clock::now() - start_time < std::chrono::seconds(30)) {\n\t\t\tstruct pollfd pfd = {server_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 10);\n\n\t\t\tstruct http2_poll_item items[64];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 64, &count);\n\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream == nullptr && items[i].readable) {\n\t\t\t\t\tstruct http2_stream *stream = http2_ctx_accept_stream(ctx);\n\t\t\t\t\tif (stream) {\n\t\t\t\t\t\tstreams.push_back(stream);\n\t\t\t\t\t}\n\t\t\t\t} else if (items[i].stream && items[i].readable) {\n\t\t\t\t\tstruct http2_stream *stream = items[i].stream;\n\t\t\t\t\tuint8_t buf[1024];\n\t\t\t\t\twhile (http2_stream_read_body(stream, buf, sizeof(buf)) > 0)\n\t\t\t\t\t\t;\n\n\t\t\t\t\tif (http2_stream_is_end(stream)) {\n\t\t\t\t\t\tchar response[256];\n\t\t\t\t\t\tint response_len = snprintf(response, sizeof(response), \"Echo %d\", http2_stream_get_id(stream));\n\t\t\t\t\t\tchar content_length[32];\n\t\t\t\t\t\tsnprintf(content_length, sizeof(content_length), \"%d\", response_len);\n\t\t\t\t\t\tstruct http2_header_pair headers[] = {{\"content-type\", \"text/plain\"},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  {\"content-length\", content_length}};\n\t\t\t\t\t\thttp2_stream_set_response(stream, 200, headers, 2);\n\t\t\t\t\t\thttp2_stream_write_body(stream, (const uint8_t *)response, response_len, 1);\n\t\t\t\t\t\tserver_processed++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (items[i].stream) {\n\t\t\t\t\thttp2_stream_put(items[i].stream);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (auto stream : streams) {\n\t\t\thttp2_stream_close(stream);\n\t\t}\n\t\thttp2_ctx_close(ctx);\n\t});\n\n\tstd::thread client_thread([this, NUM_STREAMS, &client_completed, &test_completed]() {\n\t\tusleep(50000);\n\t\tstruct http2_ctx *ctx = http2_ctx_client_new(\"test-client\", bio_read, bio_write, &client_sock, NULL);\n\t\tASSERT_NE(ctx, nullptr);\n\n\t\t// Handshake\n\t\tauto start_time = std::chrono::steady_clock::now();\n\t\tint ret = 0;\n\t\twhile (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(5)) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, 10);\n\t\t\tret = http2_ctx_handshake(ctx);\n\t\t\tif (ret == 1)\n\t\t\t\tbreak;\n\t\t\tif (ret < 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tASSERT_EQ(ret, 1) << \"Client handshake failed\";\n\n\t\tstd::vector<struct http2_stream *> streams;\n\t\tstreams.reserve(NUM_STREAMS);\n\t\tstd::set<int> completed_ids;\n\n\t\tauto process_events = [&](int timeout_ms) {\n\t\t\tstruct pollfd pfd = {client_sock, POLLIN, 0};\n\t\t\tpoll(&pfd, 1, timeout_ms);\n\n\t\t\tstruct http2_poll_item items[64];\n\t\t\tint count = 0;\n\t\t\thttp2_ctx_poll(ctx, items, 64, &count);\n\n\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\tif (items[i].stream && items[i].readable) {\n\t\t\t\t\tstruct http2_stream *stream = items[i].stream;\n\t\t\t\t\tuint8_t buf[1024];\n\t\t\t\t\twhile (http2_stream_read_body(stream, buf, sizeof(buf)) > 0)\n\t\t\t\t\t\t;\n\n\t\t\t\t\tif (http2_stream_is_end(stream)) {\n\t\t\t\t\t\tint id = http2_stream_get_id(stream);\n\t\t\t\t\t\tif (completed_ids.find(id) == completed_ids.end()) {\n\t\t\t\t\t\t\tcompleted_ids.insert(id);\n\t\t\t\t\t\t\tclient_completed++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (items[i].stream) {\n\t\t\t\t\thttp2_stream_put(items[i].stream);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tfor (int i = 0; i < NUM_STREAMS; i++) {\n\t\t\tstruct http2_stream *stream = http2_stream_new(ctx);\n\t\t\tif (stream) {\n\t\t\t\tstreams.push_back(stream);\n\t\t\t\tchar path[64];\n\t\t\t\tsnprintf(path, sizeof(path), \"/stream%d\", i);\n\t\t\t\tchar body[64];\n\t\t\t\tint body_len = snprintf(body, sizeof(body), \"Req %d\", i);\n\n\t\t\t\tstruct http2_header_pair headers[] = {{\"content-type\", \"text/plain\"}, {NULL, NULL}};\n\t\t\t\thttp2_stream_set_request(stream, \"POST\", path, NULL, headers);\n\t\t\t\thttp2_stream_write_body(stream, (const uint8_t *)body, body_len, 1);\n\t\t\t}\n\n\t\t\t// Process events periodically to prevent deadlock/buffer overflow\n\t\t\tif (i % 10 == 0) {\n\t\t\t\tprocess_events(0);\n\t\t\t}\n\t\t}\n\t\tASSERT_EQ(streams.size(), NUM_STREAMS);\n\n\t\tstart_time = std::chrono::steady_clock::now();\n\t\twhile (client_completed < NUM_STREAMS &&\n\t\t\t   std::chrono::steady_clock::now() - start_time < std::chrono::seconds(30)) {\n\t\t\tprocess_events(10);\n\t\t}\n\n\t\tEXPECT_EQ(client_completed, NUM_STREAMS);\n\n\t\tfor (auto stream : streams) {\n\t\t\thttp2_stream_close(stream);\n\t\t}\n\t\thttp2_ctx_close(ctx);\n\t\ttest_completed = true;\n\t});\n\n\tserver_thread.join();\n\tclient_thread.join();\n}\n"
  },
  {
    "path": "test/cases/test-local-domain.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/dns_client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass LocalDomain : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST(LocalDomain, query)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\tsmartdns::TempFile hosts_file;\n\n\tstd::string listen_url = \"udp://\";\n\tlisten_url += DNS_MDNS_IP;\n\tlisten_url += \":\" + std::to_string(DNS_MDNS_PORT);\n\n\tserver_upstream1.Start(listen_url.c_str(), [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[][4] = {{1, 2, 3, 4}};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"102:304:500::1\", 60, 100);\n\thosts_file.Write(\"1.2.3.1 pc\\n\");\n\thosts_file.Write(\"1.2.3.2 phone\\n\");\n\thosts_file.Write(\"1.2.3.3 router\\n\");\n\n\t\n\tstd::string conf = R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nlocal-domain lan\n# mdns-lookup yes\n)\"\"\";\n\tconf += \"hosts-file \" + hosts_file.GetPath() + \"\\n\";\n\tconf += \"\\n\";\n\tserver.Start(conf);\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"b.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\n\tASSERT_TRUE(client.Query(\"pc A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"pc\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.1\");\n\n\tASSERT_TRUE(client.Query(\"phone.lan A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"phone.lan\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 59);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.2\");\n\n\tASSERT_TRUE(client.Query(\"router.lan AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n\nTEST(LocalDomain, ptr)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\tsmartdns::TempFile hosts_file;\n\n\tstd::string listen_url = \"udp://\";\n\tlisten_url += DNS_MDNS_IP;\n\tlisten_url += \":\" + std::to_string(DNS_MDNS_PORT);\n\n\tserver_upstream1.Start(listen_url.c_str(), [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype != DNS_T_PTR) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tdns_add_PTR(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, \"host.local\");\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t});\n\thosts_file.Write(\"1.2.3.1 pc\\n\");\n\thosts_file.Write(\"1.2.3.2 phone\\n\");\n\thosts_file.Write(\"1.2.3.3 router\\n\");\n\n\tstd::string conf = R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nlocal-domain lan\n)\"\"\";\n\tconf += \"hosts-file \" + hosts_file.GetPath() + \"\\n\";\n\tconf += \"\\n\";\n\tserver.Start(conf);\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"-x 1.2.3.1\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"1.3.2.1.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"pc.\");\n}\n"
  },
  {
    "path": "test/cases/test-mdns.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/dns_client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass mDNS : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST(mDNS, query)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tstd::string listen_url = \"udp://\";\n\tlisten_url += DNS_MDNS_IP;\n\tlisten_url += \":\" + std::to_string(DNS_MDNS_PORT);\n\n\tserver_upstream1.Start(listen_url.c_str(), [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[][4] = {{1, 2, 3, 4}};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[][16] = {{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr[0]);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"102:304:500::1\", 60, 100);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nmdns-lookup yes\ngroup-begin test\naddress -\n)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"b.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\n\tASSERT_TRUE(client.Query(\"host A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"host\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"host.local.\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetName(), \"host.local\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"host A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"host\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 59);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"host.local.\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetName(), \"host.local\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"host AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"host\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"host.local.\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetName(), \"host.local\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"102:304:500::1\");\n\n\tASSERT_TRUE(client.Query(\"host.local A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"host.local\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST(mDNS, ptr)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tstd::string listen_url = \"udp://\";\n\tlisten_url += DNS_MDNS_IP;\n\tlisten_url += \":\" + std::to_string(DNS_MDNS_PORT);\n\n\tserver_upstream1.Start(listen_url.c_str(), [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype != DNS_T_PTR) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tdns_add_PTR(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, \"host.local\");\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nmdns-lookup yes\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"-x 127.0.0.9\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"9.0.0.127.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"host.local.\");\n}\n"
  },
  {
    "path": "test/cases/test-mock-server.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nTEST(MockServer, query_fail)\n{\n\tsmartdns::MockServer server;\n\tsmartdns::Client client;\n\tserver.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\trequest->response_data_len = 0;\n\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t});\n\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tEXPECT_EQ(client.GetStatus(), \"SERVFAIL\");\n}\n\nTEST(MockServer, soa)\n{\n\tsmartdns::MockServer server;\n\tsmartdns::Client client;\n\tserver.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n}\n\nTEST(MockServer, noerror)\n{\n\tsmartdns::MockServer server;\n\tsmartdns::Client client;\n\tserver.Start(\"udp://0.0.0.0:61053\",\n\t\t\t\t [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_OK; });\n\n\tASSERT_TRUE(client.Query(\"example.com\", 61053));\n\tstd::cout << client.GetResult() << std::endl;\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n"
  },
  {
    "path": "test/cases/test-nameserver.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass NameServer : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(NameServer, cname)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"9.10.11.12\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:62053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:63053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nserver 127.0.0.1:62053 -group g1 -exclude-default-group\nserver 127.0.0.1:63053 -group g2 -exclude-default-group\nnameserver /a.com/g1\nnameserver /b.com/g2\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"c.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"c.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"9.10.11.12\");\n}\n"
  },
  {
    "path": "test/cases/test-perf.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass Perf : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Perf, no_speed_check)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tif (smartdns::IsCommandExists(\"dnsperf\") == false) {\n\t\tprintf(\"dnsperf not found, skip test, please install dnsperf first.\\n\");\n\t\tGTEST_SKIP();\n\t}\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\nlog-level error\nsocket-buff-size 1M\n)\"\"\");\n\tstd::string file = \"/tmp/smartdns-perftest-domain.list\" + smartdns::GenerateRandomString(5);\n\tstd::string cmd = \"dnsperf -p 60053 -b 1024\";\n\tcmd += \" -d \";\n\tcmd += file;\n\tstd::ofstream ofs(file);\n\tASSERT_TRUE(ofs.is_open());\n\tDefer\n\t{\n\t\tofs.close();\n\t\tunlink(file.c_str());\n\t};\n\n\tfor (int i = 0; i < 100000; i++) {\n\t\tstd::string domain = smartdns::GenerateRandomString(10);\n\t\tdomain += \".\";\n\t\tdomain += smartdns::GenerateRandomString(3);\n\n\t\tif (random() % 2 == 0) {\n\t\t\tdomain += \" A\";\n\t\t} else {\n\t\t\tdomain += \" AAAA\";\n\t\t}\n\n\t\tdomain += \"\\n\";\n\n\t\tofs.write(domain.c_str(), domain.length());\n\t\tofs.flush();\n\t}\n\n\tsystem(cmd.c_str());\n}\n"
  },
  {
    "path": "test/cases/test-ping.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/fast_ping.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/tlog.h\"\n#include \"gtest/gtest.h\"\n\nclass Ping : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp()\n\t{\n\t\tEXPECT_EQ(fast_ping_init(), 0);\n\t\tloglevel = tlog_getlevel();\n\t\ttlog_setlevel(TLOG_DEBUG);\n\t}\n\tvirtual void TearDown()\n\t{\n\t\tfast_ping_exit();\n\t\ttlog_setlevel(loglevel);\n\t}\n\n  private:\n\ttlog_level loglevel;\n};\n\nvoid ping_result_callback(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result,\n\t\t\t\t\t\t  struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv, int error,\n\t\t\t\t\t\t  void *userptr)\n{\n\tint *count = (int *)userptr;\n\tif (result == PING_RESULT_RESPONSE) {\n\t\t*count = 1;\n\t\ttlog(TLOG_INFO, \"ping to %s succeeded, seq=%d, ttl=%d\", host, seqno, ttl);\n\t}\n}\n\nTEST_F(Ping, icmp)\n{\n\tstruct ping_host_struct *ping_host;\n\tint count = 0;\n\t\n\tif (smartdns::IsICMPAvailable() == false) {\n\t\ttlog(TLOG_INFO, \"ICMP is not available, skip this test.\");\n\t\tGTEST_SKIP();\n\t\treturn;\n\t}\n\n\tping_host = fast_ping_start(PING_TYPE_ICMP, \"127.0.0.1\", 1, 1, 200, ping_result_callback, &count);\n\tASSERT_NE(ping_host, nullptr);\n\tusleep(10000);\n\tfast_ping_stop(ping_host);\n\tEXPECT_EQ(count, 1);\n}\n\nTEST_F(Ping, tcp)\n{\n\tstruct ping_host_struct *ping_host;\n\tint count = 0;\n\tping_host = fast_ping_start(PING_TYPE_TCP, \"127.0.0.1:1\", 1, 1, 200, ping_result_callback, &count);\n\tASSERT_NE(ping_host, nullptr);\n\tusleep(10000);\n\tfast_ping_stop(ping_host);\n\tEXPECT_EQ(count, 1);\n}\n\nTEST_F(Ping, tcp_syn)\n{\n\tstruct ping_host_struct *ping_host;\n\tint count = 0;\n\t\n\t/* Test TCP SYN ping - may not work in all environments */\n\tping_host = fast_ping_start(PING_TYPE_TCP_SYN, \"127.0.0.1:53\", 5, 1000, 1000, ping_result_callback, &count);\n\tif (ping_host == nullptr) {\n\t\ttlog(TLOG_INFO, \"TCP SYN ping not available (need root/CAP_NET_RAW), skip this test.\");\n\t\tGTEST_SKIP();\n\t\treturn;\n\t}\n\t\n\tusleep(10000);\n\tfast_ping_stop(ping_host);\n\t\n\tEXPECT_GT(count, 0);\n}\n\nTEST_F(Ping, tcp_syn_v6)\n{\n\tstruct ping_host_struct *ping_host;\n\tint count = 0;\n\n\t/* Test TCP SYN ping - may not work in all environments */\n\tping_host = fast_ping_start(PING_TYPE_TCP_SYN, \"[::1]:443\", 2, 1000, 1000, ping_result_callback, &count);\n\tif (ping_host == nullptr) {\n\t\ttlog(TLOG_INFO, \"TCP SYN ping not available (need root/CAP_NET_RAW), skip this test.\");\n\t\tGTEST_SKIP();\n\t\treturn;\n\t}\n\t\n\tusleep(1000000);\n\tfast_ping_stop(ping_host);\n\t\n\tEXPECT_GT(count, 0);\n}\n\nTEST_F(Ping, tcp_syn_concurrent)\n{\n\tstruct ping_host_struct *ping_host1;\n\tstruct ping_host_struct *ping_host2;\n\tint count1 = 0, count2 = 0;\n\t\n\t/* Test concurrent TCP SYN pings to different servers */\n\tping_host1 = fast_ping_start(PING_TYPE_TCP_SYN, \"127.0.0.1:22\", 1, 1, 500, ping_result_callback, &count1);\n\tif (ping_host1 == nullptr) {\n\t\ttlog(TLOG_INFO, \"TCP SYN ping not available, skip this test.\");\n\t\tGTEST_SKIP();\n\t\treturn;\n\t}\n\t\n\ttlog(TLOG_INFO, \"First ping started, now starting second...\");\n\t\n\t/* Use Alibaba DNS server (accessible in China) */\n\tping_host2 = fast_ping_start(PING_TYPE_TCP_SYN, \"127.0.0.2:53\", 1, 1, 500, ping_result_callback, &count2);\n\tASSERT_NE(ping_host2, nullptr);\n\t\n\ttlog(TLOG_INFO, \"Both pings started, waiting 200ms...\");\n\tusleep(200000); /* Wait 200ms for responses */\n\t\n\ttlog(TLOG_INFO, \"Wait complete, stopping pings...\");\n\tfast_ping_stop(ping_host1);\n\tfast_ping_stop(ping_host2);\n\tusleep(50000); /* Brief wait for cleanup */\n\t\n\ttlog(TLOG_INFO, \"TCP SYN concurrent ping test completed, count1=%d, count2=%d\", count1, count2);\n\t\n\t/* At least one should succeed (localhost SSH should be reachable) */\n\tEXPECT_GE(count1 + count2, 1);\n}\n\nvoid fake_ping_result_callback(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result,\n\t\t\t\t\t\t\t   struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv,\n\t\t\t\t\t\t\t   int error, void *userptr)\n{\n\tif (result == PING_RESULT_RESPONSE) {\n\t\tint *count = (int *)userptr;\n\t\tdouble rtt = tv->tv_sec * 1000.0 + tv->tv_usec / 1000.0;\n\t\ttlog(TLOG_INFO, \"from %15s: seq=%d ttl=%d time=%.3f\\n\", host, seqno, ttl, rtt);\n\t\t*count = (int)rtt;\n\t}\n}\n\nTEST_F(Ping, fake_icmp)\n{\n\tstruct ping_host_struct *ping_host;\n\tint count = 0;\n\tfast_ping_fake_ip_add(PING_TYPE_ICMP, \"1.2.3.4\", 60, 5);\n\tping_host = fast_ping_start(PING_TYPE_ICMP, \"1.2.3.4\", 1, 1000, 200, fake_ping_result_callback, &count);\n\tASSERT_NE(ping_host, nullptr);\n\tusleep(100000);\n\tfast_ping_stop(ping_host);\n\tEXPECT_GE(count, 5);\n}\n"
  },
  {
    "path": "test/cases/test-ptr.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass Ptr : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Ptr, query)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_PTR) {\n\t\t\tdns_add_PTR(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 30, \"my-hostname\");\n\t\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"4.3.2.1.in-addr.arpa PTR\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"4.3.2.1.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"PTR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"my-hostname.\");\n}\n\nTEST_F(Ptr, address_expand_ptr)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\nexpand-ptr-from-address yes\naddress /a.com/10.11.12.13\naddress /a.com/64:ff9b::1010:1010\naddress /pi.local/192.168.1.1\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"13.12.11.10.in-addr.arpa PTR\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"13.12.11.10.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"PTR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"a.com.\");\n\n\tASSERT_TRUE(client.Query(\"0.1.0.1.0.1.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa PTR\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(),\n\t\t\t  \"0.1.0.1.0.1.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.9.f.f.4.6.0.0.ip6.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"PTR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"a.com.\");\n\n\tASSERT_TRUE(client.Query(\"-x 192.168.1.1\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"1.1.168.192.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"PTR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"pi.local.\");\n}\n\nTEST_F(Ptr, smartdns)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_PTR) {\n\t\t\tdns_add_PTR(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 30, \"my-hostname\");\n\t\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nserver-name my-server\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"1.0.0.127.in-addr.arpa PTR\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"1.0.0.127.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"PTR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"my-server.\");\n}\n\n\nTEST_F(Ptr, private_soa)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_PTR) {\n\t\t\tdns_add_PTR(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 30, \"my-hostname\");\n\t\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"4.3.168.192.in-addr.arpa PTR\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"4.3.168.192.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n}\n\nTEST_F(Ptr, private_nameserver)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_PTR) {\n\t\t\tdns_add_PTR(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 30, \"my-hostname\");\n\t\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053 -group test-server\nnameserver /168.192.in-addr.arpa/test-server\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"4.3.168.192.in-addr.arpa PTR\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"4.3.168.192.in-addr.arpa\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"PTR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"my-hostname.\");\n}"
  },
  {
    "path": "test/cases/test-qtype-soa.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass QtypeSOA : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(QtypeSOA, AAAA_HTTPS)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nforce-qtype-SOA 28,65\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com -t HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n}\n\nTEST_F(QtypeSOA, AAAA_Except)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nforce-qtype-SOA 28\naddress /a.com/-\n)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n}\n\nTEST_F(QtypeSOA, force_AAAA_SOA_Except)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nforce-AAAA-SOA yes\naddress /a.com/-\n)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n}\n\nTEST_F(QtypeSOA, force_AAAA_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\nforce-AAAA-SOA yes\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n}\n\nTEST_F(QtypeSOA, bind_force_AAAA_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(\nbind [::]:60053\nbind [::]:60153 -force-aaaa-soa\nserver 127.0.0.1:61053\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\n\tASSERT_TRUE(client.Query(\"a.com A\", 60153));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60153));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n}\n\nTEST_F(QtypeSOA, HTTPS_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstd::map<int, int> qid_map;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_HTTPS) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tstruct dns_rr_nested svcparam_buffer;\n\n\t\tdns_add_HTTPS_start(&svcparam_buffer, packet, DNS_RRS_AN, request->domain.c_str(), 3, 1, \"a.com\");\n\t\tconst char alph[] = \"\\x02h2\\x05h3-19\";\n\t\tint alph_len = sizeof(alph) - 1;\n\t\tdns_HTTPS_add_alpn(&svcparam_buffer, alph, alph_len);\n\t\tdns_HTTPS_add_port(&svcparam_buffer, 443);\n\t\tunsigned char add_v4[] = {1, 2, 3, 4};\n\t\tunsigned char *addr[1] = {add_v4};\n\t\tdns_HTTPS_add_ipv4hint(&svcparam_buffer, addr, 1);\n\t\tunsigned char ech[] = {0x00, 0x45, 0xfe, 0x0d, 0x00};\n\t\tdns_HTTPS_add_ech(&svcparam_buffer, (void *)ech, sizeof(ech));\n\t\tunsigned char add_v6[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\taddr[0] = add_v6;\n\t\tdns_HTTPS_add_ipv6hint(&svcparam_buffer, addr, 1);\n\t\tdns_add_HTTPS_end(&svcparam_buffer);\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-console yes\ndualstack-ip-selection no\nspeed-check-mode none\naddress /a.com/#\nlog-level debug\ncache-persist no)\"\"\");\n\tsmartdns::Client client;\n    ASSERT_TRUE(client.Query(\"a.com HTTPS\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAuthorityNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NXDOMAIN\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAuthority()[0].GetTTL(), 30);\n\tEXPECT_EQ(client.GetAuthority()[0].GetType(), \"SOA\");\n}\n"
  },
  {
    "path": "test/cases/test-rule.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/dns.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass Rule : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Rule, Match)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /a.com/5.6.7.8\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"a.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"aa.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"aa.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Rule, PrefixWildcardMatch)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /*a.com/5.6.7.8\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"a.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"aa.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"aa.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"ab.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"ab.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Rule, SubDomainMatchOnly)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /*.a.com/5.6.7.8\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"aa.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"aa.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Rule, RootDomainMatchOnly)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /-.a.com/5.6.7.8\naddress /b.com/3.4.5.6\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"a.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"b.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"ba.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"ba.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"b.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"3.4.5.6\");\n}\n\nTEST_F(Rule, AAAA_SOA)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress /-.a.com/#6\naddress /*.b.com/#6\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"a.a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\n\tASSERT_TRUE(client.Query(\"a.b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n}\n\nTEST_F(Rule, root)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"64:ff9b::102:304\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\naddress #6\naddress /-.a.com/-6\naddress \n)\"\"\");\n\tsmartdns::Client client;\n\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 700);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"64:ff9b::102:304\");\n\n\tASSERT_TRUE(client.Query(\"a.a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\n\tASSERT_TRUE(client.Query(\"b.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n}\n\nTEST_F(Rule, root_and_sub)\n{\n\tsmartdns::Server server;\n\tsmartdns::MockServer server_upstream;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"9.9.9.9\", 700);\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nlog-level debug\nspeed-check-mode none\naddress /q.a.com/1.2.3.4\naddress /-.a.com/4.5.6.7\naddress /*.a.com/7.8.9.10\n)\"\"\");\n\tsmartdns::Client client;\n\n\t// 1. q.a.com should be 1.2.3.4\n\tASSERT_TRUE(client.Query(\"q.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\t// 2. a.com should be 4.5.6.7 (matching -.a.com)\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"4.5.6.7\");\n\n\t// 3. other.a.com should be 7.8.9.10 (matching *.a.com)\n\tASSERT_TRUE(client.Query(\"other.a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"7.8.9.10\");\n}"
  },
  {
    "path": "test/cases/test-same-pending-query.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass SamePending : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(SamePending, pending)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\tstd::map<int, int> qid_map;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tstd::string domain = request->domain;\n\t\tif (qid_map.find(request->packet->head.id) != qid_map.end()) {\n\t\t\tqid_map[request->packet->head.id]++;\n\t\t\tusleep(5000);\n\t\t} else {\n\t\t\tqid_map[request->packet->head.id] = 1;\n\t\t\tusleep(20000);\n\t\t}\n\n\t\tif (request->domain.length() == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tunsigned char addr[4] = {1, 2, 3, 4};\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr);\n\t\t} else if (request->qtype == DNS_T_AAAA) {\n\t\t\tunsigned char addr[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), 61, addr);\n\t\t} else {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\trequest->response_packet->head.rcode = DNS_RC_NOERROR;\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ncache-size 0\nspeed-check-mode none\nlog-level error\n)\"\"\");\n\n\tstd::vector<std::thread> threads;\n\tfor (int i = 0; i < 5; i++) {\n\t\tauto t = std::thread([&]() {\n\t\t\tfor (int j = 0; j < 10; j++) {\n\t\t\t\tsmartdns::Client client;\n\t\t\t\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\t\t\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\t\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\t\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\t\t\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t\t\t}\n\t\t});\n\t\tthreads.push_back(std::move(t));\n\t}\n\n\tfor (auto &t : threads) {\n\t\tt.join();\n\t}\n\n\tEXPECT_LT(qid_map.size(), 80);\n}\n"
  },
  {
    "path": "test/cases/test-server.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"gtest/gtest.h\"\n\nclass Server : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(Server, all_unreach)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tEXPECT_EQ(request->domain, \"e.com\");\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001::\", 128, 10000);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver tls://255.255.255.255\nserver https://255.255.255.255\nserver tcp://255.255.255.255\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tEXPECT_EQ(client.GetStatus(), \"SERVFAIL\");\n\tEXPECT_EQ(client.GetAnswerNum(), 0);\n\n\t/* server should not crash */\n\tASSERT_TRUE(client.Query(\"a.com +tcp\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tEXPECT_EQ(client.GetStatus(), \"SERVFAIL\");\n\tEXPECT_EQ(client.GetAnswerNum(), 0);\n}\n\nTEST_F(Server, one_nxdomain)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tusleep(50000);\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:62053\",\n\t\t\t\t\t\t   [](struct smartdns::ServerRequestContext *request) { return smartdns::SERVER_REQUEST_SOA; });\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001::\", 128, 10000);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053\nserver 127.0.0.1:62053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Server, retry_no_result_with_NOERROR)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::Server server;\n\tint count = 0;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tif (count++ < 2) {\n\t\t\tdns_add_domain(request->response_packet, request->domain.c_str(), request->qtype, request->qclass);\n\t\t\trequest->response_packet->head.tc = 1;\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Server, retry_no_response)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::Server server;\n\tint count = 0;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tcount++;\n\t\treturn smartdns::SERVER_REQUEST_NO_RESPONSE;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"SERVFAIL\");\n\tEXPECT_GE(client.GetQueryTime(), 1500);\n\tEXPECT_GE(count, 4);\n}\n\nTEST_F(Server, max_queries)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::Server server;\n\tint count = 0;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tsleep(1);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 128, 10);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nmax-query-limit 2\n)\"\"\");\n\n\tstd::vector<std::thread> threads;\n\tint success_num = 0;\n\tint refused_num = 0;\n\tfor (int i = 0; i < 5; i++) {\n\t\tauto t = std::thread([&]() {\n\t\t\tsmartdns::Client client;\n\t\t\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\t\t\tif (client.GetStatus() == \"NOERROR\") {\n\t\t\t\tsuccess_num++;\n\t\t\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\t\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\t\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\t\t\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t\t\t} else if (client.GetStatus() == \"REFUSED\") {\n\t\t\t\trefused_num++;\n\t\t\t} else {\n\t\t\t\tFAIL();\n\t\t\t}\n\t\t});\n\t\tthreads.push_back(std::move(t));\n\t}\n\n\tfor (auto &t : threads) {\n\t\tt.join();\n\t}\n\n\tEXPECT_EQ(success_num, 2);\n\tEXPECT_EQ(refused_num, 3);\n\n\tfor (int i = 0; i < 5; i++) {\n\t\tsmartdns::Client client;\n\t\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\t\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\t\tASSERT_EQ(client.GetAnswerNum(), 1);\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\t\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\t}\n}\n\nTEST_F(Server, interface)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001::\", 128, 10000);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053 -interface lo\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Server, refused)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::Server server;\n\tint count = 0;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\trequest->response_packet->head.rcode = DNS_RC_REFUSED;\n\t\tdns_add_domain(request->response_packet, request->domain.c_str(), request->qtype, request->qclass);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 0);\n\tEXPECT_EQ(client.GetStatus(), \"REFUSED\");\n\tEXPECT_LT(client.GetQueryTime(), 100);\n}\n\nTEST_F(Server, fallback)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001::\", 128, 10000);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053 -fallback\nserver 127.0.0.1:61054\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_GE(client.GetQueryTime(), 1000);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Server, fallback_group)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001::\", 128, 10000);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053 -e -group fallback\nserver 127.0.0.1:61054\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_GE(client.GetQueryTime(), 1000);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Server, groups)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::MockServer server_upstream3;\n\tsmartdns::Server server;\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:61054\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.5\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream3.Start(\"udp://0.0.0.0:61055\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.6\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 128, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.5\", 128, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.6\", 128, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053 \nserver 127.0.0.1:61054 -e -group a -group b\nserver 127.0.0.1:61055 -e -group c -group d\nnameserver /a.com/a\nnameserver /b.com/b\nnameserver /c.com/c\nnameserver /d.com/d\nnameserver /e.com/unknown\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.5\");\n\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.5\");\n\n\tASSERT_TRUE(client.Query(\"c.com\", 60053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"c.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.6\");\n\n\tASSERT_TRUE(client.Query(\"d.com\", 60053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"d.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.6\");\n\n\tASSERT_TRUE(client.Query(\"e.com\", 60053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"e.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"f.com\", 60053));\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"f.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(Server, repeat_group)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tstatic int count = 0;\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tcount++;\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\tif (count > 1) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.5\", 611);\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001::\", 128, 10000);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053 -e -group a -group a\nnameserver /a.com/a\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tusleep(100000);\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\n\nTEST_F(Server, bad_block_ip)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::MockServer server_upstream3;\n\tsmartdns::Server server;\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"127.0.0.1\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:61054\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"::\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream3.Start(\"udp://0.0.0.0:61055\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tusleep(100000); \n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.6\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 128, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.5\", 128, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.6\", 128, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053 \nserver 127.0.0.1:61054 \nserver 127.0.0.1:61055\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.6\");\n}\n\nTEST_F(Server, bad_block_ip_no_check_speed)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::MockServer server_upstream3;\n\tsmartdns::Server server;\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"127.0.0.1\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:61054\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"::\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver_upstream3.Start(\"udp://0.0.0.0:61055\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tusleep(100000); \n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.6\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 128, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.5\", 128, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.6\", 128, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053 \nserver 127.0.0.1:61054 \nserver 127.0.0.1:61055\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.6\");\n}\n\nTEST_F(Server, case_insensitive)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tusleep(100000);\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\", 611);\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 128, 1000);\n\tserver.Start(R\"\"\"(bind [::]:60053\nbind-tcp [::]:60053\nserver 127.0.0.1:61053 \n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tASSERT_TRUE(client.Query(\"A.cOm\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LE(client.GetQueryTime(), 5);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"A.cOm\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}"
  },
  {
    "path": "test/cases/test-speed-check.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass SpeedCheck : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(SpeedCheck, response_mode)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nresponse-mode first-ping\ndomain-rules /a.com/ -r fastest-response\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tif (smartdns::IsICMPAvailable()) {\n\t\tEXPECT_GT(client.GetQueryTime(), 100);\n\t}\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 10);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n}\n\nTEST_F(SpeedCheck, none)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 40);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 40);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n}\n\nTEST_F(SpeedCheck, domain_rules_none)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndomain-rules /a.com/ -c none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tif (smartdns::IsICMPAvailable()) {\n\t\tEXPECT_GT(client.GetQueryTime(), 200);\n\t}\n\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 20);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n}\n\nTEST_F(SpeedCheck, only_ping)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 1200);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n}\n\nTEST_F(SpeedCheck, no_ping_fallback_tcp)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 1000);\n\tserver.MockPing(PING_TYPE_TCP, \"5.6.7.8:80\", 60, 100);\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping,tcp:80\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 500);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n}\n\nTEST_F(SpeedCheck, tcp_faster_than_ping)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 300);\n\tserver.MockPing(PING_TYPE_TCP, \"5.6.7.8:80\", 60, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping,tcp:80\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 500);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n}\n\nTEST_F(SpeedCheck, fastest_ip)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 110);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode ping\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 200);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\n\tusleep(220 * 1000);\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 20);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n}\n\nTEST_F(SpeedCheck, unreach_best_ipv4)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"9.10.11.12\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 10000);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 10000);\n\tserver.MockPing(PING_TYPE_ICMP, \"9.10.11.12\", 60, 10000);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nserver 127.0.0.1:62053\nspeed-check-mode ping\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 1200);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(SpeedCheck, unreach_best_ipv6)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::3\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 10000);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 10000);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::3\", 60, 10000);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nserver 127.0.0.1:62053\nspeed-check-mode ping\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 1200);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_GT(client.GetAnswer()[0].GetTTL(), 597);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::2\");\n}\n\nTEST_F(SpeedCheck, global_none_rule_check)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 150);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\ndomain-rules /b.com/ -c ping\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"b.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_GT(client.GetQueryTime(), 50);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"b.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\n\tASSERT_TRUE(client.Query(\"a.com\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_LT(client.GetQueryTime(), 20);\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n\tEXPECT_EQ(client.GetAnswer()[1].GetData(), \"5.6.7.8\");\n}"
  },
  {
    "path": "test/cases/test-srv.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass SRV : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(SRV, query)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_SRV) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tdns_add_SRV(packet, DNS_RRS_AN, request->domain.c_str(), 603, 1, 1, 443, \"www.example.com\");\n\t\tdns_add_SRV(packet, DNS_RRS_AN, request->domain.c_str(), 603, 1, 1, 443, \"www1.example.com\");\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"_ldap._tcp.local.com SRV\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 2);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"_ldap._tcp.local.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 603);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"SRV\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 1 443 www.example.com.\");\n}\n\nTEST_F(SRV, match)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_SRV) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\n\t\tstruct dns_packet *packet = request->response_packet;\n\t\tdns_add_SRV(packet, DNS_RRS_AN, request->domain.c_str(), 603, 1, 1, 443, \"www.example.com\");\n\t\tdns_add_SRV(packet, DNS_RRS_AN, request->domain.c_str(), 603, 1, 1, 443, \"www1.example.com\");\n\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\nsrv-record /_ldap._tcp.local.com/www.a.com,443,1,1\nsrv-record /_ldap._tcp.local.com/www1.a.com,443,1,1\nsrv-record /_ldap._tcp.local.com/www2.a.com,443,1,1\nspeed-check-mode none\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"_ldap._tcp.local.com SRV\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 3);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"_ldap._tcp.local.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 600);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"SRV\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1 1 443 www.a.com.\");\n}\n"
  },
  {
    "path": "test/cases/test-stress.cc",
    "content": "#include \"server.h\"\n#include \"smartdns/dns.h\"\n#include \"gtest/gtest.h\"\n#include <atomic>\n#include <chrono>\n#include <thread>\n#include <vector>\n#include <string>\n#include <cstdlib>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <cstring>\n\n// Helper function to get environment variable with default value\nint get_env_int(const char* name, int default_value) {\n    const char* value = std::getenv(name);\n    if (value) {\n        return std::atoi(value);\n    }\n    return default_value;\n}\n\n// Simple UDP DNS query function\nbool udp_dns_query(const std::string& domain, int port) {\n    int sock = socket(AF_INET, SOCK_DGRAM, 0);\n    if (sock < 0) return false;\n\n    struct sockaddr_in addr;\n    memset(&addr, 0, sizeof(addr));\n    addr.sin_family = AF_INET;\n    addr.sin_port = htons(port);\n    inet_pton(AF_INET, \"127.0.0.1\", &addr.sin_addr);\n\n    // Build simple DNS query for A record\n    unsigned char query[256];\n    memset(query, 0, sizeof(query));\n\n    // Random ID\n    query[0] = rand() % 256;\n    query[1] = rand() % 256;\n\n    // Flags: recursion desired\n    query[2] = 0x01;\n    query[3] = 0x00;\n\n    // QDCOUNT = 1\n    query[4] = 0x00;\n    query[5] = 0x01;\n\n    // Encode domain name\n    int pos = 12;\n    size_t start = 0;\n    size_t dot_pos = domain.find('.');\n    while (dot_pos != std::string::npos) {\n        std::string label = domain.substr(start, dot_pos - start);\n        query[pos++] = label.size();\n        memcpy(&query[pos], label.c_str(), label.size());\n        pos += label.size();\n        start = dot_pos + 1;\n        dot_pos = domain.find('.', start);\n    }\n    std::string label = domain.substr(start);\n    query[pos++] = label.size();\n    memcpy(&query[pos], label.c_str(), label.size());\n    pos += label.size();\n    query[pos++] = 0; // null terminator\n\n    // QTYPE: A (1)\n    query[pos++] = 0x00;\n    query[pos++] = 0x01;\n\n    // QCLASS: IN (1)\n    query[pos++] = 0x00;\n    query[pos++] = 0x01;\n\n    // Send query\n    if (sendto(sock, query, pos, 0, (struct sockaddr*)&addr, sizeof(addr)) < 0) {\n        close(sock);\n        return false;\n    }\n\n    // Receive response\n    unsigned char response[512];\n    socklen_t addr_len = sizeof(addr);\n    int recv_len = recvfrom(sock, response, sizeof(response), 0, (struct sockaddr*)&addr, &addr_len);\n    close(sock);\n\n    if (recv_len < 12) return false;\n\n    // Check RCODE (last 4 bits of flags)\n    if ((response[3] & 0x0F) == 0) return true; // NOERROR\n\n    return false;\n}\n\n// Protocol stress test configuration\nstruct ProtocolConfig {\n    std::string name;\n    std::string bind_config;\n    std::string server_config;\n    std::string upstream_bind_config;\n};\n\nclass Stress : public ::testing::TestWithParam<ProtocolConfig> {\nprotected:\n    void SetUp() override {\n        // Common setup if needed\n    }\n\n    void TearDown() override {\n        // Common cleanup if needed\n    }\n};\n\n// Define protocol configurations\nconst ProtocolConfig protocols[] = {\n    {\n        \"UDP\",\n        \"bind [::]:61053\",\n        \"server udp://127.0.0.1:60053\",\n        \"bind [::]:60053\"\n    },\n    {\n        \"TCP\",\n        \"bind [::]:61053\",\n        \"server tcp://127.0.0.1:60053\",\n        \"bind-tcp [::]:60053\"\n    },\n    {\n        \"TLS\",\n        \"bind [::]:61053\",\n        \"server tls://127.0.0.1:60053 -no-check-certificate\",\n        \"bind-tls [::]:60053\"\n    },\n    {\n        \"HTTP2\",\n        \"bind [::]:61053\",\n        \"server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn h2\",\n        \"bind-https [::]:60053 -alpn h2\"\n    },\n    {\n        \"HTTP1_1\",\n        \"bind [::]:61053\",\n        \"server https://127.0.0.1:60053/dns-query -no-check-certificate -alpn http/1.1\",\n        \"bind-https [::]:60053 -alpn http/1.1\"\n    }\n};\n\n// Test stress for each protocol: 100 clients, each making 100 queries\nTEST_P(Stress, Query) {\n    const auto& config = GetParam();\n\n    smartdns::Server upstream_server;\n    smartdns::Server main_server;\n\n    // Start upstream server (second layer) that returns fixed IP and mocks ping\n    upstream_server.Start(config.upstream_bind_config + R\"\"\"(\naddress /test.com/192.168.1.100\naddress /example.com/192.168.1.101\naddress /domain.com/192.168.1.102\n)\"\"\");\n\n    // Mock ping responses for the IPs\n    main_server.MockPing(PING_TYPE_ICMP, \"192.168.1.100\", 60, 10);\n    main_server.MockPing(PING_TYPE_ICMP, \"192.168.1.101\", 60, 5);\n    main_server.MockPing(PING_TYPE_ICMP, \"192.168.1.102\", 60, 20);\n\n    // Start main server that forwards to upstream via specified protocol\n    main_server.Start(config.bind_config + \"\\n\" + config.server_config + R\"\"\"(\ncache-size 0\nspeed-check-mode ping\n)\"\"\");\n\n    // Wait for servers to be ready\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n\n    std::vector<std::thread> client_threads;\n    std::atomic<int> total_queries{0};\n    std::atomic<int> success_count{0};\n    std::atomic<int> failure_count{0};\n    std::atomic<bool> stop_all_tasks{false};  // Flag to control all tasks exit\n\n    const int num_clients = get_env_int(\"SMARTDNS_STRESS_CLIENTS\", 1);\n    const int queries_per_client = get_env_int(\"SMARTDNS_STRESS_QUERIES\", 200);\n\n    auto start_time = std::chrono::steady_clock::now();\n\n    // Launch 100 client threads, each making 100 queries\n    for (int client_id = 0; client_id < num_clients; client_id++) {\n        client_threads.emplace_back([client_id, &total_queries, &success_count, &failure_count, &stop_all_tasks, queries_per_client]() {\n            for (int query_id = 0; query_id < queries_per_client; query_id++) {\n                // Check if stop flag is set, terminate all tasks\n                if (stop_all_tasks.load()) {\n                    return;\n                }\n\n                std::string domain;\n\n                // Rotate through different domains to test various responses\n                switch (query_id % 3) {\n                case 0:\n                    domain = \"test.com\";\n                    break;\n                case 1:\n                    domain = \"example.com\";\n                    break;\n                case 2:\n                    domain = \"domain.com\";\n                    break;\n                }\n\n                total_queries++;\n                if (udp_dns_query(domain, 61053)) {\n                    success_count++;\n                } else {\n                    failure_count++;\n                    stop_all_tasks.store(true);  // Set flag to stop all tasks\n                    return;\n                }\n            }\n        });\n    }\n\n    // Wait for all client threads to complete\n    for (auto& t : client_threads) {\n        t.join();\n    }\n\n    auto end_time = std::chrono::steady_clock::now();\n    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);\n\n    int expected_total = num_clients * queries_per_client;\n    double qps = (expected_total * 1000.0) / duration.count();\n\n    std::cout << config.name << \" Stress Test Results:\" << std::endl;\n    std::cout << \"  Total Queries: \" << total_queries.load() << \" (expected: \" << expected_total << \")\" << std::endl;\n    std::cout << \"  Success: \" << success_count.load() << std::endl;\n    std::cout << \"  Failure: \" << failure_count.load() << std::endl;\n    std::cout << \"  Duration: \" << duration.count() << \"ms\" << std::endl;\n    std::cout << \"  QPS: \" << qps << std::endl;\n    double success_rate = total_queries.load() > 0 ? (success_count.load() * 100.0 / total_queries.load()) : 0.0;\n    std::cout << \"  Success Rate: \" << success_rate << \"%\" << std::endl;\n\n    // Assertions\n    EXPECT_FALSE(stop_all_tasks.load());  // No failures should occur, all tasks should complete\n    EXPECT_EQ(total_queries.load(), expected_total);\n    EXPECT_EQ(success_count.load(), expected_total);\n    EXPECT_EQ(failure_count.load(), 0);\n}\n\n// Instantiate the test for each protocol\nINSTANTIATE_TEST_SUITE_P(, Stress, \n                         ::testing::ValuesIn(protocols),\n                         [](const ::testing::TestParamInfo<ProtocolConfig>& info) {\n                             return info.param.name;\n                         });\n// filter to run specific tests\n// ./test.bin --gtest_filter=\"Stress.Query/UDP\"\n// ./test.bin --gtest_filter=\"Stress.Query/TCP\"\n"
  },
  {
    "path": "test/cases/test-subnet.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include \"smartdns/dns.h\"\n#include \"include/utils.h\"\n#include \"server.h\"\n#include \"smartdns/util.h\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nclass SubNet : public ::testing::Test\n{\n  protected:\n\tvirtual void SetUp() {}\n\tvirtual void TearDown() {}\n};\n\nTEST_F(SubNet, pass_subnet)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tstruct dns_opt_ecs ecs;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tint has_ecs = 0;\n\n\t\trr_count = 0;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count <= 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\tif (ret != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thas_ecs = 1;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (has_ecs == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.family != 1) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (memcmp(ecs.addr, \"\\x08\\x08\\x08\\x00\", 4) != 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.source_prefix != 24) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A +subnet=8.8.8.8/24\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(SubNet, conf)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_A) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tstruct dns_opt_ecs ecs;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tint has_ecs = 0;\n\n\t\trr_count = 0;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count <= 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\tif (ret != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thas_ecs = 1;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (has_ecs == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.family != DNS_OPT_ECS_FAMILY_IPV4) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (memcmp(ecs.addr, \"\\x08\\x08\\x08\\x00\", 4) != 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.source_prefix != 24) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nedns-client-subnet 8.8.8.8/24\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"1.2.3.4\");\n}\n\nTEST_F(SubNet, conf_v6)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_AAAA) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tstruct dns_opt_ecs ecs;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tint has_ecs = 0;\n\n\t\trr_count = 0;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count <= 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\tif (ret != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thas_ecs = 1;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (has_ecs == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.family != DNS_OPT_ECS_FAMILY_IPV6) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (memcmp(ecs.addr, \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\", 16) != 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.source_prefix != 64) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 70);\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nedns-client-subnet ffff:ffff:ffff:ffff:ffff::/64\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::1\");\n}\n\nTEST_F(SubNet, v4_server_subnet_txt)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_TXT) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tstruct dns_opt_ecs ecs;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tint has_ecs = 0;\n\n\t\trr_count = 0;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count <= 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\tif (ret != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thas_ecs = 1;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (has_ecs == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.family != DNS_OPT_ECS_FAMILY_IPV4) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (memcmp(ecs.addr, \"\\x08\\x08\\x08\\x00\", 4) != 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.source_prefix != 24) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tdns_add_TXT(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 6, \"hello world\");\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053 -subnet 8.8.8.8/24 -subnet-all-query-types\ndualstack-ip-selection no\nrr-ttl-min 0\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com TXT\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 6);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"TXT\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"\\\"hello world\\\"\");\n}\n\nTEST_F(SubNet, v6_default_subnet_txt)\n{\n\tsmartdns::MockServer server_upstream;\n\tsmartdns::Server server;\n\n\tserver_upstream.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype != DNS_T_TXT) {\n\t\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t\t}\n\t\tstruct dns_opt_ecs ecs;\n\t\tstruct dns_rrs *rrs = NULL;\n\t\tint rr_count = 0;\n\t\tint i = 0;\n\t\tint ret = 0;\n\t\tint has_ecs = 0;\n\n\t\trr_count = 0;\n\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\tif (rr_count <= 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\tif (ret != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thas_ecs = 1;\n\t\t\tbreak;\n\t\t}\n\t\tif (has_ecs == 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.family != DNS_OPT_ECS_FAMILY_IPV6) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (memcmp(ecs.addr, \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\", 16) != 0) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tif (ecs.source_prefix != 64) {\n\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t}\n\n\t\tdns_add_TXT(request->response_packet, DNS_RRS_AN, request->domain.c_str(), 6, \"hello world\");\n\t\treturn smartdns::SERVER_REQUEST_OK;\n\t});\n\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:61053\ndualstack-ip-selection no\nrr-ttl-min 0\nedns-client-subnet ffff:ffff:ffff:ffff:ffff::/64\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com TXT\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 6);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"TXT\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"\\\"hello world\\\"\");\n}\n\nTEST_F(SubNet, per_server)\n{\n\tsmartdns::MockServer server_upstream1;\n\tsmartdns::MockServer server_upstream2;\n\tsmartdns::Server server;\n\n\tserver_upstream1.Start(\"udp://0.0.0.0:61053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\t\t\tstruct dns_opt_ecs ecs;\n\t\t\tstruct dns_rrs *rrs = NULL;\n\t\t\tint rr_count = 0;\n\t\t\tint i = 0;\n\t\t\tint ret = 0;\n\t\t\tint has_ecs = 0;\n\n\t\t\trr_count = 0;\n\t\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\t\tif (rr_count <= 0) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thas_ecs = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (has_ecs == 1) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tstruct dns_opt_ecs ecs;\n\t\t\tstruct dns_rrs *rrs = NULL;\n\t\t\tint rr_count = 0;\n\t\t\tint i = 0;\n\t\t\tint ret = 0;\n\t\t\tint has_ecs = 0;\n\n\t\t\trr_count = 0;\n\t\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\t\tif (rr_count <= 0) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thas_ecs = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (has_ecs == 1) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver_upstream2.Start(\"udp://0.0.0.0:62053\", [&](struct smartdns::ServerRequestContext *request) {\n\t\tif (request->qtype == DNS_T_A) {\n\n\t\t\tstruct dns_opt_ecs ecs;\n\t\t\tstruct dns_rrs *rrs = NULL;\n\t\t\tint rr_count = 0;\n\t\t\tint i = 0;\n\t\t\tint ret = 0;\n\t\t\tint has_ecs = 0;\n\n\t\t\trr_count = 0;\n\t\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\t\tif (rr_count <= 0) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thas_ecs = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (has_ecs == 0) {\n\t\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"1.2.3.4\");\n\t\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t\t}\n\n\t\t\tif (ecs.family != DNS_OPT_ECS_FAMILY_IPV4) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tif (memcmp(ecs.addr, \"\\x08\\x08\\x08\\x00\", 4) != 0) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tif (ecs.source_prefix != 24) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"5.6.7.8\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\tif (request->qtype == DNS_T_AAAA) {\n\t\t\tstruct dns_opt_ecs ecs;\n\t\t\tstruct dns_rrs *rrs = NULL;\n\t\t\tint rr_count = 0;\n\t\t\tint i = 0;\n\t\t\tint ret = 0;\n\t\t\tint has_ecs = 0;\n\n\t\t\trr_count = 0;\n\t\t\trrs = dns_get_rrs_start(request->packet, DNS_RRS_OPT, &rr_count);\n\t\t\tif (rr_count <= 0) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tfor (i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(request->packet, rrs)) {\n\t\t\t\tmemset(&ecs, 0, sizeof(ecs));\n\t\t\t\tret = dns_get_OPT_ECS(rrs, &ecs);\n\t\t\t\tif (ret != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\thas_ecs = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (has_ecs == 0) {\n\t\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::1\");\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tif (ecs.family != DNS_OPT_ECS_FAMILY_IPV6) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tif (memcmp(ecs.addr, \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\", 16) != 0) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tif (ecs.source_prefix != 64) {\n\t\t\t\treturn smartdns::SERVER_REQUEST_ERROR;\n\t\t\t}\n\n\t\t\tsmartdns::MockServer::AddIP(request, request->domain.c_str(), \"2001:db8::2\");\n\t\t\treturn smartdns::SERVER_REQUEST_OK;\n\t\t}\n\n\t\treturn smartdns::SERVER_REQUEST_SOA;\n\t});\n\n\tserver.MockPing(PING_TYPE_ICMP, \"1.2.3.4\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"5.6.7.8\", 60, 10);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::1\", 60, 100);\n\tserver.MockPing(PING_TYPE_ICMP, \"2001:db8::2\", 60, 10);\n\tserver.Start(R\"\"\"(bind [::]:60053\nserver 127.0.0.1:62053 -subnet=8.8.8.8/24 -subnet=ffff:ffff:ffff:ffff:ffff::/64\nserver 127.0.0.1:61053\ndualstack-ip-selection no\n)\"\"\");\n\tsmartdns::Client client;\n\tASSERT_TRUE(client.Query(\"a.com A\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"A\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"5.6.7.8\");\n\n\tASSERT_TRUE(client.Query(\"a.com AAAA\", 60053));\n\tstd::cout << client.GetResult() << std::endl;\n\tASSERT_EQ(client.GetAnswerNum(), 1);\n\tEXPECT_EQ(client.GetStatus(), \"NOERROR\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetName(), \"a.com\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetTTL(), 3);\n\tEXPECT_EQ(client.GetAnswer()[0].GetType(), \"AAAA\");\n\tEXPECT_EQ(client.GetAnswer()[0].GetData(), \"2001:db8::2\");\n}\n"
  },
  {
    "path": "test/client.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"client.h\"\n#include <iostream>\n#include <memory>\n#include <regex>\n#include <signal.h>\n#include <string>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <vector>\n\nnamespace smartdns\n{\n\nstd::vector<std::string> StringSplit(const std::string &s, const char delim)\n{\n\tstd::vector<std::string> ret;\n\tstd::string::size_type lastPos = s.find_first_not_of(delim, 0);\n\tstd::string::size_type pos = s.find_first_of(delim, lastPos);\n\twhile (std::string::npos != pos || std::string::npos != lastPos) {\n\t\tret.push_back(s.substr(lastPos, pos - lastPos));\n\t\tlastPos = s.find_first_not_of(delim, pos);\n\t\tpos = s.find_first_of(delim, lastPos);\n\t}\n\n\treturn ret;\n}\n\nDNSRecord::DNSRecord() {}\n\nDNSRecord::~DNSRecord() {}\n\nbool DNSRecord::Parser(const std::string &line)\n{\n\tstd::vector<std::string> fields_first = StringSplit(line, '\\t');\n\tstd::vector<std::string> fields;\n\n\tfor (const auto &f : fields_first) {\n\t\tstd::vector<std::string> fields_second = StringSplit(f, ' ');\n\t\tfor (const auto &s : fields_second) {\n\t\t\tif (s.length() > 0) {\n\t\t\t\tfields.push_back(s);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (fields.size() < 3) {\n\t\tstd::cerr << \"Invalid DNS record: \" << line << \", size: \" << fields.size() << std::endl;\n\t\treturn false;\n\t}\n\n\tif (fields.size() == 3) {\n\t\tname_ = fields[0];\n\t\tif (name_.size() > 1) {\n\t\t\tname_.resize(name_.size() - 1);\n\t\t}\n\t\tclass_ = fields[1];\n\t\ttype_ = fields[2];\n\t\treturn true;\n\t}\n\n\tname_ = fields[0];\n\tif (name_.size() > 1) {\n\t\tname_.resize(name_.size() - 1);\n\t}\n\tttl_ = std::stoi(fields[1]);\n\tclass_ = fields[2];\n\ttype_ = fields[3];\n\tdata_ = fields[4];\n\n\tfor (int i = 5; i < fields.size(); i++) {\n\t\tdata_ += \" \" + fields[i];\n\t}\n\n\treturn true;\n}\n\nstd::string DNSRecord::GetName()\n{\n\treturn name_;\n}\n\nstd::string DNSRecord::GetType()\n{\n\treturn type_;\n}\n\nstd::string DNSRecord::GetClass()\n{\n\treturn class_;\n}\n\nint DNSRecord::GetTTL()\n{\n\treturn ttl_;\n}\n\nstd::string DNSRecord::GetData()\n{\n\treturn data_;\n}\n\nClient::Client() {}\n\nbool Client::Query(const std::string &dig_cmds, int port, const std::string &ip)\n{\n\tClear();\n\n\tstd::string cmd = \"dig \";\n\tif (port > 0) {\n\t\tcmd += \"-p \" + std::to_string(port);\n\t}\n\n\tif (ip.length() > 0) {\n\t\tcmd += \" @\" + ip;\n\t} else {\n\t\tcmd += \" @127.0.0.1\";\n\t}\n\n\tcmd += \" \" + dig_cmds;\n\tcmd += \" +tries=1\";\n\tFILE *fp = nullptr;\n\n\tfp = popen(cmd.c_str(), \"r\");\n\tif (fp == nullptr) {\n\t\treturn false;\n\t}\n\n\tstd::shared_ptr<FILE> pipe(fp, pclose);\n\tresult_.clear();\n\tchar buffer[4096];\n\tusleep(10000);\n\twhile (fgets(buffer, 4096, pipe.get())) {\n\t\tresult_ += buffer;\n\t}\n\n\tif (ParserResult() == false) {\n\t\tClear();\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstd::vector<DNSRecord> Client::GetQuery()\n{\n\treturn records_query_;\n}\n\nstd::vector<DNSRecord> Client::GetAnswer()\n{\n\treturn records_answer_;\n}\n\nstd::vector<DNSRecord> Client::GetAuthority()\n{\n\treturn records_authority_;\n}\n\nstd::vector<DNSRecord> Client::GetAdditional()\n{\n\treturn records_additional_;\n}\n\nstd::vector<std::string> Client::GetOpt()\n{\n\treturn records_opt_;\n}\n\nint Client::GetAnswerNum()\n{\n\treturn answer_num_;\n}\n\nint Client::GetAuthorityNum()\n{\n\treturn authority_num_;\n}\n\nstd::string Client::GetStatus()\n{\n\treturn status_;\n}\n\nstd::string Client::GetServer()\n{\n\treturn server_;\n}\n\nint Client::GetQueryTime()\n{\n\treturn query_time_;\n}\n\nint Client::GetMsgSize()\n{\n\treturn msg_size_;\n}\n\nstd::string Client::GetFlags()\n{\n\treturn flags_;\n}\n\nstd::string Client::GetResult()\n{\n\treturn result_;\n}\n\nvoid Client::Clear()\n{\n\tresult_.clear();\n\tanswer_num_ = 0;\n\tstatus_.clear();\n\tserver_.clear();\n\tquery_time_ = 0;\n\tmsg_size_ = 0;\n\tflags_.clear();\n\trecords_query_.clear();\n\trecords_answer_.clear();\n\trecords_authority_.clear();\n\trecords_additional_.clear();\n}\n\nvoid Client::PrintResult()\n{\n\tstd::cout << result_ << std::endl;\n}\n\nbool Client::ParserRecord(const std::string &record_str, std::vector<DNSRecord> &record)\n{\n\tDNSRecord r;\n\n\tstd::vector<std::string> lines = StringSplit(record_str, '\\n');\n\n\tfor (auto &line : lines) {\n\t\tif (r.Parser(line) == false) {\n\t\t\treturn false;\n\t\t}\n\n\t\trecord.push_back(r);\n\t}\n\n\treturn true;\n}\n\nbool Client::ParserResult()\n{\n\tstd::smatch match;\n\n\tstd::regex reg_goanswer(\";; Got answer:\");\n\tif (std::regex_search(result_, match, reg_goanswer) == false) {\n\t\tstd::cout << \"DIG FAILED:\\n\" << result_ << std::endl;\n\t\treturn false;\n\t}\n\n\tstd::regex reg_opt(\";; OPT PSEUDOSECTION:\\\\n((?:.|\\\\n|\\\\r\\\\n)+?)\\\\n;;\",\n\t\t\t\t\t\t\tstd::regex::ECMAScript | std::regex::optimize);\n\tif (std::regex_search(result_, match, reg_opt)) {\n\t\tstd::string opt_str = match[1];\n\n\t\tstd::vector<std::string> lines = StringSplit(opt_str, '\\n');\n\t\tfor (auto &line : lines) {\n\t\t\tif (line.length() <= 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tline = line.substr(2);\n\t\t\trecords_opt_.push_back(line);\n\t\t}\n\t}\n\n\tstd::regex reg_answer_num(\", ANSWER: ([0-9]+),\");\n\tif (std::regex_search(result_, match, reg_answer_num)) {\n\t\tanswer_num_ = std::stoi(match[1]);\n\t}\n\n\tstd::regex reg_authority_num(\", AUTHORITY: ([0-9]+),\");\n\tif (std::regex_search(result_, match, reg_authority_num)) {\n\t\tauthority_num_ = std::stoi(match[1]);\n\t}\n\n\tstd::regex reg_status(\", status: ([A-Z]+),\");\n\tif (std::regex_search(result_, match, reg_status)) {\n\t\tstatus_ = match[1];\n\t}\n\n\tstd::regex reg_server(\";; SERVER: ([0-9.]+)#\");\n\tif (std::regex_search(result_, match, reg_server)) {\n\t\tserver_ = match[1];\n\t}\n\n\tstd::regex reg_querytime(\";; Query time: ([0-9]+) msec\");\n\tif (std::regex_search(result_, match, reg_querytime)) {\n\t\tquery_time_ = std::stoi(match[1]);\n\t}\n\n\tstd::regex reg_msg_size(\";; MSG SIZE  rcvd: ([0-9]+)\");\n\tif (std::regex_search(result_, match, reg_msg_size)) {\n\t\tmsg_size_ = std::stoi(match[1]);\n\t}\n\n\tstd::regex reg_flags(\";; flags: ([a-z A-Z]+);\");\n\tif (std::regex_search(result_, match, reg_flags)) {\n\t\tflags_ = match[1];\n\t}\n\n\tstd::regex reg_question(\";; QUESTION SECTION:\\\\n((?:.|\\\\n|\\\\r\\\\n)+?)\\\\n{2,}\",\n\t\t\t\t\t\t\tstd::regex::ECMAScript | std::regex::optimize);\n\tif (std::regex_search(result_, match, reg_question)) {\n\t\tif (ParserRecord(match[1], records_query_) == false) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tstd::regex reg_answer(\";; ANSWER SECTION:\\\\n((?:.|\\\\n|\\\\r\\\\n)+?)\\\\n{2,}\",\n\t\t\t\t\t\t  std::regex::ECMAScript | std::regex::optimize);\n\tif (std::regex_search(result_, match, reg_answer)) {\n\t\tif (ParserRecord(match[1], records_answer_) == false) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (answer_num_ != records_answer_.size()) {\n\t\t\tstd::cout << \"DIG FAILED: Num Not Match\\n\" << result_ << std::endl;\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tstd::regex reg_authority(\";; AUTHORITY SECTION:\\\\n((?:.|\\\\n|\\\\r\\\\n)+?)\\\\n{2,}\",\n\t\t\t\t\t\t\t std::regex::ECMAScript | std::regex::optimize);\n\tif (std::regex_search(result_, match, reg_authority)) {\n\t\tif (ParserRecord(match[1], records_authority_) == false) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (authority_num_ != records_authority_.size()) {\n\t\t\tstd::cout << \"DIG FAILED: Num Not Match\\n\" << result_ << std::endl;\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tstd::regex reg_addition(\";; ADDITIONAL SECTION:\\\\n((?:.|\\\\n|\\\\r\\\\n)+?)\\\\n{2,}\",\n\t\t\t\t\t\t\tstd::regex::ECMAScript | std::regex::optimize);\n\tif (std::regex_search(result_, match, reg_answer)) {\n\t\tif (ParserRecord(match[1], records_additional_) == false) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nClient::~Client() {}\n\n} // namespace smartdns"
  },
  {
    "path": "test/client.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _SMARTDNS_CLIENT_\n#define _SMARTDNS_CLIENT_\n\n#include <string>\n#include <unistd.h>\n#include <vector>\n\nnamespace smartdns\n{\n\nclass DNSRecord\n{\n  public:\n\tDNSRecord();\n\tvirtual ~DNSRecord();\n\n\tbool Parser(const std::string &line);\n\n\tstd::string GetName();\n\n\tstd::string GetType();\n\n\tstd::string GetClass();\n\n\tint GetTTL();\n\n\tstd::string GetData();\n\n  private:\n\tstd::string name_;\n\tstd::string type_;\n\tstd::string class_;\n\tint ttl_;\n\tstd::string data_;\n};\n\nclass Client\n{\n  public:\n\tClient();\n\tvirtual ~Client();\n\tbool Query(const std::string &dig_cmds, int port = 0, const std::string &ip = \"\");\n\n\tstd::string GetResult();\n\n\tstd::vector<DNSRecord> GetQuery();\n\n\tstd::vector<DNSRecord> GetAnswer();\n\n\tstd::vector<DNSRecord> GetAuthority();\n\n\tstd::vector<DNSRecord> GetAdditional();\n\n\tstd::vector<std::string> GetOpt();\n\n\tint GetAnswerNum();\n\n\tint GetAuthorityNum();\n\n\tstd::string GetStatus();\n\n\tstd::string GetServer();\n\n\tint GetQueryTime();\n\n\tint GetMsgSize();\n\n\tstd::string GetFlags();\n\n\tvoid Clear();\n\n\tvoid PrintResult();\n\n  private:\n\tbool ParserResult();\n\tbool ParserRecord(const std::string &record_str, std::vector<DNSRecord> &record);\n\tstd::string result_;\n\tint answer_num_{0};\n\tint authority_num_{0};\n\tstd::string status_;\n\tstd::string server_;\n\tint query_time_{0};\n\tint msg_size_{0};\n\tstd::string flags_;\n\n\tstd::vector<DNSRecord> records_query_;\n\tstd::vector<DNSRecord> records_answer_;\n\tstd::vector<DNSRecord> records_authority_;\n\tstd::vector<DNSRecord> records_additional_;\n\tstd::vector<std::string> records_opt_;\n};\n\n} // namespace smartdns\n#endif // _SMARTDNS_CLIENT_"
  },
  {
    "path": "test/include/utils.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _SMARTDNS_TEST_UTILS_\n#define _SMARTDNS_TEST_UTILS_\n\n#include <fstream>\n#include <functional>\n#include <string>\n#include <vector>\n\nnamespace smartdns\n{\n\nclass DeferGuard\n{\n  public:\n\ttemplate <class Callable>\n\n\tDeferGuard(Callable &&fn) noexcept : fn_(std::forward<Callable>(fn))\n\t{\n\t}\n\tDeferGuard(DeferGuard &&other) noexcept\n\t{\n\t\tfn_ = std::move(other.fn_);\n\t\tother.fn_ = nullptr;\n\t}\n\n\tvirtual ~DeferGuard()\n\t{\n\t\tif (fn_) {\n\t\t\tfn_();\n\t\t}\n\t};\n\tDeferGuard(const DeferGuard &) = delete;\n\tvoid operator=(const DeferGuard &) = delete;\n\n  private:\n\tstd::function<void()> fn_;\n};\n\n#define SMARTDNS_CONCAT_(a, b) a##b\n#define SMARTDNS_CONCAT(a, b) SMARTDNS_CONCAT_(a, b)\n#define Defer ::smartdns::DeferGuard SMARTDNS_CONCAT(__defer__, __LINE__) = [&]()\n\nclass TempFile\n{\n  public:\n\tTempFile();\n\tTempFile(const std::string &line);\n\tvirtual ~TempFile();\n\n\tbool Write(const std::string &line);\n\n\tstd::string GetPath();\n\n\tvoid SetPattern(const std::string &pattern);\n\n\tvoid Close();\n\n  private:\n\tbool NewTempFile();\n\tstd::string path_;\n\tstd::ofstream ofs_;\n\tstd::string pattern_;\n};\n\nclass Commander\n{\n  public:\n\tCommander();\n\tvirtual ~Commander();\n\n\tbool Run(const std::vector<std::string> &cmds);\n\n\tbool Run(const std::string &cmd);\n\n\tvoid Kill();\n\n\tvoid Terminate();\n\n\tint ExitCode();\n\n\tint GetPid();\n\n  private:\n\tpid_t pid_{-1};\n\tint exit_code_ = {-1};\n};\n\nbool IsCommandExists(const std::string &cmd);\n\nstd::string GenerateRandomString(int len);\n\nint ParserArg(const std::string &cmd, std::vector<std::string> &args);\n\nstd::vector<std::string> GetAvailableIPAddresses();\n\nbool IsICMPAvailable();\n\n} // namespace smartdns\n#endif // _SMARTDNS_TEST_UTILS_\n"
  },
  {
    "path": "test/server.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"server.h\"\n#include \"smartdns/dns_server.h\"\n#include \"smartdns/fast_ping.h\"\n#include \"include/utils.h\"\n#include \"smartdns/smartdns.h\"\n#include \"smartdns/util.h\"\n#include <arpa/inet.h>\n#include <fcntl.h>\n#include <fstream>\n#include <netinet/in.h>\n#include <poll.h>\n#include <signal.h>\n#include <string>\n#include <sys/socket.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n#include <vector>\n\nextern int dns_ping_cap_force_enable;\n\nnamespace smartdns\n{\n\nMockServer::MockServer() {}\n\nMockServer::~MockServer()\n{\n\tStop();\n}\n\nbool MockServer::IsRunning()\n{\n\tif (fd_ > 0) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid MockServer::Stop()\n{\n\tif (run_ == true) {\n\t\trun_ = false;\n\t\tif (thread_.joinable()) {\n\t\t\tthread_.join();\n\t\t}\n\t}\n\n\tif (fd_ > 0) {\n\t\tclose(fd_);\n\t\tfd_ = -1;\n\t}\n}\n\nvoid MockServer::Run()\n{\n\twhile (run_ == true) {\n\t\tstruct pollfd fds[1];\n\t\tfds[0].fd = fd_;\n\t\tfds[0].events = POLLIN;\n\t\tfds[0].revents = 0;\n\t\tint ret = poll(fds, 1, 100);\n\t\tif (ret == 0) {\n\t\t\tcontinue;\n\t\t} else if (ret < 0) {\n\t\t\tsleep(1);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (fds[0].revents & POLLIN) {\n\t\t\tstruct sockaddr_storage from;\n\t\t\tsocklen_t addrlen = sizeof(from);\n\t\t\tunsigned char in_buff[4096];\n\t\t\tint query_id = 0;\n\t\t\tint len = recvfrom(fd_, in_buff, sizeof(in_buff), 0, (struct sockaddr *)&from, &addrlen);\n\t\t\tif (len < 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tchar packet_buff[4096];\n\t\t\tunsigned char response_data_buff[4096];\n\t\t\tunsigned char response_packet_buff[4096];\n\t\t\tmemset(packet_buff, 0, sizeof(packet_buff));\n\t\t\tstruct dns_packet *packet = (struct dns_packet *)packet_buff;\n\t\t\tstruct ServerRequestContext request;\n\t\t\tmemset(&request, 0, sizeof(request));\n\n\t\t\tret = dns_decode(packet, sizeof(packet_buff), in_buff, len);\n\t\t\tif (ret == 0) {\n\t\t\t\trequest.packet = packet;\n\t\t\t\tquery_id = packet->head.id;\n\t\t\t\tif (packet->head.qr == DNS_QR_QUERY) {\n\t\t\t\t\tstruct dns_rrs *rrs = nullptr;\n\t\t\t\t\tint rr_count = 0;\n\t\t\t\t\tint qtype = 0;\n\t\t\t\t\tint qclass = 0;\n\t\t\t\t\tchar domain[256];\n\n\t\t\t\t\trrs = dns_get_rrs_start(packet, DNS_RRS_QD, &rr_count);\n\t\t\t\t\tfor (int i = 0; i < rr_count && rrs; i++, rrs = dns_get_rrs_next(packet, rrs)) {\n\t\t\t\t\t\tret = dns_get_domain(rrs, domain, sizeof(domain), &qtype, &qclass);\n\t\t\t\t\t\tif (ret == 0) {\n\t\t\t\t\t\t\trequest.domain = domain;\n\t\t\t\t\t\t\trequest.qtype = (dns_type)qtype;\n\t\t\t\t\t\t\trequest.qclass = qclass;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trequest.from = (struct sockaddr_storage *)&from;\n\t\t\trequest.fromlen = addrlen;\n\t\t\trequest.request_data = in_buff;\n\t\t\trequest.request_data_len = len;\n\t\t\trequest.response_packet = (struct dns_packet *)response_packet_buff;\n\t\t\trequest.response_data = response_data_buff;\n\t\t\trequest.response_data_len = 0;\n\t\t\trequest.response_data_max_len = sizeof(response_data_buff);\n\n\t\t\tstruct dns_head head;\n\t\t\tmemset(&head, 0, sizeof(head));\n\t\t\thead.id = query_id;\n\t\t\thead.qr = DNS_QR_ANSWER;\n\t\t\thead.opcode = DNS_OP_QUERY;\n\t\t\thead.aa = 0;\n\t\t\thead.rd = 0;\n\t\t\thead.ra = 1;\n\t\t\thead.rcode = DNS_RC_NOERROR;\n\t\t\tdns_packet_init(request.response_packet, sizeof(response_packet_buff), &head);\n\n\t\t\tauto callback_ret = callback_(&request);\n\t\t\tif (callback_ret == SERVER_REQUEST_ERROR) {\n\t\t\t\tdns_packet_init(request.response_packet, sizeof(response_packet_buff), &head);\n\t\t\t\trequest.response_packet->head.rcode = DNS_RC_SERVFAIL;\n\t\t\t\tdns_add_domain(request.response_packet, request.domain.c_str(), request.qtype, request.qclass);\n\t\t\t\trequest.response_data_len =\n\t\t\t\t\tdns_encode(request.response_data, request.response_data_max_len, request.response_packet);\n\t\t\t} else if (callback_ret == SERVER_REQUEST_NO_RESPONSE) {\n\t\t\t\tcontinue;\n\t\t\t} else if (request.response_data_len == 0) {\n\t\t\t\tif (callback_ret == SERVER_REQUEST_OK) {\n\t\t\t\t\trequest.response_data_len =\n\t\t\t\t\t\tdns_encode(request.response_data, request.response_data_max_len, request.response_packet);\n\t\t\t\t} else if (callback_ret == SERVER_REQUEST_SOA) {\n\t\t\t\t\tstruct dns_soa soa;\n\t\t\t\t\tmemset(&soa, 0, sizeof(soa));\n\t\t\t\t\tstrncpy(soa.mname, \"ns1.example.com\", sizeof(soa.mname));\n\t\t\t\t\tstrncpy(soa.rname, \"hostmaster.example.com\", sizeof(soa.rname));\n\t\t\t\t\tsoa.serial = 1;\n\t\t\t\t\tsoa.refresh = 3600;\n\t\t\t\t\tsoa.retry = 600;\n\t\t\t\t\tsoa.expire = 86400;\n\t\t\t\t\tsoa.minimum = 3600;\n\t\t\t\t\tdns_packet_init(request.response_packet, sizeof(response_packet_buff), &head);\n\t\t\t\t\tdns_add_domain(request.response_packet, request.domain.c_str(), request.qtype, request.qclass);\n\t\t\t\t\trequest.response_packet->head.rcode = DNS_RC_NXDOMAIN;\n\t\t\t\t\tdns_add_SOA(request.response_packet, DNS_RRS_AN, request.domain.c_str(), 1, &soa);\n\t\t\t\t\trequest.response_data_len =\n\t\t\t\t\t\tdns_encode(request.response_data, request.response_data_max_len, request.response_packet);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsendto(fd_, request.response_data, request.response_data_len, MSG_NOSIGNAL, (struct sockaddr *)&from,\n\t\t\t\t   addrlen);\n\t\t}\n\t}\n}\n\nbool MockServer::AddIP(struct ServerRequestContext *request, const std::string &domain, const std::string &ip, int ttl)\n{\n\tstruct sockaddr_storage addr;\n\tsocklen_t addrlen = sizeof(addr);\n\tmemset(&addr, 0, sizeof(addr));\n\n\tif (GetAddr(ip, \"53\", SOCK_DGRAM, IPPROTO_UDP, &addr, &addrlen)) {\n\t\tif (addr.ss_family == AF_INET) {\n\t\t\tstruct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;\n\t\t\tdns_add_A(request->response_packet, DNS_RRS_AN, domain.c_str(), ttl,\n\t\t\t\t\t  (unsigned char *)&addr4->sin_addr.s_addr);\n\t\t} else if (addr.ss_family == AF_INET6) {\n\t\t\tstruct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;\n\t\t\tdns_add_AAAA(request->response_packet, DNS_RRS_AN, domain.c_str(), ttl,\n\t\t\t\t\t\t (unsigned char *)&addr6->sin6_addr.s6_addr);\n\t\t}\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nbool MockServer::GetAddr(const std::string &host, const std::string port, int type, int protocol,\n\t\t\t\t\t\t struct sockaddr_storage *addr, socklen_t *addrlen)\n\n{\n\tstruct addrinfo hints;\n\tstruct addrinfo *result = nullptr;\n\n\tmemset(&hints, 0, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = type;\n\thints.ai_protocol = protocol;\n\thints.ai_flags = AI_PASSIVE;\n\tif (getaddrinfo(host.c_str(), port.c_str(), &hints, &result) != 0) {\n\t\tgoto errout;\n\t}\n\n\tmemcpy(addr, result->ai_addr, result->ai_addrlen);\n\t*addrlen = result->ai_addrlen;\n\tfreeaddrinfo(result);\n\treturn true;\nerrout:\n\tif (result) {\n\t\tfreeaddrinfo(result);\n\t}\n\n\treturn false;\n}\n\nbool MockServer::Start(const std::string &url, ServerRequest callback)\n{\n\tchar c_scheme[256];\n\tchar c_host[256];\n\tint port;\n\tchar c_path[256];\n\tint fd;\n\tint yes = 1;\n\tstruct sockaddr_storage addr;\n\tsocklen_t addrlen;\n\n\tif (callback == nullptr) {\n\t\treturn false;\n\t}\n\n\tif (parse_uri(url.c_str(), c_scheme, c_host, &port, c_path) != 0) {\n\t\treturn false;\n\t}\n\n\tstd::string scheme(c_scheme);\n\tstd::string host(c_host);\n\tstd::string path(c_path);\n\n\tif (scheme != \"udp\") {\n\t\treturn false;\n\t}\n\n\tif (GetAddr(host, std::to_string(port), SOCK_DGRAM, IPPROTO_UDP, &addr, &addrlen) == false) {\n\t\treturn false;\n\t}\n\n\tfd = socket(addr.ss_family, SOCK_DGRAM | SOCK_CLOEXEC, 0);\n\tif (fd < 0) {\n\t\treturn false;\n\t}\n\n\tsetsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));\n\tsetsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));\n\n\tif (bind(fd, (struct sockaddr *)&addr, addrlen) != 0) {\n\t\tclose(fd);\n\t\treturn false;\n\t}\n\n\trun_ = true;\n\tthread_ = std::thread(&MockServer::Run, this);\n\tfd_ = fd;\n\tcallback_ = callback;\n\treturn true;\n}\n\nServer::Server()\n{\n\tmode_ = Server::CREATE_MODE_FORK;\n}\n\nServer::Server(enum Server::CREATE_MODE mode)\n{\n\tmode_ = mode;\n}\n\nvoid Server::MockPing(PING_TYPE type, const std::string &host, int ttl, float time)\n{\n\tstruct MockPingIP ping_ip;\n\tping_ip.type = type;\n\tping_ip.host = host;\n\tping_ip.ttl = ttl;\n\tping_ip.time = time;\n\tmock_ping_ips_.push_back(ping_ip);\n}\n\nvoid Server::StartPost(void *arg)\n{\n\tServer *server = (Server *)arg;\n\tbool has_ipv6 = false;\n\tfor (auto &it : server->mock_ping_ips_) {\n\t\tif (has_ipv6 == false && check_is_ipv6(it.host.c_str()) == 0) {\n\t\t\thas_ipv6 = true;\n\t\t}\n\n\t\tfast_ping_fake_ip_add(it.type, it.host.c_str(), it.ttl, it.time);\n\t}\n\n\tif (has_ipv6 == true) {\n\t\tfast_ping_fake_ip_add(PING_TYPE_ICMP, \"2001::\", 64, 10);\n\t\tfast_ping_fake_ip_add(PING_TYPE_TCP, \"[2001::]:80\", 64, 10);\n\t\tfast_ping_fake_ip_add(PING_TYPE_TCP, \"[2001::]:443\", 64, 10);\n\t\tdns_server_check_ipv6_ready();\n\t}\n}\n\nbool Server::Start(const std::string &conf, enum CONF_TYPE type)\n{\n\tpid_t pid = 0;\n\tint fds[2];\n\tstd::string conf_file;\n\n\tfds[0] = 0;\n\tfds[1] = 0;\n\tDefer\n\t{\n\t\tif (fds[0] > 0) {\n\t\t\tclose(fds[0]);\n\t\t}\n\n\t\tif (fds[1] > 0) {\n\t\t\tclose(fds[1]);\n\t\t}\n\t};\n\n\tconst char *default_conf = R\"\"\"(\nlog-num 0\nlog-console yes\nlog-level debug\ncache-persist no\n)\"\"\";\n\n\tif (type == CONF_TYPE_STRING) {\n\t\tconf_temp_file_.SetPattern(\"/tmp/smartdns_conf.XXXXXX\");\n\t\tconf_temp_file_.Write(default_conf);\n\t\tconf_temp_file_.Write(conf);\n\t\tconf_file = conf_temp_file_.GetPath();\n\t} else if (type == CONF_TYPE_FILE) {\n\t\tconf_file = conf;\n\t} else {\n\t\treturn false;\n\t}\n\n\tif (access(conf_file.c_str(), F_OK) != 0) {\n\t\treturn false;\n\t}\n\n\tconf_file_ = conf_file;\n\n\tif (pipe2(fds, O_CLOEXEC | O_NONBLOCK) != 0) {\n\t\treturn false;\n\t}\n\n\tif (mode_ == CREATE_MODE_FORK) {\n\t\tpid = fork();\n\t\tif (pid == 0) {\n\t\t\tstd::vector<std::string> args = {\n\t\t\t\t\"smartdns\", \"-f\", \"-x\", \"-c\", conf_file, \"-p\", \"-\",\n\t\t\t};\n\t\t\tchar *argv[args.size() + 1];\n\t\t\tfor (size_t i = 0; i < args.size(); i++) {\n\t\t\t\targv[i] = (char *)args[i].c_str();\n\t\t\t}\n\n\t\t\tsmartdns_reg_post_func(Server::StartPost, this);\n\t\t\tdns_ping_cap_force_enable = 1;\n\t\t\tsmartdns_test_main(args.size(), argv, fds[1], 0);\n\t\t\t_exit(1);\n\t\t} else if (pid < 0) {\n\t\t\treturn false;\n\t\t}\n\t} else if (mode_ == CREATE_MODE_THREAD) {\n\t\tthread_ = std::thread([&]() {\n\t\t\tstd::vector<std::string> args = {\"smartdns\", \"-f\", \"-x\", \"-c\", conf_file_, \"-p\", \"-\", \"-S\"};\n\t\t\tchar *argv[args.size() + 1];\n\t\t\tfor (size_t i = 0; i < args.size(); i++) {\n\t\t\t\targv[i] = (char *)args[i].c_str();\n\t\t\t}\n\n\t\t\tsmartdns_reg_post_func(Server::StartPost, this);\n\t\t\tdns_ping_cap_force_enable = 1;\n\t\t\tsmartdns_test_main(args.size(), argv, fds[1], 1);\n\t\t\tsmartdns_reg_post_func(nullptr, nullptr);\n\t\t});\n\t} else {\n\t\treturn false;\n\t}\n\n\tstruct pollfd pfd[1];\n\tpfd[0].fd = fds[0];\n\tpfd[0].events = POLLIN;\n\n\tint ret = poll(pfd, 1, 10000);\n\tif (ret == 0) {\n\t\tif (thread_.joinable()) {\n\t\t\tthread_.join();\n\t\t}\n\n\t\tif (pid > 0) {\n\t\t\tkill(pid, SIGKILL);\n\t\t}\n\t\treturn false;\n\t}\n\n\tpid_ = pid;\n\treturn pid > 0;\n}\n\nvoid Server::Stop(bool graceful)\n{\n\tif (thread_.joinable()) {\n\t\tdns_server_stop();\n\t\tthread_.join();\n\t}\n\n\tif (pid_ > 0) {\n\t\tif (graceful) {\n\t\t\tkill(pid_, SIGTERM);\n\t\t} else {\n\t\t\tkill(pid_, SIGKILL);\n\t\t}\n\t}\n\n\tif (pid_ > 0) {\n\t\twaitpid(pid_, nullptr, 0);\n\t}\n\n\tconf_temp_file_.Close();\n\n\tpid_ = 0;\n}\n\nbool Server::IsRunning()\n{\n\tif (pid_ <= 0) {\n\t\treturn false;\n\t}\n\n\tif (waitpid(pid_, nullptr, WNOHANG) == 0) {\n\t\treturn true;\n\t}\n\n\treturn kill(pid_, 0) == 0;\n}\n\nServer::~Server()\n{\n\tStop(false);\n}\n\n} // namespace smartdns"
  },
  {
    "path": "test/server.h",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef _SMARTDNS_SERVER_\n#define _SMARTDNS_SERVER_\n\n#include \"smartdns/dns.h\"\n#include \"smartdns/fast_ping.h\"\n#include \"include/utils.h\"\n#include <functional>\n#include <string>\n#include <sys/socket.h>\n#include <thread>\n#include <unistd.h>\n#include <vector>\n\nnamespace smartdns\n{\n\nclass Server\n{\n  public:\n\tstruct MockPingIP {\n\t\tPING_TYPE type;\n\t\tstd::string host;\n\t\tint ttl;\n\t\tfloat time;\n\t};\n\tenum CONF_TYPE {\n\t\tCONF_TYPE_STRING,\n\t\tCONF_TYPE_FILE,\n\t};\n\tenum CREATE_MODE {\n\t\tCREATE_MODE_FORK,\n\t\tCREATE_MODE_THREAD,\n\t};\n\tServer();\n\tServer(enum CREATE_MODE mode);\n\tvirtual ~Server();\n\n\tvoid MockPing(PING_TYPE type, const std::string &host, int ttl, float time);\n\tbool Start(const std::string &conf, enum CONF_TYPE type = CONF_TYPE_STRING);\n\tvoid Stop(bool graceful = true);\n\tbool IsRunning();\n\n  private:\n\tstatic void StartPost(void *arg);\n\tpid_t pid_;\n\tstd::thread thread_;\n\tint fd_;\n\tstd::string conf_file_;\n\tTempFile conf_temp_file_;\n\tstd::vector<MockPingIP> mock_ping_ips_;\n\tenum CREATE_MODE mode_;\n};\n\nstruct ServerRequestContext {\n\tstd::string domain;\n\tdns_type qtype;\n\tint qclass;\n\tstruct sockaddr_storage *from;\n\tsocklen_t fromlen;\n\tstruct dns_packet *packet;\n\tuint8_t *request_data;\n\tint request_data_len;\n\tuint8_t *response_data;\n\tstruct dns_packet *response_packet;\n\tint response_data_max_len;\n\tint response_data_len;\n};\n\ntypedef enum {\n\tSERVER_REQUEST_OK = 0,\n\tSERVER_REQUEST_ERROR,\n\tSERVER_REQUEST_NO_RESPONSE,\n\tSERVER_REQUEST_SOA,\n} ServerRequestResult;\n\nusing ServerRequest = std::function<ServerRequestResult(struct ServerRequestContext *request)>;\n\nclass MockServer\n{\n  public:\n\tMockServer();\n\tvirtual ~MockServer();\n\n\tbool Start(const std::string &url, ServerRequest callback);\n\tvoid Stop();\n\tbool IsRunning();\n\n\tstatic bool AddIP(struct ServerRequestContext *request, const std::string &domain, const std::string &ip,\n\t\t\t\t\t  int ttl = 60);\n\n  private:\n\tvoid Run();\n\n\tstatic bool GetAddr(const std::string &host, const std::string port, int type, int protocol,\n\t\t\t\t\t\tstruct sockaddr_storage *addr, socklen_t *addrlen);\n\tint fd_{0};\n\tstd::thread thread_;\n\tbool run_{false};\n\tServerRequest callback_{nullptr};\n};\n\n} // namespace smartdns\n#endif // _SMARTDNS_SERVER_"
  },
  {
    "path": "test/test.cc",
    "content": "/*************************************************************************\n *\n * Copyright (C) 2018-2025 Ruilin Peng (Nick) <pymumu@gmail.com>.\n *\n * smartdns is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * smartdns is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"gtest/gtest.h\"\n\nint main(int argc, char **argv)\n{\n\tif (WEXITSTATUS(system(\"which dig >/dev/null 2>&1\")) != 0) {\n\t\tstd::cerr << \"dig not found, please install it first.\" << std::endl;\n\t\treturn 1;\n\t}\n\n\t::testing::InitGoogleTest(&argc, argv);\n\treturn RUN_ALL_TESTS();\n}"
  },
  {
    "path": "test/utils.cc",
    "content": "#include \"include/utils.h\"\n#include \"smartdns/util.h\"\n#include <arpa/inet.h>\n#include <ifaddrs.h>\n#include <netinet/in.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/socket.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n\nnamespace smartdns\n{\n\nTempFile::TempFile()\n{\n\tpattern_ = \"/tmp/smartdns-test-tmp.XXXXXX\";\n}\n\nTempFile::TempFile(const std::string &line)\n{\n\tpattern_ = \"/tmp/smartdns-test-tmp.XXXXXX\";\n}\n\nTempFile::~TempFile()\n{\n\tif (ofs_.is_open()) {\n\t\tofs_.close();\n\t\tofs_.clear();\n\t}\n\n\tif (path_.length() > 0) {\n\t\tunlink(path_.c_str());\n\t}\n}\n\nvoid TempFile::Close()\n{\n\tif (ofs_.is_open()) {\n\t\tofs_.close();\n\t\tofs_.clear();\n\t}\n\n\tif (path_.length() > 0) {\n\t\tunlink(path_.c_str());\n\t\tpath_.clear();\n\t}\n}\n\nvoid TempFile::SetPattern(const std::string &pattern)\n{\n\tpattern_ = pattern;\n}\n\nbool TempFile::Write(const std::string &line)\n{\n\tif (ofs_.is_open() == false) {\n\t\tif (NewTempFile() == false) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tofs_.write(line.data(), line.size());\n\tif (ofs_.fail()) {\n\t\treturn false;\n\t}\n\tofs_.flush();\n\n\treturn true;\n}\n\nbool TempFile::NewTempFile()\n{\n\tchar filename[128];\n\tstrncpy(filename, \"/tmp/smartdns-test-tmp.XXXXXX\", sizeof(filename));\n\tint fd = mkstemp(filename);\n\tif (fd < 0) {\n\t\treturn false;\n\t}\n\tDefer\n\t{\n\t\tclose(fd);\n\t};\n\n\tstd::ofstream ofs(filename);\n\tif (ofs.is_open() == false) {\n\t\treturn false;\n\t}\n\tofs_ = std::move(ofs);\n\tpath_ = filename;\n\n\treturn true;\n}\n\nstd::string TempFile::GetPath()\n{\n\tif (ofs_.is_open() == false) {\n\t\tif (NewTempFile() == false) {\n\t\t\treturn \"\";\n\t\t}\n\t}\n\n\treturn path_;\n}\n\nCommander::Commander() {}\nCommander::~Commander()\n{\n\tKill();\n}\n\nbool Commander::Run(const std::string &cmd)\n{\n\tstd::vector<std::string> args;\n\tif (ParserArg(cmd, args) != 0) {\n\t\treturn false;\n\t}\n\n\treturn Run(args);\n}\n\nbool Commander::Run(const std::vector<std::string> &cmds)\n{\n\tpid_t pid;\n\n\tif (pid_ > 0) {\n\t\treturn false;\n\t}\n\n\tpid = fork();\n\tif (pid < 0) {\n\t\treturn false;\n\t}\n\n\tif (pid == 0) {\n\t\tchar *argv[cmds.size() + 1];\n\t\tfor (int i = 0; i < cmds.size(); i++) {\n\t\t\targv[i] = (char *)cmds[i].c_str();\n\t\t}\n\t\targv[cmds.size()] = nullptr;\n\t\texecvp(argv[0], argv);\n\t\t_exit(1);\n\t}\n\n\tpid_ = pid;\n\n\treturn true;\n}\n\nvoid Commander::Kill()\n{\n\tif (pid_ <= 0) {\n\t\treturn;\n\t}\n\n\tkill(pid_, SIGKILL);\n}\n\nvoid Commander::Terminate()\n{\n\tif (pid_ <= 0) {\n\t\treturn;\n\t}\n\n\tkill(pid_, SIGTERM);\n}\n\nint Commander::ExitCode()\n{\n\tint wstatus = 0;\n\tif (exit_code_ >= 0) {\n\t\treturn exit_code_;\n\t}\n\n\tif (pid_ <= 0) {\n\t\treturn -1;\n\t}\n\n\tif (waitpid(pid_, &wstatus, 0) == -1) {\n\t\treturn -1;\n\t}\n\n\texit_code_ = WEXITSTATUS(wstatus);\n\n\treturn exit_code_;\n}\n\nint Commander::GetPid()\n{\n\treturn pid_;\n}\n\nbool IsCommandExists(const std::string &cmd)\n{\n\tchar *copy_path = nullptr;\n\tchar cmd_path[4096];\n\tconst char *env_path = getenv(\"PATH\");\n\tchar *save_ptr = nullptr;\n\n\tif (env_path == nullptr) {\n\t\tenv_path = \"/bin:/usr/bin:/usr/local/bin\";\n\t}\n\n\tcopy_path = strdup(env_path);\n\tif (copy_path == nullptr) {\n\t\treturn false;\n\t}\n\n\tDefer\n\t{\n\t\tfree(copy_path);\n\t};\n\n\tfor (char *tok = strtok_r(copy_path, \":\", &save_ptr); tok; tok = strtok_r(nullptr, \":\", &save_ptr)) {\n\t\tsnprintf(cmd_path, sizeof(cmd_path), \"%s/%s\", tok, cmd.c_str());\n\t\tif (access(cmd_path, X_OK) != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstd::string GenerateRandomString(int len)\n{\n\tstd::string result;\n\tstatic const char alphanum[] = \"0123456789\"\n\t\t\t\t\t\t\t\t   \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\t\t\t\t\t\t\t\t   \"abcdefghijklmnopqrstuvwxyz\";\n\tresult.resize(len);\n\n\tfor (int i = 0; i < len; ++i) {\n\t\tresult[i] = alphanum[rand() % (sizeof(alphanum) - 1)];\n\t}\n\n\treturn result;\n}\n\nint ParserArg(const std::string &cmd, std::vector<std::string> &args)\n{\n\tstd::string arg;\n\tchar quoteChar = 0;\n\n\tfor (char ch : cmd) {\n\t\tif (quoteChar == '\\\\') {\n\t\t\targ.push_back(ch);\n\t\t\tquoteChar = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (quoteChar && ch != quoteChar) {\n\t\t\targ.push_back(ch);\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (ch) {\n\t\tcase '\\'':\n\t\tcase '\\\"':\n\t\tcase '\\\\':\n\t\t\tquoteChar = quoteChar ? 0 : ch;\n\t\t\tbreak;\n\t\tcase ' ':\n\t\tcase '\\t':\n\t\tcase '\\n':\n\t\t\tif (!arg.empty()) {\n\t\t\t\targs.push_back(arg);\n\t\t\t\targ.clear();\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\targ.push_back(ch);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!arg.empty()) {\n\t\targs.push_back(arg);\n\t}\n\n\treturn 0;\n}\n\nstd::vector<std::string> GetAvailableIPAddresses()\n{\n\tstd::vector<std::string> ipAddresses;\n\n\tstruct ifaddrs *ifAddrStruct = nullptr;\n\tstruct ifaddrs *ifa = nullptr;\n\tvoid *tmpAddrPtr = nullptr;\n\n\tgetifaddrs(&ifAddrStruct);\n\n\tfor (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next) {\n\t\tif (!ifa->ifa_addr) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (ifa->ifa_addr->sa_family == AF_INET) { // IPv4 address\n\t\t\ttmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;\n\t\t\tchar addressBuffer[INET_ADDRSTRLEN];\n\t\t\tinet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);\n\t\t\tstd::string ipAddress(addressBuffer);\n\n\t\t\tif (!ipAddress.empty() && ipAddress.substr(0, 4) != \"127.\") {\n\t\t\t\tipAddresses.push_back(ipAddress);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (ifAddrStruct != nullptr) {\n\t\tfreeifaddrs(ifAddrStruct);\n\t}\n\n\treturn ipAddresses;\n}\n\nbool IsICMPAvailable()\n{\n\tint fd = -1;\n\tif (has_unprivileged_ping()) {\n\t\tfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);\n\t} else {\n\t\tfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);\n\t}\n\n\tif (fd < 0) {\n\t\treturn false;\n\t}\n\n\tclose(fd);\n\n\treturn true;\n}\n\n} // namespace smartdns"
  }
]