Repository: ValdikSS/GoodbyeDPI Branch: master Commit: f593a276f9ec Files: 28 Total size: 224.8 KB Directory structure: gitextract_5oql52xk/ ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── config.yml │ │ └── feature.yml │ └── workflows/ │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md └── src/ ├── Makefile ├── blackwhitelist.c ├── blackwhitelist.h ├── dnsredir.c ├── dnsredir.h ├── fakepackets.c ├── fakepackets.h ├── goodbyedpi-rc.rc ├── goodbyedpi.c ├── goodbyedpi.exe.manifest ├── goodbyedpi.h ├── service.c ├── service.h ├── ttltrack.c ├── ttltrack.h └── utils/ ├── getline.c ├── getline.h ├── repl_str.c ├── repl_str.h └── uthash.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 4 insert_final_newline = true end_of_line = lf ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: The program crashes, hangs, certain function does not work / Программа падает, зависает, отдельная функция не работает description: File a bug report / Сообщить об ошибке в программе body: - type: markdown attributes: value: | ### Carefully read all the text IN FULL. Take this seriously. ### Use this form only for software bug reports! The website does not open? That's likely NOT a bug, do NOT report it here! You have a question regarding the program? That's not a bug either! #### If in doubt, [use NTC.party forum](https://ntc.party/c/community-software/goodbyedpi). ### Внимательно прочитайте ВЕСЬ текст ниже. Отнеситесь к этому со всей ответственностью. ### Используйте эту форму только для сообщений об ошибках в программе! Неоткрывающиеся сайты таковыми не являются, вопросы по программе к ошибкам не относятся. #### Если у вас есть сомнения, [воспользуйтесь форумом NTC.party](https://ntc.party/c/community-software/goodbyedpi). GoodbyeDPI does not guarantee to work with your ISP for every blocked website or at all. If GoodbyeDPI can't unblock some or any websites, this is most likely not a software bug, and you should not report it here. Please only report software bugs, such as: * program crash * incorrect network packet handling * antivirus incompatibility * DNS redirection problems * memory leaks * other software issues Please make sure to check other opened and closed issues, it could be your bug has been reported already. For questions, or if in doubt, [use NTC.party forum](https://ntc.party/c/community-software/goodbyedpi). ### ИСПОЛЬЗУЙТЕ ЭТУ ФОРМУ ТОЛЬКО ДЛЯ БАГОВ! Веб-сайт не открывается? Это, скорее всего, не баг, не сообщайте сюда! GoodbyeDPI не гарантирует ни 100% работу с вашим провайдером, ни работу с каждым заблокированным сайтом. Если GoodbyeDPI не разблокирует доступ к некоторым или всем веб-сайтам, вероятнее всего, это не программная ошибка, и не стоит о ней сообщать здесь. Пожалуйста, сообщайте только об ошибках в программе, таких как: * падение программы * некорректная обработка сетевых пакетов * несовместимость с антивирусами * проблемы с перенаправлением DNS * утечки памяти * другие ошибки в программе Также посмотрите другие открытые и закрытые баги. Возможно, ошибка уже обсуждалась или исправлена. Для вопросов, а также в случае сомнений в определении бага, обращайтесь [на форум NTC.party](https://ntc.party/c/community-software/goodbyedpi). - type: checkboxes id: terms attributes: label: CAPTCHA description: Confirm that you have read the text above / Подтвердите, что вы прочитали текст выше options: - label: I understand I could be banned from the repository if I misusing issue section not for posting bugs, but for question or 'broken website' report. / Я понимаю, что меня могут заблокировать в репозитории, если я буду использовать раздел issue не для сообщений об ошибках, а для вопросов или сообщении о «неработающем веб-сайте». required: true validations: required: true - type: input id: os attributes: label: Operating system / операционная система description: Enter your Windows version. For Windows 10 and 11 include build/update number. placeholder: ex. Windows 10 20H2 validations: required: true - type: dropdown id: is-svc attributes: label: Running as service / Запуск программы как сервис description: Do you use GoodbyeDPI as a Windows Service? options: - I run it as a regular program / Запускаю программу обычным образом - I installed it as a service / Установил как сервис Windows validations: required: true - type: textarea id: what-happened attributes: label: Describe the bug / Опишите ошибку программы description: A clear and concise description of what the bug is / Подробно опишите, в чём заключается ошибка placeholder: Attach the screenshots for clarity / При необходимости приложите скриншоты validations: required: true - type: textarea id: additional-info attributes: label: Additional information / Дополнительная информация description: If you have a hints on why this bug happens, you can express it here / Если у вас есть предположения об источнике бага, вы можете изложить их здесь ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: The program worked yesterday but not today / Программа работала вчера, но перестала работать сегодня url: https://ntc.party/c/community-software/goodbyedpi/8 about: Visit support community forum, people will certainly help you! / Посетите форум поддержки, где сообщество вам поможет! - name: Сertain website is still unreachable / Отдельный веб-сайт всё ещё недоступен url: https://ntc.party/c/community-software/goodbyedpi/8 about: That could be solved with community support! / Проблема будет решена силами сообщества! - name: Questions about the program / Вопросы по програме url: https://ntc.party/c/community-software/goodbyedpi/8 about: Please ask and answer questions on forum / Пожалуйста, задавайте вопросы только на форуме ================================================ FILE: .github/ISSUE_TEMPLATE/feature.yml ================================================ name: Feature request / Предложить новую функциональность description: Suggest an idea or function for this project / Предложить новую идею или функциональность в программе body: - type: markdown attributes: value: | If you think of a great function or circumvention method which is missing from the program, go ahead and suggest it here. But first make sure to search exiting tickets. Если вы придумали новую функцию или метод обхода, которого еще нет в программе, напишите о нем подробнее здесь. Но сначала убедитесь с помощью поиска, что такого запроса еще не было. - type: checkboxes id: ensure attributes: label: I've made sure there's no existing feature request / Я убедился, что такой функциональности еще никто не предлагал options: - label: I've made sure there's no existing feature request / Я убедился, что такой функциональности еще никто не предлагал required: true - type: textarea id: description attributes: label: Describe your feature / Опишите ваше предложение validations: required: true ================================================ FILE: .github/workflows/build.yml ================================================ name: Build GoodbyeDPI on: push: paths: - 'src/**' pull_request: paths: - 'src/**' workflow_dispatch: env: WINDIVERT_URL: https://reqrypt.org/download/WinDivert-2.2.0-D.zip WINDIVERT_NAME: WinDivert-2.2.0-D.zip WINDIVERT_BASENAME: WinDivert-2.2.0-D WINDIVERT_SHA256: 1d461cfdfa7ba88ebcfbb3603b71b703e9f72aba8aeff99a75ce293e6f89d2ba jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Declare short commit variable id: vars run: | echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Install MinGW-w64 run: > sudo rm /var/lib/man-db/auto-update && sudo DEBIAN_FRONTEND=noninteractive XZ_DEFAULTS="-T0" XZ_OPT="-T0" eatmydata apt install -y --no-install-recommends gcc-mingw-w64 - name: Download WinDivert from cache id: windivert-cache uses: actions/cache@v4 with: path: ${{ env.WINDIVERT_NAME }} key: ${{ env.WINDIVERT_SHA256 }} - name: Download WinDivert from the website if: steps.windivert-cache.outputs.cache-hit != 'true' run: > wget ${{ env.WINDIVERT_URL }} && (echo ${{ env.WINDIVERT_SHA256 }} ${{ env.WINDIVERT_NAME }} | sha256sum -c) - name: Unpack WinDivert run: unzip ${{ env.WINDIVERT_NAME }} - name: Compile x86_64 run: > cd src && make clean && make CPREFIX=x86_64-w64-mingw32- BIT64=1 WINDIVERTHEADERS=../${{ env.WINDIVERT_BASENAME }}/include WINDIVERTLIBS=../${{ env.WINDIVERT_BASENAME }}/x64 -j4 - name: Prepare x86_64 directory run: | mkdir goodbyedpi_x86_64_${{ steps.vars.outputs.sha_short }} cp src/goodbyedpi.exe ${{ env.WINDIVERT_BASENAME }}/x64/*.{dll,sys} goodbyedpi_x86_64_${{ steps.vars.outputs.sha_short }} - name: Upload output file x86_64 uses: actions/upload-artifact@v4 with: name: goodbyedpi_x86_64_${{ steps.vars.outputs.sha_short }} path: goodbyedpi_x86_64_${{ steps.vars.outputs.sha_short }} - name: Compile i686 run: > cd src && make clean && make CPREFIX=i686-w64-mingw32- WINDIVERTHEADERS=../${{ env.WINDIVERT_BASENAME }}/include WINDIVERTLIBS=../${{ env.WINDIVERT_BASENAME }}/x86 -j4 - name: Prepare x86 directory run: | mkdir goodbyedpi_x86_${{ steps.vars.outputs.sha_short }} cp src/goodbyedpi.exe ${{ env.WINDIVERT_BASENAME }}/x86/*.{dll,sys} goodbyedpi_x86_${{ steps.vars.outputs.sha_short }} - name: Upload output file x86 uses: actions/upload-artifact@v4 with: name: goodbyedpi_x86_${{ steps.vars.outputs.sha_short }} path: goodbyedpi_x86_${{ steps.vars.outputs.sha_short }} ================================================ FILE: .gitignore ================================================ *.o *.exe ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ GoodbyeDPI — Deep Packet Inspection circumvention utility ========================= This software designed to bypass Deep Packet Inspection systems found in many Internet Service Providers which block access to certain websites. It handles DPI connected using optical splitter or port mirroring (**Passive DPI**) which do not block any data but just replying faster than requested destination, and **Active DPI** connected in sequence. **Windows 7, 8, 8.1, 10 or 11** with administrator privileges required. # Quick start * **For Russia**: Download [latest version from Releases page](https://github.com/ValdikSS/GoodbyeDPI/releases), unpack the file and run **1_russia_blacklist_dnsredir.cmd** script. * For other countries: Download [latest version from Releases page](https://github.com/ValdikSS/GoodbyeDPI/releases), unpack the file and run **2_any_country_dnsredir.cmd**. These scripts launch GoodbyeDPI in recommended mode with DNS resolver redirection to Yandex DNS on non-standard port (to prevent DNS poisoning). If it works — congratulations! You can use it as-is or configure further. # How to use Download [latest version from Releases page](https://github.com/ValdikSS/GoodbyeDPI/releases) and run. ## Supported arguments To get relevant information about your version of the program, use the -h (--help) argument at startup. ``` Usage: goodbyedpi.exe [OPTION...] -p block passive DPI -q block QUIC/HTTP3 -r replace Host with hoSt -s remove space between host header and its value -m mix Host header case (test.com -> tEsT.cOm) -f set HTTP fragmentation to value -k enable HTTP persistent (keep-alive) fragmentation and set it to value -n do not wait for first segment ACK when -k is enabled -e set HTTPS fragmentation to value -a additional space between Method and Request-URI (enables -s, may break sites) -w try to find and parse HTTP traffic on all processed ports (not only on port 80) --port additional TCP port to perform fragmentation on (and HTTP tricks with -w) --ip-id handle additional IP ID (decimal, drop redirects and TCP RSTs with this ID). This option can be supplied multiple times. --dns-addr redirect UDP DNS requests to the supplied IP address (experimental) --dns-port redirect UDP DNS requests to the supplied port (53 by default) --dnsv6-addr redirect UDPv6 DNS requests to the supplied IPv6 address (experimental) --dnsv6-port redirect UDPv6 DNS requests to the supplied port (53 by default) --dns-verb print verbose DNS redirection messages --blacklist perform circumvention tricks only to host names and subdomains from supplied text file (HTTP Host/TLS SNI). This option can be supplied multiple times. --allow-no-sni perform circumvention if TLS SNI can't be detected with --blacklist enabled. --frag-by-sni if SNI is detected in TLS packet, fragment the packet right before SNI value. --set-ttl activate Fake Request Mode and send it with supplied TTL value. DANGEROUS! May break websites in unexpected ways. Use with care (or --blacklist). --auto-ttl [a1-a2-m] activate Fake Request Mode, automatically detect TTL and decrease it based on a distance. If the distance is shorter than a2, TTL is decreased by a2. If it's longer, (a1; a2) scale is used with the distance as a weight. If the resulting TTL is more than m(ax), set it to m. Default (if set): --auto-ttl 1-4-10. Also sets --min-ttl 3. DANGEROUS! May break websites in unexpected ways. Use with care (or --blacklist). --min-ttl minimum TTL distance (128/64 - TTL) for which to send Fake Request in --set-ttl and --auto-ttl modes. --wrong-chksum activate Fake Request Mode and send it with incorrect TCP checksum. May not work in a VM or with some routers, but is safer than set-ttl. --wrong-seq activate Fake Request Mode and send it with TCP SEQ/ACK in the past. --native-frag fragment (split) the packets by sending them in smaller packets, without shrinking the Window Size. Works faster (does not slow down the connection) and better. --reverse-frag fragment (split) the packets just as --native-frag, but send them in the reversed order. Works with the websites which could not handle segmented HTTPS TLS ClientHello (because they receive the TCP flow "combined"). --fake-from-hex Load fake packets for Fake Request Mode from HEX values (like 1234abcDEF). This option can be supplied multiple times, in this case each fake packet would be sent on every request in the command line argument order. --fake-with-sni Generate fake packets for Fake Request Mode with given SNI domain name. The packets mimic Mozilla Firefox 130 TLS ClientHello packet (with random generated fake SessionID, key shares and ECH grease). Can be supplied multiple times for multiple fake packets. --fake-gen Generate random-filled fake packets for Fake Request Mode, value of them (up to 30). --fake-resend Send each fake packet value number of times. Default: 1 (send each packet once). --max-payload [value] packets with TCP payload data more than [value] won't be processed. Use this option to reduce CPU usage by skipping huge amount of data (like file transfers) in already established sessions. May skip some huge HTTP requests from being processed. Default (if set): --max-payload 1200. LEGACY modesets: -1 -p -r -s -f 2 -k 2 -n -e 2 (most compatible mode) -2 -p -r -s -f 2 -k 2 -n -e 40 (better speed for HTTPS yet still compatible) -3 -p -r -s -e 40 (better speed for HTTP and HTTPS) -4 -p -r -s (best speed) Modern modesets (more stable, more compatible, faster): -5 -f 2 -e 2 --auto-ttl --reverse-frag --max-payload -6 -f 2 -e 2 --wrong-seq --reverse-frag --max-payload -7 -f 2 -e 2 --wrong-chksum --reverse-frag --max-payload -8 -f 2 -e 2 --wrong-seq --wrong-chksum --reverse-frag --max-payload -9 -f 2 -e 2 --wrong-seq --wrong-chksum --reverse-frag --max-payload -q (this is the default) Note: combination of --wrong-seq and --wrong-chksum generates two different fake packets. ``` ## How to check To check if your ISP's DPI could be circumvented, first make sure that your provider does not poison DNS answers by enabling "Secure DNS (DNS over HTTPS)" option in your browser. * **Chrome**: Settings → [Privacy and security](chrome://settings/security) → Use secure DNS → With: NextDNS * **Firefox**: [Settings](about:preferences) → Network Settings → Enable DNS over HTTPS → Use provider: NextDNS Then run the `goodbyedpi.exe` executable without any options. If it works — congratulations! You can use it as-is or configure further, for example by using `--blacklist` option if the list of blocked websites is known and available for your country. If your provider intercepts DNS requests, you may want to use `--dns-addr` option to a public DNS resolver running on non-standard port (such as Yandex DNS `77.88.8.8:1253`) or configure DNS over HTTPS/TLS using third-party applications. Check the .cmd scripts and modify it according to your preference and network conditions. # How does it work ### Passive DPI Most Passive DPI send HTTP 302 Redirect if you try to access blocked website over HTTP and TCP Reset in case of HTTPS, faster than destination website. Packets sent by DPI usually have IP Identification field equal to `0x0000` or `0x0001`, as seen with Russian providers. These packets, if they redirect you to another website (censorship page), are blocked by GoodbyeDPI. ### Active DPI Active DPI is more tricky to fool. Currently the software uses 7 methods to circumvent Active DPI: * TCP-level fragmentation for first data packet * TCP-level fragmentation for persistent (keep-alive) HTTP sessions * Replacing `Host` header with `hoSt` * Removing space between header name and value in `Host` header * Adding additional space between HTTP Method (GET, POST etc) and URI * Mixing case of Host header value * Sending fake HTTP/HTTPS packets with low Time-To-Live value, incorrect checksum or incorrect TCP Sequence/Acknowledgement numbers to fool DPI and prevent delivering them to the destination These methods should not break any website as they're fully compatible with TCP and HTTP standards, yet it's sufficient to prevent DPI data classification and to circumvent censorship. Additional space may break some websites, although it's acceptable by HTTP/1.1 specification (see 19.3 Tolerant Applications). The program loads WinDivert driver which uses Windows Filtering Platform to set filters and redirect packets to the userspace. It's running as long as console window is visible and terminates when you close the window. # How to build from source This project can be built using **GNU Make** and [**mingw**](https://mingw-w64.org). The only dependency is [WinDivert](https://github.com/basil00/Divert). To build x86 exe run: `make CPREFIX=i686-w64-mingw32- WINDIVERTHEADERS=/path/to/windivert/include WINDIVERTLIBS=/path/to/windivert/x86` And for x86_64: `make CPREFIX=x86_64-w64-mingw32- BIT64=1 WINDIVERTHEADERS=/path/to/windivert/include WINDIVERTLIBS=/path/to/windivert/amd64` # How to install as Windows Service Check examples in `service_install_russia_blacklist.cmd`, `service_install_russia_blacklist_dnsredir.cmd` and `service_remove.cmd` scripts. Modify them according to your own needs. # Known issues * Horribly outdated Windows 7 installations are not able to load WinDivert driver due to missing support for SHA256 digital signatures. Install KB3033929 [x86](https://www.microsoft.com/en-us/download/details.aspx?id=46078)/[x64](https://www.microsoft.com/en-us/download/details.aspx?id=46148), or better, update the whole system using Windows Update. * Intel/Qualcomm Killer network cards: `Advanced Stream Detect` in Killer Control Center is incompatible with GoodbyeDPI, [disable it](https://github.com/ValdikSS/GoodbyeDPI/issues/541#issuecomment-2296038239). * QUIK trading software [may interfere with GoodbyeDPI](https://github.com/ValdikSS/GoodbyeDPI/issues/677#issuecomment-2390595606). First start QUIK, then GoodbyeDPI. * ~~Some SSL/TLS stacks unable to process fragmented ClientHello packets, and HTTPS websites won't open. Bug: [#4](https://github.com/ValdikSS/GoodbyeDPI/issues/4), [#64](https://github.com/ValdikSS/GoodbyeDPI/issues/64).~~ Fragmentation issues are fixed in v0.1.7. * ~~ESET Antivirus is incompatible with WinDivert driver [#91](https://github.com/ValdikSS/GoodbyeDPI/issues/91). This is most probably antivirus bug, not WinDivert.~~ # Similar projects - **[zapret](https://github.com/bol-van/zapret)** by @bol-van (for MacOS, Linux and Windows) - **[Green Tunnel](https://github.com/SadeghHayeri/GreenTunnel)** by @SadeghHayeri (for MacOS, Linux and Windows) - **[DPI Tunnel CLI](https://github.com/nomoresat/DPITunnel-cli)** by @zhenyolka (for Linux and routers) - **[DPI Tunnel for Android](https://github.com/nomoresat/DPITunnel-android)** by @zhenyolka (for Android) - **[PowerTunnel](https://github.com/krlvm/PowerTunnel)** by @krlvm (for Windows, MacOS and Linux) - **[PowerTunnel for Android](https://github.com/krlvm/PowerTunnel-Android)** by @krlvm (for Android) - **[SpoofDPI](https://github.com/xvzc/SpoofDPI)** by @xvzc (for macOS and Linux) - **[SpoofDPI-Platform](https://github.com/r3pr3ss10n/SpoofDPI-Platform)** by @r3pr3ss10n (for Android, macOS, Windows) - **[GhosTCP](https://github.com/macronut/ghostcp)** by @macronut (for Windows) - **[ByeDPI](https://github.com/hufrea/byedpi)** for Linux/Windows + **[ByeDPIAndroid](https://github.com/dovecoteescapee/ByeDPIAndroid/)** / **[ByeByeDPI](https://github.com/romanvht/ByeByeDPI/)** for Android (no root) - **[youtubeUnblock](https://github.com/Waujito/youtubeUnblock/)** by @Waujito (for OpenWRT/Entware routers and Linux) # Kudos Thanks @basil00 for [WinDivert](https://github.com/basil00/Divert). That's the main part of this program. Thanks for every [BlockCheck](https://github.com/ValdikSS/blockcheck) contributor. It would be impossible to understand DPI behaviour without this utility. ================================================ FILE: src/Makefile ================================================ ifndef MSYSTEM CPREFIX = x86_64-w64-mingw32- endif WINDIVERTHEADERS = ../../../include WINDIVERTLIBS = ../../binary MINGWLIB = /usr/x86_64-w64-mingw32/lib/ TARGET = goodbyedpi.exe # Linking SSP does not work for some reason, the executable doesn't start. #LIBS = -L$(WINDIVERTLIBS) -Wl,-Bstatic -lssp -Wl,-Bdynamic -lWinDivert -lws2_32 LIBS = -L$(WINDIVERTLIBS) -lWinDivert -lws2_32 -l:libssp.a CC = $(CPREFIX)gcc CCWINDRES = $(CPREFIX)windres ifeq (, $(shell which $(CPREFIX)windres)) CCWINDRES = windres endif CFLAGS = -std=c99 -pie -fPIE -pipe -I$(WINDIVERTHEADERS) -L$(WINDIVERTLIBS) \ -O2 -D_FORTIFY_SOURCE=2 -fstack-protector \ -Wall -Wextra -Wpedantic -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=2 \ -Wformat-security -Wno-format-nonliteral -Wshadow -Wstrict-aliasing=1 \ -Wnull-dereference -Warray-bounds=2 -Wimplicit-fallthrough=3 \ -Wstringop-overflow=4 \ -Wformat-signedness -Wstrict-overflow=2 -Wcast-align=strict \ -Wfloat-equal -Wcast-align -Wsign-conversion \ #-fstack-protector-strong LDFLAGS = -fstack-protector -Wl,-O1,-pie,--dynamicbase,--nxcompat,--sort-common,--as-needed \ -Wl,--disable-auto-image-base ifdef BIT64 LDFLAGS += -Wl,--high-entropy-va -Wl,--pic-executable,-e,mainCRTStartup else CFLAGS += -m32 LDFLAGS += -Wl,--pic-executable,-e,_mainCRTStartup -m32 endif .PHONY: default all clean default: $(TARGET) all: default OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c utils/*.c)) goodbyedpi-rc.o HEADERS = $(wildcard *.h utils/*.h) %.o: %.c $(HEADERS) $(CC) $(CFLAGS) -c $< -o $@ goodbyedpi-rc.o: $(CCWINDRES) goodbyedpi-rc.rc goodbyedpi-rc.o .PRECIOUS: $(TARGET) $(OBJECTS) $(TARGET): $(OBJECTS) $(CC) $(OBJECTS) $(LDFLAGS) $(LIBS) -s -o $@ clean: -rm -f *.o utils/*.o -rm -f $(TARGET) ================================================ FILE: src/blackwhitelist.c ================================================ /* * Blacklist for GoodbyeDPI HTTP DPI circumvention tricks * * This is a simple domain hash table. * Domain records are added from a text file, where every * domain is separated with a new line. */ #include #include #include "goodbyedpi.h" #include "utils/uthash.h" #include "utils/getline.h" typedef struct blackwhitelist_record { const char *host; UT_hash_handle hh; /* makes this structure hashable */ } blackwhitelist_record_t; static blackwhitelist_record_t *blackwhitelist = NULL; static int check_get_hostname(const char *host) { blackwhitelist_record_t *tmp_record = NULL; if (!blackwhitelist) return FALSE; HASH_FIND_STR(blackwhitelist, host, tmp_record); if (tmp_record) { debug("check_get_hostname found host\n"); return TRUE; } debug("check_get_hostname host not found\n"); return FALSE; } static int add_hostname(const char *host) { if (!host) return FALSE; blackwhitelist_record_t *tmp_record = malloc(sizeof(blackwhitelist_record_t)); char *host_c = NULL; if (!check_get_hostname(host)) { host_c = strdup(host); tmp_record->host = host_c; HASH_ADD_KEYPTR(hh, blackwhitelist, tmp_record->host, strlen(tmp_record->host), tmp_record); debug("Added host %s\n", host_c); return TRUE; } debug("Not added host %s\n", host); free(tmp_record); if (host_c) free(host_c); return FALSE; } int blackwhitelist_load_list(const char *filename) { char *line = malloc(HOST_MAXLEN + 1); size_t linelen = HOST_MAXLEN + 1; int cnt = 0; ssize_t read; FILE *fp = fopen(filename, "r"); if (!fp) return FALSE; while ((read = getline(&line, &linelen, fp)) != -1) { /* works with both \n and \r\n */ line[strcspn(line, "\r\n")] = '\0'; if (strlen(line) > HOST_MAXLEN) { printf("WARNING: host %s exceeds maximum host length and has not been added\n", line); continue; } if (strlen(line) < 2) { printf("WARNING: host %s is less than 2 characters, skipping\n", line); continue; } if (add_hostname(line)) cnt++; } free(line); if (!blackwhitelist) return FALSE; printf("Loaded %d hosts from file %s\n", cnt, filename); fclose(fp); return TRUE; } int blackwhitelist_check_hostname(const char *host_addr, size_t host_len) { char current_host[HOST_MAXLEN + 1]; char *tokenized_host = NULL; if (host_len > HOST_MAXLEN) return FALSE; if (host_addr && host_len) { memcpy(current_host, host_addr, host_len); current_host[host_len] = '\0'; } if (check_get_hostname(current_host)) return TRUE; tokenized_host = strchr(current_host, '.'); while (tokenized_host != NULL && tokenized_host < (current_host + HOST_MAXLEN)) { if (check_get_hostname(tokenized_host + 1)) return TRUE; tokenized_host = strchr(tokenized_host + 1, '.'); } debug("____blackwhitelist_check_hostname FALSE: host %s\n", current_host); return FALSE; } ================================================ FILE: src/blackwhitelist.h ================================================ int blackwhitelist_load_list(const char *filename); int blackwhitelist_check_hostname(const char *host_addr, size_t host_len); ================================================ FILE: src/dnsredir.c ================================================ /* * DNS UDP Connection Tracker for GoodbyeDPI * * This is a simple connection tracker for DNS UDP data. * It's not a proper one. The caveats as follows: * * Uses only source IP address and port as a hash key; * * One-shot only. Removes conntrack record as soon as gets the reply; * * Does not properly parse DNS request and response, only checks some bytes; * * But anyway, it works fine for DNS. */ #include #include #include #include "goodbyedpi.h" #include "dnsredir.h" #include "utils/uthash.h" /* key ('4' for IPv4 or '6' for IPv6 + srcip[16] + srcport[2]) */ #define UDP_CONNRECORD_KEY_LEN 19 #define DNS_CLEANUP_INTERVAL_SEC 30 /* HACK! * uthash uses strlen() for HASH_FIND_STR. * We have null bytes in our key, so we can't use strlen() * And since it's always UDP_CONNRECORD_KEY_LEN bytes long, * we don't need to use any string function to determine length. */ #undef uthash_strlen #define uthash_strlen(s) UDP_CONNRECORD_KEY_LEN typedef struct udp_connrecord { /* key ('4' for IPv4 or '6' for IPv6 + srcip[16] + srcport[2]) */ char key[UDP_CONNRECORD_KEY_LEN]; time_t time; /* time when this record was added */ uint32_t dstip[4]; uint16_t dstport; UT_hash_handle hh; /* makes this structure hashable */ } udp_connrecord_t; static time_t last_cleanup = 0; static udp_connrecord_t *conntrack = NULL; void flush_dns_cache() { INT_PTR WINAPI (*DnsFlushResolverCache)(); HMODULE dnsapi = LoadLibrary("dnsapi.dll"); if (dnsapi == NULL) { printf("Can't load dnsapi.dll to flush DNS cache!\n"); exit(EXIT_FAILURE); } DnsFlushResolverCache = GetProcAddress(dnsapi, "DnsFlushResolverCache"); if (DnsFlushResolverCache == NULL || !DnsFlushResolverCache()) printf("Can't flush DNS cache!"); FreeLibrary(dnsapi); } inline static void fill_key_data(char *key, const uint8_t is_ipv6, const uint32_t srcip[4], const uint16_t srcport) { if (is_ipv6) { *(uint8_t*)(key) = '6'; ipv6_copy_addr((uint32_t*)(key + sizeof(uint8_t)), srcip); } else { *(uint8_t*)(key) = '4'; ipv4_copy_addr((uint32_t*)(key + sizeof(uint8_t)), srcip); } *(uint16_t*)(key + sizeof(uint8_t) + sizeof(uint32_t) * 4) = srcport; } inline static void fill_data_from_key(uint8_t *is_ipv6, uint32_t srcip[4], uint16_t *srcport, const char *key) { if (key[0] == '6') { *is_ipv6 = 1; ipv6_copy_addr(srcip, (uint32_t*)(key + sizeof(uint8_t))); } else { *is_ipv6 = 0; ipv4_copy_addr(srcip, (uint32_t*)(key + sizeof(uint8_t))); } *srcport = *(uint16_t*)(key + sizeof(uint8_t) + sizeof(uint32_t) * 4); } inline static void construct_key(const uint32_t srcip[4], const uint16_t srcport, char *key, const uint8_t is_ipv6) { debug("Construct key enter\n"); if (key) { debug("Constructing key\n"); fill_key_data(key, is_ipv6, srcip, srcport); } debug("Construct key end\n"); } inline static void deconstruct_key(const char *key, const udp_connrecord_t *connrecord, conntrack_info_t *conn_info) { debug("Deconstruct key enter\n"); if (key && conn_info) { debug("Deconstructing key\n"); fill_data_from_key(&conn_info->is_ipv6, conn_info->srcip, &conn_info->srcport, key); if (conn_info->is_ipv6) ipv6_copy_addr(conn_info->dstip, connrecord->dstip); else ipv4_copy_addr(conn_info->dstip, connrecord->dstip); conn_info->dstport = connrecord->dstport; } debug("Deconstruct key end\n"); } static int check_get_udp_conntrack_key(const char *key, udp_connrecord_t **connrecord) { udp_connrecord_t *tmp_connrecord = NULL; if (!conntrack) return FALSE; HASH_FIND_STR(conntrack, key, tmp_connrecord); if (tmp_connrecord) { if (connrecord) *connrecord = tmp_connrecord; debug("check_get_udp_conntrack_key found key\n"); return TRUE; } debug("check_get_udp_conntrack_key key not found\n"); return FALSE; } static int add_udp_conntrack(const uint32_t srcip[4], const uint16_t srcport, const uint32_t dstip[4], const uint16_t dstport, const uint8_t is_ipv6 ) { if (!(srcip && srcport && dstip && dstport)) return FALSE; udp_connrecord_t *tmp_connrecord = malloc(sizeof(udp_connrecord_t)); construct_key(srcip, srcport, tmp_connrecord->key, is_ipv6); if (!check_get_udp_conntrack_key(tmp_connrecord->key, NULL)) { tmp_connrecord->time = time(NULL); if (is_ipv6) { ipv6_copy_addr(tmp_connrecord->dstip, dstip); } else { ipv4_copy_addr(tmp_connrecord->dstip, dstip); } tmp_connrecord->dstport = dstport; HASH_ADD_STR(conntrack, key, tmp_connrecord); debug("Added UDP conntrack\n"); return TRUE; } debug("Not added UDP conntrack\n"); free(tmp_connrecord); return FALSE; } static void dns_cleanup() { udp_connrecord_t *tmp_connrecord, *tmp_connrecord2 = NULL; if (last_cleanup == 0) { last_cleanup = time(NULL); return; } if (difftime(time(NULL), last_cleanup) >= DNS_CLEANUP_INTERVAL_SEC) { last_cleanup = time(NULL); HASH_ITER(hh, conntrack, tmp_connrecord, tmp_connrecord2) { if (difftime(last_cleanup, tmp_connrecord->time) >= DNS_CLEANUP_INTERVAL_SEC) { HASH_DEL(conntrack, tmp_connrecord); free(tmp_connrecord); } } } } int dns_is_dns_packet(const char *packet_data, const UINT packet_dataLen, const int outgoing) { if (packet_dataLen < 16) return FALSE; if (outgoing && (ntohs(*(const uint16_t*)(packet_data + 2)) & 0xFA00) == 0 && (ntohs(*(const uint32_t*)(packet_data + 6))) == 0) { return TRUE; } else if (!outgoing && (ntohs(*(const uint16_t*)(packet_data + 2)) & 0xF800) == 0x8000) { return TRUE; } return FALSE; } int dns_handle_outgoing(const uint32_t srcip[4], const uint16_t srcport, const uint32_t dstip[4], const uint16_t dstport, const char *packet_data, const UINT packet_dataLen, const uint8_t is_ipv6) { if (packet_dataLen < 16) return FALSE; dns_cleanup(); if (dns_is_dns_packet(packet_data, packet_dataLen, 1)) { /* Looks like DNS request */ debug("trying to add srcport = %hu, dstport = %hu\n", ntohs(srcport), ntohs(dstport)); return add_udp_conntrack(srcip, srcport, dstip, dstport, is_ipv6); } debug("____dns_handle_outgoing FALSE: srcport = %hu, dstport = %hu\n", ntohs(srcport), ntohs(dstport)); return FALSE; } int dns_handle_incoming(const uint32_t srcip[4], const uint16_t srcport, const char *packet_data, const UINT packet_dataLen, conntrack_info_t *conn_info, const uint8_t is_ipv6) { char key[UDP_CONNRECORD_KEY_LEN]; udp_connrecord_t *tmp_connrecord = NULL; if (packet_dataLen < 16 || !conn_info) return FALSE; dns_cleanup(); if (dns_is_dns_packet(packet_data, packet_dataLen, 0)) { /* Looks like DNS response */ construct_key(srcip, srcport, key, is_ipv6); if (check_get_udp_conntrack_key(key, &tmp_connrecord) && tmp_connrecord) { /* Connection exists in conntrack, moving on */ deconstruct_key(key, tmp_connrecord, conn_info); HASH_DEL(conntrack, tmp_connrecord); free(tmp_connrecord); return TRUE; } } debug("____dns_handle_incoming FALSE: srcport = %hu\n", ntohs(srcport)); return FALSE; } ================================================ FILE: src/dnsredir.h ================================================ #ifndef _DNSREDIR_H #define _DNSREDIR_H #include typedef struct conntrack_info { uint8_t is_ipv6; uint32_t srcip[4]; uint16_t srcport; uint32_t dstip[4]; uint16_t dstport; } conntrack_info_t; inline static void ipv4_copy_addr(uint32_t dst[4], const uint32_t src[4]) { dst[0] = src[0]; dst[1] = 0; dst[2] = 0; dst[3] = 0; } inline static void ipv6_copy_addr(uint32_t dst[4], const uint32_t src[4]) { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; } int dns_handle_incoming(const uint32_t srcip[4], const uint16_t srcport, const char *packet_data, const UINT packet_dataLen, conntrack_info_t *conn_info, const uint8_t is_ipv6); int dns_handle_outgoing(const uint32_t srcip[4], const uint16_t srcport, const uint32_t dstip[4], const uint16_t dstport, const char *packet_data, const UINT packet_dataLen, const uint8_t is_ipv6 ); void flush_dns_cache(); int dns_is_dns_packet(const char *packet_data, const UINT packet_dataLen, const int outgoing); #endif ================================================ FILE: src/fakepackets.c ================================================ #include #define _CRT_RAND_S #include #include #include #include #include #include #include "windivert.h" #include "goodbyedpi.h" struct fake_t { const unsigned char* data; size_t size; }; static struct fake_t *fakes[30] = {0}; int fakes_count = 0; int fakes_resend = 1; static const unsigned char fake_http_request[] = "GET / HTTP/1.1\r\nHost: www.w3.org\r\n" "User-Agent: curl/7.65.3\r\nAccept: */*\r\n" "Accept-Encoding: deflate, gzip, br\r\n\r\n"; static const unsigned char fake_https_request[] = { 0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0x9a, 0x8f, 0xa7, 0x6a, 0x5d, 0x57, 0xf3, 0x62, 0x19, 0xbe, 0x46, 0x82, 0x45, 0xe2, 0x59, 0x5c, 0xb4, 0x48, 0x31, 0x12, 0x15, 0x14, 0x79, 0x2c, 0xaa, 0xcd, 0xea, 0xda, 0xf0, 0xe1, 0xfd, 0xbb, 0x20, 0xf4, 0x83, 0x2a, 0x94, 0xf1, 0x48, 0x3b, 0x9d, 0xb6, 0x74, 0xba, 0x3c, 0x81, 0x63, 0xbc, 0x18, 0xcc, 0x14, 0x45, 0x57, 0x6c, 0x80, 0xf9, 0x25, 0xcf, 0x9c, 0x86, 0x60, 0x50, 0x31, 0x2e, 0xe9, 0x00, 0x22, 0x13, 0x01, 0x13, 0x03, 0x13, 0x02, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x2c, 0xc0, 0x30, 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x33, 0x00, 0x39, 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x01, 0x91, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x0d, 0x00, 0x00, 0x0a, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x01, 0x00, 0x01, 0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x6b, 0x00, 0x69, 0x00, 0x1d, 0x00, 0x20, 0xb0, 0xe4, 0xda, 0x34, 0xb4, 0x29, 0x8d, 0xd3, 0x5c, 0x70, 0xd3, 0xbe, 0xe8, 0xa7, 0x2a, 0x6b, 0xe4, 0x11, 0x19, 0x8b, 0x18, 0x9d, 0x83, 0x9a, 0x49, 0x7c, 0x83, 0x7f, 0xa9, 0x03, 0x8c, 0x3c, 0x00, 0x17, 0x00, 0x41, 0x04, 0x4c, 0x04, 0xa4, 0x71, 0x4c, 0x49, 0x75, 0x55, 0xd1, 0x18, 0x1e, 0x22, 0x62, 0x19, 0x53, 0x00, 0xde, 0x74, 0x2f, 0xb3, 0xde, 0x13, 0x54, 0xe6, 0x78, 0x07, 0x94, 0x55, 0x0e, 0xb2, 0x6c, 0xb0, 0x03, 0xee, 0x79, 0xa9, 0x96, 0x1e, 0x0e, 0x98, 0x17, 0x78, 0x24, 0x44, 0x0c, 0x88, 0x80, 0x06, 0x8b, 0xd4, 0x80, 0xbf, 0x67, 0x7c, 0x37, 0x6a, 0x5b, 0x46, 0x4c, 0xa7, 0x98, 0x6f, 0xb9, 0x22, 0x00, 0x2b, 0x00, 0x09, 0x08, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x0d, 0x00, 0x18, 0x00, 0x16, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c, 0x00, 0x02, 0x40, 0x01, 0x00, 0x15, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Captured from Firefox 130.0.1 static const unsigned char fake_clienthello_part0[] = { // 116 bytes // TLS 1.2 ClientHello header (DD for length placeholder) 0x16, 0x03, 0x01, 0xDD, 0xDD, 0x01, 0x00, 0xDD, 0xDD, 0x03, 0x03, // Random bytes (AA for placeholder) 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // Random Session ID 0x20, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // Cipher Suites 0x00, 0x22, 0x13, 0x01, 0x13, 0x03, 0x13, 0x02, 0xC0, 0x2B, 0xC0, 0x2F, 0xCC, 0xA9, 0xCC, 0xA8, 0xC0, 0x2C, 0xC0, 0x30, 0xC0, 0x0A, 0xC0, 0x09, 0xC0, 0x13, 0xC0, 0x14, 0x00, 0x9C, 0x00, 0x9D, 0x00, 0x2F, 0x00, 0x35, // Compression Methods 0x01, 0x00, // Extensions Length 0xDD, 0xDD, }; // SNI: 00 00 L1 L1 L2 L2 00 L3 L3 (sni) // L1 = L+5, L2 = L+3, L3 = L // 9 + L bytes static const unsigned char fake_clienthello_part1[] = { // 523 bytes // extended_master_secret 0x00, 0x17, 0x00, 0x00, // renegotiation_info 0xFF, 0x01, 0x00, 0x01, 0x00, // supported_groups 0x00, 0x0A, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0x1D, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x01, 0x00, 0x01, 0x01, // ex_point_formats 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00, // session_ticket 0x00, 0x23, 0x00, 0x00, // ALPN 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0C, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2F, 0x31, 0x2E, 0x31, // status_request 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, // delegated_credentials 0x00, 0x22, 0x00, 0x0A, 0x00, 0x08, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x02, 0x03, // key_share 0x00, 0x33, 0x00, 0x6B, 0x00, 0x69, 0x00, 0x1D, 0x00, 0x20, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x17, 0x00, 0x41, 0x04, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, // supported_versions 0x00, 0x2B, 0x00, 0x05, 0x04, 0x03, 0x04, 0x03, 0x03, // signature_algorithms 0x00, 0x0D, 0x00, 0x18, 0x00, 0x16, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x08, 0x04, 0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x02, 0x03, 0x02, 0x01, // psk_key_exchange_modes 0x00, 0x2D, 0x00, 0x02, 0x01, 0x01, // record_size_limit 0x00, 0x1C, 0x00, 0x02, 0x40, 0x01, // encrypted_client_hello 0xFE, 0x0D, 0x01, 0x19, 0x00, 0x00, 0x01, 0x00, 0x01, 0xAA, 0x00, 0x20, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0xEF, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA }; // JA4: t13d1715h2_5b57614c22b0_5c2c66f702b0 // JA4_r: t13d1715h2_002f,0035,009c,009d,1301,1302,1303,c009,c00a,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0017,001c,0022,0023,002b,002d,0033,fe0d,ff01_0403,0503,0603,0804,0805,0806,0401,0501,0601,0203,0201 // JA3 Fullstring: 771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-34-51-43-13-45-28-65037,29-23-24-25-256-257,0 // JA3: b5001237acdf006056b409cc433726b0 static int send_fake_data(const HANDLE w_filter, const PWINDIVERT_ADDRESS addr, const char *pkt, const UINT packetLen, const BOOL is_ipv6, const BOOL is_https, const BYTE set_ttl, const BYTE set_checksum, const BYTE set_seq, const struct fake_t *fake_data ) { char packet_fake[MAX_PACKET_SIZE]; WINDIVERT_ADDRESS addr_new; PVOID packet_data; UINT packet_dataLen; UINT packetLen_new; PWINDIVERT_IPHDR ppIpHdr; PWINDIVERT_IPV6HDR ppIpV6Hdr; PWINDIVERT_TCPHDR ppTcpHdr; unsigned const char *fake_request_data = is_https ? fake_https_request : fake_http_request; UINT fake_request_size = is_https ? sizeof(fake_https_request) : sizeof(fake_http_request) - 1; if (fake_data) { fake_request_data = fake_data->data; fake_request_size = fake_data->size; } memcpy(&addr_new, addr, sizeof(WINDIVERT_ADDRESS)); memcpy(packet_fake, pkt, packetLen); addr_new.TCPChecksum = 0; addr_new.IPChecksum = 0; if (!is_ipv6) { // IPv4 TCP Data packet if (!WinDivertHelperParsePacket(packet_fake, packetLen, &ppIpHdr, NULL, NULL, NULL, NULL, &ppTcpHdr, NULL, &packet_data, &packet_dataLen, NULL, NULL)) return 1; } else { // IPv6 TCP Data packet if (!WinDivertHelperParsePacket(packet_fake, packetLen, NULL, &ppIpV6Hdr, NULL, NULL, NULL, &ppTcpHdr, NULL, &packet_data, &packet_dataLen, NULL, NULL)) return 1; } if (packetLen + fake_request_size + 1 > MAX_PACKET_SIZE) return 2; memcpy(packet_data, fake_request_data, fake_request_size); packetLen_new = packetLen - packet_dataLen + fake_request_size; if (!is_ipv6) { ppIpHdr->Length = htons( ntohs(ppIpHdr->Length) - packet_dataLen + fake_request_size ); if (set_ttl) ppIpHdr->TTL = set_ttl; } else { ppIpV6Hdr->Length = htons( ntohs(ppIpV6Hdr->Length) - packet_dataLen + fake_request_size ); if (set_ttl) ppIpV6Hdr->HopLimit = set_ttl; } if (set_seq) { // This is the smallest ACK drift Linux can't handle already, since at least v2.6.18. // https://github.com/torvalds/linux/blob/v2.6.18/net/netfilter/nf_conntrack_proto_tcp.c#L395 ppTcpHdr->AckNum = htonl(ntohl(ppTcpHdr->AckNum) - 66000); // This is just random, no specifics about this value. ppTcpHdr->SeqNum = htonl(ntohl(ppTcpHdr->SeqNum) - 10000); } // Recalculate the checksum WinDivertHelperCalcChecksums(packet_fake, packetLen_new, &addr_new, 0ULL); if (set_checksum) { // ...and damage it ppTcpHdr->Checksum = htons(ntohs(ppTcpHdr->Checksum) - 1); } //printf("Pseudo checksum: %d\n", addr_new.TCPChecksum); WinDivertSend( w_filter, packet_fake, packetLen_new, NULL, &addr_new ); debug("Fake packet: OK"); return 0; } static int send_fake_request(const HANDLE w_filter, const PWINDIVERT_ADDRESS addr, const char *pkt, const UINT packetLen, const BOOL is_ipv6, const BOOL is_https, const BYTE set_ttl, const BYTE set_checksum, const BYTE set_seq, const struct fake_t *fake_data ) { if (set_ttl) { send_fake_data(w_filter, addr, pkt, packetLen, is_ipv6, is_https, set_ttl, FALSE, FALSE, fake_data); } if (set_checksum) { send_fake_data(w_filter, addr, pkt, packetLen, is_ipv6, is_https, FALSE, set_checksum, FALSE, fake_data); } if (set_seq) { send_fake_data(w_filter, addr, pkt, packetLen, is_ipv6, is_https, FALSE, FALSE, set_seq, fake_data); } return 0; } int send_fake_http_request(const HANDLE w_filter, const PWINDIVERT_ADDRESS addr, const char *pkt, const UINT packetLen, const BOOL is_ipv6, const BYTE set_ttl, const BYTE set_checksum, const BYTE set_seq ) { int ret = 0; for (int i=0; isize = size; fake->data = data; for (size_t k = 0; k <= sizeof(fakes) / sizeof(*fakes); k++) { if (!fakes[k]) { fakes[k] = fake; fakes_count++; return 0; } } return 3; } int fake_load_from_hex(const char *data) { size_t len = strlen(data); if (len < 2 || len % 2 || len > (1420 * 2)) return 1; unsigned char *finaldata = calloc((len + 2) / 2, 1); for (size_t i = 0; i= '0' && curchar <= '9') curchar -= '0'; else if (curchar >= 'a' && curchar <= 'f') curchar -= 'a' - 0xA; else if (curchar >= 'A' && curchar <= 'F') curchar -= 'A' - 0xA; else return 2; // incorrect character, not a hex data if (!j) { num1 = curchar; curchar = num2; continue; } num2 = curchar; } debug("Processed num1: %X, num2: %X\n", num1, num2); finalchar = (num1 << 4) | num2; debug("Final char: %X\n", finalchar); finaldata[i/2] = finalchar; } return fake_add(finaldata, len / 2); } int fake_load_random(unsigned int count, unsigned int maxsize) { if (count < 1 || count > sizeof(fakes) / sizeof(*fakes)) return 1; unsigned int random = 0; for (unsigned int i=0; i> 8) & 0xFF; buffer[offset + 1] = value & 0xFF; } int fake_load_from_sni(const char *domain_name) { if (!domain_name) { return 1; // just extra safeguard against NPE } // calculate sizes const int name_size = strlen(domain_name); const int part0_size = sizeof(fake_clienthello_part0); const int part1_size = sizeof(fake_clienthello_part1); const int sni_head_size = 9; const int packet_size = part0_size + part1_size + sni_head_size + name_size; // allocate memory unsigned char *packet = malloc(packet_size); // copy major parts of packet memcpy(packet, fake_clienthello_part0, part0_size); memcpy(&packet[part0_size + sni_head_size + name_size], fake_clienthello_part1, part1_size); // replace placeholders with random generated values unsigned int random = 0; for (int i = 0; i < packet_size; i++) { if (packet[i] == 0xAA) { rand_s(&random); packet[i] = random & 0xFF; } } // write size fields into packet set_uint16be(packet, 0x0003, packet_size - 5); set_uint16be(packet, 0x0007, packet_size - 9); set_uint16be(packet, 0x0072, packet_size - 116); // write SNI extension set_uint16be(packet, part0_size + 0, 0x0000); set_uint16be(packet, part0_size + 2, name_size + 5); set_uint16be(packet, part0_size + 4, name_size + 3); packet[part0_size + 6] = 0x00; set_uint16be(packet, part0_size + 7, name_size); memcpy(&packet[part0_size + sni_head_size], domain_name, name_size); // add packet to fakes return fake_add(packet, packet_size); } ================================================ FILE: src/fakepackets.h ================================================ extern int fakes_count; extern int fakes_resend; int send_fake_http_request(const HANDLE w_filter, const PWINDIVERT_ADDRESS addr, const char *pkt, const UINT packetLen, const BOOL is_ipv6, const BYTE set_ttl, const BYTE set_checksum, const BYTE set_seq ); int send_fake_https_request(const HANDLE w_filter, const PWINDIVERT_ADDRESS addr, const char *pkt, const UINT packetLen, const BOOL is_ipv6, const BYTE set_ttl, const BYTE set_checksum, const BYTE set_seq ); int fake_load_from_hex(const char *data); int fake_load_from_sni(const char *domain_name); int fake_load_random(unsigned int count, unsigned int maxsize); ================================================ FILE: src/goodbyedpi-rc.rc ================================================ 1 24 "goodbyedpi.exe.manifest" id ICON "icon.ico" ================================================ FILE: src/goodbyedpi.c ================================================ /* * GoodbyeDPI — Passive DPI blocker and Active DPI circumvention utility. */ #include #include #include #include #include #include #include #include #include #include #include "windivert.h" #include "goodbyedpi.h" #include "utils/repl_str.h" #include "service.h" #include "dnsredir.h" #include "ttltrack.h" #include "blackwhitelist.h" #include "fakepackets.h" // My mingw installation does not load inet_pton definition for some reason WINSOCK_API_LINKAGE INT WSAAPI inet_pton(INT Family, LPCSTR pStringBuf, PVOID pAddr); #define GOODBYEDPI_VERSION "v0.2.3rc3" #define die() do { sleep(20); exit(EXIT_FAILURE); } while (0) #define MAX_FILTERS 4 #define DIVERT_NO_LOCALNETSv4_DST "(" \ "(ip.DstAddr < 127.0.0.1 or ip.DstAddr > 127.255.255.255) and " \ "(ip.DstAddr < 10.0.0.0 or ip.DstAddr > 10.255.255.255) and " \ "(ip.DstAddr < 192.168.0.0 or ip.DstAddr > 192.168.255.255) and " \ "(ip.DstAddr < 172.16.0.0 or ip.DstAddr > 172.31.255.255) and " \ "(ip.DstAddr < 169.254.0.0 or ip.DstAddr > 169.254.255.255)" \ ")" #define DIVERT_NO_LOCALNETSv4_SRC "(" \ "(ip.SrcAddr < 127.0.0.1 or ip.SrcAddr > 127.255.255.255) and " \ "(ip.SrcAddr < 10.0.0.0 or ip.SrcAddr > 10.255.255.255) and " \ "(ip.SrcAddr < 192.168.0.0 or ip.SrcAddr > 192.168.255.255) and " \ "(ip.SrcAddr < 172.16.0.0 or ip.SrcAddr > 172.31.255.255) and " \ "(ip.SrcAddr < 169.254.0.0 or ip.SrcAddr > 169.254.255.255)" \ ")" #define DIVERT_NO_LOCALNETSv6_DST "(" \ "(ipv6.DstAddr > ::1) and " \ "(ipv6.DstAddr < 2001::0 or ipv6.DstAddr > 2001:1::0) and " \ "(ipv6.DstAddr < fc00::0 or ipv6.DstAddr > fe00::0) and " \ "(ipv6.DstAddr < fe80::0 or ipv6.DstAddr > fec0::0) and " \ "(ipv6.DstAddr < ff00::0 or ipv6.DstAddr > ffff::0)" \ ")" #define DIVERT_NO_LOCALNETSv6_SRC "(" \ "(ipv6.SrcAddr > ::1) and " \ "(ipv6.SrcAddr < 2001::0 or ipv6.SrcAddr > 2001:1::0) and " \ "(ipv6.SrcAddr < fc00::0 or ipv6.SrcAddr > fe00::0) and " \ "(ipv6.SrcAddr < fe80::0 or ipv6.SrcAddr > fec0::0) and " \ "(ipv6.SrcAddr < ff00::0 or ipv6.SrcAddr > ffff::0)" \ ")" /* #IPID# is a template to find&replace */ #define IPID_TEMPLATE "#IPID#" #define MAXPAYLOADSIZE_TEMPLATE "#MAXPAYLOADSIZE#" #define FILTER_STRING_TEMPLATE \ "(tcp and !impostor and !loopback " MAXPAYLOADSIZE_TEMPLATE " and " \ "((inbound and (" \ "(" \ "(" \ "(ipv6 or (ip.Id >= 0x0 and ip.Id <= 0xF) " IPID_TEMPLATE \ ") and " \ "tcp.SrcPort == 80 and tcp.Ack" \ ") or " \ "((tcp.SrcPort == 80 or tcp.SrcPort == 443) and tcp.Ack and tcp.Syn)" \ ")" \ " and (" DIVERT_NO_LOCALNETSv4_SRC " or " DIVERT_NO_LOCALNETSv6_SRC "))) or " \ "(outbound and " \ "(tcp.DstPort == 80 or tcp.DstPort == 443) and tcp.Ack and " \ "(" DIVERT_NO_LOCALNETSv4_DST " or " DIVERT_NO_LOCALNETSv6_DST "))" \ "))" #define FILTER_PASSIVE_BLOCK_QUIC "outbound and !impostor and !loopback and udp " \ "and udp.DstPort == 443 and udp.PayloadLength >= 1200 " \ "and udp.Payload[0] >= 0xC0 and udp.Payload32[1b] == 0x01" #define FILTER_PASSIVE_STRING_TEMPLATE "inbound and ip and tcp and " \ "!impostor and !loopback and " \ "(true " IPID_TEMPLATE ") and " \ "(tcp.SrcPort == 443 or tcp.SrcPort == 80) and tcp.Rst and " \ DIVERT_NO_LOCALNETSv4_SRC #define SET_HTTP_FRAGMENT_SIZE_OPTION(fragment_size) do { \ if (!http_fragment_size) { \ http_fragment_size = (unsigned int)fragment_size; \ } \ else if (http_fragment_size != (unsigned int)fragment_size) { \ printf( \ "WARNING: HTTP fragment size is already set to %u, not changing.\n", \ http_fragment_size \ ); \ } \ } while (0) #define TCP_HANDLE_OUTGOING_TTL_PARSE_PACKET_IF() \ if ((packet_v4 && tcp_handle_outgoing(&ppIpHdr->SrcAddr, &ppIpHdr->DstAddr, \ ppTcpHdr->SrcPort, ppTcpHdr->DstPort, \ &tcp_conn_info, 0)) \ || \ (packet_v6 && tcp_handle_outgoing(ppIpV6Hdr->SrcAddr, ppIpV6Hdr->DstAddr, \ ppTcpHdr->SrcPort, ppTcpHdr->DstPort, \ &tcp_conn_info, 1))) #define TCP_HANDLE_OUTGOING_FAKE_PACKET(func) do { \ should_send_fake = 1; \ if (do_auto_ttl || ttl_min_nhops) { \ TCP_HANDLE_OUTGOING_TTL_PARSE_PACKET_IF() { \ if (do_auto_ttl) { \ /* If Auto TTL mode */ \ ttl_of_fake_packet = tcp_get_auto_ttl(tcp_conn_info.ttl, auto_ttl_1, auto_ttl_2, \ ttl_min_nhops, auto_ttl_max); \ if (do_tcp_verb) { \ printf("Connection TTL = %d, Fake TTL = %d\n", tcp_conn_info.ttl, ttl_of_fake_packet); \ } \ } \ else if (ttl_min_nhops) { \ /* If not Auto TTL mode but --min-ttl is set */ \ if (!tcp_get_auto_ttl(tcp_conn_info.ttl, 0, 0, ttl_min_nhops, 0)) { \ /* Send only if nhops >= min_ttl */ \ should_send_fake = 0; \ } \ } \ } \ } \ if (should_send_fake) \ func(w_filter, &addr, packet, packetLen, packet_v6, \ ttl_of_fake_packet, do_wrong_chksum, do_wrong_seq); \ } while (0) enum ERROR_CODE{ ERROR_DEFAULT = 1, ERROR_PORT_BOUNDS, ERROR_DNS_V4_ADDR, ERROR_DNS_V6_ADDR, ERROR_DNS_V4_PORT, ERROR_DNS_V6_PORT, ERROR_BLACKLIST_LOAD, ERROR_AUTOTTL, ERROR_ATOUSI, ERROR_AUTOB }; static int running_from_service = 0; static int exiting = 0; static HANDLE filters[MAX_FILTERS]; static int filter_num = 0; static const char http10_redirect_302[] = "HTTP/1.0 302 "; static const char http11_redirect_302[] = "HTTP/1.1 302 "; static const char http_host_find[] = "\r\nHost: "; static const char http_host_replace[] = "\r\nhoSt: "; static const char http_useragent_find[] = "\r\nUser-Agent: "; static const char location_http[] = "\r\nLocation: http://"; static const char connection_close[] = "\r\nConnection: close"; static const char *http_methods[] = { "GET ", "HEAD ", "POST ", "PUT ", "DELETE ", "CONNECT ", "OPTIONS ", }; static struct option long_options[] = { {"port", required_argument, 0, 'z' }, {"dns-addr", required_argument, 0, 'd' }, {"dns-port", required_argument, 0, 'g' }, {"dnsv6-addr", required_argument, 0, '!' }, {"dnsv6-port", required_argument, 0, '@' }, {"dns-verb", no_argument, 0, 'v' }, {"blacklist", required_argument, 0, 'b' }, {"allow-no-sni",no_argument, 0, ']' }, {"frag-by-sni", no_argument, 0, '>' }, {"ip-id", required_argument, 0, 'i' }, {"set-ttl", required_argument, 0, '$' }, {"min-ttl", required_argument, 0, '[' }, {"auto-ttl", optional_argument, 0, '+' }, {"wrong-chksum",no_argument, 0, '%' }, {"wrong-seq", no_argument, 0, ')' }, {"native-frag", no_argument, 0, '*' }, {"reverse-frag",no_argument, 0, '(' }, {"max-payload", optional_argument, 0, '|' }, {"fake-from-hex", required_argument, 0, 'u' }, {"fake-with-sni", required_argument, 0, '}' }, {"fake-gen", required_argument, 0, 'j' }, {"fake-resend", required_argument, 0, 't' }, {"debug-exit", optional_argument, 0, 'x' }, {0, 0, 0, 0 } }; static char *filter_string = NULL; static char *filter_passive_string = NULL; static void add_filter_str(int proto, int port) { const char *udp = " or (udp and !impostor and !loopback and " \ "(udp.SrcPort == %d or udp.DstPort == %d))"; const char *tcp = " or (tcp and !impostor and !loopback " MAXPAYLOADSIZE_TEMPLATE " and " \ "(tcp.SrcPort == %d or tcp.DstPort == %d))"; char *current_filter = filter_string; size_t new_filter_size = strlen(current_filter) + (proto == IPPROTO_UDP ? strlen(udp) : strlen(tcp)) + 16; char *new_filter = malloc(new_filter_size); strcpy(new_filter, current_filter); if (proto == IPPROTO_UDP) sprintf(new_filter + strlen(new_filter), udp, port, port); else sprintf(new_filter + strlen(new_filter), tcp, port, port); filter_string = new_filter; free(current_filter); } static void add_ip_id_str(int id) { char *newstr; const char *ipid = " or ip.Id == %d"; char *addfilter = malloc(strlen(ipid) + 16); sprintf(addfilter, ipid, id); newstr = repl_str(filter_string, IPID_TEMPLATE, addfilter); free(filter_string); filter_string = newstr; newstr = repl_str(filter_passive_string, IPID_TEMPLATE, addfilter); free(filter_passive_string); filter_passive_string = newstr; } static void add_maxpayloadsize_str(unsigned short maxpayload) { char *newstr; /* 0x47455420 is "GET ", 0x504F5354 is "POST", big endian. */ const char *maxpayloadsize_str = "and (tcp.PayloadLength ? tcp.PayloadLength < %hu " \ "or tcp.Payload32[0] == 0x47455420 or tcp.Payload32[0] == 0x504F5354 " \ "or (tcp.Payload[0] == 0x16 and tcp.Payload[1] == 0x03 and tcp.Payload[2] <= 0x03): true)"; char *addfilter = malloc(strlen(maxpayloadsize_str) + 16); sprintf(addfilter, maxpayloadsize_str, maxpayload); newstr = repl_str(filter_string, MAXPAYLOADSIZE_TEMPLATE, addfilter); free(filter_string); filter_string = newstr; } static void finalize_filter_strings() { char *newstr, *newstr2; newstr2 = repl_str(filter_string, IPID_TEMPLATE, ""); newstr = repl_str(newstr2, MAXPAYLOADSIZE_TEMPLATE, ""); free(filter_string); free(newstr2); filter_string = newstr; newstr = repl_str(filter_passive_string, IPID_TEMPLATE, ""); free(filter_passive_string); filter_passive_string = newstr; } static char* dumb_memmem(const char* haystack, unsigned int hlen, const char* needle, unsigned int nlen) { // naive implementation if (nlen > hlen) return NULL; size_t i; for (i=0; i limitValue) { puts(msg); exit(ERROR_ATOUSI); } return (unsigned short int)res; } BYTE atoub(const char *str, const char *msg) { long unsigned int res = strtoul(str, NULL, 10u); enum { limitValue=0xFFu }; if(res > limitValue) { puts(msg); exit(ERROR_AUTOB); } return (BYTE)res; } static HANDLE init(char *filter, UINT64 flags) { LPTSTR errormessage = NULL; DWORD errorcode = 0; filter = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, 0, flags); if (filter != INVALID_HANDLE_VALUE) return filter; errorcode = GetLastError(); FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorcode, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), (LPTSTR)&errormessage, 0, NULL); printf("Error opening filter: %d %s\n", errorcode, errormessage); LocalFree(errormessage); if (errorcode == 2) printf("The driver files WinDivert32.sys or WinDivert64.sys were not found.\n"); else if (errorcode == 654) printf("An incompatible version of the WinDivert driver is currently loaded.\n" "Please unload it with the following commands ran as administrator:\n\n" "sc stop windivert\n" "sc delete windivert\n" "sc stop windivert14\n" "sc delete windivert14\n"); else if (errorcode == 1275) printf("This error occurs for various reasons, including:\n" "the WinDivert driver is blocked by security software; or\n" "you are using a virtualization environment that does not support drivers.\n"); else if (errorcode == 1753) printf("This error occurs when the Base Filtering Engine service has been disabled.\n" "Enable Base Filtering Engine service.\n"); else if (errorcode == 577) printf("Could not load driver due to invalid digital signature.\n" "Windows Server 2016 systems must have secure boot disabled to be \n" "able to load WinDivert driver.\n" "Windows 7 systems must be up-to-date or at least have KB3033929 installed.\n" "https://www.microsoft.com/en-us/download/details.aspx?id=46078\n\n" "WARNING! If you see this error on Windows 7, it means your system is horribly " "outdated and SHOULD NOT BE USED TO ACCESS THE INTERNET!\n" "Most probably, you don't have security patches installed and anyone in you LAN or " "public Wi-Fi network can get full access to your computer (MS17-010 and others).\n" "You should install updates IMMEDIATELY.\n"); return NULL; } static int deinit(HANDLE handle) { if (handle) { WinDivertShutdown(handle, WINDIVERT_SHUTDOWN_BOTH); WinDivertClose(handle); return TRUE; } return FALSE; } void deinit_all() { for (int i = 0; i < filter_num; i++) { deinit(filters[i]); } } static void sigint_handler(int sig __attribute__((unused))) { exiting = 1; deinit_all(); exit(EXIT_SUCCESS); } static void mix_case(char *pktdata, unsigned int pktlen) { unsigned int i; if (pktlen <= 0) return; for (i = 0; i < pktlen; i++) { if (i % 2) { pktdata[i] = (char) toupper(pktdata[i]); } } } static int is_passivedpi_redirect(const char *pktdata, unsigned int pktlen) { /* First check if this is HTTP 302 redirect */ if (memcmp(pktdata, http11_redirect_302, sizeof(http11_redirect_302)-1) == 0 || memcmp(pktdata, http10_redirect_302, sizeof(http10_redirect_302)-1) == 0) { /* Then check if this is a redirect to new http site with Connection: close */ if (dumb_memmem(pktdata, pktlen, location_http, sizeof(location_http)-1) && dumb_memmem(pktdata, pktlen, connection_close, sizeof(connection_close)-1)) { return TRUE; } } return FALSE; } static int find_header_and_get_info(const char *pktdata, unsigned int pktlen, const char *hdrname, char **hdrnameaddr, char **hdrvalueaddr, unsigned int *hdrvaluelen) { char *data_addr_rn; char *hdr_begin; *hdrvaluelen = 0u; *hdrnameaddr = NULL; *hdrvalueaddr = NULL; /* Search for the header */ hdr_begin = dumb_memmem(pktdata, pktlen, hdrname, strlen(hdrname)); if (!hdr_begin) return FALSE; if (pktdata > hdr_begin) return FALSE; /* Set header address */ *hdrnameaddr = hdr_begin; *hdrvalueaddr = hdr_begin + strlen(hdrname); /* Search for header end (\r\n) */ data_addr_rn = dumb_memmem(*hdrvalueaddr, pktlen - (uintptr_t)(*hdrvalueaddr - pktdata), "\r\n", 2); if (data_addr_rn) { *hdrvaluelen = (uintptr_t)(data_addr_rn - *hdrvalueaddr); if (*hdrvaluelen >= 3 && *hdrvaluelen <= HOST_MAXLEN) return TRUE; } return FALSE; } /** * Very crude Server Name Indication (TLS ClientHello hostname) extractor. */ static int extract_sni(const char *pktdata, unsigned int pktlen, char **hostnameaddr, unsigned int *hostnamelen) { unsigned int ptr = 0; unsigned const char *d = (unsigned const char *)pktdata; unsigned const char *hnaddr = 0; int hnlen = 0; while (ptr + 8 < pktlen) { /* Search for specific Extensions sequence */ if (d[ptr] == '\0' && d[ptr+1] == '\0' && d[ptr+2] == '\0' && d[ptr+4] == '\0' && d[ptr+6] == '\0' && d[ptr+7] == '\0' && /* Check Extension length, Server Name list length * and Server Name length relations */ d[ptr+3] - d[ptr+5] == 2 && d[ptr+5] - d[ptr+8] == 3) { if (ptr + 8 + d[ptr+8] > pktlen) { return FALSE; } hnaddr = &d[ptr+9]; hnlen = d[ptr+8]; /* Limit hostname size up to 253 bytes */ if (hnlen < 3 || hnlen > HOST_MAXLEN) { return FALSE; } /* Validate that hostname has only ascii lowercase characters */ for (int i=0; i= '0' && hnaddr[i] <= '9') || (hnaddr[i] >= 'a' && hnaddr[i] <= 'z') || hnaddr[i] == '.' || hnaddr[i] == '-')) { return FALSE; } } *hostnameaddr = (char*)hnaddr; *hostnamelen = (unsigned int)hnlen; return TRUE; } ptr++; } return FALSE; } static inline void change_window_size(const PWINDIVERT_TCPHDR ppTcpHdr, unsigned int size) { if (size >= 1 && size <= 0xFFFFu) { ppTcpHdr->Window = htons((u_short)size); } } /* HTTP method end without trailing space */ static PVOID find_http_method_end(const char *pkt, unsigned int http_frag, int *is_fragmented) { unsigned int i; for (i = 0; i<(sizeof(http_methods) / sizeof(*http_methods)); i++) { if (memcmp(pkt, http_methods[i], strlen(http_methods[i])) == 0) { if (is_fragmented) *is_fragmented = 0; return (char*)pkt + strlen(http_methods[i]) - 1; } /* Try to find HTTP method in a second part of fragmented packet */ if ((http_frag == 1 || http_frag == 2) && memcmp(pkt, http_methods[i] + http_frag, strlen(http_methods[i]) - http_frag) == 0 ) { if (is_fragmented) *is_fragmented = 1; return (char*)pkt + strlen(http_methods[i]) - http_frag - 1; } } return NULL; } /** Fragment and send the packet. * * This function cuts off the end of the packet (step=0) or * the beginning of the packet (step=1) with fragment_size bytes. */ static void send_native_fragment(HANDLE w_filter, WINDIVERT_ADDRESS addr, char *packet, UINT packetLen, PVOID packet_data, UINT packet_dataLen, int packet_v4, int packet_v6, PWINDIVERT_IPHDR ppIpHdr, PWINDIVERT_IPV6HDR ppIpV6Hdr, PWINDIVERT_TCPHDR ppTcpHdr, unsigned int fragment_size, int step) { char packet_bak[MAX_PACKET_SIZE]; memcpy(packet_bak, packet, packetLen); UINT orig_packetLen = packetLen; if (fragment_size >= packet_dataLen) { if (step == 1) fragment_size = 0; else return; } if (step == 0) { if (packet_v4) ppIpHdr->Length = htons( ntohs(ppIpHdr->Length) - packet_dataLen + fragment_size ); else if (packet_v6) ppIpV6Hdr->Length = htons( ntohs(ppIpV6Hdr->Length) - packet_dataLen + fragment_size ); //printf("step0 (%d:%d), pp:%d, was:%d, now:%d\n", // packet_v4, packet_v6, ntohs(ppIpHdr->Length), // packetLen, packetLen - packet_dataLen + fragment_size); packetLen = packetLen - packet_dataLen + fragment_size; } else if (step == 1) { if (packet_v4) ppIpHdr->Length = htons( ntohs(ppIpHdr->Length) - fragment_size ); else if (packet_v6) ppIpV6Hdr->Length = htons( ntohs(ppIpV6Hdr->Length) - fragment_size ); //printf("step1 (%d:%d), pp:%d, was:%d, now:%d\n", packet_v4, packet_v6, ntohs(ppIpHdr->Length), // packetLen, packetLen - fragment_size); memmove(packet_data, (char*)packet_data + fragment_size, packet_dataLen - fragment_size); packetLen -= fragment_size; ppTcpHdr->SeqNum = htonl(ntohl(ppTcpHdr->SeqNum) + fragment_size); } addr.IPChecksum = 0; addr.TCPChecksum = 0; WinDivertHelperCalcChecksums( packet, packetLen, &addr, 0 ); WinDivertSend( w_filter, packet, packetLen, NULL, &addr ); memcpy(packet, packet_bak, orig_packetLen); //printf("Sent native fragment of %d size (step%d)\n", packetLen, step); } int main(int argc, char *argv[]) { static enum packet_type_e { unknown, ipv4_tcp, ipv4_tcp_data, ipv4_udp_data, ipv6_tcp, ipv6_tcp_data, ipv6_udp_data } packet_type; bool debug_exit = false; int i, should_reinject, should_recalc_checksum = 0; int sni_ok = 0; int opt; int packet_v4, packet_v6; HANDLE w_filter = NULL; WINDIVERT_ADDRESS addr; char packet[MAX_PACKET_SIZE]; PVOID packet_data; UINT packetLen; UINT packet_dataLen; PWINDIVERT_IPHDR ppIpHdr; PWINDIVERT_IPV6HDR ppIpV6Hdr; PWINDIVERT_TCPHDR ppTcpHdr; PWINDIVERT_UDPHDR ppUdpHdr; conntrack_info_t dns_conn_info; tcp_conntrack_info_t tcp_conn_info; int do_passivedpi = 0, do_block_quic = 0, do_fragment_http = 0, do_fragment_http_persistent = 0, do_fragment_http_persistent_nowait = 0, do_fragment_https = 0, do_host = 0, do_host_removespace = 0, do_additional_space = 0, do_http_allports = 0, do_host_mixedcase = 0, do_dnsv4_redirect = 0, do_dnsv6_redirect = 0, do_dns_verb = 0, do_tcp_verb = 0, do_blacklist = 0, do_allow_no_sni = 0, do_fragment_by_sni = 0, do_fake_packet = 0, do_auto_ttl = 0, do_wrong_chksum = 0, do_wrong_seq = 0, do_native_frag = 0, do_reverse_frag = 0; unsigned int http_fragment_size = 0; unsigned int https_fragment_size = 0; unsigned int current_fragment_size = 0; unsigned short max_payload_size = 0; BYTE should_send_fake = 0; BYTE ttl_of_fake_packet = 0; BYTE ttl_min_nhops = 0; BYTE auto_ttl_1 = 0; BYTE auto_ttl_2 = 0; BYTE auto_ttl_max = 0; uint32_t dnsv4_addr = 0; struct in6_addr dnsv6_addr = {0}; struct in6_addr dns_temp_addr = {0}; uint16_t dnsv4_port = htons(53); uint16_t dnsv6_port = htons(53); char *host_addr, *useragent_addr, *method_addr; unsigned int host_len, useragent_len; int http_req_fragmented; char *hdr_name_addr = NULL, *hdr_value_addr = NULL; unsigned int hdr_value_len; // Make sure to search DLLs only in safe path, not in current working dir. SetDllDirectory(""); SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE | BASE_SEARCH_PATH_PERMANENT); if (!running_from_service) { running_from_service = 1; if (service_register(argc, argv)) { /* We've been called as a service. Register service * and exit this thread. main() would be called from * service.c next time. * * Note that if service_register() succeedes it does * not return until the service is stopped. * That is why we should set running_from_service * before calling service_register and unset it * afterwards. */ return 0; } running_from_service = 0; } if (filter_string == NULL) filter_string = strdup(FILTER_STRING_TEMPLATE); if (filter_passive_string == NULL) filter_passive_string = strdup(FILTER_PASSIVE_STRING_TEMPLATE); printf( "GoodbyeDPI " GOODBYEDPI_VERSION ": Passive DPI blocker and Active DPI circumvention utility\n" "https://github.com/ValdikSS/GoodbyeDPI\n\n" ); if (argc == 1) { /* enable mode -9 by default */ do_fragment_http = do_fragment_https = 1; do_reverse_frag = do_native_frag = 1; http_fragment_size = https_fragment_size = 2; do_fragment_http_persistent = do_fragment_http_persistent_nowait = 1; do_fake_packet = 1; do_wrong_chksum = 1; do_wrong_seq = 1; do_block_quic = 1; max_payload_size = 1200; } while ((opt = getopt_long(argc, argv, "123456789pqrsaf:e:mwk:n", long_options, NULL)) != -1) { switch (opt) { case '1': do_passivedpi = do_host = do_host_removespace \ = do_fragment_http = do_fragment_https \ = do_fragment_http_persistent \ = do_fragment_http_persistent_nowait = 1; break; case '2': do_passivedpi = do_host = do_host_removespace \ = do_fragment_http = do_fragment_https \ = do_fragment_http_persistent \ = do_fragment_http_persistent_nowait = 1; https_fragment_size = 40u; break; case '3': do_passivedpi = do_host = do_host_removespace \ = do_fragment_https = 1; https_fragment_size = 40u; break; case '4': do_passivedpi = do_host = do_host_removespace = 1; break; case '5': do_fragment_http = do_fragment_https = 1; do_reverse_frag = do_native_frag = 1; http_fragment_size = https_fragment_size = 2; do_fragment_http_persistent = do_fragment_http_persistent_nowait = 1; do_fake_packet = 1; do_auto_ttl = 1; max_payload_size = 1200; break; case '6': do_fragment_http = do_fragment_https = 1; do_reverse_frag = do_native_frag = 1; http_fragment_size = https_fragment_size = 2; do_fragment_http_persistent = do_fragment_http_persistent_nowait = 1; do_fake_packet = 1; do_wrong_seq = 1; max_payload_size = 1200; break; case '9': // +7+8 do_block_quic = 1; // fall through case '8': // +7 do_wrong_seq = 1; // fall through case '7': do_fragment_http = do_fragment_https = 1; do_reverse_frag = do_native_frag = 1; http_fragment_size = https_fragment_size = 2; do_fragment_http_persistent = do_fragment_http_persistent_nowait = 1; do_fake_packet = 1; do_wrong_chksum = 1; max_payload_size = 1200; break; case 'p': do_passivedpi = 1; break; case 'q': do_block_quic = 1; break; case 'r': do_host = 1; break; case 's': do_host_removespace = 1; break; case 'a': do_additional_space = 1; do_host_removespace = 1; break; case 'm': do_host_mixedcase = 1; break; case 'f': do_fragment_http = 1; SET_HTTP_FRAGMENT_SIZE_OPTION(atousi(optarg, "Fragment size should be in range [0 - 0xFFFF]\n")); break; case 'k': do_fragment_http_persistent = 1; do_native_frag = 1; SET_HTTP_FRAGMENT_SIZE_OPTION(atousi(optarg, "Fragment size should be in range [0 - 0xFFFF]\n")); break; case 'n': do_fragment_http_persistent = 1; do_fragment_http_persistent_nowait = 1; do_native_frag = 1; break; case 'e': do_fragment_https = 1; https_fragment_size = atousi(optarg, "Fragment size should be in range [0 - 65535]\n"); break; case 'w': do_http_allports = 1; break; case 'z': // --port /* i is used as a temporary variable here */ i = atoi(optarg); if (i <= 0 || i > 65535) { printf("Port parameter error!\n"); exit(ERROR_PORT_BOUNDS); } if (i != 80 && i != 443) add_filter_str(IPPROTO_TCP, i); i = 0; break; case 'i': // --ip-id /* i is used as a temporary variable here */ i = atousi(optarg, "IP ID parameter error!\n"); add_ip_id_str(i); i = 0; break; case 'd': // --dns-addr if ((inet_pton(AF_INET, optarg, dns_temp_addr.s6_addr) == 1) && !do_dnsv4_redirect) { do_dnsv4_redirect = 1; if (inet_pton(AF_INET, optarg, &dnsv4_addr) != 1) { puts("DNS address parameter error!"); exit(ERROR_DNS_V4_ADDR); } add_filter_str(IPPROTO_UDP, 53); flush_dns_cache(); break; } puts("DNS address parameter error!"); exit(ERROR_DNS_V4_ADDR); break; case '!': // --dnsv6-addr if ((inet_pton(AF_INET6, optarg, dns_temp_addr.s6_addr) == 1) && !do_dnsv6_redirect) { do_dnsv6_redirect = 1; if (inet_pton(AF_INET6, optarg, dnsv6_addr.s6_addr) != 1) { puts("DNS address parameter error!"); exit(ERROR_DNS_V6_ADDR); } add_filter_str(IPPROTO_UDP, 53); flush_dns_cache(); break; } puts("DNS address parameter error!"); exit(ERROR_DNS_V6_ADDR); break; case 'g': // --dns-port if (!do_dnsv4_redirect) { puts("--dns-port should be used with --dns-addr!\n" "Make sure you use --dns-addr and pass it before " "--dns-port"); exit(ERROR_DNS_V4_PORT); } dnsv4_port = atousi(optarg, "DNS port parameter error!"); if (dnsv4_port != 53) { add_filter_str(IPPROTO_UDP, dnsv4_port); } dnsv4_port = htons(dnsv4_port); break; case '@': // --dnsv6-port if (!do_dnsv6_redirect) { puts("--dnsv6-port should be used with --dnsv6-addr!\n" "Make sure you use --dnsv6-addr and pass it before " "--dnsv6-port"); exit(ERROR_DNS_V6_PORT); } dnsv6_port = atousi(optarg, "DNS port parameter error!"); if (dnsv6_port != 53) { add_filter_str(IPPROTO_UDP, dnsv6_port); } dnsv6_port = htons(dnsv6_port); break; case 'v': do_dns_verb = 1; do_tcp_verb = 1; break; case 'b': // --blacklist do_blacklist = 1; if (!blackwhitelist_load_list(optarg)) { printf("Can't load blacklist from file!\n"); exit(ERROR_BLACKLIST_LOAD); } break; case ']': // --allow-no-sni do_allow_no_sni = 1; break; case '>': // --frag-by-sni do_fragment_by_sni = 1; break; case '$': // --set-ttl do_auto_ttl = auto_ttl_1 = auto_ttl_2 = auto_ttl_max = 0; do_fake_packet = 1; ttl_of_fake_packet = atoub(optarg, "Set TTL parameter error!"); break; case '[': // --min-ttl do_fake_packet = 1; ttl_min_nhops = atoub(optarg, "Set Minimum TTL number of hops parameter error!"); break; case '+': // --auto-ttl do_fake_packet = 1; do_auto_ttl = 1; if (!optarg && argv[optind] && argv[optind][0] != '-') optarg = argv[optind]; if (optarg) { char *autottl_copy = strdup(optarg); if (strchr(autottl_copy, '-')) { // token "-" found, start X-Y parser char *autottl_current = strtok(autottl_copy, "-"); auto_ttl_1 = atoub(autottl_current, "Set Auto TTL parameter error!"); autottl_current = strtok(NULL, "-"); if (!autottl_current) { puts("Set Auto TTL parameter error!"); exit(ERROR_AUTOTTL); } auto_ttl_2 = atoub(autottl_current, "Set Auto TTL parameter error!"); autottl_current = strtok(NULL, "-"); if (!autottl_current) { puts("Set Auto TTL parameter error!"); exit(ERROR_AUTOTTL); } auto_ttl_max = atoub(autottl_current, "Set Auto TTL parameter error!"); } else { // single digit parser auto_ttl_2 = atoub(optarg, "Set Auto TTL parameter error!"); auto_ttl_1 = auto_ttl_2; } free(autottl_copy); } break; case '%': // --wrong-chksum do_fake_packet = 1; do_wrong_chksum = 1; break; case ')': // --wrong-seq do_fake_packet = 1; do_wrong_seq = 1; break; case '*': // --native-frag do_native_frag = 1; do_fragment_http_persistent = 1; do_fragment_http_persistent_nowait = 1; break; case '(': // --reverse-frag do_reverse_frag = 1; do_native_frag = 1; do_fragment_http_persistent = 1; do_fragment_http_persistent_nowait = 1; break; case '|': // --max-payload if (!optarg && argv[optind] && argv[optind][0] != '-') optarg = argv[optind]; if (optarg) max_payload_size = atousi(optarg, "Max payload size parameter error!"); else max_payload_size = 1200; break; case 'u': // --fake-from-hex if (fake_load_from_hex(optarg)) { printf("WARNING: bad fake HEX value %s\n", optarg); } break; case '}': // --fake-with-sni if (fake_load_from_sni(optarg)) { printf("WARNING: bad domain name for SNI: %s\n", optarg); } break; case 'j': // --fake-gen if (fake_load_random(atoub(optarg, "Fake generator parameter error!"), 200)) { puts("WARNING: fake generator has failed!"); } break; case 't': // --fake-resend fakes_resend = atoub(optarg, "Fake resend parameter error!"); if (fakes_resend == 1) puts("WARNING: fake-resend is 1, no resending is in place!"); else if (!fakes_resend) puts("WARNING: fake-resend is 0, fake packet mode is disabled!"); else if (fakes_resend > 100) puts("WARNING: fake-resend value is a little too high, don't you think?"); break; case 'x': // --debug-exit debug_exit = true; break; default: puts("Usage: goodbyedpi.exe [OPTION...]\n" " -p block passive DPI\n" " -q block QUIC/HTTP3\n" " -r replace Host with hoSt\n" " -s remove space between host header and its value\n" " -a additional space between Method and Request-URI (enables -s, may break sites)\n" " -m mix Host header case (test.com -> tEsT.cOm)\n" " -f set HTTP fragmentation to value\n" " -k enable HTTP persistent (keep-alive) fragmentation and set it to value\n" " -n do not wait for first segment ACK when -k is enabled\n" " -e set HTTPS fragmentation to value\n" " -w try to find and parse HTTP traffic on all processed ports (not only on port 80)\n" " --port additional TCP port to perform fragmentation on (and HTTP tricks with -w)\n" " --ip-id handle additional IP ID (decimal, drop redirects and TCP RSTs with this ID).\n" " --dns-addr redirect UDPv4 DNS requests to the supplied IPv4 address (experimental)\n" " --dns-port redirect UDPv4 DNS requests to the supplied port (53 by default)\n" " --dnsv6-addr redirect UDPv6 DNS requests to the supplied IPv6 address (experimental)\n" " --dnsv6-port redirect UDPv6 DNS requests to the supplied port (53 by default)\n" " --dns-verb print verbose DNS redirection messages\n" " --blacklist perform circumvention tricks only to host names and subdomains from\n" " supplied text file (HTTP Host/TLS SNI).\n" " This option can be supplied multiple times.\n" " --allow-no-sni perform circumvention if TLS SNI can't be detected with --blacklist enabled.\n" " --frag-by-sni if SNI is detected in TLS packet, fragment the packet right before SNI value.\n" " --set-ttl activate Fake Request Mode and send it with supplied TTL value.\n" " DANGEROUS! May break websites in unexpected ways. Use with care (or --blacklist).\n" " --auto-ttl [a1-a2-m] activate Fake Request Mode, automatically detect TTL and decrease\n" " it based on a distance. If the distance is shorter than a2, TTL is decreased\n" " by a2. If it's longer, (a1; a2) scale is used with the distance as a weight.\n" " If the resulting TTL is more than m(ax), set it to m.\n" " Default (if set): --auto-ttl 1-4-10. Also sets --min-ttl 3.\n" " DANGEROUS! May break websites in unexpected ways. Use with care (or --blacklist).\n" " --min-ttl minimum TTL distance (128/64 - TTL) for which to send Fake Request\n" " in --set-ttl and --auto-ttl modes.\n" " --wrong-chksum activate Fake Request Mode and send it with incorrect TCP checksum.\n" " May not work in a VM or with some routers, but is safer than set-ttl.\n" " Could be combined with --set-ttl\n" " --wrong-seq activate Fake Request Mode and send it with TCP SEQ/ACK in the past.\n" " --native-frag fragment (split) the packets by sending them in smaller packets, without\n" " shrinking the Window Size. Works faster (does not slow down the connection)\n" " and better.\n" " --reverse-frag fragment (split) the packets just as --native-frag, but send them in the\n" " reversed order. Works with the websites which could not handle segmented\n" " HTTPS TLS ClientHello (because they receive the TCP flow \"combined\").\n" " --fake-from-hex Load fake packets for Fake Request Mode from HEX values (like 1234abcDEF).\n" " This option can be supplied multiple times, in this case each fake packet\n" " would be sent on every request in the command line argument order.\n" " --fake-with-sni Generate fake packets for Fake Request Mode with given SNI domain name.\n" " The packets mimic Mozilla Firefox 130 TLS ClientHello packet\n" " (with random generated fake SessionID, key shares and ECH grease).\n" " Can be supplied multiple times for multiple fake packets.\n" " --fake-gen Generate random-filled fake packets for Fake Request Mode, value of them\n" " (up to 30).\n" " --fake-resend Send each fake packet value number of times.\n" " Default: 1 (send each packet once).\n" " --max-payload [value] packets with TCP payload data more than [value] won't be processed.\n" " Use this option to reduce CPU usage by skipping huge amount of data\n" " (like file transfers) in already established sessions.\n" " May skip some huge HTTP requests from being processed.\n" " Default (if set): --max-payload 1200.\n" "\n"); puts("LEGACY modesets:\n" " -1 -p -r -s -f 2 -k 2 -n -e 2 (most compatible mode)\n" " -2 -p -r -s -f 2 -k 2 -n -e 40 (better speed for HTTPS yet still compatible)\n" " -3 -p -r -s -e 40 (better speed for HTTP and HTTPS)\n" " -4 -p -r -s (best speed)" "\n" "Modern modesets (more stable, more compatible, faster):\n" " -5 -f 2 -e 2 --auto-ttl --reverse-frag --max-payload\n" " -6 -f 2 -e 2 --wrong-seq --reverse-frag --max-payload\n" " -7 -f 2 -e 2 --wrong-chksum --reverse-frag --max-payload\n" " -8 -f 2 -e 2 --wrong-seq --wrong-chksum --reverse-frag --max-payload\n" " -9 -f 2 -e 2 --wrong-seq --wrong-chksum --reverse-frag --max-payload -q (this is the default)\n\n" "Note: combination of --wrong-seq and --wrong-chksum generates two different fake packets.\n" ); exit(ERROR_DEFAULT); } } if (!http_fragment_size) http_fragment_size = 2; if (!https_fragment_size) https_fragment_size = 2; if (!auto_ttl_1) auto_ttl_1 = 1; if (!auto_ttl_2) auto_ttl_2 = 4; if (do_auto_ttl) { if (!ttl_min_nhops) ttl_min_nhops = 3; if (!auto_ttl_max) auto_ttl_max = 10; } printf("Block passive: %d\n" /* 1 */ "Block QUIC/HTTP3: %d\n" /* 1 */ "Fragment HTTP: %u\n" /* 2 */ "Fragment persistent HTTP: %u\n" /* 3 */ "Fragment HTTPS: %u\n" /* 4 */ "Fragment by SNI: %u\n" /* 5 */ "Native fragmentation (splitting): %d\n" /* 6 */ "Fragments sending in reverse: %d\n" /* 7 */ "hoSt: %d\n" /* 8 */ "Host no space: %d\n" /* 9 */ "Additional space: %d\n" /* 10 */ "Mix Host: %d\n" /* 11 */ "HTTP AllPorts: %d\n" /* 12 */ "HTTP Persistent Nowait: %d\n" /* 13 */ "DNS redirect: %d\n" /* 14 */ "DNSv6 redirect: %d\n" /* 15 */ "Allow missing SNI: %d\n" /* 16 */ "Fake requests, TTL: %s (fixed: %hu, auto: %hu-%hu-%hu, min distance: %hu)\n" /* 17 */ "Fake requests, wrong checksum: %d\n" /* 18 */ "Fake requests, wrong SEQ/ACK: %d\n" /* 19 */ "Fake requests, custom payloads: %d\n" /* 20 */ "Fake requests, resend: %d\n" /* 21 */ "Max payload size: %hu\n", /* 22 */ do_passivedpi, do_block_quic, /* 1 */ (do_fragment_http ? http_fragment_size : 0), /* 2 */ (do_fragment_http_persistent ? http_fragment_size : 0),/* 3 */ (do_fragment_https ? https_fragment_size : 0), /* 4 */ do_fragment_by_sni, /* 5 */ do_native_frag, /* 6 */ do_reverse_frag, /* 7 */ do_host, /* 8 */ do_host_removespace, /* 9 */ do_additional_space, /* 10 */ do_host_mixedcase, /* 11 */ do_http_allports, /* 12 */ do_fragment_http_persistent_nowait, /* 13 */ do_dnsv4_redirect, /* 14 */ do_dnsv6_redirect, /* 15 */ do_allow_no_sni, /* 16 */ do_auto_ttl ? "auto" : (do_fake_packet ? "fixed" : "disabled"), /* 17 */ ttl_of_fake_packet, do_auto_ttl ? auto_ttl_1 : 0, do_auto_ttl ? auto_ttl_2 : 0, do_auto_ttl ? auto_ttl_max : 0, ttl_min_nhops, do_wrong_chksum, /* 18 */ do_wrong_seq, /* 19 */ fakes_count, /* 20 */ fakes_resend, /* 21 */ max_payload_size /* 22 */ ); if (do_fragment_http && http_fragment_size > 2 && !do_native_frag) { puts("\nWARNING: HTTP fragmentation values > 2 are not fully compatible " "with other options. Please use values <= 2 or disable HTTP fragmentation " "completely."); } if (do_native_frag && !(do_fragment_http || do_fragment_https)) { puts("\nERROR: Native fragmentation is enabled but fragment sizes are not set.\n" "Fragmentation has no effect."); die(); } if (max_payload_size) add_maxpayloadsize_str(max_payload_size); finalize_filter_strings(); puts("\nOpening filter"); filter_num = 0; if (do_passivedpi) { /* IPv4 only filter for inbound RST packets with ID [0x0; 0xF] */ filters[filter_num] = init( filter_passive_string, WINDIVERT_FLAG_DROP); if (filters[filter_num] == NULL) die(); filter_num++; } if (do_block_quic) { filters[filter_num] = init( FILTER_PASSIVE_BLOCK_QUIC, WINDIVERT_FLAG_DROP); if (filters[filter_num] == NULL) die(); filter_num++; } /* * IPv4 & IPv6 filter for inbound HTTP redirection packets and * active DPI circumvention */ filters[filter_num] = init(filter_string, 0); w_filter = filters[filter_num]; filter_num++; for (i = 0; i < filter_num; i++) { if (filters[i] == NULL) die(); } if (debug_exit) { printf("Debug Exit\n"); exit(EXIT_SUCCESS); } printf("Filter activated, GoodbyeDPI is now running!\n"); signal(SIGINT, sigint_handler); while (1) { if (WinDivertRecv(w_filter, packet, sizeof(packet), &packetLen, &addr)) { debug("Got %s packet, len=%d!\n", addr.Outbound ? "outbound" : "inbound", packetLen); should_reinject = 1; should_recalc_checksum = 0; sni_ok = 0; ppIpHdr = (PWINDIVERT_IPHDR)NULL; ppIpV6Hdr = (PWINDIVERT_IPV6HDR)NULL; ppTcpHdr = (PWINDIVERT_TCPHDR)NULL; ppUdpHdr = (PWINDIVERT_UDPHDR)NULL; packet_v4 = packet_v6 = 0; packet_type = unknown; // Parse network packet and set it's type if (WinDivertHelperParsePacket(packet, packetLen, &ppIpHdr, &ppIpV6Hdr, NULL, NULL, NULL, &ppTcpHdr, &ppUdpHdr, &packet_data, &packet_dataLen, NULL, NULL)) { if (ppIpHdr) { packet_v4 = 1; if (ppTcpHdr) { packet_type = ipv4_tcp; if (packet_data) { packet_type = ipv4_tcp_data; } } else if (ppUdpHdr && packet_data) { packet_type = ipv4_udp_data; } } else if (ppIpV6Hdr) { packet_v6 = 1; if (ppTcpHdr) { packet_type = ipv6_tcp; if (packet_data) { packet_type = ipv6_tcp_data; } } else if (ppUdpHdr && packet_data) { packet_type = ipv6_udp_data; } } } debug("packet_type: %d, packet_v4: %d, packet_v6: %d\n", packet_type, packet_v4, packet_v6); if (packet_type == ipv4_tcp_data || packet_type == ipv6_tcp_data) { //printf("Got parsed packet, len=%d!\n", packet_dataLen); /* Got a TCP packet WITH DATA */ /* Handle INBOUND packet with data and find HTTP REDIRECT in there */ if (!addr.Outbound && packet_dataLen > 16) { /* If INBOUND packet with DATA (tcp.Ack) */ /* Drop packets from filter with HTTP 30x Redirect */ if (do_passivedpi && is_passivedpi_redirect(packet_data, packet_dataLen)) { if (packet_v4) { //printf("Dropping HTTP Redirect packet!\n"); should_reinject = 0; } else if (packet_v6 && WINDIVERT_IPV6HDR_GET_FLOWLABEL(ppIpV6Hdr) == 0x0) { /* Contrary to IPv4 where we get only packets with IP ID 0x0-0xF, * for IPv6 we got all the incoming data packets since we can't * filter them in a driver. * * Handle only IPv6 Flow Label == 0x0 for now */ //printf("Dropping HTTP Redirect packet!\n"); should_reinject = 0; } } } /* Handle OUTBOUND packet on port 443, search for something that resembles * TLS handshake, send fake request. */ else if (addr.Outbound && ((do_fragment_https ? packet_dataLen == https_fragment_size : 0) || packet_dataLen > 16) && ppTcpHdr->DstPort != htons(80) && (do_fake_packet || do_native_frag) ) { /** * In case of Window Size fragmentation=2, we'll receive only 2 byte packet. * But if the packet is more than 2 bytes, check ClientHello byte. */ if ((packet_dataLen == 2 && memcmp(packet_data, "\x16\x03", 2) == 0) || (packet_dataLen >= 3 && ( memcmp(packet_data, "\x16\x03\x01", 3) == 0 || memcmp(packet_data, "\x16\x03\x03", 3) == 0 ))) { if (do_blacklist || do_fragment_by_sni) { sni_ok = extract_sni(packet_data, packet_dataLen, &host_addr, &host_len); } if ( (do_blacklist && sni_ok && blackwhitelist_check_hostname(host_addr, host_len) ) || (do_blacklist && !sni_ok && do_allow_no_sni) || (!do_blacklist) ) { #ifdef DEBUG char lsni[HOST_MAXLEN + 1] = {0}; extract_sni(packet_data, packet_dataLen, &host_addr, &host_len); memcpy(lsni, host_addr, host_len); printf("Blocked HTTPS website SNI: %s\n", lsni); #endif if (do_fake_packet) { TCP_HANDLE_OUTGOING_FAKE_PACKET(send_fake_https_request); } if (do_native_frag) { // Signal for native fragmentation code handler should_recalc_checksum = 1; } } } } /* Handle OUTBOUND packet on port 80, search for Host header */ else if (addr.Outbound && packet_dataLen > 16 && (do_http_allports ? 1 : (ppTcpHdr->DstPort == htons(80))) && find_http_method_end(packet_data, (do_fragment_http ? http_fragment_size : 0u), &http_req_fragmented) && (do_host || do_host_removespace || do_host_mixedcase || do_fragment_http_persistent || do_fake_packet)) { /* Find Host header */ if (find_header_and_get_info(packet_data, packet_dataLen, http_host_find, &hdr_name_addr, &hdr_value_addr, &hdr_value_len) && hdr_value_len > 0 && hdr_value_len <= HOST_MAXLEN && (do_blacklist ? blackwhitelist_check_hostname(hdr_value_addr, hdr_value_len) : 1)) { host_addr = hdr_value_addr; host_len = hdr_value_len; #ifdef DEBUG char lhost[HOST_MAXLEN + 1] = {0}; memcpy(lhost, host_addr, host_len); printf("Blocked HTTP website Host: %s\n", lhost); #endif if (do_native_frag) { // Signal for native fragmentation code handler should_recalc_checksum = 1; } if (do_fake_packet) { TCP_HANDLE_OUTGOING_FAKE_PACKET(send_fake_http_request); } if (do_host_mixedcase) { mix_case(host_addr, host_len); should_recalc_checksum = 1; } if (do_host) { /* Replace "Host: " with "hoSt: " */ memcpy(hdr_name_addr, http_host_replace, strlen(http_host_replace)); should_recalc_checksum = 1; //printf("Replaced Host header!\n"); } /* If removing space between host header and its value * and adding additional space between Method and Request-URI */ if (do_additional_space && do_host_removespace) { /* End of "Host:" without trailing space */ method_addr = find_http_method_end(packet_data, (do_fragment_http ? http_fragment_size : 0), NULL); if (method_addr) { memmove(method_addr + 1, method_addr, (size_t)(host_addr - method_addr - 1)); should_recalc_checksum = 1; } } /* If just removing space between host header and its value */ else if (do_host_removespace) { if (find_header_and_get_info(packet_data, packet_dataLen, http_useragent_find, &hdr_name_addr, &hdr_value_addr, &hdr_value_len)) { useragent_addr = hdr_value_addr; useragent_len = hdr_value_len; /* We move Host header value by one byte to the left and then * "insert" stolen space to the end of User-Agent value because * some web servers are not tolerant to additional space in the * end of Host header. * * Nothing is done if User-Agent header is missing. */ if (useragent_addr && useragent_len > 0) { /* useragent_addr is in the beginning of User-Agent value */ if (useragent_addr > host_addr) { /* Move one byte to the LEFT from "Host:" * to the end of User-Agent */ memmove(host_addr - 1, host_addr, (size_t)(useragent_addr + useragent_len - host_addr)); host_addr -= 1; /* Put space in the end of User-Agent header */ *(char*)((unsigned char*)useragent_addr + useragent_len - 1) = ' '; should_recalc_checksum = 1; //printf("Replaced Host header!\n"); } else { /* User-Agent goes BEFORE Host header */ /* Move one byte to the RIGHT from the end of User-Agent * to the "Host:" */ memmove(useragent_addr + useragent_len + 1, useragent_addr + useragent_len, (size_t)(host_addr - 1 - (useragent_addr + useragent_len))); /* Put space in the end of User-Agent header */ *(char*)((unsigned char*)useragent_addr + useragent_len) = ' '; should_recalc_checksum = 1; //printf("Replaced Host header!\n"); } } /* if (host_len <= HOST_MAXLEN && useragent_addr) */ } /* if (find_header_and_get_info http_useragent) */ } /* else if (do_host_removespace) */ } /* if (find_header_and_get_info http_host) */ } /* Handle OUTBOUND packet with data */ /* * should_recalc_checksum mean we have detected a packet to handle and * modified it in some way. * Handle native fragmentation here, incl. sending the packet. */ if (should_reinject && should_recalc_checksum && do_native_frag) { current_fragment_size = 0; if (do_fragment_http && ppTcpHdr->DstPort == htons(80)) { current_fragment_size = http_fragment_size; } else if (do_fragment_https && ppTcpHdr->DstPort != htons(80)) { if (do_fragment_by_sni && sni_ok) { current_fragment_size = (void*)host_addr - packet_data; } else { current_fragment_size = https_fragment_size; } } if (current_fragment_size) { send_native_fragment(w_filter, addr, packet, packetLen, packet_data, packet_dataLen,packet_v4, packet_v6, ppIpHdr, ppIpV6Hdr, ppTcpHdr, current_fragment_size, do_reverse_frag); send_native_fragment(w_filter, addr, packet, packetLen, packet_data, packet_dataLen,packet_v4, packet_v6, ppIpHdr, ppIpV6Hdr, ppTcpHdr, current_fragment_size, !do_reverse_frag); continue; } } } /* Handle TCP packet with data */ /* Else if we got TCP packet without data */ else if (packet_type == ipv4_tcp || packet_type == ipv6_tcp) { /* If we got INBOUND SYN+ACK packet */ if (!addr.Outbound && ppTcpHdr->Syn == 1 && ppTcpHdr->Ack == 1) { //printf("Changing Window Size!\n"); /* * Window Size is changed even if do_fragment_http_persistent * is enabled as there could be non-HTTP data on port 80 */ if (do_fake_packet && (do_auto_ttl || ttl_min_nhops)) { if (!((packet_v4 && tcp_handle_incoming(&ppIpHdr->SrcAddr, &ppIpHdr->DstAddr, ppTcpHdr->SrcPort, ppTcpHdr->DstPort, 0, ppIpHdr->TTL)) || (packet_v6 && tcp_handle_incoming((uint32_t*)&ppIpV6Hdr->SrcAddr, (uint32_t*)&ppIpV6Hdr->DstAddr, ppTcpHdr->SrcPort, ppTcpHdr->DstPort, 1, ppIpV6Hdr->HopLimit)))) { if (do_tcp_verb) puts("[TCP WARN] Can't add TCP connection record."); } } if (!do_native_frag) { if (do_fragment_http && ppTcpHdr->SrcPort == htons(80)) { change_window_size(ppTcpHdr, http_fragment_size); should_recalc_checksum = 1; } else if (do_fragment_https && ppTcpHdr->SrcPort != htons(80)) { change_window_size(ppTcpHdr, https_fragment_size); should_recalc_checksum = 1; } } } } /* Else if we got UDP packet with data */ else if ((do_dnsv4_redirect && (packet_type == ipv4_udp_data)) || (do_dnsv6_redirect && (packet_type == ipv6_udp_data))) { if (!addr.Outbound) { if ((packet_v4 && dns_handle_incoming(&ppIpHdr->DstAddr, ppUdpHdr->DstPort, packet_data, packet_dataLen, &dns_conn_info, 0)) || (packet_v6 && dns_handle_incoming(ppIpV6Hdr->DstAddr, ppUdpHdr->DstPort, packet_data, packet_dataLen, &dns_conn_info, 1))) { /* Changing source IP and port to the values * from DNS conntrack */ if (packet_v4) ppIpHdr->SrcAddr = dns_conn_info.dstip[0]; else if (packet_v6) ipv6_copy_addr(ppIpV6Hdr->SrcAddr, dns_conn_info.dstip); ppUdpHdr->DstPort = dns_conn_info.srcport; ppUdpHdr->SrcPort = dns_conn_info.dstport; should_recalc_checksum = 1; } else { if (dns_is_dns_packet(packet_data, packet_dataLen, 0)) should_reinject = 0; if (do_dns_verb && !should_reinject) { printf("[DNS] Error handling incoming packet: srcport = %hu, dstport = %hu\n", ntohs(ppUdpHdr->SrcPort), ntohs(ppUdpHdr->DstPort)); } } } else if (addr.Outbound) { if ((packet_v4 && dns_handle_outgoing(&ppIpHdr->SrcAddr, ppUdpHdr->SrcPort, &ppIpHdr->DstAddr, ppUdpHdr->DstPort, packet_data, packet_dataLen, 0)) || (packet_v6 && dns_handle_outgoing(ppIpV6Hdr->SrcAddr, ppUdpHdr->SrcPort, ppIpV6Hdr->DstAddr, ppUdpHdr->DstPort, packet_data, packet_dataLen, 1))) { /* Changing destination IP and port to the values * from configuration */ if (packet_v4) { ppIpHdr->DstAddr = dnsv4_addr; ppUdpHdr->DstPort = dnsv4_port; } else if (packet_v6) { ipv6_copy_addr(ppIpV6Hdr->DstAddr, (uint32_t*)dnsv6_addr.s6_addr); ppUdpHdr->DstPort = dnsv6_port; } should_recalc_checksum = 1; } else { if (dns_is_dns_packet(packet_data, packet_dataLen, 1)) should_reinject = 0; if (do_dns_verb && !should_reinject) { printf("[DNS] Error handling outgoing packet: srcport = %hu, dstport = %hu\n", ntohs(ppUdpHdr->SrcPort), ntohs(ppUdpHdr->DstPort)); } } } } if (should_reinject) { //printf("Re-injecting!\n"); if (should_recalc_checksum) { WinDivertHelperCalcChecksums(packet, packetLen, &addr, (UINT64)0LL); } WinDivertSend(w_filter, packet, packetLen, NULL, &addr); } } else { // error, ignore if (!exiting) printf("Error receiving packet!\n"); break; } } } ================================================ FILE: src/goodbyedpi.exe.manifest ================================================ GoodbyeDPI ================================================ FILE: src/goodbyedpi.h ================================================ #define HOST_MAXLEN 253 #define MAX_PACKET_SIZE 9016 #ifndef DEBUG #define debug(...) do {} while (0) #else #define debug(...) printf(__VA_ARGS__) #endif int main(int argc, char *argv[]); void deinit_all(); ================================================ FILE: src/service.c ================================================ #include #include #include "goodbyedpi.h" #include "service.h" #define SERVICE_NAME "GoodbyeDPI" static SERVICE_STATUS ServiceStatus; static SERVICE_STATUS_HANDLE hStatus; static int service_argc = 0; static char **service_argv = NULL; int service_register(int argc, char *argv[]) { int i, ret; SERVICE_TABLE_ENTRY ServiceTable[] = { {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)service_main}, {NULL, NULL} }; /* * Save argc & argv as service_main is called with different * arguments, which are passed from "start" command, not * from the program command line. * We don't need this behaviour. * * Note that if StartServiceCtrlDispatcher() succeedes * it does not return until the service is stopped, * so we should copy all arguments first and then * handle the failure. */ if (!service_argc && !service_argv) { service_argc = argc; service_argv = calloc((size_t)(argc + 1), sizeof(void*)); for (i = 0; i < argc; i++) { service_argv[i] = strdup(argv[i]); } } ret = StartServiceCtrlDispatcher(ServiceTable); if (service_argc && service_argv) { for (i = 0; i < service_argc; i++) { free(service_argv[i]); } free(service_argv); } return ret; } void service_main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) { ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ServiceStatus.dwCurrentState = SERVICE_RUNNING; ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwServiceSpecificExitCode = 0; ServiceStatus.dwCheckPoint = 1; ServiceStatus.dwWaitHint = 0; hStatus = RegisterServiceCtrlHandler( SERVICE_NAME, (LPHANDLER_FUNCTION)service_controlhandler); if (hStatus == (SERVICE_STATUS_HANDLE)0) { // Registering Control Handler failed return; } SetServiceStatus(hStatus, &ServiceStatus); // Calling main with saved argc & argv ServiceStatus.dwWin32ExitCode = (DWORD)main(service_argc, service_argv); ServiceStatus.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(hStatus, &ServiceStatus); return; } // Control handler function void service_controlhandler(DWORD request) { switch(request) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: deinit_all(); ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCurrentState = SERVICE_STOPPED; default: break; } // Report current status SetServiceStatus(hStatus, &ServiceStatus); return; } ================================================ FILE: src/service.h ================================================ int service_register(); void service_main(int argc, char *argv[]); void service_controlhandler(DWORD request); ================================================ FILE: src/ttltrack.c ================================================ /** * TCP (TTL) Connection Tracker for GoodbyeDPI * * Monitors SYN/ACK only, to extract the TTL value of the remote server. * */ #include #include #include #include #include "goodbyedpi.h" #include "ttltrack.h" #include "utils/uthash.h" /* key ('4' for IPv4 or '6' for IPv6 + srcip[16] + dstip[16] + srcport[2] + dstport[2]) */ #define TCP_CONNRECORD_KEY_LEN 37 #define TCP_CLEANUP_INTERVAL_SEC 30 /* HACK! * uthash uses strlen() for HASH_FIND_STR. * We have null bytes in our key, so we can't use strlen() * And since it's always TCP_CONNRECORD_KEY_LEN bytes long, * we don't need to use any string function to determine length. */ #undef uthash_strlen #define uthash_strlen(s) TCP_CONNRECORD_KEY_LEN typedef struct tcp_connrecord { /* key ('4' for IPv4 or '6' for IPv6 + srcip[16] + dstip[16] + srcport[2] + dstport[2]) */ char key[TCP_CONNRECORD_KEY_LEN]; time_t time; /* time when this record was added */ uint16_t ttl; UT_hash_handle hh; /* makes this structure hashable */ } tcp_connrecord_t; static time_t last_cleanup = 0; static tcp_connrecord_t *conntrack = NULL; inline static void fill_key_data(char *key, const uint8_t is_ipv6, const uint32_t srcip[4], const uint32_t dstip[4], const uint16_t srcport, const uint16_t dstport) { unsigned int offset = 0; if (is_ipv6) { *(uint8_t*)(key) = '6'; offset += sizeof(uint8_t); ipv6_copy_addr((uint32_t*)(key + offset), srcip); offset += sizeof(uint32_t) * 4; ipv6_copy_addr((uint32_t*)(key + offset), dstip); offset += sizeof(uint32_t) * 4; } else { *(uint8_t*)(key) = '4'; offset += sizeof(uint8_t); ipv4_copy_addr((uint32_t*)(key + offset), srcip); offset += sizeof(uint32_t) * 4; ipv4_copy_addr((uint32_t*)(key + offset), dstip); offset += sizeof(uint32_t) * 4; } *(uint16_t*)(key + offset) = srcport; offset += sizeof(srcport); *(uint16_t*)(key + offset) = dstport; offset += sizeof(dstport); } inline static void fill_data_from_key(uint8_t *is_ipv6, uint32_t srcip[4], uint32_t dstip[4], uint16_t *srcport, uint16_t *dstport, const char *key) { unsigned int offset = 0; if (key[0] == '6') { *is_ipv6 = 1; offset += sizeof(uint8_t); ipv6_copy_addr(srcip, (uint32_t*)(key + offset)); offset += sizeof(uint32_t) * 4; ipv6_copy_addr(dstip, (uint32_t*)(key + offset)); offset += sizeof(uint32_t) * 4; } else { *is_ipv6 = 0; offset += sizeof(uint8_t); ipv4_copy_addr(srcip, (uint32_t*)(key + offset)); offset += sizeof(uint32_t) * 4; ipv4_copy_addr(dstip, (uint32_t*)(key + offset)); offset += sizeof(uint32_t) * 4; } *srcport = *(uint16_t*)(key + offset); offset += sizeof(*srcport); *dstport = *(uint16_t*)(key + offset); offset += sizeof(*dstport); } inline static void construct_key(const uint32_t srcip[4], const uint32_t dstip[4], const uint16_t srcport, const uint16_t dstport, char *key, const uint8_t is_ipv6) { debug("Construct key enter\n"); if (key) { debug("Constructing key\n"); fill_key_data(key, is_ipv6, srcip, dstip, srcport, dstport); } debug("Construct key end\n"); } inline static void deconstruct_key(const char *key, const tcp_connrecord_t *connrecord, tcp_conntrack_info_t *conn_info) { debug("Deconstruct key enter\n"); if (key && conn_info) { debug("Deconstructing key\n"); fill_data_from_key(&conn_info->is_ipv6, conn_info->srcip, conn_info->dstip, &conn_info->srcport, &conn_info->dstport, key); conn_info->ttl = connrecord->ttl; } debug("Deconstruct key end\n"); } static int check_get_tcp_conntrack_key(const char *key, tcp_connrecord_t **connrecord) { tcp_connrecord_t *tmp_connrecord = NULL; if (!conntrack) return FALSE; HASH_FIND_STR(conntrack, key, tmp_connrecord); if (tmp_connrecord) { if (connrecord) *connrecord = tmp_connrecord; debug("check_get_tcp_conntrack_key found key\n"); return TRUE; } debug("check_get_tcp_conntrack_key key not found\n"); return FALSE; } static int add_tcp_conntrack(const uint32_t srcip[4], const uint32_t dstip[4], const uint16_t srcport, const uint16_t dstport, const uint8_t is_ipv6, const uint8_t ttl ) { if (!(srcip && srcport && dstip && dstport)) return FALSE; tcp_connrecord_t *tmp_connrecord = malloc(sizeof(tcp_connrecord_t)); construct_key(srcip, dstip, srcport, dstport, tmp_connrecord->key, is_ipv6); if (!check_get_tcp_conntrack_key(tmp_connrecord->key, NULL)) { tmp_connrecord->time = time(NULL); tmp_connrecord->ttl = ttl; HASH_ADD_STR(conntrack, key, tmp_connrecord); debug("Added TCP conntrack %u:%hu - %u:%hu\n", srcip[0], ntohs(srcport), dstip[0], ntohs(dstport)); return TRUE; } debug("Not added TCP conntrack %u:%hu - %u:%hu\n", srcip[0], ntohs(srcport), dstip[0], ntohs(dstport)); free(tmp_connrecord); return FALSE; } static void tcp_cleanup() { tcp_connrecord_t *tmp_connrecord, *tmp_connrecord2 = NULL; if (last_cleanup == 0) { last_cleanup = time(NULL); return; } if (difftime(time(NULL), last_cleanup) >= TCP_CLEANUP_INTERVAL_SEC) { last_cleanup = time(NULL); HASH_ITER(hh, conntrack, tmp_connrecord, tmp_connrecord2) { if (difftime(last_cleanup, tmp_connrecord->time) >= TCP_CLEANUP_INTERVAL_SEC) { HASH_DEL(conntrack, tmp_connrecord); free(tmp_connrecord); } } } } int tcp_handle_incoming(uint32_t srcip[4], uint32_t dstip[4], uint16_t srcport, uint16_t dstport, uint8_t is_ipv6, uint8_t ttl) { tcp_cleanup(); debug("trying to add TCP srcport = %hu, dstport = %hu\n", ntohs(srcport), ntohs(dstport)); return add_tcp_conntrack(srcip, dstip, srcport, dstport, is_ipv6, ttl); debug("____tcp_handle_incoming FALSE: srcport = %hu, dstport = %hu\n", ntohs(srcport), ntohs(dstport)); return FALSE; } int tcp_handle_outgoing(uint32_t srcip[4], uint32_t dstip[4], uint16_t srcport, uint16_t dstport, tcp_conntrack_info_t *conn_info, uint8_t is_ipv6) { char key[TCP_CONNRECORD_KEY_LEN]; tcp_connrecord_t *tmp_connrecord = NULL; if (!conn_info) return FALSE; tcp_cleanup(); construct_key(dstip, srcip, dstport, srcport, key, is_ipv6); if (check_get_tcp_conntrack_key(key, &tmp_connrecord) && tmp_connrecord) { /* Connection exists in conntrack, moving on */ deconstruct_key(key, tmp_connrecord, conn_info); HASH_DEL(conntrack, tmp_connrecord); free(tmp_connrecord); debug("____tcp_handle_outgoing TRUE: srcport = %hu\n", ntohs(srcport)); return TRUE; } debug("____tcp_handle_outgoing FALSE: srcport = %hu\n", ntohs(srcport)); return FALSE; } int tcp_get_auto_ttl(const uint8_t ttl, const uint8_t autottl1, const uint8_t autottl2, const uint8_t minhops, const uint8_t maxttl) { uint8_t nhops = 0; uint8_t ttl_of_fake_packet = 0; if (ttl > 98 && ttl < 128) { nhops = 128 - ttl; } else if (ttl > 34 && ttl < 64) { nhops = 64 - ttl; } else { return 0; } if (nhops <= autottl1 || nhops < minhops) { return 0; } ttl_of_fake_packet = nhops - autottl2; if (ttl_of_fake_packet < autottl2 && nhops <= 9) { ttl_of_fake_packet = nhops - autottl1 - trunc((autottl2 - autottl1) * ((float)nhops/10)); } if (maxttl && ttl_of_fake_packet > maxttl) { ttl_of_fake_packet = maxttl; } return ttl_of_fake_packet; } ================================================ FILE: src/ttltrack.h ================================================ #ifndef _TTLTRACK_H #define _TTLTRACK_H #include #include "dnsredir.h" typedef struct tcp_conntrack_info { uint8_t is_ipv6; uint8_t ttl; uint32_t srcip[4]; uint16_t srcport; uint32_t dstip[4]; uint16_t dstport; } tcp_conntrack_info_t; int tcp_handle_incoming(uint32_t srcip[4], uint32_t dstip[4], uint16_t srcport, uint16_t dstport, uint8_t is_ipv6, uint8_t ttl); int tcp_handle_outgoing(uint32_t srcip[4], uint32_t dstip[4], uint16_t srcport, uint16_t dstport, tcp_conntrack_info_t *conn_info, uint8_t is_ipv6); int tcp_get_auto_ttl(const uint8_t ttl, const uint8_t autottl1, const uint8_t autottl2, const uint8_t minhops, const uint8_t maxttl); #endif ================================================ FILE: src/utils/getline.c ================================================ /* $NetBSD: getdelim.c,v 1.2 2015/12/25 20:12:46 joerg Exp $ */ /* NetBSD-src: getline.c,v 1.2 2014/09/16 17:23:50 christos Exp */ /*- * Copyright (c) 2011 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "getline.h" #if !HAVE_GETDELIM ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp) { char *ptr, *eptr; if (*buf == NULL || *bufsiz == 0) { *bufsiz = BUFSIZ; if ((*buf = malloc(*bufsiz)) == NULL) return -1; } for (ptr = *buf, eptr = *buf + *bufsiz;;) { int c = fgetc(fp); if (c == -1) { if (feof(fp)) { ssize_t diff = (ssize_t)(ptr - *buf); if (diff != 0) { *ptr = '\0'; return diff; } } return -1; } *ptr++ = c; if (c == delimiter) { *ptr = '\0'; return ptr - *buf; } if (ptr + 2 >= eptr) { char *nbuf; size_t nbufsiz = *bufsiz * 2; ssize_t d = ptr - *buf; if ((nbuf = realloc(*buf, nbufsiz)) == NULL) return -1; *buf = nbuf; *bufsiz = nbufsiz; eptr = nbuf + nbufsiz; ptr = nbuf + d; } } } #endif #if !HAVE_GETLINE ssize_t getline(char **buf, size_t *bufsiz, FILE *fp) { return getdelim(buf, bufsiz, '\n', fp); } #endif ================================================ FILE: src/utils/getline.h ================================================ #if !HAVE_GETDELIM ssize_t getdelim(char **, size_t *, int, FILE *); #endif #if !HAVE_GETLINE ssize_t getline(char **, size_t *, FILE *); #endif ================================================ FILE: src/utils/repl_str.c ================================================ #include #include #include #if (__STDC_VERSION__ >= 199901L) #include #endif char *repl_str(const char *str, const char *from, const char *to) { /* Adjust each of the below values to suit your needs. */ /* Increment positions cache size initially by this number. */ size_t cache_sz_inc = 16; /* Thereafter, each time capacity needs to be increased, * multiply the increment by this factor. */ const size_t cache_sz_inc_factor = 3; /* But never increment capacity by more than this number. */ const size_t cache_sz_inc_max = 1048576; char *pret, *ret = NULL; const char *pstr2, *pstr = str; size_t i, count = 0; #if (__STDC_VERSION__ >= 199901L) uintptr_t *pos_cache_tmp, *pos_cache = NULL; #else ptrdiff_t *pos_cache_tmp, *pos_cache = NULL; #endif size_t cache_sz = 0; size_t cpylen, orglen, retlen, tolen, fromlen = strlen(from); /* Find all matches and cache their positions. */ while ((pstr2 = strstr(pstr, from)) != NULL) { count++; /* Increase the cache size when necessary. */ if (cache_sz < count) { cache_sz += cache_sz_inc; pos_cache_tmp = realloc(pos_cache, sizeof(*pos_cache) * cache_sz); if (pos_cache_tmp == NULL) { goto end_repl_str; } else pos_cache = pos_cache_tmp; cache_sz_inc *= cache_sz_inc_factor; if (cache_sz_inc > cache_sz_inc_max) { cache_sz_inc = cache_sz_inc_max; } } pos_cache[count-1] = (uintptr_t)(pstr2 - str); pstr = pstr2 + fromlen; } orglen = (size_t)(pstr - str) + strlen(pstr); /* Allocate memory for the post-replacement string. */ if (count > 0) { tolen = strlen(to); retlen = orglen + (tolen - fromlen) * count; } else retlen = orglen; ret = malloc(retlen + 1); if (ret == NULL) { goto end_repl_str; } if (count == 0) { /* If no matches, then just duplicate the string. */ strcpy(ret, str); } else { /* Otherwise, duplicate the string whilst performing * the replacements using the position cache. */ pret = ret; memcpy(pret, str, pos_cache[0]); pret += pos_cache[0]; for (i = 0; i < count; i++) { memcpy(pret, to, tolen); pret += tolen; pstr = str + pos_cache[i] + fromlen; cpylen = (i == count-1 ? orglen : pos_cache[i+1]) - pos_cache[i] - fromlen; memcpy(pret, pstr, cpylen); pret += cpylen; } ret[retlen] = '\0'; } end_repl_str: /* Free the cache and return the post-replacement string, * which will be NULL in the event of an error. */ free(pos_cache); return ret; } ================================================ FILE: src/utils/repl_str.h ================================================ char *repl_str(const char *str, const char *from, const char *to); ================================================ FILE: src/utils/uthash.h ================================================ /* Copyright (c) 2003-2021, Troy D. Hanson http://troydhanson.github.io/uthash/ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #define UTHASH_VERSION 2.3.0 #include /* memcmp, memset, strlen */ #include /* ptrdiff_t */ #include /* exit */ #if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT /* This codepath is provided for backward compatibility, but I plan to remove it. */ #warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" typedef unsigned int uint32_t; typedef unsigned char uint8_t; #elif defined(HASH_NO_STDINT) && HASH_NO_STDINT #else #include /* uint8_t, uint32_t */ #endif /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #if !defined(DECLTYPE) && !defined(NO_DECLTYPE) #if defined(_MSC_VER) /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #endif #elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) #define NO_DECLTYPE #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #endif #ifdef NO_DECLTYPE #define DECLTYPE(x) #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while (0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while (0) #endif #ifndef uthash_malloc #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #endif #ifndef uthash_free #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #endif #ifndef uthash_bzero #define uthash_bzero(a,n) memset(a,'\0',n) #endif #ifndef uthash_strlen #define uthash_strlen(s) strlen(s) #endif #ifndef HASH_FUNCTION #define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) #endif #ifndef HASH_KEYCMP #define HASH_KEYCMP(a,b,n) memcmp(a,b,n) #endif #ifndef uthash_noexpand_fyi #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #endif #ifndef uthash_expand_fyi #define uthash_expand_fyi(tbl) /* can be defined to log expands */ #endif #ifndef HASH_NONFATAL_OOM #define HASH_NONFATAL_OOM 0 #endif #if HASH_NONFATAL_OOM /* malloc failures can be recovered from */ #ifndef uthash_nonfatal_oom #define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) #define IF_HASH_NONFATAL_OOM(x) x #else /* malloc failures result in lost memory, hash tables are unusable */ #ifndef uthash_fatal #define uthash_fatal(msg) exit(-1) /* fatal OOM error */ #endif #define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") #define IF_HASH_NONFATAL_OOM(x) #endif /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhp */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) /* calculate the hash handle from element address elp */ #define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) #define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ do { \ struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ unsigned _hd_bkt; \ HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ (head)->hh.tbl->buckets[_hd_bkt].count++; \ _hd_hh_item->hh_next = NULL; \ _hd_hh_item->hh_prev = NULL; \ } while (0) #define HASH_VALUE(keyptr,keylen,hashv) \ do { \ HASH_FUNCTION(keyptr, keylen, hashv); \ } while (0) #define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_bkt; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ } \ } \ } while (0) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ (out) = NULL; \ if (head) { \ unsigned _hf_hashv; \ HASH_VALUE(keyptr, keylen, _hf_hashv); \ HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) #define HASH_BLOOM_MAKE(tbl,oomed) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!(tbl)->bloom_bv) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } \ } while (0) #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0) #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) #else #define HASH_BLOOM_MAKE(tbl,oomed) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #define HASH_BLOOM_BYTELEN 0U #endif #define HASH_MAKE_TABLE(hh,head,oomed) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ if (!(head)->hh.tbl) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ if (!(head)->hh.tbl->buckets) { \ HASH_RECORD_OOM(oomed); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } else { \ uthash_bzero((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ uthash_free((head)->hh.tbl->buckets, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ } \ ) \ } \ } \ } while (0) #define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ } while (0) #define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ do { \ (replaced) = NULL; \ HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ if (replaced) { \ HASH_DELETE(hh, head, replaced); \ } \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ } while (0) #define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ } while (0) #define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ do { \ unsigned _hr_hashv; \ HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ } while (0) #define HASH_APPEND_LIST(hh, head, add) \ do { \ (add)->hh.next = NULL; \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail->next = (add); \ (head)->hh.tbl->tail = &((add)->hh); \ } while (0) #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ do { \ if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ break; \ } \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #ifdef NO_DECLTYPE #undef HASH_AKBI_INNER_LOOP #define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ do { \ char *_hs_saved_head = (char*)(head); \ do { \ DECLTYPE_ASSIGN(head, _hs_iter); \ if (cmpfcn(head, add) > 0) { \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ break; \ } \ DECLTYPE_ASSIGN(head, _hs_saved_head); \ } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ } while (0) #endif #if HASH_NONFATAL_OOM #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ if (!(oomed)) { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ if (oomed) { \ HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ HASH_DELETE_HH(hh, head, &(add)->hh); \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } else { \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } \ } else { \ (add)->hh.tbl = NULL; \ uthash_nonfatal_oom(add); \ } \ } while (0) #else #define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ do { \ unsigned _ha_bkt; \ (head)->hh.tbl->num_items++; \ HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ } while (0) #endif #define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (char*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ void *_hs_iter = (head); \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ if (_hs_iter) { \ (add)->hh.next = _hs_iter; \ if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ } else { \ (head) = (add); \ } \ HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ } else { \ HASH_APPEND_LIST(hh, head, add); \ } \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ } while (0) #define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ do { \ unsigned _hs_hashv; \ HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ } while (0) #define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) #define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) #define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ do { \ IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ (add)->hh.hashv = (hashval); \ (add)->hh.key = (const void*) (keyptr); \ (add)->hh.keylen = (unsigned) (keylen_in); \ if (!(head)) { \ (add)->hh.next = NULL; \ (add)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh, add, _ha_oomed); \ IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ (head) = (add); \ IF_HASH_NONFATAL_OOM( } ) \ } else { \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_APPEND_LIST(hh, head, add); \ } \ HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ } while (0) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_hashv; \ HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ } while (0) #define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) #define HASH_TO_BKT(hashv,num_bkts,bkt) \ do { \ bkt = ((hashv) & ((num_bkts) - 1U)); \ } while (0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ HASH_DELETE_HH(hh, head, &(delptr)->hh) #define HASH_DELETE_HH(hh,head,delptrhh) \ do { \ struct UT_hash_handle *_hd_hh_del = (delptrhh); \ if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } else { \ unsigned _hd_bkt; \ if (_hd_hh_del == (head)->hh.tbl->tail) { \ (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ } \ if (_hd_hh_del->prev != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ } else { \ DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ } \ if (_hd_hh_del->next != NULL) { \ HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ } \ HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ do { \ unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ } while (0) #define HASH_ADD_STR(head,strfield,add) \ do { \ unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ } while (0) #define HASH_REPLACE_STR(head,strfield,add,replaced) \ do { \ unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ } while (0) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_REPLACE_INT(head,intfield,add,replaced) \ HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #include /* fprintf, stderr */ #define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head,where) \ do { \ struct UT_hash_handle *_thh; \ if (head) { \ unsigned _bkt_i; \ unsigned _count = 0; \ char *_prev; \ for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ unsigned _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ (where), (void*)_thh->hh_prev, (void*)_prev); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev != (char*)_thh->prev) { \ HASH_OOPS("%s: invalid prev %p, actual %p\n", \ (where), (void*)_thh->prev, (void*)_prev); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ (where), (head)->hh.tbl->num_items, _count); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head,where) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ #define HASH_BER(key,keylen,hashv) \ do { \ unsigned _hb_keylen = (unsigned)keylen; \ const unsigned char *_hb_key = (const unsigned char*)(key); \ (hashv) = 0; \ while (_hb_keylen-- != 0U) { \ (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ } \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ #define HASH_SAX(key,keylen,hashv) \ do { \ unsigned _sx_i; \ const unsigned char *_hs_key = (const unsigned char*)(key); \ hashv = 0; \ for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ } \ } while (0) /* FNV-1a variation */ #define HASH_FNV(key,keylen,hashv) \ do { \ unsigned _fn_i; \ const unsigned char *_hf_key = (const unsigned char*)(key); \ (hashv) = 2166136261U; \ for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ hashv = hashv ^ _hf_key[_fn_i]; \ hashv = hashv * 16777619U; \ } \ } while (0) #define HASH_OAT(key,keylen,hashv) \ do { \ unsigned _ho_i; \ const unsigned char *_ho_key=(const unsigned char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ } while (0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,hashv) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ unsigned const char *_hj_key=(unsigned const char*)(key); \ hashv = 0xfeedbeefu; \ _hj_i = _hj_j = 0x9e3779b9u; \ _hj_k = (unsigned)(keylen); \ while (_hj_k >= 12U) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12U; \ } \ hashv += (unsigned)(keylen); \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ default: ; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ } while (0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,hashv) \ do { \ unsigned const char *_sfh_key=(unsigned const char*)(key); \ uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ \ unsigned _sfh_rem = _sfh_len & 3U; \ _sfh_len >>= 2; \ hashv = 0xcafebabeu; \ \ /* Main loop */ \ for (;_sfh_len > 0U; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2U*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ break; \ default: ; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ } while (0) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ do { \ if ((head).hh_head != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ } else { \ (out) = NULL; \ } \ while ((out) != NULL) { \ if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ break; \ } \ } \ if ((out)->hh.hh_next != NULL) { \ DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ } else { \ (out) = NULL; \ } \ } \ } while (0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ do { \ UT_hash_bucket *_ha_head = &(head); \ _ha_head->count++; \ (addhh)->hh_next = _ha_head->hh_head; \ (addhh)->hh_prev = NULL; \ if (_ha_head->hh_head != NULL) { \ _ha_head->hh_head->hh_prev = (addhh); \ } \ _ha_head->hh_head = (addhh); \ if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ && !(addhh)->tbl->noexpand) { \ HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ IF_HASH_NONFATAL_OOM( \ if (oomed) { \ HASH_DEL_IN_BKT(head,addhh); \ } \ ) \ } \ } while (0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(head,delhh) \ do { \ UT_hash_bucket *_hd_head = &(head); \ _hd_head->count--; \ if (_hd_head->hh_head == (delhh)) { \ _hd_head->hh_head = (delhh)->hh_next; \ } \ if ((delhh)->hh_prev) { \ (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ } \ if ((delhh)->hh_next) { \ (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ } \ } while (0) /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ if (!_he_new_buckets) { \ HASH_RECORD_OOM(oomed); \ } else { \ uthash_bzero(_he_new_buckets, \ sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ (tbl)->ideal_chain_maxlen = \ ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ (tbl)->nonideal_items = 0; \ for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh != NULL) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ _he_newbkt = &(_he_new_buckets[_he_bkt]); \ if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ (tbl)->nonideal_items++; \ if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ _he_newbkt->expand_mult++; \ } \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head != NULL) { \ _he_newbkt->hh_head->hh_prev = _he_thh; \ } \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ (tbl)->num_buckets *= 2U; \ (tbl)->log2_num_buckets++; \ (tbl)->buckets = _he_new_buckets; \ (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ ((tbl)->ineff_expands+1U) : 0U; \ if ((tbl)->ineff_expands > 1U) { \ (tbl)->noexpand = 1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } \ } while (0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head != NULL) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping != 0U) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p != NULL) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ _hs_psize++; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ if (_hs_q == NULL) { \ break; \ } \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ if (_hs_psize == 0U) { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else if ((cmpfcn( \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ )) <= 0) { \ _hs_e = _hs_p; \ if (_hs_p != NULL) { \ _hs_p = ((_hs_p->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ } \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = ((_hs_q->next != NULL) ? \ HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail != NULL ) { \ _hs_tail->next = ((_hs_e != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ if (_hs_e != NULL) { \ _hs_e->prev = ((_hs_tail != NULL) ? \ ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ } \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ if (_hs_tail != NULL) { \ _hs_tail->next = NULL; \ } \ if (_hs_nmerges <= 1U) { \ _hs_looping = 0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2U; \ } \ HASH_FSCK(hh, head, "HASH_SRT"); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt = NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if ((src) != NULL) { \ for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh != NULL; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh != NULL) { \ _last_elt_hh->next = _elt; \ } \ if ((dst) == NULL) { \ DECLTYPE_ASSIGN(dst, _elt); \ HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ uthash_nonfatal_oom(_elt); \ (dst) = NULL; \ continue; \ } \ ) \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ (dst)->hh_dst.tbl->num_items++; \ IF_HASH_NONFATAL_OOM( \ if (_hs_oomed) { \ HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ _dst_hh->tbl = NULL; \ uthash_nonfatal_oom(_elt); \ continue; \ } \ ) \ HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if ((head) != NULL) { \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head) = NULL; \ } \ } while (0) #define HASH_OVERHEAD(hh,head) \ (((head) != NULL) ? ( \ (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ sizeof(UT_hash_table) + \ (HASH_BLOOM_BYTELEN))) : 0U) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) #else #define HASH_ITER(hh,head,el,tmp) \ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1u #define HASH_BLOOM_SIGNATURE 0xb12220f2u typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; uint8_t bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ const void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */