pax_global_header00006660000000000000000000000064152227151200014507gustar00rootroot0000000000000052 comment=b17f829f7ed1fc0f358af0be527fd2f7a3206c5d libtorrent-0.16.17/000077500000000000000000000000001522271512000140475ustar00rootroot00000000000000libtorrent-0.16.17/.clang-format000066400000000000000000000015711522271512000164260ustar00rootroot00000000000000--- BasedOnStyle: LLVM Standard: c++20 AllowShortFunctionsOnASingleLine: All AllowShortLambdasOnASingleLine: All AlwaysBreakAfterReturnType: TopLevelDefinitions BinPackArguments: false BinPackParameters: false BreakConstructorInitializers: AfterColon BreakStringLiterals: false ColumnLimit: 0 ConstructorInitializerAllOnOneLineOrOnePerLine: false ContinuationIndentWidth: 2 Cpp11BracedListStyle: false IndentCaseLabels: false IndentWidth: 2 PackConstructorInitializers: Never PenaltyReturnTypeOnItsOwnLine: 130 PointerAlignment: Left SpacesInContainerLiterals: true AlignEscapedNewlines: Right AlignConsecutiveDeclarations: Enabled: true AcrossEmptyLines: true AcrossComments: false AlignConsecutiveMacros: Enabled: true AlignConsecutiveAssignments: Enabled: true IncludeCategories: - Regex: "^(config|globals)\\.h" Priority: -1 - Regex: "^torrent/.*" Priority: 1 libtorrent-0.16.17/.clang-tidy000066400000000000000000000011221522271512000160770ustar00rootroot00000000000000--- Checks: '-*,misc-include-cleaner' FormatStyle: 'file' CheckOptions: - key: misc-include-cleaner.MissingIncludes value: false - key: readability-identifier-naming.LocalVariableCase value: lower_case - key: readability-identifier-naming.ParameterCase value: lower_case - key: readability-identifier-naming.FunctionCase value: lower_case - key: readability-identifier-naming.PrivateMemberPrefix value: m_ - key: readability-identifier-naming.PrivateMemberCase value: lower_case - key: readability-identifier-naming.ClassConstantCase value: lower_case libtorrent-0.16.17/.dir-locals.el000066400000000000000000000003121522271512000164740ustar00rootroot00000000000000;;; Directory Local Variables ;;; For more information see (info "(emacs) Directory Variables") ((c++-mode (flycheck-clang-language-standard . "c++17") (flycheck-gcc-language-standard . "c++17"))) libtorrent-0.16.17/.github/000077500000000000000000000000001522271512000154075ustar00rootroot00000000000000libtorrent-0.16.17/.github/FUNDING.yml000066400000000000000000000012541522271512000172260ustar00rootroot00000000000000# These are supported funding model platforms github: [rakshasa] patreon: rtorrent open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: ['https://rakshasa.github.io/rtorrent/donate.html'] libtorrent-0.16.17/.github/workflows/000077500000000000000000000000001522271512000174445ustar00rootroot00000000000000libtorrent-0.16.17/.github/workflows/post-static-analysis.yml000066400000000000000000000074301522271512000242660ustar00rootroot00000000000000# Secure workflow with access to repository secrets and GitHub token for posting analysis results name: Post the static analysis results on: workflow_run: workflows: [ "Static analysis" ] types: [ completed ] jobs: clang-tidy-results: # Trigger the job only if the previous (insecure) workflow completed successfully if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-22.04 permissions: pull-requests: write # OPTIONAL: auto-closing conversations requires the `contents` permission contents: read steps: - name: Download analysis results uses: actions/github-script@v7 with: script: | const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, repo: context.repo.repo, run_id: ${{ github.event.workflow_run.id }}, }); const matchArtifact = artifacts.data.artifacts.filter((artifact) => { return artifact.name == "clang-tidy-result" })[0]; const download = await github.rest.actions.downloadArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: matchArtifact.id, archive_format: "zip", }); const fs = require("fs"); fs.writeFileSync("${{ github.workspace }}/clang-tidy-result.zip", Buffer.from(download.data)); - name: Extract analysis results run: | mkdir clang-tidy-result unzip -j clang-tidy-result.zip -d clang-tidy-result - name: Set environment variables uses: actions/github-script@v7 with: script: | const assert = require("node:assert").strict; const fs = require("fs"); function exportVar(varName, fileName, regEx) { const val = fs.readFileSync("${{ github.workspace }}/clang-tidy-result/" + fileName, { encoding: "ascii" }).trimEnd(); assert.ok(regEx.test(val), "Invalid value format for " + varName); core.exportVariable(varName, val); } exportVar("PR_ID", "pr-id.txt", /^[0-9]+$/); exportVar("PR_HEAD_REPO", "pr-head-repo.txt", /^[-./0-9A-Z_a-z]+$/); exportVar("PR_HEAD_SHA", "pr-head-sha.txt", /^[0-9A-Fa-f]+$/); - uses: actions/checkout@v4 with: repository: ${{ env.PR_HEAD_REPO }} ref: ${{ env.PR_HEAD_SHA }} persist-credentials: false - name: Redownload analysis results uses: actions/github-script@v7 with: script: | const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, repo: context.repo.repo, run_id: ${{ github.event.workflow_run.id }}, }); const matchArtifact = artifacts.data.artifacts.filter((artifact) => { return artifact.name == "clang-tidy-result" })[0]; const download = await github.rest.actions.downloadArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: matchArtifact.id, archive_format: "zip", }); const fs = require("fs"); fs.writeFileSync("${{ github.workspace }}/clang-tidy-result.zip", Buffer.from(download.data)); - name: Extract analysis results run: | mkdir clang-tidy-result unzip -j clang-tidy-result.zip -d clang-tidy-result - name: Run clang-tidy-pr-comments action uses: platisd/clang-tidy-pr-comments@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} clang_tidy_fixes: clang-tidy-result/fixes.yml pull_request_id: ${{ env.PR_ID }} libtorrent-0.16.17/.github/workflows/static-analysis.yml000066400000000000000000000045031522271512000233010ustar00rootroot00000000000000name: Static analysis on: pull_request jobs: clang-tidy: runs-on: ubuntu-22.04 steps: - name: Update Packages run: | sudo apt-get update - name: Install Dependencies run: | sudo apt-get install -y \ clang-tidy \ bear \ libcppunit-dev \ libcurl4-openssl-dev - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: Fetch base branch run: | git remote add upstream "https://github.com/${{ github.event.pull_request.base.repo.full_name }}" git fetch --no-tags --no-recurse-submodules upstream "${{ github.event.pull_request.base.ref }}" - name: Configure Project run: | libtoolize aclocal -I scripts autoconf -i autoheader automake --add-missing ./configure - name: Prepare compile_commands.json run: | bear -- make - name: Create results directory run: | mkdir clang-tidy-result - name: Analyze run: | git diff -U0 "$(git merge-base HEAD "upstream/${{ github.event.pull_request.base.ref }}")" | clang-tidy-diff -p1 -path build -export-fixes clang-tidy-result/fixes.yml - name: Save PR metadata run: | echo "${{ github.event.number }}" > clang-tidy-result/pr-id.txt echo "${{ github.event.pull_request.head.repo.full_name }}" > clang-tidy-result/pr-head-repo.txt echo "${{ github.event.pull_request.head.sha }}" > clang-tidy-result/pr-head-sha.txt - uses: actions/upload-artifact@v4 with: name: clang-tidy-result path: clang-tidy-result/ # - name: Run clang-tidy-pr-comments action # uses: platisd/clang-tidy-pr-comments@v1 # with: # # The GitHub token (or a personal access token) # github_token: ${{ secrets.GITHUB_TOKEN }} # # The path to the clang-tidy fixes generated previously # clang_tidy_fixes: clang-tidy-result/fixes.yml # # Optionally set to true if you want the Action to request # # changes in case warnings are found # request_changes: true # # Optionally set the number of comments per review # # to avoid GitHub API timeouts for heavily loaded # # pull requests # suggestions_per_comment: 10 libtorrent-0.16.17/.github/workflows/unit-tests.yml000066400000000000000000000134031522271512000223070ustar00rootroot00000000000000name: Unit tests on: pull_request: workflow_dispatch: jobs: Ubuntu: runs-on: ubuntu-24.04 steps: - name: Prepare run: | sudo apt-get update sudo apt-get install -y \ libcppunit-dev \ libcurl4-openssl-dev - uses: actions/checkout@v4 - name: Run run: | autoreconf -fiv ./configure make make check MacOS: needs: Ubuntu runs-on: macos-latest env: TEST_IGNORE_SIGNAL_INTERRUPT_LATENCY: YES steps: - name: Prepare run: | brew install \ automake \ cppunit \ curl \ libtool - uses: actions/checkout@v4 - name: Run run: | autoreconf -fiv ./configure make make check DragonflyBSD: if: ${{ false }} # Disabled to reduce GitHub resource usage needs: Ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: vmactions/dragonflybsd-vm@v1 with: prepare: | pkg install -y \ automake \ autoconf \ cppunit \ curl \ libtool \ openssl \ pkgconf run: | autoreconf -fiv ./configure make make check FreeBSD: if: ${{ false }} # Disabled to reduce GitHub resource usage needs: Ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: vmactions/freebsd-vm@v1 with: prepare: | pkg install -y \ automake \ autoconf \ cppunit \ curl \ libtool \ openssl \ pkgconf run: | autoreconf -fiv ./configure make make check NetBSD: needs: Ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: vmactions/netbsd-vm@v1 with: prepare: | # 1. Force export the correct package repository path matching your OS version export OS_VERSION=$(uname -r | cut -d_ -f1) export ARCH=$(uname -p) export PKG_PATH="https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/${ARCH}/${OS_VERSION}/All/" # 2. Clean up conflicting legacy GCC artifacts sudo rm -rf /usr/pkg/gcc10/libexec/gcc/x86_64--netbsd/10.5.0/cc1plus # 3. Update the package database and install pkgin if not present sudo /usr/sbin/pkg_add -U pkgin || true # 4. Install packages using pkgin (handles dependencies and upgrades automatically) # NOTE: 'openssl' is removed because it is provided by the NetBSD base system. sudo pkgin -y update sudo pkgin -y install \ automake \ autoconf \ cppunit \ curl \ libtool \ pkgconf \ openssl3 \ gcc14 # /usr/sbin/pkg_add -v \ # automake \ # autoconf \ # cppunit \ # curl \ # libtool \ # openssl \ # pkgconf \ # gcc14 run: | export CC=gcc export CXX=g++ export PATH="/usr/pkg/gcc14/bin:/usr/pkg/bin:/usr/pkg/sbin:$PATH" export CPPFLAGS="-I/usr/pkg/include" export LDFLAGS="-L/usr/pkg/lib -Wl,-R/usr/pkg/lib" export PKG_CONFIG_PATH="/usr/pkg/lib/pkgconfig" autoreconf -fiv ./configure make make check OpenBSD: needs: Ubuntu runs-on: ubuntu-latest env: # TODO: Try without this: TEST_IGNORE_SIGNAL_INTERRUPT_LATENCY: YES AUTOCONF_VERSION: 2.63 AUTOMAKE_VERSION: 1.18 steps: - uses: actions/checkout@v4 - uses: vmactions/openbsd-vm@v1 with: # Upgraded to utilize native EVFILT_USER features in 7.9 release: "7.9" envs: 'TEST_IGNORE_SIGNAL_INTERRUPT_LATENCY AUTOCONF_VERSION AUTOMAKE_VERSION' prepare: | pkg_add -v -I \ automake-1.18.1 \ autoconf-2.63p1 \ cppunit \ curl \ libtool \ openssl run: | export PATH="/usr/local/bin:$PATH" ulimit -n 1000 autoreconf -fiv ./configure --disable-mincore make make check OmniOS: if: ${{ false }} # Avoid excessive resource usage. needs: Ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: vmactions/omnios-vm@v1 with: prepare: | pkg install \ automake \ autoconf \ curl \ gcc14 \ libtool \ openssl \ pkg-config run: | autoreconf -fiv ./configure make # make check Alpine: needs: Ubuntu runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: # platform: ['x86_64', 'x86'] platform: ['x86_64'] # Disabled x86 to reduce GitHub resource usage defaults: run: shell: alpine.sh {0} steps: - uses: actions/checkout@v4 - uses: jirutka/setup-alpine@v1 with: branch: edge arch: ${{matrix.platform}} packages: > build-base automake autoconf cppunit-dev curl-dev libtool linux-headers openssl-dev pkgconf zlib-dev - name: Compile and Test run: | autoreconf -fiv ./configure grep LT_SMP_CACHE_BYTES config.h make make check libtorrent-0.16.17/.gitignore000066400000000000000000000015641522271512000160450ustar00rootroot00000000000000# Compiled source # ################### *.o *.lo *.a *.la *.so *.in *.sub *.pc *.orig *.rej # Autoconf files # ################## .deps .libs Makefile aclocal.m4 ‎actmp.* ar-lib autom4te.cache compile /config.h config.guess config.status confdefs.h conftest.dir configure conftest.* depcomp install-sh libtool ltmain.sh missing stamp-h1 scripts/libtool.m4 scripts/lt*.m4 # Editor poo # ############## .#* \#*# *~ # Packages # ############ # it's better to unpack these files and commit the raw source # git has its own built in compression methods *.gz *.tar *.zip # Logs and databases # ###################### *.log compile_commands.json # OS generated files # ###################### .DS_Store? .dirstamp ehthumbs.db Icon? Thumbs.db TAGS # LibTorrent specific files ########################### test/utils_log_test.* test/LibTorrent_Test* test/LibTorrentTest.trs test-driver libtorrent-0.16.17/AUTHORS000066400000000000000000000000521522271512000151140ustar00rootroot00000000000000Jari Sundell libtorrent-0.16.17/COPYING000066400000000000000000000431311522271512000151040ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. libtorrent-0.16.17/INSTALL000066400000000000000000000366141522271512000151120ustar00rootroot00000000000000Installation Instructions ************************* Copyright (C) 1994-1996, 1999-2002, 2004-2016 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without warranty of any kind. Basic Installation ================== Briefly, the shell command './configure && make && make install' should configure, build, and install this package. The following more-detailed instructions are generic; see the 'README' file for instructions specific to this package. Some packages provide this 'INSTALL' file but do not implement all of the features documented below. The lack of an optional feature in a given package is not necessarily a bug. More recommendations for GNU packages can be found in *note Makefile Conventions: (standards)Makefile Conventions. The 'configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a 'Makefile' in each directory of the package. It may also create one or more '.h' files containing system-dependent definitions. Finally, it creates a shell script 'config.status' that you can run in the future to recreate the current configuration, and a file 'config.log' containing compiler output (useful mainly for debugging 'configure'). It can also use an optional file (typically called 'config.cache' and enabled with '--cache-file=config.cache' or simply '-C') that saves the results of its tests to speed up reconfiguring. Caching is disabled by default to prevent problems with accidental use of stale cache files. If you need to do unusual things to compile the package, please try to figure out how 'configure' could check whether to do them, and mail diffs or instructions to the address given in the 'README' so they can be considered for the next release. If you are using the cache, and at some point 'config.cache' contains results you don't want to keep, you may remove or edit it. The file 'configure.ac' (or 'configure.in') is used to create 'configure' by a program called 'autoconf'. You need 'configure.ac' if you want to change it or regenerate 'configure' using a newer version of 'autoconf'. The simplest way to compile this package is: 1. 'cd' to the directory containing the package's source code and type './configure' to configure the package for your system. Running 'configure' might take a while. While running, it prints some messages telling which features it is checking for. 2. Type 'make' to compile the package. 3. Optionally, type 'make check' to run any self-tests that come with the package, generally using the just-built uninstalled binaries. 4. Type 'make install' to install the programs and any data files and documentation. When installing into a prefix owned by root, it is recommended that the package be configured and built as a regular user, and only the 'make install' phase executed with root privileges. 5. Optionally, type 'make installcheck' to repeat any self-tests, but this time using the binaries in their final installed location. This target does not install anything. Running this target as a regular user, particularly if the prior 'make install' required root privileges, verifies that the installation completed correctly. 6. You can remove the program binaries and object files from the source code directory by typing 'make clean'. To also remove the files that 'configure' created (so you can compile the package for a different kind of computer), type 'make distclean'. There is also a 'make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. 7. Often, you can also type 'make uninstall' to remove the installed files again. In practice, not all packages have tested that uninstallation works correctly, even though it is required by the GNU Coding Standards. 8. Some packages, particularly those that use Automake, provide 'make distcheck', which can by used by developers to test that all other targets like 'make install' and 'make uninstall' work correctly. This target is generally not run by end users. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the 'configure' script does not know about. Run './configure --help' for details on some of the pertinent environment variables. You can give 'configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c99 CFLAGS=-g LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you can use GNU 'make'. 'cd' to the directory where you want the object files and executables to go and run the 'configure' script. 'configure' automatically checks for the source code in the directory that 'configure' is in and in '..'. This is known as a "VPATH" build. With a non-GNU 'make', it is safer to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use 'make distclean' before reconfiguring for another architecture. On MacOS X 10.5 and later systems, you can create libraries and executables that work on multiple system types--known as "fat" or "universal" binaries--by specifying multiple '-arch' options to the compiler but only a single '-arch' option to the preprocessor. Like this: ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CPP="gcc -E" CXXCPP="g++ -E" This is not guaranteed to produce working output in all cases, you may have to build one architecture at a time and combine the results using the 'lipo' tool if you have problems. Installation Names ================== By default, 'make install' installs the package's commands under '/usr/local/bin', include files under '/usr/local/include', etc. You can specify an installation prefix other than '/usr/local' by giving 'configure' the option '--prefix=PREFIX', where PREFIX must be an absolute file name. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you pass the option '--exec-prefix=PREFIX' to 'configure', the package uses PREFIX as the prefix for installing programs and libraries. Documentation and other data files still use the regular prefix. In addition, if you use an unusual directory layout you can give options like '--bindir=DIR' to specify different values for particular kinds of files. Run 'configure --help' for a list of the directories you can set and what kinds of files go in them. In general, the default for these options is expressed in terms of '${prefix}', so that specifying just '--prefix' will affect all of the other directory specifications that were not explicitly provided. The most portable way to affect installation locations is to pass the correct locations to 'configure'; however, many packages provide one or both of the following shortcuts of passing variable assignments to the 'make install' command line to change installation locations without having to reconfigure or recompile. The first method involves providing an override variable for each affected directory. For example, 'make install prefix=/alternate/directory' will choose an alternate location for all directory configuration variables that were expressed in terms of '${prefix}'. Any directories that were specified during 'configure', but not in terms of '${prefix}', must each be overridden at install time for the entire installation to be relocated. The approach of makefile variable overrides for each directory variable is required by the GNU Coding Standards, and ideally causes no recompilation. However, some platforms have known limitations with the semantics of shared libraries that end up requiring recompilation when using this method, particularly noticeable in packages that use GNU Libtool. The second method involves providing the 'DESTDIR' variable. For example, 'make install DESTDIR=/alternate/directory' will prepend '/alternate/directory' before all installation names. The approach of 'DESTDIR' overrides is not required by the GNU Coding Standards, and does not work on platforms that have drive letters. On the other hand, it does better at avoiding recompilation issues, and works well even when some directory options were not specified in terms of '${prefix}' at 'configure' time. Optional Features ================= If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving 'configure' the option '--program-prefix=PREFIX' or '--program-suffix=SUFFIX'. Some packages pay attention to '--enable-FEATURE' options to 'configure', where FEATURE indicates an optional part of the package. They may also pay attention to '--with-PACKAGE' options, where PACKAGE is something like 'gnu-as' or 'x' (for the X Window System). The 'README' should mention any '--enable-' and '--with-' options that the package recognizes. For packages that use the X Window System, 'configure' can usually find the X include and library files automatically, but if it doesn't, you can use the 'configure' options '--x-includes=DIR' and '--x-libraries=DIR' to specify their locations. Some packages offer the ability to configure how verbose the execution of 'make' will be. For these packages, running './configure --enable-silent-rules' sets the default to minimal output, which can be overridden with 'make V=1'; while running './configure --disable-silent-rules' sets the default to verbose, which can be overridden with 'make V=0'. Particular systems ================== On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC is not installed, it is recommended to use the following options in order to use an ANSI C compiler: ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" and if that doesn't work, install pre-built binaries of GCC for HP-UX. HP-UX 'make' updates targets which have the same time stamps as their prerequisites, which makes it generally unusable when shipped generated files such as 'configure' are involved. Use GNU 'make' instead. On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot parse its '' header file. The option '-nodtk' can be used as a workaround. If GNU CC is not installed, it is therefore recommended to try ./configure CC="cc" and if that doesn't work, try ./configure CC="cc -nodtk" On Solaris, don't put '/usr/ucb' early in your 'PATH'. This directory contains several dysfunctional programs; working variants of these programs are available in '/usr/bin'. So, if you need '/usr/ucb' in your 'PATH', put it _after_ '/usr/bin'. On Haiku, software installed for all users goes in '/boot/common', not '/usr/local'. It is recommended to use the following options: ./configure --prefix=/boot/common Specifying the System Type ========================== There may be some features 'configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, 'configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the '--build=TYPE' option. TYPE can either be a short name for the system type, such as 'sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file 'config.sub' for the possible values of each field. If 'config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the option '--target=TYPE' to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with '--host=TYPE'. Sharing Defaults ================ If you want to set default values for 'configure' scripts to share, you can create a site shell script called 'config.site' that gives default values for variables like 'CC', 'cache_file', and 'prefix'. 'configure' looks for 'PREFIX/share/config.site' if it exists, then 'PREFIX/etc/config.site' if it exists. Or, you can set the 'CONFIG_SITE' environment variable to the location of the site script. A warning: not all 'configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to 'configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the 'configure' command line, using 'VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified 'gcc' to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for 'CONFIG_SHELL' due to an Autoconf limitation. Until the limitation is lifted, you can use this workaround: CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash 'configure' Invocation ====================== 'configure' recognizes the following options to control how it operates. '--help' '-h' Print a summary of all of the options to 'configure', and exit. '--help=short' '--help=recursive' Print a summary of the options unique to this package's 'configure', and exit. The 'short' variant lists options used only in the top level, while the 'recursive' variant lists options also present in any nested packages. '--version' '-V' Print the version of Autoconf used to generate the 'configure' script, and exit. '--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally 'config.cache'. FILE defaults to '/dev/null' to disable caching. '--config-cache' '-C' Alias for '--cache-file=config.cache'. '--quiet' '--silent' '-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to '/dev/null' (any error messages will still be shown). '--srcdir=DIR' Look for the package's source code in directory DIR. Usually 'configure' can determine that directory automatically. '--prefix=DIR' Use DIR as the installation prefix. *note Installation Names:: for more details, including other options available for fine-tuning the installation locations. '--no-create' '-n' Run the configure checks, but stop before creating any output files. 'configure' also accepts some other, not widely useful, options. Run 'configure --help' for more details. libtorrent-0.16.17/Makefile.am000066400000000000000000000004111522271512000160770ustar00rootroot00000000000000SUBDIRS = src test pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libtorrent.pc EXTRA_DIST= \ scripts/checks.m4 \ scripts/common.m4 \ scripts/attributes.m4 \ scripts/ssl.m4 \ doc/main.xml \ doc/http.xml \ doc/torrent.xml ACLOCAL_AMFLAGS = -I scripts libtorrent-0.16.17/README.md000066400000000000000000000025541522271512000153340ustar00rootroot00000000000000LibTorrent ======== Introduction ------------ High performance torrent library for multiple clients. LibTorrent is a BitTorrent library written in C++ for *nix, with a focus on high performance and good code. The library differentiates itself from other implementations by transferring directly from file pages to the network stack. To learn how to use libtorrent visit the [Wiki](https://github.com/rakshasa/libtorrent/wiki). Related Projects ---------------- * [https://github.com/rakshasa/rbedit](https://github.com/rakshasa/rbedit): A dependency-free bencode editor. * [https://github.com/rakshasa/rtorrent](https://github.com/rakshasa/rtorrent): high performance ncurses based torrent client Donate to rTorrent development ------------------------------ * [Paypal](https://paypal.me/jarisundellno) * [Patreon](https://www.patreon.com/rtorrent) * [SubscribeStar](https://www.subscribestar.com/rtorrent) * Bitcoin: 1MpmXm5AHtdBoDaLZstJw8nupJJaeKu8V8 * Ethereum: 0x9AB1e3C3d8a875e870f161b3e9287Db0E6DAfF78 * Litecoin: LdyaVR67LBnTf6mAT4QJnjSG2Zk67qxmfQ * Cardano: addr1qytaslmqmk6dspltw06sp0zf83dh09u79j49ceh5y26zdcccgq4ph7nmx6kgmzeldauj43254ey97f3x4xw49d86aguqwfhlte Help keep rTorrent development going by donating to its creator. Building -------- Create configure files: ``` autoreconf -ivf ``` Configure, build and install: ``` ./configure make make install ``` libtorrent-0.16.17/configure.ac000066400000000000000000000053231522271512000163400ustar00rootroot00000000000000AC_INIT([[libtorrent]],[[0.16.17]],[[sundell.software@gmail.com]]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIRS([scripts]) AM_INIT_AUTOMAKE([serial-tests subdir-objects foreign]) LT_INIT([[disable-static]]) # When releasing the first 1.x.y version, we need to start with 1.1.0 (or higher) as we've already # used 0.16.x. AC_DEFINE([[PEER_NAME]], [["-lt1011-"]], [[Identifier that is part of the default peer id.]]) AC_DEFINE([[PEER_VERSION]], [["lt\x10\x11"]], [[4 byte client and version identifier for DHT.]]) LIBTORRENT_CURRENT=47 LIBTORRENT_REVISION=0 LIBTORRENT_AGE=0 LIBTORRENT_INTERFACE_VERSION_INFO=$LIBTORRENT_CURRENT:$LIBTORRENT_REVISION:$LIBTORRENT_AGE LIBTORRENT_INTERFACE_VERSION_NO=$LIBTORRENT_CURRENT.$LIBTORRENT_AGE.$LIBTORRENT_REVISION AC_SUBST(LIBTORRENT_CURRENT) AC_SUBST(LIBTORRENT_INTERFACE_VERSION_INFO) AC_SUBST(LIBTORRENT_INTERFACE_VERSION_NO) AC_PROG_CXX AX_CXX_COMPILE_STDCXX(20, noext, mandatory) RAK_CHECK_CFLAGS RAK_CHECK_CXXFLAGS RAK_ENABLE_DEBUG RAK_ENABLE_EXTRA_DEBUG RAK_ENABLE_WERROR TORRENT_CHECK_ATOMIC TORRENT_CHECK_CACHELINE TORRENT_CHECK_CUSTOM_ENDIAN64 TORRENT_CHECK_MADVISE TORRENT_CHECK_POSIX_FADVISE TORRENT_WITHOUT_KQUEUE TORRENT_WITHOUT_EPOLL TORRENT_WITH_ADDRESS_SPACE TORRENT_WITH_INOTIFY AC_ARG_ENABLE(attribute-visibility, AS_HELP_STRING([--disable-attribute-visibility],[disable symbol visibility attribute [[default=enable]]]), [ if test "$enableval" = "yes"; then CC_ATTRIBUTE_VISIBILITY fi ],[ CC_ATTRIBUTE_VISIBILITY ]) AC_ARG_ENABLE(execinfo, AS_HELP_STRING([--disable-execinfo],[disable libexecinfo [[default=enable]]]), [ if test "$enableval" = "yes"; then AX_EXECINFO fi ],[ AX_EXECINFO ]) if test "x$use_kqueue" != "xyes" && test "x$use_epoll" != "xyes"; then AC_MSG_ERROR([Must enable at least one of either kqueue or epoll]) fi AX_PTHREAD AC_CHECK_FUNCS([fallocate posix_fallocate accept4 pipe2 epoll_create1 kqueue1 inotify_init1]) TORRENT_ENABLE_CUSTOM_STACK_SIZE PKG_CHECK_MODULES([LIBCURL], [libcurl],, [LIBCURL_CHECK_CONFIG]) PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"]) PKG_CHECK_MODULES([ZLIB], [zlib]) CFLAGS="$LIBCURL_CFLAGS $PTHREAD_CFLAGS $ZLIB_CFLAGS $CFLAGS" CXXFLAGS="$LIBCURL_CFLAGS $LIBCURL_CPPFLAGS $PTHREAD_CFLAGS $ZLIB_CFLAGS $CXXFLAGS" LIBS="$LIBCURL_LIBS $ATOMIC_LIBS $PTHREAD_LIBS $ZLIB_LIBS $LIBS" TORRENT_CHECK_OPENSSL TORRENT_DISABLE_PTHREAD_SETNAME_NP TORRENT_MINCORE TORRENT_ENABLE_INSTRUMENTATION LIBTORRENT_LIBS="-ltorrent" LIBTORRENT_CFLAGS="" AC_SUBST(LIBTORRENT_LIBS) AC_SUBST(LIBTORRENT_CFLAGS) AC_DEFINE(HAVE_CONFIG_H, 1, true if config.h was included) AC_CONFIG_FILES([ libtorrent.pc Makefile src/Makefile src/torrent/Makefile test/Makefile ]) AC_OUTPUT libtorrent-0.16.17/doc/000077500000000000000000000000001522271512000146145ustar00rootroot00000000000000libtorrent-0.16.17/doc/RELEASE_CHECKLIST000066400000000000000000000005371522271512000172150ustar00rootroot00000000000000Just some personal notes on what needs to be done when making a release. * Download a couple torrents using Valgrind. * Check both client and library version number. * Commit any changes. * Package releases. * Upload to host. * Update frontpage. * Download, compile and test. * Tag the release in SVN. * Send mail to the ML, update ChangeLog page. libtorrent-0.16.17/doc/http.xml000066400000000000000000000047711522271512000163260ustar00rootroot00000000000000 Http handler
Introduction LibTorrent depends on the client to handle http downloads, thus the library does not have a dependency on any specific http library. The library provides a base class named torrent::Http with virtual member functions that the client must implement, and a sigc++ slot which must be set to create an instance of the derived torrent::Http class when called. The torrent::Http class and the factory slot related functions can be found in the header "torrent/http.h". The http handler should have reasonable connection timeouts, be non-blocking and not do reconnects on failed downloads.
Factory Slot The client registers the desired factory slot with the static torrent::Http::set_factory member function. Using sigc++ the client may bind values to the arguments of their function to avoid depending on globals. The factory slot must return a pointer to a new instance with the base type torrent::Http, and the caller takes responsibility of deleting the object. (Note: consider making the cleanup a slot)
Output Stream The data downloaded by the http handler is to be written to torrent::Http::m_stream which is a pointer to an std::iostream. The http handler must not change any of the flags on the stream.
Start Http::start is called by the library when it wishes to initiate a http download. Your Http derived class must implement this function. It must be non-blocking and thread-safe. This means that if a seperate thread is used for downloading then it must not emit any signal while the main thread is inside the library.
close Http::close is used bu the library to stop and close a download. No signals may be emited after this. Http::m_data should not be cleared. The library may clear the Http::m_data pointer after this.
Signals There are two mutually exclusive signals that are called when the download has stopped. The signal torrent::Http::m_signalDone is called if the download was successful and torrent::Http::m_stream contains the complete data. Or if the download was unsuccessful for some reason, then torrent::Http::m_signalFailed is called with an error message.
libtorrent-0.16.17/doc/main.xml000066400000000000000000000003541522271512000162640ustar00rootroot00000000000000 ]> libTorrent Manual &torrent_section; &http_section; libtorrent-0.16.17/doc/multitracker-spec.txt000066400000000000000000000045461522271512000210240ustar00rootroot00000000000000 MULTITRACKER METADATA ENTRY SPECIFICATION ========================================= This specification is for John Hoffman's proposed extension to the BitTorrent metadata format. This extension is not official as of this writing. * "announce-list" In addition to the standard "announce" key, in the main area of the metadata file and not part of the "info" section, will be a new key, "announce-list". This key will refer to a list of lists of URLs, and will contain a list of tiers of announces. If the client is compatible with the multitracker specification, and if the "announce-list" key is present, the client will ignore the "announce" key and only use the URLs in "announce-list". * order of processing The tiers of announces will be processed sequentially; all URLs in each tier must be checked before the client goes on to the next tier. URLs within each tier will be processed in a randomly chosen order; in other words, the list will be shuffled when first read, and then parsed in order. In addition, if a connection with a tracker is successful, it will be moved to the front of the tier. * examples. d['announce-list'] = [ [tracker1], [backup1], [backup2] ] On each announce, first try tracker1, then if that cannot be reached, try backup1 and backup2 respectively. On the next announce, repeat in the same order. This is meant for when the trackers are standard and can not share information. d['announce-list'] = [[ tracker1, tracker2, tracker3 ]] First, shuffle the list. (For argument's sake, we'll say the list has already been shuffled.) Then, if tracker1 cannot be reached, try tracker2. If tracker2 can be reached, the list is now: tracker2,tracker1,tracker3. From then on, this will be the order the client tries. If later neither tracker2 nor tracker1 can be reached, but tracker3 responds, then the list will be changed to: tracker3,tracker2,tracker1, and will be tried in that order in the future. This form is meant for trackers which can trade peer information and will cause the clients to help balance the load between the trackers. d['announce-list'] = [ [ tracker1, tracker2 ], [backup1] ] The first tier, consisting of tracker1 and tracker2, is shuffled. Both trackers 1 and 2 will be tried on each announce (though perhaps in varying order) before the client tries to reach backup1.libtorrent-0.16.17/doc/torrent.xml000066400000000000000000000063651522271512000170450ustar00rootroot00000000000000 Torrent
State
Closed This is the initial state of a download. When switching to this mode, all tracker requests are closed and the bitfield of completed chunks is cleared. File paths can only be changed in this state. Functions for getting information on bitfields, chunk count and various others will return size 0 in this state. (TODO: Check which)
torrent::Download::is_open() == false; torrent::Download::is_active() == false; torrent::Download::is_tracker_busy() == false;
Open This is the state after a successfull call to torrent::Download::open(). This function throws torrent::local_error if the download could not be opened. All files in the download have been created and are open. The initial hash check must be done to get a valid bitfield of completed chunks.
torrent::Download::is_open() == true; torrent::Download::is_active() == false;
Active A download is active after calling torrent::Download::start(). Only downloads that are in an open state and has a valid bitfield of completed chunks can be activated.
torrent::Download::is_open() == true; torrent::Download::is_active() == true; torrent::Download::is_hash_checked() == true;
A tracker request will be made when torrent::Download::stop() is called on an active download. It is not required to wait for the tracker request to finish before calling torrent::Download::close(), but it is recommened so the tracker knows this client is not available.
File Paths The paths of files in a Download consists of two parts, the root directory and the paths of each file. The file paths are read from the torrent file and the files usually reside in the root directory. The root directory is by default "./" for single file torrents and "./[torrent_name]/" for multi-file torrents.
// Get and set the root directory. std::string torrent::Download::get_root_dir(); void torrent::Download::set_root_dir(const std::string& dir); // Get the torrent::Entry class for each file in the download. torrent::Entry torrent::Download::get_entry(uint32_t index); uint32_t torrent::Download::get_entry_size(); typedef std::list<std::string> torrent::Entry::Path; // Get and set the file path. std::string torrent::Entry::get_path(); const Path& torrent::Entry::get_path_list(); void torrent::Entry::set_path_list(const Path& l);
The modifications can only be done while the download is in a closed state. Modifying the file paths will not change the "info hash" part of the bencode'd torrent associated with the download. (TODO: When exporting, save root directory and file paths to another section of the torrent)
libtorrent-0.16.17/extra/000077500000000000000000000000001522271512000151725ustar00rootroot00000000000000libtorrent-0.16.17/extra/bt/000077500000000000000000000000001522271512000155775ustar00rootroot00000000000000libtorrent-0.16.17/extra/bt/bt.sh000077500000000000000000000001741522271512000165450ustar00rootroot00000000000000#!/bin/sh mkdir $1 cd $1 nice -n11 btdownloadheadless.py --max_upload_rate $1 --url file:///tmp/bt/t.torrent > /dev/null & libtorrent-0.16.17/extra/bt/run.sh000077500000000000000000000000761522271512000167450ustar00rootroot00000000000000#!/bin/sh for ((i = 0; i < 10; i++)); do bt.sh down$i ; done libtorrent-0.16.17/extra/compile_object.sh000077500000000000000000000005711522271512000205120ustar00rootroot00000000000000STUFF="-Wall -O2 -I.. -I../src/ -lcrypto `pkg-config --libs-only-L openssl` `pkg-config --cflags openssl`" g++ $STUFF -DNEW_OBJECT -o test_object test_object.cc object.cc ../src/torrent/object_stream.cc ../src/torrent/exceptions.cc g++ $STUFF -DOLD_OBJECT -o test_object_old test_object.cc ../src/torrent/object.cc ../src/torrent/object_stream.cc ../src/torrent/exceptions.cc libtorrent-0.16.17/extra/compile_partial_queue.sh000077500000000000000000000001021522271512000220720ustar00rootroot00000000000000g++ -Wall -O2 -g -I.. -o test_partial_queue test_partial_queue.cc libtorrent-0.16.17/extra/corrupt_file.cc000066400000000000000000000020531522271512000201760ustar00rootroot00000000000000#include #include #include #include #include #include #include "../rak/file_stat.h" void corrupt_region(int fd, int pos, int length) { char buf[length]; for (char *first = buf, *last = buf + length; first != last; ++first) *first = '\0'; if (write(fd, buf, length) == -1) throw std::runtime_error("Could not write data to file."); } int main(int argc, char** argv) { if (argc != 4) throw std::runtime_error("Too few arguments."); int seed; int length; if (sscanf(argv[1], "%i", &seed) != 1) throw std::runtime_error("Could not parse seed."); if (sscanf(argv[2], "%i", &length) != 1) throw std::runtime_error("Could not parse length."); int fd = open(argv[3], O_RDWR); if (fd == -1) throw std::runtime_error("Could not open file."); rak::file_stat fileStat; if (!fileStat.update(fd)) throw std::runtime_error("Could not read fs stat."); srand(seed); corrupt_region(fd, rand() % (fileStat.size() - length), length); return 0; } libtorrent-0.16.17/extra/object.cc000066400000000000000000000066021522271512000167530ustar00rootroot00000000000000#include "config.h" #include #include "object.h" namespace torrent { Object::Object(const Object& b) : m_state(b.type()) { switch (type()) { case type_none: break; case type_value: m_value = b.m_value; break; case type_string: m_string = new string_type(*b.m_string); break; case type_list: m_list = new list_type(*b.m_list); break; case type_map: m_map = new map_type(*b.m_map); break; } } Object& Object::operator = (const Object& src) { if (&src == this) return *this; clear(); m_state = src.m_state; switch (type()) { case type_none: break; case type_value: m_value = src.m_value; break; case type_string: m_string = new string_type(*src.m_string); break; case type_list: m_list = new list_type(*src.m_list); break; case type_map: m_map = new map_type(*src.m_map); break; } return *this; } void Object::clear() { switch (type()) { case type_none: case type_value: break; case type_string: delete m_string; break; case type_list: delete m_list; break; case type_map: delete m_map; break; } m_state = type_none; } Object& Object::get_key(const std::string& k) { check_throw(type_map); map_type::iterator itr = m_map->find(k); if (itr == m_map->end()) throw bencode_error("Object operator [" + k + "] could not find element"); return itr->second; } const Object& Object::get_key(const std::string& k) const { check_throw(type_map); map_type::const_iterator itr = m_map->find(k); if (itr == m_map->end()) throw bencode_error("Object operator [" + k + "] could not find element"); return itr->second; } Object& Object::move(Object& src) { if (this == &src) return *this; clear(); std::memcpy(this, &src, sizeof(Object)); std::memset(&src, 0, sizeof(Object)); return *this; } Object& Object::swap(Object& src) { char tmp[sizeof(Object)]; std::memcpy(tmp, &src, sizeof(Object)); std::memcpy(&src, this, sizeof(Object)); std::memcpy(this, tmp, sizeof(Object)); return *this; } Object& Object::merge_copy(const Object& object, uint32_t maxDepth) { if (maxDepth == 0) return (*this = object); if (object.is_map()) { if (!is_map()) *this = Object(type_map); map_type& dest = as_map(); map_type::iterator destItr = dest.begin(); map_type::const_iterator srcItr = object.as_map().begin(); map_type::const_iterator srcLast = object.as_map().end(); while (srcItr != srcLast) { destItr = std::find_if(destItr, dest.end(), [&srcItr](const map_type::value_type& dest) { return srcItr->first <= dest.first; }); if (srcItr->first < destItr->first) // destItr remains valid and pointing to the next possible // position. dest.insert(destItr, *srcItr); else destItr->second.merge_copy(srcItr->second, maxDepth - 1); srcItr++; } } else if (object.is_list()) { if (!is_list()) *this = Object(type_list); list_type& dest = as_list(); list_type::iterator destItr = dest.begin(); list_type::const_iterator srcItr = object.as_list().begin(); list_type::const_iterator srcLast = object.as_list().end(); while (srcItr != srcLast) { if (destItr == dest.end()) destItr = dest.insert(destItr, *srcItr); else destItr->merge_copy(*srcItr, maxDepth - 1); destItr++; } } else { *this = object; } return *this; } } libtorrent-0.16.17/extra/object.h000066400000000000000000000210431522271512000166110ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2007, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_OBJECT_H #define LIBTORRENT_OBJECT_H #include #include #include #include #include #include namespace torrent { // Add support for the GCC move semantics. // // Not today? class ObjectRefRef; class LIBTORRENT_EXPORT Object { public: typedef int64_t value_type; typedef std::string string_type; typedef std::list list_type; typedef std::map map_type; typedef map_type::key_type key_type; // Figure a better name for this? typedef uint32_t state_type; enum type_type { TYPE_NONE, TYPE_VALUE, TYPE_STRING, TYPE_LIST, TYPE_MAP }; // The users of the library should only ever set/get the bits in the // public mask, else risk waking dragons. // // Consider if part of the mask should be made to clear upon copy, // and do so for both private and public? static const state_type mask_private = 0x0000ffff; static const state_type mask_public = 0xffff0000; static const state_type mask_type = 0x000000ff; // static const state_type flag_const = 0x000000; static const state_type flag_reference = 0x100; static const state_type type_none = TYPE_NONE; static const state_type type_value = TYPE_VALUE; static const state_type type_string = TYPE_STRING; static const state_type type_list = TYPE_LIST; static const state_type type_map = TYPE_MAP; // Add ctors that take a use_copy, use_move and use_internal_move // parameters. Object() : m_state(type_none) {} Object(const value_type v) : m_state(type_value), m_value(v) {} // Don't inline these. Object(const char* s) : m_state(type_string), m_string(new string_type(s)) {} Object(const string_type& s) : m_state(type_string), m_string(new string_type(s)) {} Object(const Object& b); // Hmm... reconsider this. explicit Object(type_type t); ~Object() { clear(); } void clear(); type_type type() const { return (type_type)(m_state & mask_type); } bool is_value() const { return type() == type_value; } bool is_string() const { return type() == type_string; } bool is_list() const { return type() == type_list; } bool is_map() const { return type() == type_map; } bool is_reference() const { return m_state & flag_reference; } // Add _mutable_ to non-const access. value_type& as_value() { check_throw(type_value); return m_value; } const value_type& as_value() const { check_throw(type_value); return m_value; } string_type& as_string() { check_throw(type_string); return *m_string; } const string_type& as_string() const { check_throw(type_string); return *m_string; } list_type& as_list() { check_throw(type_list); return *m_list; } const list_type& as_list() const { check_throw(type_list); return *m_list; } map_type& as_map() { check_throw(type_map); return *m_map; } const map_type& as_map() const { check_throw(type_map); return *m_map; } // Map operations: bool has_key(const key_type& k) const { check_throw(type_map); return m_map->find(k) != m_map->end(); } bool has_key_value(const key_type& k) const { check_throw(type_map); return check(m_map->find(k), type_value); } bool has_key_string(const key_type& k) const { check_throw(type_map); return check(m_map->find(k), type_string); } bool has_key_list(const key_type& k) const { check_throw(type_map); return check(m_map->find(k), type_list); } bool has_key_map(const key_type& k) const { check_throw(type_map); return check(m_map->find(k), type_map); } Object& get_key(const key_type& k); const Object& get_key(const key_type& k) const; value_type& get_key_value(const key_type& k) { return get_key(k).as_value(); } const value_type& get_key_value(const key_type& k) const { return get_key(k).as_value(); } string_type& get_key_string(const key_type& k) { return get_key(k).as_string(); } const string_type& get_key_string(const key_type& k) const { return get_key(k).as_string(); } list_type& get_key_list(const key_type& k) { return get_key(k).as_list(); } const list_type& get_key_list(const key_type& k) const { return get_key(k).as_list(); } map_type& get_key_map(const key_type& k) { return get_key(k).as_map(); } const map_type& get_key_map(const key_type& k) const { return get_key(k).as_map(); } Object& insert_key(const key_type& k, const Object& b) { check_throw(type_map); return (*m_map)[k] = b; } void erase_key(const key_type& k) { check_throw(type_map); m_map->erase(k); } // List operations: Object& insert_front(const Object& b) { check_throw(type_list); return *m_list->insert(m_list->begin(), b); } Object& insert_back(const Object& b) { check_throw(type_list); return *m_list->insert(m_list->end(), b); } // Copy and merge operations: Object& move(Object& b); Object& swap(Object& b); // Only map entries are merged. Object& merge_move(Object object, uint32_t maxDepth = ~uint32_t()); Object& merge_copy(const Object& object, uint32_t maxDepth = ~uint32_t()); Object& operator = (const Object& b); private: inline bool check(map_type::const_iterator itr, state_type t) const; inline void check_throw(state_type t) const; state_type m_state; union { int64_t m_value; string_type* m_string; list_type* m_list; map_type* m_map; }; }; // Inline? // // Or just replace with specific ctors? inline Object::Object(type_type t) : m_state(t) { switch (t) { case TYPE_NONE: break; case TYPE_VALUE: m_value = value_type(); break; case TYPE_STRING: m_string = new string_type(); break; case TYPE_LIST: m_list = new list_type(); break; case TYPE_MAP: m_map = new map_type(); break; } } inline bool Object::check(map_type::const_iterator itr, state_type t) const { return itr != m_map->end() && itr->second.type() == t; } inline void Object::check_throw(state_type t) const { if (t != type()) throw bencode_error("Wrong object type."); } } #endif libtorrent-0.16.17/extra/posix_fallocate.cc000066400000000000000000000010771522271512000206620ustar00rootroot00000000000000#include #include #include "../src/utils/timer.h" namespace torrent { int64_t Timer::m_cache; } #define FILE_SIZE ((off_t)1 << 20) int main() { int fd = open("./posix_fallocate.out", O_CREAT | O_RDWR); if (ftruncate(fd, FILE_SIZE)) { std::cout << "truncate failed" << std::endl; return -1; } torrent::Timer t1 = torrent::Timer::current(); int v = posix_fallocate(fd, 0, FILE_SIZE); torrent::Timer t2 = torrent::Timer::current(); std::cout << "Return: " << v << " Time: " << (t2 - t1).usec() << std::endl; return 0; } libtorrent-0.16.17/extra/test_object.cc000066400000000000000000000076761522271512000200260ustar00rootroot00000000000000#include #include #include "../src/torrent/object_stream.h" #ifdef NEW_OBJECT #include "object.h" typedef torrent::Object return_type; //#define OBJECTREF_MOVE(x) torrent::ObjectRef::move(x) #define OBJECTREF_MOVE(x) x #else #include "../src/torrent/object.h" typedef torrent::Object return_type; #define OBJECTREF_MOVE(x) x #endif #define TIME_WRAPPER(name, body) \ rak::timer \ time_##name(unsigned int n) { \ rak::timer started = rak::timer::current(); \ \ for (unsigned int i = 0; i < n; i++) { \ body; \ } \ \ return rak::timer::current() - started; \ } typedef std::list std_list_type; void f() {} torrent::Object func_create_string_20() { return torrent::Object("12345678901234567890"); } std::string func_create_std_string_20() { return "12345678901234567890"; } return_type func_create_string_list_20() { torrent::Object tmp(torrent::Object::TYPE_LIST); torrent::Object::list_type& listRef = tmp.as_list(); for (int i = 0; i < 10; i++) listRef.push_back(torrent::Object("12345678901234567890")); return OBJECTREF_MOVE(tmp); } std_list_type func_create_std_string_list_20() { std_list_type tmp(torrent::Object::TYPE_LIST); for (int i = 0; i < 10; i++) tmp.push_back("12345678901234567890"); return tmp; } torrent::Object stringList20(func_create_string_list_20()); // return_type // func_copy_string_list_20_f() { // torrent::Object tmp(stringList20); // return OBJECTREF_MOVE(tmp); // } torrent::Object tmp1; return_type func_copy_string_list_20() { tmp1 = stringList20; return OBJECTREF_MOVE(tmp1); } TIME_WRAPPER(dummy, f(); ) TIME_WRAPPER(string, torrent::Object s("12345678901234567890"); ) TIME_WRAPPER(std_string, std::string s("12345678901234567890"); ) TIME_WRAPPER(return_string, torrent::Object s = func_create_string_20(); ) TIME_WRAPPER(return_std_string, std::string s = func_create_std_string_20(); ) TIME_WRAPPER(return_string_list, torrent::Object s(func_create_string_list_20()); ) TIME_WRAPPER(return_std_string_list, std_list_type s(func_create_std_string_list_20()); ) TIME_WRAPPER(copy_string_list, torrent::Object s(func_copy_string_list_20()); ) int main(int argc, char** argv) { // std::cout << "sizeof(torrent::Object): " << sizeof(torrent::Object) << std::endl; // std::cout << "sizeof(torrent::Object::value_type): " << sizeof(torrent::Object::value_type) << std::endl; // std::cout << "sizeof(torrent::Object::string_type): " << sizeof(torrent::Object::string_type) << std::endl; // std::cout << "sizeof(torrent::Object::map_type): " << sizeof(torrent::Object::map_type) << std::endl; // std::cout << "sizeof(torrent::Object::list_type): " << sizeof(torrent::Object::list_type) << std::endl; std::cout.setf(std::ios::right, std::ios::adjustfield); std::cout << "time_dummy: " << std::setw(8) << time_dummy(100000).usec() << std::endl; std::cout << "time_string: " << std::setw(8) << time_string(100000).usec() << std::endl; std::cout << "time_std_string: " << std::setw(8) << time_std_string(100000).usec() << std::endl; std::cout << "time_return_string: " << std::setw(8) << time_return_string(100000).usec() << std::endl; std::cout << "time_return_std_string: " << std::setw(8) << time_return_std_string(100000).usec() << std::endl; std::cout << std::endl; std::cout << "time_return_string_list: " << std::setw(8) << time_return_string_list(100000).usec() << std::endl; std::cout << "time_return_std_string_list: " << std::setw(8) << time_return_std_string_list(100000).usec() << std::endl; std::cout << "time_copy_string_list: " << std::setw(8) << time_copy_string_list(100000).usec() << std::endl; return 0; } libtorrent-0.16.17/extra/test_partial_queue.cc000066400000000000000000000031541522271512000214030ustar00rootroot00000000000000#include #include #include #include void test_fill() { rak::partial_queue queue; queue.enable(8); queue.clear(); std::cout << "test_fill()" << std::endl; std::cout << " inserting: "; int i = 0; while (true) { uint8_t k = rand() % 256; if (queue.insert(k, k)) std::cout << '[' << (uint32_t)k << ']' << ' '; // else // std::cout << '<' << (uint32_t)k << '>' << ' '; if (queue.is_full() && ++i == 100) break; } std::cout << std::endl << " popping: "; while (queue.prepare_pop()) { std::cout << queue.pop() << ' '; } std::cout << std::endl; } void test_random() { rak::partial_queue queue; queue.enable(8); queue.clear(); std::cout << "test_random()" << std::endl; std::cout << " inserting: "; for (int i = 0; i < 10 && !queue.is_full(); ++i) { uint8_t k = rand() % 256; if (queue.insert(k, k)) std::cout << '[' << (uint32_t)k << ']' << ' '; else std::cout << '<' << (uint32_t)k << '>' << ' '; } std::cout << std::endl << " popping: "; while (queue.prepare_pop()) { if (rand() % 2) { std::cout << queue.pop() << ' '; } else { uint8_t k = rand() % 128; if (queue.insert(k, k)) std::cout << '[' << (uint32_t)k << ']' << ' '; else std::cout << '<' << (uint32_t)k << '>' << ' '; } } std::cout << std::endl; } int main(int argc, char** argv) { srand(rak::timer::current().usec()); std::cout << "sizeof(rak::partial_queue): " << sizeof(rak::partial_queue) << std::endl; test_fill(); test_random(); return 0; } libtorrent-0.16.17/extra/test_queue.cc000066400000000000000000000030711522271512000176650ustar00rootroot00000000000000#include #include #include rak::priority_queue_default queue;//(priority_compare(), priority_equal(), priority_erase()); rak::priority_item items[100]; int last = 0; class test { public: test() {} void f() { std::cout << "Called: " << std::endl; } }; void print_item(rak::priority_item* p) { std::cout << (p - items) << ' ' << p->time().usec() << std::endl; if (p->time().usec() < last) { std::cout << "order bork." << std::endl; exit(-1); } last = p->time().usec(); p->clear_time(); if (std::rand() % 5) { int i = rand() % 100; std::cout << "erase " << i << ' ' << items[i].time().usec() << std::endl; priority_queue_erase(&queue, items + i); } } int main() { try { test t; for (rak::priority_item* first = items, *last = items + 100; first != last; ++first) { first->set_slot([&t] { t.f(); }); priority_queue_insert(&queue, first, (std::rand() % 50) + 1); } // std::vector due; // std::copy(rak::queue_popper(queue, rak::bind2nd(std::mem_fun(&rak::priority_item::compare), 20)), // rak::queue_popper(queue, rak::bind2nd(std::mem_fun(&rak::priority_item::compare), rak::timer())), // std::back_inserter(due)); // std::for_each(due.begin(), due.end(), std::ptr_fun(&print_item)); while (!queue.empty()) { rak::priority_item* i = queue.top(); queue.pop(); print_item(i); } } catch (std::logic_error& e) { std::cout << "Exception: " << e.what() << std::endl; } return 0; } libtorrent-0.16.17/extra/test_ranges.cc000066400000000000000000000015461522271512000200250ustar00rootroot00000000000000#include #include #include "../rak/ranges.h" void print_ranges(rak::ranges& r) { rak::ranges::iterator itr = r.begin(); std::cout << std::endl; while (itr != r.end()) { std::cout << itr->first << ' ' << itr->second << std::endl; ++itr; } } int main() { rak::ranges r; r.insert(10, 20); r.insert(30, 40); r.insert(50, 60); assert(r.has(10) && !r.has(20) && r.has(30) && !r.has(40)); r.insert(0, 5); assert(r.has(2) && !r.has(5) && r.has(10)); r.insert(5, 30); assert(r.has(0) && r.has(7) && r.has(29) && r.has(30) && !r.has(40)); print_ranges(r); // r.erase(15, 55); // print(r); // assert(r.size() == 3); // assert(r.has(14)); // assert(!r.has(15)); // assert(!r.has(54)); // assert(r.has(55)); // r.insert(5, 60); // print(r); return 0; } libtorrent-0.16.17/extra/test_sockaddr.cc000066400000000000000000000041441522271512000203350ustar00rootroot00000000000000#include #include #include "../src/torrent/object.h" void print_addr(const char* name, const rak::socket_address_inet& sa) { std::cout << name << ": " << sa.family() << ' ' << sa.address_str() << ':' << sa.port() << std::endl; } bool lookup_address(const char* name) { rak::address_info* result; std::cout << "Lookup: " << name << std::endl; // int errcode = rak::address_info::get_address_info(name, 0, 0, &result); int errcode = rak::address_info::get_address_info(name, PF_INET6, 0, &result); if (errcode != 0) { std::cout << "Failed: " << rak::address_info::strerror(errcode) << std::endl; return false; } for (rak::address_info* itr = result; itr != NULL; itr = itr->next()) { std::cout << "Flags: " << itr->flags() << std::endl; std::cout << "Family: " << itr->family() << std::endl; std::cout << "Socket Type: " << itr->socket_type() << std::endl; std::cout << "Protocol: " << itr->protocol() << std::endl; std::cout << "Length: " << itr->length() << std::endl; std::cout << "Address: " << itr->address()->family() << ' ' << itr->address()->address_str() << ':' << itr->address()->port() << std::endl; } // Release. freeaddrinfo(reinterpret_cast(result)); return true; } int main(int argc, char** argv) { std::cout << "sizeof(sockaddr_in): " << sizeof(sockaddr_in) << std::endl; std::cout << "sizeof(sockaddr_in6): " << sizeof(sockaddr_in6) << std::endl; rak::socket_address saNone; saNone.set_family(); print_addr("none", *saNone.sa_inet()); rak::socket_address_inet sa1; sa1.set_family(); sa1.set_port(0); sa1.set_address_any(); print_addr("sa1", sa1); rak::socket_address_inet sa2; sa2.set_family(); sa2.set_port(12345); if (!sa2.set_address_str("123.45.67.255")) return -1; print_addr("sa2", sa2); rak::socket_address sa3; sa3.sa_inet()->set_family(); sa3.sa_inet()->set_port(6999); sa3.sa_inet()->set_address_str("127.0.0.2"); print_addr("sa3", *sa3.sa_inet()); lookup_address("www.uio.no"); lookup_address("www.ipv6.org"); lookup_address("lampedusa"); return 0; } libtorrent-0.16.17/libtorrent.pc.in000066400000000000000000000004621522271512000171660ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libtorrent Description: A BitTorrent library Version: @VERSION@ Requires.private: zlib, libcrypto, libcurl Libs: -L${libdir} -ltorrent Libs.private: @PTHREAD_LIBS@ @ATOMIC_LIBS@ @LIBCURL_LIBS@ Cflags: -I${includedir} libtorrent-0.16.17/scripts/000077500000000000000000000000001522271512000155365ustar00rootroot00000000000000libtorrent-0.16.17/scripts/attributes.m4000066400000000000000000000025521522271512000201720ustar00rootroot00000000000000# Functions to check for attributes support in compiler AC_DEFUN([CC_ATTRIBUTE_INTERNAL], [ AC_CACHE_CHECK([if compiler supports __attribute__((visibility("internal")))], [cc_cv_attribute_internal], [AC_COMPILE_IFELSE([AC_LANG_SOURCE([ void __attribute__((visibility("internal"))) internal_function() { } ])], [cc_cv_attribute_internal=yes], [cc_cv_attribute_internal=no]) ]) if test "x$cc_cv_attribute_internal" = "xyes"; then AC_DEFINE([SUPPORT_ATTRIBUTE_INTERNAL], 1, [Define this if the compiler supports the internal visibility attribute]) $1 else true $2 fi ]) AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [ AC_LANG_PUSH(C++) tmp_CXXFLAGS=$CXXFLAGS CXXFLAGS="$CXXFLAGS -fvisibility=hidden" AC_CACHE_CHECK([if compiler supports __attribute__((visibility("default")))], [cc_cv_attribute_visibility], [AC_COMPILE_IFELSE([AC_LANG_SOURCE([ void __attribute__((visibility("default"))) visibility_function() { } ])], [cc_cv_attribute_visibility=yes], [cc_cv_attribute_visibility=no]) ]) CXXFLAGS=$tmp_CXXFLAGS AC_LANG_POP(C++) if test "x$cc_cv_attribute_visibility" = "xyes"; then AC_DEFINE([SUPPORT_ATTRIBUTE_VISIBILITY], 1, [Define this if the compiler supports the visibility attributes.]) CXXFLAGS="$CXXFLAGS -fvisibility=hidden" $1 else true $2 fi ]) libtorrent-0.16.17/scripts/ax_cxx_compile_stdcxx.m4000066400000000000000000000550171522271512000224070ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and # CXXCPP to enable support. VERSION may be '11', '14', '17', '20', or # '23' for the respective C++ standard version. # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with # preference for no added switch, and then for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is # required and that the macro should error out if no mode with that # support is found. If specified 'optional', then configuration proceeds # regardless, after defining HAVE_CXX${VERSION} if and only if a # supporting mode is found. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # Copyright (c) 2019 Enji Cooper # Copyright (c) 2020 Jason Merrill # Copyright (c) 2021, 2024 Jörn Heusipp # Copyright (c) 2015, 2022, 2023, 2024 Olly Betts # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 25 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], [$1], [14], [ax_cxx_compile_alternatives="14 1y"], [$1], [17], [ax_cxx_compile_alternatives="17 1z"], [$1], [20], [ax_cxx_compile_alternatives="20"], [$1], [23], [ax_cxx_compile_alternatives="23"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], [$2], [noext], [], [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], [$3], [optional], [ax_cxx_compile_cxx$1_required=false], [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) AC_LANG_PUSH([C++])dnl ac_success=no m4_if([$2], [], [dnl AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, ax_cv_cxx_compile_cxx$1, [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [ax_cv_cxx_compile_cxx$1=yes], [ax_cv_cxx_compile_cxx$1=no])]) if test x$ax_cv_cxx_compile_cxx$1 = xyes; then ac_success=yes fi]) m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do switch="-std=gnu++${alternative}" cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done fi]) m4_if([$2], [ext], [], [dnl if test x$ac_success = xno; then dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" dnl MSVC needs -std:c++NN for C++17 and later (default is C++14) for alternative in ${ax_cxx_compile_alternatives}; do for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}" MSVC; do if test x"$switch" = xMSVC; then dnl AS_TR_SH maps both `:` and `=` to `_` so -std:c++17 would collide dnl with -std=c++17. We suffix the cache variable name with _MSVC to dnl avoid this. switch=-std:c++${alternative} cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_${switch}_MSVC]) else cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) fi AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done if test x$ac_success = xyes; then break fi done fi]) AC_LANG_POP([C++]) if test x$ax_cxx_compile_cxx$1_required = xtrue; then if test x$ac_success = xno; then AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) fi fi if test x$ac_success = xno; then HAVE_CXX$1=0 AC_MSG_NOTICE([No compiler with C++$1 support was found]) else HAVE_CXX$1=1 AC_DEFINE(HAVE_CXX$1,1, [define if the compiler supports basic C++$1 syntax]) fi AC_SUBST(HAVE_CXX$1) ]) dnl Test body for checking C++11 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11] ) dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14] ) dnl Test body for checking C++17 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17] ) dnl Test body for checking C++20 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 _AX_CXX_COMPILE_STDCXX_testbody_new_in_20] ) dnl Test body for checking C++23 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_23], [_AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 _AX_CXX_COMPILE_STDCXX_testbody_new_in_23] ) dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ // If the compiler admits that it is not ready for C++11, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" // MSVC always sets __cplusplus to 199711L in older versions; newer versions // only set it correctly if /Zc:__cplusplus is specified as well as a // /std:c++NN switch: // // https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ // // The value __cplusplus ought to have is available in _MSVC_LANG since // Visual Studio 2015 Update 3: // // https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros // // This was also the first MSVC version to support C++14 so we can't use the // value of either __cplusplus or _MSVC_LANG to quickly rule out MSVC having // C++11 or C++14 support, but we can check _MSVC_LANG for C++17 and later. #elif __cplusplus < 201103L && !defined _MSC_VER #error "This is not a C++11 compiler" #else namespace cxx11 { namespace test_static_assert { template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; } namespace test_final_override { struct Base { virtual ~Base() {} virtual void f() {} }; struct Derived : public Base { virtual ~Derived() override {} virtual void f() override {} }; } namespace test_double_right_angle_brackets { template < typename T > struct check {}; typedef check single_type; typedef check> double_type; typedef check>> triple_type; typedef check>>> quadruple_type; } namespace test_decltype { int f() { int a = 1; decltype(a) b = 2; return a + b; } } namespace test_type_deduction { template < typename T1, typename T2 > struct is_same { static const bool value = false; }; template < typename T > struct is_same { static const bool value = true; }; template < typename T1, typename T2 > auto add(T1 a1, T2 a2) -> decltype(a1 + a2) { return a1 + a2; } int test(const int c, volatile int v) { static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == false, ""); auto ac = c; auto av = v; auto sumi = ac + av + 'x'; auto sumf = ac + av + 1.0; static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == true, ""); return (sumf > 0.0) ? sumi : add(c, v); } } namespace test_noexcept { int f() { return 0; } int g() noexcept { return 0; } static_assert(noexcept(f()) == false, ""); static_assert(noexcept(g()) == true, ""); } namespace test_constexpr { template < typename CharT > unsigned long constexpr strlen_c_r(const CharT *const s, const unsigned long acc) noexcept { return *s ? strlen_c_r(s + 1, acc + 1) : acc; } template < typename CharT > unsigned long constexpr strlen_c(const CharT *const s) noexcept { return strlen_c_r(s, 0UL); } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("1") == 1UL, ""); static_assert(strlen_c("example") == 7UL, ""); static_assert(strlen_c("another\0example") == 7UL, ""); } namespace test_rvalue_references { template < int N > struct answer { static constexpr int value = N; }; answer<1> f(int&) { return answer<1>(); } answer<2> f(const int&) { return answer<2>(); } answer<3> f(int&&) { return answer<3>(); } void test() { int i = 0; const int c = 0; static_assert(decltype(f(i))::value == 1, ""); static_assert(decltype(f(c))::value == 2, ""); static_assert(decltype(f(0))::value == 3, ""); } } namespace test_uniform_initialization { struct test { static const int zero {}; static const int one {1}; }; static_assert(test::zero == 0, ""); static_assert(test::one == 1, ""); } namespace test_lambdas { void test1() { auto lambda1 = [](){}; auto lambda2 = lambda1; lambda1(); lambda2(); } int test2() { auto a = [](int i, int j){ return i + j; }(1, 2); auto b = []() -> int { return '0'; }(); auto c = [=](){ return a + b; }(); auto d = [&](){ return c; }(); auto e = [a, &b](int x) mutable { const auto identity = [](int y){ return y; }; for (auto i = 0; i < a; ++i) a += b--; return x + identity(a + b); }(0); return a + b + c + d + e; } int test3() { const auto nullary = [](){ return 0; }; const auto unary = [](int x){ return x; }; using nullary_t = decltype(nullary); using unary_t = decltype(unary); const auto higher1st = [](nullary_t f){ return f(); }; const auto higher2nd = [unary](nullary_t f1){ return [unary, f1](unary_t f2){ return f2(unary(f1())); }; }; return higher1st(nullary) + higher2nd(nullary)(unary); } } namespace test_variadic_templates { template struct sum; template struct sum { static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); static_assert(sum<1>::value == 1, ""); static_assert(sum<23>::value == 23, ""); static_assert(sum<1, 2>::value == 3, ""); static_assert(sum<5, 5, 11>::value == 21, ""); static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); } // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this. namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } } // namespace cxx11 #endif // __cplusplus >= 201103L ]]) dnl Tests for new features in C++14 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ // If the compiler admits that it is not ready for C++14, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201402L && !defined _MSC_VER #error "This is not a C++14 compiler" #else namespace cxx14 { namespace test_polymorphic_lambdas { int test() { const auto lambda = [](auto&&... args){ const auto istiny = [](auto x){ return (sizeof(x) == 1UL) ? 1 : 0; }; const int aretiny[] = { istiny(args)... }; return aretiny[0]; }; return lambda(1, 1L, 1.0f, '1'); } } namespace test_binary_literals { constexpr auto ivii = 0b0000000000101010; static_assert(ivii == 42, "wrong value"); } namespace test_generalized_constexpr { template < typename CharT > constexpr unsigned long strlen_c(const CharT *const s) noexcept { auto length = 0UL; for (auto p = s; *p; ++p) ++length; return length; } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("x") == 1UL, ""); static_assert(strlen_c("test") == 4UL, ""); static_assert(strlen_c("another\0test") == 7UL, ""); } namespace test_lambda_init_capture { int test() { auto x = 0; const auto lambda1 = [a = x](int b){ return a + b; }; const auto lambda2 = [a = lambda1(x)](){ return a; }; return lambda2(); } } namespace test_digit_separators { constexpr auto ten_million = 100'000'000; static_assert(ten_million == 100000000, ""); } namespace test_return_type_deduction { auto f(int& x) { return x; } decltype(auto) g(int& x) { return x; } template < typename T1, typename T2 > struct is_same { static constexpr auto value = false; }; template < typename T > struct is_same { static constexpr auto value = true; }; int test() { auto x = 0; static_assert(is_same::value, ""); static_assert(is_same::value, ""); return x; } } } // namespace cxx14 #endif // __cplusplus >= 201402L ]]) dnl Tests for new features in C++17 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ // If the compiler admits that it is not ready for C++17, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 201703L #error "This is not a C++17 compiler" #else #include #include #include namespace cxx17 { namespace test_constexpr_lambdas { constexpr int foo = [](){return 42;}(); } namespace test::nested_namespace::definitions { } namespace test_fold_expression { template int multiply(Args... args) { return (args * ... * 1); } template bool all(Args... args) { return (args && ...); } } namespace test_extended_static_assert { static_assert (true); } namespace test_auto_brace_init_list { auto foo = {5}; auto bar {5}; static_assert(std::is_same, decltype(foo)>::value); static_assert(std::is_same::value); } namespace test_typename_in_template_template_parameter { template typename X> struct D; } namespace test_fallthrough_nodiscard_maybe_unused_attributes { int f1() { return 42; } [[nodiscard]] int f2() { [[maybe_unused]] auto unused = f1(); switch (f1()) { case 17: f1(); [[fallthrough]]; case 42: f1(); } return f1(); } } namespace test_extended_aggregate_initialization { struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1 {{1, 2}, {}, 4}; // full initialization derived d2 {{}, {}, 4}; // value-initialized bases } namespace test_general_range_based_for_loop { struct iter { int i; int& operator* () { return i; } const int& operator* () const { return i; } iter& operator++() { ++i; return *this; } }; struct sentinel { int i; }; bool operator== (const iter& i, const sentinel& s) { return i.i == s.i; } bool operator!= (const iter& i, const sentinel& s) { return !(i == s); } struct range { iter begin() const { return {0}; } sentinel end() const { return {5}; } }; void f() { range r {}; for (auto i : r) { [[maybe_unused]] auto v = i; } } } namespace test_lambda_capture_asterisk_this_by_value { struct t { int i; int foo() { return [*this]() { return i; }(); } }; } namespace test_enum_class_construction { enum class byte : unsigned char {}; byte foo {42}; } namespace test_constexpr_if { template int f () { if constexpr(cond) { return 13; } else { return 42; } } } namespace test_selection_statement_with_initializer { int f() { return 13; } int f2() { if (auto i = f(); i > 0) { return 3; } switch (auto i = f(); i + 4) { case 17: return 2; default: return 1; } } } namespace test_template_argument_deduction_for_class_templates { template struct pair { pair (T1 p1, T2 p2) : m1 {p1}, m2 {p2} {} T1 m1; T2 m2; }; void f() { [[maybe_unused]] auto p = pair{13, 42u}; } } namespace test_non_type_auto_template_parameters { template struct B {}; B<5> b1; B<'a'> b2; } namespace test_structured_bindings { int arr[2] = { 1, 2 }; std::pair pr = { 1, 2 }; auto f1() -> int(&)[2] { return arr; } auto f2() -> std::pair& { return pr; } struct S { int x1 : 2; volatile double y1; }; S f3() { return {}; } auto [ x1, y1 ] = f1(); auto& [ xr1, yr1 ] = f1(); auto [ x2, y2 ] = f2(); auto& [ xr2, yr2 ] = f2(); const auto [ x3, y3 ] = f3(); } namespace test_exception_spec_type_system { struct Good {}; struct Bad {}; void g1() noexcept; void g2(); template Bad f(T*, T*); template Good f(T1*, T2*); static_assert (std::is_same_v); } namespace test_inline_variables { template void f(T) {} template inline T g(T) { return T{}; } template<> inline void f<>(int) {} template<> int g<>(int) { return 5; } } } // namespace cxx17 #endif // (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 201703L ]]) dnl Tests for new features in C++20 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ #ifndef __cplusplus #error "This is not a C++ compiler" #elif (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 202002L #error "This is not a C++20 compiler" #else #include namespace cxx20 { // As C++20 supports feature test macros in the standard, there is no // immediate need to actually test for feature availability on the // Autoconf side. } // namespace cxx20 #endif // (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 202002L ]]) dnl Tests for new features in C++23 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_23], [[ #ifndef __cplusplus #error "This is not a C++ compiler" #elif (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 202302L #error "This is not a C++23 compiler" #else #include namespace cxx23 { // As C++23 supports feature test macros in the standard, there is no // immediate need to actually test for feature availability on the // Autoconf side. } // namespace cxx23 #endif // (defined _MSVC_LANG ? _MSVC_LANG : __cplusplus) < 202302L ]]) libtorrent-0.16.17/scripts/ax_execinfo.m4000066400000000000000000000047611522271512000203000ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_execinfo.html # =========================================================================== # # SYNOPSIS # # AX_EXECINFO([ACTION-IF-EXECINFO-H-IS-FOUND], [ACTION-IF-EXECINFO-H-IS-NOT-FOUND], [ADDITIONAL-TYPES-LIST]) # # DESCRIPTION # # Checks for execinfo.h header and if the len parameter/return type can be # found from a list, also define backtrace_size_t to that type. # # By default the list of types to try contains int and size_t, but should # some yet undiscovered system use e.g. unsigned, the 3rd argument can be # used for extensions. I'd like to hear of further suggestions. # # Executes ACTION-IF-EXECINFO-H-IS-FOUND when present and the execinfo.h # header is found or ACTION-IF-EXECINFO-H-IS-NOT-FOUND in case the header # seems unavailable. # # Also adds -lexecinfo to LIBS on BSD if needed. # # LICENSE # # Copyright (c) 2014 Thomas Jahns # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 2 AC_DEFUN([AX_EXECINFO], [AC_CHECK_HEADERS([execinfo.h]) AS_IF([test x"$ac_cv_header_execinfo_h" = xyes], [AC_CACHE_CHECK([size parameter type for backtrace()], [ax_cv_proto_backtrace_type], [AC_LANG_PUSH([C]) for ax_cv_proto_backtrace_type in size_t int m4_ifnblank([$3],[$3 ])none; do AS_IF([test "${ax_cv_proto_backtrace_type}" = none], [ax_cv_proto_backtrace_type= ; break]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ #include extern ${ax_cv_proto_backtrace_type} backtrace(void **addrlist, ${ax_cv_proto_backtrace_type} len); char **backtrace_symbols(void *const *buffer, ${ax_cv_proto_backtrace_type} size); ])], [break]) done AC_LANG_POP([C])])]) AS_IF([test x${ax_cv_proto_backtrace_type} != x], [AC_DEFINE_UNQUOTED([backtrace_size_t], [$ax_cv_proto_backtrace_type], [Defined to return type of backtrace().])]) AC_SEARCH_LIBS([backtrace],[execinfo]) AS_IF([test x"${ax_cv_proto_backtrace_type}" != x -a x"$ac_cv_header_execinfo_h" = xyes -a x"$ac_cv_search_backtrace" != xno], [AC_DEFINE([HAVE_BACKTRACE],[1], [Defined if backtrace() could be fully identified.]) ]m4_ifnblank([$1],[$1 ]),m4_ifnblank([$2],[$2 ]))]) dnl dnl Local Variables: dnl mode: autoconf dnl End: dnl libtorrent-0.16.17/scripts/ax_pthread.m4000066400000000000000000000540341522271512000201250ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is # needed for multi-threaded programs (defaults to the value of CC # respectively CXX otherwise). (This is necessary on e.g. AIX to use the # special cc_r/CC_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also to link with them as well. For example, you might link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threaded programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # CXX="$PTHREAD_CXX" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to # that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # Copyright (c) 2019 Marc Stevens # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 31 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_PROG_SED]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on Tru64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CC="$CC" ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = "xno"; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi CC="$ax_pthread_save_CC" CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items with a "," contain both # C compiler flags (before ",") and linker flags (after ","). Other items # starting with a "-" are C compiler flags, and remaining items are # library names, except for "none" which indicates that we try without # any flags at all, and "pthread-config" which is a program returning # the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 # (Note: HP C rejects this with "bad form for `-t' option") # -pthreads: Solaris/gcc (Note: HP C also rejects) # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads and # -D_REENTRANT too), HP C (must be checked before -lpthread, which # is present but should not be used directly; and before -mthreads, # because the compiler interprets this as "-mt" + "-hreads") # -mthreads: Mingw32/gcc, Lynx/gcc # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case $host_os in freebsd*) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) ax_pthread_flags="-kthread lthread $ax_pthread_flags" ;; hpux*) # From the cc(1) man page: "[-mt] Sets various -D flags to enable # multi-threading and also sets -lpthread." ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" ;; openedition*) # IBM z/OS requires a feature-test macro to be defined in order to # enable POSIX threads at all, so give the user a hint if this is # not set. (We don't define these ourselves, as they can affect # other portions of the system API in unpredictable ways.) AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], [ # if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) AX_PTHREAD_ZOS_MISSING # endif ], [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) ;; solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (N.B.: The stubs are missing # pthread_cleanup_push, or rather a function called by this macro, # so we could check for that, but who knows whether they'll stub # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac # Are we compiling with Clang? AC_CACHE_CHECK([whether $CC is Clang], [ax_cv_PTHREAD_CLANG], [ax_cv_PTHREAD_CLANG=no # Note that Autoconf sets GCC=yes for Clang as well as GCC if test "x$GCC" = "xyes"; then AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ # if defined(__clang__) && defined(__llvm__) AX_PTHREAD_CC_IS_CLANG # endif ], [ax_cv_PTHREAD_CLANG=yes]) fi ]) ax_pthread_clang="$ax_cv_PTHREAD_CLANG" # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) # Note that for GCC and Clang -pthread generally implies -lpthread, # except when -nostdlib is passed. # This is problematic using libtool to build C++ shared libraries with pthread: # [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 # [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 # [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 # To solve this, first try -pthread together with -lpthread for GCC AS_IF([test "x$GCC" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) # Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first AS_IF([test "x$ax_pthread_clang" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread"]) # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is # correctly enabled case $host_os in darwin* | hpux* | linux* | osf* | solaris*) ax_pthread_check_macro="_REENTRANT" ;; aix*) ax_pthread_check_macro="_THREAD_SAFE" ;; *) ax_pthread_check_macro="--" ;; esac AS_IF([test "x$ax_pthread_check_macro" = "x--"], [ax_pthread_check_cond=0], [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) if test "x$ax_pthread_ok" = "xno"; then for ax_pthread_try_flag in $ax_pthread_flags; do case $ax_pthread_try_flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; *,*) PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) PTHREAD_CFLAGS="$ax_pthread_try_flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) PTHREAD_LIBS="-l$ax_pthread_try_flag" ;; esac ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include # if $ax_pthread_check_cond # error "$ax_pthread_check_macro must be defined" # endif static void *some_global = NULL; static void routine(void *a) { /* To avoid any unused-parameter or unused-but-set-parameter warning. */ some_global = a; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" AC_MSG_RESULT([$ax_pthread_ok]) AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way if test "x$ax_pthread_clang" = "xyes"; then # Clang takes -pthread; it has never supported any other flag # (Note 1: This will need to be revisited if a system that Clang # supports has POSIX threads in a separate library. This tends not # to be the way of modern systems, but it's conceivable.) # (Note 2: On some systems, notably Darwin, -pthread is not needed # to get POSIX threads support; the API is always present and # active. We could reasonably leave PTHREAD_CFLAGS empty. But # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused # during compilation"). They expect -pthread to be passed in only # when source code is being compiled. # # Problem is, this is at odds with the way Automake and most other # C build frameworks function, which is that the same flags used in # compilation (CFLAGS) are also used in linking. Many systems # supported by AX_PTHREAD require exactly this for POSIX threads # support, and in fact it is often not straightforward to specify a # flag that is used only in the compilation phase and not in # linking. Such a scenario is extremely rare in practice. # # Even though use of the -pthread flag in linking would only print # a warning, this can be a nuisance for well-run software projects # that build with -Werror. So if the active version of Clang has # this misfeature, we search for an option to squash it. AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown # Create an alternate version of $ac_link that compiles and # links in two steps (.c -> .o, .o -> exe) instead of one # (.c -> exe), because the warning occurs only in the second # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" ac_link="$ax_pthread_save_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [ac_link="$ax_pthread_2step_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [break]) ]) done ac_link="$ax_pthread_save_ac_link" CFLAGS="$ax_pthread_save_CFLAGS" AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" ]) case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in no | unknown) ;; *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; esac fi # $ax_pthread_clang = yes # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_CACHE_CHECK([for joinable pthread attribute], [ax_cv_PTHREAD_JOINABLE_ATTR], [ax_cv_PTHREAD_JOINABLE_ATTR=unknown for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $ax_pthread_attr; return attr /* ; */])], [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], []) done ]) AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ test "x$ax_pthread_joinable_attr_defined" != "xyes"], [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$ax_cv_PTHREAD_JOINABLE_ATTR], [Define to necessary symbol if this constant uses a non-standard name on your system.]) ax_pthread_joinable_attr_defined=yes ]) AC_CACHE_CHECK([whether more special flags are required for pthreads], [ax_cv_PTHREAD_SPECIAL_FLAGS], [ax_cv_PTHREAD_SPECIAL_FLAGS=no case $host_os in solaris*) ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" ;; esac ]) AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ test "x$ax_pthread_special_flags_added" != "xyes"], [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" ax_pthread_special_flags_added=yes]) AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT; return i;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ test "x$ax_pthread_prio_inherit_defined" != "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) ax_pthread_prio_inherit_defined=yes ]) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" # More AIX lossage: compile with *_r variant if test "x$GCC" != "xyes"; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [ AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) ], [ AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) ] ) ]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) AC_SUBST([PTHREAD_CXX]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD libtorrent-0.16.17/scripts/checks.m4000066400000000000000000000206411522271512000172430ustar00rootroot00000000000000AC_DEFUN([TORRENT_CHECK_EPOLL], [ AC_MSG_CHECKING(for epoll support) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include int main() { int fd = epoll_create(100); return 0; } ])], [ use_epoll=yes AC_DEFINE(USE_EPOLL, 1, Use epoll.) AC_MSG_RESULT(yes) ], [ AC_MSG_RESULT(no) ]) ]) AC_DEFUN([TORRENT_WITHOUT_EPOLL], [ AC_ARG_WITH(epoll, AS_HELP_STRING([--without-epoll],[do not check for epoll support]), [ if test "$withval" = "yes"; then TORRENT_CHECK_EPOLL fi ], [ TORRENT_CHECK_EPOLL ]) ]) AC_DEFUN([TORRENT_CHECK_KQUEUE], [ AC_MSG_CHECKING(for kqueue support) AC_LINK_IFELSE([AC_LANG_SOURCE([ #include /* Because OpenBSD's sys/event.h fails to compile otherwise. Yeah... */ #include int main() { int fd = kqueue(); return 0; } ])], [ use_kqueue=yes AC_DEFINE(USE_KQUEUE, 1, Use kqueue.) AC_MSG_RESULT(yes) ], [ AC_MSG_RESULT(no) ]) ]) AC_DEFUN([TORRENT_WITH_KQUEUE], [ AC_ARG_WITH(kqueue, AS_HELP_STRING([--with-kqueue],[enable kqueue [[default=no]]]), [ if test "$withval" = "yes"; then TORRENT_CHECK_KQUEUE fi ]) ]) AC_DEFUN([TORRENT_WITHOUT_KQUEUE], [ AC_ARG_WITH(kqueue, AS_HELP_STRING([--without-kqueue],[do not check for kqueue support]), [ if test "$withval" = "yes"; then TORRENT_CHECK_KQUEUE fi ], [ TORRENT_CHECK_KQUEUE ]) ]) AC_DEFUN([TORRENT_WITH_ADDRESS_SPACE], [ AC_ARG_WITH(address-space, AS_HELP_STRING([--with-address-space=MB],[change the default address space size [default=1024mb-or-32768mb]]), [ if test ! -z $withval -a "$withval" != "yes" -a "$withval" != "no"; then AC_DEFINE_UNQUOTED(DEFAULT_ADDRESS_SPACE_SIZE, [$withval]) else AC_MSG_ERROR(--with-address-space requires a parameter.) fi ], [ AC_CHECK_SIZEOF(long) if test $ac_cv_sizeof_long = 8; then AC_DEFINE(DEFAULT_ADDRESS_SPACE_SIZE, 32768, Default address space size.) else AC_DEFINE(DEFAULT_ADDRESS_SPACE_SIZE, 1024, Default address space size.) fi ]) ]) AC_DEFUN([TORRENT_CHECK_ATOMIC], [ AC_MSG_CHECKING([whether 64-bit atomic operations require -latomic]) AC_LANG_PUSH(C++) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[std::atomic x(0); return x.load();]])], [ AC_MSG_RESULT([no]) ATOMIC_LIBS="" ], [ save_LIBS="$LIBS" LIBS="$LIBS -latomic" AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include #include ]], [[std::atomic x(0); return x.load();]])], [ AC_MSG_RESULT([yes]) ATOMIC_LIBS="-latomic" ], [ AC_MSG_RESULT([unsupported]) AC_MSG_ERROR([Compiler target lacks proper 64-bit atomic support.]) ]) LIBS="$save_LIBS" ]) AC_LANG_POP(C++) AC_SUBST([ATOMIC_LIBS]) ]) AC_DEFUN([TORRENT_WITH_XMLRPC_C], [ AC_MSG_CHECKING(for XMLRPC-C) AC_ARG_WITH(xmlrpc-c, AS_HELP_STRING([--with-xmlrpc-c=PATH],[enable XMLRPC-C support]), [ if test "$withval" = "no"; then AC_MSG_RESULT(no) else if test "$withval" = "yes"; then xmlrpc_cc_prg="xmlrpc-c-config" else xmlrpc_cc_prg="$withval" fi if eval $xmlrpc_cc_prg --version 2>/dev/null >/dev/null; then CXXFLAGS="$CXXFLAGS `$xmlrpc_cc_prg --cflags server-util`" LIBS="$LIBS `$xmlrpc_cc_prg server-util --libs`" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[ xmlrpc_registry_new(NULL); ]])],[ AC_MSG_RESULT(ok) ],[ AC_MSG_RESULT(failed) AC_MSG_ERROR(Could not compile XMLRPC-C test.) ]) AC_DEFINE(HAVE_XMLRPC_C, 1, Support for XMLRPC-C.) else AC_MSG_RESULT(failed) AC_MSG_ERROR(Could not compile XMLRPC-C test.) fi fi ],[ AC_MSG_RESULT(ignored) ]) ]) AC_DEFUN([TORRENT_WITH_TINYXML2], [ AC_MSG_CHECKING(for tinyxml2) AC_ARG_WITH(xmlrpc-tinyxml2, AS_HELP_STRING([--with-xmlrpc-tinyxml2],[enable XMLRPC support via tinyxml2]), [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_XMLRPC_TINYXML2, 1, Support for XMLRPC via tinyxml2.) ],[ AC_MSG_RESULT(ignored) ]) ]) AC_DEFUN([TORRENT_WITH_LUA], [ AC_ARG_WITH(lua, AS_HELP_STRING([--with-lua],[enable LUA support]), [ if test "$withval" = "no"; then AC_MSG_RESULT(no) else AX_PROG_LUA # 1. Override AX_LUA_LIBS default crash behavior AX_LUA_LIBS([have_lua_libs=yes], [have_lua_libs=no]) # 2. Override AX_LUA_HEADERS default crash behavior AX_LUA_HEADERS([have_lua_headers=yes], [have_lua_headers=no]) # 3. Only inject if both checks completely pass if test "x$have_lua_libs" = "xyes" && test "x$have_lua_headers" = "xyes"; then AC_DEFINE(HAVE_LUA, 1, Use LUA.) AC_DEFINE(LUA_DATADIR, [PACKAGE_DATADIR "/lua"], [LUA data directory]) LIBS="$LIBS $LUA_LIB" CXXFLAGS="$CXXFLAGS $LUA_INCLUDE" else # Throw fatal error ONLY if user strictly ran --with-lua=yes if test "$withval" = "yes"; then AC_MSG_ERROR([Lua support explicitly requested, but compatible Lua 5.3 libs/headers were not found.]) else AC_MSG_WARN([Lua 5.3 libs or headers missing. Proceeding without Lua support.]) fi fi fi ],[ AC_MSG_RESULT(ignored) ]) ]) AC_DEFUN([TORRENT_WITH_INOTIFY], [ AC_LANG_PUSH(C++) AC_CHECK_HEADERS([sys/inotify.h]) AC_MSG_CHECKING([whether sys/inotify.h actually works]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include int main(int,const char**) { return (-1 == inotify_init()); }]) ],[ AC_DEFINE(USE_INOTIFY, 1, [sys/inotify.h exists and works correctly]) AC_MSG_RESULT(yes)], [AC_MSG_RESULT(failed)] ) AC_LANG_POP(C++) ]) AC_DEFUN([TORRENT_CHECK_PTHREAD_SETNAME_NP], [ AC_CHECK_HEADERS(pthread.h) AC_MSG_CHECKING(for pthread_setname_np type) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #define _GNU_SOURCE #include #include ]], [[ pthread_t t; pthread_setname_np(t, "foo"); ]])],[ AC_DEFINE(HAS_PTHREAD_SETNAME_NP_GENERIC, 1, The function to set pthread name has a pthread_t argumet.) AC_MSG_RESULT(generic) ],[ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include ]],[[ pthread_t t; pthread_setname_np("foo"); ]])],[ AC_DEFINE(HAS_PTHREAD_SETNAME_NP_DARWIN, 1, The function to set pthread name has no pthread argument.) AC_MSG_RESULT(darwin) ],[ AC_MSG_RESULT(no) ]) ]) ]) AC_DEFUN([TORRENT_DISABLE_PTHREAD_SETNAME_NP], [ AC_MSG_CHECKING([for pthread_setname_no]) AC_ARG_ENABLE(pthread-setname-np, AS_HELP_STRING([--disable-pthread-setname-np],[disable pthread_setname_np]), [ if test "$enableval" = "no"; then AC_MSG_RESULT(disabled) else AC_MSG_RESULT(checking) TORRENT_CHECK_PTHREAD_SETNAME_NP fi ], [ TORRENT_CHECK_PTHREAD_SETNAME_NP ] ) ]) AC_DEFUN([TORRENT_WITH_SYSTEMD], [ AC_ARG_WITH(systemd, AS_HELP_STRING([--with-systemd],[enable systemd socket activation support [[default=no]]]), [ if test "$withval" = "yes"; then PKG_CHECK_MODULES([SYSTEMD], [libsystemd], [ CXXFLAGS="$CXXFLAGS $SYSTEMD_CFLAGS" LIBS="$LIBS $SYSTEMD_LIBS" AC_DEFINE(HAVE_SYSTEMD, 1, [Support for systemd socket activation.]) ], [AC_MSG_ERROR([libsystemd not found. Install libsystemd-dev (or the equivalent for your distribution).])]) fi ]) ]) AC_DEFUN([TORRENT_WITHOUT_NCURSES], [ AC_ARG_WITH([ncurses], [AS_HELP_STRING([--without-ncurses], [build without ncurses (daemon-only mode)])], [with_ncurses=$withval], [with_ncurses=yes]) if test "x$with_ncurses" = xno; then AC_DEFINE([HAVE_NO_NCURSES], [1], [Define to 1 if building without ncurses]) CURSES_LIBS="" CURSES_CFLAGS="" CURSES_LIB="" fi AM_CONDITIONAL([NO_NCURSES], [test "x$with_ncurses" = xno]) ]) libtorrent-0.16.17/scripts/common.m4000066400000000000000000000241221522271512000172710ustar00rootroot00000000000000AC_DEFUN([TORRENT_MINCORE_SIGNEDNESS], [ AC_LANG_PUSH(C++) AC_MSG_CHECKING(signedness of mincore parameter) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include #include #include void f() { mincore((char*)0, 0, (unsigned char*)0); } ])], [ AC_DEFINE(USE_MINCORE, 1, Use mincore) AC_DEFINE(USE_MINCORE_UNSIGNED, 1, use unsigned char* in mincore) AC_MSG_RESULT(unsigned) ], [ AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include #include #include void f() { mincore((char*)0, 0, (char*)0); } ])], [ AC_DEFINE(USE_MINCORE, 1, Use mincore) AC_DEFINE(USE_MINCORE_UNSIGNED, 0, use char* in mincore) AC_MSG_RESULT(signed) ], [ AC_MSG_ERROR([failed, do *not* attempt fix this with --disable-mincore unless you are running Win32 or OpenBSD.]) ]) ]) AC_LANG_POP(C++) ]) AC_DEFUN([TORRENT_MINCORE], [ AC_ARG_ENABLE(mincore, AS_HELP_STRING([--disable-mincore], [disable mincore check [[default=enable]]]), [ if test "$enableval" = "yes"; then TORRENT_MINCORE_SIGNEDNESS() else AC_MSG_CHECKING(for mincore) AC_MSG_RESULT(disabled) fi ],[ TORRENT_MINCORE_SIGNEDNESS() ]) ]) AC_DEFUN([TORRENT_CHECK_MADVISE], [ AC_MSG_CHECKING(for madvise) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include #include void f() { static char test@<:@1024@:>@; madvise((void *)test, sizeof(test), MADV_NORMAL); } ])], [ AC_MSG_RESULT(yes) AC_DEFINE(USE_MADVISE, 1, Use madvise) ], [ AC_MSG_RESULT(no) ]) ]) AC_DEFUN([TORRENT_CHECK_POSIX_FADVISE], [ AC_MSG_CHECKING(for posix_fadvise) AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include void f() { posix_fadvise(0, 0, 0, POSIX_FADV_RANDOM); } ])], [ AC_MSG_RESULT(yes) AC_DEFINE(USE_POSIX_FADVISE, 1, Use posix_fadvise) ], [ AC_MSG_RESULT(no) ]) ]) AC_DEFUN([TORRENT_CHECK_CACHELINE], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_MSG_CHECKING([for target cacheline size]) case "$host_os" in linux*) # REGION: Linux Kernel Extraction Loop AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ #include #include void* vptr; void f() { int res = posix_memalign(&vptr, SMP_CACHE_BYTES, 42); (void)res; } ]])],[ # We need an explicit variable fallback condition inside AC_COMPUTE_INT AC_COMPUTE_INT([torrent_cv_cacheline_size], [SMP_CACHE_BYTES], [#include ], [torrent_cv_cacheline_size=0]) if test "$torrent_cv_cacheline_size" -gt 0; then AC_MSG_RESULT([linux builtin ($torrent_cv_cacheline_size bytes)]) AC_DEFINE_UNQUOTED([LT_SMP_CACHE_BYTES], [$torrent_cv_cacheline_size], [System-defined Linux L1 SMP cacheline size.]) else # Handle scenarios where macro maps to a complex runtime expression or fails AC_MSG_RESULT([failed to parse SMP_CACHE_BYTES value]) AC_MSG_FAILURE([Linux kernel headers found, but cacheline constant could not be computed at compile-time.]) fi ],[ # Explicitly validate the CPU type even on Linux if the header check fails case "$host_cpu" in x86_64*|amd64*|i386*|i486*|i586*|i686*) AC_MSG_RESULT([linux fallback x86 64 bytes]) AC_DEFINE([LT_SMP_CACHE_BYTES], 64, [Fallback 64-byte alignment for Linux x86 hardware.]) ;; arm*|aarch64*|powerpc*|ppc*|s390x*) AC_MSG_RESULT([linux fallback enterprise 128 bytes]) AC_DEFINE([LT_SMP_CACHE_BYTES], 128, [Fallback 128-byte alignment for Linux enterprise hardware.]) ;; riscv32*|riscv64*) AC_MSG_RESULT([linux fallback RISC-V 64 bytes]) AC_DEFINE([LT_SMP_CACHE_BYTES], 64, [Fallback 64-byte alignment for Linux RISC-V hardware.]) ;; loongarch32*|loongarch64*) AC_MSG_RESULT([linux fallback LoongArch 64 bytes]) AC_DEFINE([LT_SMP_CACHE_BYTES], 64, [Fallback 64-byte alignment for Linux LoongArch hardware.]) ;; *) AC_MSG_RESULT([unrecognized CPU arch on Linux header fallback]) AC_MSG_FAILURE([Unrecognized CPU architecture ($host_cpu) on Linux fallback path. Aborting build.]) ;; esac ]) ;; *) # REGION: Cross-Platform Strict Hardware Mapping (macOS, FreeBSD, OpenBSD, NetBSD) case "$host_cpu" in x86_64*|amd64*|i386*|i486*|i586*|i686*) # Explicit x86 desktop hardware baseline block AC_MSG_RESULT([$host_os ($host_cpu) standard x86 64 bytes]) AC_DEFINE([LT_SMP_CACHE_BYTES], 64, [Standard 64-byte alignment for stable x86 hardware layout.]) ;; arm*|aarch64*|powerpc*|ppc*|s390x*) # Explicit modern enterprise and Apple Silicon hardware baseline block AC_MSG_RESULT([$host_os ($host_cpu) stable enterprise 128 bytes]) AC_DEFINE([LT_SMP_CACHE_BYTES], 128, [Optimized 128-byte alignment for newer high-performance chipsets.]) ;; *) # STRICT ENFORCEMENT: Fail the build immediately if the CPU isn't explicitly known AC_MSG_RESULT([unrecognized architecture]) AC_MSG_FAILURE([The target CPU architecture ($host_cpu) is unrecognized. Aborting configuration to prevent fatal runtime false-sharing or memory misalignment errors.]) ;; esac ;; esac ]) AC_DEFUN([TORRENT_CHECK_CUSTOM_ENDIAN64], [ AC_CHECK_HEADERS([endian.h sys/endian.h libkern/OSByteOrder.h]) AC_C_BIGENDIAN AH_TEMPLATE([CUSTOM_ENDIAN_HEADER], [The system header to include for endian conversions.]) AH_TEMPLATE([custom_ntohll], [Convert 64-bit integer from network to host byte order.]) AH_TEMPLATE([custom_htonll], [Convert 64-bit integer from host to network byte order.]) dnl 1. Initialize our header variable tracking endian_header_file='' dnl 2. Test if native be64toh works natively via (Standard Linux/glibc) AC_MSG_CHECKING([for native be64toh via endian.h]) AC_LINK_IFELSE([AC_LANG_SOURCE([ #include #ifdef HAVE_ENDIAN_H # include #endif int main() { uint64_t x = be64toh(1); return 0; } ])], [has_native_be64=yes; endian_header_file=''], [has_native_be64=no] ) AC_MSG_RESULT([$has_native_be64]) dnl 3. If missing, test if it works via (True BSD systems like FreeBSD) if test "$has_native_be64" = "no"; then AC_MSG_CHECKING([for native be64toh via sys/endian.h]) AC_LINK_IFELSE([AC_LANG_SOURCE([ #include #ifdef HAVE_SYS_ENDIAN_H # include #endif int main() { uint64_t x = be64toh(1); return 0; } ])], [has_native_be64=yes; endian_header_file=''], [has_native_be64=no] ) AC_MSG_RESULT([$has_native_be64]) fi dnl 4. Route definitions cleanly based on actual testing results if test "$has_native_be64" = "yes"; then dnl Linux or true BSD environment AC_DEFINE_UNQUOTED([CUSTOM_ENDIAN_HEADER], [$endian_header_file], [The header containing native endian macros.]) AC_DEFINE([custom_ntohll(x)], [(be64toh(x))], [Use native platform be64toh]) AC_DEFINE([custom_htonll(x)], [(htobe64(x))], [Use native platform htobe64]) else dnl Fallback block: Check if we are on macOS using Apple's optimized hardware libraries if test "$ac_cv_header_libkern_OSByteOrder_h" = "yes"; then AC_DEFINE([CUSTOM_ENDIAN_HEADER], [], [The header containing native endian macros.]) AC_DEFINE([custom_ntohll(x)], [(OSSwapBigToHostInt64(x))], [Map missing be64toh to macOS native swap]) AC_DEFINE([custom_htonll(x)], [(OSSwapHostToBigInt64(x))], [Map missing htobe64 to macOS native swap]) else dnl Fallback for platforms with no built-in architecture macros (compiler built-ins) AC_DEFINE([CUSTOM_ENDIAN_HEADER], [], [The header containing native endian macros.]) if test "$ac_cv_c_bigendian" = "yes"; then AC_DEFINE([custom_ntohll(x)], [((uint64_t)(x))], [Fallback 64-bit conversion.]) AC_DEFINE([custom_htonll(x)], [((uint64_t)(x))], [Fallback 64-bit conversion.]) else AC_DEFINE([custom_ntohll(x)], [(__builtin_bswap64((uint64_t)(x)))], [Fallback 64-bit conversion.]) AC_DEFINE([custom_htonll(x)], [(__builtin_bswap64((uint64_t)(x)))], [Fallback 64-bit conversion.]) fi fi fi ]) AC_DEFUN([TORRENT_ENABLE_INSTRUMENTATION], [ AC_MSG_CHECKING([if instrumentation should be included]) AC_ARG_ENABLE(instrumentation, AS_HELP_STRING([--enable-instrumentation], [enable instrumentation [[default=disabled]]]), [ if test "$enableval" = "yes"; then AC_DEFINE(LT_INSTRUMENTATION, 1, enable instrumentation) AC_MSG_RESULT(yes) else AC_MSG_RESULT(no) fi ],[ AC_MSG_RESULT(no) ]) ]) AC_DEFUN([TORRENT_ENABLE_CUSTOM_STACK_SIZE], [ AC_ARG_ENABLE([pthread-setstacksize], [AS_HELP_STRING([--enable-pthread-setstacksize], [explicitly set pthread stack size (auto-enabled for musl)])], [enable_pthread_setstacksize=$enableval], [enable_pthread_setstacksize=auto]) AC_CANONICAL_HOST if test "x$enable_pthread_setstacksize" = "xauto"; then case "$host" in *-musl*) enable_pthread_setstacksize=yes ;; *) enable_pthread_setstacksize=no ;; esac fi if test "x$enable_pthread_setstacksize" = "xyes"; then AC_DEFINE([USE_PTHREAD_SETSTACKSIZE], [1], [Use explicit pthread stack size]) AC_DEFINE([DEFAULT_PTHREAD_STACKSIZE], [(8 * 1024 * 1024)], [Default pthread stack size in bytes]) fi ]) AC_DEFUN([TORRENT_DISABLE_IPV6], [ AC_ARG_ENABLE(ipv6, AS_HELP_STRING([--enable-ipv6], [enable ipv6 [[default=no]]]), [ if test "$enableval" = "yes"; then AC_DEFINE(RAK_USE_INET6, 1, enable ipv6 stuff) fi ]) ]) libtorrent-0.16.17/scripts/rak_compiler.m4000066400000000000000000000026111522271512000204470ustar00rootroot00000000000000AC_DEFUN([RAK_CHECK_CFLAGS], [ AC_MSG_CHECKING([for user-defined CFLAGS]) if test "$CFLAGS" = ""; then unset CFLAGS AC_MSG_RESULT([undefined]) else AC_MSG_RESULT([user-defined "$CFLAGS"]) fi ]) AC_DEFUN([RAK_CHECK_CXXFLAGS], [ AC_MSG_CHECKING([for user-defined CXXFLAGS]) if test "$CXXFLAGS" = ""; then unset CXXFLAGS AC_MSG_RESULT([undefined]) else AC_MSG_RESULT([user-defined "$CXXFLAGS"]) fi ]) AC_DEFUN([RAK_ENABLE_DEBUG], [ AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],[enable debug information [[default=yes]]]), [ if test "$enableval" = "yes"; then CXXFLAGS="$CXXFLAGS -g -DDEBUG" else CXXFLAGS="$CXXFLAGS -DNDEBUG" fi ],[ CXXFLAGS="$CXXFLAGS -g -DDEBUG" ]) ]) AC_DEFUN([RAK_ENABLE_WERROR], [ AC_ARG_ENABLE(werror, AS_HELP_STRING([--enable-werror],[enable the -Werror and -Wall flags [[default -Wall only]]]), [ if test "$enableval" = "yes"; then CXXFLAGS="$CXXFLAGS -Werror -Wall" fi ],[ CXXFLAGS="$CXXFLAGS -Wall" ]) ]) AC_DEFUN([RAK_ENABLE_EXTRA_DEBUG], [ AC_ARG_ENABLE(extra-debug, AS_HELP_STRING([--enable-extra-debug],[enable extra debugging checks [[default=no]]]), [ if test "$enableval" = "yes"; then AC_DEFINE(USE_EXTRA_DEBUG, 1, Enable extra debugging checks.) fi ]) ]) libtorrent-0.16.17/scripts/ssl.m4000066400000000000000000000002441522271512000166010ustar00rootroot00000000000000AC_DEFUN([TORRENT_CHECK_OPENSSL], [ PKG_CHECK_MODULES(OPENSSL, libcrypto, CXXFLAGS="$CXXFLAGS $OPENSSL_CFLAGS"; LIBS="$LIBS $OPENSSL_LIBS") ] ) libtorrent-0.16.17/src/000077500000000000000000000000001522271512000146365ustar00rootroot00000000000000libtorrent-0.16.17/src/Makefile.am000066400000000000000000000111141522271512000166700ustar00rootroot00000000000000SUBDIRS = torrent lib_LTLIBRARIES = libtorrent.la noinst_LTLIBRARIES = \ libtorrent_other.la libtorrent_la_LDFLAGS = -version-info $(LIBTORRENT_INTERFACE_VERSION_INFO) libtorrent_la_LIBADD = \ torrent/libtorrent_torrent.la \ libtorrent_other.la libtorrent_la_SOURCES = \ manager.cc \ manager.h \ runtime.cc \ runtime.h \ thread_main.cc \ thread_main.h libtorrent_other_la_SOURCES = \ data/chunk.cc \ data/chunk.h \ data/chunk_handle.h \ data/chunk_iterator.h \ data/chunk_list.cc \ data/chunk_list.h \ data/chunk_list_node.h \ data/chunk_part.cc \ data/chunk_part.h \ data/hash_check_queue.cc \ data/hash_check_queue.h \ data/hash_chunk.cc \ data/hash_chunk.h \ data/hash_queue.cc \ data/hash_queue.h \ data/hash_queue_node.cc \ data/hash_queue_node.h \ data/hash_torrent.cc \ data/hash_torrent.h \ data/memory_chunk.cc \ data/memory_chunk.h \ data/socket_file.cc \ data/socket_file.h \ data/thread_disk.cc \ data/thread_disk.h \ \ dht/dht_bucket.cc \ dht/dht_bucket.h \ dht/dht_hash_map.h \ dht/dht_node.cc \ dht/dht_node.h \ dht/dht_router.cc \ dht/dht_router.h \ dht/dht_server.cc \ dht/dht_server.h \ dht/dht_tracker.cc \ dht/dht_tracker.h \ dht/dht_transaction.cc \ dht/dht_transaction.h \ \ dht/transactions/dht_announce.cc \ dht/transactions/dht_announce.h \ dht/transactions/dht_search.cc \ dht/transactions/dht_search.h \ \ download/available_list.cc \ download/available_list.h \ download/chunk_selector.cc \ download/chunk_selector.h \ download/chunk_statistics.cc \ download/chunk_statistics.h \ download/delegator.cc \ download/delegator.h \ download/download_constructor.cc \ download/download_constructor.h \ download/download_main.cc \ download/download_main.h \ download/download_wrapper.cc \ download/download_wrapper.h \ \ net/address_list.cc \ net/address_list.h \ net/data_buffer.h \ net/curl_get.cc \ net/curl_get.h \ net/curl_socket.cc \ net/curl_socket.h \ net/curl_stack.cc \ net/curl_stack.h \ net/dns_cache.cc \ net/dns_cache.h \ net/dns_buffer.cc \ net/dns_buffer.h \ net/event_fd.cc \ net/event_fd.h \ net/listen.cc \ net/listen.h \ net/protocol_buffer.h \ net/socket_datagram.cc \ net/socket_datagram.h \ net/socket_stream.cc \ net/socket_stream.h \ net/thread_net.cc \ net/thread_net.h \ net/throttle_internal.cc \ net/throttle_internal.h \ net/throttle_list.cc \ net/throttle_list.h \ net/throttle_node.h \ net/udns_resolver.cc \ net/udns_resolver.h \ net/udns_library.cc \ net/udns_library.h \ \ net/proxy/proxy.h \ net/proxy/proxy_http.cc \ net/proxy/proxy_http.h \ net/proxy/proxy_socks5.cc \ net/proxy/proxy_socks5.h \ \ net/udns/config.h \ net/udns/udns.h \ \ protocol/encryption_info.h \ protocol/extensions.cc \ protocol/extensions.h \ protocol/handshake.cc \ protocol/handshake.h \ protocol/handshake_encryption.cc \ protocol/handshake_encryption.h \ protocol/handshake_manager.cc \ protocol/handshake_manager.h \ protocol/initial_seed.cc \ protocol/initial_seed.h \ protocol/peer_chunks.h \ protocol/peer_connection_base.cc \ protocol/peer_connection_base.h \ protocol/peer_connection_leech.cc \ protocol/peer_connection_leech.h \ protocol/peer_connection_metadata.cc \ protocol/peer_connection_metadata.h \ protocol/peer_factory.cc \ protocol/peer_factory.h \ protocol/protocol_base.h \ protocol/request_list.cc \ protocol/request_list.h \ \ tracker/thread_tracker.cc \ tracker/thread_tracker.h \ tracker/tracker_controller.cc \ tracker/tracker_controller.h \ tracker/tracker_dht.cc \ tracker/tracker_dht.h \ tracker/tracker_http.cc \ tracker/tracker_http.h \ tracker/tracker_list.cc \ tracker/tracker_list.h \ tracker/tracker_udp.cc \ tracker/tracker_udp.h \ tracker/tracker_worker.cc \ tracker/tracker_worker.h \ tracker/udp_router.cc \ tracker/udp_router.h \ \ utils/diffie_hellman.cc \ utils/diffie_hellman.h \ utils/functional.h \ utils/instrumentation.cc \ utils/instrumentation.h \ utils/partial_queue.h \ utils/rc4.h \ utils/sha1.h \ utils/thread_internal.h \ utils/queue_buckets.h AM_CPPFLAGS = -I$(srcdir) -I$(top_srcdir) EXTRA_DIST= \ net/udns/dnsget.c \ net/udns/ex-rdns.c \ net/udns/getopt.c \ net/udns/inet_XtoX.c \ net/udns/rblcheck.c \ net/udns/udns.h \ net/udns/udns_XtoX.c \ net/udns/udns_bl.c \ net/udns/udns_codes.c \ net/udns/udns_dn.c \ net/udns/udns_dntosp.c \ net/udns/udns_init.c \ net/udns/udns_jran.c \ net/udns/udns_misc.c \ net/udns/udns_parse.c \ net/udns/udns_resolver.c \ net/udns/udns_rr_a.c \ net/udns/udns_rr_mx.c \ net/udns/udns_rr_naptr.c \ net/udns/udns_rr_ptr.c \ net/udns/udns_rr_srv.c \ net/udns/udns_rr_txt.c libtorrent-0.16.17/src/data/000077500000000000000000000000001522271512000155475ustar00rootroot00000000000000libtorrent-0.16.17/src/data/chunk.cc000066400000000000000000000203061522271512000171670ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include #include #include #include #include #include "torrent/exceptions.h" #include "chunk.h" #include "chunk_iterator.h" namespace { jmp_buf jmp_disk_full; void bus_handler(int, siginfo_t* si, void*) { if (si && si->si_code == BUS_ADRERR) longjmp(jmp_disk_full, 1); } } // namespace namespace torrent { bool Chunk::is_all_valid() const { return !empty() && std::all_of(begin(), end(), std::mem_fn(&ChunkPart::is_valid)); } void Chunk::clear() { std::for_each(begin(), end(), std::mem_fn(&ChunkPart::clear)); m_chunkSize = 0; m_prot = ~0; base_type::clear(); } // Each add calls vector's reserve adding 1. This should keep // the size of the vector at exactly what we need. Though it // will require a few more cycles, it won't matter as we only // rarely have more than 1 or 2 nodes. // // If the user knows how many chunk parts he is going to add, then he // may call reserve prior to this. void Chunk::push_back(value_type::mapped_type mapped, const MemoryChunk& c) { m_prot &= c.get_prot(); // Gcc starts the reserved size at 1 for the first insert, so we // won't be wasting any space in the general case. base_type::insert(end(), ChunkPart(mapped, c, m_chunkSize)); m_chunkSize += c.size(); } Chunk::iterator Chunk::at_position(uint32_t pos) { if (pos >= m_chunkSize) throw internal_error("Chunk::at_position(...) tried to get Chunk position out of range."); auto itr = std::find_if(begin(), end(), [pos](const auto& chunk) { return chunk.is_contained(pos); }); if (itr == end()) throw internal_error("Chunk::at_position(...) might be mangled, at_position failed horribly"); if (itr->size() == 0) throw internal_error("Chunk::at_position(...) tried to return a node with length 0"); return itr; } Chunk::data_type Chunk::at_memory(uint32_t offset, iterator part) { if (part == end()) throw internal_error("Chunk::at_memory(...) reached end."); if (!part->chunk().is_valid()) throw internal_error("Chunk::at_memory(...) chunk part isn't valid."); if (offset < part->position() || offset >= part->position() + part->size()) throw internal_error("Chunk::at_memory(...) out of range."); offset -= part->position(); return data_type(part->chunk().begin() + offset, part->size() - offset); } bool Chunk::is_incore(uint32_t pos, uint32_t length) { auto itr = at_position(pos); if (itr == end()) throw internal_error("Chunk::incore_length(...) at end()"); uint32_t last = pos + std::min(length, chunk_size() - pos); while (itr->is_incore(pos, last - pos)) { if (++itr == end() || itr->position() >= last) return true; pos = itr->position(); } return false; } // TODO: Buggy, hitting internal_error. Likely need to fix // ChunkPart::incore_length's length align. uint32_t Chunk::incore_length(uint32_t pos, uint32_t length) { uint32_t result = 0; auto itr = at_position(pos); if (itr == end()) throw internal_error("Chunk::incore_length(...) at end()"); length = std::min(length, chunk_size() - pos); do { uint32_t incore_len = itr->incore_length(pos, length); if (incore_len > length) throw internal_error("Chunk::incore_length(...) incore_len > length."); pos += incore_len; length -= incore_len; result += incore_len; } while (pos == itr->position() + itr->size() && ++itr != end()); return result; } bool Chunk::sync(int flags) { bool success = true; for (auto& c : *this) if (!c.chunk().sync(0, c.chunk().size(), flags)) success = false; return success; } void Chunk::preload(uint32_t position, uint32_t length, bool useAdvise) { if (position >= m_chunkSize) throw internal_error("Chunk::preload(...) position > m_chunkSize."); if (length == 0) return; Chunk::data_type data; ChunkIterator itr(this, position, position + std::min(length, m_chunkSize - position)); do { data = itr.data(); // Don't do preloading for zero-length chunks. if (useAdvise) { itr.memory_chunk()->advise(itr.memory_chunk_first(), data.second, MemoryChunk::advice_willneed); } else { for (char* first = static_cast(data.first), *last = static_cast(data.first) + data.second; first < last; first += 4096) [[maybe_unused]] volatile char touchChunk = *static_cast(data.first); // Make sure we touch the last page in the range. [[maybe_unused]] volatile char ouchChunk = *(static_cast(data.first) + data.second - 1); } } while (itr.next()); } // Consider using uint32_t returning first mismatch or length if // matching. bool Chunk::to_buffer(void* buffer, uint32_t position, uint32_t length) { if (position + length > m_chunkSize) throw internal_error("Chunk::to_buffer(...) position + length > m_chunkSize."); if (length == 0) return true; Chunk::data_type data; ChunkIterator itr(this, position, position + length); do { data = itr.data(); std::memcpy(buffer, data.first, data.second); buffer = static_cast(buffer) + data.second; } while (itr.next()); return true; } // Consider using uint32_t returning first mismatch or length if // matching. bool Chunk::from_buffer(const void* buffer, uint32_t position, uint32_t length) { struct sigaction sa{}, oldact; sa.sa_sigaction = &bus_handler; sa.sa_flags = SA_SIGINFO; sigfillset(&sa.sa_mask); sigaction(SIGBUS, &sa, &oldact); if (position + length > m_chunkSize) throw internal_error("Chunk::from_buffer(...) position + length > m_chunkSize."); if (length == 0) return true; Chunk::data_type data; ChunkIterator itr(this, position, position + length); if (setjmp(jmp_disk_full) == 0) { do { data = itr.data(); std::memcpy(data.first, buffer, data.second); buffer = static_cast(buffer) + data.second; } while (itr.next()); } else { throw storage_error("no space left on disk"); } sigaction(SIGBUS, &oldact, NULL); return true; } // Consider using uint32_t returning first mismatch or length if // matching. bool Chunk::compare_buffer(const void* buffer, uint32_t position, uint32_t length) { if (position + length > m_chunkSize) throw internal_error("Chunk::compare_buffer(...) position + length > m_chunkSize."); if (length == 0) return true; Chunk::data_type data; ChunkIterator itr(this, position, position + length); do { data = itr.data(); if (std::memcmp(data.first, buffer, data.second) != 0) return false; buffer = static_cast(buffer) + data.second; } while (itr.next()); return true; } } // namespace torrent libtorrent-0.16.17/src/data/chunk.h000066400000000000000000000052441522271512000170350ustar00rootroot00000000000000#ifndef LIBTORRENT_STORAGE_CHUNK_H #define LIBTORRENT_STORAGE_CHUNK_H #include #include #include #include #include "chunk_part.h" namespace torrent { class Chunk : private std::vector { public: using base_type = std::vector; using data_type = std::pair; using base_type::value_type; using base_type::iterator; using base_type::reverse_iterator; using base_type::const_iterator; using base_type::empty; using base_type::reserve; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; using base_type::front; using base_type::back; Chunk() = default; ~Chunk() { clear(); } Chunk(const Chunk&) = delete; Chunk& operator=(const Chunk&) = delete; bool is_all_valid() const; // All permissions are set for empty chunks. bool is_readable() const { return m_prot & MemoryChunk::prot_read; } bool is_writable() const { return m_prot & MemoryChunk::prot_write; } bool has_permissions(int prot) const { return !(prot & ~m_prot); } uint32_t chunk_size() const { return m_chunkSize; } void clear(); void push_back(value_type::mapped_type mapped, const MemoryChunk& c); // The at_position functions only returns non-zero length iterators // or end. iterator at_position(uint32_t pos); iterator at_position(uint32_t pos, iterator itr); data_type at_memory(uint32_t offset, iterator part); iterator find_address(void* ptr); // Check how much of the chunk is incore from pos. bool is_incore(uint32_t pos, uint32_t length = ~uint32_t()); uint32_t incore_length(uint32_t pos, uint32_t length = ~uint32_t()); bool sync(int flags); void preload(uint32_t position, uint32_t length, bool useAdvise); bool to_buffer(void* buffer, uint32_t position, uint32_t length); bool from_buffer(const void* buffer, uint32_t position, uint32_t length); bool compare_buffer(const void* buffer, uint32_t position, uint32_t length); private: uint32_t m_chunkSize{}; int m_prot{~0}; }; inline Chunk::iterator Chunk::at_position(uint32_t pos, iterator itr) { while (itr != end() && itr->position() + itr->size() <= pos) itr++; return itr; } inline Chunk::iterator Chunk::find_address(void* ptr) { return std::find_if(begin(), end(), [&](auto& part) { return part.has_address(ptr); }); } } // namespace torrent #endif libtorrent-0.16.17/src/data/chunk_handle.h000066400000000000000000000026621522271512000203510ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_CHUNK_HANDLE_H #define LIBTORRENT_DATA_CHUNK_HANDLE_H #include "chunk_list_node.h" #include namespace torrent { class ChunkListNode; class ChunkHandle { public: ChunkHandle(ChunkListNode* c = NULL, bool wr = false, bool blk = false) : m_node(c), m_writable(wr), m_blocking(blk) {} bool is_valid() const { return m_node != NULL; } bool is_loaded() const { return m_node != NULL && m_node->is_valid(); } bool is_writable() const { return m_writable; } bool is_blocking() const { return m_blocking; } void clear() { m_node = NULL; m_writable = false; m_blocking = false; } int error_number() const { return m_errno; } void set_error_number(int e) { m_errno = e; } ChunkListNode* object() const { return m_node; } Chunk* chunk() const { return m_node->chunk(); } uint32_t index() const { return m_node->index(); } static ChunkHandle from_error(int err); private: ChunkListNode* m_node{}; bool m_writable{}; bool m_blocking{}; int m_errno{}; }; inline ChunkHandle ChunkHandle::from_error(int err) { if (err == 0) err = ENOENT; ChunkHandle h; h.set_error_number(err); return h; } } // namespace torrent #endif libtorrent-0.16.17/src/data/chunk_iterator.h000066400000000000000000000071341522271512000207460ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_DATA_CHUNK_ITERATOR_H #define LIBTORRENT_DATA_CHUNK_ITERATOR_H #include "chunk.h" namespace torrent { class ChunkIterator { public: ChunkIterator(Chunk* chunk, uint32_t first, uint32_t last); bool empty() const { return m_iterator == m_chunk->end() || m_first >= m_last; } // Only non-zero length ranges will be returned. Chunk::data_type data(); MemoryChunk* memory_chunk() { return &m_iterator->chunk(); } uint32_t memory_chunk_first() const { return m_first - m_iterator->position(); } uint32_t memory_chunk_last() const { return m_last - m_iterator->position(); } bool next(); bool forward(uint32_t length); private: Chunk* m_chunk; Chunk::iterator m_iterator; uint32_t m_first; uint32_t m_last; }; inline ChunkIterator::ChunkIterator(Chunk* chunk, uint32_t first, uint32_t last) : m_chunk(chunk), m_iterator(chunk->at_position(first)), m_first(first), m_last(last) { } inline Chunk::data_type ChunkIterator::data() { Chunk::data_type data = m_chunk->at_memory(m_first, m_iterator); data.second = std::min(data.second, m_last - m_first); return data; } inline bool ChunkIterator::next() { m_first = m_iterator->position() + m_iterator->size(); while (++m_iterator != m_chunk->end()) { if (m_iterator->size() != 0) return m_first < m_last; } return false; } // Returns true if the new position is on a file boundary while not at // the edges of the chunk. // // Do not return true if the length was zero, in order to avoid // getting stuck looping when no data is being read/written. inline bool ChunkIterator::forward(uint32_t length) { m_first += length; if (m_first >= m_last) return false; do { if (m_first < m_iterator->position() + m_iterator->size()) return true; m_iterator++; } while (m_iterator != m_chunk->end()); return false; } } // namespace torrent #endif libtorrent-0.16.17/src/data/chunk_list.cc000066400000000000000000000314111522271512000202210ustar00rootroot00000000000000#include "config.h" #include "chunk_list.h" #include "data/chunk.h" #include "torrent/exceptions.h" #include "torrent/chunk_manager.h" #include "torrent/data/download_data.h" #include "torrent/utils/log.h" #include "utils/instrumentation.h" #define LT_LOG_THIS(log_level, log_fmt, ...) \ lt_log_print_data(LOG_STORAGE_##log_level, m_data, "chunk_list", log_fmt, __VA_ARGS__); namespace torrent { struct chunk_list_earliest_modified { void operator () (ChunkListNode* node) { if (node->time_modified() < m_time && node->time_modified() != 0us) m_time = node->time_modified(); } std::chrono::microseconds m_time{this_thread::cached_time()}; }; inline bool ChunkList::is_queued(ChunkListNode* node) { return std::find(m_queue.begin(), m_queue.end(), node) != m_queue.end(); } bool ChunkList::has_chunk(size_type index, int prot) const { return base_type::at(index).is_valid() && base_type::at(index).chunk()->has_permissions(prot); } void ChunkList::resize(size_type to_size) { LT_LOG_THIS(INFO, "Resizing: from:%zu to:%u.", size(), to_size); if (!empty()) throw internal_error("ChunkList::resize(...) called on an non-empty object."); base_type::resize(to_size); uint32_t index = 0; for (auto& chunk : *this) { chunk.set_index(index); ++index; } } void ChunkList::clear() { LT_LOG_THIS(INFO, "Clearing.", 0); // Don't do any sync'ing as whomever decided to shut down really // doesn't care, so just de-reference all chunks in queue. for (auto chunk : m_queue) { if (chunk->references() != 1 || chunk->writable() != 1) throw internal_error("ChunkList::clear() called but a node in the queue is still referenced."); chunk->dec_rw(); clear_chunk(chunk, release_default); } m_queue.clear(); if (std::any_of(begin(), end(), std::mem_fn(&ChunkListNode::chunk))) throw internal_error("ChunkList::clear() called but a node with a valid chunk was found."); if (std::any_of(begin(), end(), std::mem_fn(&ChunkListNode::references))) throw internal_error("ChunkList::clear() called but a node with references != 0 was found."); if (std::any_of(begin(), end(), std::mem_fn(&ChunkListNode::writable))) throw internal_error("ChunkList::clear() called but a node with writable != 0 was found."); if (std::any_of(begin(), end(), std::mem_fn(&ChunkListNode::blocking))) throw internal_error("ChunkList::clear() called but a node with blocking != 0 was found."); base_type::clear(); } ChunkHandle ChunkList::get(size_type index, get_flags flags) { LT_LOG_THIS(DEBUG, "Get: index:%" PRIu32 " flags:%#x.", index, flags); errno = 0; ChunkListNode* node = &base_type::at(index); int allocate_flags = (flags & get_dont_log) ? ChunkManager::allocate_dont_log : 0; int prot_flags = MemoryChunk::prot_read | ((flags & get_writable) ? MemoryChunk::prot_write : 0); if (!node->is_valid()) { if (!m_manager->allocate(m_chunk_size, allocate_flags)) { LT_LOG_THIS(DEBUG, "Could not allocate: memory:%" PRIu64 " block:%" PRIu32 ".", m_manager->memory_usage(), m_manager->memory_block_count()); return ChunkHandle::from_error(ENOMEM); } Chunk* chunk; if ((flags & get_hashing)) chunk = m_slot_create_hashing_chunk(index, prot_flags); else if (flags & get_not_hashing) chunk = m_slot_create_chunk(index, prot_flags); else throw internal_error("ChunkList::get(...) called with get_hashing and get_not_hashing flags set."); if (chunk == nullptr) { int err = errno; LT_LOG_THIS(DEBUG, "Could not create: memory:%" PRIu64 " block:%" PRIu32 " errno:%i errmsg:%s.", m_manager->memory_usage(), m_manager->memory_block_count(), err, std::strerror(err)); m_manager->deallocate(m_chunk_size, allocate_flags | ChunkManager::allocate_revert_log); return ChunkHandle::from_error(err); } node->set_chunk(chunk); node->set_time_modified(0us); } else if (flags & get_writable && !node->chunk()->is_writable()) { if (node->blocking() != 0) { if ((flags & get_nonblock)) return ChunkHandle::from_error(EAGAIN); throw internal_error("No support yet for getting write permission for blocked chunk."); } Chunk* chunk = m_slot_create_chunk(index, prot_flags); if (chunk == nullptr) return ChunkHandle::from_error(errno); delete node->chunk(); node->set_chunk(chunk); node->set_time_modified(0us); } node->inc_references(); if (flags & get_writable) { node->inc_writable(); // Make sure that periodic syncing uses async on any subsequent // changes even if it was triggered before this get. node->set_sync_triggered(false); } if (flags & get_blocking) { node->inc_blocking(); } return ChunkHandle(node, flags & get_writable, flags & get_blocking); } // The chunks in 'm_queue' have been modified and need to be synced // when appropriate. Hopefully keeping the chunks mmap'ed for a while // will allow us to schedule writes at more resonable intervals. void ChunkList::release(ChunkHandle* handle, release_flags flags) { if (!handle->is_valid()) throw internal_error("ChunkList::release(...) received an invalid handle."); if (handle->object() < &*begin() || handle->object() >= &*end()) throw internal_error("ChunkList::release(...) received an unknown handle."); LT_LOG_THIS(DEBUG, "Release: index:%" PRIu32 " flags:%#x.", handle->index(), flags); if (handle->object()->references() <= 0 || (handle->is_writable() && handle->object()->writable() <= 0) || (handle->is_blocking() && handle->object()->blocking() <= 0)) throw internal_error("ChunkList::release(...) received a node with bad reference count."); if (handle->is_blocking()) { handle->object()->dec_blocking(); } if (handle->is_writable()) { if (handle->object()->writable() == 1) { if (is_queued(handle->object())) throw internal_error("ChunkList::release(...) tried to queue an already queued chunk."); // Only add those that have a modification time set? // // Only chunks that are not already in the queue will execute // this branch. m_queue.push_back(handle->object()); } else { handle->object()->dec_rw(); } } else { if (handle->object()->dec_references() == 0) { if (is_queued(handle->object())) throw internal_error("ChunkList::release(...) tried to unmap a queued chunk."); clear_chunk(handle->object(), flags); } } handle->clear(); } void ChunkList::clear_chunk(ChunkListNode* node, release_flags flags) { if (!node->is_valid()) throw internal_error("ChunkList::clear_chunk(...) !node->is_valid()."); delete node->chunk(); node->set_chunk(NULL); m_manager->deallocate(m_chunk_size, (flags & release_dont_log) ? ChunkManager::allocate_dont_log : 0); } inline bool ChunkList::sync_chunk(ChunkListNode* node, std::pair options) { if (node->references() <= 0 || node->writable() <= 0) throw internal_error("ChunkList::sync_chunk(...) got a node with invalid reference count."); if (!node->chunk()->sync(options.first)) return false; node->set_sync_triggered(true); // When returning here we're not properly deallocating the piece. // // Only release the chunk after a blocking sync. if (!options.second) return true; node->dec_rw(); if (node->references() == 0) clear_chunk(node, release_default); return true; } uint32_t ChunkList::sync_chunks(cache_list& cache, sync_flags flags) { LT_LOG_THIS(DEBUG, "Sync chunks: flags:%#x.", flags); if (m_queue.empty()) return 0; Queue::iterator split; if ((flags & sync_all)) split = m_queue.begin(); else split = std::stable_partition(m_queue.begin(), m_queue.end(), [](ChunkListNode* n) { return 1 != n->writable(); }); // Allow a flag that does more culling, so that we only get large // continous sections. // // How does this interact with timers, should be make it so that // only areas with timers are (preferably) synced? std::sort(split, m_queue.end()); // If we got enough diskspace and have not requested safe syncing, // then sync all chunks with MS_ASYNC. if (!(flags & (sync_safe | sync_sloppy))) { if (m_manager->safe_sync() || m_slot_free_diskspace(cache) <= m_manager->safe_free_diskspace()) flags = flags | sync_safe; else flags = flags | sync_force; } // TODO: This won't trigger for default sync_force. if ((flags & sync_use_timeout) && !(flags & sync_force)) split = partition_optimize(split, m_queue.end(), 50, 5, false); uint32_t failed = 0; for (auto itr = split, last = m_queue.end(); itr != last; ++itr) { // We can easily skip pieces by swap_iter, so there should be no // problem being selective about the ranges we sync. // Use a function for checking the next few chunks and see how far // we want to sync. When we want to sync everything use end. Call // before the loop, or add a check. // if we don't want to sync, swap and break. std::pair options = sync_options(*itr, flags); if (!sync_chunk(*itr, options)) { std::iter_swap(itr, split++); failed++; continue; } if (!options.second) std::iter_swap(itr, split++); } if (lt_log_is_valid(LOG_INSTRUMENTATION_MINCORE)) { instrumentation_update(INSTRUMENTATION_MINCORE_SYNC_SUCCESS, std::distance(split, m_queue.end())); instrumentation_update(INSTRUMENTATION_MINCORE_SYNC_FAILED, failed); instrumentation_update(INSTRUMENTATION_MINCORE_SYNC_NOT_SYNCED, std::distance(m_queue.begin(), split)); instrumentation_update(INSTRUMENTATION_MINCORE_SYNC_NOT_DEALLOCATED, std::count_if(split, m_queue.end(), std::mem_fn(&ChunkListNode::is_valid))); } m_queue.erase(split, m_queue.end()); // The caller must either make sure that it is safe to close the // download or set the sync_ignore_error flag. if (failed && !(flags & sync_ignore_error)) m_slot_storage_error("Could not sync chunk: " + std::string(std::strerror(errno))); return failed; } uint32_t ChunkList::sync_chunks_no_cache(sync_flags flags) { cache_list cache; return sync_chunks(cache, flags); } std::pair ChunkList::sync_options(ChunkListNode* node, sync_flags flags) { if ((flags & sync_force)) { if ((flags & sync_safe)) return std::make_pair(MemoryChunk::sync_sync, true); else return std::make_pair(MemoryChunk::sync_async, true); } else if ((flags & sync_safe)) { if (node->sync_triggered()) return std::make_pair(MemoryChunk::sync_sync, true); else return std::make_pair(MemoryChunk::sync_async, false); } else { return std::make_pair(MemoryChunk::sync_async, true); } } // Using a rather simple algorithm for now. This should really be more // robust against holes withing otherwise compact ranges and take into // consideration chunk size. ChunkList::Queue::iterator ChunkList::seek_range(Queue::iterator first, Queue::iterator last) { uint32_t prevIndex = (*first)->index(); while (++first != last) { if ((*first)->index() - prevIndex > 5) break; prevIndex = (*first)->index(); } return first; } inline bool ChunkList::check_node(ChunkListNode* node) { return node->time_modified() != 0us && node->time_modified() + std::chrono::seconds(m_manager->timeout_sync()) < this_thread::cached_time(); } // Optimize the selection of chunks to sync. Continuous regions are // preferred, while if too fragmented or if too few chunks are // available it skips syncing of all chunks. ChunkList::Queue::iterator ChunkList::partition_optimize(Queue::iterator first, Queue::iterator last, int weight, int maxDistance, bool dontSkip) { for (auto itr = first; itr != last;) { auto range = seek_range(itr, last); bool required = std::any_of(itr, range, [this](auto wrapper) { return check_node(wrapper); }); dontSkip = dontSkip || required; if (!required && std::distance(itr, range) < maxDistance) { // Don't sync this range. unsigned int l = std::min(range - itr, itr - first); std::swap_ranges(first, first + l, range - l); first += l; } else { // This probably increases too fast. weight -= std::distance(itr, range) * std::distance(itr, range); } itr = range; } // These values are all arbritrary... if (!dontSkip && weight > 0) return last; return first; } ChunkList::chunk_address_result ChunkList::find_address(void* ptr) { auto first = begin(); auto last = end(); for (; first != last; first++) { if (!first->is_valid()) continue; auto partition = first->chunk()->find_address(ptr); if (partition != first->chunk()->end()) return chunk_address_result(first, partition); first++; } return chunk_address_result(end(), Chunk::iterator()); } } // namespace torrent libtorrent-0.16.17/src/data/chunk_list.h000066400000000000000000000123611522271512000200660ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_CHUNK_LIST_H #define LIBTORRENT_DATA_CHUNK_LIST_H #include #include #include #include "chunk.h" #include "chunk_handle.h" #include "chunk_list_node.h" namespace torrent { class ChunkManager; class Content; class download_data; class DownloadWrapper; class FileList; class ChunkList : private std::vector { public: using size_type = uint32_t; using base_type = std::vector; using Queue = std::vector; using cache_list = std::vector>; using slot_chunk_index = std::function; using slot_value = std::function; using slot_string = std::function; using slot_free_space = std::function; using base_type::value_type; using base_type::reference; using base_type::difference_type; using base_type::iterator; using base_type::reverse_iterator; using base_type::const_iterator; using base_type::begin; using base_type::end; using base_type::size; using base_type::empty; using base_type::operator[]; enum sync_flags { sync_all = (1 << 0), sync_force = (1 << 1), sync_safe = (1 << 2), sync_sloppy = (1 << 3), sync_use_timeout = (1 << 4), sync_ignore_error = (1 << 5) }; enum get_flags { get_writable = (1 << 0), get_blocking = (1 << 1), get_nonblock = (1 << 2), get_hashing = (1 << 3), get_not_hashing = (1 << 4), get_dont_log = (1 << 5) }; enum release_flags { release_default = 0, release_dont_log = (1 << 0) }; static constexpr int flag_active = (1 << 0); ChunkList() = default; ~ChunkList() { clear(); } int flags() const { return m_flags; } void set_flags(int flags) { m_flags |= flags; } void unset_flags(int flags) { m_flags &= ~flags; } void change_flags(int flags, bool state) { if (state) set_flags(flags); else unset_flags(flags); } uint32_t chunk_size() const { return m_chunk_size; } size_type queue_size() const { return m_queue.size(); } download_data* data() { return m_data; } void set_data(download_data* data) { m_data = data; } void set_manager(ChunkManager* manager) { m_manager = manager; } void set_chunk_size(uint32_t cs) { m_chunk_size = cs; } bool has_chunk(size_type index, int prot) const; void resize(size_type to_size); void clear(); ChunkHandle get(size_type index, get_flags flags); void release(ChunkHandle* handle, release_flags flags); // Replace use_timeout with something like performance related // keyword. Then use that flag to decide if we should skip // non-continious regions. // Returns the number of failed syncs. uint32_t sync_chunks(cache_list& cache, sync_flags flags); uint32_t sync_chunks_no_cache(sync_flags flags); slot_string& slot_storage_error() { return m_slot_storage_error; } slot_chunk_index& slot_create_chunk() { return m_slot_create_chunk; } slot_chunk_index& slot_create_hashing_chunk() { return m_slot_create_hashing_chunk; } slot_free_space& slot_free_diskspace() { return m_slot_free_diskspace; } using chunk_address_result = std::pair; chunk_address_result find_address(void* ptr); private: ChunkList(const ChunkList&) = delete; ChunkList& operator=(const ChunkList&) = delete; inline bool is_queued(ChunkListNode* node); inline void clear_chunk(ChunkListNode* node, release_flags flags); inline bool sync_chunk(ChunkListNode* node, std::pair options); Queue::iterator partition_optimize(Queue::iterator first, Queue::iterator last, int weight, int maxDistance, bool dontSkip); static Queue::iterator seek_range(Queue::iterator first, Queue::iterator last); inline bool check_node(ChunkListNode* node); static std::pair sync_options(ChunkListNode* node, sync_flags flags); download_data* m_data{}; ChunkManager* m_manager{}; Queue m_queue; int m_flags{0}; uint32_t m_chunk_size{0}; slot_string m_slot_storage_error; slot_chunk_index m_slot_create_chunk; slot_chunk_index m_slot_create_hashing_chunk; slot_free_space m_slot_free_diskspace; }; inline ChunkList::sync_flags operator|(ChunkList::sync_flags a, ChunkList::sync_flags b) { return static_cast(static_cast(a) | static_cast(b)); } inline ChunkList::get_flags operator|(ChunkList::get_flags a, ChunkList::get_flags b) { return static_cast(static_cast(a) | static_cast(b)); } inline ChunkList::release_flags operator|(ChunkList::release_flags a, ChunkList::release_flags b) { return static_cast(static_cast(a) | static_cast(b)); } } // namespace torrent #endif libtorrent-0.16.17/src/data/chunk_list_node.h000066400000000000000000000052361522271512000210760ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_CHUNK_LIST_NODE_H #define LIBTORRENT_DATA_CHUNK_LIST_NODE_H #include "torrent/utils/chrono.h" #include namespace torrent { class Chunk; // ChunkNode can contain information like how long since it was last // used, last synced, last checked with mincore and how many // references there are to it. // // ChunkList will make sure all the nodes are cleaned up properly, so // no dtor is needed. class ChunkListNode { public: static constexpr uint32_t invalid_index = ~uint32_t(); bool is_valid() const { return m_chunk != NULL; } uint32_t index() const { return m_index; } void set_index(uint32_t idx) { m_index = idx; } Chunk* chunk() const { return m_chunk; } void set_chunk(Chunk* c) { m_chunk = c; } auto time_modified() const { return m_time_modified; } void set_time_modified(std::chrono::microseconds t) { m_time_modified = t; } auto time_preloaded() const { return m_time_preloaded; } void set_time_preloaded(std::chrono::microseconds t) { m_time_preloaded = t; } bool sync_triggered() const { return m_async_triggered; } void set_sync_triggered(bool v) { m_async_triggered = v; } int references() const { return m_references; } int dec_references() { return --m_references; } int inc_references() { return ++m_references; } int writable() const { return m_writable; } int dec_writable() { return --m_writable; } int inc_writable() { return ++m_writable; } int blocking() const { return m_blocking; } int dec_blocking() { return --m_blocking; } int inc_blocking() { return ++m_blocking; } void inc_rw() { inc_writable(); inc_references(); } void dec_rw() { dec_writable(); dec_references(); } private: uint32_t m_index{invalid_index}; Chunk* m_chunk{}; int m_references{0}; int m_writable{0}; int m_blocking{0}; bool m_async_triggered{false}; std::chrono::microseconds m_time_modified{}; std::chrono::microseconds m_time_preloaded{}; }; } // namespace torrent #endif libtorrent-0.16.17/src/data/chunk_part.cc000066400000000000000000000027711522271512000202230ustar00rootroot00000000000000#include "config.h" #include #include "torrent/exceptions.h" #include "chunk_part.h" namespace torrent { void ChunkPart::clear() { switch (m_mapped) { case MAPPED_MMAP: m_chunk.unmap(); break; default: case MAPPED_STATIC: throw internal_error("ChunkPart::clear() only MAPPED_MMAP supported."); } m_chunk.clear(); } bool ChunkPart::is_incore(uint32_t pos, uint32_t length) { length = std::min(length, remaining_from(pos)); pos = pos - m_position; if (pos > size()) throw internal_error("ChunkPart::is_incore(...) got invalid position."); if (length > size() || pos + length > size()) throw internal_error("ChunkPart::is_incore(...) got invalid length."); return m_chunk.is_incore(pos, length); } // TODO: Buggy. uint32_t ChunkPart::incore_length(uint32_t pos, uint32_t length) { // Do we want to use this? length = std::min(length, remaining_from(pos)); pos = pos - m_position; if (pos >= size()) throw internal_error("ChunkPart::incore_length(...) got invalid position"); const uint32_t touched = m_chunk.pages_touched(pos, length); auto buf = std::make_unique(touched); auto begin = buf.get(); auto end = buf.get() + touched; m_chunk.incore(begin, pos, length); uint32_t dist = std::distance(begin, std::find(begin, end, 0)); // This doesn't properly account for alignment when calculating the length. return std::min(dist ? (dist * MemoryChunk::page_size() - m_chunk.page_align()) : 0, length); } } // namespace torrent libtorrent-0.16.17/src/data/chunk_part.h000066400000000000000000000040351522271512000200600ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_STORAGE_CHUNK_PART_H #define LIBTORRENT_DATA_STORAGE_CHUNK_PART_H #include #include "memory_chunk.h" namespace torrent { class File; class ChunkPart { public: enum mapped_type { MAPPED_MMAP, MAPPED_STATIC }; ChunkPart(mapped_type mapped, const MemoryChunk& c, uint32_t pos) : m_mapped(mapped), m_chunk(c), m_position(pos) {} bool is_valid() const { return m_chunk.is_valid(); } bool is_contained(uint32_t p) const { return p >= m_position && p < m_position + size(); } bool has_address(void* p) const { return static_cast(p) >= m_chunk.begin() && p < m_chunk.end(); } void clear(); mapped_type mapped() const { return m_mapped; } MemoryChunk& chunk() { return m_chunk; } const MemoryChunk& chunk() const { return m_chunk; } uint32_t size() const { return m_chunk.size(); } uint32_t position() const { return m_position; } uint32_t remaining_from(uint32_t pos) const { return size() - (pos - m_position); } File* file() const { return m_file; } uint64_t file_offset() const { return m_file_offset; } void set_file(File* f, uint64_t f_offset) { m_file = f; m_file_offset = f_offset; } bool is_incore(uint32_t pos, uint32_t length = ~uint32_t()); uint32_t incore_length(uint32_t pos, uint32_t length = ~uint32_t()); private: mapped_type m_mapped; MemoryChunk m_chunk; uint32_t m_position; // Currently used to figure out what file and where a SIGBUS // occurred. Can also be used in the future to indicate if a part is // temporary storage, etc. File* m_file{}; uint64_t m_file_offset{0}; }; } // namespace torrent #endif libtorrent-0.16.17/src/data/hash_check_queue.cc000066400000000000000000000064051522271512000213470ustar00rootroot00000000000000#include "config.h" #include "hash_check_queue.h" #include #include "data/hash_chunk.h" #include "torrent/hash_string.h" #include "torrent/system/callbacks.h" #include "utils/instrumentation.h" namespace torrent { HashCheckQueue::HashCheckQueue() = default; HashCheckQueue::~HashCheckQueue() = default; // Always poke thread_disk after calling this. void HashCheckQueue::push_back(HashChunk* hash_chunk) { assert(std::this_thread::get_id() == main_thread::thread_id()); if (hash_chunk == NULL || !hash_chunk->chunk()->is_loaded() || !hash_chunk->chunk()->is_blocking()) throw internal_error("Invalid hash chunk passed to HashCheckQueue."); int64_t chunk_size = hash_chunk->chunk()->chunk()->chunk_size(); bool should_interrupt{}; { auto guard = std::scoped_lock(m_lock); // Set blocking...(? this needs to be possible to do after getting // the chunk) When doing this make sure we verify that the handle is // not previously blocked. should_interrupt = empty(); base_type::push_back(hash_chunk); } instrumentation_update(INSTRUMENTATION_MEMORY_HASHING_CHUNK_COUNT, 1); instrumentation_update(INSTRUMENTATION_MEMORY_HASHING_CHUNK_USAGE, chunk_size); if (should_interrupt) disk_thread::callback([this] { perform(); }); } // erase... // // The erasing function should call slot, perhaps return a bool if we // deleted, or in the rare case it has already been hashed and will // arraive in the near future? // // We could handle this by polling the done chunks on false return // values. // Lock the chunk list and increment blocking only when starting the // checking. // No removal of entries, only clearing. bool HashCheckQueue::remove(HashChunk* hash_chunk) { assert(std::this_thread::get_id() == main_thread::thread_id()); auto guard = std::scoped_lock(m_lock); bool result; auto itr = std::find(begin(), end(), hash_chunk); if (itr != end()) { base_type::erase(itr); result = true; int64_t size = hash_chunk->chunk()->chunk()->chunk_size(); instrumentation_update(INSTRUMENTATION_MEMORY_HASHING_CHUNK_COUNT, -1); instrumentation_update(INSTRUMENTATION_MEMORY_HASHING_CHUNK_USAGE, -size); } else { result = false; } return result; } void HashCheckQueue::perform() { assert(std::this_thread::get_id() == disk_thread::thread_id()); auto get_next_fn = [this]() -> HashChunk* { auto guard = std::scoped_lock(m_lock); if (empty()) return nullptr; auto* hash_chunk = base_type::front(); base_type::pop_front(); return hash_chunk; }; while (true) { auto* hash_chunk = get_next_fn(); if (hash_chunk == nullptr) break; if (!hash_chunk->chunk()->is_loaded()) throw internal_error("HashCheckQueue::perform(): !entry.node->is_loaded()."); int64_t size = hash_chunk->chunk()->chunk()->chunk_size(); instrumentation_update(INSTRUMENTATION_MEMORY_HASHING_CHUNK_COUNT, -1); instrumentation_update(INSTRUMENTATION_MEMORY_HASHING_CHUNK_USAGE, -size); if (!hash_chunk->perform(~uint32_t(), true)) throw internal_error("HashCheckQueue::perform(): !hash_chunk->perform(~uint32_t(), true)."); HashString hash; hash_chunk->hash_c(hash.data()); m_slot_chunk_done(hash_chunk, hash); } } } // namespace torrent libtorrent-0.16.17/src/data/hash_check_queue.h000066400000000000000000000021171522271512000212050ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_HASH_CHECK_QUEUE_H #define LIBTORRENT_DATA_HASH_CHECK_QUEUE_H #include #include #include #include "torrent/common.h" // TODO: Create separate directory for thread_disk's hash checking code. namespace torrent { class HashString; class HashChunk; class align_cacheline HashCheckQueue : private std::deque { public: using base_type = std::deque; using slot_chunk_handle = std::function; using base_type::iterator; using base_type::empty; using base_type::size; using base_type::begin; using base_type::end; using base_type::front; using base_type::back; HashCheckQueue(); ~HashCheckQueue(); // Guarded functions for adding new... void push_back(HashChunk* node); void perform(); bool remove(HashChunk* node); slot_chunk_handle& slot_chunk_done() { return m_slot_chunk_done; } private: std::mutex m_lock; slot_chunk_handle m_slot_chunk_done; }; } // namespace torrent #endif libtorrent-0.16.17/src/data/hash_chunk.cc000066400000000000000000000031631522271512000201740ustar00rootroot00000000000000#include "config.h" #include "chunk.h" #include "chunk_list_node.h" #include "hash_chunk.h" namespace torrent { void HashChunk::set_chunk(ChunkHandle h) { m_position = 0; m_chunk = h; m_hash.init(); } void HashChunk::hash_c(char* buffer) { m_hash.final_c(buffer); } bool HashChunk::perform(uint32_t length, bool force) { length = std::min(length, remaining()); if (m_position + length > m_chunk.chunk()->chunk_size()) throw internal_error("HashChunk::perform(...) received length out of range"); uint32_t l = force ? length : m_chunk.chunk()->incore_length(m_position); bool complete = l == length; while (l) { auto node = m_chunk.chunk()->at_position(m_position); l -= perform_part(node, l); } return complete; } void HashChunk::advise_willneed(uint32_t length) { if (!m_chunk.is_valid()) throw internal_error("HashChunk::willneed(...) called on an invalid chunk"); if (m_position + length > m_chunk.chunk()->chunk_size()) throw internal_error("HashChunk::willneed(...) received length out of range"); uint32_t pos = m_position; while (length) { auto itr = m_chunk.chunk()->at_position(pos); auto l = std::min(length, remaining_part(itr, pos)); itr->chunk().advise(pos - itr->position(), l, MemoryChunk::advice_willneed); pos += l; length -= l; ++itr; } } uint32_t HashChunk::perform_part(Chunk::iterator itr, uint32_t length) { length = std::min(length, remaining_part(itr, m_position)); m_hash.update(itr->chunk().begin() + m_position - itr->position(), length); m_position += length; return length; } } // namespace torrent libtorrent-0.16.17/src/data/hash_chunk.h000066400000000000000000000033661522271512000200430ustar00rootroot00000000000000#ifndef LIBTORRENT_HASH_CHUNK_H #define LIBTORRENT_HASH_CHUNK_H #include #include "chunk.h" #include "chunk_handle.h" #include "torrent/exceptions.h" #include "utils/sha1.h" namespace torrent { // This class interface assumes we're always going to check the whole // chunk. All we need is control of the (non-)blocking nature, and other // stuff related to performance and responsiveness. class ChunkListNode; class HashChunk { public: HashChunk(ChunkHandle h); ~HashChunk() = default; ChunkHandle* chunk() { return &m_chunk; } ChunkHandle& handle() { return m_chunk; } uint32_t remaining() const; void set_chunk(ChunkHandle h); void hash_c(char* buffer); // If force is true, then the return value is always true. bool perform(uint32_t length, bool force = true); void advise_willneed(uint32_t length); private: HashChunk(const HashChunk&) = delete; HashChunk& operator=(const HashChunk&) = delete; static uint32_t remaining_part(Chunk::iterator itr, uint32_t pos); uint32_t perform_part(Chunk::iterator itr, uint32_t length); uint32_t m_position; ChunkHandle m_chunk; Sha1 m_hash; }; inline HashChunk::HashChunk(ChunkHandle h) { set_chunk(h); } inline uint32_t HashChunk::remaining_part(Chunk::iterator itr, uint32_t pos) { return itr->size() - (pos - itr->position()); } inline uint32_t HashChunk::remaining() const { if (!m_chunk.is_loaded()) throw internal_error("HashChunk::remaining(...) called on an invalid chunk"); return m_chunk.chunk()->chunk_size() - m_position; } } // namespace torrent #endif libtorrent-0.16.17/src/data/hash_queue.cc000066400000000000000000000122001522271512000202000ustar00rootroot00000000000000#include "config.h" #include "data/hash_queue.h" #include #include #include #include "data/hash_check_queue.h" #include "data/hash_chunk.h" #include "data/thread_disk.h" #include "torrent/data/download_data.h" #include "torrent/exceptions.h" #include "torrent/system/callbacks.h" #include "torrent/utils/log.h" #include "torrent/utils/string_manip.h" #define LT_LOG_DATA(data, log_level, log_fmt, ...) \ lt_log_print_data(LOG_STORAGE_##log_level, data, "hash_queue", log_fmt, __VA_ARGS__); namespace torrent { struct HashQueueEqual { HashQueueEqual(HashQueueNode::id_type id, uint32_t index) : m_id(id), m_index(index) {} bool operator () (const HashQueueNode& q) const { return m_id == q.id() && m_index == q.get_index(); } HashQueueNode::id_type m_id; uint32_t m_index; }; struct HashQueueWillneed { HashQueueWillneed(int bytes) : m_bytes(bytes) {} bool operator () (HashQueueNode& q) { return (m_bytes -= q.call_willneed()) <= 0; } int m_bytes; }; // If madvise is not available it will always mark the pages as being // in memory, thus we don't need to modify m_maxTries to have full // disk usage. But this may cause too much blocking as it will think // everything is in memory, thus we need to throttle. // If we're done immediately, move the chunk to the front of the list so // the next work cycle gets stuff done. void HashQueue::push_back(ChunkHandle handle, HashQueueNode::id_type id, slot_done_type d) { LT_LOG_DATA(id, DEBUG, "Adding index:%" PRIu32 " to queue.", handle.index()); if (!handle.is_loaded()) throw internal_error("HashQueue::add(...) received an invalid chunk"); auto hash_chunk = new HashChunk(handle); base_type::push_back(HashQueueNode(id, hash_chunk, std::move(d))); ThreadDisk::thread_disk()->hash_check_queue()->push_back(hash_chunk); } bool HashQueue::has(HashQueueNode::id_type id) { return std::any_of(begin(), end(), [id](const auto& n) { return id == n.id(); }); } bool HashQueue::has(HashQueueNode::id_type id, uint32_t index) { return std::any_of(begin(), end(), HashQueueEqual(id, index)); } void HashQueue::remove(HashQueueNode::id_type id) { base_type::erase(std::remove_if(begin(), end(), [id, this](auto& itr) { if (itr.id() != id) return false; HashChunk *hash_chunk = itr.get_chunk(); LT_LOG_DATA(id, DEBUG, "Removing index:%" PRIu32 " from queue.", hash_chunk->handle().index()); bool result = ThreadDisk::thread_disk()->hash_check_queue()->remove(hash_chunk); // The hash chunk was not found, so we need to wait until the hash // check finishes. if (!result) { while (true) { { auto lock = std::scoped_lock(m_done_chunks_lock); auto itr = m_done_chunks.find(hash_chunk); if (itr != m_done_chunks.end()) { m_done_chunks.erase(itr); break; } } m_has_done_chunks.wait(false); } } itr.slot_done()(*hash_chunk->chunk(), NULL); itr.clear(); return true; }), end()); } void HashQueue::clear() { if (!empty()) throw internal_error("HashQueue::clear() called but valid nodes were found."); // Replace with a dtor check to ensure it is empty? // std::for_each(begin(), end(), std::mem_fn(&HashQueueNode::clear)); // base_type::clear(); } void HashQueue::work() { assert(std::this_thread::get_id() == main_thread::thread_id()); auto pop_next_fn = [this]() -> done_chunks_type::value_type { auto guard = std::scoped_lock(m_done_chunks_lock); if (m_done_chunks.empty()) { // Only clear the flag at the to ensure no unnessary callbacks are added. m_has_done_chunks = false; return {nullptr, {}}; } auto value = std::move(*m_done_chunks.begin()); m_done_chunks.erase(m_done_chunks.begin()); return value; }; while (true) { auto [hash_chunk, hash_value] = pop_next_fn(); if (hash_chunk == nullptr) break; // TODO: This is not optimal as we jump around... Check for front // of HashQueue in done_chunks instead. auto itr = std::find_if(begin(), end(), [hash_chunk](auto& node) { return node.get_chunk() == hash_chunk; }); // TODO: Fix this... if (itr == end()) throw internal_error("Could not find done chunk's node."); LT_LOG_DATA(itr->id(), DEBUG, "Passing index:%" PRIu32 " to owner: %s.", hash_chunk->handle().index(), utils::transform_to_hex_str(hash_value).c_str()); HashQueueNode::slot_done_type slotDone = itr->slot_done(); base_type::erase(itr); slotDone(hash_chunk->handle(), hash_value.c_str()); delete hash_chunk; } } void HashQueue::chunk_done(HashChunk* hash_chunk, const HashString& hash_value) { assert(std::this_thread::get_id() == disk_thread::thread_id()); auto lock = std::scoped_lock(m_done_chunks_lock); // TODO: Should we use try_emplace and check for duplicates here? m_done_chunks[hash_chunk] = hash_value; bool expected = false; if (m_has_done_chunks.compare_exchange_strong(expected, true)) { main_thread::callback([this] { work(); }); m_has_done_chunks.notify_all(); } } } // namespace torrent libtorrent-0.16.17/src/data/hash_queue.h000066400000000000000000000033601522271512000200510ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_HASH_QUEUE_H #define LIBTORRENT_DATA_HASH_QUEUE_H #include #include #include #include #include #include "torrent/hash_string.h" #include "hash_queue_node.h" #include "chunk_handle.h" namespace torrent { class HashChunk; class ThreadDisk; // Calculating hash of incore memory is blindingly fast, it's always // the loading from swap/disk that takes time. So with the exception // of large resumed downloads, try to check the hash immediately. This // helps us in getting as much done as possible while the pages are in // memory. class HashQueue : private std::deque { public: using base_type = std::deque; using done_chunks_type = std::map; using slot_done_type = HashQueueNode::slot_done_type; using slot_bool = std::function; using base_type::iterator; using base_type::empty; using base_type::size; using base_type::begin; using base_type::end; using base_type::front; using base_type::back; HashQueue() = default; ~HashQueue() { clear(); } void push_back(ChunkHandle handle, HashQueueNode::id_type id, slot_done_type d); bool has(HashQueueNode::id_type id); bool has(HashQueueNode::id_type id, uint32_t index); void remove(HashQueueNode::id_type id); void clear(); void work(); void chunk_done(HashChunk* hash_chunk, const HashString& hash_value); private: align_cacheline std::mutex m_done_chunks_lock; done_chunks_type m_done_chunks; align_cacheline std::atomic m_has_done_chunks{}; }; } // namespace torrent #endif libtorrent-0.16.17/src/data/hash_queue_node.cc000066400000000000000000000040651522271512000212170ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include "hash_chunk.h" #include "hash_queue_node.h" namespace torrent { uint32_t HashQueueNode::get_index() const { return m_chunk->chunk()->index(); } void HashQueueNode::clear() { delete m_chunk; m_chunk = NULL; } uint32_t HashQueueNode::call_willneed() { if (!m_willneed) { m_willneed = true; m_chunk->advise_willneed(m_chunk->remaining()); } return m_chunk->remaining(); } } // namespace torrent libtorrent-0.16.17/src/data/hash_queue_node.h000066400000000000000000000060011522271512000210510ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_DATA_HASH_QUEUE_NODE_H #define LIBTORRENT_DATA_HASH_QUEUE_NODE_H #include #include #include #include #include "chunk_handle.h" #include "hash_chunk.h" namespace torrent { class download_data; class HashQueueNode { public: using slot_done_type = std::function; using id_type = download_data*; HashQueueNode(id_type id, HashChunk* c, slot_done_type d) : m_id(id), m_chunk(c), m_slot_done(std::move(d)) {} id_type id() const { return m_id; } ChunkHandle& handle() { return *m_chunk->chunk(); } uint32_t get_index() const; HashChunk* get_chunk() const { return m_chunk; } bool get_willneed() const { return m_willneed; } bool perform_remaining(bool force) { return m_chunk->perform(m_chunk->remaining(), force); } void clear(); // Does not call multiple times on the same chunk. Returns the // number of bytes not checked in this chunk. uint32_t call_willneed(); slot_done_type& slot_done() { return m_slot_done; } private: id_type m_id; HashChunk* m_chunk; bool m_willneed{false}; slot_done_type m_slot_done; }; } // namespace torrent #endif libtorrent-0.16.17/src/data/hash_torrent.cc000066400000000000000000000153451522271512000205660ustar00rootroot00000000000000#include "config.h" #include #include "data/chunk_list.h" #include "torrent/exceptions.h" #include "torrent/data/download_data.h" #include "torrent/system/system.h" #include "torrent/utils/log.h" #include "hash_torrent.h" #define LT_LOG_THIS(log_level, log_fmt, ...) \ lt_log_print_data(LOG_STORAGE_##log_level, m_chunk_list->data(), "hash_torrent", log_fmt, __VA_ARGS__); namespace torrent { HashTorrent::HashTorrent(ChunkList* c) : m_chunk_list(c) { m_delay_retry.slot() = [this] { queue(false); }; } bool HashTorrent::start(bool try_quick) { LT_LOG_THIS(INFO, "start : position:%u size:%zu quick:%u.", m_position, m_chunk_list->size(), try_quick); if (m_position == m_chunk_list->size()) return true; if (m_position > 0 || m_chunk_list->empty()) throw internal_error("HashTorrent::start() call failed."); m_error_message.clear(); m_outstanding = 0; queue(try_quick); return m_position == m_chunk_list->size(); } void HashTorrent::clear() { LT_LOG_THIS(INFO, "clear", 0); m_outstanding = -1; m_position = 0; m_errno = 0; this_thread::scheduler()->erase(&m_delay_checked); this_thread::scheduler()->erase(&m_delay_retry); } bool HashTorrent::is_checked() const { // When closed the chunk list is empty. Position can be equal to // chunk list for a short while as we have outstanding chunks, so // check the latter. return !m_chunk_list->empty() && m_position == m_chunk_list->size() && m_outstanding == -1; } // After all chunks are checked it won't show as is_checked until // after this function is called. This allows for the hash done signal // to be delayed. void HashTorrent::confirm_checked() { LT_LOG_THIS(INFO, "confirmed checked", 0); if (m_outstanding != 0) throw internal_error("HashTorrent::confirm_checked() m_outstanding != 0."); m_outstanding = -1; } void HashTorrent::receive_chunkdone(uint32_t index) { LT_LOG_THIS(DEBUG, "received chunk done: index:%" PRIu32 " outstanding:%i.", index, m_outstanding); if (m_outstanding <= 0) throw internal_error("HashTorrent::receive_chunkdone() m_outstanding <= 0."); // m_signalChunk will always point to // DownloadMain::receive_hash_done, so it will take care of cleanup. // // Make sure we call chunkdone before torrentDone has a chance to // trigger. m_outstanding--; queue(false); } // Mark unsuccessful checks so that if we have just stopped the // hash checker it will ensure those pieces get rechecked upon // restart. void HashTorrent::receive_chunk_cleared(uint32_t index) { LT_LOG_THIS(DEBUG, "received chunk cleared: index:%" PRIu32 " outstanding:%i.", index, m_outstanding); if (m_outstanding <= 0) throw internal_error("HashTorrent::receive_chunk_cleared() m_outstanding < 0."); if (m_ranges.has(index)) throw internal_error("HashTorrent::receive_chunk_cleared() m_ranges.has(index)."); m_outstanding--; m_ranges.insert(index, index + 1); } void HashTorrent::queue(bool quick) { LT_LOG_THIS(INFO, "queuing : position:%u outstanding:%i quick:%u", m_position, m_outstanding, quick); if (!is_checking()) throw internal_error("HashTorrent::queue() called but it's not running."); while (m_position < m_chunk_list->size()) { if (m_outstanding > 10 && m_outstanding * m_chunk_list->chunk_size() > (128 << 20)) return; // Not very efficient, but this is seldomly done. auto itr = m_ranges.find(m_position); if (itr == m_ranges.end()) { m_position = m_chunk_list->size(); break; } else if (m_position < itr->first) { m_position = itr->first; } // Need to do increment later if we're going to support resume // hashing a quick hashed torrent. ChunkHandle handle = m_chunk_list->get(m_position, ChunkList::get_dont_log | ChunkList::get_hashing); if (quick) { // We're not actually interested in doing any hashing, so just // skip what we know is not possible to hash. // // If the file does not exist then no valid error number is // returned. if (m_outstanding != 0) throw internal_error("HashTorrent::queue() quick hashing but m_outstanding != 0."); if (handle.is_valid()) { LT_LOG_THIS(DEBUG, "quick : skip valid handle : position:%u", m_position); return m_chunk_list->release(&handle, ChunkList::release_dont_log); } if (handle.error_number() != 0 && handle.error_number() != ENOENT) { LT_LOG_THIS(DEBUG, "quick : skip invalid handle with non-ENOENT error : position:%u errno:%s", m_position, system::errno_enum(handle.error_number())); return; } m_position++; continue; } // If the error number is not valid, then we've just encountered a // file that hasn't be created/resized. Which means we ignore it // when doing initial hashing. if (handle.error_number() == ENOMEM) { LT_LOG_THIS(INFO, "ENOMEM during hash, retrying: position:%u outstanding:%i", m_position, m_outstanding); if (m_outstanding == 0) this_thread::scheduler()->update_wait_for(&m_delay_retry, std::chrono::milliseconds(100)); return; } if (handle.error_number() != 0 && handle.error_number() != ENOENT) { if (handle.is_valid()) throw internal_error("HashTorrent::queue() valid handle with error number: " + system::errno_enum_str(handle.error_number())); // We wait for all the outstanding chunks to be checked before // borking completely, else low-memory devices might not be able // to finish the hash check. if (m_outstanding != 0) return; // The rest of the outstanding chunks get ignored by // DownloadWrapper::receive_hash_done. Obsolete. auto error_pos = m_position; clear(); m_errno = handle.error_number(); m_error_message = "Hash check I/O error at chunk " + std::to_string(error_pos) + ": " + std::strerror(handle.error_number()); LT_LOG_THIS(INFO, "completed with error: position:%u errno:%s", m_position, system::errno_enum(handle.error_number())); this_thread::scheduler()->update_wait_for(&m_delay_checked, 0s); return; } m_position++; if (!handle.is_valid() && handle.error_number() == 0) throw internal_error("HashTorrent::queue() invalid handle but no error."); // Missing file, skip the hash check. if (!handle.is_valid()) continue; if (m_slot_check_chunk) m_slot_check_chunk(handle); m_outstanding++; } if (m_outstanding == 0) { LT_LOG_THIS(INFO, "completed : position:%u", m_position); // Update the scheduled item just to make sure that if hashing is // started again during the delay it won't cause an exception. this_thread::scheduler()->update_wait_for(&m_delay_checked, 0s); } } } // namespace torrent libtorrent-0.16.17/src/data/hash_torrent.h000066400000000000000000000036361522271512000204300ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_HASH_TORRENT_H #define LIBTORRENT_DATA_HASH_TORRENT_H #include #include #include #include "data/chunk_handle.h" #include "torrent/utils/ranges.h" #include "torrent/utils/scheduler.h" namespace torrent { class ChunkList; class HashTorrent { public: using Ranges = ranges; using slot_chunk_handle = std::function; HashTorrent(ChunkList* c); ~HashTorrent() { clear(); } bool start(bool try_quick); void clear(); bool is_checking() const { return m_outstanding >= 0; } bool is_checked() const; void confirm_checked(); Ranges& hashing_ranges() { return m_ranges; } uint32_t position() const { return m_position; } uint32_t outstanding() const { return m_outstanding; } int error_number() const { return m_errno; } const std::string& error_message() const { return m_error_message; } slot_chunk_handle& slot_check_chunk() { return m_slot_check_chunk; } auto& delay_checked() { return m_delay_checked; } auto& delay_retry() { return m_delay_retry; } void receive_chunkdone(uint32_t index); void receive_chunk_cleared(uint32_t index); private: void queue(bool quick); unsigned int m_position{0}; int m_outstanding{-1}; Ranges m_ranges; int m_errno{0}; std::string m_error_message; ChunkList* m_chunk_list; slot_chunk_handle m_slot_check_chunk; utils::SchedulerEntry m_delay_checked; utils::SchedulerEntry m_delay_retry; }; } // namespace torrent #endif libtorrent-0.16.17/src/data/memory_chunk.cc000066400000000000000000000061551522271512000205650ustar00rootroot00000000000000#include "config.h" #include #include #include "torrent/exceptions.h" #include "memory_chunk.h" #ifdef __sun__ extern "C" int madvise(void *, size_t, int); //#include //Should be the include line instead, but Solaris //has an annoying bug wherein it doesn't declare //madvise for C++. #endif namespace torrent { uint32_t MemoryChunk::m_pagesize = getpagesize(); inline void MemoryChunk::align_pair(uint32_t* offset, uint32_t* length) const { *offset += page_align(); *length += *offset % m_pagesize; *offset -= *offset % m_pagesize; } MemoryChunk::MemoryChunk(char* ptr, char* begin, char* end, int prot, int flags) : m_ptr(ptr), m_begin(begin), m_end(end), m_prot(prot), m_flags(flags) { if (ptr == NULL) throw internal_error("MemoryChunk::MemoryChunk(...) received ptr == NULL"); if (page_align() >= m_pagesize) throw internal_error("MemoryChunk::MemoryChunk(...) received an page alignment >= page size"); if (reinterpret_cast(ptr) % m_pagesize) throw internal_error("MemoryChunk::MemoryChunk(...) is not aligned to a page"); } void MemoryChunk::unmap() { if (!is_valid()) throw internal_error("MemoryChunk::unmap() called on an invalid object"); if (munmap(m_ptr, m_end - m_ptr) != 0) throw internal_error("MemoryChunk::unmap() system call failed: " + std::string(std::strerror(errno))); } void MemoryChunk::incore(char* buf, uint32_t offset, uint32_t length) { if (!is_valid()) throw internal_error("Called MemoryChunk::incore(...) on an invalid object"); if (!is_valid_range(offset, length)) throw internal_error("MemoryChunk::incore(...) received out-of-range input"); align_pair(&offset, &length); #if USE_MINCORE #if USE_MINCORE_UNSIGNED if (mincore(m_ptr + offset, length, reinterpret_cast(buf))) #else if (mincore(m_ptr + offset, length, buf)) #endif throw storage_error("System call mincore failed: " + std::string(std::strerror(errno))); #else // !USE_MINCORE // Pretend all pages are in memory. memset(buf, 1, pages_touched(offset, length)); #endif } bool MemoryChunk::advise(uint32_t offset, uint32_t length, int advice) { if (!is_valid()) throw internal_error("Called MemoryChunk::advise() on an invalid object"); if (!is_valid_range(offset, length)) throw internal_error("MemoryChunk::advise(...) received out-of-range input"); #if USE_MADVISE align_pair(&offset, &length); if (madvise(m_ptr + offset, length, advice) == 0) return true; else if (errno == EINVAL || (errno == ENOMEM && advice != advice_willneed) || errno == EBADF) throw internal_error("MemoryChunk::advise(...) " + std::string(strerror(errno))); else return false; #else return true; #endif } bool MemoryChunk::sync(uint32_t offset, uint32_t length, int flags) { if (!is_valid()) throw internal_error("Called MemoryChunk::sync() on an invalid object"); if (!is_valid_range(offset, length)) throw internal_error("MemoryChunk::sync(...) received out-of-range input"); align_pair(&offset, &length); return msync(m_ptr + offset, length, flags) == 0; } } // namespace torrent libtorrent-0.16.17/src/data/memory_chunk.h000066400000000000000000000120111522271512000204130ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_MEMORY_CHUNK_H #define LIBTORRENT_DATA_MEMORY_CHUNK_H #include #include #include #include #include namespace torrent { class MemoryChunk { public: // Consider information about whetever the memory maps to a file or // not, since mincore etc can only be called on files. static constexpr int prot_exec = PROT_EXEC; static constexpr int prot_read = PROT_READ; static constexpr int prot_write = PROT_WRITE; static constexpr int prot_none = PROT_NONE; static constexpr int map_shared = MAP_SHARED; static constexpr int map_anon = MAP_ANON; #ifdef USE_MADVISE static constexpr int advice_normal = MADV_NORMAL; static constexpr int advice_random = MADV_RANDOM; static constexpr int advice_sequential = MADV_SEQUENTIAL; static constexpr int advice_willneed = MADV_WILLNEED; static constexpr int advice_dontneed = MADV_DONTNEED; #else static constexpr int advice_normal = 0; static constexpr int advice_random = 1; static constexpr int advice_sequential = 2; static constexpr int advice_willneed = 3; static constexpr int advice_dontneed = 4; #endif static constexpr int sync_sync = MS_SYNC; static constexpr int sync_async = MS_ASYNC; static constexpr int sync_invalidate = MS_INVALIDATE; MemoryChunk() = default; ~MemoryChunk() { clear(); } MemoryChunk(const MemoryChunk&) = default; MemoryChunk& operator=(const MemoryChunk&) = default; // Doesn't allow ptr == NULL, use the default ctor instead. MemoryChunk(char* ptr, char* begin, char* end, int prot, int flags); bool is_valid() const { return m_ptr; } bool is_readable() const { return m_prot & PROT_READ; } bool is_writable() const { return m_prot & PROT_WRITE; } bool is_exec() const { return m_prot & PROT_EXEC; } inline bool is_valid_range(uint32_t offset, uint32_t length) const; bool has_permissions(int prot) const { return !(prot & ~m_prot); } char* ptr() const { return m_ptr; } char* begin() const { return m_begin; } char* end() const { return m_end; } int get_prot() const { return m_prot; } uint32_t size() const { return m_end - m_begin; } uint32_t size_aligned() const; inline void clear(); void unmap(); // Use errno and strerror if you want to know why these failed. void incore(char* buf, uint32_t offset, uint32_t length); bool advise(uint32_t offset, uint32_t length, int advice); bool sync(uint32_t offset, uint32_t length, int flags); bool is_incore(uint32_t offset, uint32_t length); // Helper functions for aligning offsets and ranges to page boundaries. uint32_t page_align() const { return m_begin - m_ptr; } uint32_t page_align(uint32_t o) const { return (o + page_align()) % m_pagesize; } // This won't return correct values if length == 0. inline uint32_t pages_touched(uint32_t offset, uint32_t length) const; static uint32_t page_size() { return m_pagesize; } private: inline void align_pair(uint32_t* offset, uint32_t* length) const; static uint32_t m_pagesize; char* m_ptr{}; char* m_begin{}; char* m_end{}; int m_prot; int m_flags{PROT_NONE}; }; inline bool MemoryChunk::is_valid_range(uint32_t offset, uint32_t length) const { return length != 0 && static_cast(offset) + length <= size(); } inline void MemoryChunk::clear() { m_ptr = m_begin = m_end = NULL; m_flags = PROT_NONE; } inline uint32_t MemoryChunk::pages_touched(uint32_t offset, uint32_t length) const { if (length == 0) return 0; return (length + page_align(offset) + m_pagesize - 1) / m_pagesize; } // The size of the mapped memory. inline uint32_t MemoryChunk::size_aligned() const { return std::distance(m_ptr, m_end) + page_size() - ((std::distance(m_ptr, m_end) - 1) % page_size() + 1); } inline bool MemoryChunk::is_incore(uint32_t offset, uint32_t length) { const uint32_t size = pages_touched(offset, length); auto buf = std::make_unique(size); auto begin = buf.get(); auto end = begin + size; incore(begin, offset, length); return std::find(begin, end, 0) == end; } } // namespace torrent #endif libtorrent-0.16.17/src/data/socket_file.cc000066400000000000000000000102121522271512000203410ustar00rootroot00000000000000#include "config.h" #include "data/socket_file.h" #include #include #include #include #include "torrent/exceptions.h" #include "torrent/net/fd.h" #include "torrent/utils/file_stat.h" #include "torrent/utils/log.h" #define LT_LOG_ERROR(log_fmt, ...) \ lt_log_print(LOG_STORAGE, "socket_file->%i: " log_fmt, m_fd, __VA_ARGS__); namespace torrent { bool SocketFile::open(const std::string& path, int prot, int flags, mode_t mode) { close(); if (prot & MemoryChunk::prot_read && prot & MemoryChunk::prot_write) flags |= O_RDWR; else if (prot & MemoryChunk::prot_read) flags |= O_RDONLY; else if (prot & MemoryChunk::prot_write) flags |= O_WRONLY; else throw internal_error("torrent::SocketFile::open(...) Tried to open file with no protection flags"); fd_type fd = fd_open_file(path, flags, mode); if (fd == invalid_fd) return false; m_fd = fd; return true; } void SocketFile::close() { if (!is_open()) return; ::close(m_fd); m_fd = invalid_fd; } uint64_t SocketFile::size() const { if (!is_open()) throw internal_error("SocketFile::size() called on a closed file"); utils::FileStat fs; return fs.update(m_fd) ? fs.size() : 0; } bool SocketFile::set_size(uint64_t size) const { if (!is_open()) throw internal_error("SocketFile::set_size() called on a closed file"); if (ftruncate(m_fd, size) == -1) { return false; } return true; } bool SocketFile::allocate([[maybe_unused]] uint64_t size, [[maybe_unused]] int flags) const { if (!is_open()) throw internal_error("SocketFile::allocate() called on a closed file"); #if defined(HAVE_FALLOCATE) // Optimal path for modern Linux if (::fallocate(m_fd, 0, 0, size) == -1) { LT_LOG_ERROR("fallocate failed : %s", std::strerror(errno)); return false; } #elif defined(HAVE_POSIX_FALLOCATE) // Standard fallback path for modern BSD platforms // Note: Kept your specific non-blocking optimization check if still desired if (flags & flag_fallocate_blocking) { if (::posix_fallocate(m_fd, 0, size) == -1) { LT_LOG_ERROR("posix_fallocate failed : %s", std::strerror(errno)); return false; } } #elif defined(__APPLE__) // Native fallback for macOS (which lacks both fallocate variants) fstore_t fstore; fstore.fst_flags = F_ALLOCATECONTIG; fstore.fst_posmode = F_PEOFPOSMODE; fstore.fst_offset = 0; fstore.fst_length = size; fstore.fst_bytesalloc = 0; // Fallback if contiguous block allocation fails if (::fcntl(m_fd, F_PREALLOCATE, &fstore) == -1) { fstore.fst_flags = F_ALLOCATEALL; if (::fcntl(m_fd, F_PREALLOCATE, &fstore) == -1) { LT_LOG_ERROR("fcntl(,F_PREALLOCATE,) failed : %s", std::strerror(errno)); } } #endif return true; } MemoryChunk SocketFile::create_padding_chunk(uint32_t length, int prot, int flags) { flags |= MemoryChunk::map_anon; auto ptr = static_cast(mmap(NULL, length, prot, flags, -1, 0)); if (ptr == MAP_FAILED) return MemoryChunk(); return MemoryChunk(ptr, ptr, ptr + length, prot, flags); } MemoryChunk SocketFile::create_chunk(uint64_t offset, uint32_t length, int prot, int flags) const { if (!is_open()) throw internal_error("SocketFile::get_chunk() called on a closed file"); // For some reason mapping beyond the extent of the file does not // cause mmap to complain, so we need to check manually here. if (length == 0 || offset > size() || offset + length > size()) return MemoryChunk(); uint32_t page_size = MemoryChunk::page_size(); uint64_t align = offset % page_size; if (page_size < 4096 || page_size >= (1 << 18)) throw internal_error("SocketFile::get_chunk() page size is out-of-bounds: " + std::to_string(page_size)); if (align >= page_size) throw internal_error("SocketFile::get_chunk() alignment calculation error: " + std::to_string(align)); auto ptr = static_cast(::mmap(nullptr, length + align, prot, flags, m_fd, offset - align)); if (ptr == MAP_FAILED) return MemoryChunk(); return MemoryChunk(ptr, ptr + align, ptr + align + length, prot, flags); } } // namespace torrent libtorrent-0.16.17/src/data/socket_file.h000066400000000000000000000030751522271512000202140ustar00rootroot00000000000000#ifndef LIBTORRENT_SOCKET_FILE_H #define LIBTORRENT_SOCKET_FILE_H #include #include #include #include #include "memory_chunk.h" namespace torrent { class SocketFile { public: using fd_type = int; static constexpr fd_type invalid_fd = -1; static constexpr int o_create = O_CREAT; static constexpr int o_truncate = O_TRUNC; static constexpr int o_nonblock = O_NONBLOCK; static constexpr int flag_fallocate = (1 << 0); static constexpr int flag_fallocate_blocking = (1 << 1); SocketFile() = default; ~SocketFile() = default; SocketFile(fd_type fd) : m_fd(fd) {} bool is_open() const { return m_fd != invalid_fd; } bool open(const std::string& path, int prot, int flags, mode_t mode = 0666); void close(); uint64_t size() const; bool set_size(uint64_t size) const; bool allocate(uint64_t size, int flags = 0) const; static MemoryChunk create_padding_chunk(uint32_t length, int prot, int flags); MemoryChunk create_chunk(uint64_t offset, uint32_t length, int prot, int flags) const; fd_type fd() const { return m_fd; } private: SocketFile(const SocketFile&) = delete; SocketFile& operator=(const SocketFile&) = delete; // Use custom flags if stuff like file locking etc is implemented. fd_type m_fd{invalid_fd}; }; } // namespace torrent #endif libtorrent-0.16.17/src/data/thread_disk.cc000066400000000000000000000057711522271512000203510ustar00rootroot00000000000000#include "config.h" #include "data/thread_disk.h" #include #include "thread_main.h" #include "data/hash_check_queue.h" #include "data/hash_queue.h" #include "torrent/exceptions.h" #include "torrent/net/resolver.h" #include "utils/instrumentation.h" namespace torrent { namespace disk_thread { system::Thread* thread() { return ThreadDisk::thread_disk(); } std::thread::id thread_id() { return ThreadDisk::thread_disk()->thread_id(); } void callback(std::function&& fn) { ThreadDisk::thread_disk()->callback(std::move(fn)); } void callback(system::callback_id& id, std::function&& fn) { ThreadDisk::thread_disk()->callback(id, std::move(fn)); } void callback_interrupt(std::function&& fn) { ThreadDisk::thread_disk()->callback_interrupt(std::move(fn)); } void callback_interrupt(system::callback_id& id, std::function&& fn) { ThreadDisk::thread_disk()->callback_interrupt(id, std::move(fn)); } void cancel_callback(system::callback_id& id) { ThreadDisk::thread_disk()->cancel_callback(id); } void cancel_callback_and_wait(system::callback_id& id) { ThreadDisk::thread_disk()->cancel_callback_and_wait(id); } } // namespace disk_thread ThreadDisk* ThreadDisk::m_thread_disk{nullptr}; ThreadDisk::~ThreadDisk() = default; void ThreadDisk::create_thread() { assert(m_thread_disk == nullptr && "ThreadDisk already created."); m_thread_disk = new ThreadDisk; m_thread_disk->m_hash_check_queue = std::make_unique(); } void ThreadDisk::destroy_thread() { try { delete m_thread_disk; m_thread_disk = nullptr; } catch (...) { m_thread_disk = nullptr; } } ThreadDisk* ThreadDisk::thread_disk() { return m_thread_disk; } void ThreadDisk::init_thread() { m_resolver = std::make_unique(); m_state = STATE_INITIALIZED; m_instrumentation_index = INSTRUMENTATION_POLLING_DO_POLL_DISK - INSTRUMENTATION_POLLING_DO_POLL; m_hash_check_queue->slot_chunk_done() = [](auto hc, const auto& hv) { ThreadMain::thread_main()->hash_queue()->chunk_done(hc, hv); }; } void ThreadDisk::cleanup_thread() { assert(m_hash_check_queue->empty() && "ThreadDisk::cleanup_thread(): m_hash_check_queue not empty."); } void ThreadDisk::call_events() { // lt_log_print_locked(torrent::LOG_THREAD_NOTICE, "Got thread_disk tick."); // TODO: Consider moving this into timer events instead. if ((m_flags & flag_do_shutdown)) { if ((m_flags & flag_did_shutdown)) throw internal_error("Already trigged shutdown."); m_flags |= flag_did_shutdown; throw shutdown_exception(); } process_callbacks(); } std::chrono::microseconds ThreadDisk::next_timeout() { return std::chrono::microseconds(10s); } } // namespace torrent libtorrent-0.16.17/src/data/thread_disk.h000066400000000000000000000017171522271512000202070ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_THREAD_DISK_H #define LIBTORRENT_DATA_THREAD_DISK_H #include #include "torrent/common.h" #include "torrent/system/thread.h" namespace torrent { class HashCheckQueue; class LIBTORRENT_EXPORT ThreadDisk : public system::Thread { public: ~ThreadDisk() override; static void create_thread(); static void destroy_thread(); static ThreadDisk* thread_disk(); const char* name() const override { return "rtorrent-disk"; } void init_thread() override; void cleanup_thread() override; HashCheckQueue* hash_check_queue() { return m_hash_check_queue.get(); } private: ThreadDisk() = default; void call_events() override; std::chrono::microseconds next_timeout() override; static ThreadDisk* m_thread_disk; std::unique_ptr m_hash_check_queue; }; } // namespace torrent #endif // LIBTORRENT_DATA_THREAD_DISK_H libtorrent-0.16.17/src/dht/000077500000000000000000000000001522271512000154155ustar00rootroot00000000000000libtorrent-0.16.17/src/dht/dht_bucket.cc000066400000000000000000000102021522271512000200330ustar00rootroot00000000000000#include "config.h" #include "dht_bucket.h" #include #include "dht_node.h" #include "torrent/exceptions.h" namespace torrent { DhtBucket::DhtBucket(const HashString& begin, const HashString& end) : m_begin(begin), m_end(end) { reserve(num_nodes); } void DhtBucket::add_node(DhtNode* n) { push_back(n); touch(); if (n->is_good()) m_good++; else if (n->is_bad()) m_bad++; m_fullCacheLength = 0; } void DhtBucket::remove_node(DhtNode* n) { auto itr = std::find(begin(), end(), n); if (itr == end()) throw internal_error("DhtBucket::remove_node called for node not in bucket."); erase(itr); if (n->is_good()) m_good--; else if (n->is_bad()) m_bad--; m_fullCacheLength = 0; } void DhtBucket::count() { m_good = std::count_if(begin(), end(), std::mem_fn(&DhtNode::is_good)); m_bad = std::count_if(begin(), end(), std::mem_fn(&DhtNode::is_bad)); } // Called every 15 minutes for housekeeping. void DhtBucket::update() { count(); // In case adjacent buckets whose nodes we borrowed have changed, // we force an update of the cache. m_fullCacheLength = 0; } DhtBucket::iterator DhtBucket::find_replacement_candidate(bool onlyOldest) { auto oldest = end(); auto oldestTime = std::numeric_limits::max(); for (auto itr = begin(); itr != end(); ++itr) { if ((*itr)->is_bad() && !onlyOldest) return itr; if ((*itr)->last_seen() < oldestTime) { oldestTime = (*itr)->last_seen(); oldest = itr; } } return oldest; } void DhtBucket::get_mid_point(HashString* middle) const { *middle = m_end; for (unsigned int i=0; i(m_begin[i]) + static_cast(m_end[i])) / 2; break; } } void DhtBucket::get_random_id(HashString* rand_id) const { // Generate a random ID between m_begin and m_end. // Since m_end - m_begin = 2^n - 1, we can do a bitwise AND operation. for (unsigned int i=0; i0; i--) { unsigned int sum = static_cast(mid_range[i - 1]) + carry; m_begin[i - 1] = static_cast(sum); carry = sum >> 8; } // Move nodes over to other bucket if they fall in its range, then // delete them from this one. auto split = std::partition(begin(), end(), [this](auto dht) { return dht->is_in_range(this); }); other->insert(other->end(), split, end()); for (auto& dht : *other) { dht->set_bucket(other); } erase(split, end()); other->set_time(m_last_changed); other->count(); count(); // Maintain child (adjacent narrower bucket) and parent (adjacent wider bucket) // so that given router ID is in child. if (other->is_in_range(id)) { // Make other become our new child. m_child = other; other->m_parent = this; } else { // We become other's child, other becomes our parent's child. if (parent() != NULL) { parent()->m_child = other; other->m_parent = parent(); } m_parent = other; other->m_child = this; } return other; } void DhtBucket::build_full_cache() { DhtBucketChain chain(this); char* pos = m_fullCache; do { for (auto itr = chain.bucket()->begin(); itr != chain.bucket()->end() && pos < m_fullCache + sizeof(m_fullCache); ++itr) { if (!(*itr)->is_bad()) { pos = (*itr)->store_compact(pos); if (pos > m_fullCache + sizeof(m_fullCache)) throw internal_error("DhtRouter::store_closest_nodes wrote past buffer end."); } } } while (pos < m_fullCache + sizeof(m_fullCache) && chain.next() != NULL); m_fullCacheLength = pos - m_fullCache; } } // namespace torrent libtorrent-0.16.17/src/dht/dht_bucket.h000066400000000000000000000125271522271512000177110ustar00rootroot00000000000000#ifndef LIBTORRENT_DHT_BUCKET_H #define LIBTORRENT_DHT_BUCKET_H #include #include #include "torrent/hash_string.h" #include "torrent/object_raw_bencode.h" namespace torrent { class DhtNode; // A container holding a small number of nodes that fall in a given binary // partition of the 160-bit ID space (i.e. the range ID1..ID2 where ID2-ID1+1 is // a power of 2.) class DhtBucket : private std::vector { public: static constexpr unsigned int num_nodes = 8; using base_type = std::vector; using base_type::const_iterator; using base_type::iterator; using base_type::begin; using base_type::end; using base_type::size; using base_type::empty; DhtBucket(const HashString& begin, const HashString& end); // Add new node. Does NOT set node's bucket automatically (to allow adding a // node to multiple buckets, with only one "main" bucket.) void add_node(DhtNode* n); void remove_node(DhtNode* n); // Bucket's ID range functions. const HashString& id_range_begin() const { return m_begin; } HashString& id_range_begin() { return m_begin; } const HashString& id_range_end() const { return m_end; } bool is_in_range(const HashString& id) const { return m_begin <= id && id <= m_end; } // Find middle or random ID in bucket. void get_mid_point(HashString* middle) const; void get_random_id(HashString* rand_id) const; // Node counts and bucket stats. bool is_full() const { return size() >= num_nodes; } bool has_space() const { return !is_full() || num_bad() > 0; } unsigned int num_good() const { return m_good; } unsigned int num_bad() const { return m_bad; } unsigned int age() const { return this_thread::cached_seconds().count() - m_last_changed; } void touch() { m_last_changed = this_thread::cached_seconds().count(); } void set_time(int32_t time) { m_last_changed = time; } // Called every 15 minutes after updating nodes. void update(); // Return candidate for replacement (a bad node or the oldest node); may // return end() unless has_space() is true. iterator find_replacement_candidate(bool onlyOldest = false); // Split the bucket in two and redistribute nodes. Returned bucket is the // lower half, "this" bucket keeps the upper half. Sets parent/child so // that the bucket the given ID falls in is the child. DhtBucket* split(const HashString& id); // Parent and child buckets. Parent is the adjacent bucket with double the // ID width, child the adjacent bucket with half the width (except the very // last child which has the same width.) DhtBucket* parent() const { return m_parent; } DhtBucket* child() const { return m_child; } // Return a full bucket's worth of compact node data. If this bucket is not // full, it uses nodes from the child/parent buckets until we have enough. raw_string full_bucket(); // Called by the DhtNode on its bucket to update good/bad node counts. void node_now_good(bool was_bad); void node_now_bad(bool was_good); private: void count(); void build_full_cache(); DhtBucket* m_parent{}; DhtBucket* m_child{}; int64_t m_last_changed{this_thread::cached_seconds().count()}; unsigned int m_good{0}; unsigned int m_bad{0}; size_t m_fullCacheLength{0}; // These are 40 bytes together, so might as well put them last. // m_end is const because it is used as key for the DhtRouter routing table // map, which would be inconsistent if m_end were changed carelessly. HashString m_begin; HashString m_end; char m_fullCache[num_nodes * 26]; }; // Helper class to recursively follow a chain of buckets. It first recurses // into the bucket's children since they are by definition closer to the bucket, // then continues with the bucket's parents. class DhtBucketChain { public: DhtBucketChain(const DhtBucket* b) : m_restart(b), m_cur(b) { } const DhtBucket* bucket() { return m_cur; } const DhtBucket* next(); private: const DhtBucket* m_restart; const DhtBucket* m_cur; }; inline void DhtBucket::node_now_good(bool was_bad) { m_bad -= was_bad; m_good++; } inline void DhtBucket::node_now_bad(bool was_good) { m_good -= was_good; m_bad++; } inline raw_string DhtBucket::full_bucket() { if (!m_fullCacheLength) build_full_cache(); return raw_string(m_fullCache, m_fullCacheLength); } inline const DhtBucket* DhtBucketChain::next() { // m_restart is clear when we're done recursing into the children and // follow the parents instead. if (m_restart == NULL) { m_cur = m_cur->parent(); } else { m_cur = m_cur->child(); if (m_cur == NULL) { m_cur = m_restart->parent(); m_restart = NULL; } } return m_cur; } } // namespace torrent #endif libtorrent-0.16.17/src/dht/dht_hash_map.h000066400000000000000000000106701522271512000202110ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_DHT_HASH_MAP_H #define LIBTORRENT_DHT_HASH_MAP_H #include "config.h" #include #include "dht_node.h" #include "dht_tracker.h" #include "torrent/hash_string.h" namespace torrent { // Hash functions for HashString keys, and dereferencing HashString pointers. // Since the first few bits are very similar if not identical (since the IDs // will be close to our own node ID), we use an offset of 64 bits in the hash // string. These bits will be uniformly distributed until the number of DHT // nodes on the planet approaches 2^64 which is... unlikely. // An offset of 64 bits provides 96 significant bits which is fine as long as // the size of size_t does not exceed 12 bytes, while still having correctly // aligned 64-bit access. static constexpr unsigned int hashstring_hash_ofs = 8; struct hashstring_ptr_hash { size_t operator () (const HashString* n) const { #if USE_ALIGNED size_t result = 0; const char *first = n->data() + hashstring_hash_ofs; const char *last = first + sizeof(size_t); while (first != last) result = (result << 8) + *first++; return result; #else return *reinterpret_cast(n->data() + hashstring_hash_ofs); #endif } }; struct hashstring_hash { size_t operator () (const HashString& n) const { #if USE_ALIGNED size_t result = 0; const char *first = n.data() + hashstring_hash_ofs; const char *last = first + sizeof(size_t); while (first != last) result = (result << 8) + *first++; return result; #else return *reinterpret_cast(n.data() + hashstring_hash_ofs); #endif } }; // Compare HashString pointers by dereferencing them. struct hashstring_ptr_equal { size_t operator () (const HashString* one, const HashString* two) const { return *one == *two; } }; class DhtNodeList : public std::unordered_map { public: using base_type = std::unordered_map; // Define accessor iterator with more convenient access to the key and // element values. Allows changing the map definition more easily if needed. template struct accessor_wrapper : public T { accessor_wrapper(const T& itr) : T(itr) { } const HashString& id() const { return *(**this).first; } DhtNode* node() const { return (**this).second; } }; using const_accessor = accessor_wrapper; using accessor = accessor_wrapper; DhtNode* add_node(DhtNode* n); }; using DhtTrackerList = std::unordered_map; inline DhtNode* DhtNodeList::add_node(DhtNode* n) { emplace(reinterpret_cast(n), n); return n; } } // namespace torrent #endif libtorrent-0.16.17/src/dht/dht_node.cc000066400000000000000000000050561522271512000175160ustar00rootroot00000000000000#include "config.h" #include "dht/dht_node.h" #include "torrent/object.h" #include "torrent/net/socket_address.h" #include "torrent/utils/log.h" #include "net/address_list.h" #define LT_LOG_THIS(log_fmt, ...) \ lt_log_print_hash(torrent::LOG_DHT_NODE, this->id(), "dht_node", log_fmt, __VA_ARGS__); namespace torrent { DhtNode::DhtNode(const HashString& id, const sockaddr* sa) : HashString(id), m_socket_address(sa_copy(sa)) { // TODO: Change this to use the id hash similar to how peer info // hash'es are logged. LT_LOG_THIS("created node : %s", sa_pretty_str(sa).c_str()); // if (sa->family() != AF_INET && // (sa->family() != AF_INET6 || !sa->sa_inet6()->is_any())) // throw resource_error("Address not af_inet or in6addr_any"); } DhtNode::DhtNode(const std::string& id, const Object& cache) : HashString(*HashString::cast_from(id.c_str())), m_last_seen(cache.get_key_value("t")) { // TODO: Check how DHT handles inet6. m_socket_address = sa_make_inet_h(cache.get_key_value("i"), cache.get_key_value("p")); LT_LOG_THIS("initializing node : %s", sap_pretty_str(m_socket_address).c_str()); update(); } void DhtNode::set_address(const sockaddr* sa) { m_socket_address = sa_copy(sa); } char* DhtNode::store_compact(char* buffer) const { HashString::cast_from(buffer)->assign(data()); if (m_socket_address->sa_family != AF_INET) throw internal_error("DhtNode::store_compact called with non-inet address."); auto sin = reinterpret_cast(m_socket_address.get()); SocketAddressCompact compact(sin); std::memcpy(buffer + 20, compact.c_str(), 6); return buffer + 26; } Object* DhtNode::store_cache(Object* container) const { if (m_socket_address->sa_family == AF_INET6) { // Currently, all we support is in6addr_any (checked in the constructor), // which is effectively equivalent to this. Note that we need to specify // int64_t explicitly here because a zero constant is special in C++ and // thus we need an explicit match. container->insert_key("i", int64_t{0}); container->insert_key("p", sap_port(m_socket_address)); } else if (m_socket_address->sa_family == AF_INET) { auto sin = reinterpret_cast(m_socket_address.get()); container->insert_key("i", ntohl(sin->sin_addr.s_addr)); container->insert_key("p", ntohs(sin->sin_port)); } else { throw internal_error("DhtNode::store_cache called with non-inet/inet6 address."); } container->insert_key("t", m_last_seen); return container; } } // namespace torrent libtorrent-0.16.17/src/dht/dht_node.h000066400000000000000000000064701522271512000173610ustar00rootroot00000000000000#ifndef LIBTORRENT_DHT_NODE_H #define LIBTORRENT_DHT_NODE_H #include "dht/dht_bucket.h" #include "torrent/hash_string.h" #include "torrent/object_raw_bencode.h" #include "torrent/net/types.h" namespace torrent::dht { class DhtSearch; } namespace torrent { class DhtBucket; class DhtNode : public HashString { public: // A node is considered bad if it failed to reply to this many queries. static constexpr unsigned int max_failed_replies = 5; DhtNode(const HashString& id, const sockaddr* sa); DhtNode(const std::string& id, const Object& cache); ~DhtNode() = default; const HashString& id() const { return *this; } raw_string id_raw_string() const { return raw_string(data(), size_data); } const sockaddr* address() const { return m_socket_address.get(); } void set_address(const sockaddr* sa); // For determining node quality. unsigned int last_seen() const { return m_last_seen; } unsigned int age() const { return this_thread::cached_seconds().count() - m_last_seen; } bool is_good() const { return m_recently_active; } bool is_questionable() const { return !m_recently_active; } bool is_bad() const { return m_recently_inactive >= max_failed_replies; } bool is_active() const { return m_last_seen; } // Update is called once every 15 minutes. void update() { m_recently_active = age() < 15 * 60; } // Called when node replies to us, queries us, or fails to reply. void replied() { set_good(); } void queried() { if (m_last_seen) set_good(); } void inactive(); DhtBucket* bucket() const { return m_bucket; } DhtBucket* set_bucket(DhtBucket* b) { m_bucket = b; return b; } bool is_in_range(const DhtBucket* b) { return b->is_in_range(*this); } // Store compact node information (26 bytes address, port and ID) in the given // buffer and return pointer to end of stored information. char* store_compact(char* buffer) const; // Store node cache in the given container object and return it. Object* store_cache(Object* container) const; protected: DhtNode(const DhtNode&) = delete; DhtNode& operator=(const DhtNode&) = delete; friend class dht::DhtSearch; void set_good(); void set_bad(); private: sa_unique_ptr m_socket_address; unsigned int m_last_seen{}; bool m_recently_active{}; unsigned int m_recently_inactive{}; DhtBucket* m_bucket{}; }; inline void DhtNode::set_good() { if (m_bucket != NULL && !is_good()) m_bucket->node_now_good(is_bad()); m_last_seen = this_thread::cached_seconds().count(); m_recently_inactive = 0; m_recently_active = true; } inline void DhtNode::set_bad() { if (m_bucket != NULL && !is_bad()) m_bucket->node_now_bad(is_good()); m_recently_inactive = max_failed_replies; m_recently_active = false; } inline void DhtNode::inactive() { if (m_recently_inactive + 1 == max_failed_replies) set_bad(); else m_recently_inactive++; } } // namespace torrent #endif libtorrent-0.16.17/src/dht/dht_router.cc000066400000000000000000000451311522271512000201070ustar00rootroot00000000000000#include "config.h" #include "dht_router.h" #include #include "dht_bucket.h" #include "dht_tracker.h" #include "dht_transaction.h" #include "torrent/exceptions.h" #include "torrent/net/resolver.h" #include "torrent/net/socket_address.h" #include "torrent/system/callbacks.h" #include "torrent/tracker/dht_controller.h" #include "torrent/utils/log.h" #include "utils/sha1.h" #define LT_LOG_THIS(log_fmt, ...) \ lt_log_print_hash(torrent::LOG_DHT_ROUTER, this->id(), "dht_router", log_fmt, __VA_ARGS__); namespace torrent { HashString DhtRouter::zero_id; DhtRouter::DhtRouter(const Object& cache) : DhtNode(zero_id, sa_make_inet_any().get()), // actual ID is set later m_server(this), m_curToken(random()), m_prevToken(random()), m_resolver_callback_id(system::make_callback_id()) { HashString ones_id; zero_id.clear(); ones_id.clear(0xFF); if (cache.has_key("self_id")) { const std::string& id = cache.get_key_string("self_id"); if (id.length() != HashString::size_data) throw bencode_error("Loading cache: Invalid ID."); assign(id.c_str()); } else { long buffer[size_data]; for (long* itr = buffer; itr != buffer + size_data; ++itr) *itr = random(); Sha1 sha; sha.init(); sha.update(buffer, sizeof(buffer)); sha.final_c(data()); } LT_LOG_THIS("creating", 0); set_bucket(new DhtBucket(zero_id, ones_id)); m_routingTable.emplace(bucket()->id_range_end(), bucket()); if (cache.has_key("nodes")) { const Object::map_type& nodes = cache.get_key_map("nodes"); LT_LOG_THIS("adding nodes : size:%zu", nodes.size()); for (const auto& [id, node] : nodes) { if (id.length() != HashString::size_data) throw bencode_error("Loading cache: Invalid node hash."); add_node_to_bucket(m_nodes.add_node(new DhtNode(id, node))); } } if (m_nodes.size() < num_bootstrap_complete) { m_contacts.emplace(); if (cache.has_key("contacts")) { for (const auto& contact : cache.get_key_list("contacts")) { auto litr = contact.as_list().begin(); auto host = litr->as_string(); auto port = std::next(litr)->as_value(); m_contacts->emplace_back(std::move(host), port); } } } } DhtRouter::~DhtRouter() { assert(!is_active() && "DhtRouter::~DhtRouter() called while still active."); for (auto& route : m_routingTable) delete route.second; for (auto& tracker : m_trackers) delete tracker.second; for (auto& node : m_nodes) delete node.second; } void DhtRouter::start(int port) { LT_LOG_THIS("starting: port:%d", port); m_server.start(port); // Set timeout slot and schedule it to be called immediately for initial bootstrapping if // necessary. m_task_timeout.slot() = [this] { receive_timeout_bootstrap(); }; this_thread::scheduler()->wait_for_ceil_seconds(&m_task_timeout, 1s); } void DhtRouter::stop() { if (!is_active()) return; LT_LOG_THIS("stopping", 0); this_thread::resolver()->cancel(m_resolver_callback_id); this_thread::scheduler()->erase(&m_task_timeout); m_server.stop(); } // Start a DHT get_peers and announce_peer request. void DhtRouter::announce(const HashString& info_hash, std::weak_ptr tracker) { m_server.announce(*find_bucket(info_hash)->second, info_hash, tracker); } // Cancel any running requests from the given tracker. // If info or tracker is not NULL, only cancel matching requests. void DhtRouter::cancel_announce(const HashString& info_hash, std::weak_ptr tracker) { m_server.cancel_announce(info_hash, tracker); } DhtTracker* DhtRouter::get_tracker(const HashString& hash, bool create) { auto itr = m_trackers.find(hash); if (itr != m_trackers.end()) return itr->second; if (!create) return NULL; auto [tr, inserted] = m_trackers.emplace(hash, new DhtTracker()); if (!inserted) throw internal_error("DhtRouter::get_tracker did not actually insert tracker."); return tr->second; } bool DhtRouter::want_node(const HashString& id) { // We don't want to add ourself. Also, too many broken implementations // advertise an ID of 0, which causes collisions, so reject that. if (id == this->id() || id == zero_id) return false; // We are always interested in more nodes for our own bucket (causing it // to be split if full); in other buckets only if there's space. DhtBucket* b = find_bucket(id)->second; return b == bucket() || b->has_space(); } DhtNode* DhtRouter::get_node(const HashString& id) { DhtNodeList::accessor itr = m_nodes.find(&id); if (itr == m_nodes.end()) { if (id == this->id()) return this; else return NULL; } return itr.node(); } DhtRouter::DhtBucketList::iterator DhtRouter::find_bucket(const HashString& id) { auto itr = m_routingTable.lower_bound(id); #ifdef USE_EXTRA_DEBUG if (itr == m_routingTable.end()) throw internal_error("DHT Buckets not covering entire ID space."); if (!itr->second->is_in_range(id)) throw internal_error("DhtRouter::find_bucket, m_routingTable.lower_bound did not find correct bucket."); #endif return itr; } void DhtRouter::add_bootstrap_contact(const std::string& host, int port) { if (!m_contacts.has_value()) { LT_LOG_THIS("ignoring bootstrap contact : %s:%d", host.c_str(), port); return; } // Externally obtained nodes are added to the contact list, but only if // we're still bootstrapping. We don't contact external nodes after that. if (m_contacts->size() >= num_bootstrap_contacts) m_contacts->pop_front(); m_contacts->emplace_back(host, port); LT_LOG_THIS("added bootstrap contact : %s:%d", host.c_str(), port); } void DhtRouter::contact(const sockaddr* sa, int port) { if (!is_active()) return; auto sa_tmp = sa_copy_unmapped(sa); if (sa_tmp->sa_family != AF_INET && sa_tmp->sa_family != AF_INET6) throw input_error("DhtRouter::contact() called with non-inet/inet6 address."); // Currently only IPv4 is supported. if (sa_tmp->sa_family != AF_INET) return; if (sap_is_any(sa_tmp)) throw input_error("DhtRouter::contact() called with any address."); sap_set_port(sa_tmp, port); LT_LOG_THIS("contacting node : %s:%d", sa_addr_str(sa_tmp.get()).c_str(), port); m_server.ping(zero_id, sa_tmp.get()); } // Received a query from the given node. If it has previously replied // to one of our queries, consider it alive and update the bucket mtime, // otherwise if we could use it in a bucket, try contacting it. DhtNode* DhtRouter::node_queried(const HashString& id, const sockaddr* sa) { DhtNode* node = get_node(id); if (node == nullptr) { if (want_node(id)) m_server.ping(id, sa); return nullptr; } // If we know the ID but the address is different, don't set the original node // active, but neither use this new address to prevent rogue nodes from polluting // our routing table with fake source addresses. if (!sa_equal_addr(node->address(), sa)) return nullptr; node->queried(); if (node->is_good()) node->bucket()->touch(); return node; } // Received a reply from a node we queried. // Check that it matches the information we have, set that it has replied // and update the bucket mtime. DhtNode* DhtRouter::node_replied(const HashString& id, const sockaddr* sa) { DhtNode* node = get_node(id); if (node == NULL) { if (!want_node(id)) return NULL; // New node, create it. It's a good node (it replied!) so add it to a bucket. node = m_nodes.add_node(new DhtNode(id, sa)); if (!add_node_to_bucket(node)) // deletes the node if it fails return NULL; } if (!sa_equal_addr(node->address(), sa)) return NULL; node->replied(); node->bucket()->touch(); return node; } // A node has not replied to one of our queries. DhtNode* DhtRouter::node_inactive(const HashString& id, const sockaddr* sa) { DhtNodeList::accessor itr = m_nodes.find(&id); // If not found add it to some blacklist so we won't try contacting it again immediately? if (itr == m_nodes.end()) return NULL; // Check source address. Normally node_inactive is called if we DON'T receive a reply, // however it can also be called if a node replied with an malformed response packet, // so check that the address matches so that a rogue node cannot cause other nodes // to be considered bad by sending malformed packets. if (!sa_equal_addr(itr.node()->address(), sa)) return NULL; itr.node()->inactive(); // Old node age normally implies no replies for many consecutive queries, however // after loading the node cache after a day or more we want to give each node a few // chances to reply again instead of removing all nodes instantly. if (itr.node()->is_bad() && itr.node()->age() >= timeout_remove_node) { delete_node(itr); return NULL; } return itr.node(); } // We sent a query to the given node ID, but received a reply from a different // node ID, that means the address of the original ID is invalid now. void DhtRouter::node_invalid(const HashString& id) { DhtNode* node = get_node(id); if (node == NULL || node == this) return; delete_node(m_nodes.find(&node->id())); } Object* DhtRouter::store_cache(Object* container) const { container->insert_key("self_id", str()); // Insert all nodes. Object& nodes = container->insert_key("nodes", Object::create_map()); for (const auto& [id, node] : m_nodes) { if (!node->is_bad()) node->store_cache(&nodes.insert_key(id->str(), Object::create_map())); } // Insert contacts, if we have any. if (m_contacts.has_value()) { Object& contacts = container->insert_key("contacts", Object::create_list()); for (const auto& m_contact : *m_contacts) { Object::list_type& list = contacts.insert_back(Object::create_list()).as_list(); list.emplace_back(m_contact.first); list.emplace_back(m_contact.second); } } return container; } tracker::DhtController::statistics_type DhtRouter::get_statistics() const { tracker::DhtController::statistics_type stats; if (!m_server.is_active()) stats.cycle = 0; else if (m_numRefresh < 2) // still bootstrapping stats.cycle = 1; else stats.cycle = m_numRefresh; stats.queries_received = m_server.queries_received(); stats.queries_sent = m_server.queries_sent(); stats.replies_received = m_server.replies_received(); stats.errors_received = m_server.errors_received(); stats.errors_caught = m_server.errors_caught(); stats.num_nodes = m_nodes.size(); stats.num_buckets = m_routingTable.size(); stats.num_peers = 0; stats.max_peers = 0; stats.num_trackers = m_trackers.size(); for (const auto& [_, tracker] : m_trackers) { unsigned int peers = tracker->size(); stats.num_peers += peers; stats.max_peers = std::max(peers, stats.max_peers); } return stats; } void DhtRouter::receive_timeout_bootstrap() { // If we're still bootstrapping, restart the process every 60 seconds until // we have enough nodes in our routing table. After we have 32 nodes, we switch // to a less aggressive non-bootstrap mode of collecting nodes that contact us // and through doing normal torrent announces. if (m_nodes.size() < num_bootstrap_complete) { if (!m_contacts.has_value()) throw internal_error("DhtRouter::receive_timeout_bootstrap called without contact list."); if (!m_nodes.empty() || !m_contacts->empty()) bootstrap(); // Retry in 60 seconds. this_thread::scheduler()->wait_for_ceil_seconds(&m_task_timeout, std::chrono::seconds(timeout_bootstrap_retry)); m_numRefresh = 1; // still bootstrapping } else { // We won't be needing external contacts after this. m_contacts.reset(); m_task_timeout.slot() = [this] { receive_timeout(); }; if (!m_numRefresh) { // If we're still in the startup, do the usual refreshing too. receive_timeout(); } else { // Otherwise just set the 15 minute timer. this_thread::scheduler()->wait_for_ceil_seconds(&m_task_timeout, std::chrono::seconds(timeout_update)); } m_numRefresh = 2; } } void DhtRouter::receive_timeout() { this_thread::scheduler()->wait_for_ceil_seconds(&m_task_timeout, std::chrono::seconds(timeout_update)); m_prevToken = m_curToken; m_curToken = random(); // Do some periodic accounting, refreshing buckets and marking // bad nodes. // Update nodes. for (const auto& [id, node] : m_nodes) { if (!node->bucket()) throw internal_error("DhtRouter::receive_timeout has node without bucket."); node->update(); // Try contacting nodes we haven't received anything from for a while. // Don't contact repeatedly unresponsive nodes; we keep them in case they // do send a query, until we find a better node. However, give it a last // chance just before deleting it. if (node->is_questionable() && (!node->is_bad() || node->age() >= timeout_remove_node)) m_server.ping(node->id(), node->address()); } // If bucket isn't full yet or hasn't received replies/queries from // its nodes for a while, try to find new nodes now. for (const auto& route : m_routingTable) { route.second->update(); if (!route.second->is_full() || route.second == bucket() || route.second->age() > timeout_bucket_bootstrap) bootstrap_bucket(route.second); } // Remove old peers and empty torrents from the tracker. for (auto itr = m_trackers.begin(); itr != m_trackers.end();) { auto tracker = itr->second; tracker->prune(timeout_peer_announce); if (tracker->empty()) { delete tracker; itr = m_trackers.erase(itr); continue; } ++itr; } m_server.update(); m_numRefresh++; } char* DhtRouter::generate_token(const sockaddr* sa, int token, char buffer[20]) { if (!sa_is_inet(sa)) throw internal_error("DhtRouter::generate_token called with non-inet address."); uint32_t key = reinterpret_cast(sa)->sin_addr.s_addr; Sha1 sha; sha.init(); sha.update(&token, sizeof(token)); sha.update(&key, 4); sha.final_c(buffer); return buffer; } bool DhtRouter::token_valid(raw_string token, const sockaddr* sa) const { if (token.size() != size_token) return false; // Compare given token to the reference token. char reference[20]; // First try current token. // // Else if token recently changed, some clients may be using the older one. // That way a token is valid for 15-30 minutes, instead of 0-15. return token == raw_string(generate_token(sa, m_curToken, reference), size_token) || token == raw_string(generate_token(sa, m_prevToken, reference), size_token); } DhtNode* DhtRouter::find_node(const sockaddr* sa) { for (const auto& [id, node] : m_nodes) { if (sa_equal_addr(node->address(), sa)) return node; } return nullptr; } DhtRouter::DhtBucketList::iterator DhtRouter::split_bucket(const DhtBucketList::iterator& itr, DhtNode* node) { // Split bucket. Current bucket keeps the upper half thus keeping the // map key valid, new bucket is the lower half of the original bucket. DhtBucket* newBucket = itr->second->split(id()); // If our bucket has a child now (the new bucket), move ourself into it. if (bucket()->child() != NULL) set_bucket(bucket()->child()); if (!bucket()->is_in_range(id())) throw internal_error("DhtRouter::split_bucket router ID ended up in wrong bucket."); // Insert new bucket with iterator hint = just before current bucket. auto other = m_routingTable.emplace_hint(itr, newBucket->id_range_end(), newBucket); // Check that the bucket we're not adding the node to isn't empty. if (other->second->is_in_range(node->id())) { if (itr->second->empty()) bootstrap_bucket(itr->second); } else { if (other->second->empty()) bootstrap_bucket(other->second); other = itr; } return other; } bool DhtRouter::add_node_to_bucket(DhtNode* node) { auto itr = find_bucket(node->id()); while (itr->second->is_full()) { // Bucket is full. If there are any bad nodes, remove the oldest. DhtBucket::iterator nodeItr = itr->second->find_replacement_candidate(); if (nodeItr == itr->second->end()) throw internal_error("DhtBucket::find_candidate returned no node."); if ((*nodeItr)->is_bad()) { delete_node(m_nodes.find(&(*nodeItr)->id())); } else { // Bucket is full of good nodes; if our own ID falls in // range then split the bucket else discard new node. if (itr->second != bucket()) { delete_node(m_nodes.find(&node->id())); return false; } itr = split_bucket(itr, node); } } itr->second->add_node(node); node->set_bucket(itr->second); return true; } void DhtRouter::delete_node(const DhtNodeList::accessor& itr) { if (itr == m_nodes.end()) throw internal_error("DhtRouter::delete_node called with invalid iterator."); if (itr.node()->bucket() != NULL) itr.node()->bucket()->remove_node(itr.node()); delete itr.node(); m_nodes.erase(itr); } void DhtRouter::bootstrap() { if (!m_contacts.has_value()) return; // Contact up to 8 nodes from the contact list (newest first). for (int count = 0; count < 8 && !m_contacts->empty(); count++) { int port = m_contacts->back().second; // Currently discarding SOCK_DGRAM. auto f = [this, port](const auto& sa, int) { if (sa != nullptr) contact(sa.get(), port); }; this_thread::resolver()->resolve_specific(m_resolver_callback_id, m_contacts->back().first, AF_INET, f); m_contacts->pop_back(); } // Abort unless we already found some nodes for a search. if (m_nodes.empty()) return; bootstrap_bucket(bucket()); // Aggressively ping all questionable nodes in our own bucket to weed // out bad nodes as early as possible and make room for fresh nodes. for (auto node : *bucket()) { if (!node->is_good()) m_server.ping(node->id(), node->address()); } // Also bootstrap a random bucket, if there are others. if (m_routingTable.size() < 2) return; auto itr = m_routingTable.begin(); std::advance(itr, random() % m_routingTable.size()); if (itr->second != bucket() && itr != m_routingTable.end()) bootstrap_bucket(itr->second); } void DhtRouter::bootstrap_bucket(const DhtBucket* bucket) { if (!m_server.is_active()) return; // Do a search for a random ID, or the ID adjacent to our // own when bootstrapping our own bucket. We don't search for // our own exact ID to avoid receiving only our own node info // instead of closest nodes, from nodes that know us already. if (bucket == this->bucket()) { m_contactId = id(); m_contactId[torrent::HashString::size() - 1] ^= 1; } else { bucket->get_random_id(&m_contactId); } m_server.find_node(*bucket, m_contactId); } } // namespace torrent libtorrent-0.16.17/src/dht/dht_router.h000066400000000000000000000135731522271512000177560ustar00rootroot00000000000000#ifndef LIBTORRENT_DHT_DHT_ROUTER_H #define LIBTORRENT_DHT_DHT_ROUTER_H #include "dht/dht_node.h" #include "dht/dht_hash_map.h" #include "dht/dht_server.h" #include "torrent/hash_string.h" #include "torrent/object.h" #include "torrent/net/types.h" #include "torrent/tracker/dht_controller.h" #include "torrent/utils/scheduler.h" #include namespace torrent { class DhtBucket; class DhtTracker; class TrackerDht; // Main DHT class, maintains the routing table of known nodes and talks to the // DhtServer object that handles the actual communication. class DhtRouter : public DhtNode { public: // How many bytes to return and verify from the 20-byte SHA token. static constexpr unsigned int size_token = 8; static constexpr unsigned int timeout_bootstrap_retry = 60; // Retry initial bootstrapping every minute. static constexpr unsigned int timeout_update = 15 * 60; // Regular housekeeping updates every 15 minutes. static constexpr unsigned int timeout_bucket_bootstrap = 15 * 60; // Bootstrap idle buckets after 15 minutes. static constexpr unsigned int timeout_remove_node = 4 * 60 * 60; // Remove unresponsive nodes after 4 hours. static constexpr unsigned int timeout_peer_announce = 30 * 60; // Remove peers which haven't reannounced for 30 minutes. // A node ID of all zero. static HashString zero_id; DhtRouter(const Object& cache); ~DhtRouter(); void start(int port); void stop(); bool is_active() { return m_server.is_active(); } // Pass NULL to cancel_announce to cancel all announces for the tracker. void announce(const HashString& info_hash, std::weak_ptr tracker); void cancel_announce(const HashString& info_hash, std::weak_ptr tracker); // Returns NULL if not tracking the torrent unless create is true. DhtTracker* get_tracker(const HashString& hash, bool create); // Check if we are interested in inserting a new node of the given ID // into our table (i.e. if we have space or bad nodes in the corresponding bucket). bool want_node(const HashString& id); // Add the given host to the list of potential contacts if we haven't // completed the bootstrap process, or contact the given address directly. // TODO: Remoce add_contact.... And make it check that sa is inet. void add_bootstrap_contact(const std::string& host, int port); void contact(const sockaddr* sa, int port); // Retrieve node of given ID in constant time. Return NULL if not found, unless // it's our own ID in which case it returns the DhtRouter object. DhtNode* get_node(const HashString& id); // Search for node with given address in O(n), disregarding the port. DhtNode* find_node(const sockaddr* sa); // Whenever a node queries us, replies, or is confirmed inactive (no reply) or // invalid (reply with wrong ID), we need to update its status. DhtNode* node_queried(const HashString& id, const sockaddr* sa); DhtNode* node_replied(const HashString& id, const sockaddr* sa); DhtNode* node_inactive(const HashString& id, const sockaddr* sa); void node_invalid(const HashString& id); // Store compact node information (26 bytes) for nodes closest to the // given ID in the given buffer, return new buffer end. raw_string get_closest_nodes(const HashString& id) { return find_bucket(id)->second->full_bucket(); } // Store DHT cache in the given container. Object* store_cache(Object* container) const; // Create and verify a token. Tokens are valid between 15-30 minutes from creation. raw_string make_token(const sockaddr* sa, char* buffer) const; bool token_valid(raw_string token, const sockaddr* sa) const; tracker::DhtController::statistics_type get_statistics() const; void reset_statistics() { m_server.reset_statistics(); } private: // Hostname and port of potential bootstrap nodes. using contact_t = std::pair; // Number of nodes we need to consider the bootstrap process complete. static constexpr unsigned int num_bootstrap_complete = 32; // Maximum number of potential contacts to keep until bootstrap complete. static constexpr unsigned int num_bootstrap_contacts = 64; using DhtBucketList = std::map; DhtBucketList::iterator find_bucket(const HashString& id); bool add_node_to_bucket(DhtNode* node); void delete_node(const DhtNodeList::accessor& itr); void store_closest_nodes(const HashString& id, DhtBucket* bucket); DhtBucketList::iterator split_bucket(const DhtBucketList::iterator& itr, DhtNode* node); void bootstrap(); void bootstrap_bucket(const DhtBucket* bucket); void receive_timeout(); void receive_timeout_bootstrap(); // buffer needs to hold an SHA1 hash (20 bytes), not just the token (8 bytes) static char* generate_token(const sockaddr* sa, int token, char buffer[20]); utils::SchedulerEntry m_task_timeout; DhtServer m_server{nullptr}; DhtNodeList m_nodes; DhtBucketList m_routingTable; DhtTrackerList m_trackers; HashString m_contactId; std::optional> m_contacts; int m_numRefresh{0}; bool m_networkUp; // Secret keys used for generating announce tokens. int m_curToken; int m_prevToken; system::callback_id m_resolver_callback_id; }; inline raw_string DhtRouter::make_token(const sockaddr* sa, char* buffer) const { return raw_string(generate_token(sa, m_curToken, buffer), size_token); } } // namespace torrent #endif libtorrent-0.16.17/src/dht/dht_server.cc000066400000000000000000000732151522271512000201010ustar00rootroot00000000000000#include "config.h" #include "dht/dht_server.h" #include #include #include "manager.h" #include "dht/dht_bucket.h" #include "dht/dht_router.h" #include "dht/dht_transaction.h" #include "dht/transactions/dht_announce.h" #include "torrent/exceptions.h" #include "torrent/object.h" #include "torrent/object_static_map.h" #include "torrent/object_stream.h" #include "torrent/net/fd.h" #include "torrent/net/socket_address.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/runtime.h" #include "torrent/runtime/socket_manager.h" #include "torrent/system/poll.h" #include "torrent/utils/log.h" #include "tracker/tracker_dht.h" #define LT_LOG_THIS(log_fmt, ...) \ lt_log_print_subsystem(torrent::LOG_DHT_SERVER, "dht_server", log_fmt, __VA_ARGS__); namespace { // Error in DHT protocol, avoids std::string ctor from communication_error class dht_error : public torrent::network_error { public: dht_error(int code, const char* message) : m_message(message), m_code(code) {} int code() const noexcept { return m_code; } const char* what() const noexcept override { return m_message; } private: const char* m_message; int m_code; }; } // namespace namespace torrent { // List of all possible keys we need/support in a DHT message. // Unsupported keys we receive are dropped (ignored) while decoding. // See torrent/object_static_map.h for how this works. template <> const DhtMessage::key_list_type DhtMessage::base_type::keys = { { key_a_id, "a::id*S" }, { key_a_infoHash, "a::info_hash*S" }, { key_a_port, "a::port", }, { key_a_target, "a::target*S" }, { key_a_token, "a::token*S" }, { key_e_0, "e[]*" }, { key_e_1, "e[]*" }, { key_q, "q*S" }, { key_r_id, "r::id*S" }, { key_r_nodes, "r::nodes*S" }, { key_r_token, "r::token*S" }, { key_r_values, "r::values*L" }, { key_t, "t*S" }, { key_v, "v*" }, { key_y, "y*S" }, }; DhtServer::DhtServer(DhtRouter* router) : m_router(router) { m_fileDesc = -1; reset_statistics(); m_task_timeout.slot() = [this] { receive_timeout(); }; } DhtServer::~DhtServer() { stop(); } void DhtServer::start(int port) { auto [bind_inet_address, bind_inet6_address] = runtime::network_config()->bind_udp_addresses_or_null(); if (bind_inet_address == nullptr) throw resource_error("no valid bind address for DHT server"); sa_unique_ptr bind_address; switch (bind_inet_address->sa_family) { case AF_INET: bind_address = sa_copy(bind_inet_address.get()); break; case AF_UNSPEC: bind_address = sa_make_inet_any(); break; default: throw resource_error("invalid address family for DHT server"); } m_router->set_address(bind_inet_address.get()); sap_set_port(bind_address, port); LT_LOG_THIS("starting server : %s", sap_pretty_str(bind_address).c_str()); fd_flags open_flags = fd_flag_datagram | fd_flag_nonblock | fd_flag_reuse_address; if (bind_address->sa_family == AF_INET) open_flags |= fd_flag_v4; int fd = fd_open(open_flags); if (fd == -1) { LT_LOG_THIS("could not open datagram socket : %s", std::strerror(errno)); throw resource_error("could not open datagram socket : " + std::string(strerror(errno))); } if (!fd_bind(fd, bind_address.get())) { LT_LOG_THIS("could not bind datagram socket : %s", std::strerror(errno)); fd_close(fd); throw resource_error("could not bind datagram socket : " + std::string(strerror(errno))); } set_file_descriptor(fd); // TODO: This throws internal_error on failure. runtime::socket_manager()->register_event_or_throw(this, runtime::category_internal, [this]() { this_thread::poll()->open(this); this_thread::poll()->insert_read(this); this_thread::poll()->insert_error(this); }); } void DhtServer::stop() { if (!is_active()) return; LT_LOG_THIS("stopping", 0); LT_LOG_THIS("searches : count:%zu", m_searches.size()); clear_transactions(); this_thread::scheduler()->erase(&m_task_timeout); runtime::socket_manager()->close_event_or_throw(this, [this]() { this_thread::poll()->remove_and_close(this); fd_close(file_descriptor()); set_file_descriptor(-1); }); m_networkUp = false; } void DhtServer::reset_statistics() { m_queriesReceived = 0; m_queriesSent = 0; m_repliesReceived = 0; m_errorsReceived = 0; m_errorsCaught = 0; } // Ping a node whose ID we know. void DhtServer::ping(const HashString& id, const sockaddr* sa) { // No point pinging a node that we're already contacting otherwise. auto itr = m_transactions.lower_bound(DhtTransaction::key(sa, 0)); if (itr == m_transactions.end() || !DhtTransaction::key_match(itr->first, sa)) add_transaction(std::unique_ptr(new DhtTransactionPing(id, sa)), packet_prio_low); } // Contact nodes in given bucket and ask for their nodes closest to target. void DhtServer::find_node(const DhtBucket& contacts, const HashString& target) { auto search = std::make_shared(this, target); search->add_contacts(contacts); auto n = search->get_contact(); while (n != search->end()) { add_transaction(std::unique_ptr(new DhtTransactionFindNode(n)), packet_prio_low); n = search->get_contact(); } // This shouldn't happen, it means we had no contactable nodes at all. if (!search->start()) return; // throw internal_error("DhtServer::find_node search start failed, no contactable nodes."); ///////////////// TMP m_searches.insert(search); } void DhtServer::announce(const DhtBucket& contacts, const HashString& infoHash, std::weak_ptr tracker) { auto announce = std::make_shared(this, infoHash, tracker); announce->add_contacts(contacts); auto n = announce->get_contact(); while (n != announce->end()) { add_transaction(std::unique_ptr(new DhtTransactionFindNode(n)), packet_prio_high); n = announce->get_contact(); } // This can only happen if all nodes we know are bad. if (!announce->start()) return; // throw internal_error("DhtServer::announce search start failed, no contactable nodes."); ///////////////// TMP m_searches.insert(announce); announce->update_status(); } void DhtServer::cancel_announce(const HashString& info_hash, std::weak_ptr tracker) { auto itr = m_transactions.begin(); // TODO: Verify this removes us from m_searches. while (itr != m_transactions.end()) { if (itr->second->is_search() && itr->second->as_search()->search()->is_announce()) { auto announce = dynamic_cast(itr->second->as_search()->search().get()); if (announce == nullptr) throw internal_error("DhtServer::cancel_announce dynamic_cast to DhtAnnounce failed."); bool tracker_match = !tracker.owner_before(announce->tracker()) && !announce->tracker().owner_before(tracker); if (announce->target() == info_hash && (tracker.expired() || tracker_match)) { drop_packet(itr->second->packet().get()); m_transactions.erase(itr++); continue; } } ++itr; } } void DhtServer::update() { // Reset this every 15 minutes. It'll get set back to true if we receive // any valid packets. This allows detecting when the entire network goes // down, and prevents all nodes from getting removed as unresponsive. m_networkUp = false; } void DhtServer::check_search_completed(std::shared_ptr search) { if (!search->complete()) return; // Removing search if has completed. auto itr = m_searches.find(search); if (itr == m_searches.end()) return; // throw internal_error("DhtServer::mark_search_completed search not found."); // TODO: Verify we got ref_count == 2. m_searches.erase(itr); } void DhtServer::check_search_trimming(std::shared_ptr search) { // Make sure trimming search does not delete searches that have not completed. if (search->complete()) return; // TODO: Should we erase if complete? auto itr = m_searches.lower_bound(search); if (itr != m_searches.end() && *itr == search) return; m_searches.insert(itr, search); } void DhtServer::process_query(const HashString& id, const sockaddr* sa, const DhtMessage& msg) { m_queriesReceived++; m_networkUp = true; raw_string query = msg[key_q].as_raw_string(); // Construct reply. DhtMessage reply; if (query == raw_string::from_c_str("find_node")) create_find_node_response(msg, reply); else if (query == raw_string::from_c_str("get_peers")) create_get_peers_response(msg, sa, reply); else if (query == raw_string::from_c_str("announce_peer")) create_announce_peer_response(msg, sa, reply); else if (query != raw_string::from_c_str("ping")) throw dht_error(dht_error_bad_method, "Unknown query type."); m_router->node_queried(id, sa); create_response(msg, sa, reply); } void DhtServer::create_find_node_response(const DhtMessage& req, DhtMessage& reply) { raw_string target = req[key_a_target].as_raw_string(); if (target.size() < HashString::size_data) throw dht_error(dht_error_protocol, "target string too short"); reply[key_r_nodes] = m_router->get_closest_nodes(*HashString::cast_from(target.data())); if (reply[key_r_nodes].as_raw_string().empty()) throw dht_error(dht_error_generic, "No nodes"); } void DhtServer::create_get_peers_response(const DhtMessage& req, const sockaddr* sa, DhtMessage& reply) { reply[key_r_token] = m_router->make_token(sa, reply.data_end); reply.data_end += reply[key_r_token].as_raw_string().size(); raw_string info_hash_str = req[key_a_infoHash].as_raw_string(); if (info_hash_str.size() < HashString::size_data) throw dht_error(dht_error_protocol, "info hash too short"); const HashString* info_hash = HashString::cast_from(info_hash_str.data()); DhtTracker* tracker = m_router->get_tracker(*info_hash, false); // If we're not tracking or have no peers, send closest nodes. if (!tracker || tracker->empty()) { raw_string nodes = m_router->get_closest_nodes(*info_hash); if (nodes.empty()) throw dht_error(dht_error_generic, "No peers nor nodes"); reply[key_r_nodes] = nodes; } else { reply[key_r_values] = tracker->get_peers(); } } void DhtServer::create_announce_peer_response(const DhtMessage& req, const sockaddr* sa, [[maybe_unused]] DhtMessage& reply) { raw_string info_hash = req[key_a_infoHash].as_raw_string(); if (info_hash.size() < HashString::size_data) throw dht_error(dht_error_protocol, "info hash too short"); if (!m_router->token_valid(req[key_a_token].as_raw_string(), sa)) throw dht_error(dht_error_protocol, "Token invalid."); if (!sa_is_inet(sa)) throw internal_error("DhtServer::create_announce_peer_response called with non-inet address."); DhtTracker* tracker = m_router->get_tracker(*HashString::cast_from(info_hash.data()), true); tracker->add_peer(reinterpret_cast(sa)->sin_addr.s_addr, req[key_a_port].as_value()); } void DhtServer::process_response(const HashString& id, const sockaddr* sa, const DhtMessage& response) { int transactionId = static_cast(response[key_t].as_raw_string().data()[0]); auto itr = m_transactions.find(DhtTransaction::key(sa, transactionId)); // Response to a transaction we don't have in our table. At this point it's // impossible to tell whether it used to be a valid transaction but timed out // the node did not return the ID we sent it, or it returned it with a // different address than we sent it o. Best we can do is ignore the reply, // since the protocol doesn't call for returning errors in responses. if (itr == m_transactions.end()) return; m_repliesReceived++; m_networkUp = true; // Make sure transaction is erased even if an exception is thrown. try { auto& transaction = itr->second; #ifdef USE_EXTRA_DEBUG if (DhtTransaction::key(sa, transactionId) != transaction->key(transactionId)) throw internal_error("DhtServer::process_response key mismatch."); #endif // If we contact a node but its ID is not the one we expect, ignore the reply // to prevent interference from rogue nodes. if ((id != transaction->id() && transaction->id() != torrent::DhtRouter::zero_id)) return; switch (transaction->type()) { case DhtTransaction::DHT_FIND_NODE: parse_find_node_reply(transaction->as_find_node(), response[key_r_nodes].as_raw_string()); break; case DhtTransaction::DHT_GET_PEERS: parse_get_peers_reply(transaction->as_get_peers(), response); break; // Nothing to do for DHT_PING and DHT_ANNOUNCE_PEER default: break; } // Mark node responsive only if all processing was successful, without errors. m_router->node_replied(id, sa); } catch (const std::exception&) { drop_packet(itr->second->packet().get()); m_transactions.erase(itr); m_errorsCaught++; throw; } drop_packet(itr->second->packet().get()); m_transactions.erase(itr); } void DhtServer::process_error(const sockaddr* sa, const DhtMessage& error) { int transactionId = static_cast(error[key_t].as_raw_string().data()[0]); auto itr = m_transactions.find(DhtTransaction::key(sa, transactionId)); if (itr == m_transactions.end()) return; m_repliesReceived++; m_errorsReceived++; m_networkUp = true; // Don't mark node as good (because it replied) or bad (because it returned an error). // If it consistently returns errors for valid queries it's probably broken. But a // few error messages are acceptable. So we do nothing and pretend the query never happened. drop_packet(itr->second->packet().get()); m_transactions.erase(itr); } void DhtServer::parse_find_node_reply(DhtTransactionSearch* transaction, raw_string nodes) { transaction->complete(true); if (sizeof(const compact_node_info) != 26) throw internal_error("DhtServer::parse_find_node_reply(...) bad struct size."); node_info_list list; std::copy(reinterpret_cast(nodes.data()), reinterpret_cast(nodes.data() + nodes.size() - nodes.size() % sizeof(compact_node_info)), std::back_inserter(list)); for (auto& node : list) { if (node.id() != m_router->id()) transaction->search()->add_contact(node.id(), sa_make_inet_n(node._addr.addr, node._addr.port).get()); } find_node_next(transaction); } void DhtServer::parse_get_peers_reply(DhtTransactionGetPeers* transaction, const DhtMessage& response) { auto announce = dynamic_cast(transaction->as_search()->search().get()); if (announce == nullptr) throw internal_error("DhtServer::parse_get_peers_reply dynamic_cast to DhtAnnounce failed."); transaction->complete(true); if (response[key_r_values].is_raw_list()) announce->receive_peers(response[key_r_values].as_raw_list()); if (response[key_r_token].is_raw_string()) add_transaction(std::unique_ptr(new DhtTransactionAnnouncePeer(transaction->id(), transaction->address(), announce->target(), response[key_r_token].as_raw_string())), packet_prio_low); announce->update_status(); } void DhtServer::find_node_next(DhtTransactionSearch* transaction) { int priority = packet_prio_low; if (transaction->search()->is_announce()) priority = packet_prio_high; auto node = transaction->search()->get_contact(); while (node != transaction->search()->end()) { add_transaction(std::unique_ptr(new DhtTransactionFindNode(node)), priority); node = transaction->search()->get_contact(); } if (!transaction->search()->is_announce()) return; auto announce = dynamic_cast(transaction->search().get()); if (announce == nullptr) throw internal_error("DhtServer::find_node_next dynamic_cast to DhtAnnounce failed."); if (announce->complete()) { // We have found the 8 closest nodes to the info hash. Retrieve peers // from them and announce to them. for (node = announce->start_announce(); node != announce->end(); ++node) add_transaction(std::unique_ptr(new DhtTransactionGetPeers(node)), packet_prio_high); } announce->update_status(); } void DhtServer::add_packet(std::shared_ptr packet, int priority) { switch (priority) { // High priority packets are for important queries, and quite small. // They're added to front of high priority queue and thus will be the // next packets sent. case packet_prio_high: m_highQueue.push_front(std::move(packet)); break; // Low priority query packets are added to the back of the high priority // queue and will be sent when all high priority packets have been transmitted. case packet_prio_low: m_highQueue.push_back(std::move(packet)); break; // Reply packets will be processed after all of our own packets have been send. case packet_prio_reply: m_lowQueue.push_back(std::move(packet)); break; default: throw internal_error("DhtServer::add_packet called with invalid priority."); } } void DhtServer::drop_packet(const DhtTransactionPacket* packet) { m_highQueue.erase(std::remove_if(m_highQueue.begin(), m_highQueue.end(), [packet](auto& p) { return p.get() == packet; }), m_highQueue.end()); m_lowQueue.erase(std::remove_if(m_lowQueue.begin(), m_lowQueue.end(), [packet](auto& p) { return p.get() == packet; }), m_lowQueue.end()); if (m_highQueue.empty() && m_lowQueue.empty()) this_thread::poll()->remove_write(this); } void DhtServer::create_query(transaction_itr itr, int tID, [[maybe_unused]] const sockaddr* sa, int priority) { if (itr->second->id() == m_router->id()) throw internal_error("DhtServer::create_query trying to send to itself."); DhtMessage query; // Transaction ID is a bencode string. query[key_t] = raw_bencode(query.data_end, 3); *query.data_end++ = '1'; *query.data_end++ = ':'; *query.data_end++ = tID; auto& transaction = itr->second; query[key_q] = raw_string::from_c_str(queries[transaction->type()]); query[key_y] = raw_bencode::from_c_str("1:q"); query[key_v] = raw_bencode("4:" PEER_VERSION, 6); query[key_a_id] = m_router->id_raw_string(); switch (transaction->type()) { case DhtTransaction::DHT_PING: // nothing to do break; case DhtTransaction::DHT_FIND_NODE: query[key_a_target] = transaction->as_find_node()->search()->target_raw_string(); break; case DhtTransaction::DHT_GET_PEERS: query[key_a_infoHash] = transaction->as_get_peers()->search()->target_raw_string(); break; case DhtTransaction::DHT_ANNOUNCE_PEER: query[key_a_infoHash] = transaction->as_announce_peer()->info_hash_raw_string(); query[key_a_token] = transaction->as_announce_peer()->token(); query[key_a_port] = runtime::listen_port(); break; } auto packet = std::make_shared(transaction->address(), query, tID, transaction); transaction->set_packet(packet); add_packet(packet, priority); m_queriesSent++; } void DhtServer::create_response(const DhtMessage& req, const sockaddr* sa, DhtMessage& reply) { reply[key_r_id] = m_router->id_raw_string(); reply[key_t] = req[key_t]; reply[key_y] = raw_bencode::from_c_str("1:r"); reply[key_v] = raw_bencode("4:" PEER_VERSION, 6); add_packet(std::make_shared(sa, reply), packet_prio_reply); } void DhtServer::create_error(const DhtMessage& req, const sockaddr* sa, int num, const char* msg) { DhtMessage error; if (req[key_t].is_raw_string() && req[key_t].as_raw_string().size() < 67) error[key_t] = req[key_t]; error[key_y] = raw_bencode::from_c_str("1:e"); error[key_v] = raw_bencode("4:" PEER_VERSION, 6); error[key_e_0] = num; error[key_e_1] = raw_string::from_c_str(msg); add_packet(std::make_shared(sa, error), packet_prio_reply); } int DhtServer::add_transaction(std::shared_ptr transaction, int priority) { // Try random transaction ID. This is to make it less likely that we reuse // a transaction ID from an earlier transaction which timed out and we forgot // about it, so that if the node replies after the timeout it's less likely // that we match the reply to the wrong transaction. // // If there's an existing transaction with the random ID we search for the next // unused one. Since normally only one or two transactions will be active per // node, a collision is extremely unlikely, and a linear search for the first // open one is the most efficient. unsigned int rnd = static_cast(random()); unsigned int id = rnd; auto insertItr = m_transactions.lower_bound(transaction->key(rnd)); // If key matches, keep trying successive IDs. while (insertItr != m_transactions.end() && insertItr->first == transaction->key(id)) { ++insertItr; id = static_cast(id + 1); // Give up after trying all possible IDs. This should never happen. if (id == rnd) return -1; // Transaction ID wrapped around, reset iterator. if (id == 0) insertItr = m_transactions.lower_bound(transaction->key(id)); } // We know where to insert it, so pass that as hint. insertItr = m_transactions.insert(insertItr, std::make_pair(transaction->key(id), transaction)); create_query(insertItr, id, transaction->address(), priority); start_write(); return id; } // Transaction received no reply and timed out. Mark node as bad and remove // transaction (except if it was only the quick timeout). DhtServer::transaction_itr DhtServer::failed_transaction(transaction_itr itr, bool quick) { auto transaction = itr->second; // If it was a known node, remember that it didn't reply, unless the transaction // is only stalled (had quick timeout, but not full timeout). Also if the // transaction still has an associated packet, the packet never got sent due to // throttling, so don't blame the remote node for not replying. // Finally, if we haven't received anything whatsoever so far, assume the entire // network is down and so we can't blame the node either. if (!quick && m_networkUp && transaction->packet() == NULL && transaction->id() != torrent::DhtRouter::zero_id) m_router->node_inactive(transaction->id(), transaction->address()); if (transaction->type() == DhtTransaction::DHT_FIND_NODE) { if (quick) transaction->as_find_node()->set_stalled(); else transaction->as_find_node()->complete(false); try { find_node_next(transaction->as_find_node()); } catch (const std::exception&) { if (!quick) { drop_packet(transaction->packet().get()); m_transactions.erase(itr); } throw; } } // don't actually delete the transaction until the final timeout if (quick) return ++itr; drop_packet(transaction->packet().get()); m_transactions.erase(itr++); return itr; } void DhtServer::clear_transactions() { for (auto& transaction : m_transactions) drop_packet(transaction.second->packet().get()); m_transactions.clear(); } void DhtServer::event_read() { while (true) { Object request; int type = '?'; DhtMessage message; raw_string nodeIdStr; const HashString* nodeId = NULL; char buffer[2048]; sockaddr_in6 sa_raw{}; sockaddr* sa = reinterpret_cast(&sa_raw); try { int32_t read = read_datagram_sa(buffer, sizeof(buffer), sa, sizeof(sa_raw)); if (read < 0) break; // We can currently only process mapped-IPv4 addresses, not real IPv6. // Translate them to an af_inet socket_address. if (sa_is_v4mapped(sa)) { auto sa_unmapped = sin_from_v4mapped_in6(&sa_raw); *reinterpret_cast(&sa_raw) = *sa_unmapped.get(); } if (sa->sa_family != AF_INET) continue; // If it's not a valid bencode dictionary at all, it's probably not a DHT // packet at all, so we don't throw an error to prevent bounce loops. try { static_map_read_bencode(buffer, buffer + read, message); } catch (const bencode_error&) { continue; } if (!message[key_t].is_raw_string()) throw dht_error(dht_error_protocol, "No transaction ID"); // Restrict the length of Transaction IDs. We echo them in our replies. if(message[key_t].as_raw_string().size() > 20) { throw dht_error(dht_error_protocol, "Transaction ID length too long"); } if (!message[key_y].is_raw_string()) throw dht_error(dht_error_protocol, "No message type"); if (message[key_y].as_raw_string().size() != 1) throw dht_error(dht_error_bad_method, "Unsupported message type"); type = message[key_y].as_raw_string().data()[0]; // Queries and replies have node ID in different dictionaries. if (type == 'r' || type == 'q') { if (!message[type == 'q' ? key_a_id : key_r_id].is_raw_string()) throw dht_error(dht_error_protocol, "Invalid `id' value"); nodeIdStr = message[type == 'q' ? key_a_id : key_r_id].as_raw_string(); if (nodeIdStr.size() < HashString::size_data) throw dht_error(dht_error_protocol, "`id' value too short"); nodeId = HashString::cast_from(nodeIdStr.data()); } // Sanity check the returned transaction ID. if ((type == 'r' || type == 'e') && (!message[key_t].is_raw_string() || message[key_t].as_raw_string().size() != 1)) throw dht_error(dht_error_protocol, "Invalid transaction ID type/length."); // Stupid broken implementations. if (nodeId != NULL && *nodeId == m_router->id()) throw dht_error(dht_error_protocol, "Send your own ID, not mine"); switch (type) { case 'q': process_query(*nodeId, sa, message); break; case 'r': process_response(*nodeId, sa, message); break; case 'e': process_error(sa, message); break; default: throw dht_error(dht_error_bad_method, "Unknown message type."); } // If node was querying us, reply with error packet, otherwise mark the node as "query failed", // so that if it repeatedly sends malformed replies we will drop it instead of propagating it // to other nodes. } catch (const bencode_error& e) { if ((type == 'r' || type == 'e') && nodeId != NULL) { m_router->node_inactive(*nodeId, sa); } else { snprintf(message.data_end, message.data + torrent::DhtMessage::data_size - message.data_end - 1, "Malformed packet: %s", e.what()); message.data[torrent::DhtMessage::data_size - 1] = '\0'; create_error(message, sa, dht_error_protocol, message.data_end); } } catch (const dht_error& e) { if ((type == 'r' || type == 'e') && nodeId != NULL) m_router->node_inactive(*nodeId, sa); else create_error(message, sa, e.code(), e.what()); } catch (const network_error&) { } } start_write(); } void DhtServer::process_queue(packet_queue& queue) { while (!queue.empty()) { auto packet = queue.front(); DhtTransaction::key_type transactionKey = 0; if(packet->has_transaction()) transactionKey = packet->transaction()->key(packet->id()); // Make sure its transaction hasn't timed out yet, if it has/had one // and don't bother sending non-transaction packets (replies) after // more than 15 seconds in the queue. if (packet->has_failed() || packet->age() > 15) { queue.pop_front(); continue; } queue.pop_front(); try { int written = write_datagram_sa(packet->c_str(), packet->length(), packet->address()); if (written == -1) throw network_error(); if (static_cast(written) != packet->length()) throw network_error(); } catch (const network_error&) { // Couldn't write packet, maybe something wrong with node address or routing, so mark node as bad. if (packet->has_transaction()) { auto itr = m_transactions.find(transactionKey); if (itr == m_transactions.end()) throw internal_error("DhtServer::process_queue could not find transaction."); failed_transaction(itr, false); } } if (packet->has_transaction()) { // here transaction can be already deleted by failed_transaction. auto itr = m_transactions.find(transactionKey); if (itr != m_transactions.end()) packet->transaction()->reset_packet(); } } } void DhtServer::event_write() { if (m_highQueue.empty() && m_lowQueue.empty()) throw internal_error("DhtServer::event_write called but both write queues are empty."); process_queue(m_highQueue); process_queue(m_lowQueue); if (m_highQueue.empty() && m_lowQueue.empty()) this_thread::poll()->remove_write(this); } void DhtServer::event_error() { } void DhtServer::start_write() { if (!m_highQueue.empty() || !m_lowQueue.empty()) this_thread::poll()->insert_write(this); if (!m_task_timeout.is_scheduled() && !m_transactions.empty()) this_thread::scheduler()->wait_for_ceil_seconds(&m_task_timeout, 5s); } void DhtServer::receive_timeout() { auto itr = m_transactions.begin(); while (itr != m_transactions.end()) { if (itr->second->has_quick_timeout() && itr->second->quick_timeout() < this_thread::cached_seconds().count()) { itr = failed_transaction(itr, true); } else if (itr->second->timeout() < this_thread::cached_seconds().count()) { itr = failed_transaction(itr, false); } else { ++itr; } } start_write(); } } // namespace torrent libtorrent-0.16.17/src/dht/dht_server.h000066400000000000000000000134711522271512000177410ustar00rootroot00000000000000#ifndef LIBTORRENT_DHT_SERVER_H #define LIBTORRENT_DHT_SERVER_H #include #include #include #include #include "dht/dht_transaction.h" #include "net/socket_datagram.h" #include "torrent/hash_string.h" #include "torrent/object_raw_bencode.h" #include "torrent/utils/scheduler.h" // TODO: Remove. #include "net/address_list.h" namespace torrent { class DhtBucket; class DhtNode; class DhtRouter; class DownloadInfo; class DhtMessage; class TrackerDht; // UDP server that handles the DHT node communications. class DhtServer : public SocketDatagram { public: DhtServer(DhtRouter* self); ~DhtServer() override; const char* type_name() const override { return "dht"; } bool is_active() const { return is_open(); } void start(int port); void stop(); unsigned int queries_received() const { return m_queriesReceived; } unsigned int queries_sent() const { return m_queriesSent; } unsigned int replies_received() const { return m_repliesReceived; } unsigned int errors_received() const { return m_errorsReceived; } unsigned int errors_caught() const { return m_errorsCaught; } void reset_statistics(); // Contact a node to see if it replies. Set id=0 if unknown. void ping(const HashString& id, const sockaddr* sa); // Do a find_node search with the given contacts as starting point for the // search. void find_node(const DhtBucket& contacts, const HashString& target); // Do DHT announce, starting with the given contacts. void announce(const DhtBucket& contacts, const HashString& infoHash, std::weak_ptr tracker); // Cancel given announce for given tracker, or all matching announces if info/tracker NULL. void cancel_announce(const HashString& info_hash, std::weak_ptr tracker); // Called every 15 minutes. void update(); void check_search_completed(std::shared_ptr search); void check_search_trimming(std::shared_ptr search); void event_read() override; void event_write() override; void event_error() override; private: // DHT error codes. static constexpr int dht_error_generic = 201; static constexpr int dht_error_server = 202; static constexpr int dht_error_protocol = 203; static constexpr int dht_error_bad_method = 204; struct [[gnu::packed]] compact_node_info { char _id[20]; SocketAddressCompact _addr; HashString& id() { return *HashString::cast_from(_id); } }; using packet_queue = std::deque>; using node_info_list = std::list; using search_set = std::set>; // Pending transactions. using transaction_map = std::map>; using transaction_itr = transaction_map::iterator; // DHT transaction names for given transaction type. static constexpr std::array queries{ "ping", "find_node", "get_peers", "announce_peer", }; // Priorities for the outgoing packets. static constexpr int packet_prio_high = 2; // For important queries we send (announces). static constexpr int packet_prio_low = 1; // For (relatively) unimportant queries we send. static constexpr int packet_prio_reply = 0; // For replies to peer queries. void start_write(); void process_query(const HashString& id, const sockaddr* sa, const DhtMessage& req); void process_response(const HashString& id, const sockaddr* sa, const DhtMessage& req); void process_error(const sockaddr* sa, const DhtMessage& error); void parse_find_node_reply(DhtTransactionSearch* t, raw_string nodes); void parse_get_peers_reply(DhtTransactionGetPeers* t, const DhtMessage& res); void find_node_next(DhtTransactionSearch* t); void add_packet(std::shared_ptr packet, int priority); void drop_packet(const DhtTransactionPacket* packet); void create_query(transaction_itr itr, int tID, const sockaddr* sa, int priority); void create_response(const DhtMessage& req, const sockaddr* sa, DhtMessage& reply); void create_error(const DhtMessage& req, const sockaddr* sa, int num, const char* msg); void create_find_node_response(const DhtMessage& arg, DhtMessage& reply); void create_get_peers_response(const DhtMessage& arg, const sockaddr* sa, DhtMessage& reply); void create_announce_peer_response(const DhtMessage& arg, const sockaddr* sa, DhtMessage& reply); int add_transaction(std::shared_ptr transaction, int priority); // This returns the iterator after the given one or end() transaction_itr failed_transaction(transaction_itr itr, bool quick); void clear_transactions(); void process_queue(packet_queue& queue); void receive_timeout(); DhtRouter* m_router{}; search_set m_searches; packet_queue m_highQueue; packet_queue m_lowQueue; transaction_map m_transactions; utils::SchedulerEntry m_task_timeout; unsigned int m_queriesReceived{}; unsigned int m_queriesSent{}; unsigned int m_repliesReceived{}; unsigned int m_errorsReceived{}; unsigned int m_errorsCaught{}; bool m_networkUp{false}; }; } // namespace torrent #endif libtorrent-0.16.17/src/dht/dht_tracker.cc000066400000000000000000000051751522271512000202260ustar00rootroot00000000000000#include "config.h" #include "dht_tracker.h" namespace torrent { void DhtTracker::add_peer(uint32_t addr_n, uint16_t port) { if (port == 0) return; SocketAddressCompact compact(addr_n, port); unsigned int oldest = 0; uint32_t minSeen = ~uint32_t(); // Check if peer exists. If not, find oldest peer. for (unsigned int i = 0; i < size(); i++) { if (m_peers[i].peer.addr == compact.addr) { m_peers[i].peer.port = compact.port; m_lastSeen[i] = this_thread::cached_seconds().count(); return; } if (m_lastSeen[i] < minSeen) { minSeen = m_lastSeen[i]; oldest = i; } } // If peer doesn't exist, append to list if the table is not full. if (size() < max_size) { m_peers.emplace_back(compact); m_lastSeen.push_back(this_thread::cached_seconds().count()); return; } // Peer doesn't exist and table is full: replace oldest peer. m_peers[oldest] = compact; m_lastSeen[oldest] = this_thread::cached_seconds().count(); } // Return compact info as bencoded string (8 bytes per peer) for up to 30 peers, // returning different peers for each call if there are more. raw_list DhtTracker::get_peers(unsigned int maxPeers) { if (sizeof(BencodeAddress) != 8) throw internal_error("DhtTracker::BencodeAddress is packed incorrectly."); auto first = m_peers.begin(); auto last = m_peers.end(); // If we have more than max_peers, randomly return block of peers. // The peers in overlapping blocks get picked twice as often, but // that's better than returning fewer peers. if (m_peers.size() > maxPeers) { unsigned int blocks = (m_peers.size() + maxPeers - 1) / maxPeers; first += (random() % blocks) * (m_peers.size() - maxPeers) / (blocks - 1); last = first + maxPeers; } return raw_list(first->bencode(), last->bencode() - first->bencode()); } // Remove old announces. void DhtTracker::prune(uint32_t maxAge) { uint32_t minSeen = this_thread::cached_seconds().count() - maxAge; for (unsigned int i = 0; i < m_lastSeen.size(); i++) if (m_lastSeen[i] < minSeen) m_peers[i].peer.port = 0; m_peers.erase(std::remove_if(m_peers.begin(), m_peers.end(), std::mem_fn(&BencodeAddress::empty)), m_peers.end()); m_lastSeen.erase(std::remove_if(m_lastSeen.begin(), m_lastSeen.end(), [minSeen](auto seen) { return seen < minSeen; }), m_lastSeen.end()); if (m_peers.size() != m_lastSeen.size()) throw internal_error("DhtTracker::prune did inconsistent peer pruning."); } } // namespace torrent libtorrent-0.16.17/src/dht/dht_tracker.h000066400000000000000000000035001522271512000200560ustar00rootroot00000000000000#ifndef LIBTORRENT_DHT_TRACKER_H #define LIBTORRENT_DHT_TRACKER_H #include #include "net/address_list.h" // For SA. #include "torrent/object_raw_bencode.h" namespace torrent { // Container for peers tracked in a torrent. class DhtTracker { public: // Maximum number of peers we return for a GET_PEERS query (default value only). // Needs to be small enough so that a packet with a payload of num_peers*6 bytes // does not need fragmentation. Value chosen so that the size is approximately // equal to a FIND_NODE reply (8*26 bytes). static constexpr unsigned int max_peers = 32; // Maximum number of peers we keep track of. For torrents with more peers, // we replace the oldest peer with each new announce to avoid excessively // large peer tables for very active torrents. static constexpr unsigned int max_size = 128; bool empty() const { return m_peers.empty(); } size_t size() const { return m_peers.size(); } void add_peer(uint32_t addr_n, uint16_t port); raw_list get_peers(unsigned int maxPeers = max_peers); // Remove old announces from the tracker that have not reannounced for // more than the given number of seconds. void prune(uint32_t maxAge); private: // We need to store the address as a bencoded string. struct [[gnu::packed]] BencodeAddress { char header[2]; SocketAddressCompact peer; BencodeAddress(const SocketAddressCompact& p) : peer(p) { header[0] = '6'; header[1] = ':'; } const char* bencode() const { return header; } bool empty() const { return !peer.port; } }; using PeerList = std::vector; PeerList m_peers; std::vector m_lastSeen; }; } // namespace torrent #endif libtorrent-0.16.17/src/dht/dht_transaction.cc000066400000000000000000000100161522271512000211060ustar00rootroot00000000000000#include "config.h" #include "dht/dht_transaction.h" #include #include "dht/dht_bucket.h" #include "dht/dht_server.h" #include "torrent/exceptions.h" #include "torrent/object_stream.h" #include "torrent/net/socket_address.h" #include "tracker/tracker_dht.h" namespace torrent { template<> const DhtMessage::key_list_type DhtMessage::base_type::keys; // // DhtTransactionPacket: // DhtTransactionPacket::DhtTransactionPacket(const sockaddr* s, const DhtMessage& d, unsigned int id, std::shared_ptr t) : m_socket_address(sa_copy(s)), m_id(id), m_transaction(std::move(t)) { build_buffer(d); } DhtTransactionPacket::DhtTransactionPacket(const sockaddr* s, const DhtMessage& d) : m_socket_address(sa_copy(s)), m_id(-this_thread::cached_seconds().count()) { build_buffer(d); } void DhtTransactionPacket::build_buffer(const DhtMessage& msg) { char buffer[1500]; // If the message would exceed an Ethernet frame, something went very wrong. object_buffer_t result = static_map_write_bencode_c(object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), msg); m_length = result.second - buffer; m_data = std::make_unique(m_length); memcpy(m_data.get(), buffer, m_length); } DhtTransaction::DhtTransaction(int quick_timeout, int timeout, const HashString& id, const sockaddr* sa) : m_id(id), m_hasQuickTimeout(quick_timeout > 0), m_socket_address(sa_copy(sa)), m_timeout(this_thread::cached_seconds().count() + timeout), m_quickTimeout(this_thread::cached_seconds().count() + quick_timeout) { } DhtTransaction::~DhtTransaction() { if (m_packet != NULL) m_packet->set_failed(); } DhtTransaction::key_type DhtTransaction::key(const sockaddr* sa, int id) { if (sa_is_inet(sa)) return (static_cast(reinterpret_cast(sa)->sin_addr.s_addr) << 32) + id; else if (sa_is_inet6(sa)) throw internal_error("DhtTransaction::key() called with inet6 address."); else throw internal_error("DhtTransaction::key() called with non-inet address."); } bool DhtTransaction::key_match(key_type key, const sockaddr* sa) { if (sa_is_inet(sa)) return (key >> 32) == static_cast(reinterpret_cast(sa)->sin_addr.s_addr); else if (sa_is_inet6(sa)) throw internal_error("DhtTransaction::key_match() called with inet6 address."); else throw internal_error("DhtTransaction::key_match() called with non-inet address."); } // // DhtTransactionSearch: // DhtTransactionSearch::DhtTransactionSearch(int quick_timeout, int timeout, dht::DhtSearch::const_accessor& node) : DhtTransaction(quick_timeout, timeout, node.node()->id(), node.node()->address()), m_node(node), m_search(node.search()) { if (!m_hasQuickTimeout) m_search->m_concurrency++; } DhtTransactionSearch::~DhtTransactionSearch() { if (m_node != m_search->end()) complete(false); m_search->server()->check_search_completed(std::move(m_search)); } void DhtTransactionSearch::set_stalled() { if (!m_hasQuickTimeout) throw internal_error("DhtTransactionSearch::set_stalled() called on already stalled transaction."); m_hasQuickTimeout = false; m_search->m_concurrency++; } void DhtTransactionSearch::complete(bool success) { if (m_node == m_search->end()) throw internal_error("DhtTransactionSearch::complete() called multiple times."); if (m_node.search() != m_search) throw internal_error("DhtTransactionSearch::complete() called for node from wrong search."); if (!m_hasQuickTimeout) m_search->m_concurrency--; m_search->node_status(m_node.node(), success); m_node = m_search->end(); } DhtTransaction::transaction_type DhtTransactionPing::type() const { return DHT_PING; } DhtTransaction::transaction_type DhtTransactionFindNode::type() const { return DHT_FIND_NODE; } DhtTransaction::transaction_type DhtTransactionGetPeers::type() const { return DHT_GET_PEERS; } DhtTransaction::transaction_type DhtTransactionAnnouncePeer::type() const { return DHT_ANNOUNCE_PEER; } } // namespace torrent libtorrent-0.16.17/src/dht/dht_transaction.h000066400000000000000000000176731522271512000207700ustar00rootroot00000000000000#ifndef LIBTORRENT_DHT_TRANSACTION_H #define LIBTORRENT_DHT_TRANSACTION_H #include #include #include "dht/dht_node.h" #include "dht/transactions/dht_search.h" #include "torrent/hash_string.h" #include "torrent/object_static_map.h" #include "torrent/net/types.h" #include "tracker/tracker_dht.h" namespace torrent::dht { class DhtSearch; } namespace torrent { class TrackerDht; class DhtBucket; class DhtTransactionSearch; class DhtTransaction; class DhtTransactionPing; class DhtTransactionFindNode; class DhtTransactionFindNodeAnnounce; class DhtTransactionGetPeers; class DhtTransactionAnnouncePeer; // Possible bencode keys in a DHT message. enum dht_keys { key_a_id, key_a_infoHash, key_a_port, key_a_target, key_a_token, key_e_0, key_e_1, key_q, key_r_id, key_r_nodes, key_r_token, key_r_values, key_t, key_v, key_y, key_LAST, }; class DhtMessage : public static_map_type { public: using base_type = static_map_type; // Must be big enough to hold one of the possible variable-sized reply data. // Currently either: // - error message (size doesn't really matter, it'll be truncated at worst) // - announce token (8 bytes, needs 20 bytes buffer to build) // Never more than one of the above. // And additionally for queries we send: // - transaction ID (3 bytes) static constexpr size_t data_size = 64; char data[data_size]; char* data_end{data}; }; // Class holding transaction data to be transmitted. class DhtTransactionPacket { public: // transaction packet DhtTransactionPacket(const sockaddr* s, const DhtMessage& d, unsigned int id, std::shared_ptr t); // non-transaction packet DhtTransactionPacket(const sockaddr* s, const DhtMessage& d); ~DhtTransactionPacket() = default; bool has_transaction() const { return m_id >= -1; } bool has_failed() const { return m_id == -1; } void set_failed() { m_id = -1; } const auto* address() const { return m_socket_address.get(); } auto* address() { return m_socket_address.get(); } const char* c_str() const { return m_data.get(); } size_t length() const { return m_length; } int id() const { return m_id; } int age() const { return has_transaction() ? 0 : this_thread::cached_seconds().count() + m_id; } const auto* transaction() const { return m_transaction.get(); } auto* transaction() { return m_transaction.get(); } private: DhtTransactionPacket(const DhtTransactionPacket&) = delete; DhtTransactionPacket& operator=(const DhtTransactionPacket&) = delete; void build_buffer(const DhtMessage& data); sa_unique_ptr m_socket_address; std::unique_ptr m_data; size_t m_length{}; int m_id{}; std::shared_ptr m_transaction; }; // DHT Transaction classes. DhtTransaction and DhtTransactionSearch // are not directly usable with no public constructor, since type() // is a pure virtual function. class DhtTransaction { public: virtual ~DhtTransaction(); enum transaction_type { DHT_PING, DHT_FIND_NODE, DHT_GET_PEERS, DHT_ANNOUNCE_PEER, }; // Key to uniquely identify a transaction with given per-node transaction id. using key_type = uint64_t; virtual transaction_type type() const = 0; virtual bool is_search() { return false; } key_type key(int id) const { return key(m_socket_address.get(), id); } static key_type key(const sockaddr* sa, int id); static bool key_match(key_type key, const sockaddr* sa); const HashString& id() { return m_id; } const auto* address() { return m_socket_address.get(); } int timeout() const { return m_timeout; } int quick_timeout() const { return m_quickTimeout; } bool has_quick_timeout() const { return m_hasQuickTimeout; } auto& packet() const { return m_packet; } void set_packet(std::shared_ptr& p) { m_packet = p; } void reset_packet() { m_packet.reset(); } DhtTransactionSearch* as_search(); DhtTransactionPing* as_ping(); DhtTransactionFindNode* as_find_node(); DhtTransactionGetPeers* as_get_peers(); DhtTransactionAnnouncePeer* as_announce_peer(); protected: DhtTransaction(int quick_timeout, int timeout, const HashString& id, const sockaddr* sa); // m_id must be the first element to ensure it is aligned properly, // because we later read a size_t value from it. const HashString m_id; bool m_hasQuickTimeout; private: DhtTransaction(const DhtTransaction&) = delete; DhtTransaction& operator=(const DhtTransaction&) = delete; sa_unique_ptr m_socket_address; int m_timeout; int m_quickTimeout; std::shared_ptr m_packet; }; class DhtTransactionSearch : public DhtTransaction { public: ~DhtTransactionSearch() override; bool is_search() override { return true; } auto node() { return m_node; } auto& search() { return m_search; } void set_stalled(); void complete(bool success); protected: DhtTransactionSearch(int quick_timeout, int timeout, dht::DhtSearch::const_accessor& node); private: dht::DhtSearch::const_accessor m_node; std::shared_ptr m_search; }; // Actual transaction classes. class DhtTransactionPing : public DhtTransaction { public: DhtTransactionPing(const HashString& id, const sockaddr* sa) : DhtTransaction(-1, 30, id, sa) { } transaction_type type() const override; }; class DhtTransactionFindNode : public DhtTransactionSearch { public: DhtTransactionFindNode(dht::DhtSearch::const_accessor& node) : DhtTransactionSearch(4, 30, node) { } transaction_type type() const override; }; class DhtTransactionGetPeers : public DhtTransactionSearch { public: DhtTransactionGetPeers(dht::DhtSearch::const_accessor& node) : DhtTransactionSearch(-1, 30, node) { } transaction_type type() const override; }; class DhtTransactionAnnouncePeer : public DhtTransaction { public: DhtTransactionAnnouncePeer(const HashString& id, const sockaddr* sa, const HashString& infoHash, raw_string token) : DhtTransaction(-1, 30, id, sa), m_infoHash(infoHash), m_token(token) { } transaction_type type() const override; const HashString& info_hash() { return m_infoHash; } raw_string info_hash_raw_string() const { return raw_string(m_infoHash.data(), HashString::size_data); } raw_string token() { return m_token; } private: HashString m_infoHash; raw_string m_token; }; // These could (should?) check that the type matches, or use dynamic_cast if we have RTTI. inline DhtTransactionSearch* DhtTransaction::as_search() { return static_cast(this); } inline DhtTransactionPing* DhtTransaction::as_ping() { return static_cast(this); } inline DhtTransactionFindNode* DhtTransaction::as_find_node() { return static_cast(this); } inline DhtTransactionGetPeers* DhtTransaction::as_get_peers() { return static_cast(this); } inline DhtTransactionAnnouncePeer* DhtTransaction::as_announce_peer() { return static_cast(this); } } // namespace torrent #endif libtorrent-0.16.17/src/dht/transactions/000077500000000000000000000000001522271512000201255ustar00rootroot00000000000000libtorrent-0.16.17/src/dht/transactions/dht_announce.cc000066400000000000000000000043431522271512000231050ustar00rootroot00000000000000#include "config.h" #include "dht/transactions/dht_announce.h" #include #include "dht/dht_bucket.h" #include "dht/dht_node.h" #include "net/address_list.h" #include "tracker/tracker_dht.h" namespace torrent::dht { DhtAnnounce::DhtAnnounce(DhtServer* server, const HashString& infoHash, std::weak_ptr tracker) : DhtSearch(server, infoHash), m_tracker(tracker) { } DhtAnnounce::~DhtAnnounce() { assert(complete() && "DhtAnnounce::~DhtAnnounce called while announce not complete."); TrackerDht::add_event(m_tracker, [contacted = m_contacted, replied = m_replied](TrackerDht* tracker) { const char* failure = nullptr; if (tracker->dht_state() != TrackerDht::state_announcing) { if (!contacted) failure = "No DHT nodes available for peer search."; else failure = "DHT search unsuccessful."; } else { if (!contacted) failure = "DHT search unsuccessful."; else if (replied == 0) // && !tracker->has_peers_unsafe()) failure = "Announce failed"; } if (failure != nullptr) tracker->receive_failed(failure); else tracker->receive_success(); }); } DhtSearch::const_accessor DhtAnnounce::start_announce() { trim(true); if (empty()) return end(); if (!complete() || m_next != end() || size() > DhtBucket::num_nodes) throw internal_error("DhtSearch::start_announce called in inconsistent state."); m_contacted = m_pending = size(); m_replied = 0; TrackerDht::add_event(m_tracker, [](TrackerDht* tracker) { tracker->set_dht_announce_state(); }); for (const auto& [node, _] : *this) set_node_active(node, true); return const_accessor(begin()); } void DhtAnnounce::receive_peers(raw_list peers) { AddressList address_list; address_list.parse_address_bencode(peers); TrackerDht::add_event(m_tracker, [address_list = std::move(address_list)](TrackerDht* tracker) mutable { tracker->receive_peers(std::move(address_list)); }); } void DhtAnnounce::update_status() { TrackerDht::add_event(m_tracker, [contacted = m_contacted, replied = m_replied](TrackerDht* tracker) { tracker->receive_progress(replied, contacted); }); } } // namespace torrent::dht libtorrent-0.16.17/src/dht/transactions/dht_announce.h000066400000000000000000000021331522271512000227420ustar00rootroot00000000000000#ifndef LIBTORRENT_DHT_TRANSACTIONS_DHT_ANNOUNCE_H #define LIBTORRENT_DHT_TRANSACTIONS_DHT_ANNOUNCE_H #include #include "dht/transactions/dht_search.h" #include "torrent/common.h" // DhtAnnounce is a derived class used for searches that will eventually lead to an announce to the // closest nodes. namespace torrent { class DhtBucket; class TrackerDht; } namespace torrent::dht { class DhtAnnounce : public DhtSearch { public: DhtAnnounce(DhtServer* server, const HashString& infoHash, std::weak_ptr tracker); ~DhtAnnounce() override; bool is_announce() const override { return true; } const auto& tracker() const { return m_tracker; } // Start announce and return final set of nodes in get_contact() calls. // This resets DhtSearch's completed() function, which now // counts announces instead. const_accessor start_announce(); void receive_peers(raw_list peers); void update_status(); private: std::weak_ptr m_tracker; }; } // namespace torrent::dht #endif libtorrent-0.16.17/src/dht/transactions/dht_search.cc000066400000000000000000000132401522271512000225400ustar00rootroot00000000000000#include "config.h" #include "dht/transactions/dht_search.h" #include #include "dht/dht_node.h" #include "dht/dht_server.h" namespace torrent::dht { bool dht_compare_closer::operator () (const std::unique_ptr& one, const std::unique_ptr& two) const { return DhtSearch::is_closer(one->id(), two->id(), m_target); } DhtSearch::DhtSearch(DhtServer* server, const HashString& target) : base_type(dht_compare_closer(target)), m_next(end()), m_server(server), m_target(target) { // TODO: Must be done manually to ensure we got a shared_ptr. // add_contacts(contacts); } DhtSearch::~DhtSearch() { // Make sure transactions were destructed first. Since it is the destruction // of a transaction that triggers this destructor, that should always be the // case. assert(!m_pending && "DhtSearch::~DhtSearch called with pending transactions."); assert(m_concurrency == 3 && "DhtSearch::~DhtSearch called with invalid concurrency limit."); // TODO: Hack. // for (auto& itr : *this) // itr.second->server()->check_search_trimming(itr.second); } // TODO: Check if DhtSearch gets stored somewhere, and DhtBucket seems the most likely candidate. // // This seems to be storing self in the map. // // TODO: Remove self stored in the map, and when getting accessor from map, also pass the (self) DhtSearch. // bool DhtSearch::add_contact(const HashString& id, const sockaddr* sa) { auto [itr, added] = emplace(std::make_unique(id, sa), shared_from_this()); if (added) m_restart = true; return added; } void DhtSearch::add_contacts(const DhtBucket& contacts) { DhtBucketChain chain(&contacts); // Add max_contacts=18 closest nodes, and fill up so we also have at least 8 good nodes. int needClosest = max_contacts - size(); int needGood = DhtBucket::num_nodes; for (auto itr = chain.bucket()->begin(); needClosest > 0 || needGood > 0; ++itr) { while (itr == chain.bucket()->end()) { if (!chain.next()) return; itr = chain.bucket()->begin(); } if ((!(*itr)->is_bad() || needClosest > 0) && add_contact((*itr)->id(), (*itr)->address())) { needGood -= !(*itr)->is_bad(); needClosest--; } } } // Check if a node has been contacted yet. This is the case if it is not currently // being contacted, nor has it been found to be good or bad. bool DhtSearch::node_uncontacted(const std::unique_ptr& node) { return !node->is_active() && !node->is_good() && !node->is_bad(); } // After more contacts have been added, discard least closest nodes // except if node has a transaction pending. void DhtSearch::trim(bool is_final) { // We keep: // - the max_contacts=18 closest good or unknown nodes and all nodes closer // than them (to see if further searches find closer ones) // - for announces, also the 3 closest good nodes (i.e. nodes that have // replied) to have at least that many for the actual announce // - any node that currently has transactions pending // // However, after exhausting all search nodes, we only keep good nodes. // // For our purposes, the node status is as follows: // node is bad (contacted but hasn't replied) if is_bad() // node is good (contacted and replied) if is_good() // node is currently being contacted if is_active() // node is new and unknown otherwise int needClosest = is_final ? 0 : max_contacts; int needGood = is_announce() ? max_announce : 0; // We're done if we can't find any more nodes to contact. m_next = end(); for (accessor itr = base_type::begin(); itr != end(); ) { // If we have all we need, delete current node unless it is // currently being contacted. if (!itr.node()->is_active() && needClosest <= 0 && (!itr.node()->is_good() || needGood <= 0)) { // TODO: Temporary hack. // TODO: Add server function to add it to m_search if not complete. // TODO: We should replace m_pending with a vector of txs. // TODO: We're somehow storying self in the map, and erasing causes crash as m_pending == 2. // itr.search()->server()->check_search_trimming(itr.search()); erase(itr++); continue; } // Otherwise adjust needed counts appropriately. needClosest--; needGood -= itr.node()->is_good(); // Remember the first uncontacted node as the closest one to contact next. if (m_next == end() && node_uncontacted(itr.node())) m_next = const_accessor(itr); ++itr; } m_restart = false; } bool DhtSearch::is_closer(const HashString& one, const HashString& two, const HashString& target) { for (unsigned int i=0; i(one[i] ^ target[i]) < static_cast(two[i] ^ target[i]); return false; } // TODO: Change m_pending to a vector of weak_ref. DhtSearch::const_accessor DhtSearch::get_contact() { if (m_pending >= m_concurrency) return end(); if (m_restart) trim(false); auto ret = m_next; if (ret == end()) return ret; set_node_active(ret.node(), true); m_pending++; m_contacted++; // Find next node to contact: any node we haven't contacted yet. while (++m_next != end()) { if (node_uncontacted(m_next.node())) break; } return ret; } void DhtSearch::node_status(const std::unique_ptr& n, bool success) { if (!n->is_active()) throw internal_error("DhtSearch::node_status called for invalid/inactive node."); if (success) { n->set_good(); m_replied++; } else { n->set_bad(); } m_pending--; set_node_active(n, false); } void DhtSearch::set_node_active(const std::unique_ptr& n, bool active) { n->m_last_seen = active; } } // namespace torrent::dht libtorrent-0.16.17/src/dht/transactions/dht_search.h000066400000000000000000000121251522271512000224030ustar00rootroot00000000000000#ifndef LIBTORRENT_DHT_TRANSACTIONS_DHT_SEARCH_H #define LIBTORRENT_DHT_TRANSACTIONS_DHT_SEARCH_H #include #include "torrent/common.h" #include "torrent/hash_string.h" #include "torrent/object_static_map.h" // DhtSearch implements the DHT search algorithm and holds search data that needs to be persistent // across multiple find_node transactions. // // DhtSearch contains a list of nodes sorted by closeness to the given target, and returns what // nodes to contact with up to three concurrent transactions pending. // // The map element is the DhtSearch object itself to allow the returned accessors to know which // search a given node belongs to. // TODO: Consider moving this to dht/ namespace torrent { class DhtBucket; class DhtNode; class DhtServer; class DhtTransactionSearch; class TrackerDht; } namespace torrent::dht { // Compare predicate for ID closeness. struct dht_compare_closer { dht_compare_closer(const HashString& target) : m_target(target) { } const HashString& target() const { return m_target; } raw_string target_raw_string() const { return raw_string(m_target.data(), HashString::size_data); } bool operator () (const std::unique_ptr& one, const std::unique_ptr& two) const; private: const HashString m_target; }; // Use std::enable_shared_from_this as a temporary hack. class DhtSearch : public std::enable_shared_from_this, protected std::map, std::shared_ptr, dht_compare_closer> { public: using base_type = std::map, std::shared_ptr, dht_compare_closer>; // max_contacts: Number of closest potential contact nodes to keep. // max_announce: Number of closest nodes we actually announce to. static constexpr unsigned int max_contacts = 18; static constexpr unsigned int max_announce = 3; DhtSearch(DhtServer* server, const HashString& target); virtual ~DhtSearch(); // Wrapper for iterators, allowing more convenient access to the key // and element values, which also makes it easier to change the container // without having to modify much code using iterators. template struct accessor_wrapper : public T { accessor_wrapper() = default; accessor_wrapper(const T& itr) : T(itr) { } const auto& node() const { return (**this).first; } const std::shared_ptr& search() const { return (**this).second; } }; using const_accessor = accessor_wrapper; using accessor = accessor_wrapper; // Add a potential node to contact for the search. bool add_contact(const HashString& id, const sockaddr* sa); void add_contacts(const DhtBucket& contacts); // Return next node to contact. Up to concurrent_searches nodes are returned, // and end() after that. Don't advance the accessor to get further contacts! const_accessor get_contact(); // Search statistics. int num_contacted() const { return m_contacted; } int num_replied() const { return m_replied; } bool start() { m_started = true; return m_pending; } bool complete() const { return m_started && !m_pending; } const HashString& target() const { return m_target; } raw_string target_raw_string() const { return raw_string(m_target.data(), HashString::size_data); } virtual bool is_announce() const { return false; } // Expose the otherwise private end() function but return an accessor, // to allow code checking whether get_contact returned a valid accessor. const_accessor end() const { return base_type::end(); } // Used by the sorting/comparison predicate to see which node is closer. static bool is_closer(const HashString& one, const HashString& two, const HashString& target); protected: friend class torrent::DhtTransactionSearch; DhtServer* server() const { return m_server; } void trim(bool is_final); void node_status(const std::unique_ptr& n, bool success); static void set_node_active(const std::unique_ptr& n, bool active); // Statistics about contacted nodes. unsigned int m_pending{0}; unsigned int m_contacted{0}; unsigned int m_replied{0}; unsigned int m_concurrency{3}; bool m_restart{false}; // If true, trim nodes and reset m_next on the following get_contact call. bool m_started{false}; // Next node to return in get_contact, is end() if we have no more contactable nodes. const_accessor m_next; private: DhtSearch(const DhtSearch&) = delete; DhtSearch& operator=(const DhtSearch&) = delete; static bool node_uncontacted(const std::unique_ptr& node); DhtServer* m_server; HashString m_target; }; } // namespace torrent::dht #endif libtorrent-0.16.17/src/download/000077500000000000000000000000001522271512000164455ustar00rootroot00000000000000libtorrent-0.16.17/src/download/available_list.cc000066400000000000000000000026531522271512000217350ustar00rootroot00000000000000#include "config.h" #include #include #include "download/available_list.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" namespace torrent { AvailableList::value_type AvailableList::pop_random() { if (empty()) throw internal_error("AvailableList::pop_random() called on an empty container"); size_type idx = random() % size(); auto tmp = std::move(*(begin() + idx)); *(begin() + idx) = std::move(back()); pop_back(); return tmp; } void AvailableList::insert(AddressList* source_list) { if (!want_more()) return; source_list->sort(); std::sort(begin(), end(), [](auto& a, auto& b) { return sa_less(&a.sa, &b.sa); }); // Can we use the std::remove* semantics for this, and just copy to 'l'?. // // 'l' is guaranteed to be sorted, so we can just do std::set_difference. AddressList difference; std::set_difference(source_list->begin(), source_list->end(), begin(), end(), std::back_inserter(difference), [](auto& a, auto& b) { return sa_less(&a.sa, &b.sa); }); base_type::insert(end(), difference.begin(), difference.end()); } bool AvailableList::insert_unique(const sockaddr* sa) { if (std::find_if(begin(), end(), [sa](auto& a) { return sa_equal(&a.sa, sa); }) != end()) return false; base_type::push_back(sa_inet_union_from_sa(sa)); return true; } } // namespace torrent libtorrent-0.16.17/src/download/available_list.h000066400000000000000000000024761522271512000216020ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_AVAILABLE_LIST_H #define LIBTORRENT_DOWNLOAD_AVAILABLE_LIST_H #include #include "net/address_list.h" #include "torrent/net/types.h" namespace torrent { // TODO: Always keep sorted? Also, do we use address list buffer? class AvailableList : private std::vector { public: using base_type = std::vector; using base_type::size; using base_type::capacity; using base_type::reserve; using base_type::empty; using base_type::begin; using base_type::end; value_type pop_random(); // Fuzzy size limit. size_type max_size() const { return m_maxSize; } void set_max_size(size_type s) { m_maxSize = s; } bool want_more() const { return size() <= m_maxSize; } void insert(AddressList* source_list); bool insert_unique(const sockaddr* sa); void erase(iterator itr) { *itr = std::move(back()); pop_back(); } // A place to temporarily put addresses before re-adding them to the // AvailableList. AddressList* buffer() { return &m_buffer; } private: size_type m_maxSize{1000}; AddressList m_buffer; }; } // namespace torrent #endif libtorrent-0.16.17/src/download/chunk_selector.cc000066400000000000000000000166461522271512000220010ustar00rootroot00000000000000#include "config.h" #include "download/chunk_selector.h" #include #include #include "download/chunk_statistics.h" #include "protocol/peer_chunks.h" #include "torrent/exceptions.h" namespace torrent { // Consider making statistics a part of selector. void ChunkSelector::initialize(ChunkStatistics* cs) { m_position = invalid_chunk; m_statistics = cs; Bitfield* completed = m_data->mutable_completed_bitfield(); Bitfield* untouched = m_data->mutable_untouched_bitfield(); untouched->set_size_bits(completed->size_bits()); untouched->allocate(); std::transform(completed->begin(), completed->end(), untouched->begin(), [](const Bitfield::value_type& v) { return ~v; }); untouched->update(); m_sharedQueue.enable(32); m_sharedQueue.clear(); } void ChunkSelector::cleanup() { m_data->mutable_untouched_bitfield()->clear(); m_statistics = NULL; } // Consider if ChunksSelector::not_using_index(...) needs to be // modified. void ChunkSelector::update_priorities() { if (empty()) return; m_sharedQueue.clear(); if (m_position == invalid_chunk) m_position = random() % size(); advance_position(); } uint32_t ChunkSelector::find(PeerChunks* pc, [[maybe_unused]] bool highPriority) { // This needs to be re-enabled. if (m_position == invalid_chunk) return invalid_chunk; // When we're a seeder, 'm_sharedQueue' is used. Since the peer's // bitfield is guaranteed to be filled we can use the same code as // for non-seeders. This generalization does incur a slight // performance hit as it compares against a bitfield we know has all // set. auto* queue = pc->is_seeder() ? &m_sharedQueue : pc->download_cache(); // Randomize position on average every 16 chunks to prevent // inefficient distribution with a slow seed and fast peers // all arriving at the same position. if ((random() & 63) == 0) { m_position = random() % size(); queue->clear(); } if (queue->is_enabled()) { // First check the cached queue. while (queue->prepare_pop()) { uint32_t pos = queue->pop(); if (!m_data->untouched_bitfield()->get(pos)) continue; return pos; } } else { // Only non-seeders can reach this branch as m_sharedQueue is // always enabled. queue->enable(8); } queue->clear(); (search_linear(pc->bitfield(), queue, m_data->high_priority(), m_position, size()) && search_linear(pc->bitfield(), queue, m_data->high_priority(), 0, m_position)); if (queue->prepare_pop()) { // Set that the peer has high priority pieces cached. } else { // Set that the peer has normal priority pieces cached. // Urgh... queue->clear(); (search_linear(pc->bitfield(), queue, m_data->normal_priority(), m_position, size()) && search_linear(pc->bitfield(), queue, m_data->normal_priority(), 0, m_position)); if (!queue->prepare_pop()) return invalid_chunk; } uint32_t pos = queue->pop(); if (!m_data->untouched_bitfield()->get(pos)) throw internal_error("ChunkSelector::find(...) bad index."); return pos; } bool ChunkSelector::is_wanted(uint32_t index) const { return m_data->untouched_bitfield()->get(index) && (m_data->normal_priority()->has(index) || m_data->high_priority()->has(index)); } void ChunkSelector::using_index(uint32_t index) { if (index >= size()) throw internal_error("ChunkSelector::select_index(...) index out of range."); if (!m_data->untouched_bitfield()->get(index)) throw internal_error("ChunkSelector::select_index(...) index already set."); m_data->mutable_untouched_bitfield()->unset(index); // We always know 'm_position' points to a wanted chunk. If it // changes, we need to move m_position to the next one. if (index == m_position) advance_position(); } void ChunkSelector::not_using_index(uint32_t index) { if (index >= size()) throw internal_error("ChunkSelector::deselect_index(...) index out of range."); if (m_data->untouched_bitfield()->get(index)) throw internal_error("ChunkSelector::deselect_index(...) index already unset."); m_data->mutable_untouched_bitfield()->set(index); // This will make sure that if we enable new chunks, it will start // downloading them event when 'index == invalid_chunk'. if (m_position == invalid_chunk) m_position = index; } // This could propably be split into two functions, one for checking // if it shoul insert into the request_list(), and the other // whetever we are interested in the new piece. // // Since this gets called whenever a new piece arrives, we can skip // the rarity-first/linear etc searches through bitfields and just // start downloading. bool ChunkSelector::received_have_chunk(PeerChunks* pc, uint32_t index) { if (!m_data->untouched_bitfield()->get(index)) return false; // Also check if the peer only has high-priority chunks. if (!m_data->high_priority()->has(index) && !m_data->normal_priority()->has(index)) return false; if (pc->download_cache()->is_enabled()) pc->download_cache()->insert(m_statistics->rarity(index), index); return true; } bool ChunkSelector::search_linear(const Bitfield* bf, utils::PartialQueue* pq, const download_data::priority_ranges* ranges, uint32_t first, uint32_t last) { auto itr = ranges->find(first); while (itr != ranges->end() && itr->first < last) { if (!search_linear_range(bf, pq, std::max(first, itr->first), std::min(last, itr->second))) return false; ++itr; } return true; } // Could propably add another argument for max seen or something, this // would be used to find better chunks to request. inline bool ChunkSelector::search_linear_range(const Bitfield* bf, utils::PartialQueue* pq, uint32_t first, uint32_t last) { if (first >= last || last > size()) throw internal_error("ChunkSelector::search_linear_range(...) received an invalid range."); auto local = m_data->untouched_bitfield()->begin() + first / 8; auto source = bf->begin() + first / 8; // Unset any bits before 'first'. Bitfield::value_type wanted = (*source & *local) & Bitfield::mask_from(first % 8); while (m_data->untouched_bitfield()->position(local + 1) < last) { if (wanted && !search_linear_byte(pq, m_data->untouched_bitfield()->position(local), wanted)) return false; wanted = (*++source & *++local); } // Unset any bits from 'last'. wanted &= Bitfield::mask_before(last - m_data->untouched_bitfield()->position(local)); if (wanted) return search_linear_byte(pq, m_data->untouched_bitfield()->position(local), wanted); else return true; } // Take pointer to partial_queue inline bool ChunkSelector::search_linear_byte(utils::PartialQueue* pq, uint32_t index, Bitfield::value_type wanted) { for (int i = 0; i < 8; ++i) { if (!(wanted & Bitfield::mask_at(i))) continue; if (!pq->insert(m_statistics->rarity(index + i), index + i) && pq->is_full()) return false; } return true; } void ChunkSelector::advance_position() { // Need to replace with a special-purpose function for finding the // next position. // int position = m_position; // ((m_position = search_linear(&m_bitfield, &m_highPriority, position, size())) == invalid_chunk && // (m_position = search_linear(&m_bitfield, &m_highPriority, 0, position)) == invalid_chunk && // (m_position = search_linear(&m_bitfield, &m_normalPriority, position, size())) == invalid_chunk && // (m_position = search_linear(&m_bitfield, &m_normalPriority, 0, position)) == invalid_chunk); } } // namespace torrent libtorrent-0.16.17/src/download/chunk_selector.h000066400000000000000000000063571522271512000216410ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_CHUNK_SELECTOR_H #define LIBTORRENT_DOWNLOAD_CHUNK_SELECTOR_H #include #include "torrent/bitfield.h" #include "torrent/data/download_data.h" #include "torrent/utils/ranges.h" #include "utils/partial_queue.h" namespace torrent { // This class is responsible for deciding on which chunk index to // download next based on the peer's bitfield. It keeps its own // bitfield which starts out as a copy of Content::bitfield but sets // chunks that are being downloaded. // // When updating Content::bitfield, make sure you update this bitfield // and unmark any chunks in Delegator. class ChunkStatistics; class PeerChunks; class ChunkSelector { public: static constexpr auto invalid_chunk = ~uint32_t{0}; ChunkSelector(download_data* data) : m_data(data) {} bool empty() const { return size() == 0; } uint32_t size() const { return m_data->untouched_bitfield()->size_bits(); } // const Bitfield* bitfield() { return m_data->untouched_bitfield(); } // priority_ranges* high_priority() { return &m_highPriority; } // priority_ranges* normal_priority() { return &m_normalPriority; } // Initialize doesn't update the priority cache, so it is as if it // has empty priority ranges. void initialize(ChunkStatistics* cs); void cleanup(); // Call this once you've modified the bitfield or priorities to // update cached information. This must be called once before using // find. void update_priorities(); uint32_t find(PeerChunks* pc, bool highPriority); bool is_wanted(uint32_t index) const; // Call this to set the index as being downloaded, finished etc, // thus ignored. Propably should find a better name for this. void using_index(uint32_t index); void not_using_index(uint32_t index); // The caller must ensure that the chunk index is valid and has not // been set already. // // The user only needs to call this when it needs to know whetever // it should become interested, or if it is in the process of // downloading. // // Returns whetever we're interested in that piece. bool received_have_chunk(PeerChunks* pc, uint32_t index); private: bool search_linear(const Bitfield* bf, utils::PartialQueue* pq, const download_data::priority_ranges* ranges, uint32_t first, uint32_t last); inline bool search_linear_range(const Bitfield* bf, utils::PartialQueue* pq, uint32_t first, uint32_t last); inline bool search_linear_byte(utils::PartialQueue* pq, uint32_t index, Bitfield::value_type wanted); // inline uint32_t search_rarest(const Bitfield* bf, priority_ranges* ranges, uint32_t first, uint32_t last); // inline uint32_t search_rarest_range(const Bitfield* bf, uint32_t first, uint32_t last); // inline uint32_t search_rarest_byte(uint8_t wanted); void advance_position(); download_data* m_data; ChunkStatistics* m_statistics; utils::PartialQueue m_sharedQueue; uint32_t m_position; }; } // namespace torrent #endif // LIBTORRENT_DOWNLOAD_CHUNK_SELECTOR_H libtorrent-0.16.17/src/download/chunk_statistics.cc000066400000000000000000000115671522271512000223500ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include "torrent/exceptions.h" #include "protocol/peer_chunks.h" #include "chunk_statistics.h" #include namespace torrent { inline bool ChunkStatistics::should_add(PeerChunks* pc) const { return m_accounted < max_accounted; } void ChunkStatistics::initialize(size_type s) { if (!empty()) throw internal_error("ChunkStatistics::initialize(...) called on an initialized object."); base_type::resize(s); } void ChunkStatistics::clear() { if (m_complete != 0) throw internal_error("ChunkStatistics::clear() m_complete != 0."); base_type::clear(); } void ChunkStatistics::received_connect(PeerChunks* pc) { if (pc->using_counter()) throw internal_error("ChunkStatistics::received_connect(...) pc->using_counter() == true."); if (pc->bitfield()->is_all_set()) { pc->set_using_counter(true); m_complete++; } else if (!pc->bitfield()->is_all_unset() && should_add(pc)) { // There should be additional checks, so that we don't do this // when there's no need. pc->set_using_counter(true); m_accounted++; auto itr = base_type::begin(); // Use a bitfield iterator instead. for (Bitfield::size_type index = 0; index < pc->bitfield()->size_bits(); ++index, ++itr) *itr += pc->bitfield()->get(index); } } void ChunkStatistics::received_disconnect(PeerChunks* pc) { // The 'using_counter' of complete peers is used, but not added to // 'm_accounted', so that we can safely disconnect peers right after // receiving the bitfield without calling 'received_connect'. if (!pc->using_counter()) return; pc->set_using_counter(false); if (pc->bitfield()->is_all_set()) { m_complete--; } else { if (m_accounted == 0) throw internal_error("ChunkStatistics::received_disconnect(...) m_accounted == 0."); m_accounted--; auto itr = base_type::begin(); // Use a bitfield iterator instead. for (Bitfield::size_type index = 0; index < pc->bitfield()->size_bits(); ++index, ++itr) *itr -= pc->bitfield()->get(index); } } void ChunkStatistics::received_have_chunk(PeerChunks* pc, uint32_t index, uint32_t length) { // When the bitfield is empty, it is very cheap to add the peer to // the statistics. It needs to be done here else we need to check if // a connection has sent any messages, else it might send a bitfield. if (pc->bitfield()->is_all_unset() && should_add(pc)) { if (pc->using_counter()) throw internal_error("ChunkStatistics::received_have_chunk(...) pc->using_counter() == true."); pc->set_using_counter(true); m_accounted++; } pc->bitfield()->set(index); pc->peer_rate()->insert(length); if (pc->using_counter()) { base_type::operator[](index)++; // The below code should not cause useless work to be done in case // of immediate disconnect. if (pc->bitfield()->is_all_set()) { if (m_accounted == 0) throw internal_error("ChunkStatistics::received_disconnect(...) m_accounted == 0."); m_complete++; m_accounted--; std::transform(base_type::begin(), base_type::end(), base_type::begin(), [] (auto c) { return c - 1; }); } } else { if (pc->bitfield()->is_all_set()) { pc->set_using_counter(true); m_complete++; } } } } // namespace torrent libtorrent-0.16.17/src/download/chunk_statistics.h000066400000000000000000000102731522271512000222030ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_DOWNLOAD_CHUNK_STATISTICS_H #define LIBTORRENT_DOWNLOAD_CHUNK_STATISTICS_H #include #include namespace torrent { class PeerChunks; class ChunkStatistics : public std::vector { public: using base_type = std::vector; using size_type = uint32_t; using value_type = base_type::value_type; using reference = base_type::reference; using const_reference = base_type::const_reference; using iterator = base_type::iterator; using const_iterator = base_type::const_iterator; using reverse_iterator = base_type::reverse_iterator; using base_type::empty; using base_type::size; static constexpr size_type max_accounted = 255; ChunkStatistics() = default; ~ChunkStatistics() = default; ChunkStatistics(const ChunkStatistics&) = delete; ChunkStatistics& operator=(const ChunkStatistics&) = delete; size_type complete() const { return m_complete; } //size_type incomplete() const; // Number of non-complete peers whom's bitfield is added to the // statistics. size_type accounted() const { return m_accounted; } void initialize(size_type s); void clear(); // When a peer connects and sends a non-empty bitfield and is not a // seeder, we can be fairly sure it won't just disconnect // immediately. Thus it should be resonable to possibly spend the // effort adding it to the statistics if nessesary. // Where do we decide on policy? On whetever we count the chunks, // the type of connection shouldn't matter? As f.ex PCSeed will only // make sense when seeding, it won't be counted. // Might want to prefer to add peers we are interested in, but which // arn't in us. void received_connect(PeerChunks* pc); void received_disconnect(PeerChunks* pc); // The caller must ensure that the chunk index is valid and has not // been set already. void received_have_chunk(PeerChunks* pc, uint32_t index, uint32_t length); const_iterator begin() const { return base_type::begin(); } const_iterator end() const { return base_type::end(); } const_reference rarity(size_type n) const { return base_type::operator[](n); } const_reference operator [] (size_type n) const { return base_type::operator[](n); } private: inline bool should_add(PeerChunks* pc) const; size_type m_complete{}; size_type m_accounted{}; }; } // namespace torrent #endif libtorrent-0.16.17/src/download/delegator.cc000066400000000000000000000115601522271512000207250ustar00rootroot00000000000000#include "config.h" #include "torrent/bitfield.h" #include "torrent/data/block.h" #include "torrent/data/block_list.h" #include "torrent/data/block_transfer.h" #include "protocol/peer_chunks.h" #include "delegator.h" namespace torrent { std::vector Delegator::delegate(PeerChunks* peerChunks, uint32_t affinity, uint32_t maxPieces) { // TODO: Make sure we don't queue the same piece several time on the same peer when // it timeout cancels them. std::vector new_transfers; PeerInfo* peerInfo = peerChunks->peer_info(); // Find piece with same index as affinity. This affinity should ensure that we // never start another piece while the chunk this peer used to download is still // in progress. // TODO: What if the hash failed? Don't want data from that peer again. if (affinity >= 0) { for (BlockList* itr : m_transfers) { if (new_transfers.size() >= maxPieces) return new_transfers; if (affinity == itr->index()) delegate_from_blocklist(new_transfers, maxPieces, itr, peerInfo); } } // Prioritize full seeders if (peerChunks->is_seeder()) { for (BlockList* itr : m_transfers) { if (new_transfers.size() >= maxPieces) return new_transfers; if (itr->by_seeder()) delegate_from_blocklist(new_transfers, maxPieces, itr, peerInfo); } // Create new high priority pieces. delegate_new_chunks(new_transfers, maxPieces, peerChunks, true); // Create new normal priority pieces. delegate_new_chunks(new_transfers, maxPieces, peerChunks, false); } if (new_transfers.size() >= maxPieces) return new_transfers; // Find existing high priority pieces. for (BlockList* itr : m_transfers) { if (new_transfers.size() >= maxPieces) return new_transfers; if (itr->priority() == PRIORITY_HIGH && peerChunks->bitfield()->get(itr->index())) delegate_from_blocklist(new_transfers, maxPieces, itr, peerInfo); } // Create new high priority pieces. delegate_new_chunks(new_transfers, maxPieces, peerChunks, true); // Find existing normal priority pieces. for (BlockList* itr : m_transfers) { if (new_transfers.size() >= maxPieces) return new_transfers; if (itr->priority() == PRIORITY_NORMAL && peerChunks->bitfield()->get(itr->index())) delegate_from_blocklist(new_transfers, maxPieces, itr, peerInfo); } // Create new normal priority pieces. delegate_new_chunks(new_transfers, maxPieces, peerChunks, false); if (!m_aggressive) return new_transfers; // In aggressive mode, look for possible downloads that already have // one or more transfers queued. // No more than 4 per piece. uint16_t overlapped = 5; for (BlockList* itr : m_transfers) { if (new_transfers.size() >= maxPieces) return new_transfers; if (peerChunks->bitfield()->get(itr->index()) && itr->priority() != PRIORITY_OFF) { for (auto bl_itr = itr->begin(); bl_itr != itr->end() && overlapped != 0; bl_itr++) { if (new_transfers.size() >= maxPieces || bl_itr->size_not_stalled() >= overlapped) break; if (!bl_itr->is_finished()) { BlockTransfer* inserted_info = bl_itr->insert(peerInfo); if (inserted_info != NULL) { new_transfers.push_back(inserted_info); overlapped = bl_itr->size_not_stalled(); } } } } } return new_transfers; } void Delegator::delegate_new_chunks(std::vector &transfers, uint32_t maxPieces, PeerChunks* pc, bool highPriority) { // Find new chunks and if successful, add all possible pieces into `transfers` while (transfers.size() < maxPieces) { uint32_t index = m_slot_chunk_find(pc, highPriority); if (index == ~uint32_t{0}) return; auto itr = m_transfers.insert(Piece(index, 0, m_slot_chunk_size(index)), block_size); (*itr)->set_by_seeder(pc->is_seeder()); if (highPriority) (*itr)->set_priority(PRIORITY_HIGH); else (*itr)->set_priority(PRIORITY_NORMAL); delegate_from_blocklist(transfers, maxPieces, *itr, pc->peer_info()); } } void Delegator::delegate_from_blocklist(std::vector &transfers, uint32_t maxPieces, BlockList* c, PeerInfo* peerInfo) { for (auto i = c->begin(); i != c->end() && transfers.size() < maxPieces; ++i) { // If not finished and stalled, and no one is downloading this, then assign if (!i->is_finished() && i->is_stalled() && i->size_all() == 0) transfers.push_back(i->insert(peerInfo)); } if (transfers.size() >= maxPieces) return; // Fill any remaining slots with potentially stalled pieces. for (auto i = c->begin(); i != c->end() && transfers.size() < maxPieces; ++i) { if (!i->is_finished() && i->is_stalled()) { BlockTransfer* inserted_info = i->insert(peerInfo); if (inserted_info != NULL) transfers.push_back(inserted_info); } } } } // namespace torrent libtorrent-0.16.17/src/download/delegator.h000066400000000000000000000033631522271512000205710ustar00rootroot00000000000000#ifndef LIBTORRENT_DELEGATOR_H #define LIBTORRENT_DELEGATOR_H #include #include #include #include "torrent/data/transfer_list.h" namespace torrent { class Block; class BlockList; class BlockTransfer; class Piece; class PeerChunks; class PeerInfo; class Delegator { public: using slot_peer_chunk = std::function; using slot_size = std::function; static constexpr unsigned int block_size = 1 << 14; TransferList* transfer_list() { return &m_transfers; } const TransferList* transfer_list() const { return &m_transfers; } std::vector delegate(PeerChunks* peerChunks, uint32_t affinity, uint32_t maxPieces); bool get_aggressive() const { return m_aggressive; } void set_aggressive(bool a) { m_aggressive = a; } slot_peer_chunk& slot_chunk_find() { return m_slot_chunk_find; } slot_size& slot_chunk_size() { return m_slot_chunk_size; } private: static void delegate_from_blocklist(std::vector &transfers, uint32_t maxPieces, BlockList* c, PeerInfo* peerInfo); void delegate_new_chunks(std::vector &transfers, uint32_t maxPieces, PeerChunks* pc, bool highPriority); Block* delegate_seeder(PeerChunks* peerChunks); TransferList m_transfers; bool m_aggressive{false}; // Propably should add a m_slotChunkStart thing, which will take // care of enabling etc, and will be possible to listen to. slot_peer_chunk m_slot_chunk_find; slot_size m_slot_chunk_size; }; } // namespace torrent #endif libtorrent-0.16.17/src/download/download_constructor.cc000066400000000000000000000253041522271512000232340ustar00rootroot00000000000000#include "config.h" #include "download/download_constructor.h" #include #include #include #include "manager.h" #include "download/download_wrapper.h" #include "torrent/download_info.h" #include "torrent/exceptions.h" #include "torrent/data/file.h" #include "torrent/data/file_list.h" #include "torrent/utils/string_manip.h" #include "tracker/tracker_list.h" namespace torrent { void DownloadConstructor::initialize(Object& b) { if (!b.has_key_map("info") && b.has_key_string("magnet-uri")) parse_magnet_uri(b, b.get_key_string("magnet-uri")); if (b.has_key_value("creation date")) m_download->info()->set_creation_date(b.get_key_value("creation date")); if (b.get_key("info").has_key_value("private") && b.get_key("info").get_key_value("private") == 1) m_download->info()->set_private(); parse_name(b.get_key("info")); parse_info(b.get_key("info")); } void DownloadConstructor::parse_name(const Object& b) { if (is_invalid_path_element(b.get_key("name"))) throw input_error("Bad torrent file, \"name\" is an invalid path name."); auto& name = b.get_key_string("name"); if (name.empty()) throw internal_error("DownloadConstructor::parse_name(...) Ended up with an empty Path."); m_download->info()->set_name(name); } void DownloadConstructor::parse_info(const Object& b) { FileList* fileList = m_download->main()->file_list(); if (!fileList->empty()) throw internal_error("parse_info received an already initialized Content object."); if (b.flags() & Object::flag_unordered) throw input_error("Download has unordered info dictionary."); uint32_t chunkSize; if (b.has_key_value("meta_download") && b.get_key_value("meta_download")) m_download->info()->set_flags(DownloadInfo::flag_meta_download); if (m_download->info()->is_meta_download()) { if (b.get_key_string("pieces").length() != HashString::size_data) throw input_error("Meta-download has invalid piece data."); chunkSize = 1; parse_single_file(b, chunkSize); } else { chunkSize = b.get_key_value("piece length"); if (chunkSize <= (1 << 10) || chunkSize > (512 << 20)) throw input_error("Torrent has an invalid \"piece length\"."); } if (b.has_key("length")) { parse_single_file(b, chunkSize); } else if (b.has_key("files")) { parse_multi_files(b.get_key("files"), chunkSize); fileList->set_root_dir("./" + m_download->info()->name().str()); } else if (!m_download->info()->is_meta_download()) { throw input_error("Torrent must have either length or files entry."); } if (fileList->size_bytes() == 0 && !m_download->info()->is_meta_download()) throw input_error("Torrent has zero length."); // Set chunksize before adding files to make sure the index range is // correct. m_download->set_complete_hash(b.get_key_string("pieces")); if (m_download->complete_hash().size() / 20 < fileList->size_chunks()) throw bencode_error("Torrent size and 'info:pieces' length does not match."); } void DownloadConstructor::parse_tracker(const Object& b) { const Object::list_type* announce_list = NULL; if (b.has_key_list("announce-list") && // Some torrent makers create empty/invalid 'announce-list' // entries while still having valid 'announce'. !(announce_list = &b.get_key_list("announce-list"))->empty() && std::any_of(announce_list->begin(), announce_list->end(), std::mem_fn(&Object::is_list))) { for (const auto& group : *announce_list) { add_tracker_group(group); } } else if (b.has_key("announce")) { add_tracker_single(b.get_key("announce"), 0); } if (!m_download->info()->is_private()) m_download->main()->tracker_list()->insert_url(m_download->main()->tracker_list()->size_group(), "dht://"); // Deprecating DHT nodes key in torrent files as this is no longer needed. // if (b.has_key_list("nodes")) { // for (const auto& node : b.get_key_list("nodes")) // add_dht_node(node); // } m_download->main()->tracker_list()->randomize_group_entries(); } void DownloadConstructor::add_tracker_group(const Object& b) { if (!b.is_list()) throw bencode_error("Tracker group list not a list"); auto group = m_download->main()->tracker_list()->size_group(); for (const auto& tracker : b.as_list()) { add_tracker_single(tracker, group); } } void DownloadConstructor::add_tracker_single(const Object& b, int group) { if (!b.is_string()) throw bencode_error("Tracker entry not a string"); m_download->main()->tracker_list()->insert_url(group, utils::trim_spaces_str(b.as_string())); } bool DownloadConstructor::is_valid_path_element(const Object& b) { return b.is_string() && b.as_string() != "." && b.as_string() != ".." && std::find(b.as_string().begin(), b.as_string().end(), '/') == b.as_string().end() && std::find(b.as_string().begin(), b.as_string().end(), '\0') == b.as_string().end(); } void DownloadConstructor::parse_single_file(const Object& b, uint32_t chunkSize) { if (is_invalid_path_element(b.get_key("name"))) throw input_error("Bad torrent file, \"name\" is an invalid path name."); FileList* fileList = m_download->main()->file_list(); fileList->initialize(chunkSize == 1 ? 1 : b.get_key_value("length"), chunkSize); fileList->set_multi_file(false); Path path; path.push_back(b.get_key_string("name")); if (path.empty()) throw input_error("Bad torrent file, an entry has no valid filename."); *fileList->front()->mutable_path() = path; fileList->update_paths(fileList->begin(), fileList->end()); } void DownloadConstructor::parse_multi_files(const Object& b, uint32_t chunk_size) { const Object::list_type& object_list = b.as_list(); // Multi file torrent if (object_list.empty()) throw input_error("Bad torrent file, entry has no files."); int64_t torrent_size = 0; std::vector split_list; split_list.reserve(object_list.size()); for (const auto& object : object_list) { Path path; if (object.has_key_list("path")) path = create_path(object.get_key_list("path")); if (path.empty()) throw input_error("Bad torrent file, an entry has no valid filename."); int64_t length = object.get_key_value("length"); if (length < 0 || torrent_size + length < 0) throw input_error("Bad torrent file, invalid length for file."); torrent_size += length; int attr_flags = 0; if (object.has_key_string("attr")) { if (object.get_key_string("attr").find('p') != std::string::npos) attr_flags |= File::flag_attr_padding; } split_list.emplace_back(length, path, attr_flags); } FileList* file_list = m_download->main()->file_list(); file_list->set_multi_file(true); file_list->initialize(torrent_size, chunk_size); file_list->split(file_list->begin(), &*split_list.begin(), &*split_list.end()); file_list->update_paths(file_list->begin(), file_list->end()); } inline Path DownloadConstructor::create_path(const Object::list_type& plist) { // Make sure we are given a proper file path. if (plist.empty()) throw input_error("Bad torrent file, \"path\" has zero entries."); if (std::any_of(plist.begin(), plist.end(), &DownloadConstructor::is_invalid_path_element)) throw input_error("Bad torrent file, \"path\" has zero entries or a zero length entry."); Path p; for (const auto& path : plist) p.push_back(path.as_string()); return p; } static const char* parse_base32_sha1(const char* pos, HashString& hash) { HashString::iterator hashItr = hash.begin(); static constexpr int base_shift = 8+8-5; int shift = base_shift; uint16_t decoded = 0; while (*pos) { char c = *pos++; uint16_t value; if (c >= 'A' && c <= 'Z') value = c - 'A'; else if (c >= 'a' && c <= 'z') value = c - 'a'; else if (c >= '2' && c <= '7') value = 26 + c - '2'; else if (c == '&') break; else return NULL; decoded |= (value << shift); if (shift <= 8) { // Too many characters for a base32 SHA1. if (hashItr == hash.end()) return NULL; *hashItr++ = (decoded >> 8); decoded <<= 8; shift += 3; } else { shift -= 5; } } return hashItr != hash.end() || shift != base_shift ? NULL : pos; } void DownloadConstructor::parse_magnet_uri(Object& b, const std::string& uri) { if (std::strncmp(uri.c_str(), "magnet:?", 8)) throw input_error("Invalid magnet URI."); const char* pos = uri.c_str() + 8; Object trackers(Object::create_list()); HashString hash; bool hashValid = false; while (*pos) { const char* tagStart = pos; while (*pos != '=') { if (!*pos++) break; } raw_string tag(tagStart, pos - tagStart); pos++; // hash may be base32 encoded (optional in BEP 0009 and common practice) if (raw_bencode_equal_c_str(tag, "xt")) { if (strncmp(pos, "urn:btih:", 9)) throw input_error("Invalid magnet URI."); pos += 9; const char* nextPos = parse_base32_sha1(pos, hash); if (nextPos != NULL) { pos = nextPos; hashValid = true; continue; } } // everything else, including sometimes the hash, is url encoded. std::string decoded; while (*pos) { char c = *pos++; if (c == '%') { if (sscanf(pos, "%02hhx", &c) != 1) throw input_error("Invalid magnet URI."); pos += 2; } else if (c == '&') { break; } decoded.push_back(c); } if (raw_bencode_equal_c_str(tag, "xt")) { if (decoded.length() == torrent::HashString::size_data) { // url-encoded hash as per magnet URN specs hash = *HashString::cast_from(decoded); hashValid = true; } else if (decoded.length() == torrent::HashString::size_data * 2) { // hex-encoded hash as per BEP 0009 if (utils::transform_from_hex(decoded, hash) != hash.end()) throw input_error("Invalid magnet URI."); hashValid = true; } else { throw input_error("Invalid magnet URI."); } } else if (raw_bencode_equal_c_str(tag, "tr")) { trackers.insert_back(Object::create_list()).insert_back(decoded); } // could also handle "dn" = display name (torrent name), but we can't really use that } if (!hashValid) throw input_error("Invalid magnet URI."); Object& info = b.insert_key("info", Object::create_map()); info.insert_key("pieces", hash.str()); info.insert_key("name", utils::transform_to_hex_str(hash) + ".meta"); info.insert_key("meta_download", static_cast(1)); if (!trackers.as_list().empty()) { b.insert_preserve_copy("announce", trackers.as_list().begin()->as_list().begin()->as_string()); b.insert_preserve_type("announce-list", trackers); } } } // namespace torrent libtorrent-0.16.17/src/download/download_constructor.h000066400000000000000000000023471522271512000231000ustar00rootroot00000000000000#ifndef LIBTORRENT_PARSE_DOWNLOAD_CONSTRUCTOR_H #define LIBTORRENT_PARSE_DOWNLOAD_CONSTRUCTOR_H #include #include "torrent/object.h" namespace torrent { class Content; class DownloadWrapper; class Path; class DownloadConstructor { public: void initialize(Object& b); void parse_tracker(const Object& b); void set_download(DownloadWrapper* d) { m_download = d; } private: void parse_name(const Object& b); void parse_info(const Object& b); static void parse_magnet_uri(Object& b, const std::string& uri); void add_tracker_group(const Object& b); void add_tracker_single(const Object& b, int group); static bool is_valid_path_element(const Object& b); static bool is_invalid_path_element(const Object& b) { return !is_valid_path_element(b); } void parse_single_file(const Object& b, uint32_t chunkSize); void parse_multi_files(const Object& b, uint32_t chunkSize); static Path create_path(const Object::list_type& plist); DownloadWrapper* m_download{}; }; } // namespace torrent #endif // LIBTORRENT_PARSE_DOWNLOAD_CONSTRUCTOR_H libtorrent-0.16.17/src/download/download_main.cc000066400000000000000000000416401522271512000215740ustar00rootroot00000000000000#include "config.h" #include "download/download_main.h" #include #include #include "manager.h" #include "data/chunk_list.h" #include "download/available_list.h" #include "download/chunk_selector.h" #include "download/chunk_statistics.h" #include "download/download_wrapper.h" #include "protocol/extensions.h" #include "protocol/handshake_manager.h" #include "protocol/initial_seed.h" #include "protocol/peer_connection_base.h" #include "protocol/peer_factory.h" #include "torrent/download.h" #include "torrent/exceptions.h" #include "torrent/throttle.h" #include "torrent/data/file_list.h" #include "torrent/download/choke_queue.h" #include "torrent/download/download_manager.h" #include "torrent/download_info.h" #include "torrent/peer/connection_list.h" #include "torrent/peer/peer.h" #include "torrent/peer/peer_info.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/socket_manager.h" #include "torrent/tracker/manager.h" #include "torrent/utils/log.h" #include "tracker/thread_tracker.h" #include "tracker/tracker_controller.h" #include "tracker/tracker_list.h" #define LT_LOG_THIS(log_level, log_fmt, ...) \ lt_log_print_info(LOG_TORRENT_##log_level, m_ptr->info(), "download", log_fmt, __VA_ARGS__); namespace torrent { DownloadMain::DownloadMain() : m_info(new DownloadInfo), m_tracker_list(new TrackerList), m_chunkList(new ChunkList), m_chunkSelector(new ChunkSelector(file_list()->mutable_data())), m_chunkStatistics(new ChunkStatistics), m_connectionList(new ConnectionList(this)) { m_info->set_load_date(utils::cast_seconds(utils::time_since_epoch()).count()); // Only set trivial values here, the rest is done in DownloadWrapper. m_delegator.slot_chunk_find() = [this](auto pc, auto prio) { return m_chunkSelector->find(pc, prio); }; m_delegator.slot_chunk_size() = [this](auto i) { return file_list()->chunk_index_size(i); }; m_delegator.transfer_list()->slot_canceled() = [this](auto i) { m_chunkSelector->not_using_index(i); }; m_delegator.transfer_list()->slot_queued() = [this](auto i) { m_chunkSelector->using_index(i); }; m_delegator.transfer_list()->slot_completed() = [this](auto i) { receive_chunk_done(i); }; m_delegator.transfer_list()->slot_corrupt() = [this](auto i) { receive_corrupt_chunk(i); }; m_delay_disconnect_peers.slot() = [this] { m_connectionList->disconnect_queued(); }; m_task_tracker_request.slot() = [this] { receive_tracker_request(); }; m_chunkList->set_data(file_list()->mutable_data()); m_chunkList->slot_create_chunk() = [this](uint32_t index, int prot) { return file_list()->create_chunk_index(index, prot); }; m_chunkList->slot_create_hashing_chunk() = [this](uint32_t index, int prot) { return file_list()->create_hashing_chunk_index(index, prot); }; m_chunkList->slot_free_diskspace() = [this](FileList::cache_list& cache) { return file_list()->free_diskspace(cache); }; } DownloadMain::~DownloadMain() { assert(!m_task_tracker_request.is_scheduled() && "DownloadMain::~DownloadMain(): m_task_tracker_request is scheduled."); assert(m_info->size_pex() == 0 && "DownloadMain::~DownloadMain(): m_info->size_pex() != 0."); delete m_tracker_list; delete m_connectionList; delete m_chunkStatistics; delete m_chunkList; delete m_chunkSelector; delete m_info; m_ut_pex_delta.clear(); m_ut_pex_initial.clear(); } void DownloadMain::post_initialize() { auto tc = std::make_shared(m_tracker_list); m_tracker_list->slot_success() = [tc](const auto& t, auto al) { return tc->receive_success(t, al); }; m_tracker_list->slot_failure() = [tc](const auto& t, const auto& str) { tc->receive_failure(t, str); }; m_tracker_list->slot_scrape_success() = [tc](const auto& t) { tc->receive_scrape(t); }; m_tracker_list->slot_new_peers() = [tc](auto al) { return tc->receive_new_peers(al); }; m_tracker_list->slot_tracker_enabled() = [tc](const auto& t) { tc->receive_tracker_enabled(t); }; m_tracker_list->slot_tracker_disabled() = [tc](const auto& t) { tc->receive_tracker_disabled(t); }; m_tracker_controller = ThreadTracker::thread_tracker()->tracker_manager()->add_controller(info(), std::move(tc)); } void DownloadMain::open(int flags) { if (info()->is_open()) throw internal_error("Tried to open a download that is already open"); // TODO: Move file_list open calls to DownloadMain. file_list()->open(true, (flags & FileList::open_no_create)); m_chunkList->resize(file_list()->size_chunks()); m_chunkStatistics->initialize(file_list()->size_chunks()); info()->set_flags(DownloadInfo::flag_open); } void DownloadMain::close() { if (info()->is_active()) throw internal_error("Tried to close an active download"); if (!info()->is_open()) return; info()->unset_flags(DownloadInfo::flag_open); // Don't close the tracker manager here else it will cause STOPPED // requests to be lost. TODO: Check that this is valid. // m_trackerManager->close(); m_delegator.transfer_list()->clear(); file_list()->close(); // Clear the chunklist last as it requires all referenced chunks to // be released. m_chunkStatistics->clear(); m_chunkList->clear(); m_chunkSelector->cleanup(); } void DownloadMain::start(int flags) { if (!info()->is_open()) throw internal_error("Tried to start a closed download"); if (info()->is_active()) throw internal_error("Tried to start an active download"); // Close and clear open files to ensure hashing file/mmap advise is cleared. // // We should not need to clear chunks after hash checking the whole torrent as those chunk handle // references go to zero. file_list()->close_all_files(); // If the FileList::open_no_create flag was not set, our new // behavior is to create all zero-length files with // flag_queued_create set. file_list()->open(false, (flags & ~FileList::open_no_create)); info()->set_flags(DownloadInfo::flag_active); chunk_list()->set_flags(ChunkList::flag_active); m_delegator.set_aggressive(false); update_endgame(); receive_connect_peers(); } void DownloadMain::stop() { assert(std::this_thread::get_id() == main_thread::thread_id()); if (!info()->is_active()) return; // Set this early so functions like receive_connect_peers() knows // not to eat available peers. info()->unset_flags(DownloadInfo::flag_active); chunk_list()->unset_flags(ChunkList::flag_active); m_slot_stop_handshakes(this); connection_list()->erase_remaining(connection_list()->begin(), ConnectionList::disconnect_available); m_initial_seeding.reset(); this_thread::scheduler()->erase(&m_delay_disconnect_peers); this_thread::scheduler()->erase(&m_task_tracker_request); if (info()->upload_unchoked() != 0 || info()->download_unchoked() != 0) throw internal_error("DownloadMain::stop(): info()->upload_unchoked() != 0 || info()->download_unchoked() != 0."); } bool DownloadMain::start_initial_seeding() { if (!file_list()->is_done()) return false; m_initial_seeding = std::make_unique(this); return true; } void DownloadMain::initial_seeding_done(PeerConnectionBase* pcb) { if (m_initial_seeding == nullptr) throw internal_error("DownloadMain::initial_seeding_done called when not initial seeding."); // Close all connections but the currently active one (pcb), that // one will be closed by throw close_connection() later. // // When calling initial_seed()->new_peer(...) the 'pcb' won't be in // the connection list, so don't treat it as an error. Make sure to // catch close_connection() at the caller of new_peer(...) and just // close the filedesc before proceeding as normal. auto pcb_itr = std::find(m_connectionList->begin(), m_connectionList->end(), pcb); if (pcb_itr != m_connectionList->end()) { std::iter_swap(m_connectionList->begin(), pcb_itr); m_connectionList->erase_remaining(m_connectionList->begin() + 1, ConnectionList::disconnect_available); } else { m_connectionList->erase_remaining(m_connectionList->begin(), ConnectionList::disconnect_available); } // Switch to normal seeding. auto itr = manager->download_manager()->find(m_info); (*itr)->set_connection_type(Download::CONNECTION_SEED); m_connectionList->slot_new_connection(&createPeerConnectionSeed); m_initial_seeding.reset(); // And close the current connection. throw close_connection(); } bool DownloadMain::want_pex_msg() { return m_info->is_pex_active() && m_peerList.available_list()->want_more(); } void DownloadMain::update_endgame() { if (!m_delegator.get_aggressive() && file_list()->completed_chunks() + m_delegator.transfer_list()->size() + 5 >= file_list()->size_chunks()) m_delegator.set_aggressive(true); } void DownloadMain::receive_chunk_done(unsigned int index) { // TODO: Should we unmap the chunk here if we want sequential access? ChunkHandle handle = m_chunkList->get(index, ChunkList::get_hashing); if (!handle.is_valid()) throw storage_error("DownloadState::chunk_done(...) called with an index we couldn't retrieve from storage"); m_slot_hash_check_add(handle); } void DownloadMain::receive_corrupt_chunk(PeerInfo* peerInfo) { peerInfo->set_failed_counter(peerInfo->failed_counter() + 1); // Just use some very primitive heuristics here to decide if we're // going to disconnect the peer. Also, consider adding a flag so we // don't recalculate these things whenever the peer reconnects. // That is... non at all ;) if (peerInfo->failed_counter() > HandshakeManager::max_failed) connection_list()->erase(peerInfo, ConnectionList::disconnect_unwanted); } void DownloadMain::add_peer(const sockaddr* sa) { if (runtime::network_config()->is_block_outgoing()) return; m_slot_start_handshake(sa, this); } void DownloadMain::receive_connect_peers() { if (!info()->is_active()) return; // TODO: Is this actually going to be used? AddressList* alist = peer_list()->available_list()->buffer(); if (!alist->empty()) { alist->sort(); peer_list()->insert_available(alist); alist->clear(); } if (runtime::network_config()->is_block_outgoing()) return; // while (!peer_list()->available_list()->empty() && // manager->connection_manager()->can_connect() && // connection_list()->size() < connection_list()->min_size() && // connection_list()->size() + m_slot_count_handshakes(this) < connection_list()->max_size()) { while (!peer_list()->available_list()->empty()) { if (connection_list()->size() >= connection_list()->min_size()) break; if (connection_list()->size() + m_slot_count_handshakes(this) >= connection_list()->max_size()) break; if (!runtime::socket_manager()->can_open_socket(runtime::category_generic)) break; auto sa = peer_list()->available_list()->pop_random(); if (connection_list()->find(&sa.sa) == connection_list()->end()) m_slot_start_handshake(&sa.sa, this); } } void DownloadMain::receive_tracker_success() { assert(std::this_thread::get_id() == main_thread::thread_id()); if (!info()->is_active()) return; this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_tracker_request, 10s); } void DownloadMain::receive_tracker_request() { if ((info()->is_pex_enabled() && info()->size_pex()) > 0 || connection_list()->size() + peer_list()->available_list()->size() / 2 >= connection_list()->min_size()) { m_tracker_controller.stop_requesting(); return; } m_tracker_controller.start_requesting(); } static bool SocketAddressCompact_less(const SocketAddressCompact& a, const SocketAddressCompact& b) { return (a.addr < b.addr) || ((a.addr == b.addr) && (a.port < b.port)); } void DownloadMain::do_peer_exchange() { if (!info()->is_active()) throw internal_error("DownloadMain::do_peer_exchange called on inactive download."); // Check whether we should tell the peers to stop/start sending PEX // messages. int togglePex = 0; if (!m_info->is_pex_active() && m_connectionList->size() < m_connectionList->min_size() / 2 && m_peerList.available_list()->size() < m_peerList.available_list()->max_size() / 4) { m_info->set_flags(DownloadInfo::flag_pex_active); // Only set PEX_ENABLE if we don't have max_size_pex set to zero. if (m_info->size_pex() < m_info->max_size_pex()) togglePex = PeerConnectionBase::PEX_ENABLE; } else if (m_info->is_pex_active() && m_connectionList->size() >= m_connectionList->min_size()) { // m_peerList.available_list()->size() >= m_peerList.available_list()->max_size() / 2) { togglePex = PeerConnectionBase::PEX_DISABLE; m_info->unset_flags(DownloadInfo::flag_pex_active); } // Return if we don't really want to do anything? ProtocolExtension::PEXList current; for (auto& connection : *m_connectionList) { auto pcb = connection->m_ptr(); auto sa = pcb->peer_info()->socket_address(); if (pcb->peer_info()->listen_port() != 0 && sa->sa_family == AF_INET) { // The reinterpret_cast requires const in push_back, while not in emplace_back. // // Might be the cause of corrupt address values in rtorrent-docker tests. // current.emplace_back(reinterpret_cast(&sa)->sin_addr.s_addr, htons(pcb->peer_info()->listen_port())); current.push_back(SocketAddressCompact(reinterpret_cast(sa)->sin_addr.s_addr, htons(pcb->peer_info()->listen_port()))); } if (!pcb->extensions()->is_remote_supported(ProtocolExtension::UT_PEX)) continue; if (togglePex == PeerConnectionBase::PEX_ENABLE) { pcb->set_peer_exchange(true); if (m_info->size_pex() >= m_info->max_size_pex()) togglePex = 0; } else if (!pcb->extensions()->is_local_enabled(ProtocolExtension::UT_PEX)) { continue; } else if (togglePex == PeerConnectionBase::PEX_DISABLE) { pcb->set_peer_exchange(false); continue; } // Still using the old buffer? Make a copy in this rare case. DataBuffer* message = pcb->extension_message(); if (!message->empty() && (message->data() == m_ut_pex_initial.data() || message->data() == m_ut_pex_delta.data())) { auto buffer = new char[message->length()]; memcpy(buffer, message->data(), message->length()); message->set(buffer, buffer + message->length(), true); } pcb->do_peer_exchange(); } std::sort(current.begin(), current.end(), SocketAddressCompact_less); ProtocolExtension::PEXList added; added.reserve(current.size()); std::set_difference(current.begin(), current.end(), m_ut_pex_list.begin(), m_ut_pex_list.end(), std::back_inserter(added), SocketAddressCompact_less); ProtocolExtension::PEXList removed; removed.reserve(m_ut_pex_list.size()); std::set_difference(m_ut_pex_list.begin(), m_ut_pex_list.end(), current.begin(), current.end(), std::back_inserter(removed), SocketAddressCompact_less); if (current.size() > torrent::DownloadInfo::max_size_pex_list()) { // This test is only correct as long as we have a constant max // size. if (added.size() < current.size() - torrent::DownloadInfo::max_size_pex_list()) throw internal_error("DownloadMain::do_peer_exchange() added.size() < current.size() - m_info->max_size_pex_list()."); // Randomize this: added.erase(added.end() - (current.size() - torrent::DownloadInfo::max_size_pex_list()), added.end()); // Create the new m_ut_pex_list by removing any 'removed' // addresses from the original list and then adding the new // addresses. m_ut_pex_list.erase( std::set_difference( m_ut_pex_list.begin(), m_ut_pex_list.end(), removed.begin(), removed.end(), m_ut_pex_list.begin(), SocketAddressCompact_less), m_ut_pex_list.end()); m_ut_pex_list.insert(m_ut_pex_list.end(), added.begin(), added.end()); std::sort(m_ut_pex_list.begin(), m_ut_pex_list.end(), SocketAddressCompact_less); } else { m_ut_pex_list.swap(current); } current.clear(); m_ut_pex_delta.clear(); // If no peers were added or removed, the initial message is still correct and // the delta message stays emptied. Otherwise generate the appropriate messages. if (!added.empty() || !m_ut_pex_list.empty()) { m_ut_pex_delta = ProtocolExtension::generate_ut_pex_message(added, removed); m_ut_pex_initial.clear(); m_ut_pex_initial = ProtocolExtension::generate_ut_pex_message(m_ut_pex_list, current); } } void DownloadMain::set_metadata_size(size_t size) { if (m_info->is_meta_download()) { if(size == 0 || size > (1 << 26)) throw communication_error("Peer-supplied invalid metadata size."); if (m_fileList.size_bytes() < 2) file_list()->reset_filesize(size); else if (size != m_fileList.size_bytes()) throw communication_error("Peer-supplied metadata size mismatch."); } else if (m_info->metadata_size() && m_info->metadata_size() != size) { throw communication_error("Peer-supplied metadata size mismatch."); } m_info->set_metadata_size(size); } } // namespace torrent libtorrent-0.16.17/src/download/download_main.h000066400000000000000000000147671522271512000214500ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_MAIN_H #define LIBTORRENT_DOWNLOAD_MAIN_H #include #include #include "data/chunk_handle.h" #include "download/delegator.h" #include "net/address_list.h" #include "net/data_buffer.h" #include "torrent/data/file_list.h" #include "torrent/download/group_entry.h" #include "torrent/peer/peer_list.h" #include "torrent/tracker/wrappers.h" #include "torrent/utils/scheduler.h" namespace torrent { class ChunkList; class ChunkSelector; class ChunkStatistics; class choke_group; class ConnectionList; class DownloadWrapper; class HandshakeManager; class DownloadInfo; class ThrottleList; class InitialSeeding; class DownloadMain { public: using have_queue_type = std::deque>; using pex_list = std::vector; DownloadMain(); ~DownloadMain(); DownloadMain(const DownloadMain&) = delete; DownloadMain& operator=(const DownloadMain&) = delete; void post_initialize(); void open(int flags); void close(); void start(int flags); void stop(); class choke_group* choke_group() { return m_choke_group; } const class choke_group* c_choke_group() const { return m_choke_group; } void set_choke_group(class choke_group* grp) { m_choke_group = grp; } tracker::TrackerControllerWrapper tracker_controller() { return m_tracker_controller; } TrackerList* tracker_list() { return m_tracker_list; } DownloadInfo* info() { return m_info; } // Only retrieve writable chunks when the download is active. ChunkList* chunk_list() { return m_chunkList; } ChunkSelector* chunk_selector() { return m_chunkSelector; } ChunkStatistics* chunk_statistics() { return m_chunkStatistics; } Delegator* delegator() { return &m_delegator; } have_queue_type* have_queue() { return &m_haveQueue; } InitialSeeding* initial_seeding() { return m_initial_seeding.get(); } bool start_initial_seeding(); void initial_seeding_done(PeerConnectionBase* pcb); ConnectionList* connection_list() { return m_connectionList; } FileList* file_list() { return &m_fileList; } PeerList* peer_list() { return &m_peerList; } ThrottleList* upload_throttle() { return m_upload_throttle; } void set_upload_throttle(ThrottleList* t) { m_upload_throttle = t; } ThrottleList* download_throttle() { return m_download_throttle; } void set_download_throttle(ThrottleList* t) { m_download_throttle = t; } group_entry* up_group_entry() { return &m_up_group_entry; } group_entry* down_group_entry() { return &m_down_group_entry; } DataBuffer get_ut_pex(bool initial) { return (initial ? m_ut_pex_initial : m_ut_pex_delta).clone(); } bool want_pex_msg(); void set_metadata_size(size_t s); // Carefull with these. void setup_delegator(); void setup_tracker(); using slot_count_handshakes_type = std::function; using slot_hash_check_add_type = std::function; using slot_start_handshake_type = std::function; using slot_stop_handshakes_type = std::function; void slot_start_handshake(slot_start_handshake_type s) { m_slot_start_handshake = std::move(s); } void slot_stop_handshakes(slot_stop_handshakes_type s) { m_slot_stop_handshakes = std::move(s); } void slot_count_handshakes(slot_count_handshakes_type s) { m_slot_count_handshakes = std::move(s); } void slot_hash_check_add(slot_hash_check_add_type s) { m_slot_hash_check_add = std::move(s); } void add_peer(const sockaddr* sa); void receive_connect_peers(); void receive_chunk_done(unsigned int index); void receive_corrupt_chunk(PeerInfo* peerInfo); void receive_tracker_success(); void receive_tracker_request(); void receive_do_peer_exchange(); void do_peer_exchange(); void update_endgame(); auto& delay_download_done() { return m_delay_download_done; } auto& delay_partially_done() { return m_delay_partially_done; } auto& delay_partially_restarted() { return m_delay_partially_restarted; } auto& delay_disconnect_peers() { return m_delay_disconnect_peers; } private: void setup_start(); void setup_stop(); DownloadInfo* m_info; tracker::TrackerControllerWrapper m_tracker_controller; TrackerList* m_tracker_list; class choke_group* m_choke_group{}; group_entry m_up_group_entry; group_entry m_down_group_entry; ChunkList* m_chunkList; ChunkSelector* m_chunkSelector; ChunkStatistics* m_chunkStatistics; Delegator m_delegator; have_queue_type m_haveQueue; std::unique_ptr m_initial_seeding; ConnectionList* m_connectionList; FileList m_fileList; PeerList m_peerList; DataBuffer m_ut_pex_delta; DataBuffer m_ut_pex_initial; pex_list m_ut_pex_list; ThrottleList* m_upload_throttle{}; ThrottleList* m_download_throttle{}; slot_start_handshake_type m_slot_start_handshake; slot_stop_handshakes_type m_slot_stop_handshakes; slot_count_handshakes_type m_slot_count_handshakes; slot_hash_check_add_type m_slot_hash_check_add; utils::SchedulerEntry m_delay_download_done; utils::SchedulerEntry m_delay_partially_done; utils::SchedulerEntry m_delay_partially_restarted; utils::SchedulerEntry m_delay_disconnect_peers; utils::SchedulerEntry m_task_tracker_request; }; } // namespace torrent #endif libtorrent-0.16.17/src/download/download_wrapper.cc000066400000000000000000000310511522271512000223230ustar00rootroot00000000000000#include "config.h" #include "download/download_wrapper.h" #include "data/chunk_list.h" #include "data/hash_queue.h" #include "data/hash_torrent.h" #include "download/chunk_selector.h" #include "protocol/handshake_manager.h" #include "protocol/peer_connection_base.h" #include "torrent/data/file.h" #include "torrent/data/file_list.h" #include "torrent/data/file_manager.h" #include "torrent/download_info.h" #include "torrent/peer/connection_list.h" #include "torrent/peer/peer.h" #include "torrent/tracker/manager.h" #include "torrent/utils/log.h" #include "tracker/thread_tracker.h" #include "tracker/tracker_list.h" #include "utils/functional.h" #include "utils/sha1.h" #define LT_LOG_THIS(log_fmt, ...) \ lt_log_print_info(LOG_TORRENT_INFO, this->info(), "download", log_fmt, __VA_ARGS__); #define LT_LOG_STORAGE_ERRORS(log_fmt, ...) \ lt_log_print_info(LOG_PROTOCOL_STORAGE_ERRORS, this->info(), "storage_errors", log_fmt, __VA_ARGS__); namespace torrent { DownloadWrapper::DownloadWrapper() : m_main(new DownloadMain) { m_main->delay_download_done().slot() = [this] { data()->call_download_done(); }; m_main->delay_partially_done().slot() = [this] { data()->call_partially_done(); }; m_main->delay_partially_restarted().slot() = [this] { data()->call_partially_restarted(); }; m_main->peer_list()->set_info(info()); m_main->tracker_list()->set_info(info()); m_main->chunk_list()->slot_storage_error() = [this](const auto& str) { receive_storage_error(str); }; } DownloadWrapper::~DownloadWrapper() { if (info()->is_active()) m_main->stop(); if (info()->is_open()) close(); // Check if needed. m_main->connection_list()->clear(); m_main->tracker_list()->clear(); if (m_main->tracker_controller().is_valid()) { // If the client wants to do a quick cleanup after calling close, it // will need to manually cancel the tracker requests. m_main->tracker_controller().close(); tracker_thread::manager()->remove_controller(m_main->tracker_controller()); m_main->tracker_controller().get_shared().reset(); } } void DownloadWrapper::initialize(const std::string& hash, const std::string& id, uint32_t tracker_key) { char hashObfuscated[20]; sha1_salt("req2", 4, hash.c_str(), hash.length(), hashObfuscated); info()->mutable_hash().assign(hash.c_str()); info()->mutable_hash_obfuscated().assign(hashObfuscated); info()->mutable_local_id().assign(id.c_str()); info()->slot_left() = [this] { return m_main->file_list()->left_bytes(); }; info()->slot_completed() = [this] { return m_main->file_list()->completed_bytes(); }; file_list()->mutable_data()->mutable_hash().assign(hash.c_str()); m_main->tracker_list()->set_key(tracker_key); m_main->slot_hash_check_add([this](torrent::ChunkHandle handle) { return check_chunk_hash(handle, false); }); // Info hash must be calculate from here on. m_hash_checker = std::make_unique(m_main->chunk_list()); // Connect various signals and slots. m_hash_checker->slot_check_chunk() = [this](auto h) { check_chunk_hash(h, true); }; m_hash_checker->delay_checked().slot() = [this] { receive_initial_hash(); }; m_main->post_initialize(); m_main->tracker_controller().set_slots([this](auto l) { return receive_tracker_success(l); }, [this](auto& m) { return receive_tracker_failed(m); }); } void DownloadWrapper::close() { // Stop the hashing first as we need to make sure all chunks are // released when DownloadMain::close() is called. m_hash_checker->clear(); // Clear after m_hash_checker to ensure that the empty hash done signal does // not get passed to HashTorrent. hash_queue()->remove(data()); // This could/should be async as we do not care that much if it // succeeds or not, any chunks not included in that last // hash_resume_save get ignored anyway. m_main->chunk_list()->sync_chunks_no_cache(ChunkList::sync_all | ChunkList::sync_force | ChunkList::sync_sloppy | ChunkList::sync_ignore_error); m_main->close(); // Should this perhaps be in stop? this_thread::scheduler()->erase(&m_main->delay_download_done()); this_thread::scheduler()->erase(&m_main->delay_partially_done()); this_thread::scheduler()->erase(&m_main->delay_partially_restarted()); } void DownloadWrapper::receive_initial_hash() { if (info()->is_active()) throw internal_error("DownloadWrapper::receive_initial_hash() but we're in a bad state."); if (!m_hash_checker->is_checking()) { receive_storage_error("Hash checker was unable to map chunk: " + std::string(std::strerror(m_hash_checker->error_number()))); } else { m_hash_checker->confirm_checked(); if (hash_queue()->has(data())) throw internal_error("DownloadWrapper::receive_initial_hash() found a chunk in the HashQueue."); // Initialize the ChunkSelector here so that no chunks will be // marked by HashTorrent that are not accounted for. m_main->chunk_selector()->initialize(m_main->chunk_statistics()); receive_update_priorities(); } if (data()->slot_initial_hash()) data()->slot_initial_hash()(); } void DownloadWrapper::receive_hash_done(ChunkHandle handle, const char* hash) { if (!handle.is_valid()) throw internal_error("DownloadWrapper::receive_hash_done(...) called on an invalid chunk."); if (!info()->is_open()) throw internal_error("DownloadWrapper::receive_hash_done(...) called but the download is not open."); if (m_hash_checker->is_checking()) { if (hash == NULL) { m_hash_checker->receive_chunk_cleared(handle.index()); } else { if (std::memcmp(hash, chunk_hash(handle.index()), 20) == 0) m_main->file_list()->mark_completed(handle.index()); m_hash_checker->receive_chunkdone(handle.index()); } m_main->chunk_list()->release(&handle, ChunkList::release_dont_log); return; } // If hash == NULL we're clearing the queue, so do nothing. if (hash != NULL) { if (!m_hash_checker->is_checked()) throw internal_error("DownloadWrapper::receive_hash_done(...) Was not expecting non-NULL hash."); // Receiving chunk hashes after stopping the torrent should be // safe. if (data()->untouched_bitfield()->get(handle.index())) throw internal_error("DownloadWrapper::receive_hash_done(...) received a chunk that isn't set in ChunkSelector."); if (std::memcmp(hash, chunk_hash(handle.index()), 20) == 0) { bool was_partial = data()->wanted_chunks() != 0; m_main->file_list()->mark_completed(handle.index()); m_main->delegator()->transfer_list()->hash_succeeded(handle.index(), handle.chunk()); m_main->update_endgame(); if (m_main->file_list()->is_done()) { finished_download(); } else if (was_partial && data()->wanted_chunks() == 0) { this_thread::scheduler()->erase(&m_main->delay_partially_restarted()); this_thread::scheduler()->update_wait_for(&m_main->delay_partially_done(), 0us); } if (!m_main->have_queue()->empty() && m_main->have_queue()->front().first >= this_thread::cached_time()) m_main->have_queue()->emplace_front(m_main->have_queue()->front().first + 1us, handle.index()); else m_main->have_queue()->emplace_front(this_thread::cached_time(), handle.index()); } else { // This needs to ensure the chunk is still valid. m_main->delegator()->transfer_list()->hash_failed(handle.index(), handle.chunk()); } } data()->call_chunk_done(handle.object()); m_main->chunk_list()->release(&handle, ChunkList::release_default); } void DownloadWrapper::check_chunk_hash(ChunkHandle handle, bool hashing) { // TODO: Hack... auto flags = ChunkList::get_blocking; if (hashing) flags = flags | ChunkList::get_hashing; else flags = flags | ChunkList::get_not_hashing; ChunkHandle new_handle = m_main->chunk_list()->get(handle.index(), flags); m_main->chunk_list()->release(&handle, ChunkList::release_default); hash_queue()->push_back(new_handle, data(), [this](auto c, auto h) { receive_hash_done(c, h); }); } void DownloadWrapper::receive_storage_error(const std::string& str) { m_main->stop(); close(); m_main->tracker_controller().disable(); m_main->tracker_controller().close(); LT_LOG_STORAGE_ERRORS("%s", str.c_str()); } uint32_t DownloadWrapper::receive_tracker_success(AddressList* l) { uint32_t inserted = m_main->peer_list()->insert_available(l); m_main->receive_connect_peers(); m_main->receive_tracker_success(); ::utils::slot_list_call(info()->signal_tracker_success()); return inserted; } void DownloadWrapper::receive_tracker_failed(const std::string& msg) { ::utils::slot_list_call(info()->signal_tracker_failed(), msg); } void DownloadWrapper::receive_tick(uint32_t ticks) { // Trigger culling of PeerInfo's every hour. This should be called // before the is_open check to ensure that stopped torrents reduce // their memory usage. if (ticks % 120 == 0) // if (ticks % 1 == 0) m_main->peer_list()->cull_peers(PeerList::cull_old | PeerList::cull_keep_interesting); if (!info()->is_open()) return; // Every 2 minutes. if (ticks % 4 == 0) { if (info()->is_active()) { if (info()->is_pex_enabled()) { m_main->do_peer_exchange(); // If PEX was disabled since the last peer exchange, deactivate it now. } else if (info()->is_pex_active()) { info()->unset_flags(DownloadInfo::flag_pex_active); for (auto& connection : *m_main->connection_list()) connection->m_ptr()->set_peer_exchange(false); } } for (auto itr = m_main->connection_list()->begin(); itr != m_main->connection_list()->end();) if (!(*itr)->m_ptr()->receive_keepalive()) itr = m_main->connection_list()->erase(itr, ConnectionList::disconnect_available); else itr++; } auto have_queue = m_main->have_queue(); have_queue->erase(std::find_if(have_queue->rbegin(), have_queue->rend(), [](auto& p) { return this_thread::cached_time() - 600s < p.first; }).base(), have_queue->end()); m_main->receive_connect_peers(); } void DownloadWrapper::receive_update_priorities() { LT_LOG_THIS("update priorities: chunks_selected:%" PRIu32 " wanted_chunks:%" PRIu32, m_main->chunk_selector()->size(), data()->wanted_chunks()); data()->mutable_high_priority()->clear(); data()->mutable_normal_priority()->clear(); for (auto& file : *m_main->file_list()) { switch (file->priority()) { case PRIORITY_NORMAL: { File::range_type range = file->range(); if (file->has_flags(File::flag_prioritize_first) && range.first != range.second) { data()->mutable_high_priority()->insert(range.first, range.first + 1); range.first++; } if (file->has_flags(File::flag_prioritize_last) && range.first != range.second) { data()->mutable_high_priority()->insert(range.second - 1, range.second); range.second--; } data()->mutable_normal_priority()->insert(range); break; } case PRIORITY_HIGH: data()->mutable_high_priority()->insert(file->range().first, file->range().second); break; default: break; } } bool was_partial = data()->wanted_chunks() != 0; data()->update_wanted_chunks(); m_main->chunk_selector()->update_priorities(); for (const auto& peer : *m_main->connection_list()) { peer->m_ptr()->update_interested(); } // The 'partially_done/restarted' signal only gets triggered when a // download is active and not completed. if (info()->is_active() && !file_list()->is_done() && was_partial != (data()->wanted_chunks() != 0)) { this_thread::scheduler()->erase(&m_main->delay_partially_done()); this_thread::scheduler()->erase(&m_main->delay_partially_restarted()); if (was_partial) this_thread::scheduler()->wait_for(&m_main->delay_partially_done(), 0us); else this_thread::scheduler()->wait_for(&m_main->delay_partially_restarted(), 0us); } } void DownloadWrapper::finished_download() { // We delay emitting the signal to allow the delegator to // clean up. If we do a straight call it would cause problems // for clients that wish to close and reopen the torrent, as // HashQueue, Delegator etc shouldn't be cleaned up at this // point. // // This needs to be seperated into a new function. this_thread::scheduler()->update_wait_for(&m_main->delay_download_done(), 0us); m_main->connection_list()->erase_seeders(); info()->mutable_down_rate()->reset_rate(); } } // namespace torrent libtorrent-0.16.17/src/download/download_wrapper.h000066400000000000000000000060241522271512000221670ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_WRAPPER_H #define LIBTORRENT_DOWNLOAD_WRAPPER_H #include "data/chunk_handle.h" #include "download_main.h" namespace torrent { // Remember to clean up the pointers, DownloadWrapper won't do it. class AddressList; class FileManager; class HashQueue; class HashTorrent; class HandshakeManager; class DownloadInfo; class Object; class Peer; class DownloadWrapper { public: DownloadWrapper(); ~DownloadWrapper(); DownloadWrapper(const DownloadWrapper&) = delete; DownloadWrapper& operator=(const DownloadWrapper&) = delete; DownloadInfo* info() { return m_main->info(); } download_data* data() { return m_main->file_list()->mutable_data(); } FileList* file_list() { return m_main->file_list(); } ChunkList* chunk_list() { return m_main->chunk_list(); } // Initialize hash checker and various download stuff. void initialize(const std::string& hash, const std::string& id, uint32_t tracker_key); void close(); DownloadMain* main() { return m_main.get(); } const DownloadMain* main() const { return m_main.get(); } HashTorrent* hash_checker() { return m_hash_checker.get(); } Object* bencode() { return m_bencode.get(); } void set_bencode(Object* o) { m_bencode.reset(o); } HashQueue* hash_queue() { return m_hash_queue; } void set_hash_queue(HashQueue* q) { m_hash_queue = q; } const std::string& complete_hash() { return m_hash; } const char* chunk_hash(unsigned int index) { return m_hash.c_str() + 20 * index; } void set_complete_hash(const std::string& hash) { m_hash = hash; } int connection_type() const { return m_connectionType; } void set_connection_type(int t) { m_connectionType = t; } // // Internal: // void receive_initial_hash(); void receive_hash_done(ChunkHandle handle, const char* hash); void check_chunk_hash(ChunkHandle handle, bool hashing); void receive_storage_error(const std::string& str); uint32_t receive_tracker_success(AddressList* l); void receive_tracker_failed(const std::string& msg); void receive_tick(uint32_t ticks); void receive_update_priorities(); private: void finished_download(); std::unique_ptr m_main; std::unique_ptr m_bencode; std::unique_ptr m_hash_checker; HashQueue* m_hash_queue; std::string m_hash; int m_connectionType{0}; }; } // namespace torrent #endif libtorrent-0.16.17/src/manager.cc000066400000000000000000000106471522271512000165670ustar00rootroot00000000000000#include "config.h" #include "manager.h" #include "runtime.h" #include "data/chunk_list.h" #include "data/hash_torrent.h" #include "download/download_main.h" #include "download/download_wrapper.h" #include "net/listen.h" #include "protocol/handshake_manager.h" #include "torrent/chunk_manager.h" #include "torrent/throttle.h" #include "torrent/data/file_manager.h" #include "torrent/download/choke_group.h" #include "torrent/download/choke_queue.h" #include "torrent/download/download_manager.h" #include "torrent/download/resource_manager.h" #include "torrent/peer/client_list.h" #include "torrent/runtime/network_manager.h" #include "torrent/runtime/socket_manager.h" #include "utils/instrumentation.h" #include "utils/thread_internal.h" namespace torrent { Manager* manager = nullptr; Manager::Manager() : m_chunk_manager(new ChunkManager), m_download_manager(new DownloadManager), m_file_manager(new FileManager), m_handshake_manager(new HandshakeManager), m_resource_manager(new ResourceManager), m_client_list(new ClientList), m_uploadThrottle(Throttle::create_throttle()), m_downloadThrottle(Throttle::create_throttle()) { m_task_tick.slot() = [this] { receive_tick(); }; torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_tick, 1s); m_handshake_manager->slot_download_id() = [this](auto hash) { return m_download_manager->find_main(hash); }; m_handshake_manager->slot_download_obfuscated() = [this](auto hash) { return m_download_manager->find_main_obfuscated(hash); }; runtime::network_manager()->listen_inet_unsafe()->slot_accepted() = [this](auto& handshake, auto fd, auto sa) { return m_handshake_manager->add_incoming(handshake, fd, sa); }; runtime::network_manager()->listen_inet6_unsafe()->slot_accepted() = [this](auto& handshake, auto fd, auto sa) { return m_handshake_manager->add_incoming(handshake, fd, sa); }; m_resource_manager->push_group("default"); m_resource_manager->group_back()->up_queue()->set_heuristics(HEURISTICS_UPLOAD_LEECH); m_resource_manager->group_back()->down_queue()->set_heuristics(HEURISTICS_DOWNLOAD_LEECH); } Manager::~Manager() = default; void Manager::cleanup() { torrent::this_thread::scheduler()->erase(&m_task_tick); m_handshake_manager->clear(); m_download_manager->clear(); Throttle::destroy_throttle(m_uploadThrottle); Throttle::destroy_throttle(m_downloadThrottle); instrumentation_tick(); } void Manager::initialize_download(DownloadWrapper* d) { d->main()->slot_count_handshakes([this](DownloadMain* download) { return m_handshake_manager->size_info(download); }); d->main()->slot_start_handshake([this](const sockaddr* sa, DownloadMain* download) { return m_handshake_manager->add_outgoing(sa, download); }); d->main()->slot_stop_handshakes([this](DownloadMain* download) { return m_handshake_manager->erase_download(download); }); // TODO: The resource manager doesn't need to know about this // download until we start/stop the torrent. m_download_manager->insert(d); m_resource_manager->insert(d->main(), 1); m_chunk_manager->insert(d->main()->chunk_list()); d->main()->chunk_list()->set_chunk_size(d->main()->file_list()->chunk_size()); d->main()->set_upload_throttle(m_uploadThrottle->throttle_list()); d->main()->set_download_throttle(m_downloadThrottle->throttle_list()); } void Manager::cleanup_download(DownloadWrapper* d) { d->main()->stop(); d->close(); m_resource_manager->erase(d->main()); m_chunk_manager->erase(d->main()->chunk_list()); m_download_manager->erase(d); } void Manager::receive_tick() { m_ticks++; if (m_ticks % 2 == 0) instrumentation_tick(); m_resource_manager->receive_tick(); m_chunk_manager->periodic_sync(); // To ensure the downloads get equal chance over time at using // various limited resources, like sockets for handshakes, cycle the // group in reverse order. if (!m_download_manager->empty()) { auto split = m_download_manager->end() - m_ticks % m_download_manager->size() - 1; std::for_each(split, m_download_manager->end(), [this](auto wrapper) { return wrapper->receive_tick(m_ticks); }); std::for_each(m_download_manager->begin(), split, [this](auto wrapper) { return wrapper->receive_tick(m_ticks); }); } // If you change the interval, make sure the keepalives gets // triggered every 120 seconds. torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_tick, 30s); } } // namespace torrent libtorrent-0.16.17/src/manager.h000066400000000000000000000032371522271512000164260ustar00rootroot00000000000000#ifndef LIBTORRENT_MANAGER_H #define LIBTORRENT_MANAGER_H #include #include #include #include "torrent/common.h" #include "torrent/utils/scheduler.h" namespace torrent { class DownloadManager; class FileManager; class ResourceManager; class Manager { public: Manager(); ~Manager(); auto* chunk_manager() { return m_chunk_manager.get(); } auto* download_manager() { return m_download_manager.get(); } auto* file_manager() { return m_file_manager.get(); } auto* handshake_manager() { return m_handshake_manager.get(); } auto* resource_manager() { return m_resource_manager.get(); } auto* client_list() { return m_client_list.get(); } Throttle* upload_throttle() { return m_uploadThrottle; } Throttle* download_throttle() { return m_downloadThrottle; } void cleanup(); void initialize_download(DownloadWrapper* d); void cleanup_download(DownloadWrapper* d); void receive_tick(); private: std::unique_ptr m_chunk_manager; std::unique_ptr m_download_manager; std::unique_ptr m_file_manager; std::unique_ptr m_handshake_manager; std::unique_ptr m_resource_manager; std::unique_ptr m_client_list; Throttle* m_uploadThrottle; Throttle* m_downloadThrottle; unsigned int m_ticks{0}; utils::SchedulerEntry m_task_tick; }; extern Manager* manager; } // namespace torrent #endif libtorrent-0.16.17/src/net/000077500000000000000000000000001522271512000154245ustar00rootroot00000000000000libtorrent-0.16.17/src/net/address_list.cc000066400000000000000000000052371522271512000204220ustar00rootroot00000000000000#include "config.h" #include "address_list.h" #include #include #include "torrent/net/socket_address.h" namespace torrent { void AddressList::sort() { std::sort(begin(), end(), [](auto& a, auto& b) { return sa_less(&a.sa, &b.sa); }); } void AddressList::sort_and_unique() { sort(); erase(std::unique(begin(), end(), [](auto& a, auto& b) { return sa_equal(&a.sa, &b.sa); }), end()); } void AddressList::parse_address_normal(const Object::list_type& b) { for (const auto& obj : b) { if (!obj.is_map()) continue; if (!obj.has_key_string("ip")) continue; if (!obj.has_key_value("port")) continue; auto& addr = obj.get_key_string("ip"); auto port = obj.get_key_value("port"); if (port <= 0 || port >= (1 << 16)) continue; sa_inet_union sa{}; if (inet_pton(AF_INET, addr.c_str(), &sa.inet.sin_addr)) { if (sa.inet.sin_addr.s_addr == htonl(INADDR_ANY)) continue; sa.inet.sin_family = AF_INET; sa.inet.sin_port = htons(port); } else if (inet_pton(AF_INET6, addr.c_str(), &sa.inet6.sin6_addr)) { if (IN6_IS_ADDR_UNSPECIFIED(&sa.inet6.sin6_addr)) continue; sa.inet6.sin6_family = AF_INET6; sa.inet6.sin6_port = htons(port); } else { continue; } this->push_back(sa); } } void AddressList::parse_address_compact(raw_string s) { if (sizeof(const SocketAddressCompact) != 6) throw internal_error("ConnectionList::AddressList::parse_address_compact(...) bad struct size."); std::copy(reinterpret_cast(s.data()), reinterpret_cast(s.data() + s.size() - s.size() % sizeof(SocketAddressCompact)), std::back_inserter(*this)); } void AddressList::parse_address_compact_ipv6(const std::string& s) { if (sizeof(const SocketAddressCompact6) != 18) throw internal_error("ConnectionList::AddressList::parse_address_compact_ipv6(...) bad struct size."); std::copy(reinterpret_cast(s.c_str()), reinterpret_cast(s.c_str() + s.size() - s.size() % sizeof(SocketAddressCompact6)), std::back_inserter(*this)); } void AddressList::parse_address_bencode(raw_list s) { if (sizeof(const SocketAddressCompact) != 6) throw internal_error("AddressList::parse_address_bencode(...) bad struct size."); for (auto itr = s.begin(); itr + 2 + sizeof(SocketAddressCompact) <= s.end(); itr += sizeof(SocketAddressCompact)) { if (*itr++ != '6' || *itr++ != ':') break; insert(end(), *reinterpret_cast(itr)); } } } // namespace torrent libtorrent-0.16.17/src/net/address_list.h000066400000000000000000000040311522271512000202530ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_ADDRESS_LIST_H #define LIBTORRENT_DOWNLOAD_ADDRESS_LIST_H #include #include #include "torrent/object.h" #include "torrent/object_raw_bencode.h" #include "torrent/net/types.h" namespace torrent { class AddressList : public std::vector { public: void sort(); void sort_and_unique(); // Parse normal or compact list of addresses and add to AddressList void parse_address_normal(const Object::list_type& b); void parse_address_bencode(raw_list s); void parse_address_compact(raw_string s); void parse_address_compact(const std::string& s); void parse_address_compact_ipv6(const std::string& s); }; inline void AddressList::parse_address_compact(const std::string& s) { return parse_address_compact(raw_string(s.data(), s.size())); } // Move somewhere else. struct [[gnu::packed]] SocketAddressCompact { SocketAddressCompact() = default; SocketAddressCompact(uint32_t a, uint16_t p) : addr(a), port(p) {} SocketAddressCompact(const sockaddr_in* sin) : addr(sin->sin_addr.s_addr), port(sin->sin_port) {} operator sa_inet_union () const { sa_inet_union su{}; su.inet.sin_family = AF_INET; su.inet.sin_addr.s_addr = addr; su.inet.sin_port = port; return su; } uint32_t addr; uint16_t port; const char* c_str() const { return reinterpret_cast(this); } std::string str() const { return std::string(c_str(), sizeof(*this)); } }; struct [[gnu::packed]] SocketAddressCompact6 { SocketAddressCompact6() = default; SocketAddressCompact6(in6_addr a, uint16_t p) : addr(a), port(p) {} operator sa_inet_union () const { sa_inet_union su{}; su.inet6.sin6_family = AF_INET6; su.inet6.sin6_addr = addr; su.inet6.sin6_port = port; return su; } in6_addr addr; uint16_t port; const char* c_str() const { return reinterpret_cast(this); } }; } // namespace torrent #endif libtorrent-0.16.17/src/net/curl_get.cc000066400000000000000000000344231522271512000175450ustar00rootroot00000000000000#include "config.h" #include "net/curl_get.h" #include #include #include #include #include "net/curl_socket.h" #include "net/curl_stack.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/runtime/network_config.h" #include "torrent/system/callbacks.h" #include "torrent/system/thread.h" #include "torrent/utils/log.h" #include "torrent/utils/uri_parser.h" #include "utils/functional.h" #define LT_LOG(log_fmt, ...) \ lt_log_print(LOG_NET_HTTP, "%p->http_get : " log_fmt, this, __VA_ARGS__) #define lt_easy_setopt(handle, option, value) \ { \ CURLcode code = curl_easy_setopt(handle, option, value); \ if (code != CURLE_OK) \ throw torrent::internal_error("Error calling curl_easy_setopt(" #option "): " + std::string(curl_easy_strerror(code))); \ } namespace torrent::net { CurlGet::CurlGet(std::string url, std::shared_ptr stream) : m_callback_id(system::make_callback_id()), m_url(std::move(url)), m_stream(std::move(stream)) { } CurlGet::~CurlGet() { // CurlStack keeps a shared_ptr to this object, so it will only be destroyed once it is removed // from the stack. assert(!is_stacked() && "CurlGet::~CurlGet called while still in the stack."); } int64_t CurlGet::size_done() { auto guard = lock_guard(); if (m_handle == nullptr) return 0; curl_off_t d = 0; curl_easy_getinfo(m_handle, CURLINFO_SIZE_DOWNLOAD_T, &d); return d; } int64_t CurlGet::size_total() { auto guard = lock_guard(); if (m_handle == nullptr) return 0; curl_off_t d = 0; curl_easy_getinfo(m_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &d); return d; } void CurlGet::reset(const std::string& url, std::shared_ptr stream) { auto guard = lock_guard(); if (m_was_started) throw torrent::internal_error("CurlGet::reset() called on a started object."); if (m_handle != nullptr) throw torrent::internal_error("CurlGet::reset() called on a stacked object."); m_url = url; m_stream = std::move(stream); } void CurlGet::wait_for_close() { std::unique_lock guard(m_mutex); if (!m_was_closed) throw torrent::internal_error("CurlGet::wait_for_close() called on an object that is not closing."); if (m_handle != nullptr) m_cond_closed.wait(guard, [this] { return m_handle == nullptr; }); } bool CurlGet::try_wait_for_close() { std::unique_lock guard(m_mutex); if (!m_was_closed) return false; if (m_handle != nullptr) m_cond_closed.wait(guard, [this] { return m_handle == nullptr; }); return true; } void CurlGet::set_timeout(uint32_t seconds) { auto guard = lock_guard(); if (m_was_started) throw torrent::internal_error("CurlGet::set_timeout(...) called on a started object."); m_timeout = seconds; } void CurlGet::set_max_file_size(uint32_t bytes) { auto guard = lock_guard(); if (m_was_started) throw torrent::internal_error("CurlGet::set_max_file_size(...) called on a started object."); m_max_file_size = bytes; } void CurlGet::set_initial_resolve(resolve_type type) { auto guard = lock_guard(); if (m_was_started) throw torrent::internal_error("CurlGet::set_initial_resolve(...) called on a stacked or started object."); if (type == RESOLVE_NONE) throw torrent::internal_error("CurlGet::set_initial_resolve(...) called with RESOLVE_NONE, which is not allowed."); m_initial_resolve = type; } void CurlGet::set_retry_resolve(resolve_type type) { auto guard = lock_guard(); if (m_was_started) throw torrent::internal_error("CurlGet::set_retry_resolve(...) called on a stacked or started object."); m_retry_resolve = type; } void CurlGet::set_redirect_only_http_https() { auto guard = lock_guard(); if (m_was_started) throw torrent::internal_error("CurlGet::set_redirect_only_http_https() called on a stacked or started object."); m_redirect_only_http_https = true; } void CurlGet::start(const std::shared_ptr& curl_get, CurlStack* stack) { auto self = curl_get.get(); std::unique_lock guard(self->m_mutex); if (self->m_was_started) throw torrent::internal_error("CurlGet::start() called on an already started object."); self->m_was_started = true; self->m_stack_thread = stack->thread(); stack->thread()->callback(self->m_callback_id, [stack, curl_get]() { stack->start_get(curl_get); }); } void CurlGet::close(const std::shared_ptr& curl_get, system::Thread* callback_thread, bool wait) { curl_get->close_self(curl_get, callback_thread, wait); } void CurlGet::close_self(const std::shared_ptr& curl_get, system::Thread* callback_thread, bool wait) { std::unique_lock guard(m_mutex); if (!m_was_started) throw torrent::internal_error("CurlGet::close_self() called on an object that was not started."); if (m_was_closed) throw torrent::internal_error("CurlGet::close_self() called on an already closing object."); m_stack_thread->cancel_callback(m_callback_id); if (callback_thread != nullptr) callback_thread->cancel_callback(m_callback_id); if (m_stack == nullptr) { if (m_was_started) { m_stack_thread = nullptr; m_was_started = false; m_was_closed = false; m_prepare_canceled = false; m_retrying_resolve = false; } return; } assert(std::this_thread::get_id() != m_stack->thread()->thread_id()); assert(callback_thread != m_stack->thread()); m_was_closed = true; m_stack->thread()->callback_interrupt(m_callback_id, [curl_stack = m_stack, curl_get]() { curl_stack->close_get(curl_get); }); if (wait && m_handle == nullptr) m_cond_closed.wait(guard, [this] { return m_handle == nullptr; }); } bool CurlGet::prepare_start_unsafe(CurlStack* stack) { if (m_handle != nullptr) throw torrent::internal_error("CurlGet::prepare_start(...) called on a stacked object."); if (m_stream == nullptr) throw torrent::internal_error("CurlGet::prepare_start(...) called with a null stream."); if (!m_was_started) throw torrent::internal_error("CurlGet::prepare_start(...) called on an object that was not started."); if (m_was_closed || m_was_cleaned_up) { m_prepare_canceled = true; return false; } m_handle = curl_easy_init(); m_stack = stack; m_retrying_resolve = false; if (m_handle == nullptr) throw torrent::internal_error("Call to curl_easy_init() failed."); lt_easy_setopt(m_handle, CURLOPT_URL, m_url.c_str()); lt_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, &CurlGet::receive_write); lt_easy_setopt(m_handle, CURLOPT_WRITEDATA, this); // Close function and data must be set before libcurl's create_conn() is called. // // TODO: Consier all libcurl sockets to be !m_properly_opened for now. lt_easy_setopt(m_handle, CURLOPT_OPENSOCKETFUNCTION, &CurlSocket::open_socket); lt_easy_setopt(m_handle, CURLOPT_CLOSESOCKETFUNCTION, &CurlSocket::close_socket); lt_easy_setopt(m_handle, CURLOPT_OPENSOCKETDATA, stack); lt_easy_setopt(m_handle, CURLOPT_CLOSESOCKETDATA, stack); // Disable connection reuse as libcurl doesn't provide proper API to handle idle connections being // closed/reused. // // TODO: Remove this when libcurl issues are resolved. // TODO: Make sure shutdown quickly cleans up idle connections. lt_easy_setopt(m_handle, CURLOPT_FORBID_REUSE, 1l); if (m_timeout != 0) { lt_easy_setopt(m_handle, CURLOPT_CONNECTTIMEOUT, 60l); lt_easy_setopt(m_handle, CURLOPT_TIMEOUT, static_cast(m_timeout)); } if (m_max_file_size != 0) lt_easy_setopt(m_handle, CURLOPT_MAXFILESIZE, static_cast(m_max_file_size)); if (m_redirect_only_http_https) { #if CURL_AT_LEAST_VERSION(7, 85, 0) lt_easy_setopt(m_handle, CURLOPT_REDIR_PROTOCOLS_STR, "http,https"); #else lt_easy_setopt(m_handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); #endif } lt_easy_setopt(m_handle, CURLOPT_NOSIGNAL, 1l); lt_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1l); lt_easy_setopt(m_handle, CURLOPT_MAXREDIRS, 5l); lt_easy_setopt(m_handle, CURLOPT_ENCODING, ""); // Note that if the url has a numeric IP address, libcurl will not respect the CURLOPT_IPRESOLVE // option. // // We need to make CurlGet fail on numeric urls which do not match the resolve type. if (m_initial_resolve == RESOLVE_NONE) throw torrent::internal_error("CurlGet::prepare_start_unsafe() called with m_initial_resolve set to RESOLVE_NONE."); if (m_initial_resolve == RESOLVE_WHATEVER) { lt_easy_setopt(m_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER); return true; } try { try { if (prepare_resolve_unsafe(m_initial_resolve)) return true; } catch (const torrent::input_error& e) { if (m_retry_resolve == RESOLVE_NONE) throw; // TODO: Save the error msg for later. } m_retrying_resolve = true; if (prepare_resolve_unsafe(m_retry_resolve)) return true; throw torrent::input_error("Exhausted all resolve options."); } catch (const torrent::input_error& e) { m_prepare_canceled = true; ::utils::slot_list_call(m_signal_failed, e.what()); return false; } } void CurlGet::activate_unsafe() { if (m_active) throw torrent::internal_error("CurlGet::activate() called on an already active handle."); CURLMcode code = curl_multi_add_handle(m_stack->handle(), m_handle); if (code != CURLM_OK) throw torrent::internal_error("CurlGet::activate() error calling curl_multi_add_handle: " + std::string(curl_multi_strerror(code))); m_active = true; } void CurlGet::deactivate_unsafe() { if (!m_active) throw torrent::internal_error("CurlGet::deactivate() called on an inactive handle."); CURLMcode code = curl_multi_remove_handle(m_stack->handle(), m_handle); if (code != CURLM_OK) throw torrent::internal_error("CurlGet::deactivate() error calling curl_multi_remove_handle: " + std::string(curl_multi_strerror(code))); m_active = false; } void CurlGet::cleanup_unsafe() { if (m_handle != nullptr) { if (m_active) deactivate_unsafe(); curl_easy_cleanup(m_handle); m_handle = nullptr; m_stack = nullptr; } else { if (!m_prepare_canceled) throw torrent::internal_error("CurlGet::cleanup() called on an object that has no m_handle yet m_prepare_canceled is false."); } m_stack_thread = nullptr; m_was_started = false; m_was_closed = false; m_prepare_canceled = false; m_retrying_resolve = false; // Use a separate cleanup flag so the state handling when retrying different resolves still works // properly. // // TODO: Review if this causes any issues. m_was_cleaned_up = true; } // Slots can be added at any time, however once trigger_* is called, it will make a copy of the // current list. void CurlGet::add_done_slot(const std::function& slot) { auto guard = lock_guard(); if (m_was_started) throw torrent::internal_error("CurlGet::add_done_slot() called on a started object."); m_signal_done.push_back(slot); } void CurlGet::add_failed_slot(const std::function& slot) { auto guard = lock_guard(); if (m_was_started) throw torrent::internal_error("CurlGet::add_failed_slot() called on a started object."); m_signal_failed.push_back(slot); } bool CurlGet::retry_resolve() { auto guard = lock_guard(); if (m_handle == nullptr) throw torrent::internal_error("CurlGet::retry_ipv6() called on a null m_handle."); if (m_retrying_resolve || m_retry_resolve == RESOLVE_NONE) return false; deactivate_unsafe(); m_retrying_resolve = true; try { if (!prepare_resolve_unsafe(m_retry_resolve)) return false; activate_unsafe(); return true; } catch (const torrent::input_error& e) { // TODO: Consider adding to error message. return false; } } void CurlGet::trigger_done() { auto guard = lock_guard(); // Not really needed, but just in case. m_retrying_resolve = true; if (m_was_closed) return; LT_LOG("signal done", 0); ::utils::slot_list_call(m_signal_done); } void CurlGet::trigger_failed(const std::string& message) { auto guard = lock_guard(); m_retrying_resolve = true; if (m_was_closed) return; LT_LOG("signal failed : %s", message.c_str()); // If this is changed, verify RESOLVE_NONE handling is correct. ::utils::slot_list_call(m_signal_failed, message); } void CurlGet::trigger_cleared_request() { auto guard = lock_guard(); m_retrying_resolve = true; if (m_was_closed) return; if (m_active) deactivate_unsafe(); LT_LOG("signal force cleared", 0); ::utils::slot_list_call(m_signal_failed, "Request force cleared."); } size_t CurlGet::receive_write(const char* data, size_t size, size_t nmemb, CurlGet* handle) { if (handle->m_stream->write(data, size * nmemb).fail()) return 0; return size * nmemb; } bool CurlGet::prepare_resolve_unsafe(resolve_type current_resolve) { auto [bind_inet_address, bind_inet6_address] = runtime::network_config()->bind_addresses_or_null(); int detected_family = utils::uri_detect_numeric(m_url); switch (current_resolve) { case RESOLVE_IPV4: if (bind_inet_address == nullptr) throw torrent::input_error("Bind address for requested IP protocol(s) not available."); if (detected_family == AF_INET6) throw torrent::input_error("Numeric IPv6 address in url, but IPv4 was requested."); if (bind_inet_address->sa_family != AF_UNSPEC) lt_easy_setopt(m_handle, CURLOPT_INTERFACE, sa_addr_str(bind_inet_address.get()).c_str()); lt_easy_setopt(m_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); return true; case RESOLVE_IPV6: if (bind_inet6_address == nullptr) throw torrent::input_error("Bind address for requested IP protocol(s) not available."); if (detected_family == AF_INET) throw torrent::input_error("Numeric IPv4 address in url, but IPv6 was requested."); if (bind_inet6_address->sa_family != AF_UNSPEC) lt_easy_setopt(m_handle, CURLOPT_INTERFACE, sa_addr_str(bind_inet6_address.get()).c_str()); lt_easy_setopt(m_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); return true; case RESOLVE_NONE: return false; default: throw torrent::internal_error("CurlGet::prepare_resolve_unsafe() reached unreachable code with invalid current_resolve."); } } } // namespace torrent::net libtorrent-0.16.17/src/net/curl_get.h000066400000000000000000000150161522271512000174040ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_CURL_GET_H #define LIBTORRENT_NET_CURL_GET_H #include #include #include #include #include #include #include #include "torrent/system/thread.h" namespace torrent::net { class CurlStack; class CurlGet { public: enum resolve_type { RESOLVE_NONE, RESOLVE_WHATEVER, RESOLVE_IPV4, RESOLVE_IPV6 }; CurlGet(std::string url = "", std::shared_ptr stream = nullptr); ~CurlGet(); static void start(const std::shared_ptr& curl_get, CurlStack* stack); static void close(const std::shared_ptr& curl_get, system::Thread* thread, bool wait); static void close_and_keep_callbacks(const std::shared_ptr& curl_get); static void close_and_cancel_callbacks(const std::shared_ptr& curl_get, system::Thread* callback_thread); static void close_and_wait(const std::shared_ptr& curl_get); bool is_stacked() const; bool is_active() const; bool is_closing() const; auto& callback_id(); CurlStack* curl_stack() const; const std::string& url() const; int64_t size_done(); int64_t size_total(); void reset(const std::string& url, std::shared_ptr str); void wait_for_close(); bool try_wait_for_close(); uint32_t timeout() const; void set_timeout(uint32_t seconds); uint32_t max_file_size() const; void set_max_file_size(uint32_t bytes); void set_initial_resolve(resolve_type type); void set_retry_resolve(resolve_type type); void set_redirect_only_http_https(); // The owner of the Http object must close it as soon as possible after receiving the signal, as // the implementation may allocate limited resources during its lifetime. // // These slots should not directly modify the CurlGet or CurlStack, and instead use callbacks, // etc, for such actions. // // The slots should be non-blocking and the CurlGet object will remain locked during the call, and // as such cannot be modified. // // The slots won't be called after set_was_closed() is called. void add_done_slot(const std::function& slot); void add_failed_slot(const std::function& slot); // TODO: Add add_deleted_slot, or a list of threads to remove callbacks from? protected: friend class CurlStack; void close_self(const std::shared_ptr& curl_get, system::Thread* thread, bool wait); // We need to lock when changing any of the values publically accessible. This means we don't need // to lock when changing the underlying vector. void lock() const { m_mutex.lock(); } auto lock_guard() const { return std::lock_guard(m_mutex); } void unlock() const { m_mutex.unlock(); } auto& mutex() const { return m_mutex; } bool is_active_unsafe() const { return m_active; } bool is_prepare_canceled_unsafe() const { return m_prepare_canceled; } bool is_closing_unsafe() const { return m_was_closed; } auto handle_unsafe() const { return m_handle; } [[nodiscard]] bool prepare_start_unsafe(CurlStack* stack); void activate_unsafe(); void deactivate_unsafe(); void cleanup_unsafe(); bool retry_resolve(); void notify_closed() { m_cond_closed.notify_all(); } void trigger_done(); void trigger_failed(const std::string& message); void trigger_cleared_request(); private: CurlGet(const CurlGet&) = delete; CurlGet& operator=(const CurlGet&) = delete; static size_t receive_write(const char* data, size_t size, size_t nmemb, CurlGet* handle); bool prepare_resolve_unsafe(resolve_type current_resolve); system::callback_id m_callback_id; mutable std::mutex m_mutex; CURL* m_handle{}; CurlStack* m_stack{}; system::Thread* m_stack_thread{}; bool m_active{}; bool m_prepare_canceled{}; bool m_was_started{}; bool m_was_closed{}; bool m_was_cleaned_up{}; resolve_type m_initial_resolve{RESOLVE_WHATEVER}; resolve_type m_retry_resolve{RESOLVE_NONE}; bool m_retrying_resolve{}; // When you change timeout to a different type, update curl_get.cc where it multiplies // 1s*m_timeout. std::string m_url; std::shared_ptr m_stream; uint32_t m_timeout{60}; uint32_t m_max_file_size{}; bool m_redirect_only_http_https{}; std::condition_variable m_cond_closed; std::list> m_signal_done; std::list> m_signal_failed; }; inline void CurlGet::close_and_keep_callbacks(const std::shared_ptr& curl_get) { close(curl_get, nullptr, false); } inline void CurlGet::close_and_cancel_callbacks(const std::shared_ptr& curl_get, system::Thread* thread) { close(curl_get, thread, false); } inline void CurlGet::close_and_wait(const std::shared_ptr& curl_get) { close(curl_get, nullptr, true); } inline bool CurlGet::is_stacked() const { auto guard = lock_guard(); return m_handle != nullptr; } inline bool CurlGet::is_active() const { auto guard = lock_guard(); return m_active; } inline bool CurlGet::is_closing() const { auto guard = lock_guard(); return m_was_closed; } inline auto& CurlGet::callback_id() { return m_callback_id; } inline CurlStack* CurlGet::curl_stack() const { auto guard = lock_guard(); return m_stack; } inline const std::string& CurlGet::url() const { auto guard = lock_guard(); return m_url; } inline uint32_t CurlGet::timeout() const { auto guard = lock_guard(); return m_timeout; } inline uint32_t CurlGet::max_file_size() const { auto guard = lock_guard(); return m_max_file_size; } } // namespace torrent::net #endif // LIBTORRENT_NET_CURL_GET_H libtorrent-0.16.17/src/net/curl_socket.cc000066400000000000000000000425321522271512000202560ustar00rootroot00000000000000#include "config.h" #include "curl_socket.h" #include #include #include #include "net/curl_stack.h" #include "torrent/exceptions.h" #include "torrent/net/fd.h" #include "torrent/net/socket_address.h" #include "torrent/system/poll.h" #include "torrent/system/system.h" #include "torrent/runtime/socket_manager.h" #include "torrent/utils/log.h" #include #include #if 0 #define LT_LOG_DEBUG(log_fmt, ...) #define LT_LOG_DEBUG_THIS(log_fmt, ...) #define LT_LOG_DEBUG_SOCKET(log_fmt, ...) #define LT_LOG_DEBUG_SOCKET_FD(log_fmt, ...) #define LT_LOG_DEBUG_SOCKET_FD_HANDLE(log_fmt, ...) #else #define LT_LOG_DEBUG(log_fmt, ...) \ lt_log_print(LOG_CONNECTION_FD, "curl_socket: " log_fmt, __VA_ARGS__) #define LT_LOG_DEBUG_THIS(log_fmt, ...) \ lt_log_print(LOG_CONNECTION_FD, "curl_socket->%p(%i): " log_fmt, this, this->file_descriptor(), __VA_ARGS__) #define LT_LOG_DEBUG_SOCKET(log_fmt, ...) \ lt_log_print(LOG_CONNECTION_FD, "curl_socket->%p(%i): " log_fmt, socket, socket != nullptr ? socket->file_descriptor() : 0, __VA_ARGS__) #define LT_LOG_DEBUG_SOCKET_FD(log_fmt, ...) \ lt_log_print(LOG_CONNECTION_FD, "curl_socket->%p(%i): fd:%i : " log_fmt, socket, socket != nullptr ? socket->file_descriptor() : 0, fd, __VA_ARGS__) #define LT_LOG_DEBUG_SOCKET_FD_HANDLE(log_fmt, ...) \ lt_log_print(LOG_CONNECTION_FD, "curl_socket->%p(%i): fd:%i easy_handle:%p : " log_fmt, socket, socket != nullptr ? socket->file_descriptor() : 0, fd, easy_handle, __VA_ARGS__) #endif namespace torrent::net { namespace { void verify_libcurl_internal_wakeup(int fd); } CurlSocket::CurlSocket(CurlStack* stack) : m_stack(stack) { } CurlSocket::~CurlSocket() { assert(!is_open() && "CurlSocket::~CurlSocket() !is_open()"); } int CurlSocket::receive_socket(CURL* easy_handle, curl_socket_t fd, int what, CurlStack* stack, CurlSocket* socket) { assert(this_thread::thread() == stack->thread()); if (socket != nullptr && socket->m_self_ptr.expired()) { LT_LOG_DEBUG_SOCKET_FD_HANDLE("receive_socket() : called on deleted CurlSocket, aborting : %p", socket); throw internal_error("CurlSocket::receive_socket(fd:" + std::to_string(fd) + ") called on deleted CurlSocket"); } if (what == CURL_POLL_REMOVE) return handle_poll_remove(easy_handle, fd, stack, socket); if (socket == nullptr) { if (!stack->is_running()) return 0; socket = handle_poll_new(easy_handle, fd, stack); } if (!stack->is_running()) { LT_LOG_DEBUG_SOCKET_FD_HANDLE("receive_socket() : CurlStack not running, aborting", 0); throw internal_error("CurlSocket::receive_socket(fd:" + std::to_string(fd) + ") called while CurlStack is not running"); } if (what == CURL_POLL_NONE) { LT_LOG_DEBUG_SOCKET_FD_HANDLE("receive_socket(CURL_POLL_NONE) : removing read and write", 0); this_thread::poll()->remove_read(socket); this_thread::poll()->remove_write(socket); return 0; } switch (what) { case CURL_POLL_IN: LT_LOG_DEBUG_SOCKET_FD_HANDLE("receive_socket(CURL_POLL_IN) : inserting read, removing write", 0); this_thread::poll()->insert_read(socket); this_thread::poll()->remove_write(socket); break; case CURL_POLL_OUT: LT_LOG_DEBUG_SOCKET_FD_HANDLE("receive_socket(CURL_POLL_OUT) : inserting write, removing read", 0); this_thread::poll()->insert_write(socket); this_thread::poll()->remove_read(socket); break; case CURL_POLL_INOUT: LT_LOG_DEBUG_SOCKET_FD_HANDLE("receive_socket(CURL_POLL_INOUT) : inserting read and write", 0); this_thread::poll()->insert_read(socket); this_thread::poll()->insert_write(socket); break; default: LT_LOG_DEBUG_SOCKET_FD_HANDLE("receive_socket() : unknown what value: %i, aborting", 0); throw internal_error("CurlSocket::receive_socket(fd:" + std::to_string(fd) + "): unknown what value: " + std::to_string(what)); } return 0; } // This is not called when libcurl creates socketpairs, yet it expects us to handle them in receive_socket(). curl_socket_t CurlSocket::open_socket(CurlStack *stack, curlsocktype purpose, struct curl_sockaddr *address) { assert(this_thread::thread() == stack->thread()); if (!stack->is_running()) { LT_LOG_DEBUG("open_socket() : curl stack not running, aborting", 0); return CURL_SOCKET_BAD; } if (purpose != CURLSOCKTYPE_IPCXN) { LT_LOG_DEBUG("open_socket() : unsupported socket type: %i, aborting", purpose); throw internal_error("CurlSocket::open_socket() unsupported socket type: " + std::to_string(purpose)); } auto socket_ptr = std::make_shared(stack); auto socket = socket_ptr.get(); socket->m_self_ptr = socket_ptr; auto open_func = [socket, address]() { int socktype = address->socktype; #ifdef SOCK_CLOEXEC socktype |= SOCK_CLOEXEC; #endif int fd = ::socket(address->family, socktype, address->protocol); if (fd == -1) { LT_LOG_DEBUG("open_socket() : error creating socket: %s", std::strerror(errno)); return -1; } socket->set_file_descriptor(fd); return fd; }; runtime::socket_manager()->open_event_or_throw(socket, runtime::category_http, open_func); this_thread::poll()->open(socket); this_thread::poll()->insert_error(socket); auto [itr, inserted] = stack->socket_map()->try_emplace(socket->file_descriptor(), std::move(socket_ptr)); if (!inserted) { LT_LOG_DEBUG_SOCKET("open_socket() : fd:%i : socket already exists in stack", socket->file_descriptor()); throw internal_error("CurlSocket::open_socket() : fd:" + std::to_string(socket->file_descriptor()) + " : socket already exists in stack"); } LT_LOG_DEBUG_SOCKET("open_socket() : socket opened", 0); return itr->first; } // When receive_socket() is called with CURL_POLL_REMOVE, we call CurlSocket::close() which // deregisters this callback. (INVALID COMMENT) int CurlSocket::close_socket(CurlStack* stack, curl_socket_t fd) { assert(this_thread::thread() == stack->thread()); auto itr = stack->socket_map()->find(fd); if (itr == stack->socket_map()->end()) { LT_LOG_DEBUG("close_socket() : fd:%i : socket not found in stack, aborting", 0); throw internal_error("CurlSocket::close_socket(fd:" + std::to_string(fd) + "): socket not found in stack"); } auto* socket = itr->second.get(); LT_LOG_DEBUG_SOCKET("close_socket()", 0); if (socket->m_self_ptr.expired()) { LT_LOG_DEBUG_SOCKET_FD("close_socket() : called on deleted CurlSocket, aborting", 0); throw internal_error("CurlSocket::close_socket(fd:" + std::to_string(fd) + ") called on deleted CurlSocket"); } if (!socket->is_polling()) throw internal_error("CurlSocket::close_socket(fd:" + std::to_string(fd) + "): socket not in poll"); if (fd != socket->file_descriptor()) throw internal_error("CurlSocket::close_socket(): fd mismatch : curl:" + std::to_string(fd) + " self:" + std::to_string(socket->file_descriptor())); curl_multi_assign(stack->handle(), fd, nullptr); runtime::socket_manager()->close_event_or_throw(socket, [&]() { this_thread::poll()->remove_and_close(socket); if (::close(fd) == -1) { LT_LOG_DEBUG_SOCKET_FD("close_socket() : error closing socket: %s", system::errno_enum(errno)); throw internal_error("CurlSocket::close_socket(fd:" + std::to_string(fd) + "): error closing socket: " + system::errno_enum_str(errno)); } socket->set_file_descriptor(-1); }); socket->clear_and_erase_self(itr); return 0; } CurlSocket* CurlSocket::handle_poll_new(CURL* easy_handle, curl_socket_t fd, CurlStack* stack) { auto [itr, inserted] = stack->socket_map()->try_emplace(fd, nullptr); if (inserted) { verify_libcurl_internal_wakeup(fd); auto socket_ptr = std::make_shared(stack); auto socket = socket_ptr.get(); socket->m_easy_handle = easy_handle; socket->m_curl_internal = true; socket->m_self_ptr = socket_ptr; socket->set_file_descriptor(fd); LT_LOG_DEBUG_SOCKET_FD_HANDLE("handle_poll_new() : internal libcurl socket detected", 0); runtime::socket_manager()->register_event_or_throw(socket, runtime::category_http, [&]() { this_thread::poll()->open(socket); this_thread::poll()->insert_error(socket); }); curl_multi_assign(stack->handle(), fd, socket); itr->second = std::move(socket_ptr); return socket; } auto socket = itr->second.get(); if (socket->m_easy_handle != nullptr) { LT_LOG_DEBUG_SOCKET_FD_HANDLE("handle_poll_new() : existing CurlSocket easy_handle not null, aborting", 0); throw internal_error("CurlSocket::handle_poll_new(fd:" + std::to_string(fd) + ") existing CurlSocket easy_handle not null"); } socket->m_easy_handle = easy_handle; curl_multi_assign(stack->handle(), fd, socket); LT_LOG_DEBUG_SOCKET_FD_HANDLE("handle_poll_new() : found correctly initialized socket", 0); return socket; } int CurlSocket::handle_poll_remove(CURL* easy_handle, curl_socket_t fd, CurlStack* stack, CurlSocket* socket) { // When libcurl closes a socket in the idle connection poll, it calls receive_socket() with a // null socket. if (socket == nullptr) { LT_LOG_DEBUG_SOCKET_FD_HANDLE("CurlSocket::handle_poll_remove(CURL_POLL_REMOVE) : called with null socket, ignoring", 0); return 0; } if (!socket->is_open() || !socket->is_polling()) { LT_LOG_DEBUG_SOCKET_FD_HANDLE("handle_poll_remove(CURL_POLL_REMOVE) : socket already closed, aborting", 0); throw internal_error("CurlSocket::handle_poll_remove(fd:" + std::to_string(fd) + ") CURL_POLL_REMOVE called on closed socket"); } if (socket->file_descriptor() != fd) throw internal_error("CurlSocket::handle_poll_remove(fd:" + std::to_string(fd) + ") CURL_POLL_REMOVE fd mismatch"); auto itr = stack->socket_map()->find(fd); if (itr == stack->socket_map()->end()) { LT_LOG_DEBUG_SOCKET_FD_HANDLE("handle_poll_remove(CURL_POLL_REMOVE) : socket not found in stack, aborting", 0); throw internal_error("CurlSocket::handle_poll_remove(fd:" + std::to_string(fd) + ") CURL_POLL_REMOVE socket not found in stack"); } // LibCurl doesn't use open/close_socket socketpairs, so we only do poll events for them. if (socket->m_curl_internal) { LT_LOG_DEBUG_SOCKET_FD_HANDLE("handle_poll_remove(CURL_POLL_REMOVE) : internal curl socket, removing from poll", 0); runtime::socket_manager()->unregister_event_or_throw(socket, [&]() { this_thread::poll()->remove_and_close(socket); }); curl_multi_assign(stack->handle(), fd, nullptr); socket->clear_and_erase_self(itr); return 0; } LT_LOG_DEBUG_SOCKET_FD_HANDLE("handle_poll_remove(CURL_POLL_REMOVE) : removing from read/write polling", 0); this_thread::poll()->remove_read(socket); this_thread::poll()->remove_write(socket); return 0; } void CurlSocket::event_read() { handle_action(CURL_CSELECT_IN); } void CurlSocket::event_write() { handle_action(CURL_CSELECT_OUT); } void CurlSocket::event_error() { LT_LOG_DEBUG_THIS("event_error()", 0); auto weak_self = m_self_ptr; handle_action(CURL_CSELECT_ERR); // This shouldn't happen, or might be a bug / unexpected behavior in libcurl. if (!weak_self.expired()) { LT_LOG_DEBUG_THIS("event_error() : self still exists : is_internal:%i", m_curl_internal); throw internal_error("CurlSocket::event_error() self still exists : " + std::to_string(file_descriptor()) + " : is_internal:" + std::to_string(m_curl_internal)); } } void CurlSocket::handle_action(int ev_bitmask) { assert(is_open() && "CurlSocket::handle_action() is_open()"); assert(m_stack != nullptr && "CurlSocket::handle_action() m_stack != nullptr"); // Processing might deallocate this CurlSocket. auto stack = m_stack; CurlSocket::handle_action_simple(stack, file_descriptor(), ev_bitmask); // // TODO: Use Thread::call_events() to process this: ? // while (stack->process_done_handle()) ; // Do nothing. } void CurlSocket::handle_action_simple(CurlStack* stack, int fd, int ev_bitmask) { int count{}; auto code = curl_multi_socket_action(stack->handle(), fd, ev_bitmask, &count); if (code != CURLM_OK) throw internal_error("CurlSocket::handle_action_simple(...) error calling curl_multi_socket_action: " + std::string(curl_multi_strerror(code))); } void CurlSocket::clear_and_erase_self(CurlStack::socket_map_type::iterator itr) { auto socket_map = m_stack->socket_map(); m_stack = nullptr; m_easy_handle = nullptr; set_file_descriptor(-1); socket_map->erase(itr); } namespace { void verify_libcurl_internal_wakeup(int fd) { struct stat sb; if (fstat(fd, &sb) == -1) { LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : fstat failed: %s", fd, system::errno_enum(errno)); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): fstat failed: " + system::errno_enum_str(errno)); } // Catch pipe and fifo (wakeup_pipe) if (S_ISFIFO(sb.st_mode)) return; if (S_ISSOCK(sb.st_mode)) { auto local_addr = fd_get_socket_name(fd); auto peer_addr = fd_get_peer_name(fd); if (local_addr == nullptr) { LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : getsockname failed: %s", fd, system::errno_enum(errno)); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): getsockname failed: " + system::errno_enum_str(errno)); } if (peer_addr == nullptr) { LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : getpeername failed: %s", fd, system::errno_enum(errno)); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): getpeername failed: " + system::errno_enum_str(errno)); } // Catch standard socketpairs (wakeup_socketpair) if (local_addr->sa_family == AF_UNIX && peer_addr->sa_family == AF_UNIX) { auto *un_local = reinterpret_cast(local_addr.get()); auto *un_peer = reinterpret_cast(peer_addr.get()); if (strlen(un_local->sun_path) == 0 && strlen(un_peer->sun_path) == 0) return; LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : fd appears to be a unix socket, but local/peer addresses are not anonymous", 0); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): fd appears to be a unix socket, but local/peer addresses are not anonymous"); } int socket_type{}; if (!fd_get_type(fd, &socket_type)) { LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : getsockopt(SO_TYPE) failed: %s", fd, system::errno_enum(errno)); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): getsockopt(SO_TYPE) failed: " + system::errno_enum_str(errno)); } // Catch DNS and various other UDP sockets if (socket_type == SOCK_DGRAM) return; if (socket_type != SOCK_STREAM) { LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : unexpected socket type: %i", fd, socket_type); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): unexpected socket type: " + std::to_string(socket_type)); } // Catch loopback sockets (wakeup_inet) if (sap_is_inet_inet6(local_addr) && sap_is_inet_inet6(peer_addr)) { if (!sap_is_loopback(local_addr) || !sap_is_loopback(peer_addr)) { LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : fd appears to be an inet/inet6 socket, but local/peer addresses are not loopback : %s : %s", fd, sap_pretty_str(local_addr).c_str(), sap_pretty_str(peer_addr).c_str()); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): fd appears to be an inet/inet6 socket, but local/peer addresses are not loopback"); } return; } LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : fd appears to be a socket, but not a valid libcurl internal wakeup socket", fd); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): fd appears to be a socket, but not a valid libcurl internal wakeup socket"); } // Linux eventfd (Only probe if it's an anonymous inode, NOT a socket) if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) { bool is_nonblock{}; if (!fd_get_nonblock(fd, &is_nonblock)) { LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : fd_get_nonblock failed: %s", fd, system::errno_enum(errno)); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): fd_get_nonblock failed: " + system::errno_enum_str(errno)); } if (!is_nonblock) { LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : fd is blocking, expected non-blocking", fd); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): fd is blocking, expected non-blocking"); } char dummy[7] = {0}; // Passing 7 bytes to an eventfd instantly returns EINVAL without altering state. // A regular file or unexpected stream might accept it, but it filters out eventfd perfectly. if (write(fd, dummy, 7) == -1 && errno == EINVAL) return; } LT_LOG_DEBUG("verify_libcurl_internal_wakeup(fd:%i) : fd does not appear to be a valid libcurl internal wakeup socket", fd); throw internal_error("verify_libcurl_internal_wakeup(fd:" + std::to_string(fd) + "): fd does not appear to be a valid libcurl internal wakeup socket"); } } // namespace anonymous } // namespace torrent::net libtorrent-0.16.17/src/net/curl_socket.h000066400000000000000000000031751522271512000201200ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_CURL_SOCKET_H #define LIBTORRENT_NET_CURL_SOCKET_H #include #include "net/curl_stack.h" #include "torrent/event.h" namespace torrent::net { class CurlGet; class CurlSocket : public torrent::Event { public: CurlSocket(CurlStack* stack); ~CurlSocket() override; const char* type_name() const override { return "curl_socket"; } void event_read() override; void event_write() override; void event_error() override; protected: friend class CurlGet; friend class CurlStack; static curl_socket_t open_socket(CurlStack *stack, curlsocktype purpose, struct curl_sockaddr *address); static int close_socket(CurlStack* stack, curl_socket_t fd); static int receive_socket(CURL* easy_handle, curl_socket_t fd, int what, CurlStack* stack, CurlSocket* socket); private: CurlSocket(const CurlSocket&) = delete; CurlSocket& operator=(const CurlSocket&) = delete; static CurlSocket* handle_poll_new(CURL* easy_handle, curl_socket_t fd, CurlStack* stack); static int handle_poll_remove(CURL* easy_handle, curl_socket_t fd, CurlStack* stack, CurlSocket* socket); void handle_action(int ev_bitmask); static void handle_action_simple(CurlStack* stack, int fd, int ev_bitmask); void clear_and_erase_self(CurlStack::socket_map_type::iterator itr); CurlStack* m_stack{}; CURL* m_easy_handle{}; bool m_curl_internal{}; std::weak_ptr m_self_ptr{}; }; } // namespace torrent::net #endif // LIBTORRENT_NET_CURL_SOCKET_H libtorrent-0.16.17/src/net/curl_stack.cc000066400000000000000000000211611522271512000200660ustar00rootroot00000000000000#include "config.h" #include "curl_stack.h" #include #include #include #include "net/curl_get.h" #include "net/curl_socket.h" #include "torrent/exceptions.h" #include "torrent/system/thread.h" #include "torrent/utils/log.h" #define LT_LOG_GET(log_fmt, ...) \ lt_log_print(LOG_NET_HTTP, "%p->http_get : " log_fmt, curl_get.get(), __VA_ARGS__) #define lt_easy_setopt(handle, option, value) \ { \ CURLcode code = curl_easy_setopt(handle, option, value); \ if (code != CURLE_OK) \ throw torrent::internal_error("Error calling curl_easy_setopt(" #option "): " + std::string(curl_easy_strerror(code))); \ } #define lt_multi_setopt(handle, option, value) \ { \ CURLMcode code = curl_multi_setopt(handle, option, value); \ if (code != CURLM_OK) \ throw torrent::internal_error("Error calling curl_multi_setopt(" #option "): " + std::string(curl_multi_strerror(code))); \ } namespace torrent::net { CurlStack::CurlStack(system::Thread* thread) : m_thread(thread), m_handle(curl_multi_init()) { m_task_timeout.slot() = [this]() { receive_timeout(); }; lt_multi_setopt(m_handle, CURLMOPT_TIMERDATA, this); lt_multi_setopt(m_handle, CURLMOPT_TIMERFUNCTION, &CurlStack::set_timeout); lt_multi_setopt(m_handle, CURLMOPT_SOCKETDATA, this); lt_multi_setopt(m_handle, CURLMOPT_SOCKETFUNCTION, &CurlSocket::receive_socket); lt_multi_setopt(m_handle, CURLMOPT_MAXCONNECTS, m_max_cache_connections); lt_multi_setopt(m_handle, CURLMOPT_MAX_HOST_CONNECTIONS, m_max_host_connections); lt_multi_setopt(m_handle, CURLMOPT_MAX_TOTAL_CONNECTIONS, m_max_total_connections); } CurlStack::~CurlStack() { assert(!is_running() && "CurlStack::~CurlStack() called while still running."); } void CurlStack::set_max_cache_connections(unsigned int value) { if (value > 1024) throw torrent::internal_error("CurlStack::set_max_cache_connections() called with a value greater than 1024."); auto guard = lock_guard(); m_max_cache_connections = value; lt_multi_setopt(m_handle, CURLMOPT_MAXCONNECTS, value); } void CurlStack::set_max_host_connections(unsigned int value) { if (value > 1024) throw torrent::internal_error("CurlStack::set_max_host_connections() called with a value greater than 1024."); auto guard = lock_guard(); m_max_host_connections = value; lt_multi_setopt(m_handle, CURLMOPT_MAX_HOST_CONNECTIONS, value); } void CurlStack::set_max_total_connections(unsigned int value) { if (value > 4096) throw torrent::internal_error("CurlStack::set_max_total_connections() called with a value greater than 4096."); auto guard = lock_guard(); m_max_total_connections = value; lt_multi_setopt(m_handle, CURLMOPT_MAX_TOTAL_CONNECTIONS, value); } void CurlStack::shutdown() { assert(std::this_thread::get_id() == m_thread->thread_id()); { auto guard = lock_guard(); assert(m_running && "CurlStack::shutdown() called while not running."); m_running = false; } while (!base_type::empty()) close_get(base_type::back()); curl_multi_cleanup(m_handle); m_handle = nullptr; torrent::this_thread::scheduler()->erase(&m_task_timeout); } void CurlStack::clear_requests() { assert(std::this_thread::get_id() == m_thread->thread_id()); for (auto& curl_get : *this) curl_get->trigger_cleared_request(); } void CurlStack::start_get(const std::shared_ptr& curl_get) { assert(std::this_thread::get_id() == m_thread->thread_id()); if (curl_get == nullptr) throw torrent::internal_error("CurlStack::start_get() called with a null curl_get."); LT_LOG_GET("starting : %s", curl_get->url().c_str()); { auto lock_main = std::unique_lock(m_mutex, std::defer_lock); auto lock_get = std::unique_lock(curl_get->mutex(), std::defer_lock); std::lock(lock_main, lock_get); // TODO: Check is_running, if not return error. Do not throw internal_error. if (!m_running) throw torrent::internal_error("CurlStack::start_get() called while not running."); // TODO: This might cause a race condition on cleanup_unsafe while not active? if (!curl_get->prepare_start_unsafe(this)) return; // CurlGet was already closed. if (!m_user_agent.empty()) lt_easy_setopt(curl_get->handle_unsafe(), CURLOPT_USERAGENT, m_user_agent.c_str()); if (!m_http_proxy.empty()) lt_easy_setopt(curl_get->handle_unsafe(), CURLOPT_PROXY, m_http_proxy.c_str()); if (!m_http_ca_path.empty()) lt_easy_setopt(curl_get->handle_unsafe(), CURLOPT_CAPATH, m_http_ca_path.c_str()); if (!m_http_ca_cert.empty()) lt_easy_setopt(curl_get->handle_unsafe(), CURLOPT_CAINFO, m_http_ca_cert.c_str()); lt_easy_setopt(curl_get->handle_unsafe(), CURLOPT_SSL_VERIFYHOST, m_ssl_verify_host ? 2l : 0l); lt_easy_setopt(curl_get->handle_unsafe(), CURLOPT_SSL_VERIFYPEER, m_ssl_verify_peer ? 1l : 0l); lt_easy_setopt(curl_get->handle_unsafe(), CURLOPT_DNS_CACHE_TIMEOUT, m_dns_timeout); base_type::push_back(curl_get); m_size = base_type::size(); // Calling curl_multi_add_handle() can result in CurlSocket::receive_socket() being called, // which calls CurlStack::is_running(). lock_main.unlock(); curl_get->activate_unsafe(); } } void CurlStack::close_get(const std::shared_ptr& curl_get) { assert(std::this_thread::get_id() == m_thread->thread_id()); bool was_active{}; { auto guard_get = curl_get->lock_guard(); if (!curl_get->is_prepare_canceled_unsafe()) { auto itr = std::find(base_type::begin(), base_type::end(), curl_get); if (itr == base_type::end()) throw torrent::internal_error("CurlStack::close_get() called on a CurlGet that is not in the stack."); base_type::erase(itr); m_size = base_type::size(); was_active = true; } curl_get->cleanup_unsafe(); } LT_LOG_GET("closed : %s", was_active ? "active" : "inactive"); curl_get->notify_closed(); } CurlStack::base_type::iterator CurlStack::find_curl_handle(const CURL* curl_handle) { auto itr = std::find_if(base_type::begin(), base_type::end(), [curl_handle](auto& curl_get) { return curl_get->handle_unsafe() == curl_handle; }); if (itr == base_type::end()) throw torrent::internal_error("Could not find CurlGet with the right easy_handle."); return itr; } int CurlStack::set_timeout(void*, long timeout_ms, CurlStack* stack) { assert(std::this_thread::get_id() == stack->m_thread->thread_id()); if (timeout_ms == -1) torrent::this_thread::scheduler()->erase(&stack->m_task_timeout); else torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&stack->m_task_timeout, std::chrono::milliseconds(timeout_ms)); return 0; } void CurlStack::receive_timeout() { assert(std::this_thread::get_id() == m_thread->thread_id()); int count{}; auto code = curl_multi_socket_action(m_handle, CURL_SOCKET_TIMEOUT, 0, &count); if (code != CURLM_OK) throw torrent::internal_error("CurlStack::receive_timeout() error calling curl_multi_socket_action."); while (process_done_handle()) ; // Do nothing. if (base_type::empty()) { torrent::this_thread::scheduler()->erase(&m_task_timeout); return; } if (!m_task_timeout.is_scheduled()) { // Sometimes libcurl forgets to reset the timeout. Try to poll the value in that case, or use 10 // seconds max. long timeout_ms; curl_multi_timeout(m_handle, &timeout_ms); auto timeout = std::max(std::chrono::milliseconds(timeout_ms), 10s); torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_timeout, timeout); } } // TODO: Check if curl_get is still active. bool CurlStack::process_done_handle() { int remaining_msgs{}; CURLMsg* msg = curl_multi_info_read(m_handle, &remaining_msgs); if (msg == nullptr) return false; if (msg->msg != CURLMSG_DONE) throw torrent::internal_error("CurlStack::process_done_handle() msg->msg != CURLMSG_DONE."); // TODO: Search if handle is still in the stack, and if not, assume it was closed and clean up. (check is_active) auto itr = find_curl_handle(msg->easy_handle); // TODO: Lock CurlGet here, do the retry or retrieve the slots, instead of using trigger_*. // Strictly not needed as the following conditions will also return false if the handle is // closing. if ((*itr)->is_closing()) return remaining_msgs != 0; if (msg->data.result == CURLE_COULDNT_RESOLVE_HOST && (*itr)->retry_resolve()) return remaining_msgs != 0; if (msg->data.result == CURLE_OK) (*itr)->trigger_done(); else (*itr)->trigger_failed(curl_easy_strerror(msg->data.result)); // TODO: Should start next download here rather than in close_get. return remaining_msgs != 0; } } // namespace torrent::net libtorrent-0.16.17/src/net/curl_stack.h000066400000000000000000000140401522271512000177260ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_CURL_STACK_H #define LIBTORRENT_NET_CURL_STACK_H #include #include #include #include #include #include #include #include #include "torrent/utils/scheduler.h" namespace torrent::net { class CurlGet; class CurlSocket; class align_cacheline CurlStack : private std::vector> { public: using base_type = std::vector>; using socket_map_type = std::map>; CurlStack(system::Thread* thread); ~CurlStack(); bool is_running() const; unsigned int size() const; unsigned int max_cache_connections() const; unsigned int max_host_connections() const; unsigned int max_total_connections() const; void set_max_cache_connections(unsigned int value); void set_max_host_connections(unsigned int value); void set_max_total_connections(unsigned int value); const std::string& user_agent() const; const std::string& http_proxy() const; const std::string& http_capath() const; const std::string& http_cacert() const; void set_user_agent(const std::string& s); void set_http_proxy(const std::string& s); void set_http_capath(const std::string& s); void set_http_cacert(const std::string& s); bool ssl_verify_host() const; bool ssl_verify_peer() const; void set_ssl_verify_host(bool s); void set_ssl_verify_peer(bool s); long dns_timeout() const; void set_dns_timeout(long timeout); void shutdown(); void clear_requests(); void start_get(const std::shared_ptr& curl_get); void close_get(const std::shared_ptr& curl_get); bool process_done_handle(); system::Thread* thread() const { return m_thread; } protected: friend class CurlGet; friend class CurlSocket; CURLM* handle() const { return m_handle; } auto socket_map() { return &m_socket_map; } // We need to lock when changing any of the values publically accessible. void lock() const { m_mutex.lock(); } auto lock_guard() const { return std::scoped_lock(m_mutex); } void unlock() const { m_mutex.unlock(); } private: CurlStack(const CurlStack&) = delete; CurlStack operator=(const CurlStack&) = delete; base_type::iterator find_curl_handle(const CURL* curl_handle); static int set_timeout(void*, long timeout_ms, CurlStack* stack); void receive_timeout(); // Unprotected members (including base_type vector), only changed in ways that are implicitly // thread-safe. E.g. before any threads are started or only within the owning thread. system::Thread* m_thread{}; CURLM* m_handle{}; utils::SchedulerEntry m_task_timeout; socket_map_type m_socket_map; align_cacheline mutable std::mutex m_mutex; // Mirrors base::size() std::atomic_size_t m_size{0}; // Use lock guard when accessing these members, and when modifying the underlying vector. bool m_running{true}; unsigned int m_max_cache_connections{0}; unsigned int m_max_host_connections{0}; unsigned int m_max_total_connections{32}; std::string m_user_agent; std::string m_http_proxy; std::string m_http_ca_path; std::string m_http_ca_cert; bool m_ssl_verify_host{true}; bool m_ssl_verify_peer{true}; long m_dns_timeout{60}; }; inline bool CurlStack::is_running() const { auto guard = lock_guard(); return m_running; } inline unsigned int CurlStack::size() const { return m_size; } inline unsigned int CurlStack::max_cache_connections() const { auto guard = lock_guard(); return m_max_cache_connections; } inline unsigned int CurlStack::max_host_connections() const { auto guard = lock_guard(); return m_max_host_connections; } inline unsigned int CurlStack::max_total_connections() const { auto guard = lock_guard(); return m_max_total_connections; } inline const std::string& CurlStack::user_agent() const { auto guard = lock_guard(); return m_user_agent; } inline const std::string& CurlStack::http_proxy() const { auto guard = lock_guard(); return m_http_proxy; } inline const std::string& CurlStack::http_capath() const { auto guard = lock_guard(); return m_http_ca_path; } inline const std::string& CurlStack::http_cacert() const { auto guard = lock_guard(); return m_http_ca_cert; } inline void CurlStack::set_user_agent(const std::string& s) { auto guard = lock_guard(); m_user_agent = s; } inline void CurlStack::set_http_proxy(const std::string& s) { auto guard = lock_guard(); m_http_proxy = s; } inline void CurlStack::set_http_capath(const std::string& s) { auto guard = lock_guard(); m_http_ca_path = s; } inline void CurlStack::set_http_cacert(const std::string& s) { auto guard = lock_guard(); m_http_ca_cert = s; } inline bool CurlStack::ssl_verify_host() const { auto guard = lock_guard(); return m_ssl_verify_host; } inline bool CurlStack::ssl_verify_peer() const { auto guard = lock_guard(); return m_ssl_verify_peer; } inline void CurlStack::set_ssl_verify_host(bool s) { auto guard = lock_guard(); m_ssl_verify_host = s; } inline void CurlStack::set_ssl_verify_peer(bool s) { auto guard = lock_guard(); m_ssl_verify_peer = s; } inline long CurlStack::dns_timeout() const { auto guard = lock_guard(); return m_dns_timeout; } inline void CurlStack::set_dns_timeout(long timeout) { auto guard = lock_guard(); m_dns_timeout = timeout; } } // namespace torrent::net #endif // LIBTORRENT_NET_CURL_STACK_H libtorrent-0.16.17/src/net/data_buffer.h000066400000000000000000000060651522271512000200460ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_NET_DATA_BUFFER_H #define LIBTORRENT_NET_DATA_BUFFER_H #include #include namespace torrent { // Recipient must call clear() when done with the buffer. struct DataBuffer { DataBuffer() = default; DataBuffer(char* data, char* end) : m_data(data), m_end(end) {} DataBuffer clone() const { DataBuffer d = *this; d.m_owned = false; return d; } DataBuffer release() { DataBuffer d = *this; set(NULL, NULL, false); return d; } char* data() const { return m_data; } char* end() const { return m_end; } bool owned() const { return m_owned; } bool empty() const { return m_data == NULL; } size_t length() const { return m_end - m_data; } void clear(); void set(char* data, char* end, bool owned); private: char* m_data{}; char* m_end{}; // Used to indicate if buffer held by PCB is its own and needs to be // deleted after transmission (false if shared with other connections). bool m_owned{true}; }; inline void DataBuffer::clear() { if (!empty() && m_owned) delete[] m_data; m_data = m_end = NULL; m_owned = false; } inline void DataBuffer::set(char* data, char* end, bool owned) { m_data = data; m_end = end; m_owned = owned; } } // namespace torrent #endif libtorrent-0.16.17/src/net/dns_buffer.cc000066400000000000000000000231711522271512000200540ustar00rootroot00000000000000#include "config.h" #include "net/dns_buffer.h" #include #include #include #include "net/dns_cache.h" #include "net/thread_net.h" #include "net/udns_resolver.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/system/system.h" #include "torrent/system/callbacks.h" #include "torrent/utils/log.h" #define LT_LOG(log_fmt, ...) \ lt_log_print_subsystem(LOG_NET_DNS, "dns-buffer", log_fmt, __VA_ARGS__); #define LT_LOG_REQUESTER(log_fmt, ...) \ lt_log_print(LOG_NET_DNS, "%016p->dns-buffer : " log_fmt, requester, __VA_ARGS__); namespace torrent::net { DnsBuffer::~DnsBuffer() { // assert(std::this_thread::get_id() == ThreadNet::thread_net()->thread_id()); // if (m_active_query_count != 0) // throw internal_error("DnsBuffer::~DnsBuffer() m_active_query_count != 0"); // if (!m_pending_queries.empty()) // throw internal_error("DnsBuffer::~DnsBuffer() !m_pending_queries.empty()"); { // Lock to ensure cancel_safe() calls are not in progress. auto guard = std::lock_guard(m_requesters_mutex); m_requesters.clear(); } } // Numeric addresses are resolved in torrent/net/resolver, with no use of ThreadNet. void DnsBuffer::resolve(void* requester, const std::string& hostname, int family, resolver_callback&& fn) { assert(std::this_thread::get_id() == ThreadNet::thread_net()->thread_id()); auto initialize_fn = [this, requester, fn = std::move(fn)](bool is_active) mutable { auto guard = std::scoped_lock(m_requesters_mutex); auto [itr, inserted] = m_requesters.try_emplace(requester, nullptr); if (inserted) itr->second = std::make_shared(); // TODO: Add sanity check for count during development. if (is_active) itr->second->active_query_count++; else itr->second->pending_query_count++; return DnsBufferCallback{itr->second, std::move(fn)}; }; // Check for existing active or pending query: if (m_active_query_count != 0) { for (auto& query : m_active_queries) { if (query.family == family && query.hostname == hostname) { query.callbacks.push_back(initialize_fn(true)); LT_LOG_REQUESTER("added to active query : name:%s family:%d", hostname.c_str(), family); return; } } for (auto& query : m_pending_queries) { if (query.family == family && query.hostname == hostname) { query.callbacks.push_back(initialize_fn(false)); LT_LOG_REQUESTER("added to pending query : name:%s family:%d", hostname.c_str(), family); return; } } } // No existing query, create a new one: if (m_active_query_count < max_requests) { LT_LOG_REQUESTER("added new active query : name:%s family:%d", hostname.c_str(), family); activate_and_resolve_query(DnsBufferQuery{family, hostname, {initialize_fn(true)}}); return; } LT_LOG_REQUESTER("added new pending query : name:%s family:%d", hostname.c_str(), family); m_pending_queries.insert(m_pending_queries.end(), DnsBufferQuery{family, hostname, {initialize_fn(false)}}); } // TODO: We're not flushing old m_requesters. (do we make calling cancel_safe() required for all requesters?) void DnsBuffer::cancel_safe(void* requester) { if (requester == nullptr) throw internal_error("DnsBuffer::cancel() called with null requester"); { auto guard = std::lock_guard(m_requesters_mutex); auto itr = m_requesters.find(requester); if (itr == m_requesters.end()) return; m_requesters.erase(itr); } LT_LOG_REQUESTER("canceled", 0); } void DnsBuffer::activate_pending_query() { while (!m_pending_queries.empty()) { if (m_active_query_count >= max_requests) throw internal_error("DnsBuffer::activate_pending_query() m_active_query_count >= max_requests"); auto query = std::move(m_pending_queries.front()); m_pending_queries.pop_front(); auto erase_itr = std::remove_if(query.callbacks.begin(), query.callbacks.end(), [](const auto& callback) { auto ptr = callback.requester.lock(); auto requester = ptr.get(); if (requester == nullptr) return true; if (requester->pending_query_count == 0) throw internal_error("DnsBuffer::activate_pending_query() requester pending_query_count is already 0"); requester->pending_query_count--; requester->active_query_count++; LT_LOG_REQUESTER("moved from pending to active query", 0); return false; }); query.callbacks.erase(erase_itr, query.callbacks.end()); if (query.callbacks.empty()) { LT_LOG("skipping pending query with no callbacks : name:%s family:%d", query.hostname.c_str(), query.family); continue; } activate_and_resolve_query(std::move(query)); return; } } void DnsBuffer::activate_and_resolve_query(DnsBufferQuery query) { assert(std::this_thread::get_id() == ThreadNet::thread_net()->thread_id()); if (m_active_query_count >= max_requests) throw internal_error("DnsBuffer::activate_query() m_active_query_count >= max_requests"); if (query.callbacks.empty()) throw internal_error("DnsBuffer::activate_query() called with empty callbacks"); auto itr = std::find_if(m_active_queries.begin(), m_active_queries.end(), [](const auto& query) { return query.family == -1; }); if (itr == m_active_queries.end()) throw internal_error("DnsBuffer::activate_query() itr == m_active_queries.end()"); *itr = std::move(query); m_active_query_count++; auto index = std::distance(m_active_queries.begin(), itr); auto fn = [this, index](sin_shared_ptr result_sin, int error_sin, sin6_shared_ptr result_sin6, int error_sin6) { net_thread::callback([=, this]() { this->process(index, std::move(result_sin), error_sin, std::move(result_sin6), error_sin6); }); }; // LT_LOG("activating query : requesters:%zu name:%s family:%d index:%u", // itr->callbacks.size(), itr->hostname.c_str(), itr->family, index); auto* requester = &m_active_queries[index]; ThreadNet::thread_net()->dns_resolver()->resolve(requester, itr->hostname, itr->family, std::move(fn)); } void DnsBuffer::process(unsigned int index, sin_shared_ptr result_sin, int error_sin, sin6_shared_ptr result_sin6, int error_sin6) { assert(std::this_thread::get_id() == ThreadNet::thread_net()->thread_id()); if (index >= max_requests) throw internal_error("DnsBuffer::process() index out of bounds"); auto query = std::move(m_active_queries[index]); m_active_queries[index] = DnsBufferQuery{}; m_active_query_count--; if (query.family != AF_UNSPEC && query.family != AF_INET && query.family != AF_INET6) throw internal_error("DnsBuffer::process() query.family is invalid"); if (result_sin && error_sin != 0) throw internal_error("DnsBuffer::process() query has both result and error for A record"); if (result_sin6 && error_sin6 != 0) throw internal_error("DnsBuffer::process() query has both result and error for AAAA record"); if (query.family == AF_UNSPEC) { if (result_sin == nullptr && result_sin6 == nullptr && (error_sin == 0 || error_sin6 == 0)) throw internal_error("DnsBuffer::process() query has no result and no error for AF_UNSPEC"); } else if (query.family == AF_INET) { if (result_sin == nullptr && error_sin == 0) throw internal_error("DnsBuffer::process() query has no result and no error for AF_INET"); if (result_sin6 != nullptr || error_sin6 != 0) throw internal_error("DnsBuffer::process() query has result or error for AF_INET6 when family is AF_INET"); } else if (query.family == AF_INET6) { if (result_sin6 == nullptr && error_sin6 == 0) throw internal_error("DnsBuffer::process() query has no result and no error for AF_INET6"); if (result_sin != nullptr || error_sin != 0) throw internal_error("DnsBuffer::process() query has result or error for AF_INET when family is AF_INET6"); } if (result_sin) ThreadNet::thread_net()->dns_cache()->process_success(query.hostname, AF_INET, result_sin, nullptr); else if (error_sin != 0) ThreadNet::thread_net()->dns_cache()->process_failure(query.hostname, AF_INET, error_sin); if (result_sin6) ThreadNet::thread_net()->dns_cache()->process_success(query.hostname, AF_INET6, nullptr, result_sin6); else if (error_sin6 != 0) ThreadNet::thread_net()->dns_cache()->process_failure(query.hostname, AF_INET6, error_sin6); for (auto& callback : query.callbacks) process_callback(callback, result_sin, error_sin, result_sin6, error_sin6); activate_pending_query(); } void DnsBuffer::process_callback(DnsBufferCallback& callback, sin_shared_ptr result_sin, int error_sin, sin6_shared_ptr result_sin6, int error_sin6) { auto requester_ptr = callback.requester.lock(); auto requester = requester_ptr.get(); if (requester == nullptr) return; if (requester->active_query_count == 0) throw internal_error("DnsBuffer::process() requester active_query_count is already 0"); requester->active_query_count--; LT_LOG_REQUESTER("processing callback : inet:%s inet6:%s", (error_sin == 0) ? sin_pretty_or_empty(result_sin.get()).c_str() : system::gai_enum_error(error_sin), (error_sin6 == 0) ? sin6_pretty_or_empty(result_sin6.get()).c_str() : system::gai_enum_error(error_sin6)); { // Block cancel() until this is done to ensure callbacks for the requester are all canceled. auto guard = std::lock_guard(m_requesters_mutex); if (requester_ptr.use_count() > 1) callback.callback(result_sin, error_sin, result_sin6, error_sin6); } } } // namespace torrent::net libtorrent-0.16.17/src/net/dns_buffer.h000066400000000000000000000047171522271512000177230ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_DNS_BUFFER_H #define LIBTORRENT_NET_DNS_BUFFER_H // TODO: Review headers #include #include #include #include #include #include #include #include #include "torrent/net/types.h" namespace torrent::net { // It is assume that 'requester' does very few simultaneous requests, so searching for pending // queries is not too expensive. struct DnsBufferRequester; struct DnsBufferCallback { std::weak_ptr requester; resolver_callback callback; }; struct DnsBufferQuery { // Family set to '-1' indicates an empty query. int family{-1}; std::string hostname; std::vector callbacks; }; // TODO: Add sanity checks using resolve_unique() and resolve_multi() which checks to make sure we // don't get duplicate requests. struct DnsBufferRequester { unsigned int active_query_count{}; unsigned int pending_query_count{}; }; class DnsBuffer { public: constexpr static int max_requests = 8; DnsBuffer() = default; ~DnsBuffer(); // The 'fn' callback must do work in the originating thread using callbacks with 'requester'. void resolve(void* requester, const std::string& hostname, int family, resolver_callback&& fn); // Calling thread must cancel callbacks with 'requester' afterwards. void cancel_safe(void* requester); // TODO: Add a slot for completed queries so we can add the entry to the cache before calling the // list of callbacks. private: using active_query_list = std::array; using pending_query_list = std::list; using requester_list = std::map>; void activate_pending_query(); void activate_and_resolve_query(DnsBufferQuery query); void process(unsigned int index, sin_shared_ptr result_sin, int error_sin, sin6_shared_ptr result_sin6, int error_sin6); void process_callback(DnsBufferCallback& callback, sin_shared_ptr result_sin, int error_sin, sin6_shared_ptr result_sin6, int error_sin6); unsigned int m_active_query_count{}; active_query_list m_active_queries; pending_query_list m_pending_queries; std::mutex m_requesters_mutex; requester_list m_requesters; }; } // namespace torrent::net #endif // LIBTORRENT_NET_DNS_BUFFER_H libtorrent-0.16.17/src/net/dns_cache.cc000066400000000000000000000475171522271512000176600ustar00rootroot00000000000000#include "config.h" #include "net/dns_cache.h" #include #include "net/thread_net.h" #include "net/dns_buffer.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/system/system.h" #include "torrent/utils/log.h" #define LT_LOG(log_fmt, ...) \ lt_log_print(LOG_NET_DNS, "dns-cache : " log_fmt, __VA_ARGS__); #define LT_LOG_REQUESTER(log_fmt, ...) \ lt_log_print(LOG_NET_DNS, "%016p->dns-cache : " log_fmt, requester, __VA_ARGS__); namespace torrent::net { namespace { enum { DNS_EMPTY, DNS_VALID, DNS_TRY_AGAIN, DNS_NO_RECORD, }; int dns_error(int status) { switch (status) { case DNS_TRY_AGAIN: return EAI_AGAIN; case DNS_NO_RECORD: return EAI_NONAME; default: throw internal_error("dns_error() invalid status"); } } const char* dns_error_str(int status) { switch (status) { case DNS_TRY_AGAIN: return "EAI_AGAIN"; case DNS_NO_RECORD: return "EAI_NONAME"; default: return "invalid"; } } std::chrono::minutes last_update_or_failed(const DnsCacheInfo& info) { return std::max(info.last_updated, info.last_failed_update); } } // namespace void DnsCache::resolve(void* requester, std::string hostname, int family, resolver_callback&& callback) { cull_stale_entries(); if (hostname.empty()) throw internal_error("DnsCache::resolve() hostname is empty"); if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC) throw internal_error("DnsCache::resolve() invalid address family"); auto itr = m_entries.find(hostname); if (itr == m_entries.end()) { // No need to log. ThreadNet::thread_net()->dns_buffer()->resolve(requester, hostname, family, std::move(callback)); return; } auto& sin_addr = itr->second->sin_addr; auto& sin_info = itr->second->sin_info; auto& sin6_addr = itr->second->sin6_addr; auto& sin6_info = itr->second->sin6_info; auto current_time = std::chrono::duration_cast(this_thread::cached_time()); // We need to get the status of each address family (if they're wanted), then construct the callback last: auto fn = [&](bool has_addr, int family, DnsCacheInfo& info) { if (has_addr) { // This is currently just a staleness check, we should have logic to report back when addresses // are successfully connected to. // // Or rather, we should pass a 'failed' counter in the resolve to indicate how many failed // attempts have been made to connect to the address. if (current_time > last_update_or_failed(info) + 24h) update_stale_info("valid record", requester, hostname, family, info); return DNS_VALID; } if (last_update_or_failed(info) == std::chrono::minutes(0)) return DNS_EMPTY; if (info.updating) { // TODO: Add sanity check on 'updating' here? // TODO: When updating, we add ourselves to the request unless it's a stale update or we // have very many failures / recent retries. return DNS_TRY_AGAIN; } if (info.no_record) { // TODO: Change to consider no_record a success. if (current_time > last_update_or_failed(info) + 2h) update_stale_info("no record", requester, hostname, family, info); return DNS_NO_RECORD; } // Queue a background request if failed timeout has passed, but return EAI_AGAIN as we're in // failure mode and the caller is going to retry. // // If it's been very long since the last try, we will use a normal request. // // Make sure we don't keep trying to resolve if we don't get proper responses. // TODO: Replace with 'failed' counter, and use that to determine when to retry, when to give up, etc. if (current_time > last_update_or_failed(info) + 10min) update_stale_info("failed update", requester, hostname, family, info); // TODO: Have a special code for when we're in try-again and also want to force a new resolve // due to staleness. (DSN_TRY_AGAIN_STALE) return DNS_TRY_AGAIN; }; // TODO: Detect when either A or AAAA dns server isn't responding, and cut back on retries, etc. // AF_INET: if (family == AF_INET) { auto status_sin = fn(sin_addr != nullptr, AF_INET, sin_info); switch (status_sin) { case DNS_EMPTY: LT_LOG_REQUESTER("matched cache entry, resolving missing AF_INET : hostname:%s family:AF_INET", hostname.c_str()); return queue_resolve(requester, hostname, AF_INET, sin_info, std::move(callback)); case DNS_VALID: LT_LOG_REQUESTER("matched cache entry : hostname:%s family:AF_INET sin:%s", hostname.c_str(), sin_pretty_or_empty(sin_addr.get()).c_str()); return callback(sin_addr, 0, nullptr, 0); case DNS_NO_RECORD: case DNS_TRY_AGAIN: LT_LOG_REQUESTER("matched cache entry : hostname:%s family:AF_INET sin:%s", hostname.c_str(), dns_error_str(status_sin)); return callback(nullptr, dns_error(status_sin), nullptr, 0); } } // AF_INET6: if (family == AF_INET6) { auto status_sin6 = fn(sin6_addr != nullptr, AF_INET6, sin6_info); switch (status_sin6) { case DNS_EMPTY: LT_LOG_REQUESTER("matched cache entry, resolving missing AF_INET6 : hostname:%s family:AF_INET6", hostname.c_str()); return queue_resolve(requester, hostname, AF_INET6, sin6_info, std::move(callback)); case DNS_VALID: LT_LOG_REQUESTER("matched cache entry : hostname:%s family:AF_INET6 sin6:%s", hostname.c_str(), sin6_pretty_or_empty(sin6_addr.get()).c_str()); return callback(nullptr, 0, sin6_addr, 0); case DNS_NO_RECORD: case DNS_TRY_AGAIN: LT_LOG_REQUESTER("matched cache entry : hostname:%s family:AF_INET6 sin6:%s", hostname.c_str(), dns_error_str(status_sin6)); return callback(nullptr, 0, nullptr, dns_error(status_sin6)); } } // AF_UNSPEC: auto status_sin = fn(sin_addr != nullptr, AF_INET, sin_info); auto status_sin6 = fn(sin6_addr != nullptr, AF_INET6, sin6_info); if (status_sin == DNS_VALID) { switch (status_sin6) { case DNS_EMPTY: LT_LOG_REQUESTER("matched cache entry, resolving missing AF_INET6 : hostname:%s family:AF_UNSPEC sin:%s", hostname.c_str(), sin_pretty_or_empty(sin_addr.get()).c_str()); return queue_resolve(requester, hostname, AF_INET6, sin6_info, [sin_addr, callback = std::move(callback)](auto, int, auto sin6_addr, int sin6_error) mutable { return callback(sin_addr, 0, sin6_addr, sin6_error); }); case DNS_VALID: LT_LOG_REQUESTER("matched cache entry : hostname:%s family:AF_UNSPEC sin:%s sin6:%s", hostname.c_str(), sin_pretty_or_empty(sin_addr.get()).c_str(), sin6_pretty_or_empty(sin6_addr.get()).c_str()); return callback(sin_addr, 0, sin6_addr, 0); case DNS_NO_RECORD: case DNS_TRY_AGAIN: LT_LOG_REQUESTER("matched cache entry : hostname:%s family:AF_UNSPEC sin:%s sin6:%s", hostname.c_str(), sin_pretty_or_empty(sin_addr.get()).c_str(), dns_error_str(status_sin6)); return callback(sin_addr, 0, nullptr, dns_error(status_sin6)); }; } if (status_sin6 == DNS_VALID) { switch (status_sin) { case DNS_EMPTY: LT_LOG_REQUESTER("matched cache entry, resolving missing AF_INET : hostname:%s family:AF_UNSPEC sin6:%s", hostname.c_str(), sin6_pretty_or_empty(sin6_addr.get()).c_str()); return queue_resolve(requester, hostname, AF_INET, sin_info, [sin6_addr, callback = std::move(callback)](auto sin_addr, int error_sin, auto, int) mutable { return callback(sin_addr, error_sin, sin6_addr, 0); }); case DNS_NO_RECORD: case DNS_TRY_AGAIN: LT_LOG_REQUESTER("matched cache entry : hostname:%s family:AF_UNSPEC sin:%s sin6:%s", hostname.c_str(), dns_error_str(status_sin), sin6_pretty_or_empty(sin6_addr.get()).c_str()); return callback(nullptr, dns_error(status_sin), sin6_addr, 0); case DNS_VALID: throw internal_error("DnsCache::resolve() unreachable code : status_sin6 == DNS_VALID"); }; } if (status_sin == DNS_EMPTY) { switch (status_sin6) { case DNS_NO_RECORD: case DNS_TRY_AGAIN: LT_LOG_REQUESTER("matched cache entry, resolving missing AF_INET : hostname:%s family:AF_UNSPEC sin6:%s", hostname.c_str(), dns_error_str(status_sin6)); return queue_resolve(requester, hostname, AF_INET, sin_info, std::move(callback)); case DNS_EMPTY: case DNS_VALID: throw internal_error("DnsCache::resolve() unreachable code : status_sin == DNS_EMPTY"); }; } if (status_sin6 == DNS_EMPTY) { switch (status_sin) { case DNS_NO_RECORD: case DNS_TRY_AGAIN: LT_LOG_REQUESTER("matched cache entry, resolving missing AF_INET6 : hostname:%s family:AF_UNSPEC sin:%s", hostname.c_str(), dns_error_str(status_sin)); return queue_resolve(requester, hostname, AF_INET6, sin6_info, std::move(callback)); case DNS_EMPTY: case DNS_VALID: throw internal_error("DnsCache::resolve() unreachable code : status_sin6 == DNS_EMPTY"); }; } if (status_sin == DNS_NO_RECORD || status_sin6 == DNS_NO_RECORD) { LT_LOG_REQUESTER("matched cache entry, returning no record error : hostname:%s family:AF_UNSPEC sin:%s sin6:%s", hostname.c_str(), dns_error_str(status_sin), dns_error_str(status_sin6)); return callback(nullptr, dns_error(status_sin), nullptr, dns_error(status_sin6)); } if (status_sin == DNS_TRY_AGAIN && status_sin6 == DNS_TRY_AGAIN) { LT_LOG_REQUESTER("matched cache entry, returning try again error : hostname:%s family:AF_UNSPEC sin:EAI_AGAIN sin6:EAI_AGAIN", hostname.c_str()); return callback(nullptr, EAI_AGAIN, nullptr, EAI_AGAIN); } throw internal_error("DnsCache::resolve() unreachable code : end-of-function"); } // TODO: When we add per-family errors, change this to only handle AF_INET/AF_INET6. void DnsCache::process_success(const std::string& hostname, int family, sin_shared_ptr result_sin, sin6_shared_ptr result_sin6) { if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC) throw internal_error("DnsCache::process_success() invalid address family"); if (result_sin == nullptr && result_sin6 == nullptr) throw internal_error("DnsCache::process_success() both result_sin and result_sin6 are nullptr"); if (family == AF_INET && result_sin == nullptr) throw internal_error("DnsCache::process_success() result_sin is nullptr for AF_INET"); if (family == AF_INET6 && result_sin6 == nullptr) throw internal_error("DnsCache::process_success() result_sin6 is nullptr for AF_INET6"); auto [itr, inserted] = m_entries.try_emplace(hostname, nullptr); if (inserted) itr->second = std::make_unique(); auto& sin_addr = itr->second->sin_addr; auto& sin_info = itr->second->sin_info; auto& sin6_addr = itr->second->sin6_addr; auto& sin6_info = itr->second->sin6_info; auto current_time = std::chrono::duration_cast(this_thread::cached_time()); if (inserted) { sin_info.entry_itr = itr; sin6_info.entry_itr = itr; sin_info.staleness_itr = m_sin_staleness.end(); sin6_info.staleness_itr = m_sin6_staleness.end(); if (family == AF_INET) { sin_addr = std::move(result_sin); reset_sin_updated(itr->second.get(), current_time); LT_LOG("added new cache entry : hostname:%s family:AF_INET sin:%s", hostname.c_str(), sin_pretty_or_empty(sin_addr.get()).c_str()); return; } if (family == AF_INET6) { sin6_addr = std::move(result_sin6); reset_sin6_updated(itr->second.get(), current_time); LT_LOG("added new cache entry : hostname:%s family:AF_INET6 sin6:%s", hostname.c_str(), sin6_pretty_or_empty(sin6_addr.get()).c_str()); return; } throw internal_error("DnsCache::process_success() unreachable code : inserted"); } if (family == AF_INET) { sin_addr = std::move(result_sin); sin_info.no_record = false; reset_sin_updated(itr->second.get(), current_time); // TODO: Only log if address has changed? LT_LOG("updated cache entry : hostname:%s family:AF_INET sin:%s", hostname.c_str(), sin_pretty_or_empty(sin_addr.get()).c_str()); return; } if (family == AF_INET6) { sin6_addr = std::move(result_sin6); sin6_info.no_record = false; reset_sin6_updated(itr->second.get(), current_time); LT_LOG("updated cache entry : hostname:%s family:AF_INET6 sin6:%s", hostname.c_str(), sin6_pretty_or_empty(sin6_addr.get()).c_str()); return; } throw internal_error("DnsCache::process_success() unreachable code"); } void DnsCache::process_failure(const std::string& hostname, int family, int error) { if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC) throw internal_error("DnsCache::process_failure() invalid address family"); if (error == 0) throw internal_error("DnsCache::process_failure() error is 0, should be success"); // TODO: If 'updating', add to scheduled task? auto [itr, inserted] = m_entries.try_emplace(hostname, nullptr); if (inserted) itr->second = std::make_unique(); auto& sin_addr = itr->second->sin_addr; auto& sin_info = itr->second->sin_info; auto& sin6_addr = itr->second->sin6_addr; auto& sin6_info = itr->second->sin6_info; auto current_time = std::chrono::duration_cast(this_thread::cached_time()); if (inserted) { sin_info.entry_itr = itr; sin6_info.entry_itr = itr; sin_info.staleness_itr = m_sin_staleness.end(); sin6_info.staleness_itr = m_sin6_staleness.end(); if (family == AF_INET) { switch (error) { case EAI_NONAME: // TODO: Should NONAME set success time? sin_info.no_record = true; reset_sin_failed(itr->second.get(), current_time); LT_LOG("added new cache entry with no record : hostname:%s family:AF_INET", hostname.c_str()); return; default: reset_sin_failed(itr->second.get(), current_time); LT_LOG("added new cache entry with failed update : hostname:%s family:AF_INET error:%s", hostname.c_str(), system::gai_enum_error(error)); return; }; } if (family == AF_INET6) { switch (error) { case EAI_NONAME: sin6_info.no_record = true; reset_sin6_failed(itr->second.get(), current_time); LT_LOG("added new cache entry with no record : hostname:%s family:AF_INET6", hostname.c_str()); return; default: reset_sin6_failed(itr->second.get(), current_time); LT_LOG("added new cache entry with failed update : hostname:%s family:AF_INET6 error:%s", hostname.c_str(), system::gai_enum_error(error)); return; }; } throw internal_error("DnsCache::process_failure() unreachable code : inserted"); } // TODO: EAI_NONAME should check if we're already set to no_record before doing anything. if (family == AF_INET) { switch (error) { case EAI_NONAME: sin_addr = nullptr; sin_info.no_record = true; // TODO: Should NONAME set success time? reset_sin_failed(itr->second.get(), current_time); LT_LOG("updated cache entry with no record : hostname:%s family:AF_INET", hostname.c_str()); return; default: reset_sin_failed(itr->second.get(), current_time); LT_LOG("updated cache entry with failed update : hostname:%s family:AF_INET error:%s", hostname.c_str(), system::gai_enum_error(error)); return; }; } if (family == AF_INET6) { switch (error) { case EAI_NONAME: sin6_addr = nullptr; sin6_info.no_record = true; // TODO: Should NONAME set success time? reset_sin6_failed(itr->second.get(), current_time); LT_LOG("updated cache entry with no record : hostname:%s family:AF_INET6", hostname.c_str()); return; default: reset_sin6_failed(itr->second.get(), current_time); LT_LOG("updated cache entry with failed update : hostname:%s family:AF_INET6 error:%s", hostname.c_str(), system::gai_enum_error(error)); return; }; } throw internal_error("DnsCache::process_failure() unreachable code"); } void DnsCache::reset_sin_updated(DnsCacheEntry* entry, std::chrono::minutes current_time) { auto& info = entry->sin_info; info.updating = false; info.last_updated = current_time; info.last_failed_update = std::chrono::minutes{0}; if (info.staleness_itr != m_sin_staleness.end()) m_sin_staleness.erase(info.staleness_itr); info.staleness_itr = m_sin_staleness.insert(m_sin_staleness.end(), std::ref(entry)); } void DnsCache::reset_sin6_updated(DnsCacheEntry* entry, std::chrono::minutes current_time) { auto& info = entry->sin6_info; info.updating = false; info.last_updated = current_time; info.last_failed_update = std::chrono::minutes{0}; if (info.staleness_itr != m_sin6_staleness.end()) m_sin6_staleness.erase(info.staleness_itr); info.staleness_itr = m_sin6_staleness.insert(m_sin6_staleness.end(), std::ref(entry)); } void DnsCache::reset_sin_failed(DnsCacheEntry* entry, std::chrono::minutes current_time) { auto& info = entry->sin_info; info.updating = false; info.last_updated = std::chrono::minutes{0}; info.last_failed_update = current_time; if (info.staleness_itr != m_sin_staleness.end()) m_sin_staleness.erase(info.staleness_itr); info.staleness_itr = m_sin_staleness.insert(m_sin_staleness.end(), std::ref(entry)); } void DnsCache::reset_sin6_failed(DnsCacheEntry* entry, std::chrono::minutes current_time) { auto& info = entry->sin6_info; info.updating = false; info.last_updated = std::chrono::minutes{0}; info.last_failed_update = current_time; if (info.staleness_itr != m_sin6_staleness.end()) m_sin6_staleness.erase(info.staleness_itr); info.staleness_itr = m_sin6_staleness.insert(m_sin6_staleness.end(), std::ref(entry)); } void DnsCache::cull_stale_entries() { auto current_time = std::chrono::duration_cast(this_thread::cached_time()); while (!m_sin_staleness.empty()) { auto* entry = m_sin_staleness.front(); auto& info = entry->sin_info; if (current_time < last_update_or_failed(info) + 48h) break; LT_LOG("culling stale cache entry : hostname:%s family:AF_INET sin:%s", info.entry_itr->first.c_str(), sin_pretty_or_empty(entry->sin_addr.get()).c_str()); if (entry->sin6_info.staleness_itr != m_sin6_staleness.end()) m_sin6_staleness.erase(entry->sin6_info.staleness_itr); m_entries.erase(info.entry_itr); m_sin_staleness.pop_front(); } while (!m_sin6_staleness.empty()) { auto* entry = m_sin6_staleness.front(); auto& info = entry->sin6_info; if (current_time < last_update_or_failed(info) + 48h) break; LT_LOG("culling stale cache entry : hostname:%s family:AF_INET6 sin6:%s", info.entry_itr->first.c_str(), sin6_pretty_or_empty(entry->sin6_addr.get()).c_str()); if (entry->sin_info.staleness_itr != m_sin_staleness.end()) m_sin_staleness.erase(entry->sin_info.staleness_itr); m_entries.erase(info.entry_itr); m_sin6_staleness.pop_front(); } } void DnsCache::queue_resolve(void* requester, const std::string& hostname, int family, DnsCacheInfo& info, resolver_callback&& callback) { ThreadNet::thread_net()->dns_buffer()->resolve(requester, hostname, family, std::move(callback)); info.updating = true; } void DnsCache::update_stale_info(const char* reason, void* requester, const std::string& hostname, int family, DnsCacheInfo& info) { LT_LOG_REQUESTER("stale cache entry with %s, retrying in background : hostname:%s family:%s", reason, hostname.c_str(), family_str(family)); ThreadNet::thread_net()->dns_buffer()->resolve(this, hostname, family, []( auto, int, auto, int) {}); info.updating = true; } } // namespace torrent::net libtorrent-0.16.17/src/net/dns_cache.h000066400000000000000000000044541522271512000175130ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_DNS_CACHE_H #define LIBTORRENT_NET_DNS_CACHE_H #include #include #include #include #include #include #include "torrent/net/types.h" namespace torrent::net { struct DnsCacheEntry; using DnsCacheEntries = std::unordered_map>; using DnsCacheStaleness = std::list; struct DnsCacheInfo { bool updating{}; bool no_record{}; std::chrono::minutes last_updated{}; std::chrono::minutes last_failed_update{}; DnsCacheEntries::iterator entry_itr{}; DnsCacheStaleness::iterator staleness_itr{}; }; struct DnsCacheEntry { sin_shared_ptr sin_addr; DnsCacheInfo sin_info; sin6_shared_ptr sin6_addr; DnsCacheInfo sin6_info; }; class DnsCache { public: // TODO: Add different types of resolve, e.g. force, in_error, etc. void resolve(void* requester, std::string hostname, int family, resolver_callback&& callback); protected: friend class DnsBuffer; void process_success(const std::string& hostname, int family, sin_shared_ptr result_sin, sin6_shared_ptr result_sin6); void process_failure(const std::string& hostname, int family, int error); private: void reset_sin_updated(DnsCacheEntry* entry, std::chrono::minutes current_time); void reset_sin6_updated(DnsCacheEntry* entry, std::chrono::minutes current_time); void reset_sin_failed(DnsCacheEntry* entry, std::chrono::minutes current_time); void reset_sin6_failed(DnsCacheEntry* entry, std::chrono::minutes current_time); void cull_stale_entries(); bool try_resolve_numeric(void* requester, const std::string& hostname, int family, resolver_callback&& callback); void queue_resolve(void* requester, const std::string& hostname, int family, DnsCacheInfo& info, resolver_callback&& callback); void update_stale_info(const char* reason, void* requester, const std::string& hostname, int family, DnsCacheInfo& info); DnsCacheEntries m_entries; DnsCacheStaleness m_sin_staleness; DnsCacheStaleness m_sin6_staleness; }; } // namespace torrent::net #endif // LIBTORRENT_NET_DNS_CACHE_H libtorrent-0.16.17/src/net/event_fd.cc000066400000000000000000000056421522271512000175340ustar00rootroot00000000000000#include "config.h" #include "net/event_fd.h" #include #ifdef USE_EPOLL #include #endif #include "torrent/exceptions.h" #include "torrent/runtime/socket_manager.h" #include "torrent/system/poll.h" namespace torrent::net { void EventFd::add_to_poll() { errno = 0; #ifdef USE_EPOLL set_file_descriptor(::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); m_safe_fd = file_descriptor(); #endif if (file_descriptor() == -1) throw internal_error("EventFd::add_to_poll() eventfd failed: " + std::string(std::strerror(errno))); runtime::socket_manager()->register_event_or_throw(this, runtime::category_internal, [this]() { this_thread::poll()->open(this); this_thread::poll()->insert_read(this); }); } void EventFd::remove_from_poll(system::Poll* poll) { if (!is_open()) return; m_safe_fd = -1; runtime::socket_manager()->unregister_event_or_throw(this, [this, poll]() { poll->remove_and_close(this); }); } // Poll uses a state flag to ensure we only send a signal once per interrupt. void EventFd::send_signal() { uint64_t value = 1; while (true) { switch (::write(m_safe_fd.load(), &value, sizeof(value))) { case sizeof(value): return; case 0: throw internal_error("EventFd::send_signal() write returned 0: " + this_thread::thread_name_str()); case -1: if (errno == EINTR) continue; // Only happens if the eventfd counter is at its maximum value, so it's already interrupting. if (errno == EAGAIN || errno == EWOULDBLOCK) return; // Ignore spurious interrupt attempts right before/after threads enter their event loop. if (errno == EBADF && m_safe_fd == -1) return; throw internal_error("EventFd::send_signal() write failed: " + this_thread::thread_name_str() + " : " + std::string(std::strerror(errno))); default: throw internal_error("EventFd::send_signal() write returned unexpected value: " + this_thread::thread_name_str()); } } } void EventFd::event_read() { uint64_t value; while (true) { switch (::read(file_descriptor(), &value, sizeof(value))) { case sizeof(value): return; case 0: throw internal_error("EventFd::event_read() read returned 0: " + this_thread::thread_name_str()); case -1: if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) return; throw internal_error("EventFd::event_read() read failed: " + std::string(std::strerror(errno))); default: throw internal_error("EventFd::event_read() read returned unexpected value: " + this_thread::thread_name_str()); } } } void EventFd::event_write() { throw internal_error("EventFd::event_write() called but EventFd does not support writing."); } void EventFd::event_error() { throw internal_error("EventFd::event_error() called but EventFd does not support error events."); } } // namespace torrent::net libtorrent-0.16.17/src/net/event_fd.h000066400000000000000000000012021522271512000173620ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_EVENT_FD_H #define LIBTORRENT_NET_EVENT_FD_H #include "torrent/event.h" namespace torrent::net { class EventFd : public Event { public: EventFd() = default; const char* type_name() const override { return "eventfd"; } void add_to_poll(); void remove_from_poll(system::Poll* poll); void send_signal(); protected: void event_read() override; void event_write() override; void event_error() override; private: align_cacheline std::atomic m_safe_fd{-1}; }; } // namespace torrent::net #endif libtorrent-0.16.17/src/net/listen.cc000066400000000000000000000211311522271512000172270ustar00rootroot00000000000000#include "config.h" #include "listen.h" #include "manager.h" #include "protocol/handshake.h" #include "torrent/exceptions.h" #include "torrent/net/fd.h" #include "torrent/net/socket_address.h" #include "torrent/runtime/network_manager.h" #include "torrent/runtime/socket_manager.h" #include "torrent/system/poll.h" #include "torrent/utils/log.h" #define LT_LOG(log_fmt, ...) \ lt_log_print(torrent::LOG_CONNECTION_LISTEN, "listen: " log_fmt, __VA_ARGS__); namespace torrent { namespace { std::tuple listen_fd_open(const sockaddr* bind_address) { fd_flags open_flags = fd_flag_nonblock | fd_flag_reuse_address; if (bind_address->sa_family == AF_INET) open_flags |= fd_flag_v4only; int stream_fd = fd_open(fd_flag_stream | open_flags); if (stream_fd == -1) throw resource_error("Could not open stream socket for listening: " + std::string(strerror(errno))); int datagram_fd = fd_open(fd_flag_datagram | open_flags); if (datagram_fd == -1) { fd_close(stream_fd); throw resource_error("Could not open datagram socket for listening: " + std::string(strerror(errno))); } return std::make_tuple(stream_fd, datagram_fd); } std::tuple listen_open_range(uint16_t first, uint16_t last, const sockaddr* bind_address) { sa_unique_ptr try_address = sa_copy(bind_address); auto [stream_fd, datagram_fd] = listen_fd_open(bind_address); do { sa_set_port(try_address.get(), first); if (!fd_bind(stream_fd, try_address.get())) continue; // Try to bind the datagram socket to the same port to verify DHT availability. if (!fd_bind(datagram_fd, try_address.get())) { LT_LOG("failed to bind datagram socket on port %" PRIu16 " to verify DHT availability : %s", first, std::strerror(errno)); fd_close(stream_fd); fd_close(datagram_fd); std::tie(stream_fd, datagram_fd) = listen_fd_open(bind_address); continue; } fd_close(datagram_fd); return std::make_tuple(stream_fd, first); } while (first++ < last); fd_close(stream_fd); fd_close(datagram_fd); return std::make_tuple(-1, 0); } } bool Listen::open_single(Listen* listen, const sockaddr* bind_address, uint16_t first, uint16_t last, int backlog, bool block_ipv4in6) { listen->close(); if (first == 0 || first > last) throw input_error("Tried to open listening port with an invalid range"); if (bind_address->sa_family != AF_INET && bind_address->sa_family != AF_INET6) throw input_error("Listening socket must be inet or inet6 address type"); auto [listen_fd, listen_port] = listen_open_range(first, last, bind_address); if (listen_fd == -1) { LT_LOG("failed to find a suitable listen port : last error : %s", std::strerror(errno)); return false; } if (block_ipv4in6 && bind_address->sa_family == AF_INET6 && !fd_set_v6only(listen_fd, true)) { fd_close(listen_fd); LT_LOG("failed to set IPV6_V6ONLY on socket : %s", std::strerror(errno)); throw resource_error("Could not set IPV6_V6ONLY on socket: " + std::string(strerror(errno))); } if (!fd_listen(listen_fd, backlog)) { fd_close(listen_fd); LT_LOG("failed to listen on socket : %s", std::strerror(errno)); throw resource_error("Could not listen on socket: " + std::string(strerror(errno))); } listen->open_done(listen_fd, listen_port, backlog); return true; } bool Listen::open_both(Listen* listen_inet, Listen* listen_inet6, const sockaddr* bind_inet_address, const sockaddr* bind_inet6_address, uint16_t first, uint16_t last, int backlog, bool block_ipv4in6) { listen_inet->close(); listen_inet6->close(); if (first == 0 || first > last) throw input_error("Tried to open listening port with an invalid range"); if (bind_inet_address->sa_family != AF_INET) throw input_error("Listening ipv4 socket must be inet address type"); if (bind_inet6_address->sa_family != AF_INET6) throw input_error("Listening ipv6 socket must be inet6 address type"); int inet_fd{}, inet6_fd{}; uint16_t inet_port{}, inet6_port{}; while (true) { if (first > last) { LT_LOG("Unable to find suitable dual ipv4+6 listen ports in the given range", 0); return false; } std::tie(inet_fd, inet_port) = listen_open_range(first, last, bind_inet_address); if (inet_fd == -1) { LT_LOG("Unable to find a suitable ipv4 listen port : last error : %s", std::strerror(errno)); return false; } std::tie(inet6_fd, inet6_port) = listen_open_range(inet_port, inet_port, bind_inet6_address); if (inet6_fd != -1) break; fd_close(inet_fd); first = inet_port + 1; LT_LOG("Unable to find a suitable ipv6 listen port matching ipv4 port %" PRIu16 " : last error : %s", inet_port, std::strerror(errno)); } try { if (inet_port != inet6_port) throw internal_error("Listen::open_both(): ipv4 and ipv6 ports do not match."); if (!fd_listen(inet_fd, backlog)) { LT_LOG("failed to listen on ipv4 socket : %s", std::strerror(errno)); throw resource_error("Could not listen on ipv4 socket: " + std::string(strerror(errno))); } if (block_ipv4in6 && !fd_set_v6only(inet6_fd, true)) { LT_LOG("failed to set IPV6_V6ONLY on socket : %s", std::strerror(errno)); throw resource_error("Could not set IPV6_V6ONLY on socket: " + std::string(strerror(errno))); } if (!fd_listen(inet6_fd, backlog)) { LT_LOG("failed to listen on ipv6 socket : %s", std::strerror(errno)); throw resource_error("Could not listen on ipv6 socket: " + std::string(strerror(errno))); } } catch (...) { fd_close(inet_fd); fd_close(inet6_fd); throw; } listen_inet->open_done(inet_fd, inet_port, backlog); listen_inet6->open_done(inet6_fd, inet_port, backlog); return true; } void Listen::open_done(int fd, uint16_t port, int backlog) { set_file_descriptor(fd); m_port = port; runtime::socket_manager()->register_event_or_throw(this, runtime::category_internal, [this]() { this_thread::poll()->open(this); this_thread::poll()->insert_read(this); this_thread::poll()->insert_error(this); }); LT_LOG("listen opened: fd:%i port:%" PRIu16 " backlog:%i", file_descriptor(), m_port, backlog); } void Listen::close() { if (!is_open()) return; runtime::socket_manager()->unregister_event_or_throw(this, [this]() { this_thread::poll()->remove_and_close(this); fd_close(file_descriptor()); set_file_descriptor(-1); }); m_port = 0; } void Listen::event_read() { while (true) { auto handshake = std::make_unique(); // TODO: Optimize this by adding handshake immediately to poll and put the slot_accepted() call // outside of open_func(). int fd; sa_unique_ptr sa; std::tie(fd, sa) = fd_sap_accept(file_descriptor()); if (fd == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return; // Force a new event_read() call just to be sure we don't enter an infinite loop. if (errno == ECONNABORTED) return; throw resource_error("Listener port accept() failed: " + std::string(std::strerror(errno))); } auto open_func = [&]() { // TODO: Figure out a clean way of doing this. int tmp_fd = fd; fd = -1; m_slot_accepted(handshake, tmp_fd, sa.get()); }; auto cleanup_func = [fd, &handshake](bool opened) { LT_LOG("failed to accept incoming connection : socket manager triggered cleanup", 0); if (!opened) { fd_close(fd); return; } if (handshake && handshake->is_open()) handshake->destroy_connection(false); }; // TODO: This needs to be handled differently as open_ isn't done, we're doing accept_event_or_cleanup? Add a close callback? bool result = runtime::socket_manager()->open_event_or_cleanup(handshake.get(), runtime::category_generic, open_func, cleanup_func); // If the handshake connection or socket manager failed, don't continue accepting connections. // // This allows the event loop a chance to clear out any conflicts with reused file descriptors. if (!result) return; } } void Listen::event_write() { throw internal_error("Listener does not support write()."); } void Listen::event_error() { int socket_error; if (!fd_get_socket_error(file_descriptor(), &socket_error)) throw internal_error("Listener port could not get socket error: " + std::string(std::strerror(errno))); if (socket_error != 0) throw internal_error("Listener port received an error event: " + std::string(std::strerror(socket_error))); } } // namespace torrent libtorrent-0.16.17/src/net/listen.h000066400000000000000000000025471522271512000171030ustar00rootroot00000000000000#ifndef LIBTORRENT_LISTEN_H #define LIBTORRENT_LISTEN_H #include #include #include #include "torrent/event.h" namespace torrent { class Listen : public Event { public: ~Listen() override { close(); } const char* type_name() const override { return "listen"; } static bool open_single(Listen* listen, const sockaddr* bind_address, uint16_t first, uint16_t last, int backlog, bool block_ipv4in6); static bool open_both(Listen* listen_inet, Listen* listen_inet6, const sockaddr* bind_inet_address, const sockaddr* bind_inet6_address, uint16_t first, uint16_t last, int backlog, bool block_ipv4in6); void close(); uint16_t port() const { return m_port; } auto& slot_accepted() { return m_slot_accepted; } void event_read() override; void event_write() override; void event_error() override; private: using slot_accept_type = std::function& handshake, int, const sockaddr*)>; void open_done(int fd, uint16_t port, int backlog); uint16_t m_port{0}; slot_accept_type m_slot_accepted; }; } // namespace torrent #endif // LIBTORRENT_TORRENT_H libtorrent-0.16.17/src/net/protocol_buffer.h000066400000000000000000000153401522271512000207720ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_PROTOCOL_BUFFER_H #define LIBTORRENT_NET_PROTOCOL_BUFFER_H #include #include #include #include "torrent/exceptions.h" #include CUSTOM_ENDIAN_HEADER namespace torrent { template class ProtocolBuffer { public: using value_type = uint8_t; using iterator = value_type*; // TODO: Change to 32-bit, using size_type = uint16_t; using difference_type = int16_t; void reset() { m_position = m_end = begin(); } void reset_position() { m_position = m_buffer; } bool consume(difference_type v); void set_position_itr(iterator itr) { m_position = itr; } void set_end(size_type v) { m_end = m_buffer + v; } void set_end_itr(iterator itr) { m_end = itr; } difference_type move_end(difference_type v) { m_end += v; return v; } uint8_t read_8() { return *m_position++; } uint8_t peek_8() { return *m_position; } uint16_t read_16(); uint16_t peek_16(); uint32_t read_32(); uint32_t peek_32(); uint64_t read_64(); uint64_t peek_64(); uint8_t peek_8_at(size_type pos) { return *(m_position + pos); } template void read_range(Out first, Out last); template void read_len(Out start, unsigned int len); template inline T read_int(); template inline T peek_int(); void write_8(uint8_t v) { *m_end++ = v; validate_end(); } void write_16(uint16_t v); void write_32(uint32_t v); void write_32_n(uint32_t v); void write_64(uint64_t v); template inline void write_int(T t); template void write_range(In first, In last); template void write_len(In start, unsigned int len); iterator begin() { return m_buffer; } iterator position() { return m_position; } iterator end() { return m_end; } size_type size_position() const { return m_position - m_buffer; } size_type size_end() const { return m_end - m_buffer; } size_type remaining() const { return m_end - m_position; } size_type reserved() const { return tmpl_size; } size_type reserved_left() const { return reserved() - size_end(); } void move_unused(); void validate_position() const { #ifdef USE_EXTRA_DEBUG if (m_position > m_buffer + tmpl_size) throw internal_error("ProtocolBuffer tried to read beyond scope of the buffer."); if (m_position > m_end) throw internal_error("ProtocolBuffer tried to read beyond end of the buffer."); #endif } void validate_end() const { #ifdef USE_EXTRA_DEBUG if (m_end > m_buffer + tmpl_size) throw internal_error("ProtocolBuffer tried to write beyond scope of the buffer."); #endif } private: iterator m_position; iterator m_end; value_type m_buffer[tmpl_size]; }; template inline bool ProtocolBuffer::consume(difference_type v) { m_position += v; if (remaining()) return false; return true; } template inline uint16_t ProtocolBuffer::read_16() { uint16_t value; std::memcpy(&value, m_position, sizeof(uint16_t)); m_position += sizeof(uint16_t); validate_position(); return ntohs(value); } template inline uint16_t ProtocolBuffer::peek_16() { uint16_t value; std::memcpy(&value, m_position, sizeof(uint16_t)); return ntohs(value); } template inline uint32_t ProtocolBuffer::read_32() { uint32_t value; std::memcpy(&value, m_position, sizeof(uint32_t)); m_position += sizeof(uint32_t); validate_position(); return ntohl(value); } template inline uint32_t ProtocolBuffer::peek_32() { uint32_t value; std::memcpy(&value, m_position, sizeof(uint32_t)); return ntohl(value); } template inline uint64_t ProtocolBuffer::read_64() { uint64_t value; std::memcpy(&value, m_position, sizeof(uint64_t)); m_position += sizeof(uint64_t); validate_position(); return custom_ntohll(value); } template inline uint64_t ProtocolBuffer::peek_64() { uint64_t value; std::memcpy(&value, m_position, sizeof(uint64_t)); return custom_ntohll(value); } // TODO: Use hton functions for these as well? template inline void ProtocolBuffer::write_16(uint16_t v) { write_int(v); } template inline void ProtocolBuffer::write_32(uint32_t v) { write_int(v); } template inline void ProtocolBuffer::write_32_n(uint32_t v) { *reinterpret_cast(m_end) = v; m_end += sizeof(uint32_t); validate_end(); } template inline void ProtocolBuffer::write_64(uint64_t v) { write_int(v); } template template void ProtocolBuffer::read_range(Out first, Out last) { for ( ; first != last; ++m_position, ++first) *first = *m_position; validate_position(); } template template void ProtocolBuffer::read_len(Out start, unsigned int len) { for ( ; len > 0; ++m_position, ++start, --len) *start = *m_position; validate_position(); } template template inline void ProtocolBuffer::write_int(T v) { for (iterator itr = m_end + sizeof(T); itr != m_end; v >>= 8) *(--itr) = v; m_end += sizeof(T); validate_end(); } template template void ProtocolBuffer::write_range(In first, In last) { for ( ; first != last; ++m_end, ++first) *m_end = *first; validate_end(); } template template void ProtocolBuffer::write_len(In start, unsigned int len) { for ( ; len > 0; ++m_end, ++start, --len) *m_end = *start; validate_end(); } template void ProtocolBuffer::move_unused() { std::memmove(begin(), position(), remaining()); set_end(remaining()); reset_position(); } } // namespace torrent #endif libtorrent-0.16.17/src/net/proxy/000077500000000000000000000000001522271512000166055ustar00rootroot00000000000000libtorrent-0.16.17/src/net/proxy/proxy.h000066400000000000000000000014201522271512000201340ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_PROXY_PROXY_H #define LIBTORRENT_NET_PROXY_PROXY_H #include "torrent/common.h" #include "torrent/net/types.h" namespace torrent::net::proxy { enum { state_writing, state_reading, state_done, state_error }; class Proxy { public: // virtual ~Proxy() = 0; virtual ~Proxy(); const auto* proxy_address() const; virtual int next_action() = 0; // Returns the number of bytes consumed, or 0 if no bytes were consumed. virtual uint32_t read(const void* data, uint32_t size) = 0; virtual uint32_t write(void* data, uint32_t max_size) = 0; protected: sa_inet_union m_proxy_address{}; }; inline const auto* Proxy::proxy_address() const { return &m_proxy_address.sa; } } // namespace torrent::net::proxy #endif libtorrent-0.16.17/src/net/proxy/proxy_http.cc000066400000000000000000000054771522271512000213510ustar00rootroot00000000000000#include "config.h" #include "net/proxy/proxy_http.h" #include #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" namespace torrent::net::proxy { // TODO: For some reason we're crashing. Proxy::~Proxy() = default; ProxyHttp::ProxyHttp(const sockaddr* proxy_sa, const std::string& host, uint16_t port) : m_host(std::move(host)), m_port(port), m_state(state_writing){ sa_copy_to_inet_union(proxy_sa, m_proxy_address); assert(!sa_is_any(&m_proxy_address.sa) && sa_port(&m_proxy_address.sa) != 0); assert(!m_host.empty() && host.size() < 256 && m_port != 0); } ProxyHttp::~ProxyHttp() = default; int ProxyHttp::next_action() { return m_state; } uint32_t ProxyHttp::write(void* data, uint32_t max_size) { assert(m_state == state_writing); static const char part1[] = "CONNECT "; static const char part2[] = " HTTP/1.1\r\nHost: "; static const char part3[] = "\r\n\r\n"; auto host_port_str = m_host + ":" + std::to_string(m_port); uint32_t header_size = sizeof(part1) - 1 + host_port_str.size() + sizeof(part2) - 1 + host_port_str.size() + sizeof(part3) - 1; if (max_size < header_size) throw internal_error("ProxyHttp::write() buffer too small for proxy header."); auto ptr = data; std::memcpy(ptr, part1, sizeof(part1) - 1); ptr = static_cast(ptr) + sizeof(part1) - 1; std::memcpy(ptr, host_port_str.data(), host_port_str.size()); ptr = static_cast(ptr) + host_port_str.size(); std::memcpy(ptr, part2, sizeof(part2) - 1); ptr = static_cast(ptr) + sizeof(part2) - 1; std::memcpy(ptr, host_port_str.data(), host_port_str.size()); ptr = static_cast(ptr) + host_port_str.size(); std::memcpy(ptr, part3, sizeof(part3) - 1); ptr = static_cast(ptr) + sizeof(part3) - 1; assert(std::distance(static_cast(data), static_cast(ptr)) == header_size); m_state = state_reading; return header_size; } uint32_t ProxyHttp::read(const void* data, uint32_t size) { assert(m_state == state_reading); if (!m_verified_header) { if (size < 12) return 0; if (std::memcmp(data, "HTTP/1.1 200", 12) != 0 && std::memcmp(data, "HTTP/1.0 200", 12) != 0) { m_state = state_error; return 0; } m_verified_header = true; } if (size < 4) return 0; static const char* pattern = "\r\n\r\n"; auto data_start = static_cast(data); auto data_end = data_start + size; auto itr = std::search(data_start, data_end, pattern, pattern + 4); if (itr == data_end) return std::distance(data_start, itr) - 3; if (std::distance(itr, data_end) < 4) throw internal_error("ProxyHttp::read() found end of header but itr is too close to data_end."); m_state = state_done; return std::distance(data_start, itr) + 4; } } // namespace torrent::net::proxy libtorrent-0.16.17/src/net/proxy/proxy_http.h000066400000000000000000000012441522271512000211770ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_PROXY_PROXY_HTTP_H #define LIBTORRENT_NET_PROXY_PROXY_HTTP_H #include "net/proxy/proxy.h" namespace torrent::net::proxy { class ProxyHttp : public Proxy { public: ProxyHttp(const sockaddr* proxy_sa, const std::string& host, uint16_t port); ~ProxyHttp() override; int next_action() override; uint32_t write(void* data, uint32_t max_size) override; uint32_t read(const void* data, uint32_t size) override; private: std::string m_host; uint16_t m_port{}; int m_state{}; bool m_verified_header{}; }; } // namespace torrent::net::proxy #endif libtorrent-0.16.17/src/net/proxy/proxy_socks5.cc000066400000000000000000000117031522271512000215660ustar00rootroot00000000000000#include "config.h" #include "net/proxy/proxy_socks5.h" #include #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" namespace torrent::net::proxy { ProxySocks5::ProxySocks5(const sockaddr* proxy_sa, const sockaddr* connect_sa, std::string user, std::string password) : m_user(std::move(user)), m_password(std::move(password)) { sa_copy_to_inet_union(proxy_sa, m_proxy_address); sa_copy_to_inet_union(connect_sa, m_connect_address); m_connect_port = sa_port(connect_sa); assert(!sa_is_any(&m_proxy_address.sa) && sa_port(&m_proxy_address.sa) != 0); assert(!sa_is_any(&m_connect_address.sa) && m_connect_port != 0); assert(m_user.size() <= 255); assert(m_password.size() <= 255); } ProxySocks5::~ProxySocks5() = default; int ProxySocks5::next_action() { return m_state; } uint32_t ProxySocks5::write(void* data, uint32_t max_size) { assert(m_state == state_writing); auto* ptr = static_cast(data); switch (m_socks_state) { case socks_state::greeting_write: if (max_size < 4) throw internal_error("ProxySocks5::write() buffer too small for greeting header."); *ptr++ = 0x05; // SOCKS5 Version if (!m_user.empty()) { *ptr++ = 2; // 2 authentication methods offered *ptr++ = 0x00; // No Auth *ptr++ = 0x02; // User/Password Auth } else { *ptr++ = 1; // 1 authentication method offered *ptr++ = 0x00; // No Auth } m_socks_state = socks_state::greeting_read; break; case socks_state::auth_write: if (max_size < 3 + m_user.size() + m_password.size()) throw internal_error("ProxySocks5::write() buffer too small for auth header."); *ptr++ = 0x01; // Subnegotiation Version 1 *ptr++ = static_cast(m_user.size()); std::memcpy(ptr, m_user.data(), m_user.size()); ptr += m_user.size(); *ptr++ = static_cast(m_password.size()); std::memcpy(ptr, m_password.data(), m_password.size()); ptr += m_password.size(); m_socks_state = socks_state::auth_read; break; case socks_state::connect_write: if (max_size < 22) throw internal_error("ProxySocks5::write() buffer too small for connect header."); *ptr++ = 0x05; // SOCKS5 Version *ptr++ = 0x01; // Command: CONNECT *ptr++ = 0x00; // Reserved if (m_connect_address.sa.sa_family == AF_INET) { *ptr++ = 0x01; std::memcpy(ptr, &m_connect_address.inet.sin_addr.s_addr, 4); ptr += 4; } else if (m_connect_address.sa.sa_family == AF_INET6) { *ptr++ = 0x04; std::memcpy(ptr, &m_connect_address.inet6.sin6_addr.s6_addr, 16); ptr += 16; } else { throw internal_error("ProxySocks5::write() connect address family is invalid."); } *ptr++ = static_cast(m_connect_port >> 8); *ptr++ = static_cast(m_connect_port & 0xFF); m_socks_state = socks_state::connect_read; break; default: throw internal_error("ProxySocks5::write() entered unexpected write sub-state."); } m_state = state_reading; return std::distance(static_cast(data), ptr); } uint32_t ProxySocks5::read(const void* data, uint32_t size) { assert(m_state == state_reading); auto* bytes = static_cast(data); switch (m_socks_state) { case socks_state::greeting_read: if (size < 2) return 0; if (bytes[0] != 0x05) { m_state = state_error; return 0; } switch (bytes[1]) { case 0x00: m_state = state_writing; m_socks_state = socks_state::connect_write; return 2; case 0x02: if (m_user.empty()) { m_state = state_error; return 0; } m_state = state_writing; m_socks_state = socks_state::auth_write; return 2; default: m_state = state_error; return 0; } case socks_state::auth_read: if (size < 2) return 0; if (bytes[0] != 0x01 || bytes[1] != 0x00) { m_state = state_error; return 0; } m_state = state_writing; m_socks_state = socks_state::connect_write; return 2; case socks_state ::connect_read: if (size < 2) return 0; if (bytes[0] != 0x05 || bytes[1] != 0x00) { m_state = state_error; return 0; } // The minimum size is 4 bytes (version, reply, reserved, address type) and 2 bytes for the // port, plus address length. if (size < 4 + 2) return 0; size -= 6; switch (bytes[3]) { case 0x01: if (size < 4) return 0; m_state = state_done; return 6 + 4; case 0x04: if (size < 16) return 0; m_state = state_done; return 6 + 16; case 0x03: if (size < 1 + static_cast(bytes[4])) return 0; m_state = state_done; return 6 + 1 + static_cast(bytes[4]); default: m_state = state_error; return 0; } default: m_state = state_error; return 0; } } } // namespace torrent::net::proxy libtorrent-0.16.17/src/net/proxy/proxy_socks5.h000066400000000000000000000017641522271512000214360ustar00rootroot00000000000000#ifndef TORRENT_NET_PROXY_PROXY_SOCKS5_H #define TORRENT_NET_PROXY_PROXY_SOCKS5_H #include "net/proxy/proxy.h" #include namespace torrent::net::proxy { class ProxySocks5 : public Proxy { public: ProxySocks5(const sockaddr* proxy_sa, const sockaddr* connect_sa, std::string user = "", std::string password = ""); ~ProxySocks5() override; int next_action() override; uint32_t write(void* data, uint32_t max_size) override; uint32_t read(const void* data, uint32_t size) override; private: enum class socks_state { greeting_write, greeting_read, auth_write, auth_read, connect_write, connect_read }; sa_inet_union m_connect_address{}; uint16_t m_connect_port{}; std::string m_user; std::string m_password; int m_state{state_writing}; socks_state m_socks_state{socks_state::greeting_write}; }; } // namespace torrent::net::proxy #endif libtorrent-0.16.17/src/net/socket_datagram.cc000066400000000000000000000025511522271512000210660ustar00rootroot00000000000000#include "config.h" #include "socket_datagram.h" #include #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" namespace torrent { SocketDatagram::~SocketDatagram() = default; int SocketDatagram::read_datagram(void* buffer, unsigned int length) { if (length == 0) throw internal_error("Tried to receive buffer length 0"); return ::recv(m_fileDesc, buffer, length, 0); } int SocketDatagram::write_datagram(const void* buffer, unsigned int length) { if (length == 0) throw internal_error("Tried to send buffer length 0"); return ::send(m_fileDesc, buffer, length, 0); } int SocketDatagram::read_datagram_sa(void* buffer, unsigned int length, sockaddr* from_sa, socklen_t from_length) { if (length == 0) throw internal_error("Tried to receive buffer length 0"); if (from_sa == nullptr) throw internal_error("Tried to receive datagram with NULL sockaddr pointer"); return ::recvfrom(m_fileDesc, buffer, length, 0, from_sa, &from_length); } int SocketDatagram::write_datagram_sa(const void* buffer, unsigned int length, const sockaddr* sa) { if (length == 0) throw internal_error("Tried to send buffer length 0"); int r; if (sa != nullptr) r = ::sendto(m_fileDesc, buffer, length, 0, sa, sa_length(sa)); else r = ::send(m_fileDesc, buffer, length, 0); return r; } } // namespace torrent libtorrent-0.16.17/src/net/socket_datagram.h000066400000000000000000000011721522271512000207260ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_SOCKET_DGRAM_H #define LIBTORRENT_NET_SOCKET_DGRAM_H #include #include "torrent/event.h" namespace torrent { class SocketDatagram : public Event { public: ~SocketDatagram() override; int read_datagram(void* buffer, unsigned int length); int write_datagram(const void* buffer, unsigned int length); int read_datagram_sa(void* buffer, unsigned int length, sockaddr* from_sa, socklen_t from_length); int write_datagram_sa(const void* buffer, unsigned int length, const sockaddr* sa); }; } // namespace torrent #endif libtorrent-0.16.17/src/net/socket_stream.cc000066400000000000000000000022211522271512000205730ustar00rootroot00000000000000#include "config.h" #include "socket_stream.h" namespace torrent { char* SocketStream::m_nullBuffer = new char[SocketStream::null_buffer_size]; SocketStream::~SocketStream() = default; uint32_t SocketStream::read_stream_throws(void* buf, uint32_t length) { int r = read_stream(buf, length); if (r == 0) throw close_connection(); if (r < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return 0; else if (errno == ECONNRESET || errno == ECONNABORTED) throw close_connection(); else if (errno == EDEADLK) throw blocked_connection(); else throw connection_error(errno); } return r; } uint32_t SocketStream::write_stream_throws(const void* buf, uint32_t length) { int r = write_stream(buf, length); if (r == 0) throw close_connection(); if (r < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return 0; else if (errno == ECONNRESET || errno == ECONNABORTED) throw close_connection(); else if (errno == EDEADLK) throw blocked_connection(); else throw connection_error(errno); } return r; } } // namespace torrent libtorrent-0.16.17/src/net/socket_stream.h000066400000000000000000000036521522271512000204460ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_SOCKET_STREAM_H #define LIBTORRENT_NET_SOCKET_STREAM_H #include #include #include "torrent/event.h" #include "torrent/exceptions.h" namespace torrent { class SocketStream : public Event { public: ~SocketStream() override; int read_stream(void* buf, uint32_t length); int write_stream(const void* buf, uint32_t length); // Returns the number of bytes read, or zero if the socket is // blocking. On errors or closed sockets it will throw an // appropriate exception. uint32_t read_stream_throws(void* buf, uint32_t length); uint32_t write_stream_throws(const void* buf, uint32_t length); // Handles all the error catching etc. Returns true if the buffer is // finished reading/writing. bool read_buffer(void* buf, uint32_t length, uint32_t& pos); bool write_buffer(const void* buf, uint32_t length, uint32_t& pos); uint32_t ignore_stream_throws(uint32_t length) { return read_stream_throws(m_nullBuffer, length); } protected: static constexpr size_t null_buffer_size = 1 << 17; static char* m_nullBuffer; }; inline bool SocketStream::read_buffer(void* buf, uint32_t length, uint32_t& pos) { pos += read_stream_throws(buf, length - pos); return pos == length; } inline bool SocketStream::write_buffer(const void* buf, uint32_t length, uint32_t& pos) { pos += write_stream_throws(buf, length - pos); return pos == length; } inline int SocketStream::read_stream(void* buf, uint32_t length) { if (length == 0) throw internal_error("Tried to read to buffer length 0."); return ::recv(m_fileDesc, buf, length, 0); } inline int SocketStream::write_stream(const void* buf, uint32_t length) { if (length == 0) throw internal_error("Tried to write to buffer length 0."); return ::send(m_fileDesc, buf, length, 0); } } // namespace torrent #endif libtorrent-0.16.17/src/net/thread_net.cc000066400000000000000000000103701522271512000200510ustar00rootroot00000000000000#include "config.h" #include "net/thread_net.h" #include "net/curl_stack.h" #include "net/dns_buffer.h" #include "net/dns_cache.h" #include "net/udns_resolver.h" #include "torrent/exceptions.h" #include "torrent/net/http_stack.h" #include "torrent/runtime/socket_manager.h" #include "torrent/system/callbacks.h" #include "utils/instrumentation.h" namespace torrent { class ThreadNetInternal { public: static net::HttpStack* http_stack() { return ThreadNet::thread_net()->http_stack(); } }; namespace net_thread { torrent::system::Thread* thread() { return ThreadNet::thread_net(); } std::thread::id thread_id() { return ThreadNet::thread_net()->thread_id(); } void callback(std::function&& fn) { ThreadNet::thread_net()->callback(std::move(fn)); } void callback(system::callback_id& id, std::function&& fn) { ThreadNet::thread_net()->callback(id, std::move(fn)); } void callback_interrupt(std::function&& fn) { ThreadNet::thread_net()->callback_interrupt(std::move(fn)); } void callback_interrupt(system::callback_id& id, std::function&& fn) { ThreadNet::thread_net()->callback_interrupt(id, std::move(fn)); } void cancel_callback(system::callback_id& id) { ThreadNet::thread_net()->cancel_callback(id); } torrent::net::HttpStack* http_stack() { return ThreadNetInternal::http_stack(); } } // namespace net_thread namespace { uint32_t calculate_http_host_connections(uint32_t max_size) { if (max_size >= 16384) return 3; else if (max_size >= 8096) return 2; else return 1; } } // namespace ThreadNet* ThreadNet::m_thread_net{}; ThreadNet::~ThreadNet() = default; void ThreadNet::create_thread() { auto thread = new ThreadNet; thread->m_events_callback_id = system::make_callback_id(); thread->m_http_stack = std::make_unique(thread); thread->m_dns_buffer = std::make_unique(); thread->m_dns_cache = std::make_unique(); thread->m_dns_resolver = std::make_unique(); m_thread_net = thread; } void ThreadNet::destroy_thread() { try { delete m_thread_net; m_thread_net = nullptr; } catch (...) { m_thread_net = nullptr; } } ThreadNet* ThreadNet::thread_net() { return m_thread_net; } void ThreadNet::init_thread() { m_state = STATE_INITIALIZED; m_instrumentation_index = INSTRUMENTATION_POLLING_DO_POLL_NET - INSTRUMENTATION_POLLING_DO_POLL; set_max_connections(); } void ThreadNet::init_thread_post_local() { m_dns_resolver->initialize(this); runtime::socket_manager()->subscribe_to_changes(this, [this]() { callback(m_events_callback_id, ThreadNet::set_max_connections); }); } void ThreadNet::cleanup_thread() { runtime::socket_manager()->unsubscribe_from_changes(this); m_http_stack->shutdown(); m_dns_resolver->cleanup(); } void ThreadNet::set_max_connections() { // TODO: Also set max cache connections? auto total_size = runtime::socket_manager()->category_max_size(runtime::category_http); auto host_size = calculate_http_host_connections(runtime::socket_manager()->max_size()); ThreadNet::thread_net()->m_http_stack->set_max_total_connections(total_size); ThreadNet::thread_net()->m_http_stack->set_max_host_connections(host_size); } void ThreadNet::call_events() { // lt_log_print_locked(torrent::LOG_THREAD_NOTICE, "Got thread_disk tick."); while (m_http_stack->curl_stack()->process_done_handle()) ; // Do nothing. // TODO: Consider moving this into timer events instead. if ((m_flags & flag_do_shutdown)) { if ((m_flags & flag_did_shutdown)) throw internal_error("Already trigged shutdown."); m_flags |= flag_did_shutdown; throw shutdown_exception(); } process_callbacks(); m_dns_resolver->flush(); process_callbacks(); } std::chrono::microseconds ThreadNet::next_timeout() { return std::chrono::microseconds(10s); } } // namespace torrent libtorrent-0.16.17/src/net/thread_net.h000066400000000000000000000033531522271512000177160ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_THREAD_NET_H #define LIBTORRENT_NET_THREAD_NET_H #include "torrent/common.h" #include "torrent/system/thread.h" namespace torrent { namespace net { class DnsBuffer; class DnsCache; class HttpStack; class UdnsResolver; } // namespace net class LIBTORRENT_EXPORT ThreadNet : public system::Thread { public: ~ThreadNet() override; static void create_thread(); static void destroy_thread(); static ThreadNet* thread_net(); const char* name() const override { return "rtorrent-net"; } void init_thread() override; void init_thread_post_local() override; void cleanup_thread() override; protected: friend class ThreadNetInternal; friend class torrent::net::DnsCache; friend class torrent::net::DnsBuffer; friend class torrent::net::HttpStack; friend class torrent::net::Resolver; void call_events() override; std::chrono::microseconds next_timeout() override; net::HttpStack* http_stack() const { return m_http_stack.get(); } net::DnsBuffer* dns_buffer() const { return m_dns_buffer.get(); } net::DnsCache* dns_cache() const { return m_dns_cache.get(); } net::UdnsResolver* dns_resolver() const { return m_dns_resolver.get(); } private: ThreadNet() = default; static void set_max_connections(); static ThreadNet* m_thread_net; system::callback_id m_events_callback_id; std::unique_ptr m_http_stack; std::unique_ptr m_dns_buffer; std::unique_ptr m_dns_cache; std::unique_ptr m_dns_resolver; }; } // namespace torrent #endif // LIBTORRENT_NET_THREAD_NET_H libtorrent-0.16.17/src/net/throttle_handle.h000066400000000000000000000033771522271512000207670ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2007, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_NET_THROTTLE_HANDLE_H #define LIBTORRENT_NET_THROTTLE_HANDLE_H namespace torrent { class ThrottleHandle { public: libtorrent-0.16.17/src/net/throttle_internal.cc000066400000000000000000000073141522271512000215010ustar00rootroot00000000000000#include "config.h" #include "net/throttle_internal.h" #include #include "net/throttle_list.h" #include "torrent/exceptions.h" namespace torrent { // Plans: // // Make ThrottleList do a callback when it needs more? This would // allow us to remove us from the task scheduler when we're full. Also // this would let us be abit more flexible with the interval. ThrottleInternal::ThrottleInternal(int flags) : m_flags(flags), m_next_slave(m_slave_list.end()), m_time_last_tick(torrent::this_thread::cached_time()) { if (is_root()) m_task_tick.slot() = [this] { receive_tick(); }; } ThrottleInternal::~ThrottleInternal() { if (is_root()) torrent::this_thread::scheduler()->erase(&m_task_tick); for (const auto& t : m_slave_list) delete t; } void ThrottleInternal::enable() { m_throttleList->enable(); for (auto t : m_slave_list) t->m_throttleList->enable(); if (is_root()) { // We need to start the ticks, and make sure we set timeLastTick // to a value that gives an reasonable initial quota. m_time_last_tick = std::max(torrent::this_thread::cached_time() - 1s, 0us); receive_tick(); } } void ThrottleInternal::disable() { for (auto t : m_slave_list) t->m_throttleList->disable(); m_throttleList->disable(); if (is_root()) torrent::this_thread::scheduler()->erase(&m_task_tick); } ThrottleInternal* ThrottleInternal::create_slave() { auto slave = new ThrottleInternal(flag_none); slave->m_maxRate = m_maxRate; slave->m_throttleList = new ThrottleList(); if (m_throttleList->is_enabled()) slave->enable(); m_slave_list.push_back(slave); m_next_slave = m_slave_list.end(); return slave; } void ThrottleInternal::receive_tick() { if (torrent::this_thread::cached_time() < m_time_last_tick + 90ms) throw internal_error("ThrottleInternal::receive_tick() called at a to short interval."); uint64_t count_usec = (torrent::this_thread::cached_time() - m_ptr()->m_time_last_tick).count(); uint32_t quota = count_usec * m_maxRate / 1000000; uint32_t fraction = count_usec * fraction_base / 1000000; receive_quota(quota, fraction); torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_task_tick, std::chrono::microseconds(calculate_interval())); m_time_last_tick = torrent::this_thread::cached_time(); } int32_t ThrottleInternal::receive_quota(uint32_t quota, uint32_t fraction) { m_unused_quota += quota; while (m_next_slave != m_slave_list.end()) { // TODO: Add logging and see if the use of 'quota' below is causing the quota to be // over-allocated to the next slave, instead of a smoother distribution. // // E.g. we might want to spread it over the next few slaves, especially if we have a very large // number. uint32_t need = std::min(quota, static_cast(fraction) * (*m_next_slave)->max_rate() >> fraction_bits); if (need > m_unused_quota) break; m_unused_quota -= (*m_next_slave)->receive_quota(need, fraction); m_throttleList->add_rate((*m_next_slave)->throttle_list()->rate_added()); ++m_next_slave; } uint32_t need = std::min(quota, static_cast(fraction) * m_maxRate >> fraction_bits); if (m_next_slave == m_slave_list.end() && need <= m_unused_quota) { m_unused_quota -= m_throttleList->update_quota(need); m_next_slave = m_slave_list.begin(); } // Return how much quota we used, but keep as much as one allocation's worth until the next tick // to avoid rounding errors. int32_t used = quota; if (quota < m_unused_quota) { used -= m_unused_quota - quota; m_unused_quota = quota; } // Amount used may be negative if rate changed between last tick and now. return used; } } // namespace torrent libtorrent-0.16.17/src/net/throttle_internal.h000066400000000000000000000025171522271512000213430ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_THROTTLE_INTERNAL_H #define LIBTORRENT_NET_THROTTLE_INTERNAL_H #include #include "torrent/common.h" #include "torrent/throttle.h" #include "torrent/utils/scheduler.h" namespace torrent { class ThrottleInternal : public Throttle { public: static constexpr int flag_none = 0; static constexpr int flag_root = 1; ThrottleInternal(int flags); ~ThrottleInternal(); ThrottleInternal* create_slave(); bool is_root() const { return m_flags & flag_root; } void enable(); void disable(); private: // Fraction is a fixed-precision value with the given number of bits after the decimal point. static constexpr uint32_t fraction_bits = 16; static constexpr uint32_t fraction_base = (1 << fraction_bits); using SlaveList = std::vector; void receive_tick(); // Distribute quota, return amount of quota used. May be negative // if it had more unused quota than is now allowed. int32_t receive_quota(uint32_t quota, uint32_t fraction); int m_flags; SlaveList m_slave_list; SlaveList::iterator m_next_slave; uint32_t m_unused_quota{0}; std::chrono::microseconds m_time_last_tick; utils::SchedulerEntry m_task_tick; }; } // namespace torrent #endif libtorrent-0.16.17/src/net/throttle_list.cc000066400000000000000000000136461522271512000206450ustar00rootroot00000000000000#include "config.h" #include #include #include #include "throttle_list.h" #include "throttle_node.h" namespace torrent { bool ThrottleList::is_active(const ThrottleNode* node) const { return std::find(begin(), const_iterator(m_splitActive), node) != m_splitActive; } bool ThrottleList::is_inactive(const ThrottleNode* node) const { return std::find(const_iterator(m_splitActive), end(), node) != end(); } bool ThrottleList::is_throttled(const ThrottleNode* node) const { return node->list_iterator() != end(); } // The quota already present in the node is preserved and unallocated // quota is transferred to the node. The node's quota will be less // than or equal to 'm_minChunkSize'. inline void ThrottleList::allocate_quota(ThrottleNode* node) { if (node->quota() >= m_minChunkSize) return; int quota = std::min(m_maxChunkSize - node->quota(), m_unallocatedQuota); node->set_quota(node->quota() + quota); m_outstandingQuota += quota; m_unallocatedQuota -= quota; } void ThrottleList::enable() { if (m_enabled) return; m_enabled = true; if (!empty() && m_splitActive == begin()) throw internal_error("ThrottleList::enable() m_splitActive is invalid."); } void ThrottleList::disable() { if (!m_enabled) return; m_enabled = false; m_outstandingQuota = 0; m_unallocatedQuota = 0; m_unusedUnthrottledQuota = 0; std::for_each(begin(), end(), std::mem_fn(&ThrottleNode::clear_quota)); std::for_each(m_splitActive, end(), std::mem_fn(&ThrottleNode::activate)); m_splitActive = end(); } int32_t ThrottleList::update_quota(uint32_t quota) { if (!m_enabled) throw internal_error("ThrottleList::update_quota(...) called but the object is not enabled."); // Distribute new quota to unthrottled quota first, and use // left-over unthrottled quota from last turn to be allocated // to throttled nodes this turn. // When distributing, we include the unallocated quota from the // previous turn. This will ensure that quota that was reclaimed // will have a chance of being used, even by those nodes that were // deactivated. m_unallocatedQuota += m_unusedUnthrottledQuota; m_unusedUnthrottledQuota = quota; // Add remaining to the next, even when less than activate border. while (m_splitActive != end()) { allocate_quota(*m_splitActive); if ((*m_splitActive)->quota() < m_minChunkSize) break; (*m_splitActive)->activate(); m_splitActive++; } // Use 'quota' as an upper bound to avoid accumulating unused quota // over time. Return actually used amount of quota. int32_t used = quota; if (m_unallocatedQuota > quota) { used -= m_unallocatedQuota - quota; m_unallocatedQuota = quota; } return used; } uint32_t ThrottleList::node_quota(ThrottleNode* node) const { if (!m_enabled) { // Returns max for signed integer to ensure we don't overflow // calculations. return std::numeric_limits::max(); } else if (!is_active(node)) { throw internal_error(is_inactive(node) ? "ThrottleList::node_quota(...) called on an inactive node." : "ThrottleList::node_quota(...) could not find node."); } else if (node->quota() + m_unallocatedQuota >= m_minChunkSize) { return node->quota() + m_unallocatedQuota; } else { return 0; } } void ThrottleList::add_rate(uint32_t used) { m_rateSlow.insert(used); m_rateAdded += used; } uint32_t ThrottleList::node_used(ThrottleNode* node, uint32_t used) { add_rate(used); node->rate()->insert(used); if (used == 0 || !m_enabled || node->list_iterator() == end()) return used; uint32_t quota = std::min(used, node->quota()); if (quota > m_outstandingQuota) throw internal_error("ThrottleList::node_used(...) used too much quota."); node->set_quota(node->quota() - quota); m_outstandingQuota -= quota; m_unallocatedQuota -= std::min(used - quota, m_unallocatedQuota); return used; } uint32_t ThrottleList::node_used_unthrottled(uint32_t used) { add_rate(used); // Use what we can from the unthrottled quota, // if node used too much borrow from throttled quota. uint32_t avail = std::min(used, m_unusedUnthrottledQuota); m_unusedUnthrottledQuota -= avail; m_unallocatedQuota -= std::min(used - avail, m_unallocatedQuota); return used; } void ThrottleList::node_deactivate(ThrottleNode* node) { if (!is_active(node)) throw internal_error(is_inactive(node) ? "ThrottleList::node_deactivate(...) called on an inactive node." : "ThrottleList::node_deactivate(...) could not find node."); base_type::splice(end(), *this, node->list_iterator()); if (m_splitActive == end()) m_splitActive = node->list_iterator(); } void ThrottleList::insert(ThrottleNode* node) { if (node->list_iterator() != end()) return; if (!m_enabled) { // Add to waiting queue. node->set_list_iterator(base_type::insert(end(), node)); node->clear_quota(); } else { // Add before the active split, so if we only need to decrement // m_splitActive to change the queue it is in. node->set_list_iterator(base_type::insert(m_splitActive, node)); allocate_quota(node); } m_size++; } void ThrottleList::erase(ThrottleNode* node) { if (node->list_iterator() == end()) return; if (m_size == 0) throw internal_error("ThrottleList::erase(...) called on an empty list."); // Do we need an if-statement here? if (node->quota() != 0) { if (node->quota() > m_outstandingQuota) throw internal_error("ThrottleList::erase(...) node->quota() > m_outstandingQuota."); m_outstandingQuota -= node->quota(); m_unallocatedQuota += node->quota(); } if (node->list_iterator() == m_splitActive) m_splitActive = base_type::erase(node->list_iterator()); else base_type::erase(node->list_iterator()); node->clear_quota(); node->set_list_iterator(end()); m_size--; } } // namespace torrent libtorrent-0.16.17/src/net/throttle_list.h000066400000000000000000000065771522271512000205140ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_THROTTLE_LIST_H #define LIBTORRENT_NET_THROTTLE_LIST_H #include #include "torrent/rate.h" // To allow conditional compilation depending on whether this patch is applied or not. #define LIBTORRENT_CUSTOM_THROTTLES 1 namespace torrent { class ThrottleNode; class ThrottleList : private std::list { public: using base_type = std::list; using base_type::iterator; using base_type::const_iterator; using base_type::reverse_iterator; using base_type::clear; using base_type::size; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; bool is_enabled() const { return m_enabled; } bool is_active(const ThrottleNode* node) const; bool is_inactive(const ThrottleNode* node) const; bool is_throttled(const ThrottleNode* node) const; // When disabled all nodes are active at all times. void enable(); void disable(); // Returns the amount of quota used. May be negative if it had unused // quota left over from the last call that was more than is now allowed. int32_t update_quota(uint32_t quota); uint32_t size() const { return m_size; } uint32_t outstanding_quota() const { return m_outstandingQuota; } uint32_t unallocated_quota() const { return m_unallocatedQuota; } uint32_t min_chunk_size() const { return m_minChunkSize; } void set_min_chunk_size(uint32_t v) { m_minChunkSize = v; } uint32_t max_chunk_size() const { return m_maxChunkSize; } void set_max_chunk_size(uint32_t v) { m_maxChunkSize = v; } uint32_t node_quota(ThrottleNode* node) const; uint32_t node_used(ThrottleNode* node, uint32_t used); // both node_used functions uint32_t node_used_unthrottled(uint32_t used); // return the "used" argument void node_deactivate(ThrottleNode* node); const Rate* rate_slow() const { return &m_rateSlow; } void add_rate(uint32_t used); uint32_t rate_added() { uint32_t v = m_rateAdded; m_rateAdded = 0; return v; } // It is asumed that inserted nodes are currently active. It won't // matter if they do not get any initial quota as a later activation // of an active node should be safe. void insert(ThrottleNode* node); void erase(ThrottleNode* node); private: inline void allocate_quota(ThrottleNode* node); bool m_enabled{false}; uint32_t m_size{0}; uint32_t m_outstandingQuota{0}; uint32_t m_unallocatedQuota{0}; uint32_t m_unusedUnthrottledQuota{0}; uint32_t m_rateAdded{0}; uint32_t m_minChunkSize{2 << 10}; uint32_t m_maxChunkSize{16 << 10}; Rate m_rateSlow{60}; // [m_splitActive,end> contains nodes that are inactive and need // more quote, sorted from the most urgent // node. [begin,m_splitActive> holds nodes with a large enough quota // to transmit, but are blocking. These are sorted from the longest // blocking node. iterator m_splitActive{end()}; }; } // namespace torrent #endif libtorrent-0.16.17/src/net/throttle_node.h000066400000000000000000000030011522271512000204410ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_THROTTLE_NODE_H #define LIBTORRENT_NET_THROTTLE_NODE_H #include #include "torrent/rate.h" #include "throttle_list.h" namespace torrent { class ThrottleNode { public: using iterator = ThrottleList::iterator; using const_iterator = ThrottleList::const_iterator; using slot_void = std::function; ThrottleNode(uint32_t rateSpan) : m_rate(rateSpan) { clear_quota(); } ~ThrottleNode() = default; Rate* rate() { return &m_rate; } const Rate* rate() const { return &m_rate; } uint32_t quota() const { return m_quota; } void clear_quota() { m_quota = 0; } void set_quota(uint32_t q) { m_quota = q; } iterator list_iterator() { return m_listIterator; } const_iterator list_iterator() const { return m_listIterator; } void set_list_iterator(iterator itr) { m_listIterator = itr; } void activate() { if (m_slot_activate) m_slot_activate(); } slot_void& slot_activate() { return m_slot_activate; } private: ThrottleNode(const ThrottleNode&) = delete; ThrottleNode& operator=(const ThrottleNode&) = delete; uint32_t m_quota; iterator m_listIterator; Rate m_rate; slot_void m_slot_activate; }; } // namespace torrent #endif libtorrent-0.16.17/src/net/udns/000077500000000000000000000000001522271512000163755ustar00rootroot00000000000000libtorrent-0.16.17/src/net/udns/.clang-tidy000066400000000000000000000000661522271512000204330ustar00rootroot00000000000000--- # Disable all checks in this folder. Checks: '-*' libtorrent-0.16.17/src/net/udns/COPYING.LGPL000066400000000000000000000636421522271512000202000ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! libtorrent-0.16.17/src/net/udns/config.h000066400000000000000000000002111522271512000200050ustar00rootroot00000000000000/* automatically generated by configure. */ #define HAVE_GETOPT 1 #define HAVE_INET_PTON_NTOP 1 #define HAVE_IPv6 1 #define HAVE_POLL 1 libtorrent-0.16.17/src/net/udns/dnsget.c000066400000000000000000000521671522271512000200400ustar00rootroot00000000000000/* dnsget.c simple host/dig-like application using UDNS library Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef WINDOWS #include #include #else #include #include #include #include #include #endif #include #include #include #include #include #include #include "udns.h" #ifndef HAVE_GETOPT # include "getopt.c" #endif #ifndef AF_INET6 # define AF_INET6 10 #endif static char *progname; static int verbose = 1; static int errors; static int notfound; /* verbosity level: * <0 - bare result * 0 - bare result and error messages * 1 - readable result * 2 - received packet contents and `trying ...' stuff * 3 - sent and received packet contents */ static void die(int errnum, const char *fmt, ...) { va_list ap; fprintf(stderr, "%s: ", progname); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (errnum) fprintf(stderr, ": %s\n", strerror(errnum)); else putc('\n', stderr); fflush(stderr); exit(1); } static const char *dns_xntop(int af, const void *src) { static char buf[6*5+4*4]; return dns_ntop(af, src, buf, sizeof(buf)); } struct query { const char *name; /* original query string */ unsigned char *dn; /* the DN being looked up */ enum dns_type qtyp; /* type of the query */ }; static void query_free(struct query *q) { free(q->dn); free(q); } static struct query * query_new(const char *name, const unsigned char *dn, enum dns_type qtyp) { struct query *q = malloc(sizeof(*q)); unsigned l = dns_dnlen(dn); unsigned char *cdn = malloc(l); if (!q || !cdn) die(0, "out of memory"); memcpy(cdn, dn, l); q->name = name; q->dn = cdn; q->qtyp = qtyp; return q; } static enum dns_class qcls = DNS_C_IN; static void dnserror(struct query *q, int errnum) { if (verbose >= 0) fprintf(stderr, "%s: unable to lookup %s record for %s: %s\n", progname, dns_typename(q->qtyp), dns_dntosp(q->dn), dns_strerror(errnum)); if (errnum == DNS_E_NXDOMAIN || errnum == DNS_E_NODATA) ++notfound; else ++errors; query_free(q); } static const unsigned char * printtxt(const unsigned char *c) { unsigned n = *c++; const unsigned char *e = c + n; if (verbose > 0) while(c < e) { if (*c < ' ' || *c >= 127) printf("\\%03u", *c); else if (*c == '\\' || *c == '"') printf("\\%c", *c); else putchar(*c); ++c; } else fwrite(c, n, 1, stdout); return e; } static void printhex(const unsigned char *c, const unsigned char *e) { while(c < e) printf("%02x", *c++); } static unsigned char to_b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static void printb64(const unsigned char *c, const unsigned char *e) { while(c < e) { putchar(to_b64[c[0] >> 2]); if (c+1 < e) { putchar(to_b64[(c[0] & 0x3) << 4 | c[1] >> 4]); if (c+2 < e) { putchar(to_b64[(c[1] & 0xf) << 2 | c[2] >> 6]); putchar(to_b64[c[2] & 0x3f]); } else { putchar(to_b64[(c[1] & 0xf) << 2]); putchar('='); break; } } else { putchar(to_b64[(c[0] & 0x3) << 4]); putchar('='); putchar('='); break; } c += 3; } } static void printdate(time_t time) { struct tm *tm = gmtime(&time); printf("%04d%02d%02d%02d%02d%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } static void printrr(const struct dns_parse *p, struct dns_rr *rr) { const unsigned char *pkt = p->dnsp_pkt; const unsigned char *end = p->dnsp_end; const unsigned char *dptr = rr->dnsrr_dptr; const unsigned char *dend = rr->dnsrr_dend; unsigned char *dn = rr->dnsrr_dn; const unsigned char *c; unsigned n; if (verbose > 0) { if (verbose > 1) { if (!p->dnsp_rrl && !rr->dnsrr_dn[0] && rr->dnsrr_typ == DNS_T_OPT) { printf(";EDNS%d OPT record (UDPsize: %d, ERcode: %d, Flags: 0x%02x): %d bytes\n", (rr->dnsrr_ttl>>16) & 0xff, /* version */ rr->dnsrr_cls, /* udp size */ (rr->dnsrr_ttl>>24) & 0xff, /* extended rcode */ rr->dnsrr_ttl & 0xffff, /* flags */ rr->dnsrr_dsz); return; } n = printf("%s.", dns_dntosp(rr->dnsrr_dn)); printf("%s%u\t%s\t%s\t", n > 15 ? "\t" : n > 7 ? "\t\t" : "\t\t\t", rr->dnsrr_ttl, dns_classname(rr->dnsrr_cls), dns_typename(rr->dnsrr_typ)); } else printf("%s. %s ", dns_dntosp(rr->dnsrr_dn), dns_typename(rr->dnsrr_typ)); } switch(rr->dnsrr_typ) { case DNS_T_CNAME: case DNS_T_PTR: case DNS_T_NS: case DNS_T_MB: case DNS_T_MD: case DNS_T_MF: case DNS_T_MG: case DNS_T_MR: if (dns_getdn(pkt, &dptr, end, dn, DNS_MAXDN) <= 0) goto xperr; printf("%s.", dns_dntosp(dn)); break; case DNS_T_A: if (rr->dnsrr_dsz != 4) goto xperr; printf("%d.%d.%d.%d", dptr[0], dptr[1], dptr[2], dptr[3]); break; case DNS_T_AAAA: if (rr->dnsrr_dsz != 16) goto xperr; printf("%s", dns_xntop(AF_INET6, dptr)); break; case DNS_T_MX: c = dptr + 2; if (dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0 || c != dend) goto xperr; printf("%d %s.", dns_get16(dptr), dns_dntosp(dn)); break; case DNS_T_TXT: /* first verify it */ for(c = dptr; c < dend; c += n) { n = *c++; if (c + n > dend) goto xperr; } c = dptr; n = 0; while (c < dend) { if (verbose > 0) printf(n++ ? "\" \"":"\""); c = printtxt(c); } if (verbose > 0) putchar('"'); break; case DNS_T_HINFO: /* CPU, OS */ c = dptr; n = *c++; if ((c += n) >= dend) goto xperr; n = *c++; if ((c += n) != dend) goto xperr; c = dptr; if (verbose > 0) putchar('"'); c = printtxt(c); if (verbose > 0) printf("\" \""); else putchar(' '); printtxt(c); if (verbose > 0) putchar('"'); break; case DNS_T_WKS: c = dptr; if (dptr + 4 + 2 >= end) goto xperr; printf("%s %d", dns_xntop(AF_INET, dptr), dptr[4]); c = dptr + 5; for (n = 0; c < dend; ++c, n += 8) { if (*c) { unsigned b; for (b = 0; b < 8; ++b) if (*c & (1 << (7-b))) printf(" %d", n + b); } } break; case DNS_T_SRV: /* prio weight port targetDN */ c = dptr; c += 2 + 2 + 2; if (dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0 || c != dend) goto xperr; c = dptr; printf("%d %d %d %s.", dns_get16(c+0), dns_get16(c+2), dns_get16(c+4), dns_dntosp(dn)); break; case DNS_T_NAPTR: /* order pref flags serv regexp repl */ c = dptr; c += 4; /* order, pref */ for (n = 0; n < 3; ++n) if (c >= dend) goto xperr; else c += *c + 1; if (dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0 || c != dend) goto xperr; c = dptr; printf("%u %u", dns_get16(c+0), dns_get16(c+2)); c += 4; for(n = 0; n < 3; ++n) { putchar(' '); if (verbose > 0) putchar('"'); c = printtxt(c); if (verbose > 0) putchar('"'); } printf(" %s.", dns_dntosp(dn)); break; case DNS_T_KEY: case DNS_T_DNSKEY: /* flags(2) proto(1) algo(1) pubkey */ case DNS_T_DS: case DNS_T_DLV: /* ktag(2) proto(1) algo(1) pubkey */ c = dptr; if (c + 2 + 1 + 1 > dend) goto xperr; printf("%d %d %d", dns_get16(c), c[2], c[3]); c += 2 + 1 + 1; if (c < dend) { putchar(' '); printb64(c, dend); } break; case DNS_T_SIG: case DNS_T_RRSIG: /* type(2) algo(1) labels(1) ottl(4) sexp(4) sinc(4) tag(2) sdn sig */ c = dptr; c += 2 + 1 + 1 + 4 + 4 + 4 + 2; if (dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0) goto xperr; printf("%s %u %u %u ", dns_typename(dns_get16(dptr)), dptr[2], dptr[3], dns_get32(dptr+4)); printdate(dns_get32(dptr+8)); putchar(' '); printdate(dns_get32(dptr+12)); printf(" %d %s. ", dns_get16(dptr+10), dns_dntosp(dn)); printb64(c, dend); break; case DNS_T_SSHFP: /* algo(1), fp type(1), fp... */ if (dend < dptr + 3) goto xperr; printf("%u %u ", dptr[0], dptr[1]); /* algo, fp type */ printhex(dptr + 2, dend); break; #if 0 /* unused RR types? */ case DNS_T_NSEC: /* nextDN bitmaps */ c = dptr; if (dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0) goto xperr; printf("%s.", dns_dntosp(dn)); unfinished. break; #endif case DNS_T_SOA: c = dptr; if (dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0 || dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0 || c + 4*5 != dend) goto xperr; dns_getdn(pkt, &dptr, end, dn, DNS_MAXDN); printf("%s. ", dns_dntosp(dn)); dns_getdn(pkt, &dptr, end, dn, DNS_MAXDN); printf("%s. ", dns_dntosp(dn)); printf("%u %u %u %u %u", dns_get32(dptr), dns_get32(dptr+4), dns_get32(dptr+8), dns_get32(dptr+12), dns_get32(dptr+16)); break; case DNS_T_MINFO: c = dptr; if (dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0 || dns_getdn(pkt, &c, end, dn, DNS_MAXDN) <= 0 || c != dend) goto xperr; dns_getdn(pkt, &dptr, end, dn, DNS_MAXDN); printf("%s. ", dns_dntosp(dn)); dns_getdn(pkt, &dptr, end, dn, DNS_MAXDN); printf("%s.", dns_dntosp(dn)); break; case DNS_T_NULL: default: printhex(dptr, dend); break; } putchar('\n'); return; xperr: printf("\n"); ++errors; } static int printsection(struct dns_parse *p, int nrr, const char *sname) { struct dns_rr rr; int r; if (!nrr) return 0; if (verbose > 1) printf("\n;; %s section (%d):\n", sname, nrr); p->dnsp_rrl = nrr; while((r = dns_nextrr(p, &rr)) > 0) printrr(p, &rr); if (r < 0) printf("<>\n"); return r; } /* dbgcb will only be called if verbose > 1 */ static void dbgcb(int code, const struct sockaddr *sa, unsigned slen, const unsigned char *pkt, int r, const struct dns_query *unused_q, void *unused_data) { struct dns_parse p; const unsigned char *cur, *end; int numqd; (void)unused_q; (void)unused_data; /* avoid compiler warning */ if (code > 0) { printf(";; trying %s.\n", dns_dntosp(dns_payload(pkt))); printf(";; sending %d bytes query to ", r); } else printf(";; received %d bytes response from ", r); if (sa->sa_family == AF_INET && slen >= sizeof(struct sockaddr_in)) printf("%s port %d\n", dns_xntop(AF_INET, &((struct sockaddr_in*)sa)->sin_addr), htons(((struct sockaddr_in*)sa)->sin_port)); #ifdef HAVE_IPv6 else if (sa->sa_family == AF_INET6 && slen >= sizeof(struct sockaddr_in6)) printf("%s port %d\n", dns_xntop(AF_INET6, &((struct sockaddr_in6*)sa)->sin6_addr), htons(((struct sockaddr_in6*)sa)->sin6_port)); #endif else printf("<>\n", sa->sa_family); if (code > 0 && verbose < 3) { putchar('\n'); return; } if (code == -2) printf(";; reply from unexpected source\n"); if (code == -5) printf(";; reply to a query we didn't sent (or old)\n"); if (r < DNS_HSIZE) { printf(";; short packet (%d bytes)\n", r); return; } if (dns_opcode(pkt) != 0) printf(";; unexpected opcode %d\n", dns_opcode(pkt)); if (dns_tc(pkt) != 0) printf(";; warning: TC bit set, probably incomplete reply\n"); printf(";; ->>HEADER<<- opcode: "); switch(dns_opcode(pkt)) { case 0: printf("QUERY"); break; case 1: printf("IQUERY"); break; case 2: printf("STATUS"); break; default: printf("UNKNOWN(%u)", dns_opcode(pkt)); break; } printf(", status: %s, id: %d, size: %d\n;; flags:", dns_rcodename(dns_rcode(pkt)), dns_qid(pkt), r); if (dns_qr(pkt)) printf(" qr"); if (dns_aa(pkt)) printf(" aa"); if (dns_tc(pkt)) printf(" tc"); if (dns_rd(pkt)) printf(" rd"); if (dns_ra(pkt)) printf(" ra"); /* if (dns_z(pkt)) printf(" z"); only one reserved bit left */ if (dns_ad(pkt)) printf(" ad"); if (dns_cd(pkt)) printf(" cd"); numqd = dns_numqd(pkt); printf("; QUERY: %d, ANSWER: %d, AUTHORITY: %d, ADDITIONAL: %d\n", numqd, dns_numan(pkt), dns_numns(pkt), dns_numar(pkt)); if (numqd != 1) printf(";; unexpected number of entries in QUERY section: %d\n", numqd); printf("\n;; QUERY SECTION (%d):\n", numqd); cur = dns_payload(pkt); end = pkt + r; while(numqd--) { if (dns_getdn(pkt, &cur, end, p.dnsp_dnbuf, DNS_MAXDN) <= 0 || cur + 4 > end) { printf("; invalid query section\n"); return; } r = printf(";%s.", dns_dntosp(p.dnsp_dnbuf)); printf("%s%s\t%s\n", r > 23 ? "\t" : r > 15 ? "\t\t" : r > 7 ? "\t\t\t" : "\t\t\t\t", dns_classname(dns_get16(cur+2)), dns_typename(dns_get16(cur))); cur += 4; } p.dnsp_pkt = pkt; p.dnsp_cur = p.dnsp_ans = cur; p.dnsp_end = end; p.dnsp_qdn = NULL; p.dnsp_qcls = 0; p.dnsp_qtyp = 0; p.dnsp_ttl = 0xffffffffu; p.dnsp_nrr = 0; r = printsection(&p, dns_numan(pkt), "ANSWER"); if (r == 0) r = printsection(&p, dns_numns(pkt), "AUTHORITY"); if (r == 0) r = printsection(&p, dns_numar(pkt), "ADDITIONAL"); putchar('\n'); } static void dnscb(struct dns_ctx *ctx, void *result, void *data) { int r = dns_status(ctx); struct query *q = data; struct dns_parse p; struct dns_rr rr; unsigned nrr; unsigned char dn[DNS_MAXDN]; const unsigned char *pkt, *cur, *end; if (!result) { dnserror(q, r); return; } pkt = result; end = pkt + r; cur = dns_payload(pkt); dns_getdn(pkt, &cur, end, dn, sizeof(dn)); dns_initparse(&p, NULL, pkt, cur, end); p.dnsp_qcls = 0; p.dnsp_qtyp = 0; nrr = 0; while((r = dns_nextrr(&p, &rr)) > 0) { if (!dns_dnequal(dn, rr.dnsrr_dn)) continue; if ((qcls == DNS_C_ANY || qcls == rr.dnsrr_cls) && (q->qtyp == DNS_T_ANY || q->qtyp == rr.dnsrr_typ)) ++nrr; else if (rr.dnsrr_typ == DNS_T_CNAME && !nrr) { if (dns_getdn(pkt, &rr.dnsrr_dptr, end, p.dnsp_dnbuf, sizeof(p.dnsp_dnbuf)) <= 0 || rr.dnsrr_dptr != rr.dnsrr_dend) { r = DNS_E_PROTOCOL; break; } else { if (verbose == 1) { printf("%s.", dns_dntosp(dn)); printf(" CNAME %s.\n", dns_dntosp(p.dnsp_dnbuf)); } dns_dntodn(p.dnsp_dnbuf, dn, sizeof(dn)); } } } if (!r && !nrr) r = DNS_E_NODATA; if (r < 0) { dnserror(q, r); free(result); return; } if (verbose < 2) { /* else it is already printed by dbgfn */ dns_rewind(&p, NULL); p.dnsp_qtyp = q->qtyp == DNS_T_ANY ? 0 : q->qtyp; p.dnsp_qcls = qcls == DNS_C_ANY ? 0 : qcls; while(dns_nextrr(&p, &rr)) printrr(&p, &rr); } free(result); query_free(q); } int main(int argc, char **argv) { int i; int fd; fd_set fds; struct timeval tv; time_t now; char *ns[DNS_MAXSERV]; int nns = 0; struct query *q; enum dns_type qtyp = 0; struct dns_ctx *nctx = NULL; int flags = 0; if (!(progname = strrchr(argv[0], '/'))) progname = argv[0]; else argv[0] = ++progname; if (argc <= 1) die(0, "try `%s -h' for help", progname); if (dns_init(NULL, 0) < 0 || !(nctx = dns_new(NULL))) die(errno, "unable to initialize dns library"); /* we keep two dns contexts: one may be needed to resolve * nameservers if given as names, using default options. */ while((i = getopt(argc, argv, "vqt:c:an:o:f:h")) != EOF) switch(i) { case 'v': ++verbose; break; case 'q': --verbose; break; case 't': if (optarg[0] == '*' && !optarg[1]) i = DNS_T_ANY; else if ((i = dns_findtypename(optarg)) <= 0) die(0, "unrecognized query type `%s'", optarg); qtyp = i; break; case 'c': if (optarg[0] == '*' && !optarg[1]) i = DNS_C_ANY; else if ((i = dns_findclassname(optarg)) < 0) die(0, "unrecognized query class `%s'", optarg); qcls = i; break; case 'a': qtyp = DNS_T_ANY; ++verbose; break; case 'n': if (nns >= DNS_MAXSERV) die(0, "too many nameservers, %d max", DNS_MAXSERV); ns[nns++] = optarg; break; case 'o': case 'f': { char *opt; const char *const delim = " \t,;"; for(opt = strtok(optarg, delim); opt != NULL; opt = strtok(NULL, delim)) { if (dns_set_opts(NULL, optarg) == 0) ; else if (strcmp(opt, "aa") == 0) flags |= DNS_AAONLY; else if (strcmp(optarg, "nord") == 0) flags |= DNS_NORD; else if (strcmp(optarg, "dnssec") == 0) flags |= DNS_SET_DO; else if (strcmp(optarg, "do") == 0) flags |= DNS_SET_DO; else if (strcmp(optarg, "cd") == 0) flags |= DNS_SET_CD; else die(0, "invalid option: `%s'", opt); } break; } case 'h': printf( "%s: simple DNS query tool (using udns version %s)\n" "Usage: %s [options] domain-name...\n" "where options are:\n" " -h - print this help and exit\n" " -v - be more verbose\n" " -q - be less verbose\n" " -t type - set query type (A, AAAA, PTR etc)\n" " -c class - set query class (IN (default), CH, HS, *)\n" " -a - equivalent to -t ANY -v\n" " -n ns - use given nameserver(s) instead of default\n" " (may be specified multiple times)\n" " -o opt,opt,... (comma- or space-separated list,\n" " may be specified more than once):\n" " set resovler options (the same as setting $RES_OPTIONS):\n" " timeout:sec - initial query timeout\n" " attempts:num - number of attempt to resovle a query\n" " ndots:num - if name has more than num dots, lookup it before search\n" " port:num - port number for queries instead of default 53\n" " udpbuf:num - size of UDP buffer (use EDNS0 if >512)\n" " or query flags:\n" " aa,nord,dnssec,do,cd - set query flag (auth-only, no recursion,\n" " enable DNSSEC (DNSSEC Ok), check disabled)\n" , progname, dns_version(), progname); return 0; default: die(0, "try `%s -h' for help", progname); } argc -= optind; argv += optind; if (!argc) die(0, "no name(s) to query specified"); if (nns) { /* if nameservers given as names, resolve them. * We only allow IPv4 nameservers as names for now. * Ok, it is easy enouth to try both AAAA and A, * but the question is what to do by default. */ struct sockaddr_in sin; int j, r = 0, opened = 0; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(dns_set_opt(NULL, DNS_OPT_PORT, -1)); dns_add_serv(NULL, NULL); for(i = 0; i < nns; ++i) { if (dns_pton(AF_INET, ns[i], &sin.sin_addr) <= 0) { struct dns_rr_a4 *rr; if (!opened) { if (dns_open(nctx) < 0) die(errno, "unable to initialize dns context"); opened = 1; } rr = dns_resolve_a4(nctx, ns[i], 0); if (!rr) die(0, "unable to resolve nameserver %s: %s", ns[i], dns_strerror(dns_status(nctx))); for(j = 0; j < rr->dnsa4_nrr; ++j) { sin.sin_addr = rr->dnsa4_addr[j]; if ((r = dns_add_serv_s(NULL, (struct sockaddr *)&sin)) < 0) break; } free(rr); } else r = dns_add_serv_s(NULL, (struct sockaddr *)&sin); if (r < 0) die(errno, "unable to add nameserver %s", dns_xntop(AF_INET, &sin.sin_addr)); } } dns_free(nctx); fd = dns_open(NULL); if (fd < 0) die(errno, "unable to initialize dns context"); if (verbose > 1) dns_set_dbgfn(NULL, dbgcb); if (flags) dns_set_opt(NULL, DNS_OPT_FLAGS, flags); for (i = 0; i < argc; ++i) { char *name = argv[i]; union { struct in_addr addr; struct in6_addr addr6; } a; unsigned char dn[DNS_MAXDN]; enum dns_type l_qtyp = 0; int abs; if (dns_pton(AF_INET, name, &a.addr) > 0) { dns_a4todn(&a.addr, 0, dn, sizeof(dn)); l_qtyp = DNS_T_PTR; abs = 1; } #ifdef HAVE_IPv6 else if (dns_pton(AF_INET6, name, &a.addr6) > 0) { dns_a6todn(&a.addr6, 0, dn, sizeof(dn)); l_qtyp = DNS_T_PTR; abs = 1; } #endif else if (!dns_ptodn(name, strlen(name), dn, sizeof(dn), &abs)) die(0, "invalid name `%s'\n", name); else l_qtyp = DNS_T_A; if (qtyp) l_qtyp = qtyp; q = query_new(name, dn, l_qtyp); if (abs) abs = DNS_NOSRCH; if (!dns_submit_dn(NULL, dn, qcls, l_qtyp, abs, 0, dnscb, q)) dnserror(q, dns_status(NULL)); } FD_ZERO(&fds); now = 0; while((i = dns_timeouts(NULL, -1, now)) > 0) { FD_SET(fd, &fds); tv.tv_sec = i; tv.tv_usec = 0; i = select(fd+1, &fds, 0, 0, &tv); now = time(NULL); if (i > 0) dns_ioevent(NULL, now); } return errors ? 1 : notfound ? 100 : 0; } libtorrent-0.16.17/src/net/udns/ex-rdns.c000066400000000000000000000062021522271512000201210ustar00rootroot00000000000000/* ex-rdns.c parallel rDNS resolver example - read IP addresses from stdin, write domain names to stdout Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include "udns.h" static int curq; static const char *n2ip(const unsigned char *c) { static char b[sizeof("255.255.255.255")]; sprintf(b, "%u.%u.%u.%u", c[0], c[1], c[2], c[3]); return b; } static void dnscb(struct dns_ctx *ctx, struct dns_rr_ptr *rr, void *data) { const char *ip = n2ip((unsigned char *)&data); int i; --curq; if (rr) { printf("%s", ip); for(i = 0; i < rr->dnsptr_nrr; ++i) printf(" %s", rr->dnsptr_ptr[i]); putchar('\n'); free(rr); } else fprintf(stderr, "%s: %s\n", ip, dns_strerror(dns_status(ctx))); } int main(int argc, char **argv) { int c; time_t now; int maxq = 10; struct pollfd pfd; char linebuf[1024]; char *eol; int eof; if (dns_init(NULL, 1) < 0) { fprintf(stderr, "unable to initialize dns library\n"); return 1; } while((c = getopt(argc, argv, "m:r")) != EOF) switch(c) { case 'm': maxq = atoi(optarg); break; case 'r': dns_set_opt(0, DNS_OPT_FLAGS, dns_set_opt(0, DNS_OPT_FLAGS, -1) | DNS_NORD); break; default: return 1; } if (argc != optind) return 1; pfd.fd = dns_sock(0); pfd.events = POLLIN; now = time(NULL); c = optind; eof = 0; while(curq || !eof) { if (!eof && curq < maxq) { union { struct in_addr a; void *p; } pa; if (!fgets(linebuf, sizeof(linebuf), stdin)) { eof = 1; continue; } eol = strchr(linebuf, '\n'); if (eol) *eol = '\0'; if (!linebuf[0]) continue; if (dns_pton(AF_INET, linebuf, &pa.a) <= 0) fprintf(stderr, "%s: invalid address\n", linebuf); else if (dns_submit_a4ptr(0, &pa.a, dnscb, pa.p) == 0) fprintf(stderr, "%s: unable to submit query: %s\n", linebuf, dns_strerror(dns_status(0))); else ++curq; continue; } if (curq) { c = dns_timeouts(0, -1, now); c = poll(&pfd, 1, c < 0 ? -1 : c * 1000); now = time(NULL); if (c) dns_ioevent(0, now); } } return 0; } libtorrent-0.16.17/src/net/udns/getopt.c000066400000000000000000000075541522271512000200560ustar00rootroot00000000000000/* getopt.c * Simple getopt() implementation. * * Standard interface: * extern int getopt(int argc, char *const *argv, const char *opts); * extern int optind; current index in argv[] * extern char *optarg; argument for the current option * extern int optopt; the current option * extern int opterr; to control error printing * * Some minor extensions: * ignores leading `+' sign in opts[] (unemplemented GNU extension) * handles optional arguments, in form "x::" in opts[] * if opts[] starts with `:', will return `:' in case of missing required * argument, instead of '?'. * * Compile with -DGETOPT_NO_OPTERR to never print errors internally. * Compile with -DGETOPT_NO_STDIO to use write() calls instead of fprintf() for * error reporting (ignored with -DGETOPT_NO_OPTERR). * Compile with -DGETOPT_CLASS=static to get static linkage. * Compile with -DGETOPT_MY to redefine all visible symbols to be prefixed * with "my_", like my_getopt instead of getopt. * Compile with -DTEST to get a test executable. * * Written by Michael Tokarev. Public domain. */ #include #ifndef GETOPT_CLASS # define GETOPT_CLASS #endif #ifdef GETOPT_MY # define optarg my_optarg # define optind my_optind # define opterr my_opterr # define optopt my_optopt # define getopt my_getopt #endif GETOPT_CLASS char *optarg /* = NULL */; GETOPT_CLASS int optind = 1; GETOPT_CLASS int opterr = 1; GETOPT_CLASS int optopt; static char *nextc /* = NULL */; #if defined(GETOPT_NO_OPTERR) #define printerr(argv, msg) #elif defined(GETOPT_NO_STDIO) extern int write(int, void *, int); static void printerr(char *const *argv, const char *msg) { if (opterr) { char buf[64]; unsigned pl = strlen(argv[0]); unsigned ml = strlen(msg); char *p; if (pl + /*": "*/2 + ml + /*" -- c\n"*/6 > sizeof(buf)) { write(2, argv[0], pl); p = buf; } else { memcpy(buf, argv[0], ml); p = buf + pl; } *p++ = ':'; *p++ = ' '; memcpy(p, msg, ml); p += ml; *p++ = ' '; *p++ = '-'; *p++ = '-'; *p++ = ' '; *p++ = optopt; *p++ = '\n'; write(2, buf, p - buf); } } #else #include static void printerr(char *const *argv, const char *msg) { if (opterr) fprintf(stderr, "%s: %s -- %c\n", argv[0], msg, optopt); } #endif GETOPT_CLASS int getopt(int argc, char *const *argv, const char *opts) { char *p; optarg = 0; if (*opts == '+') /* GNU extension (permutation) - isn't supported */ ++opts; if (!optind) { /* a way to reset things */ nextc = 0; optind = 1; } if (!nextc || !*nextc) { /* advance to the next argv element */ /* done scanning? */ if (optind >= argc) return -1; /* not an optional argument */ if (argv[optind][0] != '-') return -1; /* bare `-' */ if (argv[optind][1] == '\0') return -1; /* special case `--' argument */ if (argv[optind][1] == '-' && argv[optind][2] == '\0') { ++optind; return -1; } nextc = argv[optind] + 1; } optopt = *nextc++; if (!*nextc) ++optind; p = strchr(opts, optopt); if (!p || optopt == ':') { printerr(argv, "illegal option"); return '?'; } if (p[1] == ':') { if (*nextc) { optarg = nextc; nextc = NULL; ++optind; } else if (p[2] != ':') { /* required argument */ if (optind >= argc) { printerr(argv, "option requires an argument"); return *opts == ':' ? ':' : '?'; } else optarg = argv[optind++]; } } return optopt; } #ifdef TEST #include int main(int argc, char **argv) { int c; while((c = getopt(argc, argv, "ab:c::")) != -1) switch(c) { case 'a': case 'b': case 'c': printf("option %c %s\n", c, optarg ? optarg : "(none)"); break; default: return -1; } for(c = optind; c < argc; ++c) printf("non-opt: %s\n", argv[c]); return 0; } #endif libtorrent-0.16.17/src/net/udns/inet_XtoX.c000066400000000000000000000174021522271512000204660ustar00rootroot00000000000000/* inet_XtoX.c * Simple implementation of the following functions: * inet_ntop(), inet_ntoa(), inet_pton(), inet_aton(). * * Differences from traditional implementaitons: * o modifies destination buffers even on error return. * o no fancy (hex, or 1.2) input support in inet_aton() * o inet_aton() does not accept junk after an IP address. * o inet_ntop(AF_INET) requires at least 16 bytes in dest, * and inet_ntop(AF_INET6) at least 40 bytes * (traditional inet_ntop() will try to fit anyway) * * Compile with -Dinet_XtoX_prefix=pfx_ to have pfx_*() instead of inet_*() * Compile with -Dinet_XtoX_no_ntop or -Dinet_XtoX_no_pton * to disable net2str or str2net conversions. * * #define inet_XtoX_prototypes and #include "this_file.c" * to get function prototypes only (but not for inet_ntoa()). * #define inet_XtoX_decl to be `static' for static visibility, * or use __declspec(dllexport) or somesuch... * * Compile with -DTEST to test against stock implementation. * * Written by Michael Tokarev. Public domain. */ #ifdef inet_XtoX_prototypes struct in_addr; #else #include #ifdef TEST # include # include # include # include # include # include # include # undef inet_XtoX_prefix # define inet_XtoX_prefix mjt_inet_ # undef inet_XtoX_no_ntop # undef inet_XtoX_no_pton #else /* !TEST */ struct in_addr { /* declare it here to avoid messing with headers */ unsigned char x[4]; }; #endif /* TEST */ #endif /* inet_XtoX_prototypes */ #ifndef inet_XtoX_prefix # define inet_XtoX_prefix inet_ #endif #ifndef inet_XtoX_decl # define inet_XtoX_decl /*empty*/ #endif #define cc2_(x,y) cc2__(x,y) #define cc2__(x,y) x##y #define fn(x) cc2_(inet_XtoX_prefix,x) #ifndef inet_XtoX_no_ntop inet_XtoX_decl const char * fn(ntop)(int af, const void *src, char *dst, unsigned size); #ifndef inet_XtoX_prototypes static int mjt_ntop4(const void *_src, char *dst, int size) { unsigned i, x, r; char *p; const unsigned char *s = _src; if (size < 4*4) /* for simplicity, disallow non-max-size buffer */ return 0; for (i = 0, p = dst; i < 4; ++i) { if (i) *p++ = '.'; x = r = s[i]; if (x > 99) { *p++ = (char)(r / 100 + '0'); r %= 100; } if (x > 9) { *p++ = (char)(r / 10 + '0'); r %= 10; } *p++ = (char)(r + '0'); } *p = '\0'; return 1; } static char *hexc(char *p, unsigned x) { static char hex[16] = "0123456789abcdef"; if (x > 0x0fff) *p++ = hex[(x >>12) & 15]; if (x > 0x00ff) *p++ = hex[(x >> 8) & 15]; if (x > 0x000f) *p++ = hex[(x >> 4) & 15]; *p++ = hex[x & 15]; return p; } static int mjt_ntop6(const void *_src, char *dst, int size) { unsigned i; unsigned short w[8]; unsigned bs = 0, cs = 0; unsigned bl = 0, cl = 0; char *p; const unsigned char *s = _src; if (size < 40) /* for simplicity, disallow non-max-size buffer */ return 0; for(i = 0; i < 8; ++i, s += 2) { w[i] = (((unsigned short)(s[0])) << 8) | s[1]; if (!w[i]) { if (!cl++) cs = i; } else { if (cl > bl) bl = cl, bs = cs; } } if (cl > bl) bl = cl, bs = cs; p = dst; if (bl == 1) bl = 0; if (bl) { for(i = 0; i < bs; ++i) { if (i) *p++ = ':'; p = hexc(p, w[i]); } *p++ = ':'; i += bl; if (i == 8) *p++ = ':'; } else i = 0; for(; i < 8; ++i) { if (i) *p++ = ':'; if (i == 6 && !bs && (bl == 6 || (bl == 5 && w[5] == 0xffff))) return mjt_ntop4(s - 4, p, size - (p - dst)); p = hexc(p, w[i]); } *p = '\0'; return 1; } inet_XtoX_decl const char * fn(ntop)(int af, const void *src, char *dst, unsigned size) { switch(af) { /* don't use AF_*: don't mess with headers */ case 2: /* AF_INET */ if (mjt_ntop4(src, dst, size)) return dst; break; case 10: /* AF_INET6 */ if (mjt_ntop6(src, dst, size)) return dst; break; default: errno = EAFNOSUPPORT; return (char*)0; } errno = ENOSPC; return (char*)0; } inet_XtoX_decl const char * fn(ntoa)(struct in_addr addr) { static char buf[4*4]; mjt_ntop4(&addr, buf, sizeof(buf)); return buf; } #endif /* inet_XtoX_prototypes */ #endif /* inet_XtoX_no_ntop */ #ifndef inet_XtoX_no_pton inet_XtoX_decl int fn(pton)(int af, const char *src, void *dst); inet_XtoX_decl int fn(aton)(const char *src, struct in_addr *addr); #ifndef inet_XtoX_prototypes static int mjt_pton4(const char *c, void *dst) { unsigned char *a = dst; unsigned n, o; for (n = 0; n < 4; ++n) { if (*c < '0' || *c > '9') return 0; o = *c++ - '0'; while(*c >= '0' && *c <= '9') if ((o = o * 10 + (*c++ - '0')) > 255) return 0; if (*c++ != (n == 3 ? '\0' : '.')) return 0; *a++ = (unsigned char)o; } return 1; } static int mjt_pton6(const char *c, void *dst) { unsigned short w[8], *a = w, *z, *i; unsigned v, o; const char *sc; unsigned char *d = dst; if (*c != ':') z = (unsigned short*)0; else if (*++c != ':') return 0; else ++c, z = a; i = 0; for(;;) { v = 0; sc = c; for(;;) { if (*c >= '0' && *c <= '9') o = *c - '0'; else if (*c >= 'a' && *c <= 'f') o = *c - 'a' + 10; else if (*c >= 'A' && *c <= 'F') o = *c - 'A' + 10; else break; v = (v << 4) | o; if (v > 0xffff) return 0; ++c; } if (sc == c) { if (z == a && !*c) break; else return 0; } if (*c == ':') { if (a >= w + 8) return 0; *a++ = v; if (*++c == ':') { if (z) return 0; z = a; if (!*++c) break; } } else if (!*c) { if (a >= w + 8) return 0; *a++ = v; break; } else if (*c == '.') { if (a > w + 6) return 0; if (!mjt_pton4(sc, d)) return 0; *a++ = ((unsigned)(d[0]) << 8) | d[1]; *a++ = ((unsigned)(d[2]) << 8) | d[3]; break; } else return 0; } v = w + 8 - a; if ((v && !z) || (!v && z)) return 0; for(i = w; ; ++i) { if (i == z) while(v--) { *d++ = '\0'; *d++ = '\0'; } if (i >= a) break; *d++ = (unsigned char)((*i >> 8) & 255); *d++ = (unsigned char)(*i & 255); } return 1; } inet_XtoX_decl int fn(pton)(int af, const char *src, void *dst) { switch(af) { /* don't use AF_*: don't mess with headers */ case 2 /* AF_INET */: return mjt_pton4(src, dst); case 10 /* AF_INET6 */: return mjt_pton6(src, dst); default: errno = EAFNOSUPPORT; return -1; } } inet_XtoX_decl int fn(aton)(const char *src, struct in_addr *addr) { return mjt_pton4(src, addr); } #endif /* inet_XtoX_prototypes */ #endif /* inet_XtoX_no_pton */ #ifdef TEST int main(int argc, char **argv) { int i; char n0[16], n1[16]; char p0[64], p1[64]; int af = AF_INET; int pl = sizeof(p0); int r0, r1; const char *s0, *s1; while((i = getopt(argc, argv, "46a:p:")) != EOF) switch(i) { case '4': af = AF_INET; break; case '6': af = AF_INET6; break; case 'a': case 'p': pl = atoi(optarg); break; default: return 1; } for(i = optind; i < argc; ++i) { char *a = argv[i]; printf("%s:\n", a); r0 = inet_pton(af, a, n0); printf(" p2n stock: %s\n", (r0 < 0 ? "(notsupp)" : !r0 ? "(inval)" : fn(ntop)(af,n0,p0,sizeof(p0)))); r1 = fn(pton)(af, a, n1); printf(" p2n this : %s\n", (r1 < 0 ? "(notsupp)" : !r1 ? "(inval)" : fn(ntop)(af,n1,p1,sizeof(p1)))); if ((r0 > 0) != (r1 > 0) || (r0 > 0 && r1 > 0 && memcmp(n0, n1, af == AF_INET ? 4 : 16) != 0)) printf(" DIFFER!\n"); s0 = inet_ntop(af, n1, p0, pl); printf(" n2p stock: %s\n", s0 ? s0 : "(inval)"); s1 = fn(ntop)(af, n1, p1, pl); printf(" n2p this : %s\n", s1 ? s1 : "(inval)"); if ((s0 != 0) != (s1 != 0) || (s0 && s1 && strcmp(s0, s1) != 0)) printf(" DIFFER!\n"); } return 0; } #endif /* TEST */ libtorrent-0.16.17/src/net/udns/rblcheck.c000066400000000000000000000237511522271512000203260ustar00rootroot00000000000000/* rblcheck.c dnsbl (rbl) checker application Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #ifdef WINDOWS # include #else # include # include # include # include #endif #include #include #include #include "udns.h" #ifndef HAVE_GETOPT # include "getopt.c" #endif static constexpr auto version = "udns-rblcheck " UDNS_VERSION; static char *progname; static void error(int die, const char *fmt, ...) { va_list ap; fprintf(stderr, "%s: ", progname); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); putc('\n', stderr); fflush(stderr); if (die) exit(1); } struct rblookup { struct ipcheck *parent; struct in_addr key; const char *zone; struct dns_rr_a4 *addr; struct dns_rr_txt *txt; }; struct ipcheck { const char *name; int naddr; int listed; struct rblookup *lookup; }; #define notlisted ((void*)1) static int nzones, nzalloc; static const char **zones; static int do_txt; static int stopfirst; static int verbose = 1; /* verbosity level: * <0 - only bare As/TXTs * 0 - what RBL result * 1(default) - what is listed by RBL: result * 2 - what is[not ]listed by RBL: result, name lookups */ static int listed; static int failures; static void *ecalloc(int size, int cnt) { void *t = calloc(size, cnt); if (!t) error(1, "out of memory"); return t; } static void addzone(const char *zone) { if (nzones >= nzalloc) { const char **zs = (const char**)ecalloc(sizeof(char*), (nzalloc += 16)); if (zones) { memcpy(zs, zones, nzones * sizeof(char*)); free(zones); } zones = zs; } zones[nzones++] = zone; } static int addzonefile(const char *fname) { FILE *f = fopen(fname, "r"); char linebuf[2048]; if (!f) return 0; while(fgets(linebuf, sizeof(linebuf), f)) { char *p = linebuf, *e; while(*p == ' ' || *p == '\t') ++p; if (*p == '#' || *p == '\n') continue; e = p; while(*e && *e != ' ' && *e != '\t' && *e != '\n') ++e; *e++ = '\0'; p = memcpy(ecalloc(e - p, 1), p, e - p); // strdup addzone(p); } fclose(f); return 1; } static void dnserror(struct rblookup *ipl, const char *what) { char buf[4*4]; error(0, "unable to %s for %s (%s): %s", what, dns_ntop(AF_INET, &ipl->key, buf, sizeof(buf)), ipl->zone, dns_strerror(dns_status(0))); ++failures; } static void display_result(struct ipcheck *ipc) { int j; struct rblookup *l, *le; char buf[4*4]; if (!ipc->naddr) return; for (l = ipc->lookup, le = l + nzones * ipc->naddr; l < le; ++l) { if (!l->addr) continue; if (verbose < 2 && l->addr == notlisted) continue; if (verbose >= 0) { dns_ntop(AF_INET, &l->key, buf, sizeof(buf)); if (ipc->name) printf("%s[%s]", ipc->name, buf); else printf("%s", buf); } if (l->addr == notlisted) { printf(" is NOT listed by %s\n", l->zone); continue; } else if (verbose >= 1) printf(" is listed by %s: ", l->zone); else if (verbose >= 0) printf(" %s ", l->zone); if (verbose >= 1 || !do_txt) for (j = 0; j < l->addr->dnsa4_nrr; ++j) printf("%s%s", j ? " " : "", dns_ntop(AF_INET, &l->addr->dnsa4_addr[j], buf, sizeof(buf))); if (!do_txt) ; else if (l->txt) { for(j = 0; j < l->txt->dnstxt_nrr; ++j) { unsigned char *t = l->txt->dnstxt_txt[j].txt; unsigned char *e = t + l->txt->dnstxt_txt[j].len; printf("%s\"", verbose > 0 ? "\n\t" : j ? " " : ""); while(t < e) { if (*t < ' ' || *t >= 127) printf("\\x%02x", *t); else if (*t == '\\' || *t == '"') printf("\\%c", *t); else putchar(*t); ++t; } putchar('"'); } free(l->txt); } else printf("%s", verbose > 0 ? "\n\t" : ""); free(l->addr); putchar('\n'); } free(ipc->lookup); } static void txtcb(struct dns_ctx *ctx, struct dns_rr_txt *r, void *data) { struct rblookup *ipl = data; if (r) { ipl->txt = r; ++ipl->parent->listed; } else if (dns_status(ctx) != DNS_E_NXDOMAIN) dnserror(ipl, "lookup DNSBL TXT record"); } static void a4cb(struct dns_ctx *ctx, struct dns_rr_a4 *r, void *data) { struct rblookup *ipl = data; if (r) { ipl->addr = r; ++listed; if (do_txt) { if (dns_submit_a4dnsbl_txt(0, &ipl->key, ipl->zone, txtcb, ipl)) return; dnserror(ipl, "submit DNSBL TXT record"); } ++ipl->parent->listed; } else if (dns_status(ctx) != DNS_E_NXDOMAIN) dnserror(ipl, "lookup DNSBL A record"); else ipl->addr = notlisted; } static int submit_a_queries(struct ipcheck *ipc, int naddr, const struct in_addr *addr) { int z, a; struct rblookup *rl = ecalloc(sizeof(*rl), nzones * naddr); ipc->lookup = rl; ipc->naddr = naddr; for(a = 0; a < naddr; ++a) { for(z = 0; z < nzones; ++z) { rl->key = addr[a]; rl->zone = zones[z]; rl->parent = ipc; if (!dns_submit_a4dnsbl(0, &rl->key, rl->zone, a4cb, rl)) dnserror(rl, "submit DNSBL A query"); ++rl; } } return 0; } static void namecb(struct dns_ctx *ctx, struct dns_rr_a4 *rr, void *data) { struct ipcheck *ipc = data; if (rr) { submit_a_queries(ipc, rr->dnsa4_nrr, rr->dnsa4_addr); free(rr); } else { error(0, "unable to lookup `%s': %s", ipc->name, dns_strerror(dns_status(ctx))); ++failures; } } static int submit(struct ipcheck *ipc) { struct in_addr addr; if (dns_pton(AF_INET, ipc->name, &addr) > 0) { submit_a_queries(ipc, 1, &addr); ipc->name = NULL; } else if (!dns_submit_a4(0, ipc->name, 0, namecb, ipc)) { error(0, "unable to submit name query for %s: %s\n", ipc->name, dns_strerror(dns_status(0))); ++failures; } return 0; } static void waitdns(struct ipcheck *ipc) { struct timeval tv; fd_set fds; int c; int fd = dns_sock(NULL); time_t now = 0; FD_ZERO(&fds); while((c = dns_timeouts(NULL, -1, now)) > 0) { FD_SET(fd, &fds); tv.tv_sec = c; tv.tv_usec = 0; c = select(fd+1, &fds, NULL, NULL, &tv); now = time(NULL); if (c > 0) dns_ioevent(NULL, now); if (stopfirst && ipc->listed) break; } } int main(int argc, char **argv) { int c; struct ipcheck ipc; char *nameserver = NULL; int zgiven = 0; if (!(progname = strrchr(argv[0], '/'))) progname = argv[0]; else argv[0] = ++progname; while((c = getopt(argc, argv, "hqtvms:S:cn:")) != EOF) switch(c) { case 's': ++zgiven; addzone(optarg); break; case 'S': ++zgiven; if (addzonefile(optarg)) break; error(1, "unable to read zonefile `%s'", optarg); break; /* avoid warning */ case 'c': ++zgiven; nzones = 0; break; case 'q': --verbose; break; case 'v': ++verbose; break; case 't': do_txt = 1; break; case 'n': nameserver = optarg; break; case 'm': ++stopfirst; break; case 'h': printf("%s: %s (udns library version %s).\n", progname, version, dns_version()); printf("Usage is: %s [options] address..\n", progname); printf( "Where options are:\n" " -h - print this help and exit\n" " -s service - add the service (DNSBL zone) to the serice list\n" " -S service-file - add the DNSBL zone(s) read from the given file\n" " -c - clear service list\n" " -v - increase verbosity level (more -vs => more verbose)\n" " -q - decrease verbosity level (opposite of -v)\n" " -t - obtain and print TXT records if any\n" " -m - stop checking after first address match in any list\n" " -n ipaddr - use the given nameserver instead of the default\n" "(if no -s or -S option is given, use $RBLCHECK_ZONES, ~/.rblcheckrc\n" "or /etc/rblcheckrc in that order)\n" ); return 0; default: error(1, "use `%s -h' for help", progname); } if (!zgiven) { char *s = getenv("RBLCHECK_ZONES"); if (s) { char *k; s = strdup(s); for(k = strtok(s, " \t"); k; k = strtok(NULL, " \t")) addzone(k); free(s); } else { /* probably worthless on windows? */ char *path; char *home = getenv("HOME"); if (!home) home = "."; path = malloc(strlen(home) + 1 + sizeof(".rblcheckrc")); sprintf(path, "%s/.rblcheckrc", home); if (!addzonefile(path)) addzonefile("/etc/rblcheckrc"); free(path); } } if (!nzones) error(1, "no service (zone) list specified (-s or -S option)"); argv += optind; argc -= optind; if (!argc) return 0; if (dns_init(NULL, 0) < 0) error(1, "unable to initialize DNS library: %s", strerror(errno)); if (nameserver) { dns_add_serv(NULL, NULL); if (dns_add_serv(NULL, nameserver) < 0) error(1, "wrong IP address for a nameserver: `%s'", nameserver); } if (dns_open(NULL) < 0) error(1, "unable to initialize DNS library: %s", strerror(errno)); for (c = 0; c < argc; ++c) { if (c && (verbose > 1 || (verbose == 1 && do_txt))) putchar('\n'); memset(&ipc, 0, sizeof(ipc)); ipc.name = argv[c]; submit(&ipc); waitdns(&ipc); display_result(&ipc); if (stopfirst > 1 && listed) break; } return listed ? 100 : failures ? 2 : 0; } libtorrent-0.16.17/src/net/udns/udns.h000066400000000000000000000651631522271512000175320ustar00rootroot00000000000000/* udns.h header file for the UDNS library. Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef UDNS_VERSION /* include guard */ #define UDNS_VERSION "0.6" #ifdef WINDOWS # ifdef UDNS_DYNAMIC_LIBRARY # ifdef DNS_LIBRARY_BUILD # define UDNS_API __declspec(dllexport) # define UDNS_DATA_API __declspec(dllexport) # else # define UDNS_API __declspec(dllimport) # define UDNS_DATA_API __declspec(dllimport) # endif # endif #endif #ifndef UDNS_API # define UDNS_API #endif #ifndef UDNS_DATA_API # define UDNS_DATA_API #endif #include /* for time_t */ #ifdef __cplusplus extern "C" { #endif /* forward declarations if sockets stuff isn't #include'd */ struct in_addr; struct in6_addr; struct sockaddr; /**************************************************************************/ /**************** Common definitions **************************************/ UDNS_API const char * dns_version(void); struct dns_ctx; struct dns_query; /* shorthand for [const] unsigned char */ typedef unsigned char dnsc_t; typedef const unsigned char dnscc_t; #define DNS_MAXDN 255 /* max DN length */ #define DNS_DNPAD 1 /* padding for DN buffers */ #define DNS_MAXLABEL 63 /* max DN label length */ #define DNS_MAXNAME 1024 /* max asciiz domain name length */ #define DNS_HSIZE 12 /* DNS packet header size */ #define DNS_PORT 53 /* default domain port */ #define DNS_MAXSERV 6 /* max servers to consult */ #define DNS_MAXPACKET 512 /* max traditional-DNS UDP packet size */ #define DNS_EDNS0PACKET 4096 /* EDNS0 packet size to use */ enum dns_class { /* DNS RR Classes */ DNS_C_INVALID = 0, /* invalid class */ DNS_C_IN = 1, /* Internet */ DNS_C_CH = 3, /* CHAOS */ DNS_C_HS = 4, /* HESIOD */ DNS_C_ANY = 255 /* wildcard */ }; enum dns_type { /* DNS RR Types */ DNS_T_INVALID = 0, /* Cookie. */ DNS_T_A = 1, /* Host address. */ DNS_T_NS = 2, /* Authoritative server. */ DNS_T_MD = 3, /* Mail destination. */ DNS_T_MF = 4, /* Mail forwarder. */ DNS_T_CNAME = 5, /* Canonical name. */ DNS_T_SOA = 6, /* Start of authority zone. */ DNS_T_MB = 7, /* Mailbox domain name. */ DNS_T_MG = 8, /* Mail group member. */ DNS_T_MR = 9, /* Mail rename name. */ DNS_T_NULL = 10, /* Null resource record. */ DNS_T_WKS = 11, /* Well known service. */ DNS_T_PTR = 12, /* Domain name pointer. */ DNS_T_HINFO = 13, /* Host information. */ DNS_T_MINFO = 14, /* Mailbox information. */ DNS_T_MX = 15, /* Mail routing information. */ DNS_T_TXT = 16, /* Text strings. */ DNS_T_RP = 17, /* Responsible person. */ DNS_T_AFSDB = 18, /* AFS cell database. */ DNS_T_X25 = 19, /* X_25 calling address. */ DNS_T_ISDN = 20, /* ISDN calling address. */ DNS_T_RT = 21, /* Router. */ DNS_T_NSAP = 22, /* NSAP address. */ DNS_T_NSAP_PTR = 23, /* Reverse NSAP lookup (deprecated). */ DNS_T_SIG = 24, /* Security signature. */ DNS_T_KEY = 25, /* Security key. */ DNS_T_PX = 26, /* X.400 mail mapping. */ DNS_T_GPOS = 27, /* Geographical position (withdrawn). */ DNS_T_AAAA = 28, /* Ip6 Address. */ DNS_T_LOC = 29, /* Location Information. */ DNS_T_NXT = 30, /* Next domain (security). */ DNS_T_EID = 31, /* Endpoint identifier. */ DNS_T_NIMLOC = 32, /* Nimrod Locator. */ DNS_T_SRV = 33, /* Server Selection. */ DNS_T_ATMA = 34, /* ATM Address */ DNS_T_NAPTR = 35, /* Naming Authority PoinTeR */ DNS_T_KX = 36, /* Key Exchange */ DNS_T_CERT = 37, /* Certification record */ DNS_T_A6 = 38, /* IPv6 address (deprecates AAAA) */ DNS_T_DNAME = 39, /* Non-terminal DNAME (for IPv6) */ DNS_T_SINK = 40, /* Kitchen sink (experimentatl) */ DNS_T_OPT = 41, /* EDNS0 option (meta-RR) */ DNS_T_DS = 43, /* DNSSEC */ DNS_T_SSHFP = 44, DNS_T_IPSECKEY = 45, DNS_T_RRSIG = 46, /* DNSSEC */ DNS_T_NSEC = 47, /* DNSSEC */ DNS_T_DNSKEY = 48, DNS_T_DHCID = 49, DNS_T_NSEC3 = 50, DNS_T_NSEC3PARAMS = 51, DNS_T_TALINK = 58, /* draft-ietf-dnsop-trust-history */ DNS_T_SPF = 99, DNS_T_UINFO = 100, DNS_T_UID = 101, DNS_T_GID = 102, DNS_T_UNSPEC = 103, DNS_T_TSIG = 250, /* Transaction signature. */ DNS_T_IXFR = 251, /* Incremental zone transfer. */ DNS_T_AXFR = 252, /* Transfer zone of authority. */ DNS_T_MAILB = 253, /* Transfer mailbox records. */ DNS_T_MAILA = 254, /* Transfer mail agent records. */ DNS_T_ANY = 255, /* Wildcard match. */ DNS_T_ZXFR = 256, /* BIND-specific, nonstandard. */ DNS_T_DLV = 32769, /* RFC 4431, 5074, DNSSEC Lookaside Validation */ DNS_T_MAX = 65536 }; /**************************************************************************/ /**************** Domain Names (DNs) **************************************/ /* return length of the DN */ UDNS_API unsigned dns_dnlen(dnscc_t *dn); /* return #of labels in a DN */ UDNS_API unsigned dns_dnlabels(dnscc_t *dn); /* lower- and uppercase single DN char */ #define DNS_DNLC(c) ((c) >= 'A' && (c) <= 'Z' ? (c) - 'A' + 'a' : (c)) #define DNS_DNUC(c) ((c) >= 'a' && (c) <= 'z' ? (c) - 'a' + 'A' : (c)) /* compare the DNs, return dnlen of equal or 0 if not */ UDNS_API unsigned dns_dnequal(dnscc_t *dn1, dnscc_t *dn2); /* copy one DN to another, size checking */ UDNS_API unsigned dns_dntodn(dnscc_t *sdn, dnsc_t *ddn, unsigned ddnsiz); /* convert asciiz string of length namelen (0 to use strlen) to DN */ UDNS_API int dns_ptodn(const char *name, unsigned namelen, dnsc_t *dn, unsigned dnsiz, int *isabs); /* simpler form of dns_ptodn() */ #define dns_sptodn(name,dn,dnsiz) dns_ptodn((name),0,(dn),(dnsiz),0) UDNS_DATA_API extern dnscc_t dns_inaddr_arpa_dn[14]; #define DNS_A4RSIZE 30 UDNS_API int dns_a4todn(const struct in_addr *addr, dnscc_t *tdn, dnsc_t *dn, unsigned dnsiz); UDNS_API int dns_a4ptodn(const struct in_addr *addr, const char *tname, dnsc_t *dn, unsigned dnsiz); UDNS_API dnsc_t * dns_a4todn_(const struct in_addr *addr, dnsc_t *dn, dnsc_t *dne); UDNS_DATA_API extern dnscc_t dns_ip6_arpa_dn[10]; #define DNS_A6RSIZE 74 UDNS_API int dns_a6todn(const struct in6_addr *addr, dnscc_t *tdn, dnsc_t *dn, unsigned dnsiz); UDNS_API int dns_a6ptodn(const struct in6_addr *addr, const char *tname, dnsc_t *dn, unsigned dnsiz); UDNS_API dnsc_t * dns_a6todn_(const struct in6_addr *addr, dnsc_t *dn, dnsc_t *dne); /* convert DN into asciiz string */ UDNS_API int dns_dntop(dnscc_t *dn, char *name, unsigned namesiz); /* convert DN into asciiz string, using static buffer (NOT thread-safe!) */ UDNS_API const char * dns_dntosp(dnscc_t *dn); /* return buffer size (incl. null byte) required for asciiz form of a DN */ UDNS_API unsigned dns_dntop_size(dnscc_t *dn); /* either wrappers or reimplementations for inet_ntop() and inet_pton() */ UDNS_API const char *dns_ntop(int af, const void *src, char *dst, unsigned size); UDNS_API int dns_pton(int af, const char *src, void *dst); /**************************************************************************/ /**************** DNS raw packet layout ***********************************/ enum dns_rcode { /* reply codes */ DNS_R_NOERROR = 0, /* ok, no error */ DNS_R_FORMERR = 1, /* format error */ DNS_R_SERVFAIL = 2, /* server failed */ DNS_R_NXDOMAIN = 3, /* domain does not exists */ DNS_R_NOTIMPL = 4, /* not implemented */ DNS_R_REFUSED = 5, /* query refused */ /* these are for BIND_UPDATE */ DNS_R_YXDOMAIN = 6, /* Name exists */ DNS_R_YXRRSET = 7, /* RRset exists */ DNS_R_NXRRSET = 8, /* RRset does not exist */ DNS_R_NOTAUTH = 9, /* Not authoritative for zone */ DNS_R_NOTZONE = 10, /* Zone of record different from zone section */ /*ns_r_max = 11,*/ /* The following are TSIG extended errors */ DNS_R_BADSIG = 16, DNS_R_BADKEY = 17, DNS_R_BADTIME = 18 }; static __inline unsigned dns_get16(dnscc_t *s) { return ((unsigned)s[0]<<8) | s[1]; } static __inline unsigned dns_get32(dnscc_t *s) { return ((unsigned)s[0]<<24) | ((unsigned)s[1]<<16) | ((unsigned)s[2]<<8) | s[3]; } static __inline dnsc_t *dns_put16(dnsc_t *d, unsigned n) { *d++ = (dnsc_t)((n >> 8) & 255); *d++ = (dnsc_t)(n & 255); return d; } static __inline dnsc_t *dns_put32(dnsc_t *d, unsigned n) { *d++ = (dnsc_t)((n >> 24) & 255); *d++ = (dnsc_t)((n >> 16) & 255); *d++ = (dnsc_t)((n >> 8) & 255); *d++ = (dnsc_t)(n & 255); return d; } /* DNS Header layout */ enum { /* bytes 0:1 - query ID */ DNS_H_QID1 = 0, DNS_H_QID2 = 1, DNS_H_QID = DNS_H_QID1, #define dns_qid(pkt) dns_get16((pkt)+DNS_H_QID) /* byte 2: flags1 */ DNS_H_F1 = 2, DNS_HF1_QR = 0x80, /* query response flag */ #define dns_qr(pkt) ((pkt)[DNS_H_F1]&DNS_HF1_QR) DNS_HF1_OPCODE = 0x78, /* opcode, 0 = query */ #define dns_opcode(pkt) (((pkt)[DNS_H_F1]&DNS_HF1_OPCODE)>>3) DNS_HF1_AA = 0x04, /* auth answer */ #define dns_aa(pkt) ((pkt)[DNS_H_F1]&DNS_HF1_AA) DNS_HF1_TC = 0x02, /* truncation flag */ #define dns_tc(pkt) ((pkt)[DNS_H_F1]&DNS_HF1_TC) DNS_HF1_RD = 0x01, /* recursion desired (may be set in query) */ #define dns_rd(pkt) ((pkt)[DNS_H_F1]&DNS_HF1_RD) /* byte 3: flags2 */ DNS_H_F2 = 3, DNS_HF2_RA = 0x80, /* recursion available */ #define dns_ra(pkt) ((pkt)[DNS_H_F2]&DNS_HF2_RA) DNS_HF2_Z = 0x40, /* reserved */ DNS_HF2_AD = 0x20, /* DNSSEC: authentic data */ #define dns_ad(pkt) ((pkt)[DNS_H_F2]&DNS_HF2_AD) DNS_HF2_CD = 0x10, /* DNSSEC: checking disabled */ #define dns_cd(pkt) ((pkt)[DNS_H_F2]&DNS_HF2_CD) DNS_HF2_RCODE = 0x0f, /* response code, DNS_R_XXX above */ #define dns_rcode(pkt) ((pkt)[DNS_H_F2]&DNS_HF2_RCODE) /* bytes 4:5: qdcount, numqueries */ DNS_H_QDCNT1 = 4, DNS_H_QDCNT2 = 5, DNS_H_QDCNT = DNS_H_QDCNT1, #define dns_numqd(pkt) dns_get16((pkt)+4) /* bytes 6:7: ancount, numanswers */ DNS_H_ANCNT1 = 6, DNS_H_ANCNT2 = 7, DNS_H_ANCNT = DNS_H_ANCNT1, #define dns_numan(pkt) dns_get16((pkt)+6) /* bytes 8:9: nscount, numauthority */ DNS_H_NSCNT1 = 8, DNS_H_NSCNT2 = 9, DNS_H_NSCNT = DNS_H_NSCNT1, #define dns_numns(pkt) dns_get16((pkt)+8) /* bytes 10:11: arcount, numadditional */ DNS_H_ARCNT1 = 10, DNS_H_ARCNT2 = 11, DNS_H_ARCNT = DNS_H_ARCNT1, #define dns_numar(pkt) dns_get16((pkt)+10) #define dns_payload(pkt) ((pkt)+DNS_HSIZE) /* EDNS0 (OPT RR) flags (Ext. Flags) */ DNS_EF1_DO = 0x80, /* DNSSEC OK */ }; /* packet buffer: start at pkt, end before pkte, current pos *curp. * extract a DN and set *curp to the next byte after DN in packet. * return -1 on error, 0 if dnsiz is too small, or dnlen on ok. */ UDNS_API int dns_getdn(dnscc_t *pkt, dnscc_t **curp, dnscc_t *end, dnsc_t *dn, unsigned dnsiz); /* skip the DN at position cur in packet ending before pkte, * return pointer to the next byte after the DN or NULL on error */ UDNS_API dnscc_t * dns_skipdn(dnscc_t *end, dnscc_t *cur); struct dns_rr { /* DNS Resource Record */ dnsc_t dnsrr_dn[DNS_MAXDN]; /* the DN of the RR */ enum dns_class dnsrr_cls; /* Class */ enum dns_type dnsrr_typ; /* Type */ unsigned dnsrr_ttl; /* Time-To-Live (TTL) */ unsigned dnsrr_dsz; /* data size */ dnscc_t *dnsrr_dptr; /* pointer to start of data */ dnscc_t *dnsrr_dend; /* past end of data */ }; struct dns_parse { /* RR/packet parsing state */ dnscc_t *dnsp_pkt; /* start of the packet */ dnscc_t *dnsp_end; /* end of the packet */ dnscc_t *dnsp_cur; /* current packet position */ dnscc_t *dnsp_ans; /* start of answer section */ int dnsp_rrl; /* number of RRs left to go */ int dnsp_nrr; /* RR count so far */ unsigned dnsp_ttl; /* TTL value so far */ dnscc_t *dnsp_qdn; /* the RR DN we're looking for */ enum dns_class dnsp_qcls; /* RR class we're looking for or 0 */ enum dns_type dnsp_qtyp; /* RR type we're looking for or 0 */ dnsc_t dnsp_dnbuf[DNS_MAXDN]; /* domain buffer */ }; /* initialize the parse structure */ UDNS_API void dns_initparse(struct dns_parse *p, dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end); /* search next RR, <0=error, 0=no more RRs, >0 = found. */ UDNS_API int dns_nextrr(struct dns_parse *p, struct dns_rr *rr); UDNS_API void dns_rewind(struct dns_parse *p, dnscc_t *qdn); /**************************************************************************/ /**************** Resolver Context ****************************************/ /* default resolver context */ UDNS_DATA_API extern struct dns_ctx dns_defctx; /* reset resolver context to default state, close it if open, drop queries */ UDNS_API void dns_reset(struct dns_ctx *ctx); /* reset resolver context and read in system configuration */ UDNS_API int dns_init(struct dns_ctx *ctx, int do_open); /* return new resolver context with the same settings as copy */ UDNS_API struct dns_ctx * dns_new(const struct dns_ctx *copy); /* free resolver context returned by dns_new(); all queries are dropped */ UDNS_API void dns_free(struct dns_ctx *ctx); /* add nameserver for a resolver context (or reset nslist if serv==NULL) */ UDNS_API int dns_add_serv(struct dns_ctx *ctx, const char *serv); /* add nameserver using struct sockaddr structure (with ports) */ UDNS_API int dns_add_serv_s(struct dns_ctx *ctx, const struct sockaddr *sa); /* add search list element for a resolver context (or reset it if srch==NULL) */ UDNS_API int dns_add_srch(struct dns_ctx *ctx, const char *srch); /* set options for a resolver context */ UDNS_API int dns_set_opts(struct dns_ctx *ctx, const char *opts); enum dns_opt { /* options */ DNS_OPT_FLAGS, /* flags, DNS_F_XXX */ DNS_OPT_TIMEOUT, /* timeout in secounds */ DNS_OPT_NTRIES, /* number of retries */ DNS_OPT_NDOTS, /* ndots */ DNS_OPT_UDPSIZE, /* EDNS0 UDP size */ DNS_OPT_PORT, /* port to use */ }; /* set or get (if val<0) an option */ UDNS_API int dns_set_opt(struct dns_ctx *ctx, enum dns_opt opt, int val); enum dns_flags { DNS_NOSRCH = 0x00010000, /* do not perform search */ DNS_NORD = 0x00020000, /* request no recursion */ DNS_AAONLY = 0x00040000, /* set AA flag in queries */ DNS_SET_DO = 0x00080000, /* set EDNS0 "DO" bit (DNSSEC OK) */ DNS_SET_CD = 0x00100000, /* set CD bit (DNSSEC: checking disabled) */ }; /* set the debug function pointer */ typedef void (dns_dbgfn)(int code, const struct sockaddr *sa, unsigned salen, dnscc_t *pkt, int plen, const struct dns_query *q, void *data); UDNS_API void dns_set_dbgfn(struct dns_ctx *ctx, dns_dbgfn *dbgfn); /* open and return UDP socket */ UDNS_API int dns_open(struct dns_ctx *ctx); /* return UDP socket or -1 if not open */ UDNS_API int dns_sock(const struct dns_ctx *ctx); /* close the UDP socket */ UDNS_API void dns_close(struct dns_ctx *ctx); /* return number of requests queued */ UDNS_API int dns_active(const struct dns_ctx *ctx); /* return status of the last operation */ UDNS_API int dns_status(const struct dns_ctx *ctx); UDNS_API void dns_setstatus(struct dns_ctx *ctx, int status); /* handle I/O event on UDP socket */ UDNS_API void dns_ioevent(struct dns_ctx *ctx, time_t now); /* process any timeouts, return time in secounds to the * next timeout (or -1 if none) but not greather than maxwait */ UDNS_API int dns_timeouts(struct dns_ctx *ctx, int maxwait, time_t now); /* define timer requesting routine to use */ typedef void dns_utm_fn(struct dns_ctx *ctx, int timeout, void *data); UDNS_API void dns_set_tmcbck(struct dns_ctx *ctx, dns_utm_fn *fn, void *data); /**************************************************************************/ /**************** Making Queries ******************************************/ /* query callback routine */ typedef void dns_query_fn(struct dns_ctx *ctx, void *result, void *data); /* query parse routine: raw DNS => application structure */ typedef int dns_parse_fn(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **res); enum dns_status { DNS_E_NOERROR = 0, /* ok, not an error */ DNS_E_TEMPFAIL = -1, /* timeout, SERVFAIL or similar */ DNS_E_PROTOCOL = -2, /* got garbled reply */ DNS_E_NXDOMAIN = -3, /* domain does not exists */ DNS_E_NODATA = -4, /* domain exists but no data of reqd type */ DNS_E_NOMEM = -5, /* out of memory while processing */ DNS_E_BADQUERY = -6 /* the query is malformed */ }; /* submit generic DN query */ UDNS_API struct dns_query * dns_submit_dn(struct dns_ctx *ctx, dnscc_t *dn, int qcls, int qtyp, int flags, dns_parse_fn *parse, dns_query_fn *cbck, void *data); /* submit generic name query */ UDNS_API struct dns_query * dns_submit_p(struct dns_ctx *ctx, const char *name, int qcls, int qtyp, int flags, dns_parse_fn *parse, dns_query_fn *cbck, void *data); /* cancel the given async query in progress */ UDNS_API int dns_cancel(struct dns_ctx *ctx, struct dns_query *q); /* resolve a generic query, return the answer */ UDNS_API void * dns_resolve_dn(struct dns_ctx *ctx, dnscc_t *qdn, int qcls, int qtyp, int flags, dns_parse_fn *parse); UDNS_API void * dns_resolve_p(struct dns_ctx *ctx, const char *qname, int qcls, int qtyp, int flags, dns_parse_fn *parse); UDNS_API void * dns_resolve(struct dns_ctx *ctx, struct dns_query *q); /* Specific RR handlers */ #define dns_rr_common(prefix) \ char *prefix##_cname; /* canonical name */ \ char *prefix##_qname; /* original query name */ \ unsigned prefix##_ttl; /* TTL value */ \ int prefix##_nrr /* number of records */ struct dns_rr_null { /* NULL RRset, aka RRset template */ dns_rr_common(dnsn); }; UDNS_API int dns_stdrr_size(const struct dns_parse *p); UDNS_API void * dns_stdrr_finish(struct dns_rr_null *ret, char *cp, const struct dns_parse *p); struct dns_rr_a4 { /* the A RRset */ dns_rr_common(dnsa4); struct in_addr *dnsa4_addr; /* array of addresses, naddr elements */ }; UDNS_API dns_parse_fn dns_parse_a4; /* A RR parsing routine */ typedef void /* A query callback routine */ dns_query_a4_fn(struct dns_ctx *ctx, struct dns_rr_a4 *result, void *data); /* submit A IN query */ UDNS_API struct dns_query * dns_submit_a4(struct dns_ctx *ctx, const char *name, int flags, dns_query_a4_fn *cbck, void *data); /* resolve A IN query */ UDNS_API struct dns_rr_a4 * dns_resolve_a4(struct dns_ctx *ctx, const char *name, int flags); struct dns_rr_a6 { /* the AAAA RRset */ dns_rr_common(dnsa6); struct in6_addr *dnsa6_addr; /* array of addresses, naddr elements */ }; UDNS_API dns_parse_fn dns_parse_a6; /* A RR parsing routine */ typedef void /* A query callback routine */ dns_query_a6_fn(struct dns_ctx *ctx, struct dns_rr_a6 *result, void *data); /* submit AAAA IN query */ UDNS_API struct dns_query * dns_submit_a6(struct dns_ctx *ctx, const char *name, int flags, dns_query_a6_fn *cbck, void *data); /* resolve AAAA IN query */ UDNS_API struct dns_rr_a6 * dns_resolve_a6(struct dns_ctx *ctx, const char *name, int flags); struct dns_rr_ptr { /* the PTR RRset */ dns_rr_common(dnsptr); char **dnsptr_ptr; /* array of PTRs */ }; UDNS_API dns_parse_fn dns_parse_ptr; /* PTR RR parsing routine */ typedef void /* PTR query callback */ dns_query_ptr_fn(struct dns_ctx *ctx, struct dns_rr_ptr *result, void *data); /* submit PTR IN in-addr.arpa query */ UDNS_API struct dns_query * dns_submit_a4ptr(struct dns_ctx *ctx, const struct in_addr *addr, dns_query_ptr_fn *cbck, void *data); /* resolve PTR IN in-addr.arpa query */ UDNS_API struct dns_rr_ptr * dns_resolve_a4ptr(struct dns_ctx *ctx, const struct in_addr *addr); /* the same as above, but for ip6.arpa */ UDNS_API struct dns_query * dns_submit_a6ptr(struct dns_ctx *ctx, const struct in6_addr *addr, dns_query_ptr_fn *cbck, void *data); UDNS_API struct dns_rr_ptr * dns_resolve_a6ptr(struct dns_ctx *ctx, const struct in6_addr *addr); struct dns_mx { /* single MX RR */ int priority; /* MX priority */ char *name; /* MX name */ }; struct dns_rr_mx { /* the MX RRset */ dns_rr_common(dnsmx); struct dns_mx *dnsmx_mx; /* array of MXes */ }; UDNS_API dns_parse_fn dns_parse_mx; /* MX RR parsing routine */ typedef void /* MX RR callback */ dns_query_mx_fn(struct dns_ctx *ctx, struct dns_rr_mx *result, void *data); /* submit MX IN query */ UDNS_API struct dns_query * dns_submit_mx(struct dns_ctx *ctx, const char *name, int flags, dns_query_mx_fn *cbck, void *data); /* resolve MX IN query */ UDNS_API struct dns_rr_mx * dns_resolve_mx(struct dns_ctx *ctx, const char *name, int flags); struct dns_txt { /* single TXT record */ int len; /* length of the text */ dnsc_t *txt; /* pointer to text buffer. May contain nulls. */ }; struct dns_rr_txt { /* the TXT RRset */ dns_rr_common(dnstxt); struct dns_txt *dnstxt_txt; /* array of TXT records */ }; UDNS_API dns_parse_fn dns_parse_txt; /* TXT RR parsing routine */ typedef void /* TXT RR callback */ dns_query_txt_fn(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data); /* submit TXT query */ UDNS_API struct dns_query * dns_submit_txt(struct dns_ctx *ctx, const char *name, int qcls, int flags, dns_query_txt_fn *cbck, void *data); /* resolve TXT query */ UDNS_API struct dns_rr_txt * dns_resolve_txt(struct dns_ctx *ctx, const char *name, int qcls, int flags); struct dns_srv { /* single SRV RR */ int priority; /* SRV priority */ int weight; /* SRV weight */ int port; /* SRV port */ char *name; /* SRV name */ }; struct dns_rr_srv { /* the SRV RRset */ dns_rr_common(dnssrv); struct dns_srv *dnssrv_srv; /* array of SRVes */ }; UDNS_API dns_parse_fn dns_parse_srv; /* SRV RR parsing routine */ typedef void /* SRV RR callback */ dns_query_srv_fn(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data); /* submit SRV IN query */ UDNS_API struct dns_query * dns_submit_srv(struct dns_ctx *ctx, const char *name, const char *srv, const char *proto, int flags, dns_query_srv_fn *cbck, void *data); /* resolve SRV IN query */ UDNS_API struct dns_rr_srv * dns_resolve_srv(struct dns_ctx *ctx, const char *name, const char *srv, const char *proto, int flags); /* NAPTR (RFC3403) RR type */ struct dns_naptr { /* single NAPTR RR */ int order; /* NAPTR order */ int preference; /* NAPTR preference */ char *flags; /* NAPTR flags */ char *service; /* NAPTR service */ char *regexp; /* NAPTR regexp */ char *replacement; /* NAPTR replacement */ }; struct dns_rr_naptr { /* the NAPTR RRset */ dns_rr_common(dnsnaptr); struct dns_naptr *dnsnaptr_naptr; /* array of NAPTRes */ }; UDNS_API dns_parse_fn dns_parse_naptr; /* NAPTR RR parsing routine */ typedef void /* NAPTR RR callback */ dns_query_naptr_fn(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *data); /* submit NAPTR IN query */ UDNS_API struct dns_query * dns_submit_naptr(struct dns_ctx *ctx, const char *name, int flags, dns_query_naptr_fn *cbck, void *data); /* resolve NAPTR IN query */ UDNS_API struct dns_rr_naptr * dns_resolve_naptr(struct dns_ctx *ctx, const char *name, int flags); UDNS_API struct dns_query * dns_submit_a4dnsbl(struct dns_ctx *ctx, const struct in_addr *addr, const char *dnsbl, dns_query_a4_fn *cbck, void *data); UDNS_API struct dns_query * dns_submit_a4dnsbl_txt(struct dns_ctx *ctx, const struct in_addr *addr, const char *dnsbl, dns_query_txt_fn *cbck, void *data); UDNS_API struct dns_rr_a4 * dns_resolve_a4dnsbl(struct dns_ctx *ctx, const struct in_addr *addr, const char *dnsbl); UDNS_API struct dns_rr_txt * dns_resolve_a4dnsbl_txt(struct dns_ctx *ctx, const struct in_addr *addr, const char *dnsbl); UDNS_API struct dns_query * dns_submit_a6dnsbl(struct dns_ctx *ctx, const struct in6_addr *addr, const char *dnsbl, dns_query_a4_fn *cbck, void *data); UDNS_API struct dns_query * dns_submit_a6dnsbl_txt(struct dns_ctx *ctx, const struct in6_addr *addr, const char *dnsbl, dns_query_txt_fn *cbck, void *data); UDNS_API struct dns_rr_a4 * dns_resolve_a6dnsbl(struct dns_ctx *ctx, const struct in6_addr *addr, const char *dnsbl); UDNS_API struct dns_rr_txt * dns_resolve_a6dnsbl_txt(struct dns_ctx *ctx, const struct in6_addr *addr, const char *dnsbl); UDNS_API struct dns_query * dns_submit_rhsbl(struct dns_ctx *ctx, const char *name, const char *rhsbl, dns_query_a4_fn *cbck, void *data); UDNS_API struct dns_query * dns_submit_rhsbl_txt(struct dns_ctx *ctx, const char *name, const char *rhsbl, dns_query_txt_fn *cbck, void *data); UDNS_API struct dns_rr_a4 * dns_resolve_rhsbl(struct dns_ctx *ctx, const char *name, const char *rhsbl); UDNS_API struct dns_rr_txt * dns_resolve_rhsbl_txt(struct dns_ctx *ctx, const char *name, const char *rhsbl); /**************************************************************************/ /**************** Names, Names ********************************************/ struct dns_nameval { int val; const char *name; }; UDNS_DATA_API extern const struct dns_nameval dns_classtab[]; UDNS_DATA_API extern const struct dns_nameval dns_typetab[]; UDNS_DATA_API extern const struct dns_nameval dns_rcodetab[]; UDNS_API int dns_findname(const struct dns_nameval *nv, const char *name); #define dns_findclassname(cls) dns_findname(dns_classtab, (cls)) #define dns_findtypename(type) dns_findname(dns_typetab, (type)) #define dns_findrcodename(rcode) dns_findname(dns_rcodetab, (rcode)) UDNS_API const char *dns_classname(enum dns_class cls); UDNS_API const char *dns_typename(enum dns_type type); UDNS_API const char *dns_rcodename(enum dns_rcode rcode); const char *_dns_format_code(char *buf, const char *prefix, int code); UDNS_API const char *dns_strerror(int errnum); /* simple pseudo-random number generator, code by Bob Jenkins */ struct udns_jranctx { /* the context */ unsigned a, b, c, d; }; /* initialize the RNG with a given seed */ UDNS_API void udns_jraninit(struct udns_jranctx *x, unsigned seed); /* return next random number. 32bits on most platforms so far. */ UDNS_API unsigned udns_jranval(struct udns_jranctx *x); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* include guard */ libtorrent-0.16.17/src/net/udns/udns_XtoX.c000066400000000000000000000027321522271512000205000ustar00rootroot00000000000000/* udns_XtoX.c dns_ntop() and dns_pton() routines, which are either - wrappers for inet_ntop() and inet_pton() or - reimplementations of those routines. Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "udns.h" #ifdef HAVE_INET_PTON_NTOP #include #include #include const char *dns_ntop(int af, const void *src, char *dst, unsigned size) { return inet_ntop(af, src, dst, size); } int dns_pton(int af, const char *src, void *dst) { return inet_pton(af, src, dst); } #else #define inet_XtoX_prefix dns_ #include "inet_XtoX.c" #endif libtorrent-0.16.17/src/net/udns/udns_bl.c000066400000000000000000000121321522271512000201660ustar00rootroot00000000000000/* udns_bl.c DNSBL stuff Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "udns.h" #ifndef NULL # define NULL 0 #endif struct dns_query * dns_submit_a4dnsbl(struct dns_ctx *ctx, const struct in_addr *addr, const char *dnsbl, dns_query_a4_fn *cbck, void *data) { dnsc_t dn[DNS_MAXDN]; if (dns_a4ptodn(addr, dnsbl, dn, sizeof(dn)) <= 0) { dns_setstatus(ctx, DNS_E_BADQUERY); return NULL; } return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_A, DNS_NOSRCH, dns_parse_a4, (dns_query_fn*)cbck, data); } struct dns_query * dns_submit_a4dnsbl_txt(struct dns_ctx *ctx, const struct in_addr *addr, const char *dnsbl, dns_query_txt_fn *cbck, void *data) { dnsc_t dn[DNS_MAXDN]; if (dns_a4ptodn(addr, dnsbl, dn, sizeof(dn)) <= 0) { dns_setstatus(ctx, DNS_E_BADQUERY); return NULL; } return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_TXT, DNS_NOSRCH, dns_parse_txt, (dns_query_fn*)cbck, data); } struct dns_rr_a4 * dns_resolve_a4dnsbl(struct dns_ctx *ctx, const struct in_addr *addr, const char *dnsbl) { return (struct dns_rr_a4 *) dns_resolve(ctx, dns_submit_a4dnsbl(ctx, addr, dnsbl, 0, 0)); } struct dns_rr_txt * dns_resolve_a4dnsbl_txt(struct dns_ctx *ctx, const struct in_addr *addr, const char *dnsbl) { return (struct dns_rr_txt *) dns_resolve(ctx, dns_submit_a4dnsbl_txt(ctx, addr, dnsbl, 0, 0)); } struct dns_query * dns_submit_a6dnsbl(struct dns_ctx *ctx, const struct in6_addr *addr, const char *dnsbl, dns_query_a4_fn *cbck, void *data) { dnsc_t dn[DNS_MAXDN]; if (dns_a6ptodn(addr, dnsbl, dn, sizeof(dn)) <= 0) { dns_setstatus(ctx, DNS_E_BADQUERY); return NULL; } return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_A, DNS_NOSRCH, dns_parse_a4, (dns_query_fn*)cbck, data); } struct dns_query * dns_submit_a6dnsbl_txt(struct dns_ctx *ctx, const struct in6_addr *addr, const char *dnsbl, dns_query_txt_fn *cbck, void *data) { dnsc_t dn[DNS_MAXDN]; if (dns_a6ptodn(addr, dnsbl, dn, sizeof(dn)) <= 0) { dns_setstatus(ctx, DNS_E_BADQUERY); return NULL; } return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_TXT, DNS_NOSRCH, dns_parse_txt, (dns_query_fn*)cbck, data); } struct dns_rr_a4 * dns_resolve_a6dnsbl(struct dns_ctx *ctx, const struct in6_addr *addr, const char *dnsbl) { return (struct dns_rr_a4 *) dns_resolve(ctx, dns_submit_a6dnsbl(ctx, addr, dnsbl, 0, 0)); } struct dns_rr_txt * dns_resolve_a6dnsbl_txt(struct dns_ctx *ctx, const struct in6_addr *addr, const char *dnsbl) { return (struct dns_rr_txt *) dns_resolve(ctx, dns_submit_a6dnsbl_txt(ctx, addr, dnsbl, 0, 0)); } static int dns_rhsbltodn(const char *name, const char *rhsbl, dnsc_t dn[DNS_MAXDN]) { int l = dns_sptodn(name, dn, DNS_MAXDN); if (l <= 0) return 0; l = dns_sptodn(rhsbl, dn+l-1, DNS_MAXDN-l+1); if (l <= 0) return 0; return 1; } struct dns_query * dns_submit_rhsbl(struct dns_ctx *ctx, const char *name, const char *rhsbl, dns_query_a4_fn *cbck, void *data) { dnsc_t dn[DNS_MAXDN]; if (!dns_rhsbltodn(name, rhsbl, dn)) { dns_setstatus(ctx, DNS_E_BADQUERY); return NULL; } return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_A, DNS_NOSRCH, dns_parse_a4, (dns_query_fn*)cbck, data); } struct dns_query * dns_submit_rhsbl_txt(struct dns_ctx *ctx, const char *name, const char *rhsbl, dns_query_txt_fn *cbck, void *data) { dnsc_t dn[DNS_MAXDN]; if (!dns_rhsbltodn(name, rhsbl, dn)) { dns_setstatus(ctx, DNS_E_BADQUERY); return NULL; } return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_TXT, DNS_NOSRCH, dns_parse_txt, (dns_query_fn*)cbck, data); } struct dns_rr_a4 * dns_resolve_rhsbl(struct dns_ctx *ctx, const char *name, const char *rhsbl) { return (struct dns_rr_a4*) dns_resolve(ctx, dns_submit_rhsbl(ctx, name, rhsbl, 0, 0)); } struct dns_rr_txt * dns_resolve_rhsbl_txt(struct dns_ctx *ctx, const char *name, const char *rhsbl) { return (struct dns_rr_txt*) dns_resolve(ctx, dns_submit_rhsbl_txt(ctx, name, rhsbl, 0, 0)); } libtorrent-0.16.17/src/net/udns/udns_codes.c000066400000000000000000000146411522271512000206750ustar00rootroot00000000000000/* Automatically generated. */ #include "udns.h" const struct dns_nameval dns_typetab[] = { {DNS_T_INVALID,"INVALID"}, {DNS_T_A,"A"}, {DNS_T_NS,"NS"}, {DNS_T_MD,"MD"}, {DNS_T_MF,"MF"}, {DNS_T_CNAME,"CNAME"}, {DNS_T_SOA,"SOA"}, {DNS_T_MB,"MB"}, {DNS_T_MG,"MG"}, {DNS_T_MR,"MR"}, {DNS_T_NULL,"NULL"}, {DNS_T_WKS,"WKS"}, {DNS_T_PTR,"PTR"}, {DNS_T_HINFO,"HINFO"}, {DNS_T_MINFO,"MINFO"}, {DNS_T_MX,"MX"}, {DNS_T_TXT,"TXT"}, {DNS_T_RP,"RP"}, {DNS_T_AFSDB,"AFSDB"}, {DNS_T_X25,"X25"}, {DNS_T_ISDN,"ISDN"}, {DNS_T_RT,"RT"}, {DNS_T_NSAP,"NSAP"}, {DNS_T_NSAP_PTR,"NSAP_PTR"}, {DNS_T_SIG,"SIG"}, {DNS_T_KEY,"KEY"}, {DNS_T_PX,"PX"}, {DNS_T_GPOS,"GPOS"}, {DNS_T_AAAA,"AAAA"}, {DNS_T_LOC,"LOC"}, {DNS_T_NXT,"NXT"}, {DNS_T_EID,"EID"}, {DNS_T_NIMLOC,"NIMLOC"}, {DNS_T_SRV,"SRV"}, {DNS_T_ATMA,"ATMA"}, {DNS_T_NAPTR,"NAPTR"}, {DNS_T_KX,"KX"}, {DNS_T_CERT,"CERT"}, {DNS_T_A6,"A6"}, {DNS_T_DNAME,"DNAME"}, {DNS_T_SINK,"SINK"}, {DNS_T_OPT,"OPT"}, {DNS_T_DS,"DS"}, {DNS_T_SSHFP,"SSHFP"}, {DNS_T_IPSECKEY,"IPSECKEY"}, {DNS_T_RRSIG,"RRSIG"}, {DNS_T_NSEC,"NSEC"}, {DNS_T_DNSKEY,"DNSKEY"}, {DNS_T_DHCID,"DHCID"}, {DNS_T_NSEC3,"NSEC3"}, {DNS_T_NSEC3PARAMS,"NSEC3PARAMS"}, {DNS_T_TALINK,"TALINK"}, {DNS_T_SPF,"SPF"}, {DNS_T_UINFO,"UINFO"}, {DNS_T_UID,"UID"}, {DNS_T_GID,"GID"}, {DNS_T_UNSPEC,"UNSPEC"}, {DNS_T_TSIG,"TSIG"}, {DNS_T_IXFR,"IXFR"}, {DNS_T_AXFR,"AXFR"}, {DNS_T_MAILB,"MAILB"}, {DNS_T_MAILA,"MAILA"}, {DNS_T_ANY,"ANY"}, {DNS_T_ZXFR,"ZXFR"}, {DNS_T_DLV,"DLV"}, {DNS_T_MAX,"MAX"}, {0,0}}; const char *dns_typename(enum dns_type code) { static char nm[20]; switch(code) { case DNS_T_INVALID: return dns_typetab[0].name; case DNS_T_A: return dns_typetab[1].name; case DNS_T_NS: return dns_typetab[2].name; case DNS_T_MD: return dns_typetab[3].name; case DNS_T_MF: return dns_typetab[4].name; case DNS_T_CNAME: return dns_typetab[5].name; case DNS_T_SOA: return dns_typetab[6].name; case DNS_T_MB: return dns_typetab[7].name; case DNS_T_MG: return dns_typetab[8].name; case DNS_T_MR: return dns_typetab[9].name; case DNS_T_NULL: return dns_typetab[10].name; case DNS_T_WKS: return dns_typetab[11].name; case DNS_T_PTR: return dns_typetab[12].name; case DNS_T_HINFO: return dns_typetab[13].name; case DNS_T_MINFO: return dns_typetab[14].name; case DNS_T_MX: return dns_typetab[15].name; case DNS_T_TXT: return dns_typetab[16].name; case DNS_T_RP: return dns_typetab[17].name; case DNS_T_AFSDB: return dns_typetab[18].name; case DNS_T_X25: return dns_typetab[19].name; case DNS_T_ISDN: return dns_typetab[20].name; case DNS_T_RT: return dns_typetab[21].name; case DNS_T_NSAP: return dns_typetab[22].name; case DNS_T_NSAP_PTR: return dns_typetab[23].name; case DNS_T_SIG: return dns_typetab[24].name; case DNS_T_KEY: return dns_typetab[25].name; case DNS_T_PX: return dns_typetab[26].name; case DNS_T_GPOS: return dns_typetab[27].name; case DNS_T_AAAA: return dns_typetab[28].name; case DNS_T_LOC: return dns_typetab[29].name; case DNS_T_NXT: return dns_typetab[30].name; case DNS_T_EID: return dns_typetab[31].name; case DNS_T_NIMLOC: return dns_typetab[32].name; case DNS_T_SRV: return dns_typetab[33].name; case DNS_T_ATMA: return dns_typetab[34].name; case DNS_T_NAPTR: return dns_typetab[35].name; case DNS_T_KX: return dns_typetab[36].name; case DNS_T_CERT: return dns_typetab[37].name; case DNS_T_A6: return dns_typetab[38].name; case DNS_T_DNAME: return dns_typetab[39].name; case DNS_T_SINK: return dns_typetab[40].name; case DNS_T_OPT: return dns_typetab[41].name; case DNS_T_DS: return dns_typetab[42].name; case DNS_T_SSHFP: return dns_typetab[43].name; case DNS_T_IPSECKEY: return dns_typetab[44].name; case DNS_T_RRSIG: return dns_typetab[45].name; case DNS_T_NSEC: return dns_typetab[46].name; case DNS_T_DNSKEY: return dns_typetab[47].name; case DNS_T_DHCID: return dns_typetab[48].name; case DNS_T_NSEC3: return dns_typetab[49].name; case DNS_T_NSEC3PARAMS: return dns_typetab[50].name; case DNS_T_TALINK: return dns_typetab[51].name; case DNS_T_SPF: return dns_typetab[52].name; case DNS_T_UINFO: return dns_typetab[53].name; case DNS_T_UID: return dns_typetab[54].name; case DNS_T_GID: return dns_typetab[55].name; case DNS_T_UNSPEC: return dns_typetab[56].name; case DNS_T_TSIG: return dns_typetab[57].name; case DNS_T_IXFR: return dns_typetab[58].name; case DNS_T_AXFR: return dns_typetab[59].name; case DNS_T_MAILB: return dns_typetab[60].name; case DNS_T_MAILA: return dns_typetab[61].name; case DNS_T_ANY: return dns_typetab[62].name; case DNS_T_ZXFR: return dns_typetab[63].name; case DNS_T_DLV: return dns_typetab[64].name; case DNS_T_MAX: return dns_typetab[65].name; } return _dns_format_code(nm,"type",code); } const struct dns_nameval dns_classtab[] = { {DNS_C_INVALID,"INVALID"}, {DNS_C_IN,"IN"}, {DNS_C_CH,"CH"}, {DNS_C_HS,"HS"}, {DNS_C_ANY,"ANY"}, {0,0}}; const char *dns_classname(enum dns_class code) { static char nm[20]; switch(code) { case DNS_C_INVALID: return dns_classtab[0].name; case DNS_C_IN: return dns_classtab[1].name; case DNS_C_CH: return dns_classtab[2].name; case DNS_C_HS: return dns_classtab[3].name; case DNS_C_ANY: return dns_classtab[4].name; } return _dns_format_code(nm,"class",code); } const struct dns_nameval dns_rcodetab[] = { {DNS_R_NOERROR,"NOERROR"}, {DNS_R_FORMERR,"FORMERR"}, {DNS_R_SERVFAIL,"SERVFAIL"}, {DNS_R_NXDOMAIN,"NXDOMAIN"}, {DNS_R_NOTIMPL,"NOTIMPL"}, {DNS_R_REFUSED,"REFUSED"}, {DNS_R_YXDOMAIN,"YXDOMAIN"}, {DNS_R_YXRRSET,"YXRRSET"}, {DNS_R_NXRRSET,"NXRRSET"}, {DNS_R_NOTAUTH,"NOTAUTH"}, {DNS_R_NOTZONE,"NOTZONE"}, {DNS_R_BADSIG,"BADSIG"}, {DNS_R_BADKEY,"BADKEY"}, {DNS_R_BADTIME,"BADTIME"}, {0,0}}; const char *dns_rcodename(enum dns_rcode code) { static char nm[20]; switch(code) { case DNS_R_NOERROR: return dns_rcodetab[0].name; case DNS_R_FORMERR: return dns_rcodetab[1].name; case DNS_R_SERVFAIL: return dns_rcodetab[2].name; case DNS_R_NXDOMAIN: return dns_rcodetab[3].name; case DNS_R_NOTIMPL: return dns_rcodetab[4].name; case DNS_R_REFUSED: return dns_rcodetab[5].name; case DNS_R_YXDOMAIN: return dns_rcodetab[6].name; case DNS_R_YXRRSET: return dns_rcodetab[7].name; case DNS_R_NXRRSET: return dns_rcodetab[8].name; case DNS_R_NOTAUTH: return dns_rcodetab[9].name; case DNS_R_NOTZONE: return dns_rcodetab[10].name; case DNS_R_BADSIG: return dns_rcodetab[11].name; case DNS_R_BADKEY: return dns_rcodetab[12].name; case DNS_R_BADTIME: return dns_rcodetab[13].name; } return _dns_format_code(nm,"rcode",code); } libtorrent-0.16.17/src/net/udns/udns_dn.c000066400000000000000000000232421522271512000201760ustar00rootroot00000000000000/* udns_dn.c domain names manipulation routines Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "udns.h" unsigned dns_dnlen(dnscc_t *dn) { register dnscc_t *d = dn; while(*d) d += 1 + *d; return (unsigned)(d - dn) + 1; } unsigned dns_dnlabels(register dnscc_t *dn) { register unsigned l = 0; while(*dn) ++l, dn += 1 + *dn; return l; } unsigned dns_dnequal(register dnscc_t *dn1, register dnscc_t *dn2) { register unsigned c; dnscc_t *dn = dn1; for(;;) { if ((c = *dn1++) != *dn2++) return 0; if (!c) return (unsigned)(dn1 - dn); while(c--) { if (DNS_DNLC(*dn1) != DNS_DNLC(*dn2)) return 0; ++dn1; ++dn2; } } } unsigned dns_dntodn(dnscc_t *sdn, dnsc_t *ddn, unsigned ddnsiz) { unsigned sdnlen = dns_dnlen(sdn); if (ddnsiz < sdnlen) return 0; memcpy(ddn, sdn, sdnlen); return sdnlen; } int dns_ptodn(const char *name, unsigned namelen, dnsc_t *dn, unsigned dnsiz, int *isabs) { dnsc_t *dp; /* current position in dn (len byte first) */ dnsc_t *const de /* end of dn: last byte that can be filled up */ = dn + (dnsiz >= DNS_MAXDN ? DNS_MAXDN : dnsiz) - 1; dnscc_t *np = (dnscc_t *)name; dnscc_t *ne = np + (namelen ? namelen : strlen((char*)np)); dnsc_t *llab; /* start of last label (llab[-1] will be length) */ unsigned c; /* next input character, or length of last label */ if (!dnsiz) return 0; dp = llab = dn + 1; while(np < ne) { if (*np == '.') { /* label delimiter */ c = dp - llab; /* length of the label */ if (!c) { /* empty label */ if (np == (dnscc_t *)name && np + 1 == ne) { /* special case for root dn, aka `.' */ ++np; break; } return -1; /* zero label */ } if (c > DNS_MAXLABEL) return -1; /* label too long */ llab[-1] = (dnsc_t)c; /* update len of last label */ llab = ++dp; /* start new label, llab[-1] will be len of it */ ++np; continue; } /* check whenever we may put out one more byte */ if (dp >= de) /* too long? */ return dnsiz >= DNS_MAXDN ? -1 : 0; if (*np != '\\') { /* non-escape, simple case */ *dp++ = *np++; continue; } /* handle \-style escape */ /* note that traditionally, domain names (gethostbyname etc) * used decimal \dd notation, not octal \ooo (RFC1035), so * we're following this tradition here. */ if (++np == ne) return -1; /* bad escape */ else if (*np >= '0' && *np <= '9') { /* decimal number */ /* we allow not only exactly 3 digits as per RFC1035, * but also 2 or 1, for better usability. */ c = *np++ - '0'; if (np < ne && *np >= '0' && *np <= '9') { /* 2digits */ c = c * 10 + *np++ - '0'; if (np < ne && *np >= '0' && *np <= '9') { c = c * 10 + *np++ - '0'; if (c > 255) return -1; /* bad escape */ } } } else c = *np++; *dp++ = (dnsc_t)c; /* place next out byte */ } if ((c = dp - llab) > DNS_MAXLABEL) return -1; /* label too long */ if ((llab[-1] = (dnsc_t)c) != 0) { *dp++ = 0; if (isabs) *isabs = 0; } else if (isabs) *isabs = 1; return dp - dn; } dnscc_t dns_inaddr_arpa_dn[14] = "\07in-addr\04arpa"; dnsc_t * dns_a4todn_(const struct in_addr *addr, dnsc_t *dn, dnsc_t *dne) { const unsigned char *s = ((const unsigned char *)addr) + 4; while(s > (const unsigned char *)addr) { unsigned n = *--s; dnsc_t *p = dn + 1; if (n > 99) { if (p + 2 > dne) return 0; *p++ = n / 100 + '0'; *p++ = (n % 100 / 10) + '0'; *p = n % 10 + '0'; } else if (n > 9) { if (p + 1 > dne) return 0; *p++ = n / 10 + '0'; *p = n % 10 + '0'; } else { if (p > dne) return 0; *p = n + '0'; } *dn = p - dn; dn = p + 1; } return dn; } int dns_a4todn(const struct in_addr *addr, dnscc_t *tdn, dnsc_t *dn, unsigned dnsiz) { dnsc_t *dne = dn + (dnsiz > DNS_MAXDN ? DNS_MAXDN : dnsiz); dnsc_t *p; unsigned l; p = dns_a4todn_(addr, dn, dne); if (!p) return 0; if (!tdn) tdn = dns_inaddr_arpa_dn; l = dns_dnlen(tdn); if (p + l > dne) return dnsiz >= DNS_MAXDN ? -1 : 0; memcpy(p, tdn, l); return (p + l) - dn; } int dns_a4ptodn(const struct in_addr *addr, const char *tname, dnsc_t *dn, unsigned dnsiz) { dnsc_t *p; int r; if (!tname) return dns_a4todn(addr, NULL, dn, dnsiz); p = dns_a4todn_(addr, dn, dn + dnsiz); if (!p) return 0; r = dns_sptodn(tname, p, dnsiz - (p - dn)); return r != 0 ? r : dnsiz >= DNS_MAXDN ? -1 : 0; } dnscc_t dns_ip6_arpa_dn[10] = "\03ip6\04arpa"; dnsc_t * dns_a6todn_(const struct in6_addr *addr, dnsc_t *dn, dnsc_t *dne) { const unsigned char *s = ((const unsigned char *)addr) + 16; if (dn + 64 > dne) return 0; while(s > (const unsigned char *)addr) { unsigned n = *--s & 0x0f; *dn++ = 1; *dn++ = n > 9 ? n + 'a' - 10 : n + '0'; *dn++ = 1; n = *s >> 4; *dn++ = n > 9 ? n + 'a' - 10 : n + '0'; } return dn; } int dns_a6todn(const struct in6_addr *addr, dnscc_t *tdn, dnsc_t *dn, unsigned dnsiz) { dnsc_t *dne = dn + (dnsiz > DNS_MAXDN ? DNS_MAXDN : dnsiz); dnsc_t *p; unsigned l; p = dns_a6todn_(addr, dn, dne); if (!p) return 0; if (!tdn) tdn = dns_ip6_arpa_dn; l = dns_dnlen(tdn); if (p + l > dne) return dnsiz >= DNS_MAXDN ? -1 : 0; memcpy(p, tdn, l); return (p + l) - dn; } int dns_a6ptodn(const struct in6_addr *addr, const char *tname, dnsc_t *dn, unsigned dnsiz) { dnsc_t *p; int r; if (!tname) return dns_a6todn(addr, NULL, dn, dnsiz); p = dns_a6todn_(addr, dn, dn + dnsiz); if (!p) return 0; r = dns_sptodn(tname, p, dnsiz - (p - dn)); return r != 0 ? r : dnsiz >= DNS_MAXDN ? -1 : 0; } /* return size of buffer required to convert the dn into asciiz string. * Keep in sync with dns_dntop() below. */ unsigned dns_dntop_size(dnscc_t *dn) { unsigned size = 0; /* the size reqd */ dnscc_t *le; /* label end */ while(*dn) { /* *dn is the length of the next label, non-zero */ if (size) ++size; /* for the dot */ le = dn + *dn + 1; ++dn; do { switch(*dn) { case '.': case '\\': /* Special modifiers in zone files. */ case '"': case ';': case '@': case '$': size += 2; break; default: if (*dn <= 0x20 || *dn >= 0x7f) /* \ddd decimal notation */ size += 4; else size += 1; } } while(++dn < le); } size += 1; /* zero byte at the end - string terminator */ return size > DNS_MAXNAME ? 0 : size; } /* Convert the dn into asciiz string. * Keep in sync with dns_dntop_size() above. */ int dns_dntop(dnscc_t *dn, char *name, unsigned namesiz) { char *np = name; /* current name ptr */ char *const ne = name + namesiz; /* end of name */ dnscc_t *le; /* label end */ while(*dn) { /* *dn is the length of the next label, non-zero */ if (np != name) { if (np >= ne) goto toolong; *np++ = '.'; } le = dn + *dn + 1; ++dn; do { switch(*dn) { case '.': case '\\': /* Special modifiers in zone files. */ case '"': case ';': case '@': case '$': if (np + 2 > ne) goto toolong; *np++ = '\\'; *np++ = *dn; break; default: if (*dn <= 0x20 || *dn >= 0x7f) { /* \ddd decimal notation */ if (np + 4 >= ne) goto toolong; *np++ = '\\'; *np++ = '0' + (*dn / 100); *np++ = '0' + ((*dn % 100) / 10); *np++ = '0' + (*dn % 10); } else { if (np >= ne) goto toolong; *np++ = *dn; } } } while(++dn < le); } if (np >= ne) goto toolong; *np++ = '\0'; return np - name; toolong: return namesiz >= DNS_MAXNAME ? -1 : 0; } #ifdef TEST #include #include int main(int argc, char **argv) { int i; int sz; dnsc_t dn[DNS_MAXDN+10]; dnsc_t *dl, *dp; int isabs; sz = (argc > 1) ? atoi(argv[1]) : 0; for(i = 2; i < argc; ++i) { int r = dns_ptodn(argv[i], 0, dn, sz, &isabs); printf("%s: ", argv[i]); if (r < 0) printf("error\n"); else if (!r) printf("buffer too small\n"); else { printf("len=%d dnlen=%d size=%d name:", r, dns_dnlen(dn), dns_dntop_size(dn)); dl = dn; while(*dl) { printf(" %d=", *dl); dp = dl + 1; dl = dp + *dl; while(dp < dl) { if (*dp <= ' ' || *dp >= 0x7f) printf("\\%03d", *dp); else if (*dp == '.' || *dp == '\\') printf("\\%c", *dp); else putchar(*dp); ++dp; } } if (isabs) putchar('.'); putchar('\n'); } } return 0; } #endif /* TEST */ libtorrent-0.16.17/src/net/udns/udns_dntosp.c000066400000000000000000000021451522271512000211030ustar00rootroot00000000000000/* udns_dntosp.c dns_dntosp() = convert DN to asciiz string using static buffer Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "udns.h" static char name[DNS_MAXNAME]; const char *dns_dntosp(dnscc_t *dn) { return dns_dntop(dn, name, sizeof(name)) > 0 ? name : 0; } libtorrent-0.16.17/src/net/udns/udns_init.c000066400000000000000000000152731522271512000205450ustar00rootroot00000000000000/* udns_init.c resolver initialisation stuff Copyright (C) 2006 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef WINDOWS # include /* includes */ # include /* for dns server addresses etc */ #else # include # include # include #endif /* !WINDOWS */ #include #include #include "udns.h" #define ISSPACE(x) (x == ' ' || x == '\t' || x == '\r' || x == '\n') static const char space[] = " \t\r\n"; static void dns_set_serv_internal(struct dns_ctx *ctx, char *serv) { dns_add_serv(ctx, NULL); for(serv = strtok(serv, space); serv; serv = strtok(NULL, space)) dns_add_serv(ctx, serv); } static void dns_set_srch_internal(struct dns_ctx *ctx, char *srch) { dns_add_srch(ctx, NULL); for(srch = strtok(srch, space); srch; srch = strtok(NULL, space)) dns_add_srch(ctx, srch); } #ifdef WINDOWS #ifndef NO_IPHLPAPI /* Apparently, some systems does not have proper headers for IPHLPAIP to work. * The best is to upgrade headers, but here's another, ugly workaround for * this: compile with -DNO_IPHLPAPI. */ typedef DWORD (WINAPI *GetAdaptersAddressesFunc)( ULONG Family, DWORD Flags, PVOID Reserved, PIP_ADAPTER_ADDRESSES pAdapterAddresses, PULONG pOutBufLen); static int dns_initns_iphlpapi(struct dns_ctx *ctx) { HANDLE h_iphlpapi; GetAdaptersAddressesFunc pfnGetAdAddrs; PIP_ADAPTER_ADDRESSES pAddr, pAddrBuf; PIP_ADAPTER_DNS_SERVER_ADDRESS pDnsAddr; ULONG ulOutBufLen; DWORD dwRetVal; int ret = -1; h_iphlpapi = LoadLibrary("iphlpapi.dll"); if (!h_iphlpapi) return -1; pfnGetAdAddrs = (GetAdaptersAddressesFunc) GetProcAddress(h_iphlpapi, "GetAdaptersAddresses"); if (!pfnGetAdAddrs) goto freelib; ulOutBufLen = 0; dwRetVal = pfnGetAdAddrs(AF_UNSPEC, 0, NULL, NULL, &ulOutBufLen); if (dwRetVal != ERROR_BUFFER_OVERFLOW) goto freelib; pAddrBuf = malloc(ulOutBufLen); if (!pAddrBuf) goto freelib; dwRetVal = pfnGetAdAddrs(AF_UNSPEC, 0, NULL, pAddrBuf, &ulOutBufLen); if (dwRetVal != ERROR_SUCCESS) goto freemem; for (pAddr = pAddrBuf; pAddr; pAddr = pAddr->Next) for (pDnsAddr = pAddr->FirstDnsServerAddress; pDnsAddr; pDnsAddr = pDnsAddr->Next) dns_add_serv_s(ctx, pDnsAddr->Address.lpSockaddr); ret = 0; freemem: free(pAddrBuf); freelib: FreeLibrary(h_iphlpapi); return ret; } #else /* NO_IPHLPAPI */ #define dns_initns_iphlpapi(ctx) (-1) #endif /* NO_IPHLPAPI */ static int dns_initns_registry(struct dns_ctx *ctx) { LONG res; HKEY hk; DWORD type = REG_EXPAND_SZ | REG_SZ; DWORD len; char valBuf[1024]; #define REGKEY_WINNT "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters" #define REGKEY_WIN9x "SYSTEM\\CurrentControlSet\\Services\\VxD\\MSTCP" res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGKEY_WINNT, 0, KEY_QUERY_VALUE, &hk); if (res != ERROR_SUCCESS) res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGKEY_WIN9x, 0, KEY_QUERY_VALUE, &hk); if (res != ERROR_SUCCESS) return -1; len = sizeof(valBuf) - 1; res = RegQueryValueEx(hk, "NameServer", NULL, &type, (BYTE*)valBuf, &len); if (res != ERROR_SUCCESS || !len || !valBuf[0]) { len = sizeof(valBuf) - 1; res = RegQueryValueEx(hk, "DhcpNameServer", NULL, &type, (BYTE*)valBuf, &len); } RegCloseKey(hk); if (res != ERROR_SUCCESS || !len || !valBuf[0]) return -1; valBuf[len] = '\0'; /* nameservers are stored as a whitespace-seperate list: * "192.168.1.1 123.21.32.12" */ dns_set_serv_internal(ctx, valBuf); return 0; } #else /* !WINDOWS */ static int dns_init_resolvconf(struct dns_ctx *ctx) { char *v; char buf[2049]; /* this buffer is used to hold /etc/resolv.conf */ int has_srch = 0; /* read resolv.conf... */ { int fd = open("/etc/resolv.conf", O_RDONLY); if (fd >= 0) { int l = read(fd, buf, sizeof(buf) - 1); close(fd); buf[l < 0 ? 0 : l] = '\0'; } else buf[0] = '\0'; } if (buf[0]) { /* ...and parse it */ char *line, *nextline; line = buf; do { nextline = strchr(line, '\n'); if (nextline) *nextline++ = '\0'; v = line; while(*v && !ISSPACE(*v)) ++v; if (!*v) continue; *v++ = '\0'; while(ISSPACE(*v)) ++v; if (!*v) continue; if (strcmp(line, "domain") == 0) { dns_set_srch_internal(ctx, strtok(v, space)); has_srch = 1; } else if (strcmp(line, "search") == 0) { dns_set_srch_internal(ctx, v); has_srch = 1; } else if (strcmp(line, "nameserver") == 0) dns_add_serv(ctx, strtok(v, space)); else if (strcmp(line, "options") == 0) dns_set_opts(ctx, v); } while((line = nextline) != NULL); } buf[sizeof(buf)-1] = '\0'; /* get list of nameservers from env. vars. */ if ((v = getenv("NSCACHEIP")) != NULL || (v = getenv("NAMESERVERS")) != NULL) { strncpy(buf, v, sizeof(buf) - 1); dns_set_serv_internal(ctx, buf); } /* if $LOCALDOMAIN is set, use it for search list */ if ((v = getenv("LOCALDOMAIN")) != NULL) { strncpy(buf, v, sizeof(buf) - 1); dns_set_srch_internal(ctx, buf); has_srch = 1; } if ((v = getenv("RES_OPTIONS")) != NULL) dns_set_opts(ctx, v); /* if still no search list, use local domain name */ if (!has_srch && gethostname(buf, sizeof(buf) - 1) == 0 && (v = strchr(buf, '.')) != NULL && *++v != '\0') dns_add_srch(ctx, v); return 0; } #endif /* !WINDOWS */ int dns_init(struct dns_ctx *ctx, int do_open) { if (!ctx) ctx = &dns_defctx; dns_reset(ctx); #ifdef WINDOWS if (dns_initns_iphlpapi(ctx) != 0) dns_initns_registry(ctx); /*XXX WINDOWS: probably good to get default domain and search list too... * And options. Something is in registry. */ /*XXX WINDOWS: maybe environment variables are also useful? */ #else dns_init_resolvconf(ctx); #endif return do_open ? dns_open(ctx) : 0; } libtorrent-0.16.17/src/net/udns/udns_jran.c000066400000000000000000000027511522271512000205310ustar00rootroot00000000000000/* udns_jran.c: small non-cryptographic random number generator * taken from http://burtleburtle.net/bob/rand/smallprng.html * by Bob Jenkins, Public domain. */ #include "udns.h" #define rot32(x,k) (((x) << (k)) | ((x) >> (32-(k)))) #define rot64(x,k) (((x) << (k)) | ((x) >> (64-(k)))) #define tr32(x) ((x)&0xffffffffu) unsigned udns_jranval(struct udns_jranctx *x) { /* This routine can be made to work with either 32 or 64bit words - * if JRAN_32_64 is defined when compiling the file. * We use if() instead of #if since there's no good * portable way to check sizeof() in preprocessor without * introducing some ugly configure-time checks. * Most compilers will optimize the wrong branches away anyway. * By default it assumes 32bit integers */ #ifdef JRAN_32_64 if (sizeof(unsigned) == 4) { #endif unsigned e = tr32(x->a - rot32(x->b, 27)); x->a = tr32(x->b ^ rot32(x->c, 17)); x->b = tr32(x->c + x->d); x->c = tr32(x->d + e); x->d = tr32(e + x->a); #ifdef JRAN_32_64 } else if (sizeof(unsigned) == 8) { /* assuming it's 64bits */ unsigned e = x->a - rot64(x->b, 7); x->a = x->b ^ rot64(x->c, 13); x->b = x->c + rot64(x->d, 37); x->c = x->d + e; x->d = e + x->a; } else { unsigned e = 0; x->d = 1/e; /* bail */ } #endif return x->d; } void udns_jraninit(struct udns_jranctx *x, unsigned seed) { unsigned i; x->a = 0xf1ea5eed; x->b = x->c = x->d = seed; for (i = 0; i < 20; ++i) (void)udns_jranval(x); } libtorrent-0.16.17/src/net/udns/udns_misc.c000066400000000000000000000041021522271512000205220ustar00rootroot00000000000000/* udns_misc.c miscellaneous routines Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "udns.h" int dns_findname(const struct dns_nameval *nv, const char *name) { register const char *a, *b; for(; nv->name; ++nv) for(a = name, b = nv->name; ; ++a, ++b) if (DNS_DNUC(*a) != *b) break; else if (!*a) return nv->val; return -1; } const char *_dns_format_code(char *buf, const char *prefix, int code) { char *bp = buf; unsigned c, n; do *bp++ = DNS_DNUC(*prefix); while(*++prefix); *bp++ = '#'; if (code < 0) code = -code, *bp++ = '-'; n = 0; c = code; do ++n; while((c /= 10)); c = code; bp[n--] = '\0'; do bp[n--] = c % 10 + '0'; while((c /= 10)); return buf; } const char *dns_strerror(int err) { if (err >= 0) return "successeful completion"; switch(err) { case DNS_E_TEMPFAIL: return "temporary failure in name resolution"; case DNS_E_PROTOCOL: return "protocol error"; case DNS_E_NXDOMAIN: return "domain name does not exist"; case DNS_E_NODATA: return "valid domain but no data of requested type"; case DNS_E_NOMEM: return "out of memory"; case DNS_E_BADQUERY: return "malformed query"; default: return "unknown error"; } } const char *dns_version(void) { return UDNS_VERSION; } libtorrent-0.16.17/src/net/udns/udns_parse.c000066400000000000000000000126751522271512000207170ustar00rootroot00000000000000/* udns_parse.c raw DNS packet parsing routines Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include "udns.h" dnscc_t *dns_skipdn(dnscc_t *cur, dnscc_t *end) { unsigned c; for(;;) { if (cur >= end) return NULL; c = *cur++; if (!c) return cur; if (c & 192) /* jump */ return cur + 1 >= end ? NULL : cur + 1; cur += c; } } int dns_getdn(dnscc_t *pkt, dnscc_t **cur, dnscc_t *end, dnsc_t *dn, unsigned dnsiz) { unsigned c; dnscc_t *pp = *cur; /* current packet pointer */ dnsc_t *dp = dn; /* current dn pointer */ dnsc_t *const de /* end of the DN dest */ = dn + (dnsiz < DNS_MAXDN ? dnsiz : DNS_MAXDN); dnscc_t *jump = NULL; /* ptr after first jump if any */ unsigned loop = 100; /* jump loop counter */ for(;;) { /* loop by labels */ if (pp >= end) /* reached end of packet? */ return -1; c = *pp++; /* length of the label */ if (!c) { /* empty label: terminate */ if (dn >= de) /* can't fit terminator */ goto noroom; *dp++ = 0; /* return next pos: either after the first jump or current */ *cur = jump ? jump : pp; return dp - dn; } if (c & 192) { /* jump */ if (pp >= end) /* eop instead of jump pos */ return -1; if (!jump) jump = pp + 1; /* remember first jump */ else if (!--loop) return -1; /* too many jumps */ c = ((c & ~192) << 8) | *pp; /* new pos */ if (c < DNS_HSIZE) /* don't allow jump into the header */ return -1; pp = pkt + c; continue; } if (c > DNS_MAXLABEL) /* too long label? */ return -1; if (pp + c > end) /* label does not fit in packet? */ return -1; if (dp + c + 1 > de) /* if enouth room for the label */ goto noroom; *dp++ = c; /* label length */ memcpy(dp, pp, c); /* and the label itself */ dp += c; pp += c; /* advance to the next label */ } noroom: return dnsiz < DNS_MAXDN ? 0 : -1; } void dns_rewind(struct dns_parse *p, dnscc_t *qdn) { p->dnsp_qdn = qdn; p->dnsp_cur = p->dnsp_ans; p->dnsp_rrl = dns_numan(p->dnsp_pkt); p->dnsp_ttl = 0xffffffffu; p->dnsp_nrr = 0; } void dns_initparse(struct dns_parse *p, dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end) { p->dnsp_pkt = pkt; p->dnsp_end = end; p->dnsp_rrl = dns_numan(pkt); p->dnsp_qdn = qdn; assert(cur + 4 <= end); if ((p->dnsp_qtyp = (enum dns_type)dns_get16(cur+0)) == DNS_T_ANY) p->dnsp_qtyp = (enum dns_type)0; if ((p->dnsp_qcls = (enum dns_class)dns_get16(cur+2)) == DNS_C_ANY) p->dnsp_qcls = (enum dns_class)0; p->dnsp_cur = p->dnsp_ans = cur + 4; p->dnsp_ttl = 0xffffffffu; p->dnsp_nrr = 0; } int dns_nextrr(struct dns_parse *p, struct dns_rr *rr) { dnscc_t *cur = p->dnsp_cur; while(p->dnsp_rrl > 0) { --p->dnsp_rrl; if (dns_getdn(p->dnsp_pkt, &cur, p->dnsp_end, rr->dnsrr_dn, sizeof(rr->dnsrr_dn)) <= 0) return -1; if (cur + 10 > p->dnsp_end) return -1; rr->dnsrr_typ = (enum dns_type)dns_get16(cur); rr->dnsrr_cls = (enum dns_class)dns_get16(cur+2); rr->dnsrr_ttl = dns_get32(cur+4); rr->dnsrr_dsz = dns_get16(cur+8); rr->dnsrr_dptr = cur = cur + 10; rr->dnsrr_dend = cur = cur + rr->dnsrr_dsz; if (cur > p->dnsp_end) return -1; if (p->dnsp_qdn && !dns_dnequal(p->dnsp_qdn, rr->dnsrr_dn)) continue; if ((!p->dnsp_qcls || p->dnsp_qcls == rr->dnsrr_cls) && (!p->dnsp_qtyp || p->dnsp_qtyp == rr->dnsrr_typ)) { p->dnsp_cur = cur; ++p->dnsp_nrr; if (p->dnsp_ttl > rr->dnsrr_ttl) p->dnsp_ttl = rr->dnsrr_ttl; return 1; } if (p->dnsp_qdn && rr->dnsrr_typ == DNS_T_CNAME && !p->dnsp_nrr) { if (dns_getdn(p->dnsp_pkt, &rr->dnsrr_dptr, p->dnsp_end, p->dnsp_dnbuf, sizeof(p->dnsp_dnbuf)) <= 0 || rr->dnsrr_dptr != rr->dnsrr_dend) return -1; p->dnsp_qdn = p->dnsp_dnbuf; if (p->dnsp_ttl > rr->dnsrr_ttl) p->dnsp_ttl = rr->dnsrr_ttl; } } p->dnsp_cur = cur; return 0; } int dns_stdrr_size(const struct dns_parse *p) { return dns_dntop_size(p->dnsp_qdn) + (p->dnsp_qdn == dns_payload(p->dnsp_pkt) ? 0 : dns_dntop_size(dns_payload(p->dnsp_pkt))); } void *dns_stdrr_finish(struct dns_rr_null *ret, char *cp, const struct dns_parse *p) { cp += dns_dntop(p->dnsp_qdn, (ret->dnsn_cname = cp), DNS_MAXNAME); if (p->dnsp_qdn == dns_payload(p->dnsp_pkt)) ret->dnsn_qname = ret->dnsn_cname; else dns_dntop(dns_payload(p->dnsp_pkt), (ret->dnsn_qname = cp), DNS_MAXNAME); ret->dnsn_ttl = p->dnsp_ttl; return ret; } libtorrent-0.16.17/src/net/udns/udns_resolver.c000066400000000000000000001226651522271512000214470ustar00rootroot00000000000000/* udns_resolver.c resolver stuff (main module) Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef WINDOWS # include /* includes */ # include /* needed for struct in6_addr */ #else # include # include # include # include # include # include # ifdef HAVE_POLL # include # else # ifdef HAVE_SYS_SELECT_H # include # endif # endif # ifdef HAVE_TIMES # include # endif # define closesocket(sock) close(sock) #endif /* !WINDOWS */ #include #include #include #include #include #include #include "udns.h" #ifndef EAFNOSUPPORT # define EAFNOSUPPORT EINVAL #endif #ifndef MSG_DONTWAIT # define MSG_DONTWAIT 0 #endif struct dns_qlist { struct dns_query *head, *tail; }; struct dns_query { struct dns_query *dnsq_next; /* double-linked list */ struct dns_query *dnsq_prev; unsigned dnsq_origdnl0; /* original query DN len w/o last 0 */ unsigned dnsq_flags; /* control flags for this query */ unsigned dnsq_servi; /* index of next server to try */ unsigned dnsq_servwait; /* bitmask: servers left to wait */ unsigned dnsq_servskip; /* bitmask: servers to skip */ unsigned dnsq_servnEDNS0; /* bitmask: servers refusing EDNS0 */ unsigned dnsq_try; /* number of tries made so far */ dnscc_t *dnsq_nxtsrch; /* next search pointer @dnsc_srchbuf */ time_t dnsq_deadline; /* when current try will expire */ dns_parse_fn *dnsq_parse; /* parse: raw => application */ dns_query_fn *dnsq_cbck; /* the callback to call when done */ void *dnsq_cbdata; /* user data for the callback */ #ifndef NDEBUG struct dns_ctx *dnsq_ctx; /* the resolver context */ #endif /* char fields at the end to avoid padding */ dnsc_t dnsq_id[2]; /* query ID */ dnsc_t dnsq_typcls[4]; /* requested RR type+class */ dnsc_t dnsq_dn[DNS_MAXDN+DNS_DNPAD]; /* the query DN +alignment */ }; /* working with dns_query lists */ static __inline void qlist_init(struct dns_qlist *list) { list->head = list->tail = NULL; } static __inline void qlist_remove(struct dns_qlist *list, struct dns_query *q) { if (q->dnsq_prev) q->dnsq_prev->dnsq_next = q->dnsq_next; else list->head = q->dnsq_next; if (q->dnsq_next) q->dnsq_next->dnsq_prev = q->dnsq_prev; else list->tail = q->dnsq_prev; } static __inline void qlist_add_head(struct dns_qlist *list, struct dns_query *q) { q->dnsq_next = list->head; if (list->head) list->head->dnsq_prev = q; else list->tail = q; list->head = q; q->dnsq_prev = NULL; } static __inline void qlist_insert_after(struct dns_qlist *list, struct dns_query *q, struct dns_query *prev) { if ((q->dnsq_prev = prev) != NULL) { if ((q->dnsq_next = prev->dnsq_next) != NULL) q->dnsq_next->dnsq_prev = q; else list->tail = q; prev->dnsq_next = q; } else qlist_add_head(list, q); } union sockaddr_ns { struct sockaddr sa; struct sockaddr_in sin; #ifdef HAVE_IPv6 struct sockaddr_in6 sin6; #endif }; #define sin_eq(a,b) \ ((a).sin_port == (b).sin_port && \ (a).sin_addr.s_addr == (b).sin_addr.s_addr) #define sin6_eq(a,b) \ ((a).sin6_port == (b).sin6_port && \ memcmp(&(a).sin6_addr, &(b).sin6_addr, sizeof(struct in6_addr)) == 0) struct dns_ctx { /* resolver context */ /* settings */ unsigned dnsc_flags; /* various flags */ unsigned dnsc_timeout; /* timeout (base value) for queries */ unsigned dnsc_ntries; /* number of retries */ unsigned dnsc_ndots; /* ndots to assume absolute name */ unsigned dnsc_port; /* default port (DNS_PORT) */ unsigned dnsc_udpbuf; /* size of UDP buffer */ /* array of nameserver addresses */ union sockaddr_ns dnsc_serv[DNS_MAXSERV]; unsigned dnsc_nserv; /* number of nameservers */ unsigned dnsc_salen; /* length of socket addresses */ dnsc_t dnsc_srchbuf[1024]; /* buffer for searchlist */ dnsc_t *dnsc_srchend; /* current end of srchbuf */ dns_utm_fn *dnsc_utmfn; /* register/cancel timer events */ void *dnsc_utmctx; /* user timer context for utmfn() */ time_t dnsc_utmexp; /* when user timer expires */ dns_dbgfn *dnsc_udbgfn; /* debugging function */ /* dynamic data */ struct udns_jranctx dnsc_jran; /* random number generator state */ unsigned dnsc_nextid; /* next queue ID to use if !0 */ int dnsc_udpsock; /* UDP socket */ struct dns_qlist dnsc_qactive; /* active list sorted by deadline */ int dnsc_nactive; /* number entries in dnsc_qactive */ dnsc_t *dnsc_pbuf; /* packet buffer (udpbuf size) */ int dnsc_qstatus; /* last query status value */ }; static const struct { const char *name; enum dns_opt opt; unsigned offset; unsigned min, max; } dns_opts[] = { #define opt(name,opt,field,min,max) \ {name,opt,offsetof(struct dns_ctx,field),min,max} opt("retrans", DNS_OPT_TIMEOUT, dnsc_timeout, 1,300), opt("timeout", DNS_OPT_TIMEOUT, dnsc_timeout, 1,300), opt("retry", DNS_OPT_NTRIES, dnsc_ntries, 1,50), opt("attempts", DNS_OPT_NTRIES, dnsc_ntries, 1,50), opt("ndots", DNS_OPT_NDOTS, dnsc_ndots, 0,1000), opt("port", DNS_OPT_PORT, dnsc_port, 1,0xffff), opt("udpbuf", DNS_OPT_UDPSIZE, dnsc_udpbuf, DNS_MAXPACKET,65536), #undef opt }; #define dns_ctxopt(ctx,idx) (*((unsigned*)(((char*)ctx)+dns_opts[idx].offset))) #define ISSPACE(x) (x == ' ' || x == '\t' || x == '\r' || x == '\n') struct dns_ctx dns_defctx; #define SETCTX(ctx) if (!ctx) ctx = &dns_defctx #define SETCTXINITED(ctx) SETCTX(ctx); assert(CTXINITED(ctx)) #define CTXINITED(ctx) (ctx->dnsc_flags & DNS_INITED) #define SETCTXFRESH(ctx) SETCTXINITED(ctx); assert(!CTXOPEN(ctx)) #define SETCTXINACTIVE(ctx) \ SETCTXINITED(ctx); assert(!ctx->dnsc_nactive) #define SETCTXOPEN(ctx) SETCTXINITED(ctx); assert(CTXOPEN(ctx)) #define CTXOPEN(ctx) (ctx->dnsc_udpsock >= 0) #if defined(NDEBUG) || !defined(DEBUG) #define dns_assert_ctx(ctx) #else static void dns_assert_ctx(const struct dns_ctx *ctx) { int nactive = 0; const struct dns_query *q; for(q = ctx->dnsc_qactive.head; q; q = q->dnsq_next) { assert(q->dnsq_ctx == ctx); assert(q == (q->dnsq_next ? q->dnsq_next->dnsq_prev : ctx->dnsc_qactive.tail)); assert(q == (q->dnsq_prev ? q->dnsq_prev->dnsq_next : ctx->dnsc_qactive.head)); ++nactive; } assert(nactive == ctx->dnsc_nactive); } #endif enum { DNS_INTERNAL = 0xffff, /* internal flags mask */ DNS_INITED = 0x0001, /* the context is initialized */ DNS_ASIS_DONE = 0x0002, /* search: skip the last as-is query */ DNS_SEEN_NODATA = 0x0004, /* search: NODATA has been received */ }; int dns_add_serv(struct dns_ctx *ctx, const char *serv) { union sockaddr_ns *sns; SETCTXFRESH(ctx); if (!serv) return (ctx->dnsc_nserv = 0); if (ctx->dnsc_nserv >= DNS_MAXSERV) return errno = ENFILE, -1; sns = &ctx->dnsc_serv[ctx->dnsc_nserv]; memset(sns, 0, sizeof(*sns)); if (dns_pton(AF_INET, serv, &sns->sin.sin_addr) > 0) { sns->sin.sin_family = AF_INET; return ++ctx->dnsc_nserv; } #ifdef HAVE_IPv6 if (dns_pton(AF_INET6, serv, &sns->sin6.sin6_addr) > 0) { sns->sin6.sin6_family = AF_INET6; return ++ctx->dnsc_nserv; } #endif errno = EINVAL; return -1; } int dns_add_serv_s(struct dns_ctx *ctx, const struct sockaddr *sa) { SETCTXFRESH(ctx); if (!sa) return (ctx->dnsc_nserv = 0); if (ctx->dnsc_nserv >= DNS_MAXSERV) return errno = ENFILE, -1; #ifdef HAVE_IPv6 else if (sa->sa_family == AF_INET6) ctx->dnsc_serv[ctx->dnsc_nserv].sin6 = *(struct sockaddr_in6*)sa; #endif else if (sa->sa_family == AF_INET) ctx->dnsc_serv[ctx->dnsc_nserv].sin = *(struct sockaddr_in*)sa; else return errno = EAFNOSUPPORT, -1; return ++ctx->dnsc_nserv; } int dns_set_opts(struct dns_ctx *ctx, const char *opts) { unsigned i, v; int err = 0; SETCTXINACTIVE(ctx); for(;;) { while(ISSPACE(*opts)) ++opts; if (!*opts) break; for(i = 0; ; ++i) { if (i >= sizeof(dns_opts)/sizeof(dns_opts[0])) { ++err; break; } v = strlen(dns_opts[i].name); if (strncmp(dns_opts[i].name, opts, v) != 0 || (opts[v] != ':' && opts[v] != '=')) continue; opts += v + 1; v = 0; if (*opts < '0' || *opts > '9') { ++err; break; } do v = v * 10 + (*opts++ - '0'); while (*opts >= '0' && *opts <= '9'); if (v < dns_opts[i].min) v = dns_opts[i].min; if (v > dns_opts[i].max) v = dns_opts[i].max; dns_ctxopt(ctx, i) = v; break; } while(*opts && !ISSPACE(*opts)) ++opts; } return err; } int dns_set_opt(struct dns_ctx *ctx, enum dns_opt opt, int val) { int prev; unsigned i; SETCTXINACTIVE(ctx); for(i = 0; i < sizeof(dns_opts)/sizeof(dns_opts[0]); ++i) { if (dns_opts[i].opt != opt) continue; prev = dns_ctxopt(ctx, i); if (val >= 0) { unsigned v = val; if (v < dns_opts[i].min || v > dns_opts[i].max) { errno = EINVAL; return -1; } dns_ctxopt(ctx, i) = v; } return prev; } if (opt == DNS_OPT_FLAGS) { prev = ctx->dnsc_flags & ~DNS_INTERNAL; if (val >= 0) ctx->dnsc_flags = (ctx->dnsc_flags & DNS_INTERNAL) | (val & ~DNS_INTERNAL); return prev; } errno = ENOSYS; return -1; } int dns_add_srch(struct dns_ctx *ctx, const char *srch) { int dnl; SETCTXINACTIVE(ctx); if (!srch) { memset(ctx->dnsc_srchbuf, 0, sizeof(ctx->dnsc_srchbuf)); ctx->dnsc_srchend = ctx->dnsc_srchbuf; return 0; } dnl = sizeof(ctx->dnsc_srchbuf) - (ctx->dnsc_srchend - ctx->dnsc_srchbuf) - 1; dnl = dns_sptodn(srch, ctx->dnsc_srchend, dnl); if (dnl > 0) ctx->dnsc_srchend += dnl; ctx->dnsc_srchend[0] = '\0'; /* we ensure the list is always ends at . */ if (dnl > 0) return 0; errno = EINVAL; return -1; } static void dns_drop_utm(struct dns_ctx *ctx) { if (ctx->dnsc_utmfn) ctx->dnsc_utmfn(NULL, -1, ctx->dnsc_utmctx); ctx->dnsc_utmctx = NULL; ctx->dnsc_utmexp = -1; } static void _dns_request_utm(struct dns_ctx *ctx, time_t now) { struct dns_query *q; time_t deadline; int timeout; q = ctx->dnsc_qactive.head; if (!q) deadline = -1, timeout = -1; else if (!now || q->dnsq_deadline <= now) deadline = 0, timeout = 0; else deadline = q->dnsq_deadline, timeout = (int)(deadline - now); if (ctx->dnsc_utmexp == deadline) return; ctx->dnsc_utmfn(ctx, timeout, ctx->dnsc_utmctx); ctx->dnsc_utmexp = deadline; } static __inline void dns_request_utm(struct dns_ctx *ctx, time_t now) { if (ctx->dnsc_utmfn) _dns_request_utm(ctx, now); } void dns_set_dbgfn(struct dns_ctx *ctx, dns_dbgfn *dbgfn) { SETCTXINITED(ctx); ctx->dnsc_udbgfn = dbgfn; } void dns_set_tmcbck(struct dns_ctx *ctx, dns_utm_fn *fn, void *data) { SETCTXINITED(ctx); dns_drop_utm(ctx); ctx->dnsc_utmfn = fn; ctx->dnsc_utmctx = data; if (CTXOPEN(ctx)) dns_request_utm(ctx, 0); } static unsigned dns_nonrandom_32(void) { #ifdef WINDOWS FILETIME ft; GetSystemTimeAsFileTime(&ft); return ft.dwLowDateTime; #else struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_usec; #endif } /* This is historic deprecated API */ UDNS_API unsigned dns_random16(void); unsigned dns_random16(void) { unsigned x = dns_nonrandom_32(); return (x ^ (x >> 16)) & 0xffff; } static void dns_init_rng(struct dns_ctx *ctx) { udns_jraninit(&ctx->dnsc_jran, dns_nonrandom_32()); ctx->dnsc_nextid = 0; } void dns_close(struct dns_ctx *ctx) { struct dns_query *q, *p; SETCTX(ctx); if (CTXINITED(ctx)) { if (ctx->dnsc_udpsock >= 0) closesocket(ctx->dnsc_udpsock); ctx->dnsc_udpsock = -1; if (ctx->dnsc_pbuf) free(ctx->dnsc_pbuf); ctx->dnsc_pbuf = NULL; q = ctx->dnsc_qactive.head; while((p = q) != NULL) { q = q->dnsq_next; free(p); } qlist_init(&ctx->dnsc_qactive); ctx->dnsc_nactive = 0; dns_drop_utm(ctx); } } void dns_reset(struct dns_ctx *ctx) { SETCTX(ctx); dns_close(ctx); memset(ctx, 0, sizeof(*ctx)); ctx->dnsc_timeout = 4; ctx->dnsc_ntries = 3; ctx->dnsc_ndots = 1; ctx->dnsc_udpbuf = DNS_EDNS0PACKET; ctx->dnsc_port = DNS_PORT; ctx->dnsc_udpsock = -1; ctx->dnsc_srchend = ctx->dnsc_srchbuf; qlist_init(&ctx->dnsc_qactive); dns_init_rng(ctx); ctx->dnsc_flags = DNS_INITED; } struct dns_ctx *dns_new(const struct dns_ctx *copy) { struct dns_ctx *ctx; SETCTXINITED(copy); dns_assert_ctx(copy); ctx = (struct dns_ctx *)malloc(sizeof(*ctx)); if (!ctx) return NULL; *ctx = *copy; ctx->dnsc_udpsock = -1; qlist_init(&ctx->dnsc_qactive); ctx->dnsc_nactive = 0; ctx->dnsc_pbuf = NULL; ctx->dnsc_qstatus = 0; ctx->dnsc_srchend = ctx->dnsc_srchbuf + (copy->dnsc_srchend - copy->dnsc_srchbuf); ctx->dnsc_utmfn = NULL; ctx->dnsc_utmctx = NULL; dns_init_rng(ctx); return ctx; } void dns_free(struct dns_ctx *ctx) { assert(ctx != NULL && ctx != &dns_defctx); dns_reset(ctx); free(ctx); } int dns_open(struct dns_ctx *ctx) { int sock; unsigned i; int port; union sockaddr_ns *sns; #ifdef HAVE_IPv6 unsigned have_inet6 = 0; #endif SETCTXINITED(ctx); assert(!CTXOPEN(ctx)); port = htons((unsigned short)ctx->dnsc_port); /* ensure we have at least one server */ if (!ctx->dnsc_nserv) { sns = ctx->dnsc_serv; sns->sin.sin_family = AF_INET; sns->sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); ctx->dnsc_nserv = 1; } for (i = 0; i < ctx->dnsc_nserv; ++i) { sns = &ctx->dnsc_serv[i]; /* set port for each sockaddr */ #ifdef HAVE_IPv6 if (sns->sa.sa_family == AF_INET6) { if (!sns->sin6.sin6_port) sns->sin6.sin6_port = (unsigned short)port; ++have_inet6; } else #endif { assert(sns->sa.sa_family == AF_INET); if (!sns->sin.sin_port) sns->sin.sin_port = (unsigned short)port; } } #ifdef HAVE_IPv6 if (have_inet6 && have_inet6 < ctx->dnsc_nserv) { /* convert all IPv4 addresses to IPv6 V4MAPPED */ struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; /* V4MAPPED: ::ffff:1.2.3.4 */ sin6.sin6_addr.s6_addr[10] = 0xff; sin6.sin6_addr.s6_addr[11] = 0xff; for(i = 0; i < ctx->dnsc_nserv; ++i) { sns = &ctx->dnsc_serv[i]; if (sns->sa.sa_family == AF_INET) { sin6.sin6_port = sns->sin.sin_port; memcpy(sin6.sin6_addr.s6_addr + 4*3, &sns->sin.sin_addr, 4); sns->sin6 = sin6; } } } ctx->dnsc_salen = have_inet6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); if (have_inet6) sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); else sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); #else /* !HAVE_IPv6 */ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); ctx->dnsc_salen = sizeof(struct sockaddr_in); #endif /* HAVE_IPv6 */ if (sock < 0) { ctx->dnsc_qstatus = DNS_E_TEMPFAIL; return -1; } #ifdef WINDOWS { unsigned long on = 1; if (ioctlsocket(sock, FIONBIO, &on) == SOCKET_ERROR) { closesocket(sock); ctx->dnsc_qstatus = DNS_E_TEMPFAIL; return -1; } } #else /* !WINDOWS */ if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) < 0 || fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { closesocket(sock); ctx->dnsc_qstatus = DNS_E_TEMPFAIL; return -1; } #endif /* WINDOWS */ /* allocate the packet buffer */ if ((ctx->dnsc_pbuf = (dnsc_t*)malloc(ctx->dnsc_udpbuf)) == NULL) { closesocket(sock); ctx->dnsc_qstatus = DNS_E_NOMEM; errno = ENOMEM; return -1; } ctx->dnsc_udpsock = sock; dns_request_utm(ctx, 0); return sock; } int dns_sock(const struct dns_ctx *ctx) { SETCTXINITED(ctx); return ctx->dnsc_udpsock; } int dns_active(const struct dns_ctx *ctx) { SETCTXINITED(ctx); dns_assert_ctx(ctx); return ctx->dnsc_nactive; } int dns_status(const struct dns_ctx *ctx) { SETCTX(ctx); return ctx->dnsc_qstatus; } void dns_setstatus(struct dns_ctx *ctx, int status) { SETCTX(ctx); ctx->dnsc_qstatus = status; } /* End the query: disconnect it from the active list, free it, * and return the result to the caller. */ static void dns_end_query(struct dns_ctx *ctx, struct dns_query *q, int status, void *result) { dns_query_fn *cbck = q->dnsq_cbck; void *cbdata = q->dnsq_cbdata; ctx->dnsc_qstatus = status; assert((status < 0 && result == 0) || (status >= 0 && result != 0)); assert(cbck != 0); /*XXX callback may be NULL */ assert(ctx->dnsc_nactive > 0); --ctx->dnsc_nactive; qlist_remove(&ctx->dnsc_qactive, q); /* force the query to be unconnected */ /*memset(q, 0, sizeof(*q));*/ #ifndef NDEBUG q->dnsq_ctx = NULL; #endif free(q); cbck(ctx, result, cbdata); } #define DNS_DBG(ctx, code, sa, slen, pkt, plen) \ do { \ if (ctx->dnsc_udbgfn) \ ctx->dnsc_udbgfn(code, (sa), slen, pkt, plen, 0, 0); \ } while(0) #define DNS_DBGQ(ctx, q, code, sa, slen, pkt, plen) \ do { \ if (ctx->dnsc_udbgfn) \ ctx->dnsc_udbgfn(code, (sa), slen, pkt, plen, q, q->dnsq_cbdata); \ } while(0) static void dns_newid(struct dns_ctx *ctx, struct dns_query *q) { /* this is how we choose an identifier for a new query (qID). * For now, it's just sequential number, incremented for every query, and * thus obviously trivial to guess. * There are two choices: * a) use sequential numbers. It is plain insecure. In DNS, there are two * places where random numbers are (or can) be used to increase security: * random qID and random source port number. Without this randomness * (udns uses fixed port for all queries), or when the randomness is weak, * it's trivial to spoof query replies. With randomness however, it * becomes a bit more difficult task. Too bad we only have 16 bits for * our security, as qID is only two bytes. It isn't a security per se, * to rely on those 16 bits - an attacker can just flood us with fake * replies with all possible qIDs (only 65536 of them), and in this case, * even if we'll use true random qIDs, we'll be in trouble (not protected * against spoofing). Yes, this is only possible on a high-speed network * (probably on the LAN only, since usually a border router for a LAN * protects internal machines from packets with spoofed local addresses * from outside, and usually a nameserver resides on LAN), but it's * still very well possible to send us fake replies. * In other words: there's nothing a DNS (stub) resolver can do against * spoofing attacks, unless DNSSEC is in use, which helps here alot. * Too bad that DNSSEC isn't widespread, so relying on it isn't an * option in almost all cases... * b) use random qID, based on some random-number generation mechanism. * This way, we increase our protection a bit (see above - it's very weak * still), but we also increase risk of qID reuse and matching late replies * that comes to queries we've sent before against new queries. There are * some more corner cases around that, as well - for example, normally, * udns tries to find the query for a given reply by qID, *and* by * verifying that the query DN and other parameters are also the same * (so if the new query is against another domain name, old reply will * be ignored automatically). But certain types of replies which we now * handle - for example, FORMERR reply from servers which refuses to * process EDNS0-enabled packets - comes without all the query parameters * but the qID - so we're forced to use qID only when determining which * query the given reply corresponds to. This makes us even more * vulnerable to spoofing attacks, because an attacker don't even need to * know which queries we perform to spoof the replies - he only needs to * flood us with fake FORMERR "replies". * * That all to say: using sequential (or any other trivially guessable) * numbers for qIDs is insecure, but the whole thing is inherently insecure * as well, and this "extra weakness" that comes from weak qID choosing * algorithm adds almost nothing to the underlying problem. * * It CAN NOT be made secure. Period. That's it. * Unless we choose to implement DNSSEC, which is a whole different story. * Forcing TCP mode makes it better, but who uses TCP for DNS anyway? * (and it's hardly possible because of huge impact on the recursive * nameservers). * * Note that ALL stub resolvers (again, unless they implement and enforce * DNSSEC) suffers from this same problem. * * Here, I use a pseudo-random number generator for qIDs, instead of a * simpler sequential IDs. This is _not_ more secure than sequential * ID, but some found random IDs more enjoyeable for some reason. So * here it goes. */ /* Use random number and check if it's unique. * If it's not, try again up to 5 times. */ unsigned loop; dnsc_t c0, c1; for(loop = 0; loop < 5; ++loop) { const struct dns_query *c; if (!ctx->dnsc_nextid) ctx->dnsc_nextid = udns_jranval(&ctx->dnsc_jran); c0 = ctx->dnsc_nextid & 0xff; c1 = (ctx->dnsc_nextid >> 8) & 0xff; ctx->dnsc_nextid >>= 16; for(c = ctx->dnsc_qactive.head; c; c = c->dnsq_next) if (c->dnsq_id[0] == c0 && c->dnsq_id[1] == c1) break; /* found such entry, try again */ if (!c) break; } q->dnsq_id[0] = c0; q->dnsq_id[1] = c1; /* reset all parameters relevant for previous query lifetime */ q->dnsq_try = 0; q->dnsq_servi = 0; /*XXX probably should keep dnsq_servnEDNS0 bits? * See also comments in dns_ioevent() about FORMERR case */ q->dnsq_servwait = q->dnsq_servskip = q->dnsq_servnEDNS0 = 0; } /* Find next search suffix and fills in q->dnsq_dn. * Return 0 if no more to try. */ static int dns_next_srch(struct dns_ctx *ctx, struct dns_query *q) { unsigned dnl; for(;;) { if (q->dnsq_nxtsrch > ctx->dnsc_srchend) return 0; dnl = dns_dnlen(q->dnsq_nxtsrch); if (dnl + q->dnsq_origdnl0 <= DNS_MAXDN && (*q->dnsq_nxtsrch || !(q->dnsq_flags & DNS_ASIS_DONE))) break; q->dnsq_nxtsrch += dnl; } memcpy(q->dnsq_dn + q->dnsq_origdnl0, q->dnsq_nxtsrch, dnl); if (!*q->dnsq_nxtsrch) q->dnsq_flags |= DNS_ASIS_DONE; q->dnsq_nxtsrch += dnl; dns_newid(ctx, q); /* new ID for new qDN */ return 1; } /* find the server to try for current iteration. * Note that current dnsq_servi may point to a server we should skip -- * in that case advance to the next server. * Return true if found, false if all tried. */ static int dns_find_serv(const struct dns_ctx *ctx, struct dns_query *q) { while(q->dnsq_servi < ctx->dnsc_nserv) { if (!(q->dnsq_servskip & (1 << q->dnsq_servi))) return 1; ++q->dnsq_servi; } return 0; } /* format and send the query to a given server. * In case of network problem (sendto() fails), return -1, * else return 0. */ static int dns_send_this(struct dns_ctx *ctx, struct dns_query *q, unsigned servi, time_t now) { unsigned qlen; unsigned tries; { /* format the query buffer */ dnsc_t *p = ctx->dnsc_pbuf; memset(p, 0, DNS_HSIZE); if (!(q->dnsq_flags & DNS_NORD)) p[DNS_H_F1] |= DNS_HF1_RD; if (q->dnsq_flags & DNS_AAONLY) p[DNS_H_F1] |= DNS_HF1_AA; if (q->dnsq_flags & DNS_SET_CD) p[DNS_H_F2] |= DNS_HF2_CD; p[DNS_H_QDCNT2] = 1; memcpy(p + DNS_H_QID, q->dnsq_id, 2); p = dns_payload(p); /* copy query dn */ p += dns_dntodn(q->dnsq_dn, p, DNS_MAXDN); /* query type and class */ memcpy(p, q->dnsq_typcls, 4); p += 4; /* add EDNS0 record. DO flag requires it */ if (q->dnsq_flags & DNS_SET_DO || (ctx->dnsc_udpbuf > DNS_MAXPACKET && !(q->dnsq_servnEDNS0 & (1 << servi)))) { *p++ = 0; /* empty (root) DN */ p = dns_put16(p, DNS_T_OPT); p = dns_put16(p, ctx->dnsc_udpbuf); /* EDNS0 RCODE & VERSION; rest of the TTL field; RDLEN */ memset(p, 0, 2+2+2); if (q->dnsq_flags & DNS_SET_DO) p[2] |= DNS_EF1_DO; p += 2+2+2; ctx->dnsc_pbuf[DNS_H_ARCNT2] = 1; } qlen = p - ctx->dnsc_pbuf; assert(qlen <= ctx->dnsc_udpbuf); } /* send the query */ tries = 10; while (sendto(ctx->dnsc_udpsock, (void*)ctx->dnsc_pbuf, qlen, 0, &ctx->dnsc_serv[servi].sa, ctx->dnsc_salen) < 0) { /*XXX just ignore the sendto() error for now and try again. * In the future, it may be possible to retrieve the error code * and find which operation/query failed. *XXX try the next server too? (if ENETUNREACH is returned immediately) */ if (--tries) continue; /* if we can't send the query, fail it. */ dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0); return -1; } DNS_DBGQ(ctx, q, 1, &ctx->dnsc_serv[servi].sa, sizeof(union sockaddr_ns), ctx->dnsc_pbuf, qlen); q->dnsq_servwait |= 1 << servi; /* expect reply from this ns */ q->dnsq_deadline = now + (dns_find_serv(ctx, q) ? 1 : ctx->dnsc_timeout << q->dnsq_try); /* move the query to the proper place, according to the new deadline */ qlist_remove(&ctx->dnsc_qactive, q); { /* insert from the tail */ struct dns_query *p; for(p = ctx->dnsc_qactive.tail; p; p = p->dnsq_prev) if (p->dnsq_deadline <= q->dnsq_deadline) break; qlist_insert_after(&ctx->dnsc_qactive, q, p); } return 0; } /* send the query out using next available server * and add it to the active list, or, if no servers available, * end it. */ static void dns_send(struct dns_ctx *ctx, struct dns_query *q, time_t now) { /* if we can't send the query, return TEMPFAIL even when searching: * we can't be sure whenever the name we tried to search exists or not, * so don't continue searching, or we may find the wrong name. */ if (!dns_find_serv(ctx, q)) { /* no more servers in this iteration. Try the next cycle */ q->dnsq_servi = 0; /* reset */ q->dnsq_try++; /* next try */ if (q->dnsq_try >= ctx->dnsc_ntries || !dns_find_serv(ctx, q)) { /* no more servers and tries, fail the query */ /* return TEMPFAIL even when searching: no more tries for this * searchlist, and no single definitive reply (handled in dns_ioevent() * in NOERROR or NXDOMAIN cases) => all nameservers failed to process * current search list element, so we don't know whenever the name exists. */ dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0); return; } } dns_send_this(ctx, q, q->dnsq_servi++, now); } static void dns_dummy_cb(struct dns_ctx *unused_ctx, void *result, void *unused_data) { if (result) free(result); (void)unused_ctx; (void)unused_data; /* avoid warning */ } /* The (only, main, real) query submission routine. * Allocate new query structure, initialize it, check validity of * parameters, and add it to the head of the active list, without * trying to send it (to be picked up on next event). * Error return (without calling the callback routine) - * no memory or wrong parameters. *XXX The `no memory' case probably should go to the callback anyway... */ struct dns_query * dns_submit_dn(struct dns_ctx *ctx, dnscc_t *dn, int qcls, int qtyp, int flags, dns_parse_fn *parse, dns_query_fn *cbck, void *data) { struct dns_query *q; SETCTXOPEN(ctx); dns_assert_ctx(ctx); q = (struct dns_query *)calloc(1, sizeof(*q)); if (!q) { ctx->dnsc_qstatus = DNS_E_NOMEM; return NULL; } #ifndef NDEBUG q->dnsq_ctx = ctx; #endif q->dnsq_parse = parse; q->dnsq_cbck = cbck ? cbck : dns_dummy_cb; q->dnsq_cbdata = data; q->dnsq_origdnl0 = dns_dntodn(dn, q->dnsq_dn, sizeof(q->dnsq_dn)); assert(q->dnsq_origdnl0 > 0); --q->dnsq_origdnl0; /* w/o the trailing 0 */ dns_put16(q->dnsq_typcls+0, qtyp); dns_put16(q->dnsq_typcls+2, qcls); q->dnsq_flags = (flags | ctx->dnsc_flags) & ~DNS_INTERNAL; if (flags & DNS_NOSRCH || dns_dnlabels(q->dnsq_dn) > ctx->dnsc_ndots) { q->dnsq_nxtsrch = flags & DNS_NOSRCH ? ctx->dnsc_srchend /* end of the search list if no search requested */ : ctx->dnsc_srchbuf /* beginning of the list, but try as-is first */; q->dnsq_flags |= DNS_ASIS_DONE; dns_newid(ctx, q); } else { q->dnsq_nxtsrch = ctx->dnsc_srchbuf; dns_next_srch(ctx, q); } /* q->dnsq_deadline is set to 0 (calloc above): the new query is * "already expired" when first inserted into queue, so it's safe * to insert it into the head of the list. Next call to dns_timeouts() * will actually send it. */ qlist_add_head(&ctx->dnsc_qactive, q); ++ctx->dnsc_nactive; dns_request_utm(ctx, 0); return q; } struct dns_query * dns_submit_p(struct dns_ctx *ctx, const char *name, int qcls, int qtyp, int flags, dns_parse_fn *parse, dns_query_fn *cbck, void *data) { int isabs; SETCTXOPEN(ctx); if (dns_ptodn(name, 0, ctx->dnsc_pbuf, DNS_MAXDN, &isabs) <= 0) { ctx->dnsc_qstatus = DNS_E_BADQUERY; return NULL; } if (isabs) flags |= DNS_NOSRCH; return dns_submit_dn(ctx, ctx->dnsc_pbuf, qcls, qtyp, flags, parse, cbck, data); } /* process readable fd condition. * To be usable in edge-triggered environment, the routine * should consume all input so it should loop over. * Note it isn't really necessary to loop here, because * an application may perform the loop just fine by it's own, * but in this case we should return some sensitive result, * to indicate when to stop calling and error conditions. * Note also we may encounter all sorts of recvfrom() * errors which aren't fatal, and at the same time we may * loop forever if an error IS fatal. */ void dns_ioevent(struct dns_ctx *ctx, time_t now) { int r; unsigned servi; struct dns_query *q; dnsc_t *pbuf; dnscc_t *pend, *pcur; void *result; union sockaddr_ns sns; socklen_t slen; SETCTX(ctx); if (!CTXOPEN(ctx)) return; dns_assert_ctx(ctx); pbuf = ctx->dnsc_pbuf; if (!now) now = time(NULL); again: /* receive the reply */ slen = sizeof(sns); r = recvfrom(ctx->dnsc_udpsock, (void*)pbuf, ctx->dnsc_udpbuf, MSG_DONTWAIT, &sns.sa, &slen); if (r < 0) { /*XXX just ignore recvfrom() errors for now. * in the future it may be possible to determine which * query failed and requeue it. * Note there may be various error conditions, triggered * by both local problems and remote problems. It isn't * quite trivial to determine whenever an error is local * or remote. On local errors, we should stop, while * remote errors should be ignored (for now anyway). */ #ifdef WINDOWS if (WSAGetLastError() == WSAEWOULDBLOCK) #else if (errno == EAGAIN) #endif { dns_request_utm(ctx, now); return; } goto again; } pend = pbuf + r; pcur = dns_payload(pbuf); /* check reply header */ if (pcur > pend || dns_numqd(pbuf) > 1 || dns_opcode(pbuf) != 0) { DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r); goto again; } /* find the matching query, by qID */ for (q = ctx->dnsc_qactive.head; ; q = q->dnsq_next) { if (!q) { /* no more requests: old reply? */ DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r); goto again; } if (pbuf[DNS_H_QID1] == q->dnsq_id[0] && pbuf[DNS_H_QID2] == q->dnsq_id[1]) break; } /* if we have numqd, compare with our query qDN */ if (dns_numqd(pbuf)) { /* decode the qDN */ dnsc_t dn[DNS_MAXDN]; if (dns_getdn(pbuf, &pcur, pend, dn, sizeof(dn)) < 0 || pcur + 4 > pend) { DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r); goto again; } if (!dns_dnequal(dn, q->dnsq_dn) || memcmp(pcur, q->dnsq_typcls, 4) != 0) { /* not this query */ DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r); goto again; } /* here, query match, and pcur points past qDN in query section in pbuf */ } /* if no numqd, we only allow FORMERR rcode */ else if (dns_rcode(pbuf) != DNS_R_FORMERR) { /* treat it as bad reply if !FORMERR */ DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r); goto again; } else { /* else it's FORMERR, handled below */ } /* find server */ #ifdef HAVE_IPv6 if (sns.sa.sa_family == AF_INET6 && slen >= sizeof(sns.sin6)) { for(servi = 0; servi < ctx->dnsc_nserv; ++servi) if (sin6_eq(ctx->dnsc_serv[servi].sin6, sns.sin6)) break; } else #endif if (sns.sa.sa_family == AF_INET && slen >= sizeof(sns.sin)) { for(servi = 0; servi < ctx->dnsc_nserv; ++servi) if (sin_eq(ctx->dnsc_serv[servi].sin, sns.sin)) break; } else servi = ctx->dnsc_nserv; /* check if we expect reply from this server. * Note we can receive reply from first try if we're already at next */ if (!(q->dnsq_servwait & (1 << servi))) { /* if ever asked this NS */ DNS_DBG(ctx, -2/*wrong server*/, &sns.sa, slen, pbuf, r); goto again; } /* we got (some) reply for our query */ DNS_DBGQ(ctx, q, 0, &sns.sa, slen, pbuf, r); q->dnsq_servwait &= ~(1 << servi); /* don't expect reply from this serv */ /* process the RCODE */ switch(dns_rcode(pbuf)) { case DNS_R_NOERROR: if (dns_tc(pbuf)) { /* possible truncation. We can't deal with it. */ /*XXX for now, treat TC bit the same as SERVFAIL. * It is possible to: * a) try to decode the reply - may be ANSWER section is ok; * b) check if server understands EDNS0, and if it is, and * answer still don't fit, end query. */ break; } if (!dns_numan(pbuf)) { /* no data of requested type */ if (dns_next_srch(ctx, q)) { /* if we're searching, try next searchlist element, * but remember NODATA reply. */ q->dnsq_flags |= DNS_SEEN_NODATA; dns_send(ctx, q, now); } else /* else - nothing to search any more - finish the query. * It will be NODATA since we've seen a NODATA reply. */ dns_end_query(ctx, q, DNS_E_NODATA, 0); } /* we've got a positive reply here */ else if (q->dnsq_parse) { /* if we have parsing routine, call it and return whatever it returned */ /* don't try to re-search if NODATA here. For example, * if we asked for A but only received CNAME. Unless we'll * someday do recursive queries. And that's problematic too, since * we may be dealing with specific AA-only nameservers for a given * domain, but CNAME points elsewhere... */ r = q->dnsq_parse(q->dnsq_dn, pbuf, pcur, pend, &result); dns_end_query(ctx, q, r, r < 0 ? NULL : result); } /* else just malloc+copy the raw DNS reply */ else if ((result = malloc(r)) == NULL) dns_end_query(ctx, q, DNS_E_NOMEM, NULL); else { memcpy(result, pbuf, r); dns_end_query(ctx, q, r, result); } goto again; case DNS_R_NXDOMAIN: /* Non-existing domain. */ if (dns_next_srch(ctx, q)) /* more search entries exists, try them. */ dns_send(ctx, q, now); else /* nothing to search anymore. End the query, returning either NODATA * if we've seen it before, or NXDOMAIN if not. */ dns_end_query(ctx, q, q->dnsq_flags & DNS_SEEN_NODATA ? DNS_E_NODATA : DNS_E_NXDOMAIN, 0); goto again; case DNS_R_FORMERR: case DNS_R_NOTIMPL: /* for FORMERR and NOTIMPL rcodes, if we tried EDNS0-enabled query, * try w/o EDNS0. */ if (ctx->dnsc_udpbuf > DNS_MAXPACKET && !(q->dnsq_servnEDNS0 & (1 << servi))) { /* we always trying EDNS0 first if enabled, and retry a given query * if not available. Maybe it's better to remember inavailability of * EDNS0 in ctx as a per-NS flag, and never try again for this NS. * For long-running applications.. maybe they will change the nameserver * while we're running? :) Also, since FORMERR is the only rcode we * allow to be header-only, and in this case the only check we do to * find a query it belongs to is qID (not qDN+qCLS+qTYP), it's much * easier to spoof and to force us to perform non-EDNS0 queries only... */ q->dnsq_servnEDNS0 |= 1 << servi; dns_send_this(ctx, q, servi, now); goto again; } /* else we handle it the same as SERVFAIL etc */ case DNS_R_SERVFAIL: case DNS_R_REFUSED: /* for these rcodes, advance this request * to the next server and reschedule */ default: /* unknown rcode? hmmm... */ break; } /* here, we received unexpected reply */ q->dnsq_servskip |= (1 << servi); /* don't retry this server */ /* we don't expect replies from this server anymore. * But there may be other servers. Some may be still processing our * query, and some may be left to try. * We just ignore this reply and wait a bit more if some NSes haven't * replied yet (dnsq_servwait != 0), and let the situation to be handled * on next event processing. Timeout for this query is set correctly, * if not taking into account the one-second difference - we can try * next server in the same iteration sooner. */ /* try next server */ if (!q->dnsq_servwait) { /* next retry: maybe some other servers will reply next time. * dns_send() will end the query for us if no more servers to try. * Note we can't continue with the next searchlist element here: * we don't know if the current qdn exists or not, there's no definitive * answer yet (which is seen in cases above). *XXX standard resolver also tries as-is query in case all nameservers * failed to process our query and if not tried before. We don't do it. */ dns_send(ctx, q, now); } else { /* else don't do anything - not all servers replied yet */ } goto again; } /* handle all timeouts */ int dns_timeouts(struct dns_ctx *ctx, int maxwait, time_t now) { /* this is a hot routine */ struct dns_query *q; SETCTX(ctx); dns_assert_ctx(ctx); /* Pick up first entry from query list. * If its deadline has passed, (re)send it * (dns_send() will move it next in the list). * If not, this is the query which determines the closest deadline. */ q = ctx->dnsc_qactive.head; if (!q) return maxwait; if (!now) now = time(NULL); // Just in case, to avoid infinite loop when e.g. callbacks might add new queries with expired // deadlines. int loop = 128; while (true) { if (--loop <= 0) { maxwait = 0; break; } if (q->dnsq_deadline > now) { /* first non-expired query */ int w = (int)(q->dnsq_deadline - now); if (maxwait < 0 || maxwait > w) maxwait = w; break; } else { /* process expired deadline */ dns_send(ctx, q, now); } if ((q = ctx->dnsc_qactive.head) == NULL) break; } dns_request_utm(ctx, now); /* update timer with new deadline */ return maxwait; } struct dns_resolve_data { int dnsrd_done; void *dnsrd_result; }; static void dns_resolve_cb(__attribute__((unused)) struct dns_ctx *ctx, void *result, void *data) { struct dns_resolve_data *d = (struct dns_resolve_data *)data; d->dnsrd_result = result; d->dnsrd_done = 1; // Unused: ctx = ctx; /* ctx = ctx; */ } void *dns_resolve(struct dns_ctx *ctx, struct dns_query *q) { time_t now; struct dns_resolve_data d; int n; SETCTXOPEN(ctx); if (!q) return NULL; assert(ctx == q->dnsq_ctx); dns_assert_ctx(ctx); /* do not allow re-resolving synchronous queries */ assert(q->dnsq_cbck != dns_resolve_cb && "can't resolve synchronous query"); if (q->dnsq_cbck == dns_resolve_cb) { ctx->dnsc_qstatus = DNS_E_BADQUERY; return NULL; } q->dnsq_cbck = dns_resolve_cb; q->dnsq_cbdata = &d; d.dnsrd_done = 0; now = time(NULL); while(!d.dnsrd_done && (n = dns_timeouts(ctx, -1, now)) >= 0) { #ifdef HAVE_POLL struct pollfd pfd; pfd.fd = ctx->dnsc_udpsock; pfd.events = POLLIN; n = poll(&pfd, 1, n * 1000); #else fd_set rfd; struct timeval tv; FD_ZERO(&rfd); FD_SET(ctx->dnsc_udpsock, &rfd); tv.tv_sec = n; tv.tv_usec = 0; n = select(ctx->dnsc_udpsock + 1, &rfd, NULL, NULL, &tv); #endif now = time(NULL); if (n > 0) dns_ioevent(ctx, now); } return d.dnsrd_result; } void *dns_resolve_dn(struct dns_ctx *ctx, dnscc_t *dn, int qcls, int qtyp, int flags, dns_parse_fn *parse) { return dns_resolve(ctx, dns_submit_dn(ctx, dn, qcls, qtyp, flags, parse, NULL, NULL)); } void *dns_resolve_p(struct dns_ctx *ctx, const char *name, int qcls, int qtyp, int flags, dns_parse_fn *parse) { return dns_resolve(ctx, dns_submit_p(ctx, name, qcls, qtyp, flags, parse, NULL, NULL)); } int dns_cancel(struct dns_ctx *ctx, struct dns_query *q) { SETCTX(ctx); dns_assert_ctx(ctx); assert(q->dnsq_ctx == ctx); /* do not allow cancelling synchronous queries */ assert(q->dnsq_cbck != dns_resolve_cb && "can't cancel synchronous query"); if (q->dnsq_cbck == dns_resolve_cb) return (ctx->dnsc_qstatus = DNS_E_BADQUERY); qlist_remove(&ctx->dnsc_qactive, q); --ctx->dnsc_nactive; dns_request_utm(ctx, 0); return 0; } libtorrent-0.16.17/src/net/udns/udns_rr_a.c000066400000000000000000000072551522271512000205260ustar00rootroot00000000000000/* udns_rr_a.c parse/query A/AAAA IN records Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #ifndef WINDOWS # include # include #endif #include "udns.h" /* here, we use common routine to parse both IPv4 and IPv6 addresses. */ /* this structure should match dns_rr_a[46] */ struct dns_rr_a { dns_rr_common(dnsa); unsigned char *dnsa_addr; }; static int dns_parse_a(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **result, unsigned dsize) { struct dns_rr_a *ret; struct dns_parse p; struct dns_rr rr; int r; /* first, validate and count number of addresses */ dns_initparse(&p, qdn, pkt, cur, end); while((r = dns_nextrr(&p, &rr)) > 0) if (rr.dnsrr_dsz != dsize) return DNS_E_PROTOCOL; if (r < 0) return DNS_E_PROTOCOL; else if (!p.dnsp_nrr) return DNS_E_NODATA; ret = (struct dns_rr_a *)malloc(sizeof(*ret) + dsize * p.dnsp_nrr + dns_stdrr_size(&p)); if (!ret) return DNS_E_NOMEM; ret->dnsa_nrr = p.dnsp_nrr; ret->dnsa_addr = (unsigned char*)(ret+1); /* copy the RRs */ for (dns_rewind(&p, qdn), r = 0; dns_nextrr(&p, &rr); ++r) memcpy(ret->dnsa_addr + dsize * r, rr.dnsrr_dptr, dsize); dns_stdrr_finish((struct dns_rr_null *)ret, (char *)(ret->dnsa_addr + dsize * p.dnsp_nrr), &p); *result = ret; return 0; } int dns_parse_a4(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **result) { #ifdef AF_INET assert(sizeof(struct in_addr) == 4); #endif assert(dns_get16(cur+2) == DNS_C_IN && dns_get16(cur+0) == DNS_T_A); return dns_parse_a(qdn, pkt, cur, end, result, 4); } struct dns_query * dns_submit_a4(struct dns_ctx *ctx, const char *name, int flags, dns_query_a4_fn *cbck, void *data) { return dns_submit_p(ctx, name, DNS_C_IN, DNS_T_A, flags, dns_parse_a4, (dns_query_fn*)cbck, data); } struct dns_rr_a4 * dns_resolve_a4(struct dns_ctx *ctx, const char *name, int flags) { return (struct dns_rr_a4 *) dns_resolve_p(ctx, name, DNS_C_IN, DNS_T_A, flags, dns_parse_a4); } int dns_parse_a6(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **result) { #ifdef AF_INET6 assert(sizeof(struct in6_addr) == 16); #endif assert(dns_get16(cur+2) == DNS_C_IN && dns_get16(cur+0) == DNS_T_AAAA); return dns_parse_a(qdn, pkt, cur, end, result, 16); } struct dns_query * dns_submit_a6(struct dns_ctx *ctx, const char *name, int flags, dns_query_a6_fn *cbck, void *data) { return dns_submit_p(ctx, name, DNS_C_IN, DNS_T_AAAA, flags, dns_parse_a6, (dns_query_fn*)cbck, data); } struct dns_rr_a6 * dns_resolve_a6(struct dns_ctx *ctx, const char *name, int flags) { return (struct dns_rr_a6 *) dns_resolve_p(ctx, name, DNS_C_IN, DNS_T_AAAA, flags, dns_parse_a6); } libtorrent-0.16.17/src/net/udns/udns_rr_mx.c000066400000000000000000000055221522271512000207250ustar00rootroot00000000000000/* udns_rr_mx.c parse/query MX IN records Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "udns.h" int dns_parse_mx(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **result) { struct dns_rr_mx *ret; struct dns_parse p; struct dns_rr rr; int r, l; char *sp; dnsc_t mx[DNS_MAXDN]; assert(dns_get16(cur+2) == DNS_C_IN && dns_get16(cur+0) == DNS_T_MX); /* first, validate the answer and count size of the result */ l = 0; dns_initparse(&p, qdn, pkt, cur, end); while((r = dns_nextrr(&p, &rr)) > 0) { cur = rr.dnsrr_dptr + 2; r = dns_getdn(pkt, &cur, end, mx, sizeof(mx)); if (r <= 0 || cur != rr.dnsrr_dend) return DNS_E_PROTOCOL; l += dns_dntop_size(mx); } if (r < 0) return DNS_E_PROTOCOL; if (!p.dnsp_nrr) return DNS_E_NODATA; /* next, allocate and set up result */ l += dns_stdrr_size(&p); ret = (struct dns_rr_mx *)malloc(sizeof(*ret) + sizeof(struct dns_mx) * p.dnsp_nrr + l); if (!ret) return DNS_E_NOMEM; ret->dnsmx_nrr = p.dnsp_nrr; ret->dnsmx_mx = (struct dns_mx *)(ret+1); /* and 3rd, fill in result, finally */ sp = (char*)(ret->dnsmx_mx + p.dnsp_nrr); for (dns_rewind(&p, qdn), r = 0; dns_nextrr(&p, &rr); ++r) { ret->dnsmx_mx[r].name = sp; cur = rr.dnsrr_dptr; ret->dnsmx_mx[r].priority = dns_get16(cur); cur += 2; dns_getdn(pkt, &cur, end, mx, sizeof(mx)); sp += dns_dntop(mx, sp, DNS_MAXNAME); } dns_stdrr_finish((struct dns_rr_null *)ret, sp, &p); *result = ret; return 0; } struct dns_query * dns_submit_mx(struct dns_ctx *ctx, const char *name, int flags, dns_query_mx_fn *cbck, void *data) { return dns_submit_p(ctx, name, DNS_C_IN, DNS_T_MX, flags, dns_parse_mx, (dns_query_fn *)cbck, data); } struct dns_rr_mx * dns_resolve_mx(struct dns_ctx *ctx, const char *name, int flags) { return (struct dns_rr_mx *) dns_resolve_p(ctx, name, DNS_C_IN, DNS_T_MX, flags, dns_parse_mx); } libtorrent-0.16.17/src/net/udns/udns_rr_naptr.c000066400000000000000000000076361522271512000214350ustar00rootroot00000000000000/* udns_rr_naptr.c parse/query NAPTR IN records Copyright (C) 2005 Michael Tokarev Copyright (C) 2006 Mikael Magnusson This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "udns.h" /* Get a single string for NAPTR record, pretty much like a DN label. * String length is in first byte in *cur, so it can't be >255. */ static int dns_getstr(dnscc_t **cur, dnscc_t *ep, char *buf) { unsigned l; dnscc_t *cp = *cur; l = *cp++; if (cp + l > ep) return DNS_E_PROTOCOL; if (buf) { memcpy(buf, cp, l); buf[l] = '\0'; } cp += l; *cur = cp; return l + 1; } int dns_parse_naptr(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **result) { struct dns_rr_naptr *ret; struct dns_parse p; struct dns_rr rr; int r, l; char *sp; dnsc_t dn[DNS_MAXDN]; assert(dns_get16(cur+2) == DNS_C_IN && dns_get16(cur+0) == DNS_T_NAPTR); /* first, validate the answer and count size of the result */ l = 0; dns_initparse(&p, qdn, pkt, cur, end); while((r = dns_nextrr(&p, &rr)) > 0) { int i; dnscc_t *ep = rr.dnsrr_dend; /* first 4 bytes: order & preference */ cur = rr.dnsrr_dptr + 4; /* flags, services and regexp */ for (i = 0; i < 3; i++) { r = dns_getstr(&cur, ep, NULL); if (r < 0) return r; l += r; } /* replacement */ r = dns_getdn(pkt, &cur, end, dn, sizeof(dn)); if (r <= 0 || cur != rr.dnsrr_dend) return DNS_E_PROTOCOL; l += dns_dntop_size(dn); } if (r < 0) return DNS_E_PROTOCOL; if (!p.dnsp_nrr) return DNS_E_NODATA; /* next, allocate and set up result */ l += dns_stdrr_size(&p); ret = (struct dns_rr_naptr *)malloc(sizeof(*ret) + sizeof(struct dns_naptr) * p.dnsp_nrr + l); if (!ret) return DNS_E_NOMEM; ret->dnsnaptr_nrr = p.dnsp_nrr; ret->dnsnaptr_naptr = (struct dns_naptr *)(ret+1); /* and 3rd, fill in result, finally */ sp = (char*)(&ret->dnsnaptr_naptr[p.dnsp_nrr]); for (dns_rewind(&p, qdn), r = 0; dns_nextrr(&p, &rr); ++r) { cur = rr.dnsrr_dptr; ret->dnsnaptr_naptr[r].order = dns_get16(cur); cur += 2; ret->dnsnaptr_naptr[r].preference = dns_get16(cur); cur += 2; sp += dns_getstr(&cur, end, (ret->dnsnaptr_naptr[r].flags = sp)); sp += dns_getstr(&cur, end, (ret->dnsnaptr_naptr[r].service = sp)); sp += dns_getstr(&cur, end, (ret->dnsnaptr_naptr[r].regexp = sp)); dns_getdn(pkt, &cur, end, dn, sizeof(dn)); sp += dns_dntop(dn, (ret->dnsnaptr_naptr[r].replacement = sp), DNS_MAXNAME); } dns_stdrr_finish((struct dns_rr_null *)ret, sp, &p); *result = ret; return 0; } struct dns_query * dns_submit_naptr(struct dns_ctx *ctx, const char *name, int flags, dns_query_naptr_fn *cbck, void *data) { return dns_submit_p(ctx, name, DNS_C_IN, DNS_T_NAPTR, flags, dns_parse_naptr, (dns_query_fn *)cbck, data); } struct dns_rr_naptr * dns_resolve_naptr(struct dns_ctx *ctx, const char *name, int flags) { return (struct dns_rr_naptr *) dns_resolve_p(ctx, name, DNS_C_IN, DNS_T_NAPTR, flags, dns_parse_naptr); } libtorrent-0.16.17/src/net/udns/udns_rr_ptr.c000066400000000000000000000065151522271512000211110ustar00rootroot00000000000000/* udns_rr_ptr.c parse/query PTR records Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include "udns.h" int dns_parse_ptr(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **result) { struct dns_rr_ptr *ret; struct dns_parse p; struct dns_rr rr; int r, l, c; char *sp; dnsc_t ptr[DNS_MAXDN]; assert(dns_get16(cur+2) == DNS_C_IN && dns_get16(cur+0) == DNS_T_PTR); /* first, validate the answer and count size of the result */ l = c = 0; dns_initparse(&p, qdn, pkt, cur, end); while((r = dns_nextrr(&p, &rr)) > 0) { cur = rr.dnsrr_dptr; r = dns_getdn(pkt, &cur, end, ptr, sizeof(ptr)); if (r <= 0 || cur != rr.dnsrr_dend) return DNS_E_PROTOCOL; l += dns_dntop_size(ptr); ++c; } if (r < 0) return DNS_E_PROTOCOL; if (!c) return DNS_E_NODATA; /* next, allocate and set up result */ ret = (struct dns_rr_ptr *)malloc(sizeof(*ret) + sizeof(char **) * c + l + dns_stdrr_size(&p)); if (!ret) return DNS_E_NOMEM; ret->dnsptr_nrr = c; ret->dnsptr_ptr = (char **)(ret+1); /* and 3rd, fill in result, finally */ sp = (char*)(ret->dnsptr_ptr + c); c = 0; dns_rewind(&p, qdn); while((r = dns_nextrr(&p, &rr)) > 0) { ret->dnsptr_ptr[c] = sp; cur = rr.dnsrr_dptr; dns_getdn(pkt, &cur, end, ptr, sizeof(ptr)); sp += dns_dntop(ptr, sp, DNS_MAXNAME); ++c; } dns_stdrr_finish((struct dns_rr_null *)ret, sp, &p); *result = ret; return 0; } struct dns_query * dns_submit_a4ptr(struct dns_ctx *ctx, const struct in_addr *addr, dns_query_ptr_fn *cbck, void *data) { dnsc_t dn[DNS_A4RSIZE]; dns_a4todn(addr, 0, dn, sizeof(dn)); return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_PTR, DNS_NOSRCH, dns_parse_ptr, (dns_query_fn *)cbck, data); } struct dns_rr_ptr * dns_resolve_a4ptr(struct dns_ctx *ctx, const struct in_addr *addr) { return (struct dns_rr_ptr *) dns_resolve(ctx, dns_submit_a4ptr(ctx, addr, NULL, NULL)); } struct dns_query * dns_submit_a6ptr(struct dns_ctx *ctx, const struct in6_addr *addr, dns_query_ptr_fn *cbck, void *data) { dnsc_t dn[DNS_A6RSIZE]; dns_a6todn(addr, 0, dn, sizeof(dn)); return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_PTR, DNS_NOSRCH, dns_parse_ptr, (dns_query_fn *)cbck, data); } struct dns_rr_ptr * dns_resolve_a6ptr(struct dns_ctx *ctx, const struct in6_addr *addr) { return (struct dns_rr_ptr *) dns_resolve(ctx, dns_submit_a6ptr(ctx, addr, NULL, NULL)); } libtorrent-0.16.17/src/net/udns/udns_rr_srv.c000066400000000000000000000113421522271512000211100ustar00rootroot00000000000000/* udns_rr_srv.c parse/query SRV IN (rfc2782) records Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Copyright 2005 Thadeu Lima de Souza Cascardo 2005-09-11: Changed MX parser file into a SRV parser file */ #include #include #include #include "udns.h" int dns_parse_srv(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **result) { struct dns_rr_srv *ret; struct dns_parse p; struct dns_rr rr; int r, l; char *sp; dnsc_t srv[DNS_MAXDN]; assert(dns_get16(cur+2) == DNS_C_IN && dns_get16(cur+0) == DNS_T_SRV); /* first, validate the answer and count size of the result */ l = 0; dns_initparse(&p, qdn, pkt, cur, end); while((r = dns_nextrr(&p, &rr)) > 0) { cur = rr.dnsrr_dptr + 6; r = dns_getdn(pkt, &cur, end, srv, sizeof(srv)); if (r <= 0 || cur != rr.dnsrr_dend) return DNS_E_PROTOCOL; l += dns_dntop_size(srv); } if (r < 0) return DNS_E_PROTOCOL; if (!p.dnsp_nrr) return DNS_E_NODATA; /* next, allocate and set up result */ l += dns_stdrr_size(&p); ret = (struct dns_rr_srv *)malloc(sizeof(*ret) + sizeof(struct dns_srv) * p.dnsp_nrr + l); if (!ret) return DNS_E_NOMEM; ret->dnssrv_nrr = p.dnsp_nrr; ret->dnssrv_srv = (struct dns_srv *)(ret+1); /* and 3rd, fill in result, finally */ sp = (char*)(ret->dnssrv_srv + p.dnsp_nrr); for (dns_rewind(&p, qdn), r = 0; dns_nextrr(&p, &rr); ++r) { ret->dnssrv_srv[r].name = sp; cur = rr.dnsrr_dptr; ret->dnssrv_srv[r].priority = dns_get16(cur); ret->dnssrv_srv[r].weight = dns_get16(cur+2); ret->dnssrv_srv[r].port = dns_get16(cur+4); cur += 6; dns_getdn(pkt, &cur, end, srv, sizeof(srv)); sp += dns_dntop(srv, sp, DNS_MAXNAME); } dns_stdrr_finish((struct dns_rr_null *)ret, sp, &p); *result = ret; return 0; } /* Add a single service or proto name prepending an undescore (_), * according to rfc2782 rules. * Return 0 or the label length. * Routing assumes dn holds enouth space for a single DN label. */ static int add_sname(dnsc_t *dn, const char *sn) { int l = dns_ptodn(sn, 0, dn + 1, DNS_MAXLABEL-1, NULL); if (l <= 1 || l - 2 != dn[1]) /* Should we really check if sn is exactly one label? Do we care? */ return 0; dn[0] = l - 1; dn[1] = '_'; return l; } /* Construct a domain name for SRV query from the given name, service and proto. * The code allows any combinations of srv and proto (both are non-NULL, * both NULL, or either one is non-NULL). Whenever it makes any sense or not * is left as an exercise to programmer. * Return negative value on error (malformed query) or addition query flag(s). */ static int build_srv_dn(dnsc_t *dn, const char *name, const char *srv, const char *proto) { int p = 0, l, isabs; if (srv) { l = add_sname(dn + p, srv); if (!l) return -1; p += l; } if (proto) { l = add_sname(dn + p, proto); if (!l) return -1; p += l; } l = dns_ptodn(name, 0, dn + p, DNS_MAXDN - p, &isabs); if (l < 0) return -1; return isabs ? DNS_NOSRCH : 0; } struct dns_query * dns_submit_srv(struct dns_ctx *ctx, const char *name, const char *srv, const char *proto, int flags, dns_query_srv_fn *cbck, void *data) { dnsc_t dn[DNS_MAXDN]; int r = build_srv_dn(dn, name, srv, proto); if (r < 0) { dns_setstatus (ctx, DNS_E_BADQUERY); return NULL; } return dns_submit_dn(ctx, dn, DNS_C_IN, DNS_T_SRV, flags | r, dns_parse_srv, (dns_query_fn *)cbck, data); } struct dns_rr_srv * dns_resolve_srv(struct dns_ctx *ctx, const char *name, const char *srv, const char *proto, int flags) { dnsc_t dn[DNS_MAXDN]; int r = build_srv_dn(dn, name, srv, proto); if (r < 0) { dns_setstatus(ctx, DNS_E_BADQUERY); return NULL; } return (struct dns_rr_srv *) dns_resolve_dn(ctx, dn, DNS_C_IN, DNS_T_SRV, flags | r, dns_parse_srv); } libtorrent-0.16.17/src/net/udns/udns_rr_txt.c000066400000000000000000000056111522271512000211170ustar00rootroot00000000000000/* udns_rr_txt.c parse/query TXT records Copyright (C) 2005 Michael Tokarev This file is part of UDNS library, an async DNS stub resolver. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library, in file named COPYING.LGPL; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "udns.h" int dns_parse_txt(dnscc_t *qdn, dnscc_t *pkt, dnscc_t *cur, dnscc_t *end, void **result) { struct dns_rr_txt *ret; struct dns_parse p; struct dns_rr rr; int r, l; dnsc_t *sp; dnscc_t *cp, *ep; assert(dns_get16(cur+0) == DNS_T_TXT); /* first, validate the answer and count size of the result */ l = 0; dns_initparse(&p, qdn, pkt, cur, end); while((r = dns_nextrr(&p, &rr)) > 0) { cp = rr.dnsrr_dptr; ep = rr.dnsrr_dend; while(cp < ep) { r = *cp++; if (cp + r > ep) return DNS_E_PROTOCOL; l += r; cp += r; } } if (r < 0) return DNS_E_PROTOCOL; if (!p.dnsp_nrr) return DNS_E_NODATA; /* next, allocate and set up result */ l += (sizeof(struct dns_txt) + 1) * p.dnsp_nrr + dns_stdrr_size(&p); ret = (struct dns_rr_txt *)malloc(sizeof(*ret) + l); if (!ret) return DNS_E_NOMEM; ret->dnstxt_nrr = p.dnsp_nrr; ret->dnstxt_txt = (struct dns_txt *)(ret+1); /* and 3rd, fill in result, finally */ sp = (dnsc_t*)(ret->dnstxt_txt + p.dnsp_nrr); for(dns_rewind(&p, qdn), r = 0; dns_nextrr(&p, &rr) > 0; ++r) { ret->dnstxt_txt[r].txt = sp; cp = rr.dnsrr_dptr; ep = rr.dnsrr_dend; while(cp < ep) { l = *cp++; memcpy(sp, cp, l); sp += l; cp += l; } ret->dnstxt_txt[r].len = sp - ret->dnstxt_txt[r].txt; *sp++ = '\0'; } dns_stdrr_finish((struct dns_rr_null *)ret, (char*)sp, &p); *result = ret; return 0; } struct dns_query * dns_submit_txt(struct dns_ctx *ctx, const char *name, int qcls, int flags, dns_query_txt_fn *cbck, void *data) { return dns_submit_p(ctx, name, qcls, DNS_T_TXT, flags, dns_parse_txt, (dns_query_fn *)cbck, data); } struct dns_rr_txt * dns_resolve_txt(struct dns_ctx *ctx, const char *name, int qcls, int flags) { return (struct dns_rr_txt *) dns_resolve_p(ctx, name, qcls, DNS_T_TXT, flags, dns_parse_txt); } libtorrent-0.16.17/src/net/udns_library.cc000066400000000000000000000014111522271512000204250ustar00rootroot00000000000000#include "config.h" #define HAVE_INET_PTON_NTOP 1 #define HAVE_IPv6 1 #define HAVE_POLL 1 #undef HAVE_CONFIG_H #define register // TODO: Check if we can remove extern "C" and put this in a namespace. extern "C" { #include "net/udns/udns.h" #include "net/udns/udns_XtoX.c" #include "net/udns/udns_bl.c" #include "net/udns/udns_codes.c" #include "net/udns/udns_dn.c" #include "net/udns/udns_dntosp.c" #include "net/udns/udns_init.c" #include "net/udns/udns_jran.c" #include "net/udns/udns_misc.c" #include "net/udns/udns_parse.c" #include "net/udns/udns_resolver.c" #include "net/udns/udns_rr_a.c" #include "net/udns/udns_rr_mx.c" #include "net/udns/udns_rr_naptr.c" #include "net/udns/udns_rr_ptr.c" #include "net/udns/udns_rr_srv.c" #include "net/udns/udns_rr_txt.c" } libtorrent-0.16.17/src/net/udns_library.h000066400000000000000000000001521522271512000202700ustar00rootroot00000000000000#ifndef TORRENT_NET_UDNS_LIBRARY_H #define TORRENT_NET_UDNS_LIBRARY_H #include "net/udns/udns.h" #endif libtorrent-0.16.17/src/net/udns_resolver.cc000066400000000000000000000367301522271512000206360ustar00rootroot00000000000000#include "config.h" #include "net/udns_resolver.h" #include #include #include #include #include "net/udns/udns.h" #include "torrent/common.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/system/poll.h" #include "torrent/utils/log.h" #include "torrent/system/thread.h" #include "torrent/system/system.h" #define LT_LOG(log_fmt, ...) \ lt_log_print_subsystem(LOG_NET_DNS, "dns-resolver", log_fmt, __VA_ARGS__); #define LT_LOG_QUERY(log_fmt, ...) \ lt_log_print(LOG_NET_DNS, "%016p->dns-resolver : " log_fmt, query->requester, __VA_ARGS__); #define LT_LOG_REQUESTER(log_fmt, ...) \ lt_log_print(LOG_NET_DNS, "%016p->dns-resolver : " log_fmt, requester, __VA_ARGS__); namespace torrent::net { struct UdnsQuery { // TODO: We already use deleted. ~UdnsQuery() { parent = nullptr; } void* requester{}; std::string hostname; int family{}; resolver_callback callback; // TODO: Verify canceled and deleted atomicity. UdnsResolver* parent{}; bool canceled{}; bool deleted{}; ::dns_query* a4_query{}; ::dns_query* a6_query{}; sin_shared_ptr result_sin; sin6_shared_ptr result_sin6; int error_sin{}; int error_sin6{}; std::string cname4_name; std::string cname6_name; unsigned int cname4_depth{}; unsigned int cname6_depth{}; }; class UdnsResolverInternal { public: static void a4_callback_wrapper(::dns_ctx *ctx, ::dns_rr_a4 *result, void *data); static void a6_callback_wrapper(::dns_ctx *ctx, ::dns_rr_a6 *result, void *data); }; bool UdnsResolver::m_initialized = false; static int udnserror_to_gaierror(int udnserror) { switch (udnserror) { case DNS_E_TEMPFAIL: return EAI_AGAIN; case DNS_E_PROTOCOL: // this isn't quite right return EAI_FAIL; case DNS_E_NXDOMAIN: return EAI_NONAME; case DNS_E_NODATA: return EAI_ADDRFAMILY; case DNS_E_NOMEM: return EAI_MEMORY; case DNS_E_BADQUERY: return EAI_NONAME; default: return EAI_ADDRFAMILY; } } UdnsResolver::UdnsResolver() { if (!m_initialized) { LT_LOG("initializing udns", 0); ::dns_init(nullptr, 0); m_initialized = true; } m_task_timeout.slot() = [this]() { process_timeouts(); }; } UdnsResolver::~UdnsResolver() { assert(!is_open() && "UdnsResolver::~UdnsResolver() is_open()."); } void UdnsResolver::initialize(system::Thread* thread) { assert(std::this_thread::get_id() == thread->thread_id()); LT_LOG("initializing udns resolver: thread:%s", thread->name()); m_thread = thread; m_ctx = ::dns_new(nullptr); m_fileDesc = ::dns_open(m_ctx); if (!is_open()) throw internal_error("UdnsResolver::initialize() dns_open failed"); torrent::this_thread::poll()->open(this); } void UdnsResolver::cleanup() { assert(std::this_thread::get_id() == m_thread->thread_id()); this_thread::scheduler()->erase(&m_task_timeout); if (!is_open()) { LT_LOG("cleanup not needed, not initialized: thread:%s", m_thread->name()); return; } LT_LOG("cleaning up: thread:%s", m_thread->name()); this_thread::poll()->remove_and_close(this); ::dns_close(m_ctx); ::dns_free(m_ctx); m_fileDesc = -1; } void UdnsResolver::resolve(void* requester, const std::string& hostname, int family, resolver_callback&& callback) { assert(std::this_thread::get_id() == m_thread->thread_id()); assert(family == AF_INET || family == AF_INET6 || family == AF_UNSPEC); auto query = std::make_unique(); query->requester = requester; query->hostname = hostname; query->family = family; query->callback = std::move(callback); query->parent = this; if (try_resolve_numeric(query)) return; if (family == AF_INET || family == AF_UNSPEC) { query->a4_query = ::dns_submit_a4(m_ctx, hostname.c_str(), 0, UdnsResolverInternal::a4_callback_wrapper, query.get()); if (query->a4_query == nullptr) { LT_LOG_REQUESTER("malformed A query : name:%s", hostname.c_str()); // Unrecoverable errors, like ENOMEM. if (::dns_status(m_ctx) != DNS_E_BADQUERY) throw new internal_error("dns_submit_a4 failed"); // UDNS will fail immediately during submission of malformed domain names, // e.g., `..`. In order to maintain a clean interface, keep track of this // query internally so we can call the callback later with a failure code. query->error_sin = EAI_NONAME; { auto lock = std::scoped_lock(m_mutex); m_malformed_queries_unsafe.emplace(requester, std::move(query)); } process_timeouts(); return; } } if (family == AF_INET6 || family == AF_UNSPEC) { query->a6_query = ::dns_submit_a6(m_ctx, hostname.c_str(), 0, UdnsResolverInternal::a6_callback_wrapper, query.get()); // It should be impossible for dns_submit_a6 to fail if dns_submit_a4 // succeeded, but just in case, make it a hard failure. if (query->a6_query == nullptr) { LT_LOG_REQUESTER("malformed AAAA query : name:%s", hostname.c_str()); if (query->a4_query != nullptr) { ::dns_cancel(m_ctx, query->a4_query); query->a4_query = nullptr; } if (::dns_status(m_ctx) != DNS_E_BADQUERY) throw new internal_error("dns_submit_a6 failed"); query->error_sin = EAI_NONAME; { auto lock = std::scoped_lock(m_mutex); m_malformed_queries_unsafe.emplace(requester, std::move(query)); } process_timeouts(); return; } } LT_LOG_REQUESTER("resolving : name:%s family:%d", hostname.c_str(), family); { auto lock = std::scoped_lock(m_mutex); m_queries_unsafe.emplace(requester, std::move(query)); } process_timeouts(); } bool UdnsResolver::try_resolve_numeric(std::unique_ptr& query) { assert(std::this_thread::get_id() == m_thread->thread_id()); addrinfo hints{}; addrinfo* result{}; hints.ai_family = query->family; hints.ai_socktype = SOCK_STREAM; // Not used, but required. hints.ai_flags = AI_NUMERICHOST; int ret = ::getaddrinfo(query->hostname.c_str(), nullptr, &hints, &result); if (ret == EAI_NONAME || ret == EAI_ADDRFAMILY) return false; // No numeric address found. if (ret != 0) throw internal_error("getaddrinfo failed: " + std::string(system::gai_enum_error(ret))); LT_LOG_QUERY("resolving : numeric found : name:%s family:%d", query->hostname.c_str(), query->family); if (result->ai_family == AF_INET) { query->result_sin = sin_copy(reinterpret_cast(result->ai_addr)); query->error_sin = 0; } else if (result->ai_family == AF_INET6) { query->result_sin6 = sin6_copy(reinterpret_cast(result->ai_addr)); query->error_sin6 = 0; } else { ::freeaddrinfo(result); throw internal_error("getaddrinfo returned unsupported family"); } if (query->family != AF_UNSPEC && query->family != result->ai_family) throw internal_error("getaddrinfo returned address with unexpected family"); ::freeaddrinfo(result); { auto lock = std::lock_guard(m_mutex); process_final_result_unsafe(std::move(query)); } return true; } void UdnsResolver::cancel(void* requester) { auto lock = std::lock_guard(m_mutex); auto cancel_fn = [](std::pair range) { unsigned int count = 0; for (auto itr = range.first; itr != range.second; ++itr) { if (itr->second->canceled) continue; itr->second->canceled = true; ++count; } return count; }; unsigned int query_count = cancel_fn(m_queries_unsafe.equal_range(requester)); unsigned int malformed_count = cancel_fn(m_malformed_queries_unsafe.equal_range(requester)); LT_LOG_REQUESTER("canceled : queries:%d malformed:%d", query_count, malformed_count); } void UdnsResolver::flush() { assert(std::this_thread::get_id() == m_thread->thread_id()); // Keep lock to ensure cancel() calls do not return until after flushing is complete. auto lock = std::lock_guard(m_mutex); auto malformed_queries = std::move(m_malformed_queries_unsafe); for (auto& malformed : malformed_queries) { auto query = malformed.second.get(); if (query->canceled) continue; LT_LOG_QUERY("flushing malformed query : name:%s", query->hostname.c_str()); if (query->error_sin == 0 && query->error_sin6 == 0) throw internal_error("attempting to flush malformed query with no error"); query->callback(nullptr, query->error_sin, nullptr, query->error_sin6); } } void UdnsResolver::event_read() { ::dns_ioevent(m_ctx, 0); process_timeouts(); } void UdnsResolver::event_write() { throw internal_error("UdnsResolver::event_write() called"); } void UdnsResolver::event_error() { throw internal_error("UdnsResolver::event_error() called"); } UdnsResolver::query_map::iterator UdnsResolver::find_query_or_fail_unsafe(UdnsQuery* query) { auto range = m_queries_unsafe.equal_range(query->requester); for (auto itr = range.first; itr != range.second; ++itr) { if (itr->second.get() == query) return itr; } throw internal_error("UdnsResolver::find_query_or_fail_unsafe() called with invalid query"); } // Do not call recursively. void UdnsResolver::process_timeouts() { assert(std::this_thread::get_id() == m_thread->thread_id()); int timeout = ::dns_timeouts(m_ctx, -1, 0); if (timeout == -1) { this_thread::poll()->remove_read(this); this_thread::poll()->remove_error(this); this_thread::scheduler()->erase(&m_task_timeout); LT_LOG("processing timeouts, no pending queries", 0); return; } if (timeout < 0) throw internal_error("UdnsResolver::process_timeouts() dns_timeouts returned invalid timeout: " + std::to_string(timeout)); LT_LOG("processing timeouts, next in %d seconds", timeout); this_thread::poll()->insert_read(this); this_thread::poll()->insert_error(this); this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_timeout, timeout * 1s); } void UdnsResolverInternal::a4_callback_wrapper(::dns_ctx *ctx, ::dns_rr_a4 *result, void *data) { auto query = static_cast(data); auto lock = std::lock_guard(query->parent->m_mutex); if (query->deleted) { LT_LOG_QUERY("A records received, but query was deleted : name:%s", query->requester, query->hostname.c_str()); throw internal_error("UdnsResolver::a4_callback_wrapper called with deleted query"); } auto itr = query->parent->find_query_or_fail_unsafe(query); query->a4_query = nullptr; if (result == nullptr) { query->error_sin = udnserror_to_gaierror(::dns_status(ctx)); LT_LOG_QUERY("no A records received, calling back with error : name:%s error:%s", query->hostname.c_str(), system::gai_enum_error(query->error_sin)); UdnsResolver::process_partial_result_unsafe(itr); return; } if (result->dnsa4_nrr > 0) { query->result_sin = sin_make(); query->result_sin->sin_addr = result->dnsa4_addr[0]; LT_LOG_QUERY("A records received : name:%s nrr:%d", query->hostname.c_str(), result->dnsa4_nrr); UdnsResolver::process_partial_result_unsafe(itr); return; } if (result->dnsa4_cname != nullptr) { if (query->cname4_depth++ >= 8) { query->error_sin = EAI_FAIL; LT_LOG_QUERY("A CNAME depth exceeded : original:%s current:%s", query->hostname.c_str(), query->cname4_name.c_str()); UdnsResolver::process_partial_result_unsafe(itr); return; } query->cname4_name = result->dnsa4_cname; query->a4_query = ::dns_submit_a4(ctx, query->cname4_name.c_str(), 0, &UdnsResolverInternal::a4_callback_wrapper, query); LT_LOG_QUERY("A CNAME encountered, retrying : original:%s cname:%s depth:%u", query->hostname.c_str(), query->cname4_name.c_str(), query->cname4_depth); return; } query->error_sin = udnserror_to_gaierror(::dns_status(ctx)); if (query->error_sin6 == 0) query->error_sin6 = EAI_NODATA; LT_LOG_QUERY("No A records found : name:%s error:%s", query->hostname.c_str(), system::gai_enum_error(query->error_sin)); UdnsResolver::process_partial_result_unsafe(itr); } void UdnsResolverInternal::a6_callback_wrapper(::dns_ctx *ctx, ::dns_rr_a6 *result, void *data) { auto query = static_cast(data); auto lock = std::lock_guard(query->parent->m_mutex); if (query->deleted) { LT_LOG_QUERY("AAAA records received, but query was deleted : name:%s", query->hostname.c_str()); throw internal_error("UdnsResolver::a6_callback_wrapper called with deleted query"); } auto itr = query->parent->find_query_or_fail_unsafe(query); query->a6_query = nullptr; if (result == nullptr) { query->error_sin6 = udnserror_to_gaierror(::dns_status(ctx)); LT_LOG_QUERY("no AAAA records received, calling back with error : name:%s error:%s", query->hostname.c_str(), system::gai_enum_error(query->error_sin6)); UdnsResolver::process_partial_result_unsafe(itr); return; } if (result->dnsa6_nrr > 0) { query->result_sin6 = sin6_make(); query->result_sin6->sin6_addr = result->dnsa6_addr[0]; LT_LOG_QUERY("AAAA records received : name:%s nrr:%d", query->hostname.c_str(), result->dnsa6_nrr); UdnsResolver::process_partial_result_unsafe(itr); return; } if (result->dnsa6_cname != nullptr) { if (query->cname6_depth++ >= 8) { query->error_sin6 = EAI_FAIL; LT_LOG_QUERY("AAAA CNAME depth exceeded : original:%s current:%s", query->hostname.c_str(), query->cname6_name.c_str()); UdnsResolver::process_partial_result_unsafe(itr); return; } query->cname6_name = result->dnsa6_cname; query->a6_query = ::dns_submit_a6(ctx, query->cname6_name.c_str(), 0, &UdnsResolverInternal::a6_callback_wrapper, query); LT_LOG_QUERY("AAAA CNAME encountered, retrying : original:%s cname:%s depth:%u", query->hostname.c_str(), query->cname6_name.c_str(), query->cname6_depth); return; } query->error_sin6 = udnserror_to_gaierror(::dns_status(ctx)); if (query->error_sin6 == 0) query->error_sin6 = EAI_NODATA; LT_LOG_QUERY("No AAAA records found : name:%s error:%s", query->hostname.c_str(), system::gai_enum_error(query->error_sin6)); UdnsResolver::process_partial_result_unsafe(itr); } void UdnsResolver::process_partial_result_unsafe(query_map::iterator itr) { auto& query = itr->second; if (query->a4_query != nullptr || query->a6_query != nullptr) { LT_LOG_QUERY("processing results, waiting for other queries : name:%s", query->hostname.c_str()); return; } auto tmp = std::move(itr->second); tmp->deleted = true; tmp->parent->m_queries_unsafe.erase(itr); process_final_result_unsafe(std::move(tmp)); } void UdnsResolver::process_final_result_unsafe(std::unique_ptr&& query) { if (query->canceled) { LT_LOG_QUERY("processing results, canceled : name:%s", query->hostname.c_str()); return; } LT_LOG_QUERY("processing results, calling back : name:%s", query->hostname.c_str()); if (query->result_sin && query->error_sin != 0) throw internal_error("UdnsResolver::process_final_result_unsafe() query has both result and error for A record"); if (query->result_sin6 && query->error_sin6 != 0) throw internal_error("UdnsResolver::process_final_result_unsafe() query has both result and error for AAAA record"); query->callback(query->result_sin, query->error_sin, query->result_sin6, query->error_sin6); } } // namespace torrent::net libtorrent-0.16.17/src/net/udns_resolver.h000066400000000000000000000040671522271512000204760ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_UDNSEVENT_H #define LIBTORRENT_NET_UDNSEVENT_H #include #include #include #include "torrent/event.h" #include "torrent/net/types.h" #include "torrent/utils/scheduler.h" struct dns_ctx; namespace torrent::net { struct UdnsQuery; class UdnsResolverInternal; class UdnsResolver : public Event { public: using query_map = std::multimap>; UdnsResolver(); ~UdnsResolver() override; const char* type_name() const override { return "udns"; } void initialize(system::Thread* thread); void cleanup(); // Callback must happen in thread_net and cannot call back into the resolver. // // If the hostname is a numeric address, it will result in the callback being called immediately. void resolve(void* requester, const std::string& hostname, int family, resolver_callback&& callback); // Cancel may block if the resolver received the response and is calling the callback. void cancel(void* requester); void flush(); void event_read() override; void event_write() override; void event_error() override; protected: friend class UdnsResolverInternal; std::unique_ptr erase_query(query_map::iterator itr); query_map::iterator find_query_or_fail_unsafe(UdnsQuery* query); bool try_resolve_numeric(std::unique_ptr& query); void process_canceled(); void process_timeouts(); static void process_partial_result_unsafe(query_map::iterator itr); static void process_final_result_unsafe(std::unique_ptr&& query); static bool m_initialized; system::Thread* m_thread{}; ::dns_ctx* m_ctx{}; utils::SchedulerEntry m_task_timeout; std::mutex m_mutex; query_map m_queries_unsafe; query_map m_malformed_queries_unsafe; }; } // namespace torrent::net #endif // LIBTORRENT_NET_UDNSEVENT_H libtorrent-0.16.17/src/protocol/000077500000000000000000000000001522271512000164775ustar00rootroot00000000000000libtorrent-0.16.17/src/protocol/encryption_info.h000066400000000000000000000027241522271512000220620ustar00rootroot00000000000000#ifndef LIBTORRENT_PROTOCOL_ENCRYPTION_H #define LIBTORRENT_PROTOCOL_ENCRYPTION_H #include "utils/rc4.h" namespace torrent { class EncryptionInfo { public: void encrypt(const void *indata, void *outdata, unsigned int length) { m_encrypt.crypt(indata, outdata, length); } void encrypt(void *data, unsigned int length) { m_encrypt.crypt(data, length); } void decrypt(const void *indata, void *outdata, unsigned int length) { m_decrypt.crypt(indata, outdata, length); } void decrypt(void *data, unsigned int length) { m_decrypt.crypt(data, length); } bool is_encrypted() const { return m_encrypted; } bool is_obfuscated() const { return m_obfuscated; } bool decrypt_valid() const { return m_decryptValid; } void set_obfuscated() { m_obfuscated = true; m_encrypted = m_decryptValid = false; } void set_encrypt(const RC4& encrypt) { m_encrypt = encrypt; m_encrypted = m_obfuscated = true; } void set_decrypt(const RC4& decrypt) { m_decrypt = decrypt; m_decryptValid = true; } private: bool m_encrypted{false}; bool m_obfuscated{false}; bool m_decryptValid{false}; RC4 m_encrypt; RC4 m_decrypt; }; } // namespace torrent #endif libtorrent-0.16.17/src/protocol/extensions.cc000066400000000000000000000255171522271512000212170ustar00rootroot00000000000000#include "config.h" #include "extensions.h" #include #include #include "download/download_main.h" #include "download/download_wrapper.h" #include "manager.h" #include "protocol/peer_connection_base.h" #include "torrent/download/download_manager.h" #include "torrent/download_info.h" #include "torrent/object_stream.h" #include "torrent/net/socket_address.h" #include "torrent/peer/connection_list.h" #include "torrent/peer/peer_info.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/runtime.h" namespace torrent { template <> const ExtHandshakeMessage::key_list_type ExtHandshakeMessage::keys = { { key_e, "e" }, { key_m_utMetadata, "m::ut_metadata" }, { key_m_utPex, "m::ut_pex" }, { key_metadataSize, "metadata_size" }, { key_p, "p" }, { key_reqq, "reqq" }, { key_v, "v" }, }; template <> const ExtPEXMessage::key_list_type ExtPEXMessage::keys = { { key_pex_added, "added*S" }, }; // DEBUG: Add type info. template <> const ExtMetadataMessage::key_list_type ExtMetadataMessage::keys = { { key_msgType, "msg_type" }, { key_piece, "piece" }, { key_totalSize, "total_size" }, }; struct message_type { const char* key; ext_handshake_keys index; }; const message_type message_keys[] = { { "HANDSHAKE", key_handshake_LAST }, { "ut_pex", key_m_utPex }, { "metadata_size", key_m_utMetadata } }; void ProtocolExtension::cleanup() { // if (is_default()) // return; for (int t = HANDSHAKE + 1; t < FIRST_INVALID; t++) if (is_local_enabled(t)) unset_local_enabled(t); } void ProtocolExtension::set_local_enabled(int t) { if (is_local_enabled(t)) return; m_flags |= flag_local_enabled_base << t; switch (t) { case UT_PEX: m_download->info()->set_size_pex(m_download->info()->size_pex() + 1); break; default: break; } } void ProtocolExtension::unset_local_enabled(int t) { if (!is_local_enabled(t)) return; m_flags &= ~(flag_local_enabled_base << t); switch (t) { case UT_PEX: m_download->info()->set_size_pex(m_download->info()->size_pex() - 1); break; default: break; } } DataBuffer ProtocolExtension::generate_handshake_message() { ExtHandshakeMessage message; // Add "e" key if encryption is enabled, set it to 1 if we require // encryption for incoming connections, or 0 otherwise. if ((runtime::network_config()->encryption_options() & runtime::NetworkConfig::encryption_allow_incoming) != 0) message[key_e] = (runtime::network_config()->encryption_options() & runtime::NetworkConfig::encryption_require) != 0; message[key_p] = runtime::listen_port(); message[key_v] = raw_string::from_c_str("libTorrent " VERSION); message[key_reqq] = 2048; // maximum request queue size if (!m_download->info()->is_meta_download()) message[key_metadataSize] = m_download->info()->metadata_size(); message[key_m_utPex] = is_local_enabled(UT_PEX) ? UT_PEX : 0; message[key_m_utMetadata] = UT_METADATA; char buffer[1024]; object_buffer_t result = static_map_write_bencode_c(object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), message); int length = result.second - buffer; auto copy = new char[length]; memcpy(copy, buffer, length); return DataBuffer(copy, copy + length); } inline DataBuffer ProtocolExtension::build_bencode(size_t maxLength, const char* format, ...) { auto b = new char[maxLength]; va_list args; va_start(args, format); unsigned int length = vsnprintf(b, maxLength, format, args); va_end(args); if (length > maxLength) throw internal_error("ProtocolExtension::build_bencode wrote past buffer."); return DataBuffer(b, b + length); } DataBuffer ProtocolExtension::generate_toggle_message(MessageType t, bool on) { // TODO: Check if we're accepting this message type? // Manually create bencoded map { "m" => { message_keys[t] => on ? t : 0 } } return build_bencode(32, "d1:md%zu:%si%deee", strlen(message_keys[t].key), message_keys[t].key, on ? t : 0); } DataBuffer ProtocolExtension::generate_ut_pex_message(const PEXList& added, const PEXList& removed) { if (added.empty() && removed.empty()) return DataBuffer(); int added_len = added.size() * 6; int removed_len = removed.size() * 6; // Manually create bencoded map { "added" => added, "dropped" => dropped } auto buffer = new char[32 + added_len + removed_len]; auto end = buffer; end += sprintf(end, "d5:added%d:", added_len); memcpy(end, added.begin()->c_str(), added_len); end += added_len; end += sprintf(end, "7:dropped%d:", removed_len); memcpy(end, removed.begin()->c_str(), removed_len); end += removed_len; *end++ = 'e'; if (end - buffer > 32 + added_len + removed_len) throw internal_error("ProtocolExtension::ut_pex_message wrote beyond buffer."); return DataBuffer(buffer, end); } void ProtocolExtension::read_start(int type, uint32_t length, bool skip) { if (is_default() || (type >= FIRST_INVALID) || length > (1 << 15)) throw communication_error("Received invalid extension message."); if (m_read != NULL || static_cast(length) < 0) throw internal_error("ProtocolExtension::read_start called in inconsistent state."); m_readLeft = length; if (skip || !is_local_enabled(type)) { m_readType = SKIP_EXTENSION; } else { m_readType = type; } // Allocate the buffer even for SKIP_EXTENSION, just to make things // simpler. m_readPos = m_read = new char[length]; } bool ProtocolExtension::read_done() { bool result = true; try { switch(m_readType) { case SKIP_EXTENSION: break; case HANDSHAKE: result = parse_handshake(); break; case UT_PEX: result = parse_ut_pex(); break; case UT_METADATA: result = parse_ut_metadata(); break; default: throw internal_error("ProtocolExtension::read_done called with invalid extension type."); } } catch (const bencode_error&) { // Ignore malformed messages. // DEBUG: // throw internal_error("ProtocolExtension::read_done '" + std::string(m_read, std::distance(m_read, m_readPos)) + "'"); } delete [] m_read; m_read = NULL; m_readType = FIRST_INVALID; m_flags |= flag_received_ext; return result; } // Called whenever peer enables or disables an extension. void ProtocolExtension::peer_toggle_remote(int type, bool active) { if (type == UT_PEX) { // When ut_pex is enabled, the first peer exchange afterwards needs // to be a full message, not delta. if (active) m_flags |= flag_initial_pex; } } bool ProtocolExtension::parse_handshake() { ExtHandshakeMessage message; static_map_read_bencode(m_read, m_readPos, message); for (int t = HANDSHAKE + 1; t < FIRST_INVALID; t++) { if (!message[message_keys[t].index].is_value()) continue; uint8_t id = message[message_keys[t].index].as_value(); set_remote_supported(t); if (id != m_idMap[t - 1]) { peer_toggle_remote(t, id != 0); m_idMap[t - 1] = id; } } // If this is the first handshake, then disable any local extensions // not supported by remote. if (is_initial_handshake()) { for (int t = HANDSHAKE + 1; t < FIRST_INVALID; t++) if (!is_remote_supported(t)) unset_local_enabled(t); } if (message[key_p].is_value()) { uint16_t port = message[key_p].as_value(); if (port > 0) m_peerInfo->set_listen_port(port); } if (message[key_reqq].is_value()) m_maxQueueLength = message[key_reqq].as_value(); if (message[key_metadataSize].is_value()) m_download->set_metadata_size(message[key_metadataSize].as_value()); m_flags &= ~flag_initial_handshake; return true; } bool ProtocolExtension::parse_ut_pex() { // Ignore message if we're still in the handshake (no connection // yet), or no peers are present. ExtPEXMessage message; static_map_read_bencode(m_read, m_readPos, message); // TODO: Check if pex is enabled? if (!message[key_pex_added].is_raw_string()) return true; m_download->peer_list()->insert_pex_list(message[key_pex_added].as_raw_string()); return true; } bool ProtocolExtension::parse_ut_metadata() { ExtMetadataMessage message; // Piece data comes after bencoded extension message. const char* dataStart = static_map_read_bencode(m_read, m_readPos, message); switch(message[key_msgType].as_value()) { case 0: // Can't process new request while still having data to send. if (has_pending_message()) return false; send_metadata_piece(message[key_piece].as_value()); break; case 1: if (m_connection == NULL) break; m_connection->receive_metadata_piece(message[key_piece].as_value(), dataStart, m_readPos - dataStart); break; case 2: if (m_connection == NULL) break; m_connection->receive_metadata_piece(message[key_piece].as_value(), NULL, 0); break; } return true; } void ProtocolExtension::send_metadata_piece(size_t piece) { // Reject out-of-range piece, or if we don't have the complete metadata yet. size_t metadataSize = m_download->info()->metadata_size(); size_t pieceEnd = (metadataSize + metadata_piece_size - 1) >> metadata_piece_shift; if (m_download->info()->is_meta_download() || piece >= pieceEnd) { // reject: { "msg_type" => 2, "piece" => ... } m_pendingType = UT_METADATA; m_pending = build_bencode(sizeof(size_t) + 36, "d8:msg_typei2e5:piecei%zuee", piece); return; } // These messages will be rare, so we'll just build the // metadata here instead of caching it uselessly. auto buffer = new char[metadataSize]; object_write_bencode_c(object_write_to_buffer, NULL, object_buffer_t(buffer, buffer + metadataSize), &(*manager->download_manager()->find(m_download->info()))->bencode()->get_key("info")); // data: { "msg_type" => 1, "piece" => ..., "total_size" => ... } followed by piece data (outside of dictionary) size_t length = piece == pieceEnd - 1 ? m_download->info()->metadata_size() % metadata_piece_size : metadata_piece_size; m_pendingType = UT_METADATA; m_pending = build_bencode((2 * sizeof(size_t)) + length + 120, "d8:msg_typei1e5:piecei%zue10:total_sizei%zuee", piece, metadataSize); memcpy(m_pending.end(), buffer + (piece << metadata_piece_shift), length); m_pending.set(m_pending.data(), m_pending.end() + length, m_pending.owned()); delete [] buffer; } bool ProtocolExtension::request_metadata_piece(const Piece* p) { if (p->offset() % metadata_piece_size) throw internal_error("ProtocolExtension::request_metadata_piece got misaligned piece offset."); if (has_pending_message()) return false; m_pendingType = UT_METADATA; m_pending = build_bencode(40, "d8:msg_typei0e5:piecei%uee", static_cast(p->offset() >> metadata_piece_shift)); return true; } } // namespace torrent libtorrent-0.16.17/src/protocol/extensions.h000066400000000000000000000152431522271512000210540ustar00rootroot00000000000000#ifndef LIBTORRENT_PROTOCOL_EXTENSIONS_H #define LIBTORRENT_PROTOCOL_EXTENSIONS_H #include #include #include "torrent/exceptions.h" #include "torrent/object.h" #include "torrent/object_static_map.h" #include "net/address_list.h" #include "net/data_buffer.h" namespace torrent { class ProtocolExtension { public: enum MessageType { HANDSHAKE = 0, UT_PEX, UT_METADATA, FIRST_INVALID, // first invalid message ID SKIP_EXTENSION, }; using PEXList = std::vector; static constexpr int flag_default = 1<<0; static constexpr int flag_initial_handshake = 1<<1; static constexpr int flag_initial_pex = 1<<2; static constexpr int flag_received_ext = 1<<3; // The base bit to shift by MessageType to check if the extension is // enabled locally or supported by the peer. static constexpr int flag_local_enabled_base = 1<<8; static constexpr int flag_remote_supported_base = 1<<16; // Number of extensions we support, not counting handshake. static constexpr int extension_count = FIRST_INVALID - HANDSHAKE - 1; // Fixed size of a metadata piece (16 KB). static constexpr size_t metadata_piece_shift = 14; static constexpr size_t metadata_piece_size = 1 << metadata_piece_shift; ProtocolExtension(); ~ProtocolExtension() { delete [] m_read; } ProtocolExtension(const ProtocolExtension&) = default; ProtocolExtension& operator=(const ProtocolExtension&) = default; void cleanup(); // Create default extension object, with all extensions disabled. // Useful for eliminating checks whether peer supports extensions at all. static ProtocolExtension make_default(); void set_info(PeerInfo* peerInfo, DownloadMain* download) { m_peerInfo = peerInfo; m_download = download; } void set_connection(PeerConnectionBase* c) { m_connection = c; } DataBuffer generate_handshake_message(); static DataBuffer generate_toggle_message(MessageType t, bool on); static DataBuffer generate_ut_pex_message(const PEXList& added, const PEXList& removed); // Return peer's extension ID for the given extension type, or 0 if // disabled by peer. uint8_t id(int t) const; bool is_local_enabled(int t) const { return m_flags & flag_local_enabled_base << t; } bool is_remote_supported(int t) const { return m_flags & flag_remote_supported_base << t; } void set_local_enabled(int t); void unset_local_enabled(int t); void set_remote_supported(int t) { m_flags |= flag_remote_supported_base << t; } // General information about peer from extension handshake. uint32_t max_queue_length() const { return m_maxQueueLength; } // Handle reading extension data from peer. void read_start(int type, uint32_t length, bool skip); bool read_done(); char* read_position() { return m_readPos; } bool read_move(uint32_t v) { m_readPos += v; return (m_readLeft -= v) == 0; } uint32_t read_need() const { return m_readLeft; } bool is_complete() const { return m_readLeft == 0; } bool is_invalid() const { return m_readType == FIRST_INVALID; } bool is_default() const { return m_flags & flag_default; } // Initial PEX message after peer enables PEX needs to send full list // of peers instead of the delta list, so keep track of that. bool is_initial_handshake() const { return m_flags & flag_initial_handshake; } bool is_initial_pex() const { return m_flags & flag_initial_pex; } bool is_received_ext() const { return m_flags & flag_received_ext; } void clear_initial_pex() { m_flags &= ~flag_initial_pex; } void reset() { std::memset(&m_idMap, 0, sizeof(m_idMap)); } bool request_metadata_piece(const Piece* p); // To handle cases where the extension protocol needs to send a reply. bool has_pending_message() const { return m_pendingType != HANDSHAKE; } MessageType pending_message_type() const { return m_pendingType; } DataBuffer pending_message_data() { return m_pending.release(); } void clear_pending_message() { if (m_pending.empty()) m_pendingType = HANDSHAKE; } private: bool parse_handshake(); bool parse_ut_pex(); bool parse_ut_metadata(); [[gnu::format(printf, 2, 3)]] static DataBuffer build_bencode(size_t maxLength, const char* format, ...); void peer_toggle_remote(int type, bool active); void send_metadata_piece(size_t piece); // Map of IDs peer uses for each extension message type, excluding // HANDSHAKE. uint8_t m_idMap[extension_count]; uint32_t m_maxQueueLength; // Set HANDSHAKE as enabled and supported. Those bits should not be // touched. int m_flags{flag_local_enabled_base | flag_remote_supported_base | flag_initial_handshake}; PeerInfo* m_peerInfo{}; DownloadMain* m_download{}; PeerConnectionBase* m_connection{}; uint8_t m_readType{FIRST_INVALID}; uint32_t m_readLeft; char* m_read{}; char* m_readPos; MessageType m_pendingType{HANDSHAKE}; DataBuffer m_pending; }; enum ext_handshake_keys { key_e, key_m_utMetadata, key_m_utPex, key_metadataSize, key_p, key_reqq, key_v, key_handshake_LAST }; enum ext_pex_keys { key_pex_added, key_pex_LAST }; enum ext_metadata_keys { key_msgType, key_piece, key_totalSize, key_metadata_LAST }; using ExtHandshakeMessage = static_map_type; using ExtPEXMessage = static_map_type; using ExtMetadataMessage = static_map_type; // // // inline ProtocolExtension::ProtocolExtension() { reset(); set_local_enabled(UT_METADATA); } inline ProtocolExtension ProtocolExtension::make_default() { ProtocolExtension extension; extension.m_flags |= flag_default; return extension; } inline uint8_t ProtocolExtension::id(int t) const { if (t == HANDSHAKE) return 0; if (t - 1 >= extension_count) throw internal_error("ProtocolExtension::id message type out of range."); return m_idMap[t - 1]; } } // namespace torrent #endif libtorrent-0.16.17/src/protocol/handshake.cc000066400000000000000000001232331522271512000207400ustar00rootroot00000000000000#include "config.h" #include "handshake.h" #include #include #include #include "manager.h" #include "download/download_main.h" #include "net/throttle_list.h" #include "protocol/extensions.h" #include "protocol/handshake_manager.h" #include "torrent/download_info.h" #include "torrent/exceptions.h" #include "torrent/throttle.h" #include "torrent/net/fd.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/network_manager.h" #include "torrent/runtime/socket_manager.h" #include "torrent/system/poll.h" #include "torrent/utils/log.h" #include "torrent/utils/string_manip.h" #include "utils/diffie_hellman.h" #include "utils/sha1.h" #if DISABLED__USE_EXTRA_DEBUG #define LT_LOG_EXTRA_DEBUG_SA(sa, log_fmt, ...) \ lt_log_print(LOG_CONNECTION_HANDSHAKE, "handshake->%s: " log_fmt, sap_pretty_str(m_address).c_str(), __VA_ARGS__); #else #define LT_LOG_EXTRA_DEBUG_SA(sa, log_fmt, ...) #endif namespace { class handshake_succeeded : public torrent::network_error { }; class handshake_error : public torrent::network_error { public: handshake_error(int type, int error) : m_type(type), m_error(error) {} const char* what() const noexcept override { return "Handshake error"; } int type() const noexcept { return m_type; } int error() const noexcept { return m_error; } private: int m_type; int m_error; }; std::array error_strings{ "not BitTorrent protocol", // eh_not_bittorrent "not accepting connections", // eh_not_accepting_connections "unknown download", // eh_unknown_download "download inactive", // eh_inactive_download "seeder rejected", // eh_unwanted_connection "is self", // eh_is_self "invalid value received", // eh_invalid_value "unencrypted connection rejected", // eh_unencrypted_rejected "invalid encryption method", // eh_invalid_encryption "encryption sync failed", // eh_encryption_sync_failed "network unreachable", // eh_network_unreachable "network timeout", // eh_network_timeout "too many failed chunks", // eh_toomanyfailed "no peer info", // eh_no_peer_info "network socket error", // eh_network_socket_error "network read error", // eh_network_read_error "network write error", // eh_network_write_error }; } // namespace namespace torrent { const char* handshake_strerror(int err) { if (err < 0 || err >= Handshake::e_last) throw internal_error("handshake_strerror() called with invalid error code."); return error_strings[err]; } Handshake::Handshake() : m_encryption(HandshakeEncryption::RETRY_NONE), m_extensions(HandshakeManager::default_extensions()) { m_readBuffer.reset(); m_writeBuffer.reset(); } Handshake::~Handshake() { assert(!m_task_timeout.is_scheduled()); assert(!is_open()); m_encryption.cleanup(); } void Handshake::set_manager(HandshakeManager* handshake_manager) { m_manager = handshake_manager; m_upload_throttle = manager->upload_throttle()->throttle_list(); m_download_throttle = manager->download_throttle()->throttle_list(); m_task_timeout.slot() = [this, handshake_manager] { handshake_manager->receive_timeout(this); }; } void Handshake::initialize_incoming(HandshakeManager* handshake_manager, int fd, const sockaddr* sa, int encryption_options) { set_manager(handshake_manager); m_incoming = true; m_address = sa_copy(sa); m_encryption = encryption_options; if (m_encryption.options() & (runtime::NetworkConfig::encryption_allow_incoming | runtime::NetworkConfig::encryption_require)) m_state = READ_ENC_KEY; else m_state = READ_INFO; set_file_descriptor(fd); this_thread::poll()->open(this); this_thread::poll()->insert_read(this); this_thread::poll()->insert_error(this); // Use lower timeout here. this_thread::scheduler()->wait_for_ceil_seconds(&m_task_timeout, 60s); } void Handshake::initialize_outgoing(HandshakeManager* handshake_manager, int fd, const sockaddr* sa, int encryption_options, DownloadMain* d, PeerInfo* peerInfo) { set_manager(handshake_manager); m_download = d; m_peerInfo = peerInfo; m_peerInfo->set_flags(PeerInfo::flag_handshake); m_incoming = false; m_address = sa_copy(sa); m_encryption = encryption_options; m_upload_throttle = m_download->upload_throttle(); m_download_throttle = m_download->download_throttle(); m_state = CONNECTING; set_file_descriptor(fd); this_thread::poll()->open(this); this_thread::poll()->insert_write(this); this_thread::poll()->insert_error(this); this_thread::scheduler()->wait_for_ceil_seconds(&m_task_timeout, 60s); } void Handshake::release_connection() { if (!is_open()) throw internal_error("Handshake::release_connection called but m_fd is not open."); this_thread::scheduler()->erase(&m_task_timeout); this_thread::poll()->remove_and_close(this); m_peerInfo->unset_flags(PeerInfo::flag_handshake); m_peerInfo = nullptr; m_state = INACTIVE; set_file_descriptor(-1); } void Handshake::destroy_connection(bool use_socket_manager) { this_thread::scheduler()->erase(&m_task_timeout); if (!is_open()) throw internal_error("Handshake::destroy_connection called but m_fd is not open."); m_state = INACTIVE; auto fn = [this]() { this_thread::poll()->remove_and_close(this); fd_close(m_fileDesc); set_file_descriptor(-1); }; try { if (use_socket_manager) runtime::socket_manager()->close_event_or_throw(this, fn); else fn(); } catch (...) { if (is_polling()) this_thread::poll()->remove_and_close(this); if (is_open()) { fd_close(m_fileDesc); set_file_descriptor(-1); } throw; } if (m_peerInfo == NULL) return; // TODO: This should throw. m_download->peer_list()->disconnected(m_peerInfo, 0); m_peerInfo->unset_flags(PeerInfo::flag_handshake); m_peerInfo = NULL; if (!m_extensions->is_default()) { m_extensions->cleanup(); delete m_extensions; } } int Handshake::retry_options() { uint32_t options = m_encryption.options() & ~runtime::NetworkConfig::encryption_enable_retry; if (m_encryption.retry() == HandshakeEncryption::RETRY_PLAIN) options &= ~runtime::NetworkConfig::encryption_try_outgoing; else if (m_encryption.retry() == HandshakeEncryption::RETRY_ENCRYPTED) options |= runtime::NetworkConfig::encryption_try_outgoing; else throw internal_error("Invalid retry type."); return options; } inline uint32_t Handshake::read_unthrottled(void* buf, uint32_t length) { return m_download_throttle->node_used_unthrottled(read_stream_throws(buf, length)); } inline uint32_t Handshake::write_unthrottled(const void* buf, uint32_t length) { return m_upload_throttle->node_used_unthrottled(write_stream_throws(buf, length)); } bool Handshake::read_proxy() { if (m_readBuffer.reserved_left() == 0) throw handshake_error(handshake_dropped, e_handshake_invalid_value); m_readBuffer.move_end(read_unthrottled(m_readBuffer.end(), m_readBuffer.reserved_left())); m_readBuffer.consume(m_proxy->read(m_readBuffer.position(), m_readBuffer.remaining())); switch (m_proxy->next_action()) { case net::proxy::state_writing: this_thread::poll()->remove_read(this); this_thread::poll()->insert_write(this); m_state = PROXY_WRITE; return false; case net::proxy::state_reading: return false; case net::proxy::state_done: m_readBuffer.move_unused(); m_state = PROXY_DONE; return true; default: throw handshake_error(handshake_dropped, e_handshake_invalid_value); } } // Handshake::read_encryption_key() // Entry: * 0, [0, 508> // IU 20, [20, enc_pad_read_size> // *E 96, [96, enc_pad_read_size> bool Handshake::read_encryption_key() { if (m_incoming) { if (m_readBuffer.remaining() < 20) m_readBuffer.move_end(read_unthrottled(m_readBuffer.end(), 20 - m_readBuffer.remaining())); if (m_readBuffer.remaining() < 20) return false; if (m_readBuffer.peek_8() == 19 && std::memcmp(m_readBuffer.position() + 1, m_protocol, 19) == 0) { // got unencrypted BT handshake if (m_encryption.options() & runtime::NetworkConfig::encryption_require) throw handshake_error(handshake_dropped, e_handshake_unencrypted_rejected); m_state = READ_INFO; return true; } } // Read as much of key, pad and sync string as we can; this is safe // because peer can't send anything beyond the initial BT handshake // because it doesn't know our encryption choice yet. if (m_readBuffer.remaining() < enc_pad_read_size) m_readBuffer.move_end(read_unthrottled(m_readBuffer.end(), enc_pad_read_size - m_readBuffer.remaining())); // but we need at least the key at this point if (m_readBuffer.size_end() < 96) return false; // If the handshake fails after this, it wasn't because the peer // doesn't like encrypted connections, so don't retry unencrypted. m_encryption.set_retry(HandshakeEncryption::RETRY_NONE); if (m_incoming) prepare_key_plus_pad(); if(!m_encryption.key()->compute_secret(m_readBuffer.position(), 96)) throw handshake_error(handshake_failed, e_handshake_invalid_encryption); m_readBuffer.consume(96); // Determine the synchronisation string. if (m_incoming) m_encryption.hash_req1_to_sync(); else m_encryption.encrypt_vc_to_sync(m_download->info()->hash().c_str()); // also put as much as we can write so far in the buffer if (!m_incoming) prepare_enc_negotiation(); m_state = READ_ENC_SYNC; return true; } // Handshake::read_encryption_sync() // *E 96, [96, enc_pad_read_size> bool Handshake::read_encryption_sync() { // Check if we've read the sync string already in the previous // state. This is very likely and avoids an unneeded read. auto itr = std::search(m_readBuffer.position(), m_readBuffer.end(), reinterpret_cast(m_encryption.sync()), reinterpret_cast(m_encryption.sync()) + m_encryption.sync_length()); if (itr == m_readBuffer.end()) { // Otherwise read as many bytes as possible until we find the sync // string. int toRead = enc_pad_size + m_encryption.sync_length() - m_readBuffer.remaining(); if (toRead <= 0) throw handshake_error(handshake_failed, e_handshake_encryption_sync_failed); m_readBuffer.move_end(read_unthrottled(m_readBuffer.end(), toRead)); itr = std::search(m_readBuffer.position(), m_readBuffer.end(), reinterpret_cast(m_encryption.sync()), reinterpret_cast(m_encryption.sync()) + m_encryption.sync_length()); if (itr == m_readBuffer.end()) return false; } if (m_incoming) { // We've found HASH('req1' + S), skip that and go on reading the // SKEY hash. m_readBuffer.consume(std::distance(m_readBuffer.position(), itr) + 20); m_state = READ_ENC_SKEY; } else { m_readBuffer.consume(std::distance(m_readBuffer.position(), itr)); m_state = READ_ENC_NEGOT; } return true; } bool Handshake::read_encryption_skey() { LT_LOG_EXTRA_DEBUG_SA(m_address, "read_encryption_skey", 0) if (!fill_read_buffer(20)) return false; m_encryption.deobfuscate_hash(reinterpret_cast(m_readBuffer.position())); m_download = m_manager->download_info_obfuscated(reinterpret_cast(m_readBuffer.position())); m_readBuffer.consume(20); validate_download(); // We don't allow encrypted connections for meta-data downloads. if (m_download->info()->is_meta_download()) throw handshake_error(handshake_dropped, e_handshake_invalid_encryption); m_upload_throttle = m_download->upload_throttle(); m_download_throttle = m_download->download_throttle(); m_encryption.initialize_encrypt(m_download->info()->hash().c_str(), m_incoming); m_encryption.initialize_decrypt(m_download->info()->hash().c_str(), m_incoming); m_encryption.info()->decrypt(m_readBuffer.position(), m_readBuffer.remaining()); HandshakeEncryption::copy_vc(m_writeBuffer.end()); m_encryption.info()->encrypt(m_writeBuffer.end(), HandshakeEncryption::vc_length); m_writeBuffer.move_end(HandshakeEncryption::vc_length); m_state = READ_ENC_NEGOT; return true; } bool Handshake::read_encryption_negotiation() { LT_LOG_EXTRA_DEBUG_SA(m_address, "read_encryption_negotiation", 0) if (!fill_read_buffer(enc_negotiation_size)) return false; if (!m_incoming) { // Start decrypting, but don't decrypt beyond the initial // encrypted handshake and later the pad because we may have read // too much data which may be unencrypted if the peer chose that // option. m_encryption.initialize_decrypt(m_download->info()->hash().c_str(), m_incoming); m_encryption.info()->decrypt(m_readBuffer.position(), enc_negotiation_size); } if (!HandshakeEncryption::compare_vc(m_readBuffer.position())) throw handshake_error(handshake_failed, e_handshake_invalid_value); m_readBuffer.consume(HandshakeEncryption::vc_length); m_encryption.set_crypto(m_readBuffer.read_32()); m_readPos = m_readBuffer.read_16(); // length of padC/padD if (m_readPos > enc_pad_size) throw handshake_error(handshake_failed, e_handshake_invalid_value); // choose one of the offered encryptions, or check the chosen one is valid if (m_incoming) { if ((m_encryption.options() & runtime::NetworkConfig::encryption_prefer_plaintext) && m_encryption.has_crypto_plain()) { m_encryption.set_crypto(HandshakeEncryption::crypto_plain); } else if ((m_encryption.options() & runtime::NetworkConfig::encryption_require_RC4) && !m_encryption.has_crypto_rc4()) { throw handshake_error(handshake_dropped, e_handshake_unencrypted_rejected); } else if (m_encryption.has_crypto_rc4()) { m_encryption.set_crypto(HandshakeEncryption::crypto_rc4); } else if (m_encryption.has_crypto_plain()) { m_encryption.set_crypto(HandshakeEncryption::crypto_plain); } else { throw handshake_error(handshake_failed, e_handshake_invalid_encryption); } // at this point we can also write the rest of our negotiation reply m_writeBuffer.write_32(m_encryption.crypto()); m_writeBuffer.write_16(0); m_encryption.info()->encrypt(m_writeBuffer.end() - 4 - 2, 4 + 2); } else { if (m_encryption.crypto() != HandshakeEncryption::crypto_rc4 && m_encryption.crypto() != HandshakeEncryption::crypto_plain) throw handshake_error(handshake_failed, e_handshake_invalid_encryption); if ((m_encryption.options() & runtime::NetworkConfig::encryption_require_RC4) && (m_encryption.crypto() != HandshakeEncryption::crypto_rc4)) throw handshake_error(handshake_failed, e_handshake_invalid_encryption); } if (!m_incoming) { // decrypt appropriate part of buffer: only pad or all if (m_encryption.crypto() == HandshakeEncryption::crypto_plain) m_encryption.info()->decrypt(m_readBuffer.position(), std::min(m_readPos, m_readBuffer.remaining())); else m_encryption.info()->decrypt(m_readBuffer.position(), m_readBuffer.remaining()); } // next, skip padC/padD m_state = READ_ENC_PAD; return true; } bool Handshake::read_negotiation_reply() { if (!m_incoming) { if (m_encryption.crypto() != HandshakeEncryption::crypto_rc4) m_encryption.info()->set_obfuscated(); m_state = READ_INFO; return true; } LT_LOG_EXTRA_DEBUG_SA(m_address, "read_negotiation_reply", 0) if (!fill_read_buffer(2)) return false; // The peer may send initial payload that is RC4 encrypted even if // we have selected plaintext encryption, so read it ahead of BT // handshake. m_encryption.set_length_ia(m_readBuffer.read_16()); if (m_encryption.length_ia() > handshake_size) throw handshake_error(handshake_failed, e_handshake_invalid_value); m_state = READ_ENC_IA; return true; } bool Handshake::read_info() { LT_LOG_EXTRA_DEBUG_SA(m_address, "read_info", 0) fill_read_buffer(handshake_size); // Check the first byte as early as possible so we can // disconnect non-BT connections if they send less than 20 bytes. if ((m_readBuffer.remaining() >= 1 && m_readBuffer.peek_8() != 19) || (m_readBuffer.remaining() >= 20 && (std::memcmp(m_readBuffer.position() + 1, m_protocol, 19) != 0))) throw handshake_error(handshake_failed, e_handshake_not_bittorrent); if (m_readBuffer.remaining() < part1_size) return false; // If the handshake fails after this, it isn't being rejected because // it is unencrypted, so don't retry. m_encryption.set_retry(HandshakeEncryption::RETRY_NONE); m_readBuffer.consume(20); // Should do some option field stuff here, for now just copy. m_readBuffer.read_range(m_options, m_options + 8); // Check the info hash. if (m_incoming) { if (m_download != NULL) { // Have the download from the encrypted handshake, make sure it // matches the BT handshake. if (m_download->info()->hash().not_equal_to(reinterpret_cast(m_readBuffer.position()))) throw handshake_error(handshake_failed, e_handshake_invalid_value); } else { m_download = m_manager->download_info(reinterpret_cast(m_readBuffer.position())); } validate_download(); m_upload_throttle = m_download->upload_throttle(); m_download_throttle = m_download->download_throttle(); prepare_handshake(); } else { if (m_download->info()->hash().not_equal_to(reinterpret_cast(m_readBuffer.position()))) throw handshake_error(handshake_failed, e_handshake_invalid_value); } m_readBuffer.consume(20); m_state = READ_PEER; return true; } bool Handshake::read_peer() { LT_LOG_EXTRA_DEBUG_SA(m_address, "read_peer", 0) if (!fill_read_buffer(20)) return false; prepare_peer_info(); // Send EXTENSION_PROTOCOL handshake message if peer supports it. if (m_peerInfo->supports_extensions()) write_extension_handshake(); // Replay HAVE messages we receive after starting to send the bitfield. // This avoids replaying HAVEs for pieces received between starting the // handshake and now (e.g. when connecting takes longer). Ideally we // should make a snapshot of the bitfield here in case it changes while // we're sending it (if it can't be sent in one write() call). m_initialized_time = this_thread::cached_time(); // The download is just starting so we're not sending any // bitfield. Pretend we wrote it already. if (m_download->file_list()->bitfield()->is_all_unset() || m_download->initial_seeding() != NULL) { m_writePos = m_download->file_list()->bitfield()->size_bytes(); m_writeBuffer.write_32(0); if (m_encryption.info()->is_encrypted()) m_encryption.info()->encrypt(m_writeBuffer.end() - 4, 4); } else { prepare_bitfield(); } m_state = READ_MESSAGE; this_thread::poll()->insert_write(this); // Give some extra time for reading/writing the bitfield. this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_timeout, 120s); return true; } bool Handshake::read_bitfield() { LT_LOG_EXTRA_DEBUG_SA(m_address, "read_bitfield: size:%" PRIu32, m_bitfield.size_bytes()); if (m_readPos < m_bitfield.size_bytes()) { uint32_t length = read_unthrottled(m_bitfield.begin() + m_readPos, m_bitfield.size_bytes() - m_readPos); if (m_encryption.info()->decrypt_valid()) m_encryption.info()->decrypt(m_bitfield.begin() + m_readPos, length); m_readPos += length; } return m_readPos == m_bitfield.size_bytes(); } bool Handshake::read_extension() { if (m_readBuffer.peek_32() > m_readBuffer.reserved()) throw handshake_error(handshake_failed, e_handshake_invalid_value); int32_t need = m_readBuffer.peek_32() + 4 - m_readBuffer.remaining(); // We currently can't handle an extension handshake that doesn't // completely fit in the buffer. However these messages are usually // ~100 bytes large and the buffer holds over 1000 bytes so it // should be ok. Else maybe figure out how to disable extensions for // when peer connects next time. // // In addition, make sure there's at least 5 bytes available after // the PEX message has been read, so that we can fit the preamble of // the BITFIELD message. if (need + 5 > m_readBuffer.reserved_left()) { m_readBuffer.move_unused(); if (need + 5 > m_readBuffer.reserved_left()) throw handshake_error(handshake_failed, e_handshake_invalid_value); } LT_LOG_EXTRA_DEBUG_SA(m_address, "read_extension", 0) if (!fill_read_buffer(m_readBuffer.peek_32() + 4)) return false; uint32_t length = m_readBuffer.read_32() - 2; m_readBuffer.read_8(); m_extensions->read_start(m_readBuffer.read_8(), length, false); std::memcpy(m_extensions->read_position(), m_readBuffer.position(), length); m_extensions->read_move(length); // Does this check need to check if it is a handshake we read? if (!m_extensions->is_complete()) throw internal_error("Could not read extension handshake even though it should be in the read buffer."); m_extensions->read_done(); m_readBuffer.consume(length); return true; } bool Handshake::read_port() { if (m_readBuffer.peek_32() > m_readBuffer.reserved()) throw handshake_error(handshake_failed, e_handshake_invalid_value); int32_t need = m_readBuffer.peek_32() + 4 - m_readBuffer.remaining(); if (need + 5 > m_readBuffer.reserved_left()) { m_readBuffer.move_unused(); if (need + 5 > m_readBuffer.reserved_left()) throw handshake_error(handshake_failed, e_handshake_invalid_value); } LT_LOG_EXTRA_DEBUG_SA(m_address, "read_port", 0) if (!fill_read_buffer(m_readBuffer.peek_32() + 4)) return false; uint32_t length = m_readBuffer.read_32() - 1; m_readBuffer.read_8(); if (length == 2) runtime::network_manager()->dht_add_peer_node(m_address.get(), m_readBuffer.peek_16()); m_readBuffer.consume(length); return true; } void Handshake::read_done() { if (m_readDone != false) throw internal_error("Handshake::read_done() m_readDone != false."); // if (m_peerInfo->supports_extensions() && m_extensions->is_initial_handshake()) // throw handshake_error(handshake_failed, e_handshake_invalid_order); m_readDone = true; this_thread::poll()->remove_read(this); if (m_bitfield.empty()) { m_bitfield.set_size_bits(m_download->file_list()->bitfield()->size_bits()); m_bitfield.allocate(); m_bitfield.unset_all(); } else { m_bitfield.update(); } // Should've started to write post handshake data already, but we were // still reading the bitfield/extension and postponed it. If we had no // bitfield to send, we need to send a keep-alive now. if (m_writePos == m_download->file_list()->bitfield()->size_bytes()) prepare_post_handshake(m_download->file_list()->bitfield()->is_all_unset() || m_download->initial_seeding() != NULL); if (m_writeDone) throw handshake_succeeded(); } void Handshake::event_read() { try { restart: switch (m_state) { case PROXY_READ: // if (!read_proxy_connect()) // break; // m_state = PROXY_DONE; // this_thread::poll()->insert_write(this); if (!read_proxy()) break; return event_write(); case READ_ENC_KEY: if (!read_encryption_key()) break; if (m_state != READ_ENC_SYNC) goto restart; [[fallthrough]]; case READ_ENC_SYNC: if (!read_encryption_sync()) break; if (m_state != READ_ENC_SKEY) goto restart; [[fallthrough]]; case READ_ENC_SKEY: if (!read_encryption_skey()) break; [[fallthrough]]; case READ_ENC_NEGOT: if (!read_encryption_negotiation()) break; if (m_state != READ_ENC_PAD) goto restart; [[fallthrough]]; case READ_ENC_PAD: if (m_readPos) { LT_LOG_EXTRA_DEBUG_SA(m_address, "event_read : READ_ENC_PAD : m_readPos:%" PRIu32, m_readPos) // Read padC + lenIA or padD; pad length in m_readPos. if (!fill_read_buffer(m_readPos + (m_incoming ? 2 : 0))) // This can be improved (consume as much as was read) break; m_readBuffer.consume(m_readPos); m_readPos = 0; } if (!read_negotiation_reply()) break; if (m_state != READ_ENC_IA) goto restart; [[fallthrough]]; case READ_ENC_IA: LT_LOG_EXTRA_DEBUG_SA(m_address, "event_read : READ_ENC_IA", 0) // Just read (and automatically decrypt) the initial payload // and leave it in the buffer for READ_INFO later. if (m_encryption.length_ia() > 0 && !fill_read_buffer(m_encryption.length_ia())) break; if (m_readBuffer.remaining() > m_encryption.length_ia()) throw handshake_error(handshake_failed, e_handshake_invalid_value); if (m_encryption.crypto() != HandshakeEncryption::crypto_rc4) m_encryption.info()->set_obfuscated(); m_state = READ_INFO; [[fallthrough]]; case READ_INFO: if (!read_info()) break; if (m_state != READ_PEER) goto restart; [[fallthrough]]; case READ_PEER: if (!read_peer()) break; // Is this correct? if (m_state != READ_MESSAGE) goto restart; [[fallthrough]]; case READ_MESSAGE: case POST_HANDSHAKE: // For meta-downloads, we aren't interested in the bitfield or // extension messages here, PCMetadata handles all that. The // bitfield only refers to the single-chunk meta-data, so fake that. if (m_download->info()->is_meta_download()) { m_bitfield.set_size_bits(1); m_bitfield.allocate(); m_bitfield.set(0); read_done(); break; } LT_LOG_EXTRA_DEBUG_SA(m_address, "event_read : READ_MESSAGE", 0); if (m_readBuffer.reserved_left() < 5) m_readBuffer.move_unused(); fill_read_buffer(5); // Received a keep-alive message which means we won't be // getting any bitfield. if (m_readBuffer.remaining() >= 4 && m_readBuffer.peek_32() == 0) { m_readBuffer.read_32(); read_done(); break; } if (m_readBuffer.remaining() < 5) break; m_readPos = 0; // Extension handshake was sent after BT handshake but before // bitfield, so handle that. If we've already received a message // of this type then we will assume the peer won't be sending a // bitfield, as the second extension message will be part of the // normal traffic, not the handshake. if (m_readBuffer.peek_8_at(4) == protocol_bitfield) { const Bitfield* bitfield = m_download->file_list()->bitfield(); if (!m_bitfield.empty() || m_readBuffer.read_32() != bitfield->size_bytes() + 1) throw handshake_error(handshake_failed, e_handshake_invalid_value); m_readBuffer.read_8(); m_bitfield.set_size_bits(bitfield->size_bits()); m_bitfield.allocate(); m_readPos = std::min(m_bitfield.size_bytes(), m_readBuffer.remaining()); std::memcpy(m_bitfield.begin(), m_readBuffer.position(), m_readPos); m_readBuffer.consume(m_readPos); m_state = READ_BITFIELD; } else if (m_readBuffer.peek_8_at(4) == protocol_extension && m_extensions->is_initial_handshake()) { m_readPos = 0; m_state = READ_EXT; } else if (m_readBuffer.peek_8_at(4) == protocol_port) { // Some peers seem to send the port message before handshake, // so handle it here. m_readPos = 0; m_state = READ_PORT; } else { read_done(); break; } [[fallthrough]]; case READ_BITFIELD: case READ_EXT: case READ_PORT: // Gather the different command types into the same case group // so that we don't need 'goto restart' above. if ((m_state == READ_BITFIELD && !read_bitfield()) || (m_state == READ_EXT && !read_extension()) || (m_state == READ_PORT && !read_port())) break; m_state = READ_MESSAGE; if (!m_bitfield.empty() && (!m_peerInfo->supports_extensions() || !m_extensions->is_initial_handshake())) { read_done(); break; } goto restart; default: throw internal_error("Handshake::event_read() called in invalid state."); } // Call event_write if we have any data to write. Make sure // event_write() doesn't get called twice in this function. if (m_writeBuffer.remaining() && !this_thread::poll()->in_write(this)) { this_thread::poll()->insert_write(this); return event_write(); } } catch (const handshake_succeeded&) { m_manager->receive_succeeded(this); } catch (const handshake_error& e) { m_manager->receive_failed(this, e.type(), e.error()); } catch (const network_error&) { m_manager->receive_failed(this, handshake_failed, e_handshake_network_read_error); } } bool Handshake::fill_read_buffer(int size) { LT_LOG_EXTRA_DEBUG_SA(m_address, "fill_read_buffer : size:%i remaining:%" PRIu16 " reserved_left:%" PRIu16, size, m_readBuffer.remaining(), m_readBuffer.reserved_left()) if (m_readBuffer.remaining() < size) { if (size - m_readBuffer.remaining() > m_readBuffer.reserved_left()) throw internal_error("Handshake::fill_read_buffer(...) Buffer overflow."); int read = m_readBuffer.move_end(read_unthrottled(m_readBuffer.end(), size - m_readBuffer.remaining())); if (m_encryption.info()->decrypt_valid()) m_encryption.info()->decrypt(m_readBuffer.end() - read, read); } return m_readBuffer.remaining() >= size; } inline void Handshake::validate_download() { if (m_download == nullptr) throw handshake_error(handshake_dropped, e_handshake_unknown_download); if (!m_download->info()->is_active()) throw handshake_error(handshake_dropped, e_handshake_inactive_download); if (!m_download->info()->is_accepting_new_peers()) throw handshake_error(handshake_dropped, e_handshake_not_accepting_connections); } void Handshake::event_write() { int socket_error; try { switch (m_state) { case CONNECTING: if (!fd_get_socket_error(file_descriptor(), &socket_error)) throw internal_error("Handshake::event_write() fd_get_socket_error failed : " + std::string(std::strerror(errno))); if (socket_error != 0) throw handshake_error(handshake_failed, e_handshake_network_unreachable); [[fallthrough]]; case PROXY_WRITE: if (m_proxy) { write_proxy(); break; } // TODO: Verify placement. this_thread::poll()->insert_read(this); [[fallthrough]]; case PROXY_DONE: // If there's any bytes remaining, it means we got a reply from // the other side before our proxy connect command was finished // written. This probably means the other side isn't a proxy. if (m_writeBuffer.remaining()) throw handshake_error(handshake_failed, e_handshake_not_bittorrent); m_writeBuffer.reset(); if (m_encryption.options() & (runtime::NetworkConfig::encryption_try_outgoing | runtime::NetworkConfig::encryption_require)) { prepare_key_plus_pad(); // if connection fails, peer probably closed it because it was encrypted, so retry encrypted if enabled if (!(m_encryption.options() & runtime::NetworkConfig::encryption_require)) m_encryption.set_retry(HandshakeEncryption::RETRY_PLAIN); m_state = READ_ENC_KEY; } else { // if connection is closed before we read the handshake, it might // be rejected because it is unencrypted, in that case retry encrypted m_encryption.set_retry(HandshakeEncryption::RETRY_ENCRYPTED); prepare_handshake(); if (m_incoming) m_state = READ_PEER; else m_state = READ_INFO; } break; case READ_MESSAGE: case READ_BITFIELD: case READ_EXT: write_bitfield(); return; default: break; } if (!m_writeBuffer.remaining()) throw internal_error("event_write called with empty write buffer."); if (m_writeBuffer.consume(write_unthrottled(m_writeBuffer.position(), m_writeBuffer.remaining()))) { if (m_state == POST_HANDSHAKE) { write_done(); return; } this_thread::poll()->remove_write(this); } } catch (const handshake_succeeded&) { m_manager->receive_succeeded(this); } catch (const handshake_error& e) { m_manager->receive_failed(this, e.type(), e.error()); } catch (const network_error&) { m_manager->receive_failed(this, handshake_failed, e_handshake_network_write_error); } } void Handshake::prepare_key_plus_pad() { if (!m_encryption.initialize()) throw handshake_error(handshake_failed, e_handshake_invalid_value); m_encryption.key()->store_pub_key(m_writeBuffer.end(), 96); m_writeBuffer.move_end(96); const int length = random() % enc_pad_size; auto pad = std::make_unique(length); std::generate_n(pad.get(), length, &::random); m_writeBuffer.write_len(pad.get(), length); } void Handshake::prepare_enc_negotiation() { char hash[20]; // first piece, HASH('req1' + S) sha1_salt("req1", 4, m_encryption.key()->c_str(), m_encryption.key()->size(), m_writeBuffer.end()); m_writeBuffer.move_end(20); // second piece, HASH('req2' + SKEY) ^ HASH('req3' + S) m_writeBuffer.write_len(m_download->info()->hash_obfuscated().c_str(), 20); sha1_salt("req3", 4, m_encryption.key()->c_str(), m_encryption.key()->size(), hash); for (int i = 0; i < 20; i++) m_writeBuffer.end()[i - 20] ^= hash[i]; // last piece, ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)) m_encryption.initialize_encrypt(m_download->info()->hash().c_str(), m_incoming); Buffer::iterator old_end = m_writeBuffer.end(); HandshakeEncryption::copy_vc(m_writeBuffer.end()); m_writeBuffer.move_end(HandshakeEncryption::vc_length); if (m_encryption.options() & runtime::NetworkConfig::encryption_require_RC4) m_writeBuffer.write_32(HandshakeEncryption::crypto_rc4); else m_writeBuffer.write_32(HandshakeEncryption::crypto_plain | HandshakeEncryption::crypto_rc4); m_writeBuffer.write_16(0); m_writeBuffer.write_16(handshake_size); m_encryption.info()->encrypt(old_end, m_writeBuffer.end() - old_end); // write and encrypt BT handshake as initial payload IA prepare_handshake(); } void Handshake::prepare_handshake() { m_writeBuffer.write_8(19); m_writeBuffer.write_range(m_protocol, m_protocol + 19); std::memset(m_writeBuffer.end(), 0, 8); // Supports extension protocol. *(m_writeBuffer.end()+5) |= 0x10; // Send PORT message. if (runtime::network_manager()->is_dht_active()) *(m_writeBuffer.end()+7) |= 0x01; m_writeBuffer.move_end(8); m_writeBuffer.write_range(m_download->info()->hash().c_str(), m_download->info()->hash().c_str() + 20); m_writeBuffer.write_range(m_download->info()->local_id().c_str(), m_download->info()->local_id().c_str() + 20); if (m_encryption.info()->is_encrypted()) m_encryption.info()->encrypt(m_writeBuffer.end() - handshake_size, handshake_size); } void Handshake::prepare_peer_info() { if (std::memcmp(m_readBuffer.position(), m_download->info()->local_id().c_str(), 20) == 0) throw handshake_error(handshake_failed, e_handshake_is_self); // PeerInfo handling for outgoing connections needs to be moved to // HandshakeManager. if (m_peerInfo == nullptr) { if (!m_incoming) throw internal_error("Handshake::prepare_peer_info() !m_incoming."); m_peerInfo = m_download->peer_list()->connected(m_address.get(), PeerList::connect_incoming); if (m_peerInfo == nullptr) throw handshake_error(handshake_failed, e_handshake_no_peer_info); if (m_peerInfo->failed_counter() > torrent::HandshakeManager::max_failed) throw handshake_error(handshake_dropped, e_handshake_toomanyfailed); m_peerInfo->set_flags(PeerInfo::flag_handshake); } std::memcpy(m_peerInfo->set_options(), m_options, 8); m_peerInfo->mutable_id().assign(reinterpret_cast(m_readBuffer.position())); m_readBuffer.consume(20); utils::transform_to_hex(m_peerInfo->id(), m_peerInfo->mutable_id_hex(), m_peerInfo->mutable_id_hex() + 40); // For meta downloads, we require support of the extension protocol. if (m_download->info()->is_meta_download() && !m_peerInfo->supports_extensions()) throw handshake_error(handshake_dropped, e_handshake_unwanted_connection); } void Handshake::prepare_bitfield() { m_writeBuffer.write_32(m_download->file_list()->bitfield()->size_bytes() + 1); m_writeBuffer.write_8(protocol_bitfield); if (m_encryption.info()->is_encrypted()) m_encryption.info()->encrypt(m_writeBuffer.end() - 5, 5); m_writePos = 0; } void Handshake::prepare_post_handshake(bool must_write) { if (m_writePos != m_download->file_list()->bitfield()->size_bytes()) throw internal_error("Handshake::prepare_post_handshake called while bitfield not written completely."); m_state = POST_HANDSHAKE; Buffer::iterator old_end = m_writeBuffer.end(); // Send PORT message for DHT if enabled and peer supports it. if (m_peerInfo->supports_dht() && runtime::network_manager()->is_dht_active_and_receiving_requests()) { m_writeBuffer.write_32(3); m_writeBuffer.write_8(protocol_port); m_writeBuffer.write_16(runtime::network_manager()->dht_port()); } // Send a keep-alive if we still must send something. if (must_write && old_end == m_writeBuffer.end()) m_writeBuffer.write_32(0); if (m_encryption.info()->is_encrypted()) m_encryption.info()->encrypt(old_end, m_writeBuffer.end() - old_end); if (!m_writeBuffer.remaining()) write_done(); } void Handshake::write_done() { m_writeDone = true; this_thread::poll()->remove_write(this); // Ok to just check m_readDone as the call in event_read() won't // set it before the call. if (m_readDone) throw handshake_succeeded(); } // TODO: When adding other proxy types, make sure they're require less than the buffer size. void Handshake::write_proxy() { if (m_proxy->next_action() != net::proxy::state_writing) throw internal_error("Handshake::write_proxy() next_action != state_writing."); auto new_bytes = m_writeBuffer.move_end(m_proxy->write(m_writeBuffer.end(), m_writeBuffer.reserved_left())); if (new_bytes == 0) throw internal_error("Handshake::write_proxy() m_proxy->write() returned 0."); switch (m_proxy->next_action()) { case net::proxy::state_writing: throw internal_error("Handshake::write_proxy() next_action == state_writing after write."); case net::proxy::state_reading: this_thread::poll()->remove_write(this); this_thread::poll()->insert_read(this); m_state = PROXY_READ; break; case net::proxy::state_done: // TODO: When adding other proxy types, we might need to change this. throw internal_error("Handshake::write_proxy() next_action == state_finished after write."); break; default: throw internal_error("Handshake::write_proxy() next_action == state_error after write."); } } void Handshake::write_extension_handshake() { DownloadInfo* info = m_download->info(); if (m_extensions->is_default()) { m_extensions = new ProtocolExtension; m_extensions->set_info(m_peerInfo, m_download); } // PEX may be disabled but still active if disabled since last download tick. if (info->is_pex_enabled() && info->is_pex_active() && info->size_pex() < info->max_size_pex()) m_extensions->set_local_enabled(ProtocolExtension::UT_PEX); DataBuffer message = m_extensions->generate_handshake_message(); m_writeBuffer.write_32(message.length() + 2); m_writeBuffer.write_8(protocol_extension); m_writeBuffer.write_8(ProtocolExtension::HANDSHAKE); m_writeBuffer.write_range(message.data(), message.end()); if (m_encryption.info()->is_encrypted()) m_encryption.info()->encrypt(m_writeBuffer.end() - message.length() - 2 - 4, message.length() + 2 + 4); message.clear(); } void Handshake::write_bitfield() { const Bitfield* bitfield = m_download->file_list()->bitfield(); if (m_writeDone != false) throw internal_error("Handshake::event_write() m_writeDone != false."); if (m_writeBuffer.remaining()) if (!m_writeBuffer.consume(write_unthrottled(m_writeBuffer.position(), m_writeBuffer.remaining()))) return; if (m_writePos != bitfield->size_bytes()) { if (m_encryption.info()->is_encrypted()) { if (m_writePos == 0) m_writeBuffer.reset(); // this should be unnecessary now uint32_t length = std::min(bitfield->size_bytes() - m_writePos, m_writeBuffer.reserved()) - m_writeBuffer.size_end(); if (length > 0) { std::memcpy(m_writeBuffer.end(), bitfield->begin() + m_writePos + m_writeBuffer.size_end(), length); m_encryption.info()->encrypt(m_writeBuffer.end(), length); m_writeBuffer.move_end(length); } length = write_unthrottled(m_writeBuffer.begin(), m_writeBuffer.size_end()); m_writePos += length; if (length != m_writeBuffer.size_end() && length > 0) std::memmove(m_writeBuffer.begin(), m_writeBuffer.begin() + length, m_writeBuffer.size_end() - length); m_writeBuffer.move_end(-length); } else { m_writePos += write_unthrottled(bitfield->begin() + m_writePos, bitfield->size_bytes() - m_writePos); } } // We can't call prepare_post_handshake until the read code is done reading // the bitfield, so if we get here before then, postpone the post handshake // data until reading is done. Since we're done writing, remove us from the // poll in that case. if (m_writePos == bitfield->size_bytes()) { if (!m_readDone) this_thread::poll()->remove_write(this); else prepare_post_handshake(false); } } void Handshake::event_error() { if (m_state == INACTIVE) throw internal_error("Handshake::event_error() called on an inactive handshake."); m_manager->receive_failed(this, handshake_failed, e_handshake_network_socket_error); } } // namespace torrent libtorrent-0.16.17/src/protocol/handshake.h000066400000000000000000000171761522271512000206120ustar00rootroot00000000000000#ifndef LIBTORRENT_HANDSHAKE_H #define LIBTORRENT_HANDSHAKE_H #include "net/protocol_buffer.h" #include "net/proxy/proxy.h" #include "net/socket_stream.h" #include "torrent/bitfield.h" #include "torrent/net/socket_address.h" #include "torrent/peer/peer_info.h" #include "torrent/utils/scheduler.h" #include "handshake_encryption.h" namespace torrent { class HandshakeManager; class DownloadMain; class ThrottleList; namespace net::proxy { class Proxy; } // namespace net::proxy const char* handshake_strerror(int err); class Handshake : public SocketStream { public: static constexpr uint32_t part1_size = 20 + 28; static constexpr uint32_t part2_size = 20; static constexpr uint32_t handshake_size = part1_size + part2_size; static constexpr uint32_t read_message_size = 2 * 5; static constexpr uint32_t protocol_bitfield = 5; static constexpr uint32_t protocol_port = 9; static constexpr uint32_t protocol_extension = 20; static constexpr uint32_t enc_negotiation_size = 8 + 4 + 2; static constexpr uint32_t enc_pad_size = 512; static constexpr uint32_t enc_pad_read_size = 96 + enc_pad_size + 20; static constexpr uint32_t buffer_size = enc_pad_read_size + 20 + enc_negotiation_size + enc_pad_size + 2 + handshake_size + read_message_size; static constexpr int e_handshake_not_bittorrent = 0; static constexpr int e_handshake_not_accepting_connections = 1; static constexpr int e_handshake_unknown_download = 2; static constexpr int e_handshake_inactive_download = 3; static constexpr int e_handshake_unwanted_connection = 4; static constexpr int e_handshake_is_self = 5; static constexpr int e_handshake_invalid_value = 6; static constexpr int e_handshake_unencrypted_rejected = 7; static constexpr int e_handshake_invalid_encryption = 8; static constexpr int e_handshake_encryption_sync_failed = 9; static constexpr int e_handshake_network_unreachable = 10; static constexpr int e_handshake_network_timeout = 11; static constexpr int e_handshake_toomanyfailed = 12; static constexpr int e_handshake_no_peer_info = 13; static constexpr int e_handshake_network_socket_error = 14; static constexpr int e_handshake_network_read_error = 15; static constexpr int e_handshake_network_write_error = 16; static constexpr int e_last = 17; // static constexpr int handshake_incoming = 1; static constexpr int handshake_outgoing = 2; static constexpr int handshake_outgoing_encrypted = 3; static constexpr int handshake_outgoing_proxy = 4; // static constexpr int handshake_success = 5; static constexpr int handshake_dropped = 6; static constexpr int handshake_failed = 7; // static constexpr int handshake_retry_plaintext = 8; // static constexpr int handshake_retry_encrypted = 9; using Buffer = ProtocolBuffer; enum State { INACTIVE, CONNECTING, POST_HANDSHAKE, PROXY_WRITE, PROXY_READ, PROXY_DONE, READ_ENC_KEY, READ_ENC_SYNC, READ_ENC_SKEY, READ_ENC_NEGOT, READ_ENC_PAD, READ_ENC_IA, READ_INFO, READ_PEER, READ_MESSAGE, READ_BITFIELD, READ_EXT, READ_PORT }; Handshake(); ~Handshake() override; const char* type_name() const override { return "handshake"; } bool is_active() const { return m_state != INACTIVE; } State state() const { return m_state; } PeerInfo* peer_info() { return m_peerInfo; } const PeerInfo* peer_info() const { return m_peerInfo; } void set_peer_info(PeerInfo* p); auto* proxy(); void set_proxy(net::proxy_ptr proxy); void initialize_incoming(HandshakeManager* handshake_manager, int fd, const sockaddr* sa, int encryption_options); void initialize_outgoing(HandshakeManager* handshake_manager, int fd, const sockaddr* sa, int encryption_options, DownloadMain* d, PeerInfo* peerInfo); const sockaddr* socket_address() const { return m_address.get(); } DownloadMain* download() { return m_download; } Bitfield* bitfield() { return &m_bitfield; } void release_connection(); void destroy_connection(bool use_socket_manager = true); const void* unread_data() { return m_readBuffer.position(); } uint32_t unread_size() const { return m_readBuffer.remaining(); } std::chrono::microseconds initialized_time() const { return m_initialized_time; } void event_read() override; void event_write() override; void event_error() override; HandshakeEncryption* encryption() { return &m_encryption; } ProtocolExtension* extensions() { return m_extensions; } int retry_options(); protected: Handshake(const Handshake&) = delete; Handshake& operator=(const Handshake&) = delete; void set_manager(HandshakeManager* handshake_manager); void read_done(); bool fill_read_buffer(int size); // Check what is unnessesary. bool read_proxy(); bool read_encryption_key(); bool read_encryption_sync(); bool read_encryption_skey(); bool read_encryption_negotiation(); bool read_negotiation_reply(); bool read_info(); bool read_peer(); bool read_bitfield(); bool read_extension(); bool read_port(); void prepare_key_plus_pad(); void prepare_enc_negotiation(); void prepare_handshake(); void prepare_peer_info(); void prepare_bitfield(); void prepare_post_handshake(bool must_write); void write_done(); void write_proxy(); void write_extension_handshake(); void write_bitfield(); inline void validate_download(); uint32_t read_unthrottled(void* buf, uint32_t length); uint32_t write_unthrottled(const void* buf, uint32_t length); static constexpr auto m_protocol = "BitTorrent protocol"; State m_state{INACTIVE}; HandshakeManager* m_manager; PeerInfo* m_peerInfo{}; DownloadMain* m_download{}; net::proxy_ptr m_proxy; Bitfield m_bitfield; ThrottleList* m_upload_throttle; ThrottleList* m_download_throttle; utils::SchedulerEntry m_task_timeout; std::chrono::microseconds m_initialized_time; uint32_t m_readPos; uint32_t m_writePos; bool m_readDone{false}; bool m_writeDone{false}; bool m_incoming; c_sa_unique_ptr m_address; char m_options[8]; HandshakeEncryption m_encryption; ProtocolExtension* m_extensions; // Put these last to keep variables closer to *this. Buffer m_readBuffer; Buffer m_writeBuffer; }; inline void Handshake::set_peer_info(PeerInfo* p) { m_peerInfo = p; } inline auto* Handshake::proxy() { return m_proxy.get(); } inline void Handshake::set_proxy(net::proxy_ptr proxy) { m_proxy = std::move(proxy); } } // namespace torrent #endif libtorrent-0.16.17/src/protocol/handshake_encryption.cc000066400000000000000000000061151522271512000232110ustar00rootroot00000000000000#include "config.h" #include "handshake_encryption.h" #include "torrent/runtime/network_config.h" #include "utils/diffie_hellman.h" #include "utils/sha1.h" namespace torrent { const unsigned char HandshakeEncryption::dh_prime[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63, }; const unsigned char HandshakeEncryption::dh_generator[] = { 2 }; const unsigned char HandshakeEncryption::vc_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; bool HandshakeEncryption::should_retry() const { return (m_options & runtime::NetworkConfig::encryption_enable_retry) != 0 && m_retry != HandshakeEncryption::RETRY_NONE; } HandshakeEncryption::HandshakeEncryption(int options) : m_options(options) { } bool HandshakeEncryption::initialize() { m_key = std::make_unique(dh_prime, dh_prime_length, dh_generator, dh_generator_length); return m_key->is_valid(); } void HandshakeEncryption::cleanup() { m_key = nullptr; } bool HandshakeEncryption::compare_vc(const void* buf) { return std::memcmp(buf, vc_data, vc_length) == 0; } void HandshakeEncryption::initialize_decrypt(const char* origHash, bool incoming) { char hash[20]; unsigned char discard[1024]; sha1_salt(incoming ? "keyA" : "keyB", 4, m_key->c_str(), 96, origHash, 20, hash); m_info.set_decrypt(RC4(reinterpret_cast(hash), 20)); m_info.decrypt(discard, 1024); } void HandshakeEncryption::initialize_encrypt(const char* origHash, bool incoming) { char hash[20]; unsigned char discard[1024]; sha1_salt(incoming ? "keyB" : "keyA", 4, m_key->c_str(), 96, origHash, 20, hash); m_info.set_encrypt(RC4(reinterpret_cast(hash), 20)); m_info.encrypt(discard, 1024); } // Obfuscated hash is HASH('req2', download_hash), extract that from // HASH('req2', download_hash) ^ HASH('req3', S). void HandshakeEncryption::deobfuscate_hash(char* src) const { char tmp[20]; sha1_salt("req3", 4, m_key->c_str(), m_key->size(), tmp); for (int i = 0; i < 20; i++) src[i] ^= tmp[i]; } void HandshakeEncryption::hash_req1_to_sync() { sha1_salt("req1", 4, m_key->c_str(), m_key->size(), modify_sync(20)); } void HandshakeEncryption::encrypt_vc_to_sync(const char* origHash) { m_syncLength = vc_length; std::memcpy(m_sync, vc_data, vc_length); char hash[20]; char discard[1024]; sha1_salt("keyB", 4, m_key->c_str(), 96, origHash, 20, hash); RC4 peerEncrypt(reinterpret_cast(hash), 20); peerEncrypt.crypt(discard, 1024); peerEncrypt.crypt(m_sync, HandshakeEncryption::vc_length); } } // namespace torrent libtorrent-0.16.17/src/protocol/handshake_encryption.h000066400000000000000000000060561522271512000230570ustar00rootroot00000000000000#ifndef LIBTORRENT_PROTOCOL_HANDSHAKE_ENCRYPTION_H #define LIBTORRENT_PROTOCOL_HANDSHAKE_ENCRYPTION_H #include #include #include "encryption_info.h" namespace torrent { class DiffieHellman; class HandshakeEncryption { public: enum Retry { RETRY_NONE, RETRY_PLAIN, RETRY_ENCRYPTED, }; static constexpr int crypto_plain = 1; static constexpr int crypto_rc4 = 2; static const unsigned char dh_prime[]; static constexpr unsigned int dh_prime_length = 96; static const unsigned char dh_generator[]; static constexpr unsigned int dh_generator_length = 1; static const unsigned char vc_data[]; static constexpr unsigned int vc_length = 8; HandshakeEncryption(int options); bool has_crypto_plain() const { return m_crypto & crypto_plain; } bool has_crypto_rc4() const { return m_crypto & crypto_rc4; } const auto& key() { return m_key; } EncryptionInfo* info() { return &m_info; } int options() const { return m_options; } int crypto() const { return m_crypto; } void set_crypto(int val) { m_crypto = val; } Retry retry() const { return m_retry; } void set_retry(Retry val) { m_retry = val; } bool should_retry() const; const char* sync() const { return m_sync; } unsigned int sync_length() const { return m_syncLength; } void set_sync(const char* src, unsigned int len) { std::memcpy(m_sync, src, (m_syncLength = len)); } char* modify_sync(unsigned int len) { m_syncLength = len; return m_sync; } unsigned int length_ia() const { return m_lengthIA; } void set_length_ia(unsigned int len) { m_lengthIA = len; } bool initialize(); void cleanup(); void initialize_decrypt(const char* origHash, bool incoming); void initialize_encrypt(const char* origHash, bool incoming); void deobfuscate_hash(char* src) const; void hash_req1_to_sync(); void encrypt_vc_to_sync(const char* origHash); static void copy_vc(void* dest) { std::memset(dest, 0, vc_length); } static bool compare_vc(const void* buf); private: std::unique_ptr m_key; // A pointer instead? EncryptionInfo m_info; int m_options; int m_crypto{0}; Retry m_retry{RETRY_NONE}; char m_sync[20]; unsigned int m_syncLength{0}; unsigned int m_lengthIA{0}; }; } // namespace torrent #endif libtorrent-0.16.17/src/protocol/handshake_manager.cc000066400000000000000000000311541522271512000224320ustar00rootroot00000000000000#include "config.h" #include "handshake_manager.h" #include "handshake.h" #include "manager.h" #include "peer_connection_base.h" #include "download/download_main.h" #include "net/proxy/proxy.h" #include "torrent/download_info.h" #include "torrent/exceptions.h" #include "torrent/net/fd.h" #include "torrent/net/socket_address.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/socket_manager.h" #include "torrent/runtime/proxy_manager.h" #include "torrent/peer/peer_info.h" #include "torrent/peer/client_list.h" #include "torrent/peer/connection_list.h" #include "torrent/utils/log.h" #include "torrent/utils/string_manip.h" #define LT_LOG_SA(sa, log_fmt, ...) \ lt_log_print(LOG_CONNECTION_HANDSHAKE, "handshake_manager->%s: " log_fmt, sa_addr_str(sa).c_str(), __VA_ARGS__); #define LT_LOG_SAP(sa, log_fmt, ...) \ lt_log_print(LOG_CONNECTION_HANDSHAKE, "handshake_manager->%s: " log_fmt, sap_addr_str(sa).c_str(), __VA_ARGS__); namespace torrent { namespace { int open_and_connect_socket(const sockaddr* connect_address); bool setup_socket(int fd, int family); bool filter_sockaddr(const sockaddr* sa); } // namespace anonymous ProtocolExtension HandshakeManager::DefaultExtensions = ProtocolExtension::make_default(); HandshakeManager::HandshakeManager() = default; HandshakeManager::~HandshakeManager() { clear(); } HandshakeManager::size_type HandshakeManager::size_info(DownloadMain* info) const { return std::count_if(base_type::begin(), base_type::end(), [info](auto& h) { return info == h->download(); }); } void HandshakeManager::clear() { for (auto& h : *this) { h->destroy_connection(); h.reset(); } base_type::clear(); } HandshakeManager::value_type HandshakeManager::find_and_erase(Handshake* handshake) { auto itr = std::find_if(base_type::begin(), base_type::end(), [handshake](auto& h) { return h.get() == handshake; }); if (itr == base_type::end()) throw internal_error("HandshakeManager::erase(...) could not find handshake."); auto tmp = std::move(*itr); base_type::erase(itr); return tmp; } bool HandshakeManager::find(const sockaddr* sa) { return std::any_of(base_type::begin(), base_type::end(), [sa](auto& p2) { return p2->peer_info() != nullptr && sa_equal(sa, p2->peer_info()->socket_address()); }); } void HandshakeManager::erase_download(DownloadMain* info) { auto split = std::partition(base_type::begin(), base_type::end(), [info](auto& h) { return info != h->download(); }); std::for_each(split, base_type::end(), [](auto& h) { h->destroy_connection(); h.reset(); }); base_type::erase(split, base_type::end()); } // TODO: Replace by Handshake::prepare_incoming(fd), and close here. void HandshakeManager::add_incoming(std::unique_ptr& handshake, int fd, const sockaddr* sa) { if (!filter_sockaddr(sa)) { LT_LOG_SA(sa, "rejected incoming connection: fd:%i : filtered", fd); fd_close(fd); return; } if (!fd_set_nonblock(fd)) throw internal_error("HandshakeManager::add_incoming() fd_set_nonblocking failed : " + std::string(std::strerror(errno))); if (!setup_socket(fd, sa->sa_family)) { LT_LOG_SA(sa, "rejected incoming connection: fd:%i : setup socket failed : %s", fd, std::strerror(errno)); fd_close(fd); return; } LT_LOG_SA(sa, "accepted incoming connection: fd:%i", fd); if (sa_is_v4mapped(sa)) handshake->initialize_incoming(this, fd, sa_from_v4mapped(sa).get(), runtime::network_config()->encryption_options()); else handshake->initialize_incoming(this, fd, sa, runtime::network_config()->encryption_options()); base_type::push_back(std::move(handshake)); } void HandshakeManager::add_outgoing(const sockaddr* sa, DownloadMain* download) { if (!runtime::socket_manager()->can_open_socket(runtime::category_generic) || !filter_sockaddr(sa)) return; auto encryption_options = runtime::network_config()->encryption_options(); if (download->info()->is_meta_download()) { encryption_options &= ~runtime::NetworkConfig::encryption_try_outgoing; encryption_options &= ~runtime::NetworkConfig::encryption_require; encryption_options &= ~runtime::NetworkConfig::encryption_require_RC4; encryption_options &= ~runtime::NetworkConfig::encryption_enable_retry; } if (sa_is_v4mapped(sa)) create_outgoing(sa_from_v4mapped(sa).get(), download, encryption_options); else create_outgoing(sa, download, encryption_options); } void HandshakeManager::create_outgoing(const sockaddr* sa, DownloadMain* download, int encryption_options) { int connection_options = PeerList::connect_keep_handshakes; if (!(encryption_options & runtime::NetworkConfig::encryption_retrying)) connection_options |= PeerList::connect_filter_recent; PeerInfo* peer_info = download->peer_list()->connected(sa, connection_options); if (peer_info == NULL || peer_info->failed_counter() > max_failed) { LT_LOG_SA(sa, "rejected outgoing connection: no peer info or too many failures", 0); return; } auto handshake = std::make_unique(); auto connect_address = [sa, handshake = handshake.get()]() { auto proxy = runtime::proxy_manager()->create_proxy(sa); if (proxy != nullptr) { handshake->set_proxy(std::move(proxy)); return sa_copy(handshake->proxy()->proxy_address()); } return sa_copy(sa); }(); auto open_func = [&]() { int fd = open_and_connect_socket(connect_address.get()); if (fd == -1) return; auto type_fn = [&]() { if (encryption_options & runtime::NetworkConfig::encryption_try_outgoing) return "try encrypted"; else if (encryption_options & runtime::NetworkConfig::encryption_require) return "require encrypted"; else return "plain"; }; if (handshake->proxy()) { LT_LOG_SA(handshake->proxy()->proxy_address(), "created outgoing connection : proxy : %s : fd:%i encryption:%x", type_fn(), fd, encryption_options); } else { LT_LOG_SA(sa, "created outgoing connection : %s : fd:%i encryption:%x", type_fn(), fd, encryption_options); } handshake->initialize_outgoing(this, fd, sa, encryption_options, download, peer_info); }; auto cleanup_func = [&](bool) { if (!handshake->is_open()) { LT_LOG_SA(sa, "failed to create outgoing connection: open failed", 0); download->peer_list()->disconnected(peer_info, 0); return; } LT_LOG_SA(sa, "failed to create outgoing connection : socket manager triggered cleanup", 0); handshake->destroy_connection(false); }; runtime::socket_manager()->open_event_or_cleanup(handshake.get(), runtime::category_generic, open_func, cleanup_func); if (!handshake->is_open()) return; base_type::push_back(std::move(handshake)); } void HandshakeManager::receive_succeeded(Handshake* ptr) { if (!ptr->is_active()) throw internal_error("HandshakeManager::receive_succeeded(...) called on an inactive handshake."); auto handshake = find_and_erase(ptr); auto download = handshake->download(); auto peer_type = handshake->bitfield()->is_all_set() ? "seed" : "leech"; auto hash_str = utils::copy_escape_html_str(handshake->peer_info()->id()); auto error_func = [&](uint32_t reason) { LT_LOG_SA(handshake->peer_info()->socket_address(), "handshake dropped: type:%s id:%s reason:'%s'", peer_type, hash_str.c_str(), handshake_strerror(reason)); handshake->destroy_connection(); }; if (!download->info()->is_active()) return error_func(Handshake::e_handshake_inactive_download); if (!download->connection_list()->want_connection(handshake->peer_info(), handshake->bitfield())) return error_func(Handshake::e_handshake_unwanted_connection); auto fd = handshake->file_descriptor(); auto peer_info = handshake->peer_info(); auto new_event = runtime::socket_manager()->transfer_event(handshake.get(), [&]() -> Event* { LT_LOG_SA(peer_info->socket_address(), "transfering handshake: type:%s id:%s", peer_type, hash_str.c_str()); handshake->release_connection(); return download->connection_list()->insert(peer_info, fd, handshake->bitfield(), handshake->encryption()->info(), handshake->extensions()); }); if (new_event == nullptr) { fd_close(fd); download->peer_list()->disconnected(peer_info, 0); lt_log_print(LOG_CONNECTION_HANDSHAKE, "handshake_manager: duplicate peer: type:%s id:%s", peer_type, hash_str.c_str()); return; } auto pcb = static_cast(new_event); manager->client_list()->retrieve_id(&peer_info->mutable_client_info(), peer_info->id()); pcb->peer_chunks()->set_have_timer(handshake->initialized_time()); LT_LOG_SA(peer_info->socket_address(), "handshake success: type:%s id:%s", peer_type, hash_str.c_str()); if (handshake->unread_size() != 0) { if (handshake->unread_size() > PeerConnectionBase::ProtocolRead::buffer_size) throw internal_error("HandshakeManager::receive_succeeded(...) Unread data won't fit PCB's read buffer."); pcb->push_unread(handshake->unread_data(), handshake->unread_size()); pcb->event_read(); } } void HandshakeManager::receive_failed(Handshake* ptr, int message, int error) { if (!ptr->is_active()) throw internal_error("HandshakeManager::receive_failed(...) called on an inactive handshake."); auto handshake = find_and_erase(ptr); auto sa = handshake->socket_address(); handshake->destroy_connection(); LT_LOG_SA(sa, "Received error: message:%x %s.", message, handshake_strerror(error)); if (handshake->encryption()->should_retry()) { int retry_options = handshake->retry_options() | runtime::NetworkConfig::encryption_retrying; DownloadMain* download = handshake->download(); LT_LOG_SA(sa, "Retrying %s.", retry_options & runtime::NetworkConfig::encryption_try_outgoing ? "encrypted" : "plaintext"); create_outgoing(sa, download, retry_options); } } void HandshakeManager::receive_timeout(Handshake* h) { receive_failed(h, Handshake::handshake_failed, h->state() == Handshake::CONNECTING ? Handshake::e_handshake_network_unreachable : Handshake::e_handshake_network_timeout); } namespace { int open_and_connect_socket(const sockaddr* connect_address) { auto bind_address = runtime::network_config()->bind_address_for_connect(connect_address->sa_family); if (bind_address == nullptr) { LT_LOG_SA(connect_address, "could not create outgoing connection: blocked or invalid bind address", 0); return -1; } int fd = fd_open_family(fd_flag_stream | fd_flag_nonblock, connect_address->sa_family); if (fd == -1) { LT_LOG_SA(connect_address, "could not create outgoing connection: open stream failed : fd:%i : %s", fd, std::strerror(errno)); return -1; } auto close_fn = [fd]() { fd_close(fd); return -1; }; if (!setup_socket(fd, connect_address->sa_family)) { LT_LOG_SA(connect_address, "could not create outgoing connection: setup socket failed : fd:%i : %s", fd, std::strerror(errno)); return close_fn(); } if (!sa_is_any(bind_address.get()) && !fd_bind(fd, bind_address.get())) { LT_LOG_SA(connect_address, "could not create outgoing connection: bind failed : fd:%i : %s", fd, std::strerror(errno)); return close_fn(); } if (!fd_connect_with_family(fd, connect_address, bind_address->sa_family)) { LT_LOG_SA(connect_address, "could not create outgoing connection: connect failed : fd:%i : %s", fd, std::strerror(errno)); return close_fn(); } return fd; } bool setup_socket(int fd, int family) { auto priority = runtime::network_config()->priority(); auto send_buffer_size = runtime::network_config()->send_buffer_size(); auto recv_buffer_size = runtime::network_config()->receive_buffer_size(); errno = 0; if (priority != runtime::NetworkConfig::iptos_default && !fd_set_priority(fd, family, priority)) return false; if (send_buffer_size != 0 && !fd_set_send_buffer_size(fd, send_buffer_size)) return false; if (recv_buffer_size != 0 && !fd_set_receive_buffer_size(fd, recv_buffer_size)) return false; return true; } bool filter_sockaddr(const sockaddr* sa) { if (runtime::network_config()->is_block_ipv4() && sa_is_inet(sa)) return false; if (runtime::network_config()->is_block_ipv6() && sa_is_inet6(sa)) return false; if (sa_is_v4mapped(sa)) { if (runtime::network_config()->is_block_ipv4in6()) return false; } return true; } } // namespace anonymous } // namespace torrent libtorrent-0.16.17/src/protocol/handshake_manager.h000066400000000000000000000043031522271512000222700ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_HANDSHAKE_MANAGER_H #define LIBTORRENT_NET_HANDSHAKE_MANAGER_H #include #include #include "torrent/common.h" #include "torrent/utils/unordered_vector.h" namespace torrent { class HandshakeManager : private utils::unordered_vector> { public: using base_type = utils::unordered_vector>; using base_type::size; using slot_download = std::function; // Do not connect to peers with this many or more failed chunks. static constexpr unsigned int max_failed = 3; using base_type::empty; HandshakeManager(); ~HandshakeManager(); size_type size_info(DownloadMain* info) const; void clear(); bool find(const sockaddr* sa); void erase_download(DownloadMain* info); void add_incoming(std::unique_ptr& handshake, int fd, const sockaddr* sa); void add_outgoing(const sockaddr* sa, DownloadMain* info); slot_download& slot_download_id() { return m_slot_download_id; } slot_download& slot_download_obfuscated() { return m_slot_download_obfuscated; } // This needs to be filterable slot. DownloadMain* download_info(const char* hash) { return m_slot_download_id(hash); } DownloadMain* download_info_obfuscated(const char* hash) { return m_slot_download_obfuscated(hash); } void receive_succeeded(Handshake* h); void receive_failed(Handshake* h, int message, int error); void receive_timeout(Handshake* h); static ProtocolExtension* default_extensions() { return &DefaultExtensions; } private: HandshakeManager(const HandshakeManager&) = delete; HandshakeManager& operator=(const HandshakeManager&) = delete; void create_outgoing(const sockaddr* sa, DownloadMain* info, int encryptionOptions); value_type find_and_erase(Handshake* handshake); static ProtocolExtension DefaultExtensions; slot_download m_slot_download_id; slot_download m_slot_download_obfuscated; }; } // namespace torrent #endif libtorrent-0.16.17/src/protocol/initial_seed.cc000066400000000000000000000236031522271512000214430ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include #include "download/chunk_statistics.h" #include "torrent/download/choke_group.h" #include "torrent/download/choke_queue.h" #include "torrent/peer/peer_info.h" #include "initial_seed.h" #include "peer_connection_base.h" namespace torrent { PeerInfo* const InitialSeeding::chunk_unsent = (PeerInfo*) 0; PeerInfo* const InitialSeeding::chunk_unknown = (PeerInfo*) 1; PeerInfo* const InitialSeeding::chunk_done = (PeerInfo*) 2; InitialSeeding::InitialSeeding(DownloadMain* download) : m_chunksLeft(download->file_list()->size_chunks()), m_download(download), m_peerChunks(new PeerInfo* [m_chunksLeft] {}) { } InitialSeeding::~InitialSeeding() { unblock_all(); } bool InitialSeeding::valid_peer(PeerInfo* peer) { return peer > chunk_done; } void InitialSeeding::clear_peer(PeerInfo* peer) { if (!valid_peer(peer)) return; peer->unset_flags(PeerInfo::flag_blocked); // If peer is still connected, offer new piece right away. if (peer->connection() != NULL) peer->connection()->write_insert_poll_safe(); } void InitialSeeding::chunk_seen(uint32_t index, PeerConnectionBase* pcb) { // When we have two other seeds, trust that the download will // be sufficiently seeded and switch to normal seeding. This is // mainly for when the user accidentally enables initial seeding. if (m_download->chunk_statistics()->complete() > 1) complete(pcb); PeerInfo* peer = pcb->mutable_peer_info(); PeerInfo* old = m_peerChunks[index]; // We didn't send this chunk. Is someone else initial seeding too? // Or maybe we restarted and the peer got this chunk from someone // we did send it to. Either way, we don't know who it belongs to. // Don't mark it done until we see it from someone else, though. if (old == chunk_unsent) { m_peerChunks[index] = chunk_unknown; return; } if (old == peer || old == chunk_done) return; // We've seen two peers on the swarm receive this chunk. m_peerChunks[index] = chunk_done; if (--m_chunksLeft == 0) complete(pcb); // The peer we sent it to originally may now receive another chunk. clear_peer(old); } void InitialSeeding::chunk_complete(uint32_t index, PeerConnectionBase* pcb) { clear_peer(m_peerChunks[index]); m_peerChunks[index] = chunk_unknown; chunk_seen(index, pcb); } void InitialSeeding::new_peer(PeerConnectionBase* pcb) { PeerInfo* peer = pcb->mutable_peer_info(); if (peer->is_blocked()) peer->set_flags(PeerInfo::flag_restart); // We don't go through the peer's entire bitfield here. This eliminates // cheating by sending a bogus bitfield if it figures out we are initial // seeding, to drop us out of it. We should see HAVE messages for pieces // it has that we were waiting for anyway. We will check individual chunks // as we are about to offer them, to avoid the overhead of checking each // peer's bitfield as well. If it really was cheating, the pieces it isn't // sharing will be sent during the second round of initial seeding. // If we're on the second round, don't check // it until we're about to offer a chunk. if (m_peerChunks[m_nextChunk] != chunk_unsent) return; // But during primary initial seeding (some chunks not sent at all), // check that nobody already has the next chunk we were going to send. while (m_peerChunks[m_nextChunk] == chunk_unsent && (*m_download->chunk_statistics())[m_nextChunk]) { // Could set to chunk_done if enough peers have it, but if that was the // last one it could cause initial seeding to end and all connections to // be closed, and now is a bad time for that (still being set up). Plus // this gives us the opportunity to wait for HAVE messages and resend // the chunk if it's not being shared. m_peerChunks[m_nextChunk] = chunk_unknown; find_next(false, pcb); } } uint32_t InitialSeeding::chunk_offer(PeerConnectionBase* pcb, uint32_t chunkDone) { PeerInfo* peer = pcb->mutable_peer_info(); // If this peer completely downloaded the chunk we offered and we have too // many unused upload slots, give it another chunk to download for free. if (peer->is_blocked() && chunkDone != no_offer && m_peerChunks[chunkDone] == peer && m_download->choke_group()->up_queue()->size_total() * 10 < 9 * m_download->choke_group()->up_queue()->max_unchoked()) { m_peerChunks[chunkDone] = chunk_unknown; peer->unset_flags(PeerInfo::flag_blocked); // Otherwise check if we can offer a chunk normally. } else if (peer->is_blocked()) { if (!peer->is_restart()) return no_offer; peer->unset_flags(PeerInfo::flag_restart); // Re-connection of a peer we already sent a chunk. // Offer the same chunk again. auto peerChunksEnd = m_peerChunks.get() + m_download->file_list()->size_chunks(); auto itr = std::find(m_peerChunks.get(), peerChunksEnd, peer); if (itr != peerChunksEnd) return itr - m_peerChunks.get(); // Couldn't find the chunk, we probably sent it to someone // else since the disconnection. So offer a new one. } uint32_t index = m_nextChunk; bool secondary = false; // If we already sent this chunk to someone else, we're on the second // (or more) round. We might have already found this chunk elsewhere on // the swarm since then and need to find a different one if so. if (m_peerChunks[index] != chunk_unsent) { secondary = true; // Accounting for peers whose bitfield we didn't check when connecting. // If the chunk stats say there are enough peers who have it, believe that. if (m_peerChunks[index] != chunk_done && (*m_download->chunk_statistics())[index] > 1) chunk_complete(index, pcb); if (m_peerChunks[index] == chunk_done) index = find_next(true, pcb); } // When we only have one chunk left and we already offered it // to someone who hasn't shared it yet, offer it to everyone // else. We do not override the peer we sent it to, so they // cannot be unblocked, but when initial seeding completes // everyone is unblocked anyway. if (m_chunksLeft == 1 && valid_peer(m_peerChunks[index])) { peer->set_flags(PeerInfo::flag_blocked); return index; } // Make sure we don't accidentally offer a chunk it has // already, or it would never even request it from us. // We'll just offer it to the next peer instead. if (pcb->bitfield()->get(index)) return no_offer; m_peerChunks[index] = peer; peer->set_flags(PeerInfo::flag_blocked); find_next(secondary, pcb); return index; } bool InitialSeeding::should_upload(uint32_t index) { return m_peerChunks[index] != chunk_done; } uint32_t InitialSeeding::find_next(bool secondary, PeerConnectionBase* pcb) { if (!secondary) { // Primary seeding: find next chunk not sent yet. while (++m_nextChunk < m_download->file_list()->size_chunks()) { if (m_peerChunks[m_nextChunk] == chunk_unsent) { if (!(*m_download->chunk_statistics())[m_nextChunk]) return m_nextChunk; // Someone has this one already. We don't know if we sent it or not. m_peerChunks[m_nextChunk] = chunk_unknown; } } // Went through all chunks. Continue with secondary seeding. m_nextChunk--; } // Secondary seeding: find next chunk that's not done yet. do { if (++m_nextChunk == m_download->file_list()->size_chunks()) m_nextChunk = 0; if (m_peerChunks[m_nextChunk] != chunk_done && (*m_download->chunk_statistics())[m_nextChunk] > 1) chunk_complete(m_nextChunk, pcb); } while (m_peerChunks[m_nextChunk] == chunk_done); return m_nextChunk; } void InitialSeeding::complete(PeerConnectionBase* pcb) { unblock_all(); m_chunksLeft = 0; m_nextChunk = no_offer; // We think all chunks should be well seeded now. Check to make sure. for (uint32_t i = 0; i < m_download->file_list()->size_chunks(); i++) { if (m_download->chunk_statistics()->complete() + (*m_download->chunk_statistics())[i] < 2) { // Chunk too rare, send it again before switching to normal seeding. m_chunksLeft++; m_peerChunks[i] = chunk_unsent; if (m_nextChunk == no_offer) m_nextChunk = i; } } if (m_chunksLeft) return; m_download->initial_seeding_done(pcb); } void InitialSeeding::unblock_all() { for (const auto& peer : *m_download->peer_list()) peer.second->unset_flags(PeerInfo::flag_blocked); } } // namespace torrent libtorrent-0.16.17/src/protocol/initial_seed.h000066400000000000000000000064441522271512000213110ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_PROTOCOL_INITIAL_SEED_H #define LIBTORRENT_PROTOCOL_INITIAL_SEED_H #include "download/download_main.h" namespace torrent { class InitialSeeding { public: InitialSeeding(DownloadMain* download); ~InitialSeeding(); static constexpr uint32_t no_offer = ~uint32_t(); void new_peer(PeerConnectionBase* pcb); // Chunk was seen distributed to a peer in the swarm. void chunk_seen(uint32_t index, PeerConnectionBase* pcb); // Returns chunk we may offer the peer or no_offer if none. uint32_t chunk_offer(PeerConnectionBase* pcb, uint32_t indexDone); // During the second stage (seeding rare chunks), return // false if given chunk is already well-seeded now. True otherwise. bool should_upload(uint32_t index); private: InitialSeeding(const InitialSeeding&) = delete; InitialSeeding& operator=(const InitialSeeding&) = delete; static PeerInfo* const chunk_unsent; // Chunk never sent to anyone. static PeerInfo* const chunk_unknown; // Peer has chunk, we don't know who we sent it to. static PeerInfo* const chunk_done; // Chunk properly distributed by peer. uint32_t find_next(bool secondary, PeerConnectionBase* pcb); static bool valid_peer(PeerInfo* peer); static void clear_peer(PeerInfo* peer); void chunk_complete(uint32_t index, PeerConnectionBase* pcb); void complete(PeerConnectionBase* pcb); void unblock_all(); uint32_t m_nextChunk{0}; uint32_t m_chunksLeft; DownloadMain* m_download; std::unique_ptr m_peerChunks; }; } // namespace torrent #endif libtorrent-0.16.17/src/protocol/peer_chunks.h000066400000000000000000000047751522271512000211730ustar00rootroot00000000000000#ifndef LIBTORRENT_PROTOCOL_PEER_CHUNKS_H #define LIBTORRENT_PROTOCOL_PEER_CHUNKS_H #include #include "net/throttle_node.h" #include "torrent/bitfield.h" #include "torrent/data/piece.h" #include "torrent/rate.h" #include "torrent/utils/scheduler.h" #include "utils/partial_queue.h" namespace torrent { class PeerInfo; class PeerChunks { public: using piece_list_type = std::list; bool is_seeder() const { return m_bitfield.is_all_set(); } PeerInfo* peer_info() { return m_peerInfo; } const PeerInfo* peer_info() const { return m_peerInfo; } void set_peer_info(PeerInfo* p) { m_peerInfo = p; } bool using_counter() const { return m_usingCounter; } void set_using_counter(bool state) { m_usingCounter = state; } Bitfield* bitfield() { return &m_bitfield; } const Bitfield* bitfield() const { return &m_bitfield; } auto* download_cache() { return &m_downloadCache; } auto* upload_queue() { return &m_uploadQueue; } const auto* upload_queue() const { return &m_uploadQueue; } auto* cancel_queue() { return &m_cancelQueue; } // Timer used to figure out what HAVE_PIECE messages have not been // sent. std::chrono::microseconds have_timer() const { return m_have_timer; } void set_have_timer(std::chrono::microseconds t) { m_have_timer = t; } Rate* peer_rate() { return &m_peerRate; } const Rate* peer_rate() const { return &m_peerRate; } ThrottleNode* download_throttle() { return &m_downloadThrottle; } const ThrottleNode* download_throttle() const { return &m_downloadThrottle; } ThrottleNode* upload_throttle() { return &m_uploadThrottle; } const ThrottleNode* upload_throttle() const { return &m_uploadThrottle; } private: PeerInfo* m_peerInfo{}; bool m_usingCounter{false}; Bitfield m_bitfield; utils::PartialQueue m_downloadCache; piece_list_type m_uploadQueue; piece_list_type m_cancelQueue; std::chrono::microseconds m_have_timer{}; Rate m_peerRate{600}; ThrottleNode m_downloadThrottle{30}; ThrottleNode m_uploadThrottle{30}; }; } // namespace torrent #endif libtorrent-0.16.17/src/protocol/peer_connection_base.cc000066400000000000000000001023411522271512000231530ustar00rootroot00000000000000#include "config.h" #include "peer_connection_base.h" #include #include "manager.h" #include "data/chunk_iterator.h" #include "data/chunk_list.h" #include "download/chunk_selector.h" #include "download/chunk_statistics.h" #include "download/download_main.h" #include "torrent/chunk_manager.h" #include "torrent/exceptions.h" #include "torrent/throttle.h" #include "torrent/data/block.h" #include "torrent/download/choke_group.h" #include "torrent/download/choke_queue.h" #include "torrent/download_info.h" #include "torrent/net/fd.h" #include "torrent/peer/connection_list.h" #include "torrent/peer/peer_info.h" #include "torrent/runtime/socket_manager.h" #include "torrent/utils/log.h" #include "utils/instrumentation.h" #define LT_LOG_PIECE_EVENTS(log_fmt, ...) \ lt_log_print_info(LOG_PROTOCOL_PIECE_EVENTS, this->download()->info(), "piece_events", "%40s " log_fmt, this->peer_info()->id_hex(), __VA_ARGS__); namespace torrent { static void log_mincore_stats_func(bool is_incore, bool new_index, bool& continous) { if (!new_index && is_incore) { instrumentation_update(INSTRUMENTATION_MINCORE_INCORE_TOUCHED, 1); } if (new_index && is_incore) { instrumentation_update(INSTRUMENTATION_MINCORE_INCORE_NEW, 1); } if (!new_index && !is_incore) { instrumentation_update(INSTRUMENTATION_MINCORE_NOT_INCORE_TOUCHED, 1); } if (new_index && !is_incore) { instrumentation_update(INSTRUMENTATION_MINCORE_NOT_INCORE_NEW, 1); } if (continous && !is_incore) { instrumentation_update(INSTRUMENTATION_MINCORE_INCORE_BREAK, 1); } continous = is_incore; } PeerConnectionBase::PeerConnectionBase() : m_down(new ProtocolRead()), m_up(new ProtocolWrite()) { m_peerInfo = nullptr; } PeerConnectionBase::~PeerConnectionBase() { delete m_up; delete m_down; if (m_extensions != NULL && !m_extensions->is_default()) delete m_extensions; m_extensionMessage.clear(); } void PeerConnectionBase::initialize(DownloadMain* download, PeerInfo* peerInfo, int fd, Bitfield* bitfield, EncryptionInfo* encryptionInfo, ProtocolExtension* extensions) { if (is_open()) throw internal_error("Tried to re-set PeerConnection."); if (fd < 0) throw internal_error("PeerConnectionBase::initialize() received invalid fd."); if (encryptionInfo->is_encrypted() != encryptionInfo->decrypt_valid()) throw internal_error("Encryption and decryption inconsistent."); m_fileDesc = fd; m_peerInfo = peerInfo; m_download = download; m_encryption = *encryptionInfo; m_extensions = extensions; m_extensions->set_connection(this); m_upChoke.set_entry(m_download->up_group_entry()); m_downChoke.set_entry(m_download->down_group_entry()); m_peerChunks.set_peer_info(m_peerInfo); m_peerChunks.bitfield()->swap(*bitfield); m_up->set_throttle(m_download->upload_throttle()); m_down->set_throttle(m_download->download_throttle()); m_peerChunks.upload_throttle()->set_list_iterator(m_up->throttle()->end()); m_peerChunks.upload_throttle()->slot_activate() = [this] { this_thread::poll()->insert_write(this); }; m_peerChunks.download_throttle()->set_list_iterator(m_down->throttle()->end()); m_peerChunks.download_throttle()->slot_activate() = [this] { this_thread::poll()->insert_read(this); }; request_list()->set_delegator(m_download->delegator()); request_list()->set_peer_chunks(&m_peerChunks); try { initialize_custom(); } catch (const close_connection&) { // The handshake manager closes the socket for us. m_peerInfo = nullptr; m_download = nullptr; m_extensions = nullptr; m_fileDesc = -1; return; } this_thread::poll()->open(this); this_thread::poll()->insert_read(this); this_thread::poll()->insert_write(this); this_thread::poll()->insert_error(this); m_time_last_read = this_thread::cached_time(); m_download->chunk_statistics()->received_connect(&m_peerChunks); // Hmm... cleanup? // update_interested(); m_peerChunks.download_cache()->clear(); if (!m_download->file_list()->is_done()) { m_sendInterested = true; m_downInterested = true; } } void PeerConnectionBase::cleanup() { if (!is_open()) return; if (m_download == NULL) throw internal_error("PeerConnection::~PeerConnection() m_fd is valid but m_state and/or m_net is NULL"); // TODO: Verify that transfer counter gets modified by this... m_request_list.clear(); up_chunk_release(); down_chunk_release(); m_download->info()->set_upload_unchoked(m_download->info()->upload_unchoked() - m_upChoke.unchoked()); m_download->info()->set_download_unchoked(m_download->info()->download_unchoked() - m_downChoke.unchoked()); m_download->choke_group()->up_queue()->disconnected(this, &m_upChoke); m_download->choke_group()->down_queue()->disconnected(this, &m_downChoke); m_download->chunk_statistics()->received_disconnect(&m_peerChunks); if (!m_extensions->is_default()) m_extensions->cleanup(); runtime::socket_manager()->close_event_or_throw(this, [this]() { this_thread::poll()->remove_and_close(this); fd_close(m_fileDesc); m_fileDesc = -1; }); m_up->throttle()->erase(m_peerChunks.upload_throttle()); m_down->throttle()->erase(m_peerChunks.download_throttle()); m_up->set_state(ProtocolWrite::INTERNAL_ERROR); m_down->set_state(ProtocolRead::INTERNAL_ERROR); m_download = NULL; } void PeerConnectionBase::set_upload_snubbed(bool v) { if (v) m_download->choke_group()->up_queue()->set_snubbed(this, &m_upChoke); else m_download->choke_group()->up_queue()->set_not_snubbed(this, &m_upChoke); } bool PeerConnectionBase::receive_upload_choke(bool choke) { if (choke == m_upChoke.choked()) throw internal_error("PeerConnectionBase::receive_upload_choke(...) already set to the same state."); write_insert_poll_safe(); m_sendChoked = true; m_upChoke.set_unchoked(!choke); m_upChoke.set_time_last_choke(this_thread::cached_time()); if (choke) { m_download->info()->set_upload_unchoked(m_download->info()->upload_unchoked() - 1); m_upChoke.entry()->connection_choked(this); m_upChoke.entry()->connection_queued(this); m_download->choke_group()->up_queue()->modify_currently_unchoked(-1); m_download->choke_group()->up_queue()->modify_currently_queued(1); } else { m_download->info()->set_upload_unchoked(m_download->info()->upload_unchoked() + 1); m_upChoke.entry()->connection_unqueued(this); m_upChoke.entry()->connection_unchoked(this); m_download->choke_group()->up_queue()->modify_currently_unchoked(1); m_download->choke_group()->up_queue()->modify_currently_queued(-1); } return true; } bool PeerConnectionBase::receive_download_choke(bool choke) { if (choke == m_downChoke.choked()) throw internal_error("PeerConnectionBase::receive_download_choke(...) already set to the same state."); write_insert_poll_safe(); m_downChoke.set_unchoked(!choke); m_downChoke.set_time_last_choke(this_thread::cached_time()); if (choke) { m_download->info()->set_download_unchoked(m_download->info()->download_unchoked() - 1); m_downChoke.entry()->connection_choked(this); m_downChoke.entry()->connection_queued(this); m_download->choke_group()->down_queue()->modify_currently_unchoked(-1); m_download->choke_group()->down_queue()->modify_currently_queued(1); } else { m_download->info()->set_download_unchoked(m_download->info()->download_unchoked() + 1); m_downChoke.entry()->connection_unqueued(this); m_downChoke.entry()->connection_unchoked(this); m_download->choke_group()->down_queue()->modify_currently_unchoked(1); m_download->choke_group()->down_queue()->modify_currently_queued(-1); } if (choke) { m_peerChunks.download_cache()->disable(); // If the queue isn't empty, then we might still receive some // pieces, so don't remove us from throttle or release the chunk. if (!request_list()->is_downloading() && request_list()->queued_empty()) { m_down->throttle()->erase(m_peerChunks.download_throttle()); down_chunk_release(); } // Send uninterested if unchoked, but only _after_ receiving our // chunks? if (m_downUnchoked) { // Tell the peer we're no longer interested to avoid // disconnects. We keep the connection in the queue so that // ChokeManager::cycle(...) can attempt to get us unchoked // again. m_sendInterested = m_downInterested; m_downInterested = false; } else { // Remove from queue so that an unchoke from the remote peer // will cause the connection to be unchoked immediately by the // choke manager. // // TODO: This doesn't seem safe... m_download->choke_group()->down_queue()->set_not_queued(this, &m_downChoke); return false; } } else { m_tryRequest = true; if (!m_downInterested) { // We were marked as not interested by the cycling choke and // kept in the queue, thus the peer should have some pieces of // interest. // // We have now been 'unchoked' by the choke manager, so tell the // peer that we're again interested. If the peer doesn't unchoke // us within a cycle or two we're likely to be choked and left // out of the queue. So if the peer unchokes us at a later time, // we skip the queue and unchoke immediately. m_sendInterested = !m_downInterested; m_downInterested = true; } } return true; } void PeerConnectionBase::load_up_chunk() { if (m_upChunk.is_valid() && m_upChunk.index() == m_upPiece.index()) { // Better checking needed. // m_upChunk.chunk()->preload(m_upPiece.offset(), m_upChunk.chunk()->size()); if (lt_log_is_valid(LOG_INSTRUMENTATION_MINCORE)) log_mincore_stats_func(m_upChunk.chunk()->is_incore(m_upPiece.offset(), m_upPiece.length()), false, m_incoreContinous); return; } up_chunk_release(); m_upChunk = m_download->chunk_list()->get(m_upPiece.index(), ChunkList::get_not_hashing); if (!m_upChunk.is_valid()) throw storage_error("File chunk read error: " + std::string(std::strerror(m_upChunk.error_number()))); if (is_encrypted() && m_encryptBuffer == nullptr) { m_encryptBuffer = std::make_unique(); m_encryptBuffer->reset(); } m_incoreContinous = false; if (lt_log_is_valid(LOG_INSTRUMENTATION_MINCORE)) log_mincore_stats_func(m_upChunk.chunk()->is_incore(m_upPiece.offset(), m_upPiece.length()), true, m_incoreContinous); m_incoreContinous = true; // Also check if we've already preloaded in the recent past, even // past unmaps. ChunkManager* cm = manager->chunk_manager(); uint32_t preloadSize = m_upChunk.chunk()->chunk_size() - m_upPiece.offset(); if (cm->preload_type() == 0 || m_upChunk.object()->time_preloaded() >= this_thread::cached_time() - 60s || preloadSize < cm->preload_min_size() || m_peerChunks.upload_throttle()->rate()->rate() < cm->preload_required_rate() * ((preloadSize + (2 << 20) - 1) / (2 << 20))) { cm->inc_stats_not_preloaded(); return; } cm->inc_stats_preloaded(); m_upChunk.object()->set_time_preloaded(this_thread::cached_time()); m_upChunk.chunk()->preload(m_upPiece.offset(), m_upChunk.chunk()->chunk_size(), cm->preload_type() == 1); } void PeerConnectionBase::cancel_transfer(BlockTransfer* transfer) { if (!is_open()) throw internal_error("PeerConnectionBase::cancel_transfer(...) !is_open()"); if (transfer->peer_info() != peer_info()) throw internal_error("PeerConnectionBase::cancel_transfer(...) peer info doesn't match"); // We don't send cancel messages if the transfer has already // started. if (transfer == m_request_list.transfer()) return; write_insert_poll_safe(); m_peerChunks.cancel_queue()->push_back(transfer->piece()); } void PeerConnectionBase::event_error() { m_download->connection_list()->erase(this, 0); } bool PeerConnectionBase::should_connection_unchoke(choke_queue* cq) const { if (cq == m_download->choke_group()->up_queue()) return m_download->info()->upload_unchoked() < m_download->up_group_entry()->max_slots(); if (cq == m_download->choke_group()->down_queue()) return m_download->info()->download_unchoked() < m_download->down_group_entry()->max_slots(); return true; } bool PeerConnectionBase::down_chunk_start(const Piece& piece) { if (!request_list()->downloading(piece)) { if (piece.length() == 0) { LT_LOG_PIECE_EVENTS("(down) skipping_empty %" PRIu32 " %" PRIu32 " %" PRIu32, piece.index(), piece.offset(), piece.length()); } else { LT_LOG_PIECE_EVENTS("(down) skipping_unneeded %" PRIu32 " %" PRIu32 " %" PRIu32, piece.index(), piece.offset(), piece.length()); } return false; } if (!m_download->file_list()->is_valid_piece(piece)) throw internal_error("Incoming pieces list contains a bad piece."); if (!m_downChunk.is_valid() || piece.index() != m_downChunk.index()) { down_chunk_release(); m_downChunk = m_download->chunk_list()->get(piece.index(), ChunkList::get_not_hashing | ChunkList::get_writable); if (!m_downChunk.is_valid()) throw storage_error("File chunk write error: " + std::string(std::strerror(m_downChunk.error_number()))); } LT_LOG_PIECE_EVENTS("(down) %s %" PRIu32 " %" PRIu32 " %" PRIu32, request_list()->transfer()->is_leader() ? "started_on" : "skipping_partial", piece.index(), piece.offset(), piece.length()); return request_list()->transfer()->is_leader(); } void PeerConnectionBase::down_chunk_finished() { if (!request_list()->transfer()->is_finished()) throw internal_error("PeerConnectionBase::down_chunk_finished() Transfer not finished."); BlockTransfer* transfer = request_list()->transfer(); LT_LOG_PIECE_EVENTS("(down) %s %" PRIu32 " %" PRIu32 " %" PRIu32, transfer->is_leader() ? "completed " : "skipped ", transfer->piece().index(), transfer->piece().offset(), transfer->piece().length()); if (transfer->is_leader()) { if (!m_downChunk.is_valid()) throw internal_error("PeerConnectionBase::down_chunk_finished() Transfer is the leader, but no chunk allocated."); request_list()->finished(); m_downChunk.object()->set_time_modified(this_thread::cached_time()); } else { request_list()->skipped(); } if (m_downStall > 0) m_downStall--; // We need to release chunks when we're not sure if they will be // used in the near future so as to avoid hitting the address space // limit in high-bandwidth situations. // // Some tweaking of the pipe size might be necessary if the queue // empties too often. if (m_downChunk.is_valid() && (request_list()->queued_empty() || m_downChunk.index() != request_list()->next_queued_piece().index())) down_chunk_release(); // If we were choked by choke_manager but still had queued pieces, // then we might still be in the throttle. if (m_downChoke.choked() && request_list()->queued_empty()) m_down->throttle()->erase(m_peerChunks.download_throttle()); write_insert_poll_safe(); } bool PeerConnectionBase::down_chunk() { if (!m_down->throttle()->is_throttled(m_peerChunks.download_throttle())) throw internal_error("PeerConnectionBase::down_chunk() tried to read a piece but is not in throttle list"); if (!m_downChunk.chunk()->is_writable()) throw internal_error("PeerConnectionBase::down_part() chunk not writable, permission denided"); uint32_t quota = m_down->throttle()->node_quota(m_peerChunks.download_throttle()); if (quota == 0) { this_thread::poll()->remove_read(this); m_down->throttle()->node_deactivate(m_peerChunks.download_throttle()); return false; } uint32_t bytesTransfered = 0; BlockTransfer* transfer = m_request_list.transfer(); Chunk::data_type data; ChunkIterator itr(m_downChunk.chunk(), transfer->piece().offset() + transfer->position(), transfer->piece().offset() + std::min(transfer->position() + quota, transfer->piece().length())); do { data = itr.data(); data.second = read_stream_throws(data.first, data.second); if (is_encrypted()) m_encryption.decrypt(data.first, data.second); bytesTransfered += data.second; } while (data.second != 0 && itr.forward(data.second)); transfer->adjust_position(bytesTransfered); m_down->throttle()->node_used(m_peerChunks.download_throttle(), bytesTransfered); m_download->info()->mutable_down_rate()->insert(bytesTransfered); return transfer->is_finished(); } bool PeerConnectionBase::down_chunk_from_buffer() { m_down->buffer()->consume(down_chunk_process(m_down->buffer()->position(), m_down->buffer()->remaining())); if (!m_request_list.transfer()->is_finished() && m_down->buffer()->remaining() != 0) throw internal_error("PeerConnectionBase::down_chunk_from_buffer() !transfer->is_finished() && m_down->buffer()->remaining() != 0."); return m_request_list.transfer()->is_finished(); } // When this transfer again becomes the leader, we just return false // and wait for the next polling. It is an exceptional case so we // don't really care that much about performance. bool PeerConnectionBase::down_chunk_skip() { ThrottleList* throttle = m_down->throttle(); if (!throttle->is_throttled(m_peerChunks.download_throttle())) throw internal_error("PeerConnectionBase::down_chunk_skip() tried to read a piece but is not in throttle list"); uint32_t quota = throttle->node_quota(m_peerChunks.download_throttle()); if (quota == 0) { this_thread::poll()->remove_read(this); throttle->node_deactivate(m_peerChunks.download_throttle()); return false; } uint32_t length = read_stream_throws(m_nullBuffer, std::min(quota, m_request_list.transfer()->piece().length() - m_request_list.transfer()->position())); throttle->node_used(m_peerChunks.download_throttle(), length); if (is_encrypted()) m_encryption.decrypt(m_nullBuffer, length); if (down_chunk_skip_process(m_nullBuffer, length) != length) throw internal_error("PeerConnectionBase::down_chunk_skip() down_chunk_skip_process(m_nullBuffer, length) != length."); return m_request_list.transfer()->is_finished(); } bool PeerConnectionBase::down_chunk_skip_from_buffer() { m_down->buffer()->consume(down_chunk_skip_process(m_down->buffer()->position(), m_down->buffer()->remaining())); return m_request_list.transfer()->is_finished(); } // Process data from a leading transfer. uint32_t PeerConnectionBase::down_chunk_process(const void* buffer, uint32_t length) { if (!m_downChunk.is_valid() || m_downChunk.index() != m_request_list.transfer()->index()) throw internal_error("PeerConnectionBase::down_chunk_process(...) !m_downChunk.is_valid() || m_downChunk.index() != m_request_list.transfer()->index()."); if (length == 0) return length; BlockTransfer* transfer = m_request_list.transfer(); length = std::min(transfer->piece().length() - transfer->position(), length); m_downChunk.chunk()->from_buffer(buffer, transfer->piece().offset() + transfer->position(), length); transfer->adjust_position(length); m_down->throttle()->node_used(m_peerChunks.download_throttle(), length); m_download->info()->mutable_down_rate()->insert(length); return length; } // Process data from non-leading transfer. If this transfer encounters // mismatching data with the leader then bork this transfer. If we get // ahead of the leader, we switch the leader. uint32_t PeerConnectionBase::down_chunk_skip_process(const void* buffer, uint32_t length) { BlockTransfer* transfer = m_request_list.transfer(); // Adjust 'length' to be less than or equal to what is remaining of // the block to simplify the rest of the function. length = std::min(length, transfer->piece().length() - transfer->position()); // Hmm, this might result in more bytes than nessesary being // counted. m_down->throttle()->node_used(m_peerChunks.download_throttle(), length); m_download->info()->mutable_down_rate()->insert(length); m_download->info()->mutable_skip_rate()->insert(length); if (!transfer->is_valid()) { transfer->adjust_position(length); return length; } if (!transfer->block()->is_transfering()) throw internal_error("PeerConnectionBase::down_chunk_skip_process(...) block is not transferring, yet we have non-leaders."); // Temporary test. if (transfer->position() > transfer->block()->leader()->position()) throw internal_error("PeerConnectionBase::down_chunk_skip_process(...) transfer is past the Block's position."); // If the transfer is valid, compare the downloaded data to the // leader. uint32_t compareLength = std::min(length, transfer->block()->leader()->position() - transfer->position()); // The data doesn't match with what has previously been downloaded, // bork this transfer. if (!m_downChunk.chunk()->compare_buffer(buffer, transfer->piece().offset() + transfer->position(), compareLength)) { LT_LOG_PIECE_EVENTS("(down) download_data_mismatch %" PRIu32 " %" PRIu32 " %" PRIu32, transfer->piece().index(), transfer->piece().offset(), transfer->piece().length()); m_request_list.transfer_dissimilar(); m_request_list.transfer()->adjust_position(length); return length; } transfer->adjust_position(compareLength); if (compareLength == length) return length; // Add another check here to see if we really want to be the new // leader. transfer->block()->change_leader(transfer); if (down_chunk_process(static_cast(buffer) + compareLength, length - compareLength) != length - compareLength) throw internal_error("PeerConnectionBase::down_chunk_skip_process(...) down_chunk_process(...) returned wrong value."); return length; } bool PeerConnectionBase::down_extension() { if (m_down->buffer()->remaining()) { uint32_t need = std::min(m_extensions->read_need(), static_cast(m_down->buffer()->remaining())); std::memcpy(m_extensions->read_position(), m_down->buffer()->position(), need); m_extensions->read_move(need); m_down->buffer()->consume(need); } if (!m_extensions->is_complete()) { uint32_t bytes = read_stream_throws(m_extensions->read_position(), m_extensions->read_need()); m_down->throttle()->node_used_unthrottled(bytes); if (is_encrypted()) m_encryption.decrypt(m_extensions->read_position(), bytes); m_extensions->read_move(bytes); } // If extension can't be processed yet (due to a pending write), // disable reads until the pending message is completely sent. if (m_extensions->is_complete() && !m_extensions->is_invalid() && !m_extensions->read_done()) { this_thread::poll()->remove_read(this); return false; } return m_extensions->is_complete(); } inline uint32_t PeerConnectionBase::up_chunk_encrypt(uint32_t quota) { if (m_encryptBuffer == nullptr) throw internal_error("PeerConnectionBase::up_chunk: m_encryptBuffer is NULL."); if (quota <= m_encryptBuffer->remaining()) return quota; // Also, consider checking here if the number of bytes remaining in // the buffer is small enought that the cost of moving them would // outweigh the extra context switches, etc. if (m_encryptBuffer->remaining() == 0) { // This handles reset also for new chunk transfers. m_encryptBuffer->reset(); quota = std::min(quota, m_encryptBuffer->reserved()); } else { quota = std::min(quota - m_encryptBuffer->remaining(), m_encryptBuffer->reserved_left()); } m_upChunk.chunk()->to_buffer(m_encryptBuffer->end(), m_upPiece.offset() + m_encryptBuffer->remaining(), quota); m_encryption.encrypt(m_encryptBuffer->end(), quota); m_encryptBuffer->move_end(quota); return m_encryptBuffer->remaining(); } bool PeerConnectionBase::up_chunk() { if (!m_up->throttle()->is_throttled(m_peerChunks.upload_throttle())) throw internal_error("PeerConnectionBase::up_chunk() tried to write a piece but is not in throttle list"); if (!m_upChunk.chunk()->is_readable()) throw internal_error("ProtocolChunk::write_part() chunk not readable, permission denided"); uint32_t quota = m_up->throttle()->node_quota(m_peerChunks.upload_throttle()); if (quota == 0) { this_thread::poll()->remove_write(this); m_up->throttle()->node_deactivate(m_peerChunks.upload_throttle()); return false; } uint32_t bytesTransfered = 0; if (is_encrypted()) { // Prepare as many bytes as quota specifies, up to end of piece or // buffer. Only bytes beyond remaining() are new and will be // encrypted. quota = up_chunk_encrypt(std::min(quota, m_upPiece.length())); bytesTransfered = write_stream_throws(m_encryptBuffer->position(), quota); m_encryptBuffer->consume(bytesTransfered); } else { Chunk::data_type data; ChunkIterator itr(m_upChunk.chunk(), m_upPiece.offset(), m_upPiece.offset() + std::min(quota, m_upPiece.length())); do { data = itr.data(); data.second = write_stream_throws(data.first, data.second); bytesTransfered += data.second; } while (data.second != 0 && itr.forward(data.second)); } m_up->throttle()->node_used(m_peerChunks.upload_throttle(), bytesTransfered); m_download->info()->mutable_up_rate()->insert(bytesTransfered); // Just modifying the piece to cover the remaining data ends up // being much cleaner and we avoid an unnessesary position variable. m_upPiece.set_offset(m_upPiece.offset() + bytesTransfered); m_upPiece.set_length(m_upPiece.length() - bytesTransfered); return m_upPiece.length() == 0; } bool PeerConnectionBase::up_extension() { if (m_extensionOffset == extension_must_encrypt) { if (m_extensionMessage.owned()) { m_encryption.encrypt(m_extensionMessage.data(), m_extensionMessage.length()); } else { auto buffer = new char[m_extensionMessage.length()]; m_encryption.encrypt(m_extensionMessage.data(), buffer, m_extensionMessage.length()); m_extensionMessage.set(buffer, buffer + m_extensionMessage.length(), true); } m_extensionOffset = 0; } if (m_extensionOffset >= m_extensionMessage.length()) throw internal_error("PeerConnectionBase::up_extension bad offset."); uint32_t written = write_stream_throws(m_extensionMessage.data() + m_extensionOffset, m_extensionMessage.length() - m_extensionOffset); m_up->throttle()->node_used_unthrottled(written); m_extensionOffset += written; if (m_extensionOffset < m_extensionMessage.length()) return false; m_extensionMessage.clear(); // If we have an unprocessed message, process it now and enable reads again. if (m_extensions->is_complete() && !m_extensions->is_invalid()) { // DEBUG: What, this should fail when we block, no? if (!m_extensions->read_done()) throw internal_error("PeerConnectionBase::up_extension could not process complete extension message."); this_thread::poll()->insert_read(this); } return true; } void PeerConnectionBase::down_chunk_release() { if (m_downChunk.is_valid()) m_download->chunk_list()->release(&m_downChunk, ChunkList::release_default); } void PeerConnectionBase::up_chunk_release() { if (m_upChunk.is_valid()) m_download->chunk_list()->release(&m_upChunk, ChunkList::release_default); } void PeerConnectionBase::read_request_piece(const Piece& p) { auto itr = std::find(m_peerChunks.upload_queue()->begin(), m_peerChunks.upload_queue()->end(), p); if (m_upChoke.choked() || itr != m_peerChunks.upload_queue()->end() || p.length() > (1 << 17)) { LT_LOG_PIECE_EVENTS("(up) request_ignored %" PRIu32 " %" PRIu32 " %" PRIu32, p.index(), p.offset(), p.length()); return; } m_peerChunks.upload_queue()->push_back(p); write_insert_poll_safe(); LT_LOG_PIECE_EVENTS("(up) request_added %" PRIu32 " %" PRIu32 " %" PRIu32, p.index(), p.offset(), p.length()); } void PeerConnectionBase::read_cancel_piece(const Piece& p) { auto itr = std::find(m_peerChunks.upload_queue()->begin(), m_peerChunks.upload_queue()->end(), p); if (itr != m_peerChunks.upload_queue()->end()) { m_peerChunks.upload_queue()->erase(itr); LT_LOG_PIECE_EVENTS("(up) cancel_requested %" PRIu32 " %" PRIu32 " %" PRIu32, p.index(), p.offset(), p.length()); } else { LT_LOG_PIECE_EVENTS("(up) cancel_ignored %" PRIu32 " %" PRIu32 " %" PRIu32, p.index(), p.offset(), p.length()); } } void PeerConnectionBase::write_prepare_piece() { m_upPiece = m_peerChunks.upload_queue()->front(); m_peerChunks.upload_queue()->pop_front(); // Move these checks somewhere else? if (!m_download->file_list()->is_valid_piece(m_upPiece) || !m_download->file_list()->bitfield()->get(m_upPiece.index())) { char buffer[128]; snprintf(buffer, 128, "Peer requested an invalid piece: %u %u %u", m_upPiece.index(), m_upPiece.length(), m_upPiece.offset()); LT_LOG_PIECE_EVENTS("(up) invalid_piece_in_upload_queue %" PRIu32 " %" PRIu32 " %" PRIu32, m_upPiece.index(), m_upPiece.length(), m_upPiece.offset()); throw communication_error(buffer); } m_up->write_piece(m_upPiece); LT_LOG_PIECE_EVENTS("(up) prepared %" PRIu32 " %" PRIu32 " %" PRIu32, m_upPiece.index(), m_upPiece.length(), m_upPiece.offset()); } void PeerConnectionBase::write_prepare_extension(int type, const DataBuffer& message) { m_up->write_extension(m_extensions->id(type), message.length()); m_extensionOffset = 0; m_extensionMessage = message; // Need to encrypt the buffer, but not until the m_up // write buffer has been flushed, so flag it for now. if (is_encrypted()) m_extensionOffset = extension_must_encrypt; } // High stall count peers should request if we're *not* in endgame, or // if we're in endgame and the download is too slow. Prefere not to request // from high stall counts when we are doing decent speeds. bool PeerConnectionBase::should_request() { if (m_downChoke.choked() || !m_downInterested || !m_downUnchoked) // || m_down->get_state() == ProtocolRead::READ_SKIP_PIECE) return false; else if (!m_download->delegator()->get_aggressive()) return true; else // We check if the peer is stalled, if it is not then we should // request. If the peer is stalled then we only request if the // download rate is below a certain value. return m_downStall <= 1 || m_download->info()->down_rate()->rate() < (10 << 10); } bool PeerConnectionBase::try_request_pieces() { if (request_list()->queued_empty()) m_downStall = 0; uint32_t pipeSize = request_list()->calculate_pipe_size(m_peerChunks.download_throttle()->rate()->rate()); // Don't start requesting if we can't do it in large enough chunks. if (request_list()->pipe_size() >= (pipeSize + 10) / 2) return false; bool success = false; while (request_list()->queued_size() < pipeSize && m_up->can_write_request()) { // It should get the right number the first time around, but loop just to be sure int maxRequests = m_up->max_write_request(); int maxQueued = pipeSize - request_list()->queued_size(); int maxPieces = std::max(std::min(maxRequests, maxQueued), 1); std::vector pieces = request_list()->delegate(maxPieces); if (pieces.empty()) { return false; } for (auto& p : pieces) { if (!m_download->file_list()->is_valid_piece(*p) || !m_peerChunks.bitfield()->get(p->index())) throw internal_error("PeerConnectionBase::try_request_pieces() tried to use an invalid piece."); m_up->write_request(*p); LT_LOG_PIECE_EVENTS("(down) requesting %" PRIu32 " %" PRIu32 " %" PRIu32, p->index(), p->offset(), p->length()); success = true; } } return success; } // Send one peer exchange message according to bits set in m_sendPEXMask. // We can only send one message at a time, because the derived class // needs to flush the buffer and call up_extension before the next one. bool PeerConnectionBase::send_pex_message() { if (!m_extensions->is_remote_supported(ProtocolExtension::UT_PEX)) { m_sendPEXMask = 0; return false; } // Message to tell peer to stop/start doing PEX is small so send it first. if (m_sendPEXMask & (PEX_ENABLE | PEX_DISABLE)) { if (!m_extensions->is_remote_supported(ProtocolExtension::UT_PEX)) throw internal_error("PeerConnectionBase::send_pex_message() Not supported by peer."); write_prepare_extension(ProtocolExtension::HANDSHAKE, ProtocolExtension::generate_toggle_message(ProtocolExtension::UT_PEX, (m_sendPEXMask & PEX_ENABLE) != 0)); m_sendPEXMask &= ~(PEX_ENABLE | PEX_DISABLE); } else if (m_sendPEXMask & PEX_DO && m_extensions->id(ProtocolExtension::UT_PEX)) { const DataBuffer& pexMessage = m_download->get_ut_pex(m_extensions->is_initial_pex()); m_extensions->clear_initial_pex(); m_sendPEXMask &= ~PEX_DO; if (pexMessage.empty()) return false; write_prepare_extension(ProtocolExtension::UT_PEX, pexMessage); } else { m_sendPEXMask = 0; } return true; } // Extension protocol needs to send a reply. bool PeerConnectionBase::send_ext_message() { write_prepare_extension(m_extensions->pending_message_type(), m_extensions->pending_message_data()); m_extensions->clear_pending_message(); return true; } void PeerConnectionBase::receive_metadata_piece([[maybe_unused]] uint32_t piece, [[maybe_unused]] const char* data, [[maybe_unused]] uint32_t length) { } } // namespace torrent libtorrent-0.16.17/src/protocol/peer_connection_base.h000066400000000000000000000201771522271512000230230ustar00rootroot00000000000000#ifndef LIBTORRENT_PROTOCOL_PEER_CONNECTION_BASE_H #define LIBTORRENT_PROTOCOL_PEER_CONNECTION_BASE_H #include "thread_main.h" #include "data/chunk_handle.h" #include "net/socket_stream.h" #include "protocol/encryption_info.h" #include "protocol/extensions.h" #include "protocol/peer_chunks.h" #include "protocol/protocol_base.h" #include "protocol/request_list.h" #include "torrent/system/poll.h" #include "torrent/peer/choke_status.h" #include "torrent/peer/peer.h" namespace torrent { // Base class for peer connection classes. Rename to PeerConnection // when the migration is complete? // // This should really be modularized abit, there's too much stuff in // PeerConnectionBase and its children. Do we use additional layers of // inheritance or member instances? class choke_queue; class DownloadMain; class PeerConnectionBase : public Peer, public SocketStream { public: using ProtocolRead = ProtocolBase; using ProtocolWrite = ProtocolBase; #if USE_EXTRA_DEBUG == 666 // For testing, use a really small buffer. typedef ProtocolBuffer<256> EncryptBuffer; #else using EncryptBuffer = ProtocolBuffer<16384>; #endif // Find an optimal number for this. static constexpr uint32_t read_size = 64; // Bitmasks for peer exchange messages to send. static constexpr int PEX_DO = (1 << 0); static constexpr int PEX_ENABLE = (1 << 1); static constexpr int PEX_DISABLE = (1 << 2); PeerConnectionBase(); ~PeerConnectionBase() override; const char* type_name() const override { return "pcb"; } void initialize(DownloadMain* download, PeerInfo* p, int fd, Bitfield* bitfield, EncryptionInfo* encryptionInfo, ProtocolExtension* extensions); void cleanup(); bool is_up_choked() const { return m_upChoke.choked(); } bool is_up_interested() const { return m_upChoke.queued(); } bool is_up_snubbed() const { return m_upChoke.snubbed(); } bool is_down_queued() const { return m_downChoke.queued(); } bool is_down_local_unchoked() const { return m_downChoke.unchoked(); } bool is_down_remote_unchoked() const { return m_downUnchoked; } bool is_down_interested() const { return m_downInterested; } void set_upload_snubbed(bool v); bool is_seeder() const { return m_peerChunks.is_seeder(); } bool is_not_seeder() const { return !m_peerChunks.is_seeder(); } bool is_encrypted() const { return m_encryption.is_encrypted(); } bool is_obfuscated() const { return m_encryption.is_obfuscated(); } PeerInfo* mutable_peer_info() { return m_peerInfo; } PeerChunks* peer_chunks() { return &m_peerChunks; } const PeerChunks* c_peer_chunks() const { return &m_peerChunks; } choke_status* up_choke() { return &m_upChoke; } choke_status* down_choke() { return &m_downChoke; } DownloadMain* download() { return m_download; } RequestList* request_list() { return &m_request_list; } const RequestList* request_list() const { return &m_request_list; } ProtocolExtension* extensions() { return m_extensions; } DataBuffer* extension_message() { return &m_extensionMessage; } void do_peer_exchange() { m_sendPEXMask |= PEX_DO; } inline void set_peer_exchange(bool state); // These must be implemented by the child class. virtual void initialize_custom() = 0; virtual void update_interested() = 0; virtual bool receive_keepalive() = 0; bool receive_upload_choke(bool choke); bool receive_download_choke(bool choke); void event_error() override; void push_unread(const void* data, uint32_t size); void cancel_transfer(BlockTransfer* transfer); // Insert into the poll unless we're blocking for throttling etc. void read_insert_poll_safe(); void write_insert_poll_safe(); // Communication with the protocol extensions virtual void receive_metadata_piece(uint32_t piece, const char* data, uint32_t length); bool should_connection_unchoke(choke_queue* cq) const; protected: static constexpr uint32_t extension_must_encrypt = ~uint32_t(); inline bool read_remaining(); inline bool write_remaining(); void load_up_chunk(); void read_request_piece(const Piece& p); void read_cancel_piece(const Piece& p); void write_prepare_piece(); void write_prepare_extension(int type, const DataBuffer& message); bool down_chunk_start(const Piece& p); void down_chunk_finished(); bool down_chunk(); bool down_chunk_from_buffer(); bool down_chunk_skip(); bool down_chunk_skip_from_buffer(); uint32_t down_chunk_process(const void* buffer, uint32_t length); uint32_t down_chunk_skip_process(const void* buffer, uint32_t length); bool down_extension(); bool up_chunk(); inline uint32_t up_chunk_encrypt(uint32_t quota); bool up_extension(); void down_chunk_release(); void up_chunk_release(); bool should_request(); bool try_request_pieces(); bool send_pex_message(); bool send_ext_message(); DownloadMain* m_download{}; ProtocolRead* m_down; ProtocolWrite* m_up; PeerChunks m_peerChunks; RequestList m_request_list; ChunkHandle m_downChunk; uint32_t m_downStall{0}; Piece m_upPiece; ChunkHandle m_upChunk; // The interested state no longer follows the spec's wording as it // has been swapped. // // Thus the same ProtocolBase object now groups the same choke and // interested states togheter, thus for m_up 'interested' means the // remote peer wants upload and 'choke' means we've choked upload to // that peer. // // In the downlod object, 'queued' now means the same as the spec's // 'unchoked', while 'unchoked' means we start requesting pieces. choke_status m_upChoke; choke_status m_downChoke; bool m_downInterested{false}; bool m_downUnchoked{false}; bool m_sendChoked{false}; bool m_sendInterested{false}; bool m_tryRequest{true}; int m_sendPEXMask{0}; std::chrono::microseconds m_time_last_read{}; DataBuffer m_extensionMessage; uint32_t m_extensionOffset; std::unique_ptr m_encryptBuffer; EncryptionInfo m_encryption; ProtocolExtension* m_extensions{}; bool m_incoreContinous{false}; }; inline void PeerConnectionBase::set_peer_exchange(bool state) { if (m_extensions->is_default() || !m_extensions->is_remote_supported(ProtocolExtension::UT_PEX)) return; if (state) { m_sendPEXMask = PEX_ENABLE | (m_sendPEXMask & ~PEX_DISABLE); m_extensions->set_local_enabled(ProtocolExtension::UT_PEX); } else { m_sendPEXMask = PEX_DISABLE | (m_sendPEXMask & ~PEX_ENABLE); m_extensions->unset_local_enabled(ProtocolExtension::UT_PEX); } } inline void PeerConnectionBase::push_unread(const void* data, uint32_t size) { std::memcpy(m_down->buffer()->end(), data, size); m_down->buffer()->move_end(size); } inline void PeerConnectionBase::read_insert_poll_safe() { if (m_down->get_state() != ProtocolRead::IDLE) return; this_thread::poll()->insert_read(this); } inline void PeerConnectionBase::write_insert_poll_safe() { if (m_up->get_state() != ProtocolWrite::IDLE) return; this_thread::poll()->insert_write(this); } } // namespace torrent #endif libtorrent-0.16.17/src/protocol/peer_connection_leech.cc000066400000000000000000000555341522271512000233340ustar00rootroot00000000000000#include "config.h" #include #include #include "download/chunk_selector.h" #include "download/chunk_statistics.h" #include "download/download_main.h" #include "manager.h" #include "torrent/download/choke_group.h" #include "torrent/download/choke_queue.h" #include "torrent/download_info.h" #include "torrent/net/socket_address.h" #include "torrent/peer/connection_list.h" #include "torrent/peer/peer_info.h" #include "torrent/runtime/runtime.h" #include "torrent/utils/log.h" #include "torrent/utils/uri_parser.h" #include "extensions.h" #include "initial_seed.h" #include "peer_connection_leech.h" #define LT_LOG_NETWORK_ERRORS(log_fmt, ...) \ lt_log_print_info(LOG_PROTOCOL_NETWORK_ERRORS, this->download()->info(), "network_errors", "%40s " log_fmt, this->peer_info()->id_hex(), __VA_ARGS__); #define LT_LOG_STORAGE_ERRORS(log_fmt, ...) \ lt_log_print_info(LOG_PROTOCOL_STORAGE_ERRORS, this->download()->info(), "storage_errors", "%40s " log_fmt, this->peer_info()->id_hex(), __VA_ARGS__); namespace torrent { #if 0 template PeerConnection::~PeerConnection() { // if (m_download != NULL && m_down->get_state() != ProtocolRead::READ_BITFIELD) // m_download->bitfield_counter().dec(m_peerChunks.bitfield()->bitfield()); // priority_queue_erase(&taskScheduler, &m_taskSendChoke); } #else template PeerConnection::~PeerConnection() = default; #endif template void PeerConnection::initialize_custom() { if (type == Download::CONNECTION_INITIAL_SEED) { if (m_download->initial_seeding() == NULL) throw close_connection(); m_download->initial_seeding()->new_peer(this); } // if (m_download->content()->chunks_completed() != 0) { // m_up->write_bitfield(m_download->file_list()->bitfield()->size_bytes()); // m_up->buffer()->prepare_end(); // m_up->set_position(0); // m_up->set_state(ProtocolWrite::WRITE_BITFIELD_HEADER); // } } template void PeerConnection::update_interested() { if (type != Download::CONNECTION_LEECH) return; m_peerChunks.download_cache()->clear(); if (m_downInterested) return; // Consider just setting to interested without checking the // bitfield. The status might change by the time we get unchoked // anyway. m_sendInterested = !m_downInterested; m_downInterested = true; // Hmm... does this belong here, or should we insert ourselves into // the queue when we receive the unchoke? // m_download->choke_group()->down_queue()->set_queued(this, &m_downChoke); } template bool PeerConnection::receive_keepalive() { if (this_thread::cached_time() - m_time_last_read > 240s) return false; // There's no point in adding ourselves to the write poll if the // buffer is full, as that will already have been taken care of. if (m_up->get_state() == ProtocolWrite::IDLE && m_up->can_write_keepalive()) { write_insert_poll_safe(); ProtocolBuffer<512>::iterator old_end = m_up->buffer()->end(); m_up->write_keepalive(); if (is_encrypted()) m_encryption.encrypt(old_end, m_up->buffer()->end() - old_end); } if (type != Download::CONNECTION_LEECH) return true; m_tryRequest = true; // Stall pieces when more than one receive_keepalive() has been // called while a single piece is downloading. // // m_downStall is decremented for every successfull download, so it // should stay at zero or one when downloading at an acceptable // speed. Thus only when m_downStall >= 2 is the download actually // stalling. // // If more than 6 ticks have gone by, assume the peer forgot about // our requests or tried to cheat with empty piece messages, and try // again. // TODO: Do we need to remove from download throttle here? // // Do we also need to remove from download throttle? Check how it // worked again. // if (request_list()->empty()) // return true; if (m_downStall >= 2) request_list()->stall_prolonged(); else if (m_downStall++ != 0) request_list()->stall_initial(); return true; } // We keep the message in the buffer if it is incomplete instead of // keeping the state and remembering the read information. This // shouldn't happen very often compared to full reads. template inline bool PeerConnection::read_message() { ProtocolBuffer<512>* buf = m_down->buffer(); if (buf->remaining() < 4) return false; // Remember the start of the message so we may reset it if we don't // have the whole message. ProtocolBuffer<512>::iterator beginning = buf->position(); uint32_t length = buf->read_32(); if (length == 0) { // Keepalive message. m_down->set_last_command(ProtocolBase::KEEP_ALIVE); return true; } else if (buf->remaining() < 1) { buf->set_position_itr(beginning); return false; } else if (length > (1 << 20)) { throw communication_error("PeerConnection::read_message() got an invalid message length."); } // We do not verify the message length of those with static // length. A bug in the remote client causing the message start to // be unsyncronized would in practically all cases be caught with // the above test. // // Those that do in some weird way manage to produce a valid // command, will not be able to do any more damage than a malicious // peer. Those cases should be caught elsewhere in the code. // Temporary. m_down->set_last_command(static_cast(buf->peek_8())); switch (buf->read_8()) { case ProtocolBase::CHOKE: if (type != Download::CONNECTION_LEECH) return true; // Cancel before dequeueing so receive_download_choke knows if it // should remove us from throttle. // // Hmm... that won't work, as we arn't necessarily unchoked when // in throttle. // Which needs to be done before, and which after calling choke // manager? m_downUnchoked = false; down_chunk_release(); request_list()->choked(); m_download->choke_group()->down_queue()->set_not_queued(this, &m_downChoke); m_down->throttle()->erase(m_peerChunks.download_throttle()); return true; case ProtocolBase::UNCHOKE: if (type != Download::CONNECTION_LEECH) return true; m_downUnchoked = true; // Some peers unchoke us even though we're not interested, so we // need to ensure it doesn't get added to the queue. if (!m_downInterested) return true; request_list()->unchoked(); m_download->choke_group()->down_queue()->set_queued(this, &m_downChoke); return true; case ProtocolBase::INTERESTED: if (type == Download::CONNECTION_LEECH && m_peerChunks.bitfield()->is_all_set()) return true; m_download->choke_group()->up_queue()->set_queued(this, &m_upChoke); return true; case ProtocolBase::NOT_INTERESTED: m_download->choke_group()->up_queue()->set_not_queued(this, &m_upChoke); return true; case ProtocolBase::HAVE: if (!m_down->can_read_have_body()) break; read_have_chunk(buf->read_32()); return true; case ProtocolBase::REQUEST: if (!m_down->can_read_request_body()) break; if (!m_upChoke.choked()) { write_insert_poll_safe(); read_request_piece(m_down->read_request()); } else { m_down->read_request(); } return true; case ProtocolBase::PIECE: if (type != Download::CONNECTION_LEECH) throw communication_error("Received a piece but the connection is strictly for seeding."); if (length < 9) throw communication_error("Received a piece message that was too short."); if (!m_down->can_read_piece_body()) break; if (!down_chunk_start(m_down->read_piece(length - 9))) { // We don't want this chunk. if (down_chunk_skip_from_buffer()) { m_tryRequest = true; down_chunk_finished(); return true; } else { m_down->set_state(ProtocolRead::READ_SKIP_PIECE); m_down->throttle()->insert(m_peerChunks.download_throttle()); return false; } } else { if (down_chunk_from_buffer()) { m_tryRequest = true; down_chunk_finished(); return true; } else { m_down->set_state(ProtocolRead::READ_PIECE); m_down->throttle()->insert(m_peerChunks.download_throttle()); return false; } } case ProtocolBase::CANCEL: if (!m_down->can_read_cancel_body()) break; read_cancel_piece(m_down->read_request()); return true; case ProtocolBase::PORT: if (!m_down->can_read_port_body()) break; runtime::dht_add_peer_node(m_peerInfo->socket_address(), m_down->buffer()->read_16()); return true; case ProtocolBase::EXTENSION_PROTOCOL: if (!m_down->can_read_extension_body()) break; if (m_extensions->is_default()) { m_extensions = new ProtocolExtension(); m_extensions->set_info(m_peerInfo, m_download); } { int extension = m_down->buffer()->read_8(); m_extensions->read_start(extension, length - 2, (extension == ProtocolExtension::UT_PEX) && !m_download->want_pex_msg()); m_down->set_state(ProtocolRead::READ_EXTENSION); } if (!down_extension()) return false; if (m_extensions->has_pending_message()) write_insert_poll_safe(); m_down->set_state(ProtocolRead::IDLE); return true; default: throw communication_error("Received unsupported message type."); } // We were unsuccessfull in reading the message, need more data. buf->set_position_itr(beginning); return false; } template void PeerConnection::event_read() { m_time_last_read = this_thread::cached_time(); // Need to make sure ProtocolBuffer::end() is pointing to the end of // the unread data, and that the unread data starts from the // beginning of the buffer. Or do we use position? Propably best, // therefor ProtocolBuffer::position() points to the beginning of // the unused data. try { // Normal read. // // We rarely will read zero bytes as the read of 64 bytes will // almost always either not fill up or it will require additional // reads. // // Only loop when end hits 64. do { switch (m_down->get_state()) { case ProtocolRead::IDLE: if (m_down->buffer()->size_end() < read_size) { unsigned int length = read_stream_throws(m_down->buffer()->end(), read_size - m_down->buffer()->size_end()); m_down->throttle()->node_used_unthrottled(length); if (is_encrypted()) m_encryption.decrypt(m_down->buffer()->end(), length); m_down->buffer()->move_end(length); } while (read_message()) ; // Do nothing. if (m_down->buffer()->size_end() == read_size) { m_down->buffer()->move_unused(); break; } else { m_down->buffer()->move_unused(); return; } case ProtocolRead::READ_PIECE: if (type != Download::CONNECTION_LEECH) return; if (!request_list()->is_downloading()) throw internal_error("ProtocolRead::READ_PIECE state but RequestList is not downloading."); if (!m_request_list.transfer()->is_valid() || !m_request_list.transfer()->is_leader()) { m_down->set_state(ProtocolRead::READ_SKIP_PIECE); break; } if (!down_chunk()) return; m_tryRequest = true; m_down->set_state(ProtocolRead::IDLE); down_chunk_finished(); break; case ProtocolRead::READ_SKIP_PIECE: if (type != Download::CONNECTION_LEECH) return; if (request_list()->transfer()->is_leader()) { m_down->set_state(ProtocolRead::READ_PIECE); break; } if (!down_chunk_skip()) return; m_tryRequest = true; m_down->set_state(ProtocolRead::IDLE); down_chunk_finished(); break; case ProtocolRead::READ_EXTENSION: if (!down_extension()) return; if (m_extensions->has_pending_message()) write_insert_poll_safe(); m_down->set_state(ProtocolRead::IDLE); break; default: throw internal_error("PeerConnection::event_read() wrong state."); } // Figure out how to get rid of the shouldLoop boolean. } while (true); // Exception handlers: } catch (const close_connection&) { m_download->connection_list()->erase(this, 0); } catch (const blocked_connection&) { m_download->connection_list()->erase(this, 0); } catch (const network_error& e) { LT_LOG_NETWORK_ERRORS("network read error: %s : %s", sa_pretty_str(m_peerInfo->socket_address()).c_str(), e.what()); m_download->connection_list()->erase(this, 0); } catch (const storage_error& e) { LT_LOG_NETWORK_ERRORS("storage read error: %s", e.what()); m_download->connection_list()->erase(this, 0); } catch (const base_error& e) { std::stringstream s; s << "Connection read fd(" << m_fileDesc << ',' << m_down->get_state() << ',' << m_down->last_command() << ") \"" << e.what() << '"'; s << " '" << utils::uri_escape_html(reinterpret_cast(m_down->buffer()->begin()), reinterpret_cast(m_down->buffer()->position())) << "'"; throw internal_error(s.str()); } } template inline void PeerConnection::fill_write_buffer() { ProtocolBuffer<512>::iterator old_end = m_up->buffer()->end(); // No need to use delayed choke ever. if (m_sendChoked && m_up->can_write_choke()) { m_sendChoked = false; m_up->write_choke(m_upChoke.choked()); if (m_upChoke.choked()) { m_up->throttle()->erase(m_peerChunks.upload_throttle()); up_chunk_release(); m_peerChunks.upload_queue()->clear(); if (m_encryptBuffer != nullptr) { if (m_encryptBuffer->remaining()) throw internal_error("Deleting encryptBuffer with encrypted data remaining."); m_encryptBuffer = nullptr; } } else { m_up->throttle()->insert(m_peerChunks.upload_throttle()); } } // Send the interested state before any requests as some clients, // e.g. BitTornado 0.7.14 and uTorrent 0.3.0, disconnect if a // request has been received while uninterested. The problem arises // as they send unchoke before receiving interested. if (type == Download::CONNECTION_LEECH && m_sendInterested && m_up->can_write_interested()) { m_up->write_interested(m_downInterested); m_sendInterested = false; } if (type == Download::CONNECTION_LEECH && m_tryRequest) { if (!(m_tryRequest = !should_request()) && !(m_tryRequest = try_request_pieces()) && !request_list()->is_interested_in_active()) { m_sendInterested = true; m_downInterested = false; m_download->choke_group()->down_queue()->set_not_queued(this, &m_downChoke); } } DownloadMain::have_queue_type* haveQueue = m_download->have_queue(); if (type == Download::CONNECTION_LEECH && !haveQueue->empty() && m_peerChunks.have_timer() <= haveQueue->front().first && m_up->can_write_have()) { auto last = std::find_if(haveQueue->begin(), haveQueue->end(), [this](auto v) { return m_peerChunks.have_timer() > v.first; }); do { m_up->write_have((--last)->second); } while (last != haveQueue->begin() && m_up->can_write_have()); m_peerChunks.set_have_timer(last->first + 1us); } if (type == Download::CONNECTION_INITIAL_SEED && m_up->can_write_have()) offer_chunk(); while (type == Download::CONNECTION_LEECH && !m_peerChunks.cancel_queue()->empty() && m_up->can_write_cancel()) { m_up->write_cancel(m_peerChunks.cancel_queue()->front()); m_peerChunks.cancel_queue()->pop_front(); } if (m_sendPEXMask && m_up->can_write_extension() && send_pex_message()) { // Don't do anything else if send_pex_message() succeeded. } else if (m_extensions->has_pending_message() && m_up->can_write_extension() && send_ext_message()) { // Same. } else if (!m_upChoke.choked() && !m_peerChunks.upload_queue()->empty() && m_up->can_write_piece() && (type != Download::CONNECTION_INITIAL_SEED || should_upload())) { write_prepare_piece(); } if (is_encrypted()) m_encryption.encrypt(old_end, m_up->buffer()->end() - old_end); } template void PeerConnection::event_write() { try { do { switch (m_up->get_state()) { case ProtocolWrite::IDLE: fill_write_buffer(); if (m_up->buffer()->remaining() == 0) { this_thread::poll()->remove_write(this); return; } m_up->set_state(ProtocolWrite::MSG); [[fallthrough]]; case ProtocolWrite::MSG: if (!m_up->buffer()->consume(m_up->throttle()->node_used_unthrottled(write_stream_throws(m_up->buffer()->position(), m_up->buffer()->remaining())))) return; m_up->buffer()->reset(); if (m_up->last_command() == ProtocolBase::PIECE) { // We're uploading a piece. load_up_chunk(); m_up->set_state(ProtocolWrite::WRITE_PIECE); // fall through to WRITE_PIECE case below } else if (m_up->last_command() == ProtocolBase::EXTENSION_PROTOCOL) { m_up->set_state(ProtocolWrite::WRITE_EXTENSION); break; } else { // Break or loop? Might do an ifelse based on size of the // write buffer. Also the write buffer is relatively large. m_up->set_state(ProtocolWrite::IDLE); break; } [[fallthrough]]; case ProtocolWrite::WRITE_PIECE: if (!up_chunk()) return; m_up->set_state(ProtocolWrite::IDLE); break; case ProtocolWrite::WRITE_EXTENSION: if (!up_extension()) return; m_up->set_state(ProtocolWrite::IDLE); break; default: throw internal_error("PeerConnection::event_write() wrong state."); } } while (true); } catch (const close_connection&) { m_download->connection_list()->erase(this, 0); } catch (const blocked_connection&) { m_download->connection_list()->erase(this, 0); } catch (const network_error& e) { LT_LOG_NETWORK_ERRORS("network write error: %s : %s", sa_pretty_str(m_peerInfo->socket_address()).c_str(), e.what()); m_download->connection_list()->erase(this, 0); } catch (const storage_error& e) { LT_LOG_STORAGE_ERRORS("write error: %s", e.what()); m_download->connection_list()->erase(this, 0); } catch (const base_error& e) { std::stringstream s; s << "Connection write fd(" << m_fileDesc << ',' << m_up->get_state() << ',' << m_up->last_command() << ") \"" << e.what() << '"'; throw internal_error(s.str()); } } template void PeerConnection::read_have_chunk(uint32_t index) { if (index >= m_peerChunks.bitfield()->size_bits()) throw communication_error("Peer sent HAVE message with out-of-range index."); if (m_peerChunks.bitfield()->get(index)) return; m_download->chunk_statistics()->received_have_chunk(&m_peerChunks, index, m_download->file_list()->chunk_size()); if (type == Download::CONNECTION_INITIAL_SEED) m_download->initial_seeding()->chunk_seen(index, this); // Disconnect seeds when we are seeding (but not for initial seeding // so that we keep accurate chunk statistics until that is done). if (m_peerChunks.bitfield()->is_all_set()) { if (type == Download::CONNECTION_SEED || (type != Download::CONNECTION_INITIAL_SEED && m_download->file_list()->is_done())) throw close_connection(); m_download->choke_group()->up_queue()->set_not_queued(this, &m_upChoke); } if (type != Download::CONNECTION_LEECH || m_download->file_list()->is_done()) return; if (is_down_interested()) { if (!m_tryRequest && m_download->chunk_selector()->received_have_chunk(&m_peerChunks, index)) { m_tryRequest = true; write_insert_poll_safe(); } } else { if (m_download->chunk_selector()->received_have_chunk(&m_peerChunks, index)) { m_sendInterested = !m_downInterested; m_downInterested = true; // Ensure we get inserted into the choke manager queue in case // the peer keeps us unchoked even though we've said we're not // interested. if (m_downUnchoked) m_download->choke_group()->down_queue()->set_queued(this, &m_downChoke); // Is it enough to insert into write here? Make the interested // check branch to include insert_write, even when not sending // interested. m_tryRequest = true; write_insert_poll_safe(); } } } template<> void PeerConnection::offer_chunk() { // If bytes left to send in this chunk minus bytes about to be sent is zero, // assume the peer will have got the chunk completely. In that case we may // get another one to offer if not enough other peers are interested even // if the peer would otherwise still be blocked. uint32_t bytesLeft = m_data.bytesLeft; if (!m_peerChunks.upload_queue()->empty() && m_peerChunks.upload_queue()->front().index() == m_data.lastIndex) bytesLeft -= m_peerChunks.upload_queue()->front().length(); uint32_t index = m_download->initial_seeding()->chunk_offer(this, bytesLeft == 0 ? m_data.lastIndex : InitialSeeding::no_offer); if (index == InitialSeeding::no_offer || index == m_data.lastIndex) return; m_up->write_have(index); m_data.lastIndex = index; m_data.bytesLeft = m_download->file_list()->chunk_index_size(index); } template<> bool PeerConnection::should_upload() { // For initial seeding, check if chunk is well seeded now, and if so // remove it from the queue to better use our bandwidth on rare chunks. while (!m_peerChunks.upload_queue()->empty() && !m_download->initial_seeding()->should_upload(m_peerChunks.upload_queue()->front().index())) m_peerChunks.upload_queue()->pop_front(); // If queue ends up empty, choke peer to let it know that it // shouldn't wait for the cancelled pieces to be sent. if (m_peerChunks.upload_queue()->empty()) { m_download->choke_group()->up_queue()->set_not_queued(this, &m_upChoke); m_download->choke_group()->up_queue()->set_queued(this, &m_upChoke); // If we're sending the chunk we last offered, adjust bytes left in it. } else if (m_peerChunks.upload_queue()->front().index() == m_data.lastIndex) { m_data.bytesLeft -= m_peerChunks.upload_queue()->front().length(); if (!m_data.bytesLeft) m_data.lastIndex = InitialSeeding::no_offer; } return !m_peerChunks.upload_queue()->empty(); } // Explicit instantiation of the member functions and vtable. template class PeerConnection; template class PeerConnection; template class PeerConnection; } // namespace torrent libtorrent-0.16.17/src/protocol/peer_connection_leech.h000066400000000000000000000025361522271512000231700ustar00rootroot00000000000000#ifndef LIBTORRENT_PROTOCOL_PEER_CONNECTION_LEECH_H #define LIBTORRENT_PROTOCOL_PEER_CONNECTION_LEECH_H #include "peer_connection_base.h" #include "torrent/download.h" namespace torrent { // Type-specific data. template struct PeerConnectionData; template<> struct PeerConnectionData { }; template<> struct PeerConnectionData { }; template<> struct PeerConnectionData { uint32_t lastIndex{~uint32_t{}}; uint32_t bytesLeft; }; template class PeerConnection : public PeerConnectionBase { public: PeerConnection() = default; ~PeerConnection() override; void initialize_custom() override; void update_interested() override; bool receive_keepalive() override; void event_read() override; void event_write() override; private: PeerConnection(const PeerConnection&) = delete; PeerConnection& operator=(const PeerConnection&) = delete; inline bool read_message(); void read_have_chunk(uint32_t index); void offer_chunk(); bool should_upload(); inline void fill_write_buffer(); PeerConnectionData m_data; }; } // namespace torrent #endif libtorrent-0.16.17/src/protocol/peer_connection_metadata.cc000066400000000000000000000322571522271512000240310ustar00rootroot00000000000000#include "config.h" #include "protocol/peer_connection_metadata.h" #include #include #include "download/chunk_selector.h" #include "download/chunk_statistics.h" #include "download/download_main.h" #include "manager.h" #include "protocol/extensions.h" #include "torrent/download/choke_queue.h" #include "torrent/download_info.h" #include "torrent/peer/connection_list.h" #include "torrent/peer/peer_info.h" #include "torrent/runtime/runtime.h" #include "torrent/utils/log.h" #define LT_LOG_METADATA_EVENTS(log_fmt, ...) \ lt_log_print_info(LOG_PROTOCOL_METADATA_EVENTS, this->download()->info(), "metadata_events", "%40s " log_fmt, this->peer_info()->id_hex(), __VA_ARGS__); #define LT_LOG_STORAGE_ERRORS(log_fmt, ...) \ lt_log_print_info(LOG_PROTOCOL_STORAGE_ERRORS, this->download()->info(), "storage_errors", "%40s " log_fmt, this->peer_info()->id_hex(), __VA_ARGS__); namespace torrent { PeerConnectionMetadata::~PeerConnectionMetadata() = default; void PeerConnectionMetadata::initialize_custom() { } void PeerConnectionMetadata::update_interested() { } bool PeerConnectionMetadata::receive_keepalive() { if (this_thread::cached_time() - m_time_last_read > 240s) return false; m_tryRequest = true; // There's no point in adding ourselves to the write poll if the // buffer is full, as that will already have been taken care of. if (m_up->get_state() == ProtocolWrite::IDLE && m_up->can_write_keepalive()) { write_insert_poll_safe(); ProtocolBuffer<512>::iterator old_end = m_up->buffer()->end(); m_up->write_keepalive(); if (is_encrypted()) m_encryption.encrypt(old_end, m_up->buffer()->end() - old_end); } return true; } // We keep the message in the buffer if it is incomplete instead of // keeping the state and remembering the read information. This // shouldn't happen very often compared to full reads. inline bool PeerConnectionMetadata::read_message() { ProtocolBuffer<512>* buf = m_down->buffer(); if (buf->remaining() < 4) return false; // Remember the start of the message so we may reset it if we don't // have the whole message. ProtocolBuffer<512>::iterator beginning = buf->position(); uint32_t length = buf->read_32(); if (length == 0) { // Keepalive message. m_down->set_last_command(ProtocolBase::KEEP_ALIVE); return true; } else if (buf->remaining() < 1) { buf->set_position_itr(beginning); return false; } else if (length > (1 << 20)) { throw communication_error("PeerConnection::read_message() got an invalid message length."); } m_down->set_last_command(static_cast(buf->peek_8())); // Ignore most messages, they aren't relevant for a metadata download. switch (buf->read_8()) { case ProtocolBase::CHOKE: case ProtocolBase::UNCHOKE: case ProtocolBase::INTERESTED: case ProtocolBase::NOT_INTERESTED: return true; case ProtocolBase::HAVE: if (!m_down->can_read_have_body()) break; buf->read_32(); return true; case ProtocolBase::REQUEST: if (!m_down->can_read_request_body()) break; m_down->read_request(); return true; case ProtocolBase::PIECE: throw communication_error("Received a piece but the connection is strictly for meta data."); case ProtocolBase::CANCEL: if (!m_down->can_read_cancel_body()) break; m_down->read_request(); return true; case ProtocolBase::PORT: if (!m_down->can_read_port_body()) break; runtime::dht_add_peer_node(m_peerInfo->socket_address(), m_down->buffer()->read_16()); return true; case ProtocolBase::EXTENSION_PROTOCOL: LT_LOG_METADATA_EVENTS("protocol extension message", 0); if (!m_down->can_read_extension_body()) break; if (m_extensions->is_default()) { m_extensions = new ProtocolExtension(); m_extensions->set_info(m_peerInfo, m_download); } { int extension = m_down->buffer()->read_8(); m_extensions->read_start(extension, length - 2, (extension == ProtocolExtension::UT_PEX) && !m_download->want_pex_msg()); m_down->set_state(ProtocolRead::READ_EXTENSION); } if (!down_extension()) return false; LT_LOG_METADATA_EVENTS("protocol extension done", 0); // Drop peer if it disabled the metadata extension. if (!m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA)) throw close_connection(); m_down->set_state(ProtocolRead::IDLE); m_tryRequest = true; write_insert_poll_safe(); return true; case ProtocolBase::BITFIELD: // Discard the bitfield sent by the peer. m_skipLength = length - 1; m_down->set_state(ProtocolRead::READ_SKIP_PIECE); return false; default: throw communication_error("Received unsupported message type."); } // We were unsuccessfull in reading the message, need more data. buf->set_position_itr(beginning); return false; } void PeerConnectionMetadata::event_read() { m_time_last_read = this_thread::cached_time(); // Need to make sure ProtocolBuffer::end() is pointing to the end of // the unread data, and that the unread data starts from the // beginning of the buffer. Or do we use position? Propably best, // therefor ProtocolBuffer::position() points to the beginning of // the unused data. try { // Normal read. // // We rarely will read zero bytes as the read of 64 bytes will // almost always either not fill up or it will require additional // reads. // // Only loop when end hits 64. do { switch (m_down->get_state()) { case ProtocolRead::IDLE: if (m_down->buffer()->size_end() < read_size) { unsigned int length = read_stream_throws(m_down->buffer()->end(), read_size - m_down->buffer()->size_end()); m_down->throttle()->node_used_unthrottled(length); if (is_encrypted()) m_encryption.decrypt(m_down->buffer()->end(), length); m_down->buffer()->move_end(length); } while (read_message()) ; // Do nothing. if (m_down->buffer()->size_end() == read_size) { m_down->buffer()->move_unused(); break; } else { m_down->buffer()->move_unused(); return; } case ProtocolRead::READ_EXTENSION: if (!down_extension()) return; // Drop peer if it disabled the metadata extension. if (!m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA)) throw close_connection(); LT_LOG_METADATA_EVENTS("reading extension message", 0); m_down->set_state(ProtocolRead::IDLE); m_tryRequest = true; write_insert_poll_safe(); break; // Actually skipping the bitfield. // We never receive normal piece messages anyway. case ProtocolRead::READ_SKIP_PIECE: if (!read_skip_bitfield()) return; m_down->set_state(ProtocolRead::IDLE); break; default: throw internal_error("PeerConnection::event_read() wrong state."); } // Figure out how to get rid of the shouldLoop boolean. } while (true); // Exception handlers: } catch (const close_connection&) { m_download->connection_list()->erase(this, 0); } catch (const blocked_connection&) { m_download->connection_list()->erase(this, 0); } catch (const network_error&) { m_download->connection_list()->erase(this, 0); } catch (const storage_error& e) { LT_LOG_STORAGE_ERRORS("read error: %s", e.what()); m_download->connection_list()->erase(this, 0); } catch (const base_error& e) { std::stringstream s; s << "Connection read fd(" << m_fileDesc << ',' << m_down->get_state() << ',' << m_down->last_command() << ") \"" << e.what() << '"'; throw internal_error(s.str()); } } inline void PeerConnectionMetadata::fill_write_buffer() { ProtocolBuffer<512>::iterator old_end = m_up->buffer()->end(); if (m_tryRequest) m_tryRequest = try_request_metadata_pieces(); if (m_sendPEXMask && m_up->can_write_extension() && send_pex_message()) { // Don't do anything else if send_pex_message() succeeded. } else if (m_extensions->has_pending_message() && m_up->can_write_extension() && send_ext_message()) { // Same. } if (is_encrypted()) m_encryption.encrypt(old_end, m_up->buffer()->end() - old_end); } void PeerConnectionMetadata::event_write() { try { do { switch (m_up->get_state()) { case ProtocolWrite::IDLE: fill_write_buffer(); if (m_up->buffer()->remaining() == 0) { this_thread::poll()->remove_write(this); return; } m_up->set_state(ProtocolWrite::MSG); [[fallthrough]]; case ProtocolWrite::MSG: if (!m_up->buffer()->consume(m_up->throttle()->node_used_unthrottled(write_stream_throws(m_up->buffer()->position(), m_up->buffer()->remaining())))) return; m_up->buffer()->reset(); if (m_up->last_command() != ProtocolBase::EXTENSION_PROTOCOL) { m_up->set_state(ProtocolWrite::IDLE); break; } m_up->set_state(ProtocolWrite::WRITE_EXTENSION); [[fallthrough]]; case ProtocolWrite::WRITE_EXTENSION: if (!up_extension()) return; m_up->set_state(ProtocolWrite::IDLE); break; default: throw internal_error("PeerConnection::event_write() wrong state."); } } while (true); } catch (const close_connection&) { m_download->connection_list()->erase(this, 0); } catch (const blocked_connection&) { m_download->connection_list()->erase(this, 0); } catch (const network_error&) { m_download->connection_list()->erase(this, 0); } catch (const storage_error& e) { LT_LOG_STORAGE_ERRORS("read error: %s", e.what()); m_download->connection_list()->erase(this, 0); } catch (const base_error& e) { std::stringstream s; s << "Connection write fd(" << m_fileDesc << ',' << m_up->get_state() << ',' << m_up->last_command() << ") \"" << e.what() << '"'; throw internal_error(s.str()); } } bool PeerConnectionMetadata::read_skip_bitfield() { if (m_down->buffer()->remaining()) { uint32_t length = std::min(m_skipLength, static_cast(m_down->buffer()->remaining())); m_down->buffer()->consume(length); m_skipLength -= length; } if (m_skipLength) { uint32_t length = std::min(m_skipLength, static_cast(null_buffer_size)); length = read_stream_throws(m_nullBuffer, length); if (!length) return false; m_skipLength -= length; } return !m_skipLength; } // Same as the PCB code, but only one at a time and with the extension protocol. bool PeerConnectionMetadata::try_request_metadata_pieces() { if (m_download->file_list()->chunk_size() == 1 || !m_extensions->is_remote_supported(ProtocolExtension::UT_METADATA)) return false; if (request_list()->queued_empty()) m_downStall = 0; uint32_t pipeSize = request_list()->calculate_pipe_size(m_peerChunks.download_throttle()->rate()->rate()); // Don't start requesting if we can't do it in large enough chunks. if (request_list()->pipe_size() >= (pipeSize + 10) / 2) return false; // DEBUG: // if (!request_list()->queued_size() < pipeSize || !m_up->can_write_extension() || if (!m_up->can_write_extension() || m_extensions->has_pending_message()) return false; std::vector pieces = request_list()->delegate(1); if (pieces.empty()) return false; const Piece* p = pieces.front(); if (!m_download->file_list()->is_valid_piece(*p) || !m_peerChunks.bitfield()->get(p->index())) throw internal_error("PeerConnectionMetadata::try_request_metadata_pieces() tried to use an invalid piece."); // DEBUG: if (m_extensions->request_metadata_piece(p)) { LT_LOG_METADATA_EVENTS("request metadata piece succeded", 0); return true; } else { LT_LOG_METADATA_EVENTS("request metadata piece failed", 0); return false; } } void PeerConnectionMetadata::receive_metadata_piece(uint32_t piece, const char* data, uint32_t length) { if (data == NULL) { // Length is not set in a reject message. length = ProtocolExtension::metadata_piece_size; if ((piece << ProtocolExtension::metadata_piece_shift) + ProtocolExtension::metadata_piece_size >= m_download->file_list()->size_bytes()) length = m_download->file_list()->chunk_size() % ProtocolExtension::metadata_piece_size; m_tryRequest = false; read_cancel_piece(Piece(0, piece << ProtocolExtension::metadata_piece_shift, length)); LT_LOG_METADATA_EVENTS("rejected metadata piece", 0); return; } if (!down_chunk_start(Piece(0, piece << ProtocolExtension::metadata_piece_shift, length))) { LT_LOG_METADATA_EVENTS("skipped metadata piece", 0); down_chunk_skip_process(data, length); } else { LT_LOG_METADATA_EVENTS("processed metadata piece", 0); down_chunk_process(data, length); } if (m_request_list.transfer() != NULL && !m_request_list.transfer()->is_finished()) throw internal_error("PeerConnectionMetadata::receive_metadata_piece did not have complete piece."); m_tryRequest = true; down_chunk_finished(); } } // namespace torrent libtorrent-0.16.17/src/protocol/peer_connection_metadata.h000066400000000000000000000016321522271512000236640ustar00rootroot00000000000000#ifndef LIBTORRENT_PROTOCOL_PEER_CONNECTION_METADATA_H #define LIBTORRENT_PROTOCOL_PEER_CONNECTION_METADATA_H #include "peer_connection_base.h" #include "torrent/download.h" namespace torrent { class PeerConnectionMetadata : public PeerConnectionBase { public: ~PeerConnectionMetadata() override; void initialize_custom() override; void update_interested() override; bool receive_keepalive() override; void event_read() override; void event_write() override; void receive_metadata_piece(uint32_t piece, const char* data, uint32_t length) override; private: inline bool read_message(); bool read_skip_bitfield(); bool try_request_metadata_pieces(); inline void fill_write_buffer(); uint32_t m_skipLength; }; } // namespace torrent #endif libtorrent-0.16.17/src/protocol/peer_factory.cc000066400000000000000000000046061522271512000214760ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include "peer_factory.h" #include "peer_connection_leech.h" #include "peer_connection_metadata.h" namespace torrent { PeerConnectionBase* createPeerConnectionDefault(bool encrypted) { PeerConnectionBase* pc = new PeerConnection; return pc; } PeerConnectionBase* createPeerConnectionSeed(bool encrypted) { PeerConnectionBase* pc = new PeerConnection; return pc; } PeerConnectionBase* createPeerConnectionInitialSeed(bool encrypted) { PeerConnectionBase* pc = new PeerConnection; return pc; } PeerConnectionBase* createPeerConnectionMetadata(bool encrypted) { PeerConnectionBase* pc = new PeerConnectionMetadata; return pc; } } // namespace torrent libtorrent-0.16.17/src/protocol/peer_factory.h000066400000000000000000000040321522271512000213310ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_PEER_PEER_FACTORY_H #define LIBTORRENT_PEER_PEER_FACTORY_H namespace torrent { class PeerConnectionBase; PeerConnectionBase* createPeerConnectionDefault(bool encrypted); PeerConnectionBase* createPeerConnectionSeed(bool encrypted); PeerConnectionBase* createPeerConnectionInitialSeed(bool encrypted); PeerConnectionBase* createPeerConnectionMetadata(bool encrypted); } // namespace torrent #endif libtorrent-0.16.17/src/protocol/protocol_base.h000066400000000000000000000156021522271512000215070ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_PROTOCOL_BASE_H #define LIBTORRENT_NET_PROTOCOL_BASE_H #include "net/protocol_buffer.h" namespace torrent { class Piece; class ProtocolBase { public: using Buffer = ProtocolBuffer<512>; using size_type = uint32_t; static constexpr size_type buffer_size = 512; enum Protocol { CHOKE = 0, UNCHOKE, INTERESTED, NOT_INTERESTED, HAVE, BITFIELD, REQUEST, PIECE, CANCEL, PORT, // = 9 EXTENSION_PROTOCOL = 20, NONE, // These are not part of the protocol KEEP_ALIVE // Last command was a keep alive }; enum State { IDLE, MSG, READ_PIECE, READ_SKIP_PIECE, READ_EXTENSION, WRITE_PIECE, WRITE_EXTENSION, INTERNAL_ERROR }; ProtocolBase() { m_buffer.reset(); } Protocol last_command() const { return m_lastCommand; } void set_last_command(Protocol p) { m_lastCommand = p; } Buffer* buffer() { return &m_buffer; } ThrottleList* throttle() { return m_throttle; } void set_throttle(ThrottleList* t) { m_throttle = t; } State get_state() const { return m_state; } void set_state(State s) { m_state = s; } Piece read_request(); Piece read_piece(size_type length); void write_command(Protocol c) { m_buffer.write_8(m_lastCommand = c); } void write_keepalive(); void write_choke(bool s); void write_interested(bool s); void write_have(uint32_t index); void write_bitfield(size_type length); void write_request(const Piece& p); void write_cancel(const Piece& p); void write_piece(const Piece& p); void write_port(uint16_t port); void write_extension(uint8_t id, uint32_t length); static constexpr size_type sizeof_keepalive = 4; static constexpr size_type sizeof_choke = 5; static constexpr size_type sizeof_interested = 5; static constexpr size_type sizeof_have = 9; static constexpr size_type sizeof_have_body = 4; static constexpr size_type sizeof_bitfield = 5; static constexpr size_type sizeof_request = 17; static constexpr size_type sizeof_request_body = 12; static constexpr size_type sizeof_cancel = 17; static constexpr size_type sizeof_cancel_body = 12; static constexpr size_type sizeof_piece = 13; static constexpr size_type sizeof_piece_body = 8; static constexpr size_type sizeof_port = 7; static constexpr size_type sizeof_port_body = 2; static constexpr size_type sizeof_extension = 6; static constexpr size_type sizeof_extension_body=1; bool can_write_keepalive() const { return m_buffer.reserved_left() >= sizeof_keepalive; } bool can_write_choke() const { return m_buffer.reserved_left() >= sizeof_choke; } bool can_write_interested() const { return m_buffer.reserved_left() >= sizeof_interested; } bool can_write_have() const { return m_buffer.reserved_left() >= sizeof_have; } bool can_write_bitfield() const { return m_buffer.reserved_left() >= sizeof_bitfield; } bool can_write_request() const { return m_buffer.reserved_left() >= sizeof_request; } bool can_write_cancel() const { return m_buffer.reserved_left() >= sizeof_cancel; } bool can_write_piece() const { return m_buffer.reserved_left() >= sizeof_piece; } bool can_write_port() const { return m_buffer.reserved_left() >= sizeof_port; } bool can_write_extension() const { return m_buffer.reserved_left() >= sizeof_extension; } size_type max_write_request() const { return m_buffer.reserved_left() / sizeof_request; } bool can_read_have_body() const { return m_buffer.remaining() >= sizeof_have_body; } bool can_read_request_body() const { return m_buffer.remaining() >= sizeof_request_body; } bool can_read_cancel_body() const { return m_buffer.remaining() >= sizeof_request_body; } bool can_read_piece_body() const { return m_buffer.remaining() >= sizeof_piece_body; } bool can_read_port_body() const { return m_buffer.remaining() >= sizeof_port_body; } bool can_read_extension_body() const { return m_buffer.remaining() >= sizeof_extension_body; } protected: State m_state{IDLE}; Protocol m_lastCommand{NONE}; ThrottleList* m_throttle{}; Buffer m_buffer; }; inline Piece ProtocolBase::read_request() { uint32_t index = m_buffer.read_32(); uint32_t offset = m_buffer.read_32(); uint32_t length = m_buffer.read_32(); return Piece(index, offset, length); } inline Piece ProtocolBase::read_piece(size_type length) { uint32_t index = m_buffer.read_32(); uint32_t offset = m_buffer.read_32(); return Piece(index, offset, length); } inline void ProtocolBase::write_keepalive() { m_buffer.write_32(0); m_lastCommand = KEEP_ALIVE; } inline void ProtocolBase::write_choke(bool s) { m_buffer.write_32(1); write_command(s ? CHOKE : UNCHOKE); } inline void ProtocolBase::write_interested(bool s) { m_buffer.write_32(1); write_command(s ? INTERESTED : NOT_INTERESTED); } inline void ProtocolBase::write_have(uint32_t index) { m_buffer.write_32(5); write_command(HAVE); m_buffer.write_32(index); } inline void ProtocolBase::write_bitfield(size_type length) { m_buffer.write_32(1 + length); write_command(BITFIELD); } inline void ProtocolBase::write_request(const Piece& p) { m_buffer.write_32(13); write_command(REQUEST); m_buffer.write_32(p.index()); m_buffer.write_32(p.offset()); m_buffer.write_32(p.length()); } inline void ProtocolBase::write_cancel(const Piece& p) { m_buffer.write_32(13); write_command(CANCEL); m_buffer.write_32(p.index()); m_buffer.write_32(p.offset()); m_buffer.write_32(p.length()); } inline void ProtocolBase::write_piece(const Piece& p) { m_buffer.write_32(9 + p.length()); write_command(PIECE); m_buffer.write_32(p.index()); m_buffer.write_32(p.offset()); } inline void ProtocolBase::write_port(uint16_t port) { m_buffer.write_32(3); write_command(PORT); m_buffer.write_16(port); } inline void ProtocolBase::write_extension(uint8_t id, uint32_t length) { m_buffer.write_32(2 + length); write_command(EXTENSION_PROTOCOL); m_buffer.write_8(id); } } // namespace torrent #endif libtorrent-0.16.17/src/protocol/request_list.cc000066400000000000000000000251241522271512000215350ustar00rootroot00000000000000#include "config.h" #include "request_list.h" #include #include #include "download/delegator.h" #include "protocol/peer_chunks.h" #include "torrent/data/block.h" #include "torrent/data/block_list.h" #include "torrent/exceptions.h" #include "utils/instrumentation.h" namespace torrent { const instrumentation_enum request_list_constants::instrumentation_added[bucket_count] = { INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_ADDED, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_ADDED, INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_ADDED, INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_ADDED }; const instrumentation_enum request_list_constants::instrumentation_moved[bucket_count] = { INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_MOVED, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_MOVED, INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_MOVED, INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_MOVED }; const instrumentation_enum request_list_constants::instrumentation_removed[bucket_count] = { INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_REMOVED, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_REMOVED, INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_REMOVED, INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_REMOVED }; const instrumentation_enum request_list_constants::instrumentation_total[bucket_count] = { INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_TOTAL, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_TOTAL, INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_TOTAL, INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_TOTAL }; // Make inline... template <> void request_list_constants::destroy(BlockTransfer*& obj) { Block::release(obj); } // TODO: Add a to-be-cancelled list, timer, and use that to avoid // cancelling pieces on CHOKE->UNCHOKE weirdness in some clients. // // Right after this the client should always be allowed to queue more // pieces, perhaps add a timer for last choke and check the request // time on the queued pieces. This makes it possible to get going // again if the remote queue got cleared. // It is assumed invalid transfers have been removed. struct request_list_same_piece { request_list_same_piece(const Piece& p) : m_piece(p) {} bool operator () (const BlockTransfer* d) const { return m_piece.index() == d->piece().index() && m_piece.offset() == d->piece().offset(); } Piece m_piece; }; RequestList::~RequestList() { assert(m_transfer == nullptr); assert(m_queues.empty()); torrent::this_thread::scheduler()->erase(&m_delay_remove_choked); torrent::this_thread::scheduler()->erase(&m_delay_process_unordered); } std::vector RequestList::delegate(uint32_t maxPieces) { std::vector transfers = m_delegator->delegate(m_peerChunks, m_affinity, maxPieces); std::vector pieces; if (transfers.empty()) return pieces; instrumentation_update(INSTRUMENTATION_TRANSFER_REQUESTS_DELEGATED, transfers.size()); for (auto transfer : transfers) { m_queues.push_back(bucket_queued, transfer); pieces.push_back(&transfer->piece()); } // Use the last index returned for the next affinity m_affinity = transfers.back()->index(); return pieces; } void RequestList::stall_initial() { queue_bucket_for_all_in_queue(m_queues, bucket_queued, &Block::stalled); m_queues.move_all_to(bucket_queued, bucket_stalled); queue_bucket_for_all_in_queue(m_queues, bucket_unordered, &Block::stalled); m_queues.move_all_to(bucket_unordered, bucket_stalled); } void RequestList::stall_prolonged() { if (m_transfer != nullptr) Block::stalled(m_transfer); queue_bucket_for_all_in_queue(m_queues, bucket_queued, &Block::stalled); m_queues.move_all_to(bucket_queued, bucket_stalled); queue_bucket_for_all_in_queue(m_queues, bucket_unordered, &Block::stalled); m_queues.move_all_to(bucket_unordered, bucket_stalled); // Currently leave the the requests until the peer gets disconnected. (?) } void RequestList::choked() { // Check if we want to update the choke timer; if non-zero and // updated within a short timespan? m_last_choke = torrent::this_thread::cached_time(); if (m_queues.queue_empty(bucket_queued) && m_queues.queue_empty(bucket_unordered)) return; m_queues.move_all_to(bucket_queued, bucket_choked); m_queues.move_all_to(bucket_unordered, bucket_choked); m_queues.move_all_to(bucket_stalled, bucket_choked); torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&m_delay_remove_choked, timeout_remove_choked); } void RequestList::unchoked() { m_last_unchoke = torrent::this_thread::cached_time(); // Clear choked queue if the peer doesn't start sending previously // requested pieces. // // This handles the case where a peer does a choke immediately // followed unchoke before starting to send pieces. if (!m_queues.queue_empty(bucket_choked)) torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&m_delay_remove_choked, timeout_remove_choked); else torrent::this_thread::scheduler()->erase(&m_delay_remove_choked); } void RequestList::delay_remove_choked() { m_queues.clear(bucket_choked); } void RequestList::prepare_process_unordered(queues_type::iterator itr) { m_queues.move_to(bucket_queued, m_queues.begin(bucket_queued), itr, bucket_unordered); if (m_delay_process_unordered.is_scheduled()) return; torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_delay_process_unordered, timeout_process_unordered); // TODO: Review if this should always be updated. m_last_unordered_position = unordered_size(); } void RequestList::delay_process_unordered() { m_last_unordered_position = std::min(m_last_unordered_position, unordered_size()); instrumentation_update(INSTRUMENTATION_TRANSFER_REQUESTS_FINISHED, m_last_unordered_position); m_queues.destroy(bucket_unordered, m_queues.begin(bucket_unordered), m_queues.begin(bucket_unordered) + m_last_unordered_position); m_last_unordered_position = unordered_size(); if (m_last_unordered_position != 0) torrent::this_thread::scheduler()->wait_for_ceil_seconds(&m_delay_process_unordered, timeout_process_unordered); } void RequestList::clear() { if (is_downloading()) skipped(); m_queues.clear(bucket_queued); m_queues.clear(bucket_unordered); m_queues.clear(bucket_stalled); m_queues.clear(bucket_choked); } bool RequestList::downloading(const Piece& piece) { if (m_transfer != nullptr) throw internal_error("RequestList::downloading(...) m_transfer != nullptr."); instrumentation_update(INSTRUMENTATION_TRANSFER_REQUESTS_DOWNLOADING, 1); std::pair itr = queue_bucket_find_if_in_any(m_queues, request_list_same_piece(piece)); switch (itr.first) { case bucket_queued: if (itr.second != m_queues.begin(bucket_queued)) prepare_process_unordered(itr.second); m_transfer = m_queues.take(itr.first, itr.second); break; case bucket_unordered: // Do unordered take of element here to avoid copy shifting the whole deque. (?) // // Alternatively, move back some elements to bucket_queued. if (std::distance(m_queues.begin(itr.first), itr.second) < static_cast(m_last_unordered_position)) m_last_unordered_position--; m_transfer = m_queues.take(itr.first, itr.second); break; case bucket_stalled: // Do special handling of unordered pieces. m_transfer = m_queues.take(itr.first, itr.second); break; case bucket_choked: m_transfer = m_queues.take(itr.first, itr.second); // We make sure that the choked queue eventually gets cleared if // the peer has skipped sending some pieces from the choked queue. torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&m_delay_remove_choked, timeout_choked_received); break; default: goto downloading_error; } // We received an invalid piece length, propably zero length due to // the peer not being able to transfer the requested piece. // // We need to replace the current BlockTransfer so Block can keep // the unmodified BlockTransfer. if (piece.length() != m_transfer->piece().length()) { if (piece.length() != 0) throw communication_error("Peer sent a piece with wrong, non-zero, length."); Block::release(m_transfer); goto downloading_error; } // Check if piece isn't wanted anymore. Do this after the length // check to ensure we return a correct BlockTransfer. if (!m_transfer->is_valid()) return false; m_transfer->block()->transfering(m_transfer); return true; downloading_error: // Create a dummy BlockTransfer object to hold the piece // information. m_transfer = new BlockTransfer(); Block::create_dummy(m_transfer, m_peerChunks->peer_info(), piece); instrumentation_update(INSTRUMENTATION_TRANSFER_REQUESTS_UNKNOWN, 1); return false; } // Must clear the downloading piece. void RequestList::finished() { if (!is_downloading()) throw internal_error("RequestList::finished() called but no transfer is in progress."); if (!m_transfer->is_valid()) throw internal_error("RequestList::finished() called but transfer is invalid."); BlockTransfer* transfer = m_transfer; m_transfer = nullptr; m_delegator->transfer_list()->finished(transfer); instrumentation_update(INSTRUMENTATION_TRANSFER_REQUESTS_FINISHED, 1); } void RequestList::skipped() { if (!is_downloading()) throw internal_error("RequestList::skip() called but no transfer is in progress."); Block::release(m_transfer); m_transfer = nullptr; instrumentation_update(INSTRUMENTATION_TRANSFER_REQUESTS_SKIPPED, 1); } // Data downloaded by this non-leading transfer does not match what we // already have. void RequestList::transfer_dissimilar() { if (!is_downloading()) throw internal_error("RequestList::transfer_dissimilar() called but no transfer is in progress."); auto dummy = new BlockTransfer(); Block::create_dummy(dummy, m_peerChunks->peer_info(), m_transfer->piece()); dummy->set_position(m_transfer->position()); // TODO.... peer_info still on a block we no longer control?.. m_transfer->block()->transfer_dissimilar(m_transfer); m_transfer = dummy; } bool RequestList::is_interested_in_active() const { auto list = m_delegator->transfer_list(); return std::any_of(list->begin(), list->end(), [this](auto transfer) { return m_peerChunks->bitfield()->get(transfer->index()); }); } uint32_t RequestList::calculate_pipe_size(uint32_t rate) { // Change into KB. rate /= 1024; if (!m_delegator->get_aggressive()) { if (rate < 20) return rate + 2; else return rate / 5 + 18; } else { if (rate < 10) return rate / 5 + 1; else return rate / 10 + 2; } } } // namespace torrent libtorrent-0.16.17/src/protocol/request_list.h000066400000000000000000000111751522271512000214000ustar00rootroot00000000000000#ifndef LIBTORRENT_REQUEST_LIST_H #define LIBTORRENT_REQUEST_LIST_H #include #include #include "torrent/data/block_transfer.h" #include "torrent/utils/scheduler.h" #include "utils/instrumentation.h" #include "utils/queue_buckets.h" namespace torrent { class PeerChunks; class Delegator; struct request_list_constants { static constexpr int bucket_count = 4; static const torrent::instrumentation_enum instrumentation_added[bucket_count]; static const torrent::instrumentation_enum instrumentation_moved[bucket_count]; static const torrent::instrumentation_enum instrumentation_removed[bucket_count]; static const torrent::instrumentation_enum instrumentation_total[bucket_count]; template static void destroy(Type& obj); }; class RequestList { public: using queues_type = torrent::queue_buckets; static constexpr int bucket_queued = 0; static constexpr int bucket_unordered = 1; static constexpr int bucket_stalled = 2; static constexpr int bucket_choked = 3; static constexpr std::chrono::microseconds timeout_remove_choked{6s}; static constexpr std::chrono::microseconds timeout_choked_received{60s}; static constexpr std::chrono::microseconds timeout_process_unordered{60s}; RequestList(); ~RequestList(); // Some parameters here, like how fast we are downloading and stuff // when we start considering those. std::vector delegate(uint32_t maxPieces); void stall_initial(); void stall_prolonged(); void choked(); void unchoked(); void clear(); // The returned transfer must still be valid. bool downloading(const Piece& piece); void finished(); void skipped(); void transfer_dissimilar(); bool is_downloading() { return m_transfer != NULL; } bool is_interested_in_active() const; const Piece& next_queued_piece() const { return m_queues.front(bucket_queued)->piece(); } bool queued_empty() const { return m_queues.queue_empty(bucket_queued); } size_t queued_size() const { return m_queues.queue_size(bucket_queued); } bool unordered_empty() const { return m_queues.queue_empty(bucket_unordered); } size_t unordered_size() const { return m_queues.queue_size(bucket_unordered); } bool stalled_empty() const { return m_queues.queue_empty(bucket_stalled); } size_t stalled_size() const { return m_queues.queue_size(bucket_stalled); } bool choked_empty() const { return m_queues.queue_empty(bucket_choked); } size_t choked_size() const { return m_queues.queue_size(bucket_choked); } uint32_t pipe_size() const; uint32_t calculate_pipe_size(uint32_t rate); Delegator* delegator() { return m_delegator; } void set_delegator(Delegator* d) { m_delegator = d; } PeerChunks* peer_chunks() { return m_peerChunks; } void set_peer_chunks(PeerChunks* b) { m_peerChunks = b; } BlockTransfer* transfer() { return m_transfer; } const BlockTransfer* transfer() const { return m_transfer; } const BlockTransfer* queued_front() const { return m_queues.front(bucket_queued); } private: void delay_remove_choked(); void prepare_process_unordered(queues_type::iterator itr); void delay_process_unordered(); Delegator* m_delegator{}; PeerChunks* m_peerChunks{}; BlockTransfer* m_transfer{}; queues_type m_queues; int32_t m_affinity{-1}; std::chrono::microseconds m_last_choke{}; std::chrono::microseconds m_last_unchoke{}; size_t m_last_unordered_position{0}; torrent::utils::SchedulerEntry m_delay_remove_choked; torrent::utils::SchedulerEntry m_delay_process_unordered; }; inline RequestList::RequestList() { m_delay_remove_choked.slot() = [this] { delay_remove_choked(); }; m_delay_process_unordered.slot() = [this] { delay_process_unordered(); }; } // TODO: Make sure queued_size is never too small. inline uint32_t RequestList::pipe_size() const { return queued_size() + stalled_size() + unordered_size() / 4; } } // namespace torrent #endif libtorrent-0.16.17/src/runtime.cc000066400000000000000000000040171522271512000166320ustar00rootroot00000000000000#include "config.h" #include "runtime.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/network_manager.h" #include "torrent/runtime/runtime.h" #include "torrent/runtime/socket_manager.h" #include "torrent/runtime/proxy_manager.h" namespace torrent { Runtime* g_runtime{}; namespace runtime { bool is_initialized() { return g_runtime->is_initialized(); } bool is_shutting_down() { return g_runtime->is_shutdown_called(); } bool is_quick_shutting_down() { return g_runtime->is_quick_shutdown_called(); } void shutdown() { g_runtime->shutdown(); } void quick_shutdown() { g_runtime->quick_shutdown(); } NetworkConfig* network_config() { return g_runtime->network_config(); } NetworkManager* network_manager() { return g_runtime->network_manager(); } SocketManager* socket_manager() { return g_runtime->socket_manager(); } ProxyManager* proxy_manager() { return g_runtime->proxy_manager(); } } // namespace runtime Runtime::Runtime() : m_network_config(new runtime::NetworkConfig), m_network_manager(new runtime::NetworkManager), m_socket_manager(new runtime::SocketManager), m_proxy_manager(new runtime::ProxyManager) { } Runtime::~Runtime() = default; void Runtime::initialize() { g_runtime = new Runtime(); g_runtime->m_initialized = true; } void Runtime::shutdown() { g_runtime->m_shutdown_called = true; } void Runtime::quick_shutdown() { g_runtime->m_shutdown_called = true; g_runtime->m_quick_shutdown_called = true; // TODO: This should e.g. timeout all udp, http, dns, etc requests. } void Runtime::cleanup() { runtime::network_manager()->listen_close(); } void Runtime::destroy() { // Order matters for destruction. g_runtime->m_proxy_manager.reset(); g_runtime->m_network_manager.reset(); g_runtime->m_socket_manager.reset(); g_runtime->m_network_config.reset(); delete g_runtime; g_runtime = nullptr; } } // namespace torrent libtorrent-0.16.17/src/runtime.h000066400000000000000000000034031522271512000164720ustar00rootroot00000000000000#ifndef LIBTORRENT_RUNTIME_H #define LIBTORRENT_RUNTIME_H #include "torrent/runtime/runtime.h" namespace torrent { class LIBTORRENT_EXPORT Runtime { public: static void initialize(); static void shutdown(); static void quick_shutdown(); static void cleanup(); static void destroy(); bool is_initialized(); bool is_shutdown_called(); bool is_quick_shutdown_called(); auto* network_config(); auto* network_manager(); auto* socket_manager(); auto* proxy_manager(); private: Runtime(); ~Runtime(); std::unique_ptr m_network_config; std::unique_ptr m_network_manager; std::unique_ptr m_socket_manager; std::unique_ptr m_proxy_manager; align_cacheline std::atomic m_initialized{}; std::atomic m_shutdown_called{}; std::atomic m_quick_shutdown_called{}; }; inline bool Runtime::is_initialized() { return m_initialized.load(std::memory_order_acquire); } inline bool Runtime::is_shutdown_called() { return m_shutdown_called.load(std::memory_order_acquire); } inline bool Runtime::is_quick_shutdown_called() { return m_quick_shutdown_called.load(std::memory_order_acquire); } inline auto* Runtime::network_config() { return m_network_config.get(); } inline auto* Runtime::network_manager() { return m_network_manager.get(); } inline auto* Runtime::socket_manager() { return m_socket_manager.get(); } inline auto* Runtime::proxy_manager() { return m_proxy_manager.get(); } } // namespace torrent #endif // LIBTORRENT_RUNTIME_H libtorrent-0.16.17/src/thread_main.cc000066400000000000000000000076731522271512000174350ustar00rootroot00000000000000#include "config.h" #include "thread_main.h" #include "manager.h" #include "data/hash_queue.h" #include "torrent/data/file_manager.h" #include "torrent/net/resolver.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/network_manager.h" #include "torrent/runtime/socket_manager.h" #include "torrent/system/callbacks.h" #include "utils/instrumentation.h" namespace torrent { namespace main_thread { system::Thread* thread() { return ThreadMain::thread_base(); } std::thread::id thread_id() { return ThreadMain::thread_base()->thread_id(); } void callback(std::function&& fn) { ThreadMain::thread_base()->callback(std::move(fn)); } void callback(system::callback_id& id, std::function&& fn) { ThreadMain::thread_base()->callback(id, std::move(fn)); } void callback_interrupt(std::function&& fn) { ThreadMain::thread_base()->callback_interrupt(std::move(fn)); } void callback_interrupt(system::callback_id& id, std::function&& fn) { ThreadMain::thread_base()->callback_interrupt(id, std::move(fn)); } void cancel_callback(system::callback_id& id) { ThreadMain::thread_base()->cancel_callback(id); } void cancel_callback_and_wait(system::callback_id& id) { ThreadMain::thread_base()->cancel_callback_and_wait(id); } void set_client_callback(std::function fn) { ThreadMain::thread_main()->set_client_callback(std::move(fn)); } // TODO: Not thread safe. uint32_t hash_queue_size() { return ThreadMain::thread_main()->hash_queue()->size(); } } // namespace main_thread ThreadMain* ThreadMain::m_thread_main{}; system::Thread* ThreadMain::m_thread_base{}; ThreadMain::~ThreadMain() { cleanup_thread(); } void ThreadMain::create_thread() { m_thread_main = new ThreadMain; m_thread_base = m_thread_main; m_thread_main->m_events_callback_id = system::make_callback_id(); m_thread_main->m_hash_queue = std::make_unique(); } void ThreadMain::init_thread() { m_resolver = std::make_unique(); m_state = STATE_INITIALIZED; m_instrumentation_index = INSTRUMENTATION_POLLING_DO_POLL_MAIN - INSTRUMENTATION_POLLING_DO_POLL; init_thread_local(); // We should only initialize things here that depend on main thread, as we want to call // 'init_thread()' before 'torrent::initalize()'. } void ThreadMain::init_after_setup() { set_max_connections(); runtime::network_config()->subscribe_to_changes(this, [this]() { callback(m_events_callback_id, []() { runtime::network_manager()->listen_restart(); // TODO: Restart DHT. }); }); runtime::socket_manager()->subscribe_to_changes(this, [this]() { callback(m_events_callback_id, ThreadMain::set_max_connections); }); } void ThreadMain::cleanup_thread() { runtime::socket_manager()->unsubscribe_from_changes(this); runtime::network_config()->unsubscribe_from_changes(this); cancel_callback(m_events_callback_id); m_hash_queue.reset(); m_thread_main = nullptr; m_thread_base = nullptr; m_self = nullptr; } void ThreadMain::set_client_callback(std::function fn) { m_slot_client_callback = std::move(fn); } void ThreadMain::set_max_connections() { auto max_open_files = runtime::socket_manager()->category_max_size(runtime::category_files); manager->file_manager()->set_max_open_files(max_open_files); } void ThreadMain::call_events() { if (m_slot_client_callback) m_slot_client_callback(); process_callbacks(); } std::chrono::microseconds ThreadMain::next_timeout() { return std::chrono::microseconds(10s); } } // namespace torrent libtorrent-0.16.17/src/thread_main.h000066400000000000000000000027471522271512000172740ustar00rootroot00000000000000#ifndef LIBTORRENT_THREAD_MAIN_H #define LIBTORRENT_THREAD_MAIN_H #include #include "torrent/common.h" #include "torrent/system/thread.h" class TestMainThread; namespace torrent { class HashQueue; class LIBTORRENT_EXPORT ThreadMain : public system::Thread { public: ~ThreadMain() override; static void create_thread(); static ThreadMain* thread_main() { return m_thread_main; } static system::Thread* thread_base() { return m_thread_base; } const char* name() const override { return "rtorrent-main"; } void init_thread() override; void init_after_setup(); void cleanup_thread() override; void set_client_callback(std::function fn); HashQueue* hash_queue() { return m_hash_queue.get(); } protected: friend class ::TestMainThread; ThreadMain() = default; void call_events() override; std::chrono::microseconds next_timeout() override; static void set_thread_base(system::Thread* thread) { m_thread_base = thread; } private: static void set_max_connections(); static ThreadMain* m_thread_main; static system::Thread* m_thread_base; system::callback_id m_events_callback_id; std::unique_ptr m_hash_queue; std::function m_slot_client_callback; }; } // namespace torrent #endif // LIBTORRENT_THREAD_MAIN_H libtorrent-0.16.17/src/torrent/000077500000000000000000000000001522271512000163335ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/Makefile.am000066400000000000000000000134451522271512000203760ustar00rootroot00000000000000noinst_LTLIBRARIES = libtorrent_torrent.la libtorrent_torrent_la_SOURCES = \ data/block.cc \ data/block.h \ data/block_failed.h \ data/block_list.cc \ data/block_list.h \ data/block_transfer.h \ data/chunk_utils.cc \ data/chunk_utils.h \ data/download_data.cc \ data/download_data.h \ data/file.cc \ data/file.h \ data/file_list.cc \ data/file_list.h \ data/file_list_iterator.cc \ data/file_list_iterator.h \ data/file_manager.cc \ data/file_manager.h \ data/file_utils.cc \ data/file_utils.h \ data/piece.h \ data/transfer_list.cc \ data/transfer_list.h \ \ download/choke_group.cc \ download/choke_group.h \ download/choke_queue.cc \ download/choke_queue.h \ download/download_manager.cc \ download/download_manager.h \ download/group_entry.h \ download/resource_manager.cc \ download/resource_manager.h \ \ net/address_info.cc \ net/address_info.h \ net/connection_state.h \ net/fd.cc \ net/fd.h \ net/http_get.cc \ net/http_get.h \ net/http_stack.cc \ net/http_stack.h \ net/resolver.cc \ net/resolver.h \ net/socket_address.cc \ net/socket_address.h \ net/socket_address_key.cc \ net/socket_address_key.h \ net/types.h \ \ peer/choke_status.h \ peer/client_info.cc \ peer/client_info.h \ peer/client_list.cc \ peer/client_list.h \ peer/connection_list.cc \ peer/connection_list.h \ peer/peer.cc \ peer/peer.h \ peer/peer_info.cc \ peer/peer_info.h \ peer/peer_list.cc \ peer/peer_list.h \ \ runtime/network_config.cc \ runtime/network_config.h \ runtime/network_manager.cc \ runtime/network_manager.h \ runtime/proxy_manager.cc \ runtime/proxy_manager.h \ runtime/runtime.cc \ runtime/runtime.h \ runtime/socket_manager.cc \ runtime/socket_manager.h \ \ system/callbacks.h \ system/poll.h \ system/poll_epoll.cc \ system/poll_kqueue.cc \ system/system.cc \ system/system.h \ system/thread.cc \ system/thread.h \ \ tracker/dht_controller.cc \ tracker/dht_controller.h \ tracker/manager.cc \ tracker/manager.h \ tracker/tracker.cc \ tracker/tracker.h \ tracker/tracker_state.cc \ tracker/tracker_state.h \ tracker/wrappers.cc \ tracker/wrappers.h \ \ types/string_utf8.cc \ types/string_utf8.h \ \ utils/chrono.h \ utils/directory_events.cc \ utils/directory_events.h \ utils/extents.h \ utils/file_stat.h \ utils/log.cc \ utils/log.h \ utils/log_buffer.cc \ utils/log_buffer.h \ utils/option_strings.cc \ utils/option_strings.h \ utils/random.cc \ utils/random.h \ utils/ranges.h \ utils/resume.cc \ utils/resume.h \ utils/scheduler.cc \ utils/scheduler.h \ utils/string_manip.cc \ utils/string_manip.h \ utils/unordered_vector.h \ utils/uri_parser.cc \ utils/uri_parser.h \ \ bitfield.cc \ bitfield.h \ chunk_manager.cc \ chunk_manager.h \ common.h \ download.cc \ download.h \ download_info.h \ event.cc \ event.h \ exceptions.cc \ exceptions.h \ hash_string.h \ object.cc \ object.h \ object_raw_bencode.h \ object_static_map.cc \ object_static_map.h \ object_stream.cc \ object_stream.h \ path.cc \ path.h \ rate.cc \ rate.h \ throttle.cc \ throttle.h \ torrent.cc \ torrent.h AM_CPPFLAGS = -I$(srcdir) -I$(srcdir)/.. -I$(top_srcdir) libtorrent_torrent_data_includedir = $(includedir)/torrent/data libtorrent_torrent_data_include_HEADERS = \ data/block.h \ data/block_list.h \ data/block_transfer.h \ data/chunk_utils.h \ data/download_data.h \ data/file.h \ data/file_list.h \ data/file_list_iterator.h \ data/file_manager.h \ data/file_utils.h \ data/piece.h \ data/transfer_list.h libtorrent_torrent_download_includedir = $(includedir)/torrent/download libtorrent_torrent_download_include_HEADERS = \ download/choke_group.h \ download/choke_queue.h \ download/download_manager.h \ download/group_entry.h \ download/resource_manager.h \ download/types.h libtorrent_torrent_net_includedir = $(includedir)/torrent/net libtorrent_torrent_net_include_HEADERS = \ net/address_info.h \ net/connection_state.h \ net/fd.h \ net/http_get.h \ net/http_stack.h \ net/resolver.h \ net/socket_address.h \ net/socket_address_key.h \ net/types.h libtorrent_torrent_peer_includedir = $(includedir)/torrent/peer libtorrent_torrent_peer_include_HEADERS = \ peer/choke_status.h \ peer/client_info.h \ peer/client_list.h \ peer/connection_list.h \ peer/peer.h \ peer/peer_info.h \ peer/peer_list.h libtorrent_torrent_runtime_includedir = $(includedir)/torrent/runtime libtorrent_torrent_runtime_include_HEADERS = \ runtime/network_config.h \ runtime/network_manager.h \ runtime/proxy_manager.h \ runtime/runtime.h \ runtime/socket_manager.h libtorrent_torrent_system_includedir = $(includedir)/torrent/system libtorrent_torrent_system_include_HEADERS = \ system/callbacks.h \ system/poll.h \ system/system.h \ system/thread.h libtorrent_torrent_tracker_includedir = $(includedir)/torrent/tracker libtorrent_torrent_tracker_include_HEADERS = \ tracker/dht_controller.h \ tracker/manager.h \ tracker/tracker.h \ tracker/tracker_state.h \ tracker/wrappers.h libtorrent_torrent_types_includedir = $(includedir)/torrent/types libtorrent_torrent_types_include_HEADERS = \ types/string_utf8.h libtorrent_torrent_utils_includedir = $(includedir)/torrent/utils libtorrent_torrent_utils_include_HEADERS = \ utils/chrono.h \ utils/directory_events.h \ utils/extents.h \ utils/file_stat.h \ utils/log.h \ utils/log_buffer.h \ utils/option_strings.h \ utils/random.h \ utils/ranges.h \ utils/resume.h \ utils/scheduler.h \ utils/string_manip.h \ utils/unordered_vector.h \ utils/uri_parser.h libtorrent_torrent_includedir = $(includedir)/torrent libtorrent_torrent_include_HEADERS = \ bitfield.h \ chunk_manager.h \ common.h \ download.h \ download_info.h \ exceptions.h \ event.h \ hash_string.h \ object.h \ object_raw_bencode.h \ object_static_map.h \ object_stream.h \ path.h \ rate.h \ throttle.h \ torrent.h libtorrent-0.16.17/src/torrent/bitfield.cc000066400000000000000000000050631522271512000204300ustar00rootroot00000000000000#include "config.h" #include "bitfield.h" #include #include #include "exceptions.h" #include "utils/instrumentation.h" namespace torrent { void Bitfield::set_size_bits(size_type s) { if (m_data != NULL) throw internal_error("Bitfield::set_size_bits(size_type s) m_data != NULL."); m_size = s; } void Bitfield::set_size_set(size_type s) { if (s > m_size || m_data != NULL) throw internal_error("Bitfield::set_size_set(size_type s) s > m_size."); m_set = s; } void Bitfield::allocate() { if (m_data != nullptr) return; m_data = std::make_unique(size_bytes()); instrumentation_update(INSTRUMENTATION_MEMORY_BITFIELDS, static_cast(size_bytes())); } void Bitfield::unallocate() { if (m_data == nullptr) return; m_data = nullptr; instrumentation_update(INSTRUMENTATION_MEMORY_BITFIELDS, -static_cast(size_bytes())); } void Bitfield::update() { // Clears the unused bits. clear_tail(); m_set = 0; iterator itr = m_data.get(); iterator last = end(); // TODO: Eliminate the extra branches below by making the bitfield m_data 8-byte aligned and padded. while (itr + sizeof(uint64_t) <= last) { // Compilers optimize this completely into a single 64-bit load instruction (MOV/LDR) uint64_t chunk; std::memcpy(&chunk, itr, sizeof(uint64_t)); m_set += std::popcount(chunk); itr += sizeof(uint64_t); } if (itr + sizeof(uint32_t) <= last) { uint32_t chunk; std::memcpy(&chunk, itr, sizeof(uint32_t)); m_set += std::popcount(chunk); itr += sizeof(uint32_t); } while (itr != last) m_set += std::popcount(*itr++); } void Bitfield::copy(const Bitfield& bf) { unallocate(); m_size = bf.m_size; m_set = bf.m_set; if (bf.m_data == nullptr) { m_data = nullptr; return; } allocate(); std::copy_n(bf.m_data.get(), size_bytes(), m_data.get()); } void Bitfield::swap(Bitfield& bf) noexcept { std::swap(m_size, bf.m_size); std::swap(m_set, bf.m_set); std::swap(m_data, bf.m_data); } void Bitfield::set_all() { m_set = m_size; std::fill_n(m_data.get(), size_bytes(), ~value_type{}); clear_tail(); } void Bitfield::unset_all() { m_set = 0; std::fill_n(m_data.get(), size_bytes(), value_type{}); } // Quick hack. Speed improvements would require that m_set is kept // up-to-date. void Bitfield::set_range(size_type first, size_type last) { while (first != last) set(first++); } void Bitfield::unset_range(size_type first, size_type last) { while (first != last) unset(first++); } } // namespace torrent libtorrent-0.16.17/src/torrent/bitfield.h000066400000000000000000000066251522271512000202770ustar00rootroot00000000000000#ifndef LIBTORRENT_BITFIELD_H #define LIBTORRENT_BITFIELD_H #include #include namespace torrent { class LIBTORRENT_EXPORT Bitfield { public: using size_type = uint32_t; using value_type = uint8_t; using const_value_type = const uint8_t; using iterator = value_type*; using const_iterator = const value_type*; Bitfield() = default; ~Bitfield() { clear(); } Bitfield(const Bitfield&) = delete; Bitfield& operator=(const Bitfield&) = delete; bool empty() const { return m_data == nullptr; } bool is_all_set() const { return m_set == m_size; } bool is_all_unset() const { return m_set == 0; } bool is_tail_cleared() const { return m_size % 8 == 0 || !((*(end() - 1) & mask_from(m_size % 8))); } size_type size_bits() const { return m_size; } size_type size_bytes() const { return (m_size + 7) / 8; } size_type size_set() const { return m_set; } size_type size_unset() const { return m_size - m_set; } void set_size_bits(size_type s); void set_size_set(size_type s); // Call update if you've changed the data directly and want to // update the counters and unset the last unused bits. // // Resize clears the data? void update(); void allocate(); void unallocate(); void clear() { unallocate(); m_size = 0; m_set = 0; } void clear_tail() { if (m_size % 8) *(end() - 1) &= mask_before(m_size % 8); } void copy(const Bitfield& bf); void swap(Bitfield& bf) noexcept; void set_all(); void set_range(size_type first, size_type last); void unset_all(); void unset_range(size_type first, size_type last); bool get(size_type idx) const { return m_data[idx / 8] & mask_at(idx % 8); } void set(size_type idx) { m_set += !get(idx); m_data[idx / 8] |= mask_at(idx % 8); } void unset(size_type idx) { m_set -= get(idx); m_data[idx / 8] &= ~mask_at(idx % 8); } iterator begin() { return m_data.get(); } const_iterator begin() const { return m_data.get(); } iterator end() { return m_data != nullptr ? m_data.get() + size_bytes() : m_data.get(); } const_iterator end() const { return m_data != nullptr ? m_data.get() + size_bytes() : m_data.get(); } size_type position(const_iterator itr) const { return (itr - m_data.get()) * 8; } void from_c_str(const char* str) { std::memcpy(m_data.get(), str, size_bytes()); update(); } // Remember to use modulo. static value_type mask_at(size_type idx) { return 1 << (7 - idx); } static value_type mask_before(size_type idx) { return value_type{0xff} << (8 - idx); } static value_type mask_from(size_type idx) { return value_type{0xff} >> idx; } private: size_type m_size{}; size_type m_set{}; std::unique_ptr m_data; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/chunk_manager.cc000066400000000000000000000106161522271512000214500ustar00rootroot00000000000000#include "config.h" #include "torrent/chunk_manager.h" #include #include #include "data/chunk_list.h" #include "torrent/exceptions.h" #include "utils/instrumentation.h" namespace torrent { ChunkManager::ChunkManager() = default; ChunkManager::~ChunkManager() { assert(m_memoryUsage == 0 && "ChunkManager::~ChunkManager() m_memoryUsage != 0."); assert(m_memoryBlockCount == 0 && "ChunkManager::~ChunkManager() m_memoryBlockCount != 0."); } uint64_t ChunkManager::sync_queue_memory_usage() const { uint64_t size = 0; for (auto chunk : *this) size += chunk->queue_size() * chunk->chunk_size(); return size; } uint32_t ChunkManager::sync_queue_size() const { uint32_t size = 0; for (auto chunk : *this) size += chunk->queue_size(); return size; } uint64_t ChunkManager::estimate_max_memory_usage() { rlimit rlp; #ifdef RLIMIT_AS if (getrlimit(RLIMIT_AS, &rlp) == 0 && rlp.rlim_cur != RLIM_INFINITY) #else if (getrlimit(RLIMIT_DATA, &rlp) == 0 && rlp.rlim_cur != RLIM_INFINITY) #endif return rlp.rlim_cur; return uint64_t{DEFAULT_ADDRESS_SPACE_SIZE} << 20; } uint64_t ChunkManager::safe_free_diskspace() const { return m_memoryUsage + (uint64_t{512} << 20); } void ChunkManager::insert(ChunkList* chunkList) { chunkList->set_manager(this); base_type::push_back(chunkList); } void ChunkManager::erase(ChunkList* chunkList) { if (chunkList->queue_size() != 0) throw internal_error("ChunkManager::erase(...) chunkList->queue_size() != 0."); auto itr = std::find(base_type::begin(), base_type::end(), chunkList); if (itr == base_type::end()) throw internal_error("ChunkManager::erase(...) itr == base_type::end()."); std::iter_swap(itr, --base_type::end()); base_type::pop_back(); chunkList->set_manager(NULL); } bool ChunkManager::allocate(uint32_t size, int flags) { if (m_memoryUsage + size > (3 * m_maxMemoryUsage) / 4) try_free_memory((1 * m_maxMemoryUsage) / 4); if (m_memoryUsage + size > m_maxMemoryUsage) { if (!(flags & allocate_dont_log)) instrumentation_update(INSTRUMENTATION_MINCORE_ALLOC_FAILED, 1); return false; } if (!(flags & allocate_dont_log)) instrumentation_update(INSTRUMENTATION_MINCORE_ALLOCATIONS, size); m_memoryUsage += size; m_memoryBlockCount++; instrumentation_update(INSTRUMENTATION_MEMORY_CHUNK_COUNT, 1); instrumentation_update(INSTRUMENTATION_MEMORY_CHUNK_USAGE, size); return true; } void ChunkManager::deallocate(uint32_t size, int flags) { if (size > m_memoryUsage) throw internal_error("ChunkManager::deallocate(...) size > m_memoryUsage."); if (!(flags & allocate_dont_log)) { if (flags & allocate_revert_log) instrumentation_update(INSTRUMENTATION_MINCORE_ALLOCATIONS, -size); else instrumentation_update(INSTRUMENTATION_MINCORE_DEALLOCATIONS, size); } m_memoryUsage -= size; m_memoryBlockCount--; instrumentation_update(INSTRUMENTATION_MEMORY_CHUNK_COUNT, -1); instrumentation_update(INSTRUMENTATION_MEMORY_CHUNK_USAGE, -static_cast(size)); } void ChunkManager::try_free_memory(uint64_t size) { // Ensure that we don't call this function too often when futile as // it might be somewhat expensive. // // Note that it won't be able to free chunks that are scheduled for // hash checking, so a too low max memory setting will give problem // at high transfer speed. if (m_timerStarved + 10 >= this_thread::cached_seconds().count()) return; sync_all(0, size <= m_memoryUsage ? (m_memoryUsage - size) : 0); // The caller must ensure he tries to free a sufficiently large // amount of memory to ensure it, and other users, has enough memory // space for at least 10 seconds. m_timerStarved = this_thread::cached_seconds().count(); } void ChunkManager::periodic_sync() { sync_all(ChunkList::sync_use_timeout, 0); } void ChunkManager::sync_all(int flags, uint64_t target) { if (empty()) return; // Start from the next entry so that we don't end up repeatedly // syncing the same torrent. m_lastFreed = m_lastFreed % base_type::size() + 1; auto itr = base_type::begin() + m_lastFreed; ChunkList::cache_list cache; do { if (itr == base_type::end()) itr = base_type::begin(); (*itr)->sync_chunks(cache, static_cast(flags)); } while (++itr != base_type::begin() + m_lastFreed && m_memoryUsage >= target); m_lastFreed = itr - begin(); } } // namespace torrent libtorrent-0.16.17/src/torrent/chunk_manager.h000066400000000000000000000125031522271512000213070ustar00rootroot00000000000000#ifndef LIBTORRENT_CHUNK_MANAGER_H #define LIBTORRENT_CHUNK_MANAGER_H #include #include namespace torrent { // TODO: Currently all chunk lists are inserted, despite the download // not being open/active. class LIBTORRENT_EXPORT ChunkManager : private std::vector { public: using base_type = std::vector; using size_type = uint32_t; using base_type::iterator; using base_type::reverse_iterator; using base_type::const_iterator; using base_type::begin; using base_type::end; using base_type::size; using base_type::empty; ChunkManager(); ~ChunkManager(); uint64_t memory_usage() const { return m_memoryUsage; } uint64_t sync_queue_memory_usage() const; uint32_t memory_block_count() const { return m_memoryBlockCount; } uint32_t sync_queue_size() const; // Should we allow the client to reserve some memory? // The client should set this automatically if ulimit is set. uint64_t max_memory_usage() const { return m_maxMemoryUsage; } void set_max_memory_usage(uint64_t bytes) { m_maxMemoryUsage = bytes; } // Estimate the max memory usage possible, capped at 1GB. static uint64_t estimate_max_memory_usage(); uint64_t safe_free_diskspace() const; bool safe_sync() const { return m_safeSync; } void set_safe_sync(uint32_t state) { m_safeSync = state; } // Set the interval to wait after the last write to a chunk before // trying to sync it. By not forcing a sync too early it should give // the kernel an oppertunity to sync at its convenience. uint32_t timeout_sync() const { return m_timeoutSync; } void set_timeout_sync(uint32_t seconds) { m_timeoutSync = seconds; } uint32_t timeout_safe_sync() const { return m_timeoutSafeSync; } void set_timeout_safe_sync(uint32_t seconds) { m_timeoutSafeSync = seconds; } // Set to 0 to disable preloading. // // How the value is used is yet to be determined, but it won't be // able to use actual requests in the request queue as we can easily // stay ahead of it causing preloading to fail. uint32_t preload_type() const { return m_preloadType; } void set_preload_type(uint32_t t) { m_preloadType = t; } uint32_t preload_min_size() const { return m_preloadMinSize; } void set_preload_min_size(uint32_t bytes) { m_preloadMinSize = bytes; } // Required rate before attempting to preload chunk, per whole // megabyte of chunk size. uint32_t preload_required_rate() const { return m_preloadRequiredRate; } void set_preload_required_rate(uint32_t bytes) { m_preloadRequiredRate = bytes; } void insert(ChunkList* chunkList); void erase(ChunkList* chunkList); // The client may use these functions to affect the library's memory // usage by indicating how much it uses. This shouldn't really be // nessesary unless the client maps large amounts of memory. // // If the caller finds out the allocated memory quota isn't needed // due to e.g. other errors then 'deallocate_unused' must be called // within the context of the original 'allocate' caller in order to // properly be reflected when logging. // // The primary user of these functions is ChunkList. static constexpr int allocate_revert_log = (1 << 0); static constexpr int allocate_dont_log = (1 << 1); bool allocate(uint32_t size, int flags = 0); void deallocate(uint32_t size, int flags = 0); void try_free_memory(uint64_t size); void periodic_sync(); // Not sure if I wnt these here. Consider implementing a generic // statistics API. uint32_t stats_preloaded() const { return m_statsPreloaded; } void inc_stats_preloaded() { m_statsPreloaded++; } uint32_t stats_not_preloaded() const { return m_statsNotPreloaded; } void inc_stats_not_preloaded() { m_statsNotPreloaded++; } private: ChunkManager(const ChunkManager&) = delete; ChunkManager& operator=(const ChunkManager&) = delete; void sync_all(int flags, uint64_t target) LIBTORRENT_NO_EXPORT; uint64_t m_memoryUsage{0}; // 1/5 of the available memory should be enough for the client. If // the client really requires alot more memory it should call this // itself. uint64_t m_maxMemoryUsage{(estimate_max_memory_usage() * 4) / 5}; uint32_t m_memoryBlockCount{0}; bool m_safeSync{false}; uint32_t m_timeoutSync{600}; uint32_t m_timeoutSafeSync{900}; uint32_t m_preloadType{0}; uint32_t m_preloadMinSize{256 << 10}; uint32_t m_preloadRequiredRate{5 << 10}; uint32_t m_statsPreloaded{0}; uint32_t m_statsNotPreloaded{0}; int32_t m_timerStarved{0}; size_type m_lastFreed{0}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/common.h000066400000000000000000000076421522271512000200050ustar00rootroot00000000000000#ifndef LIBTORRENT_COMMON_H #define LIBTORRENT_COMMON_H #include #include #include #include #include #include #include #include #include #include #include struct sockaddr; struct sockaddr_in; struct sockaddr_in6; struct sockaddr_un; using namespace std::chrono_literals; namespace torrent { namespace system { using callback_id = std::shared_ptr>; } // namespace system enum priority_enum { PRIORITY_OFF = 0, PRIORITY_NORMAL, PRIORITY_HIGH }; enum tracker_enum { TRACKER_NONE, TRACKER_HTTP, TRACKER_UDP, TRACKER_DHT, }; // Just forward declare everything here so we can keep the actual // headers clean. class AddressList; class AvailableList; class Bitfield; class Block; class BlockFailed; class BlockList; class BlockTransfer; class Chunk; class ChunkList; class ChunkManager; class ChunkSelector; class ClientInfo; class ClientList; class ConnectionList; class ConnectionManager; class DhtRouter; class Download; class DownloadInfo; class DownloadMain; class DownloadWrapper; class FileList; class FileManager; class Event; class File; class FileList; class Handshake; class HandshakeManager; class HashString; class Listen; class MemoryChunk; class Object; class Path; class Peer; class PeerConnectionBase; class PeerInfo; class PeerList; class Piece; class ProtocolExtension; class Rate; class ResourceManager; class Throttle; class TrackerController; class TrackerList; class TransferList; namespace net { class HttpGet; class HttpStack; class Resolver; } // namespace net namespace runtime { class NetworkConfig; class NetworkManager; class SocketManager; } // namespace runtime namespace tracker { class DhtController; class Manager; class Tracker; } // namespace tracker namespace system { class Poll; class Thread; } // namespace system namespace utils { class Scheduler; class SchedulerEntry; } // namespace utils } // namespace torrent // This should only need to be set when compiling libtorrent. #ifdef SUPPORT_ATTRIBUTE_VISIBILITY #define LIBTORRENT_NO_EXPORT __attribute__ ((visibility("hidden"))) #define LIBTORRENT_EXPORT __attribute__ ((visibility("default"))) #else #define LIBTORRENT_NO_EXPORT #define LIBTORRENT_EXPORT #endif #define align_cacheline alignas(LT_SMP_CACHE_BYTES) namespace torrent::this_thread { system::Thread* thread() LIBTORRENT_EXPORT; const char* thread_name() LIBTORRENT_EXPORT; std::string thread_name_str() LIBTORRENT_EXPORT; std::thread::id thread_id() LIBTORRENT_EXPORT; std::chrono::microseconds cached_time() LIBTORRENT_EXPORT; std::chrono::seconds cached_seconds() LIBTORRENT_EXPORT; system::Poll* poll() LIBTORRENT_EXPORT; net::Resolver* resolver() LIBTORRENT_EXPORT; utils::Scheduler* scheduler() LIBTORRENT_EXPORT; } // namespace torrent::this_thread namespace torrent::disk_thread { system::Thread* thread() LIBTORRENT_EXPORT; std::thread::id thread_id() LIBTORRENT_EXPORT; } // namespace torrent::disk_thread namespace torrent::main_thread { system::Thread* thread() LIBTORRENT_EXPORT; std::thread::id thread_id() LIBTORRENT_EXPORT; void set_client_callback(std::function fn) LIBTORRENT_EXPORT; uint32_t hash_queue_size() LIBTORRENT_EXPORT; } // namespace torrent::main_thread namespace torrent::net_thread { system::Thread* thread() LIBTORRENT_EXPORT; std::thread::id thread_id() LIBTORRENT_EXPORT; // TODO: Move to runtime. net::HttpStack* http_stack() LIBTORRENT_EXPORT; } // namespace torrent::net_thread namespace torrent::tracker_thread { system::Thread* thread() LIBTORRENT_EXPORT; std::thread::id thread_id() LIBTORRENT_EXPORT; tracker::Manager* manager() LIBTORRENT_EXPORT; } // namespace torrent::tracker_thread #endif libtorrent-0.16.17/src/torrent/data/000077500000000000000000000000001522271512000172445ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/data/block.cc000066400000000000000000000270311522271512000206500ustar00rootroot00000000000000#include "config.h" #include #include #include "peer/peer_info.h" #include "protocol/peer_connection_base.h" #include "block.h" #include "block_failed.h" #include "block_list.h" #include "block_transfer.h" #include "exceptions.h" namespace torrent { Block::~Block() { assert((m_state == STATE_INCOMPLETE || m_state == STATE_COMPLETED) && "Block dtor with 'm_state != STATE_INCOMPLETE && m_state != STATE_COMPLETED'"); if (m_state == STATE_COMPLETED) { assert(m_leader != NULL && "Block dtor with 'm_state == STATE_COMPLETED && m_leader == NULL'"); m_leader->set_peer_info(NULL); } m_leader = NULL; m_state = STATE_INVALID; for (auto& block : m_queued) { invalidate_transfer(block); } m_queued.clear(); for (auto& block : m_transfers) { invalidate_transfer(block); } m_transfers.clear(); assert(m_notStalled == 0 && "Block::clear() m_stalled != 0."); delete m_failedList; } bool Block::is_finished() const { return m_leader != nullptr && m_leader->is_finished(); } bool Block::is_transfering() const { return m_leader != nullptr && !m_leader->is_finished(); } BlockTransfer* Block::insert(PeerInfo* peerInfo) { if (find_queued(peerInfo) || find_transfer(peerInfo)) return NULL; m_notStalled++; auto block = new BlockTransfer; block->set_peer_info(peerInfo); block->set_block(this); block->set_piece(m_piece); block->set_state(BlockTransfer::STATE_QUEUED); block->set_request_time(this_thread::cached_seconds().count()); block->set_position(0); block->set_stall(0); block->set_failed_index(BlockFailed::invalid_index); return m_queued.emplace_back(block); } void Block::erase(BlockTransfer* transfer) { if (transfer->is_erased()) throw internal_error("Block::erase(...) transfer already erased"); if (transfer->peer_info() != NULL) throw internal_error("Block::erase(...) transfer has non-null peer info"); m_notStalled -= transfer->stall() == 0; if (transfer->is_queued()) { auto itr = std::find(m_queued.begin(), m_queued.end(), transfer); if (itr == m_queued.end()) throw internal_error("Block::erase(...) Could not find transfer."); m_queued.erase(itr); } else if (!transfer->is_finished()) { auto itr = std::find(m_transfers.begin(), m_transfers.end(), transfer); if (itr == m_transfers.end()) throw internal_error("Block::erase(...) Could not find transfer."); // Need to do something different here for now, i think. m_transfers.erase(itr); if (transfer == m_leader) { if (m_state == STATE_COMPLETED) throw internal_error("Block::erase with 'transfer == m_transfer && m_state == STATE_COMPLETED'"); // When the leader is erased then any non-leading transfer must // be promoted. These non-leading transfers are guaranteed to // have the same data up to their position. PeerConnectionBase // assumes that a Block with non-leaders have a leader. // Create a range containing transfers with // is_not_leader(). Erased transfer will end up in the back. auto first = std::find_if_not(m_transfers.begin(), m_transfers.end(), std::mem_fn(&BlockTransfer::is_leader)); auto last = std::stable_partition(first, m_transfers.end(), std::mem_fn(&BlockTransfer::is_not_leader)); auto new_leader = std::max_element(first, last, [](BlockTransfer* t1, BlockTransfer* t2) { return t1->position() < t2->position(); }); if (new_leader != last) { m_leader = *new_leader; m_leader->set_state(BlockTransfer::STATE_LEADER); } else { m_leader = NULL; // If we have no new leader, remove the erased (dissimilar) // transfers so they can get another shot. They cannot be // removed when found dissimilar as that would result in them // being queued immediately. remove_erased_transfers(); } } } else { throw internal_error("Block::erase(...) Transfer is finished."); } // Check if we need to check for peer_info not being null. transfer->set_block(NULL); delete transfer; } bool Block::transfering(BlockTransfer* transfer) { if (!transfer->is_valid()) throw internal_error("Block::transfering(...) transfer->block() == NULL."); auto itr = std::find(m_queued.begin(), m_queued.end(), transfer); if (itr == m_queued.end()) throw internal_error("Block::transfering(...) not queued."); m_queued.erase(itr); m_transfers.push_back(transfer); // If this block already has an active transfer, make this transfer // skip the piece. If this transfer gets ahead of the currently // transfering, it will (a) take over as the leader if the data is // the same or (b) erase itself from this block if the data does not // match. if (m_leader != NULL) { transfer->set_state(BlockTransfer::STATE_NOT_LEADER); return false; } else { m_leader = transfer; transfer->set_state(BlockTransfer::STATE_LEADER); return true; } } // TODO: Don't depend on m_leader for access to block transfer data of // done peer_info, etc. bool Block::completed(BlockTransfer* transfer) { if (!transfer->is_valid()) throw internal_error("Block::completed(...) transfer->block() == NULL."); if (!transfer->is_leader()) throw internal_error("Block::completed(...) transfer is not the leader."); // Special case where another ignored transfer finished before the // leader? // // Perhaps do magic to the transfer, erase it or something. if (!is_finished()) throw internal_error("Block::completed(...) !is_finished()."); if (transfer != m_leader) throw internal_error("Block::completed(...) transfer != m_leader."); m_parent->inc_finished(); if (static_cast(std::count_if(m_parent->begin(), m_parent->end(), std::mem_fn(&Block::is_finished))) < m_parent->finished()) throw internal_error("Block::completed(...) Finished blocks too large."); m_notStalled -= transfer->stall() == 0; transfer->set_block(NULL); transfer->set_stall(~uint32_t()); // Currently just throw out the queued transfers. In case the hash // check fails, we might consider telling pcb during the call to // Block::transfering(...). But that would propably not be correct // as we want to trigger cancel messages from here, as hash fail is // a rare occurrence. for (const auto& block : m_queued) { invalidate_transfer(block); } m_queued.clear(); // We need to invalidate those unfinished and keep the one that // finished for later reference. remove_non_leader_transfers(); // We now know that all transfers except the current leader we're // handling has been invalidated. if (m_transfers.empty() || m_transfers.back() != transfer) throw internal_error("Block::completed(...) m_transfers.empty() || m_transfers.back() != transfer."); m_state = STATE_COMPLETED; return m_parent->is_all_finished(); } void Block::retry_transfer() { m_state = STATE_INCOMPLETE; } // Mark a non-leading transfer as having received dissimilar data to // the leader. It is then marked as erased so that we know its data // was not used, yet keep it in m_transfers so as not to cause a // re-download. void Block::transfer_dissimilar(BlockTransfer* transfer) { if (!transfer->is_not_leader() || m_leader == transfer) throw internal_error("Block::transfer_dissimilar(...) transfer is the leader."); m_notStalled -= transfer->stall() == 0; // Why not just delete? Gets done by completed(), though when // erasing the leader we need to remove dissimilar unless we have // another leader. transfer->set_state(BlockTransfer::STATE_ERASED); transfer->set_position(0); transfer->set_block(NULL); } void Block::stalled(BlockTransfer* transfer) { if (!transfer->is_valid()) return; transfer->block()->stalled_transfer(transfer); } void Block::stalled_transfer(BlockTransfer* transfer) { if (transfer->stall() == 0) { if (m_notStalled == 0) throw internal_error("Block::stalled(...) m_notStalled == 0."); m_notStalled--; // Do magic here. } transfer->set_stall(transfer->stall() + 1); } void Block::change_leader(BlockTransfer* transfer) { if (m_leader == transfer) throw internal_error("Block::change_leader(...) m_leader == transfer."); if (is_finished()) throw internal_error("Block::change_leader(...) is_finished()."); if (m_leader != NULL) m_leader->set_state(BlockTransfer::STATE_NOT_LEADER); m_leader = transfer; m_leader->set_state(BlockTransfer::STATE_LEADER); } void Block::failed_leader() { if (!is_finished()) throw internal_error("Block::failed_leader(...) !is_finished()."); m_leader = NULL; if (m_failedList != NULL) m_failedList->set_current(BlockFailed::invalid_index); } void Block::create_dummy(BlockTransfer* transfer, PeerInfo* peerInfo, const Piece& piece) { transfer->set_peer_info(peerInfo); transfer->set_block(NULL); transfer->set_piece(piece); transfer->set_state(BlockTransfer::STATE_ERASED); transfer->set_position(0); transfer->set_stall(0); transfer->set_failed_index(BlockTransfer::invalid_index); } void Block::release(BlockTransfer* transfer) { transfer->set_peer_info(NULL); if (!transfer->is_valid()) delete transfer; else // TODO: Do we need to verify that the block is 'this'? transfer->block()->erase(transfer); } // // Private: // void Block::invalidate_transfer(BlockTransfer* transfer) { if (transfer == m_leader) throw internal_error("Block::invalidate_transfer(...) transfer == m_leader."); // Check if the block is this. transfer->set_block(NULL); if (transfer->stall() == 0) { if (m_notStalled == 0) throw internal_error("Block::invalidate_transfer(...) m_notStalled == 0."); m_notStalled--; } if (transfer->peer_info() == NULL) { delete transfer; return; // Consider if this should be an exception. } // Do the canceling magic here. if (transfer->peer_info()->connection() != NULL) transfer->peer_info()->connection()->cancel_transfer(transfer); } void Block::remove_erased_transfers() { auto split = std::stable_partition(m_transfers.begin(), m_transfers.end(), std::not_fn(std::mem_fn(&BlockTransfer::is_erased))); std::for_each(split, m_transfers.end(), [this](auto block) { invalidate_transfer(block); }); m_transfers.erase(split, m_transfers.end()); } void Block::remove_non_leader_transfers() { auto split = std::stable_partition(m_transfers.begin(), m_transfers.end(), std::mem_fn(&BlockTransfer::is_leader)); std::for_each(split, m_transfers.end(), [this](auto block) { invalidate_transfer(block); }); m_transfers.erase(split, m_transfers.end()); } BlockTransfer* Block::find_queued(const PeerInfo* p) { auto itr = std::find_if(m_queued.begin(), m_queued.end(), [p](BlockTransfer* t) { return p == t->peer_info(); }); if (itr == m_queued.end()) return NULL; else return *itr; } const BlockTransfer* Block::find_queued(const PeerInfo* p) const { auto itr = std::find_if(m_queued.begin(), m_queued.end(), [p](BlockTransfer* t) { return p == t->peer_info(); }); if (itr == m_queued.end()) return NULL; else return *itr; } BlockTransfer* Block::find_transfer(const PeerInfo* p) { auto itr = std::find_if(m_transfers.begin(), m_transfers.end(), [p](BlockTransfer* t) { return p == t->peer_info(); }); if (itr == m_transfers.end()) return NULL; else return *itr; } const BlockTransfer* Block::find_transfer(const PeerInfo* p) const { auto itr = std::find_if(m_transfers.begin(), m_transfers.end(), [p](BlockTransfer* t) { return p == t->peer_info(); }); if (itr == m_transfers.end()) return NULL; else return *itr; } } // namespace torrent libtorrent-0.16.17/src/torrent/data/block.h000066400000000000000000000117071522271512000205150ustar00rootroot00000000000000#ifndef LIBTORRENT_BLOCK_H #define LIBTORRENT_BLOCK_H #include #include #include #include namespace torrent { // If you start adding slots, make sure the rest of the code creates // copies and clears the original variables before calls to erase etc. class LIBTORRENT_EXPORT Block { public: // Using vectors as they will remain small, thus the cost of erase // should be small. Later we can do faster erase by ignoring the // ordering. using transfer_list_type = std::vector; using size_type = uint32_t; enum state_type { STATE_INCOMPLETE, STATE_COMPLETED, STATE_INVALID }; Block() = default; ~Block(); // Only allow move constructions Block(const Block&) = delete; Block& operator=(const Block&) = delete; Block(Block&&) = default; Block& operator=(Block&&) = default; bool is_stalled() const { return m_notStalled == 0; } bool is_finished() const; bool is_transfering() const; bool is_peer_queued(const PeerInfo* p) const { return find_queued(p) != NULL; } bool is_peer_transfering(const PeerInfo* p) const { return find_transfer(p) != NULL; } size_type size_all() const { return m_queued.size() + m_transfers.size(); } size_type size_not_stalled() const { return m_notStalled; } BlockList* parent() { return m_parent; } const BlockList* parent() const { return m_parent; } void set_parent(BlockList* p) { m_parent = p; } const Piece& piece() const { return m_piece; } void set_piece(const Piece& p) { m_piece = p; } uint32_t index() const { return m_piece.index(); } const transfer_list_type* queued() const { return &m_queued; } const transfer_list_type* transfers() const { return &m_transfers; } // The leading transfer, whom's data we're currently using. BlockTransfer* leader() { return m_leader; } const BlockTransfer* leader() const { return m_leader; } BlockTransfer* find(const PeerInfo* p); const BlockTransfer* find(const PeerInfo* p) const; BlockTransfer* find_queued(const PeerInfo* p); const BlockTransfer* find_queued(const PeerInfo* p) const; BlockTransfer* find_transfer(const PeerInfo* p); const BlockTransfer* find_transfer(const PeerInfo* p) const; // Internal to libTorrent: BlockTransfer* insert(PeerInfo* peerInfo); void erase(BlockTransfer* transfer); bool transfering(BlockTransfer* transfer); void transfer_dissimilar(BlockTransfer* transfer); bool completed(BlockTransfer* transfer); void retry_transfer(); static void stalled(BlockTransfer* transfer); void stalled_transfer(BlockTransfer* transfer); void change_leader(BlockTransfer* transfer); void failed_leader(); BlockFailed* failed_list() { return m_failedList; } void set_failed_list(BlockFailed* f) { m_failedList = f; } static void create_dummy(BlockTransfer* transfer, PeerInfo* peerInfo, const Piece& piece); // If the queued or transfering is already removed from the block it // will just delete the object. Made static so it can be called when // block == NULL. static void release(BlockTransfer* transfer); private: void invalidate_transfer(BlockTransfer* transfer) LIBTORRENT_NO_EXPORT; void remove_erased_transfers() LIBTORRENT_NO_EXPORT; void remove_non_leader_transfers() LIBTORRENT_NO_EXPORT; BlockList* m_parent{}; Piece m_piece; state_type m_state{STATE_INCOMPLETE}; uint32_t m_notStalled{0}; transfer_list_type m_queued; transfer_list_type m_transfers; BlockTransfer* m_leader{}; BlockFailed* m_failedList{}; }; inline BlockTransfer* Block::find(const PeerInfo* p) { if (auto transfer = find_queued(p)) return transfer; else return find_transfer(p); } inline const BlockTransfer* Block::find(const PeerInfo* p) const { if (auto transfer = find_queued(p)) return transfer; else return find_transfer(p); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/block_failed.h000066400000000000000000000042321522271512000220140ustar00rootroot00000000000000#ifndef LIBTORRENT_BLOCK_FAILED_H #define LIBTORRENT_BLOCK_FAILED_H #include #include #include #include namespace torrent { class BlockFailed : public std::vector > { public: using base_type = std::vector>; using base_type::value_type; using base_type::reference; using base_type::size_type; using base_type::difference_type; using base_type::iterator; using base_type::reverse_iterator; using base_type::size; using base_type::empty; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; using base_type::operator[]; static constexpr uint32_t invalid_index = ~uint32_t(); BlockFailed() = default; ~BlockFailed(); BlockFailed(const BlockFailed&) = delete; BlockFailed& operator=(const BlockFailed&) = delete; size_type current() const { return m_current; } iterator current_iterator() { return begin() + m_current; } reverse_iterator current_reverse_iterator() { return reverse_iterator(begin() + m_current + 1); } void set_current(size_type idx) { m_current = idx; } void set_current(iterator itr) { m_current = itr - begin(); } void set_current(reverse_iterator itr) { m_current = itr.base() - begin() - 1; } iterator max_element(); reverse_iterator reverse_max_element(); private: static void delete_entry(value_type e) { delete [] e.first; } static bool compare_entries(value_type e1, value_type e2) { return e1.second < e2.second; } size_type m_current{invalid_index}; }; inline BlockFailed::~BlockFailed() { std::for_each(begin(), end(), &BlockFailed::delete_entry); } inline BlockFailed::iterator BlockFailed::max_element() { return std::max_element(begin(), end(), &BlockFailed::compare_entries); } inline BlockFailed::reverse_iterator BlockFailed::reverse_max_element() { return std::max_element(rbegin(), rend(), &BlockFailed::compare_entries); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/block_list.cc000066400000000000000000000025011522271512000216760ustar00rootroot00000000000000#include "config.h" #include #include #include "block_list.h" #include "exceptions.h" namespace torrent { BlockList::BlockList(const Piece& piece, uint32_t blockLength) : m_piece(piece) { if (piece.length() == 0) throw internal_error("BlockList::BlockList(...) received zero length piece."); // Look into optimizing this by using input iterators in the ctor. base_type::resize((m_piece.length() + blockLength - 1) / blockLength); // ATM assume offset of 0. // uint32_t offset = m_piece.offset(); uint32_t offset = 0; for (auto itr = begin(), last = end() - 1; itr != last; ++itr, offset += blockLength) { itr->set_parent(this); itr->set_piece(Piece(m_piece.index(), offset, blockLength)); } base_type::back().set_parent(this); base_type::back().set_piece(Piece(m_piece.index(), offset, (m_piece.length() % blockLength) ? m_piece.length() % blockLength : blockLength)); } // The default dtor's handles cleaning up the blocks and block transfers. BlockList::~BlockList() = default; void BlockList::do_all_failed() { clear_finished(); set_attempt(0); // Clear leaders when we want to redownload the chunk. std::for_each(begin(), end(), std::mem_fn(&Block::failed_leader)); std::for_each(begin(), end(), std::mem_fn(&Block::retry_transfer)); } } // namespace torrent libtorrent-0.16.17/src/torrent/data/block_list.h000066400000000000000000000045611522271512000215500ustar00rootroot00000000000000#ifndef LIBTORRENT_BLOCK_LIST_H #define LIBTORRENT_BLOCK_LIST_H #include #include #include #include namespace torrent { class LIBTORRENT_EXPORT BlockList : private std::vector { public: using size_type = uint32_t; using base_type = std::vector; using base_type::value_type; using base_type::reference; using base_type::difference_type; using base_type::iterator; using base_type::const_iterator; using base_type::size; using base_type::empty; using base_type::begin; using base_type::end; using base_type::operator[]; BlockList(const Piece& piece, uint32_t blockLength); ~BlockList(); BlockList(const BlockList&) = delete; BlockList& operator=(const BlockList&) = delete; bool is_all_finished() const { return m_finished == size(); } const Piece& piece() const { return m_piece; } uint32_t index() const { return m_piece.index(); } priority_enum priority() const { return m_priority; } void set_priority(priority_enum p) { m_priority = p; } size_type finished() const { return m_finished; } void inc_finished() { m_finished++; } void clear_finished() { m_finished = 0; } uint32_t failed() const { return m_failed; } // Temporary, just increment for now. void inc_failed() { m_failed++; } uint32_t attempt() const { return m_attempt; } void set_attempt(uint32_t a) { m_attempt = a; } // Set when the chunk was initially requested from a seeder. This // allows us to quickly determine if it is a suitable chunk to // request from another seeder, e.g by already knowing it is a rare // piece. bool by_seeder() const { return m_bySeeder; } void set_by_seeder(bool state) { m_bySeeder = state; } void do_all_failed(); private: Piece m_piece; priority_enum m_priority{PRIORITY_OFF}; size_type m_finished{0}; uint32_t m_failed{0}; uint32_t m_attempt{0}; bool m_bySeeder{false}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/block_transfer.h000066400000000000000000000066601522271512000224230ustar00rootroot00000000000000#ifndef LIBTORRENT_BLOCK_TRANSFER_H #define LIBTORRENT_BLOCK_TRANSFER_H #include #include #include #include #include namespace torrent { class LIBTORRENT_EXPORT BlockTransfer { public: static constexpr uint32_t invalid_index = ~uint32_t(); using key_type = PeerInfo*; enum state_type { STATE_ERASED, STATE_QUEUED, STATE_LEADER, STATE_NOT_LEADER }; BlockTransfer() = default; ~BlockTransfer(); BlockTransfer(const BlockTransfer&) = delete; BlockTransfer& operator=(const BlockTransfer&) = delete; // TODO: Do we need to also check for peer_info?... bool is_valid() const { return m_block != NULL; } bool is_erased() const { return m_state == STATE_ERASED; } bool is_queued() const { return m_state == STATE_QUEUED; } bool is_leader() const { return m_state == STATE_LEADER; } bool is_not_leader() const { return m_state == STATE_NOT_LEADER; } bool is_finished() const { return m_position == m_piece.length(); } key_type peer_info() { return m_peer_info; } key_type const_peer_info() const { return m_peer_info; } Block* block() { return m_block; } const Block* const_block() const { return m_block; } const Piece& piece() const { return m_piece; } uint32_t index() const { return m_piece.index(); } state_type state() const { return m_state; } int32_t request_time() const { return m_request_time; } // Adjust the position after any actions like erasing it from a // Block, but before if finishing. uint32_t position() const { return m_position; } uint32_t stall() const { return m_stall; } uint32_t failed_index() const { return m_failedIndex; } void set_peer_info(key_type p); void set_block(Block* b) { m_block = b; } void set_piece(const Piece& p) { m_piece = p; } void set_state(state_type s) { m_state = s; } void set_request_time(int32_t t) { m_request_time = t; } void set_position(uint32_t p) { m_position = p; } void adjust_position(uint32_t p) { m_position += p; } void set_stall(uint32_t s) { m_stall = s; } void set_failed_index(uint32_t i) { m_failedIndex = i; } private: key_type m_peer_info{}; Block* m_block{}; Piece m_piece; state_type m_state; int32_t m_request_time; uint32_t m_position; uint32_t m_stall; uint32_t m_failedIndex; }; inline BlockTransfer::~BlockTransfer() { assert(m_block == NULL && "BlockTransfer::~BlockTransfer() block not NULL"); assert(m_peer_info == NULL && "BlockTransfer::~BlockTransfer() peer_info not NULL"); } inline void BlockTransfer::set_peer_info(key_type p) { if (m_peer_info != NULL) m_peer_info->dec_transfer_counter(); m_peer_info = p; if (m_peer_info != NULL) m_peer_info->inc_transfer_counter(); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/chunk_utils.cc000066400000000000000000000064731522271512000221150ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include "chunk_utils.h" #include "download.h" #include "manager.h" #include "download/download_wrapper.h" #include "torrent/chunk_manager.h" #include "torrent/download/download_manager.h" #include "data/chunk_list.h" #include "data/file.h" namespace torrent { std::vector chunk_list_mapping(Download* download) { ChunkList* chunk_list = download->ptr()->main()->chunk_list(); std::vector mappings; for (const auto& chunk : *chunk_list) { if (!chunk.is_valid()) continue; for (const auto& itr2 : *chunk.chunk()) { if (itr2.mapped() != ChunkPart::MAPPED_MMAP) continue; vm_mapping val = {itr2.chunk().ptr(), itr2.chunk().size_aligned()}; mappings.push_back(val); } } return mappings; } chunk_info_result chunk_list_address_info(void* address) { for (const auto& chunk : *manager->chunk_manager()) { auto result = chunk->find_address(address); if (result.first != chunk->end()) { auto d_itr = manager->download_manager()->find_chunk_list(chunk); if (d_itr == manager->download_manager()->end()) return chunk_info_result(); chunk_info_result ci; ci.download = Download(*d_itr); ci.chunk_index = result.first->index(); ci.chunk_offset = result.second->position() + std::distance(result.second->chunk().begin(), static_cast(address)); ci.file_path = result.second->file()->frozen_path().c_str(); ci.file_offset = result.second->file_offset() + std::distance(result.second->chunk().begin(), static_cast(address)); return ci; } } return chunk_info_result(); } } // namespace torrent libtorrent-0.16.17/src/torrent/data/chunk_utils.h000066400000000000000000000045001522271512000217440ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_CHUNK_UTILS_H #define LIBTORRENT_CHUNK_UTILS_H #include #include #include namespace torrent { class ChunkList; struct vm_mapping { void* ptr; uint64_t length; }; // Change to ChunkList* when that becomes part of the public API. std::vector chunk_list_mapping(Download* download) LIBTORRENT_EXPORT; struct chunk_info_result { Download download; uint32_t chunk_index; uint32_t chunk_offset; const char* file_path; uint64_t file_offset; // void* chunk_begin; // void* chunk_end; // int prot; }; chunk_info_result chunk_list_address_info(void* address) LIBTORRENT_EXPORT; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/download_data.cc000066400000000000000000000055231522271512000223600ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include "torrent/exceptions.h" #include "download_data.h" namespace torrent { // Calculate the number of chunks remaining to be downloaded. // // Doing it the slow and safe way, optimize this at some point. uint32_t download_data::calc_wanted_chunks() const { if (m_completed_bitfield.is_all_set()) return 0; priority_ranges wanted_ranges = priority_ranges::create_union(m_normal_priority, m_high_priority); if (m_completed_bitfield.is_all_unset()) return wanted_ranges.intersect_distance(0, m_completed_bitfield.size_bits()); if (m_completed_bitfield.empty()) throw internal_error("download_data::update_wanted_chunks() m_completed_bitfield.empty()."); uint32_t result = 0; for (const auto& wanted_range : wanted_ranges) { //remaining = completed->count_range(itr->first, itr->second); uint32_t idx = wanted_range.first; while (idx != wanted_range.second) result += !m_completed_bitfield.get(idx++); } return result; } void download_data::verify_wanted_chunks(const char* where) const { if (m_wanted_chunks != calc_wanted_chunks()) throw internal_error("Invalid download_data::wanted_chunks() value in " + std::string(where) + "."); } } // namespace torrent libtorrent-0.16.17/src/torrent/data/download_data.h000066400000000000000000000070341522271512000222210ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_DOWNLOAD_DATA_H #define LIBTORRENT_DATA_DOWNLOAD_DATA_H #include #include #include #include #include namespace torrent { class ChunkListNode; class ChunkSelector; class Download; class DownloadWrapper; class FileList; class download_data { public: using priority_ranges = ranges; using slot_void = std::function; using slot_chunk_list_node_p = std::function; const HashString& hash() const { return m_hash; } bool is_partially_done() const { return m_wanted_chunks == 0; } bool is_not_partially_done() const { return m_wanted_chunks != 0; } const Bitfield* completed_bitfield() const { return &m_completed_bitfield; } const Bitfield* untouched_bitfield() const { return &m_untouched_bitfield; } const priority_ranges* high_priority() const { return &m_high_priority; } const priority_ranges* normal_priority() const { return &m_normal_priority; } uint32_t wanted_chunks() const { return m_wanted_chunks; } uint32_t calc_wanted_chunks() const; void verify_wanted_chunks(const char* where) const; slot_void& slot_initial_hash() const { return m_slot_initial_hash; } slot_void& slot_download_done() const { return m_slot_download_done; } slot_void& slot_partially_done() const { return m_slot_partially_done; } slot_void& slot_partially_restarted() const { return m_slot_partially_restarted; } slot_chunk_list_node_p& slot_chunk_done() const {return m_slot_chunk_done;} protected: friend class ChunkList; friend class ChunkSelector; friend class Download; friend class DownloadWrapper; friend class FileList; HashString& mutable_hash() { return m_hash; } Bitfield* mutable_completed_bitfield() { return &m_completed_bitfield; } Bitfield* mutable_untouched_bitfield() { return &m_untouched_bitfield; } priority_ranges* mutable_high_priority() { return &m_high_priority; } priority_ranges* mutable_normal_priority() { return &m_normal_priority; } void update_wanted_chunks() { m_wanted_chunks = calc_wanted_chunks(); } void set_wanted_chunks(uint32_t n) { m_wanted_chunks = n; } void call_download_done() { if (m_slot_download_done) m_slot_download_done(); } void call_partially_done() { if (m_slot_partially_done) m_slot_partially_done(); } void call_partially_restarted() { if (m_slot_partially_restarted) m_slot_partially_restarted(); } void call_chunk_done(ChunkListNode* chunk_ptr) {if(m_slot_chunk_done) m_slot_chunk_done(chunk_ptr);} private: HashString m_hash; Bitfield m_completed_bitfield; Bitfield m_untouched_bitfield; priority_ranges m_high_priority; priority_ranges m_normal_priority; uint32_t m_wanted_chunks{0}; mutable slot_void m_slot_initial_hash; mutable slot_void m_slot_download_done; mutable slot_void m_slot_partially_done; mutable slot_void m_slot_partially_restarted; mutable slot_chunk_list_node_p m_slot_chunk_done; }; } // namespace torrent #endif // LIBTORRENT_DATA_DOWNLOAD_DATA_H libtorrent-0.16.17/src/torrent/data/file.cc000066400000000000000000000071201522271512000204720ustar00rootroot00000000000000#include "config.h" #include "torrent/data/file.h" #include #include "exceptions.h" #include "manager.h" #include "data/socket_file.h" #include "torrent/data/file_manager.h" #include "torrent/utils/file_stat.h" namespace torrent { File::~File() { assert((is_padding() || !is_open()) && "File::~File() called on an open file."); } bool File::is_created() const { if (is_padding()) return true; utils::FileStat fs; // If we can't even get permission to do fstat, we might as well // consider the file as not created. This function is to be used by // the client to check that the torrent files are present and ok, // rather than as a way to find out if it is starting on a blank // slate. if (!fs.update(m_frozen_path.str())) // return errno == EACCES; return false; return fs.is_regular(); } bool File::is_correct_size() const { if (is_padding()) return true; utils::FileStat fs; if (!fs.update(m_frozen_path.str())) return false; return fs.is_regular() && static_cast(fs.size()) == m_size; } void File::set_completed_chunks(uint32_t v) { if (has_flags(flag_active)) throw internal_error("File::set_completed_chunks(...) called on an active file."); if (v > size_chunks()) throw internal_error("File::set_completed_chunks(...) called with a value larger than the chunk size."); m_completed = v; } // At some point we should pass flags for deciding if the correct size // is necessary, etc. bool File::prepare(bool hashing, int prot, int flags) { if (is_padding()) return true; m_last_touched = this_thread::cached_time().count(); // Check if we got write protection and flag_resize_queued is // set. If so don't quit as we need to try re-sizing, instead call // resize_file. if (is_open() && has_permissions(prot)) return true; // For now don't allow overridding this check in prepare. if (m_flags & flag_create_queued) flags |= SocketFile::o_create; else flags &= ~SocketFile::o_create; if (!manager->file_manager()->open(this, hashing, prot, flags)) return false; m_flags |= flag_previously_created; m_flags &= ~flag_create_queued; // Replace PROT_WRITE with something prettier. if ((m_flags & flag_resize_queued) && has_permissions(PROT_WRITE)) { m_flags &= ~flag_resize_queued; return resize_file(); } return true; } void File::set_range(uint32_t chunkSize) { if (chunkSize == 0) m_range = range_type(0, 0); else if (m_size == 0) m_range = File::range_type(m_offset / chunkSize, m_offset / chunkSize); else m_range = File::range_type(m_offset / chunkSize, (m_offset + m_size + chunkSize - 1) / chunkSize); } void File::set_match_depth(File* left, File* right) { uint32_t level = 0; auto itrLeft = left->path()->begin(); auto itrRight = right->path()->begin(); while (true) { if (itrLeft == left->path()->end() || itrRight == right->path()->end()) break; if (itrLeft->str() != itrRight->str()) break; itrLeft++; itrRight++; level++; } left->m_match_depth_next = level; right->m_match_depth_prev = level; } bool File::resize_file() const { if (is_padding()) return true; if (!is_open()) return false; // This doesn't try to re-open it as rw. if (m_size == SocketFile(m_fd).size()) return true; if (!SocketFile(m_fd).set_size(m_size)) return false; if ((m_flags & flag_fallocate) && m_priority != PRIORITY_OFF) { // Only do non-blocking fallocate. if (!SocketFile(m_fd).allocate(m_size, SocketFile::flag_fallocate)) return false; } return true; } } // namespace torrent libtorrent-0.16.17/src/torrent/data/file.h000066400000000000000000000134031522271512000203350ustar00rootroot00000000000000#ifndef LIBTORRENT_FILE_H #define LIBTORRENT_FILE_H #include #include #include namespace torrent { class LIBTORRENT_EXPORT File { public: friend class FileList; using range_type = std::pair; static constexpr int flag_active = (1 << 0); static constexpr int flag_create_queued = (1 << 1); static constexpr int flag_resize_queued = (1 << 2); static constexpr int flag_fallocate = (1 << 3); static constexpr int flag_previously_created = (1 << 4); static constexpr int flag_prioritize_first = (1 << 5); static constexpr int flag_prioritize_last = (1 << 6); static constexpr int flag_attr_padding = (1 << 7); File() =default; ~File(); bool is_created() const; bool is_open() const { return m_fd != -1; } bool is_correct_size() const; bool is_valid_position(uint64_t p) const; bool is_create_queued() const { return m_flags & flag_create_queued; } bool is_resize_queued() const { return m_flags & flag_resize_queued; } bool is_previously_created() const { return m_flags & flag_previously_created; } bool is_padding() const { return m_flags & flag_attr_padding; } bool has_flags(int flags) const { return m_flags & flags; } void set_flags(int flags); void unset_flags(int flags); bool has_permissions(int prot) const { return !(prot & ~m_protection); } uint64_t offset() const { return m_offset; } uint64_t size_bytes() const { return m_size; } uint32_t size_chunks() const { return m_range.second - m_range.first; } uint32_t completed_chunks() const { return m_completed; } void set_completed_chunks(uint32_t v); const range_type& range() const { return m_range; } uint32_t range_first() const { return m_range.first; } uint32_t range_second() const { return m_range.second; } priority_enum priority() const { return m_priority; } void set_priority(priority_enum t) { m_priority = t; } const Path* path() const { return &m_path; } Path* mutable_path() { return &m_path; } const string_utf8& frozen_path() const { return m_frozen_path; } uint32_t match_depth_prev() const { return m_match_depth_prev; } uint32_t match_depth_next() const { return m_match_depth_next; } // This should only be changed by libtorrent. int file_descriptor() const { return m_fd; } void set_file_descriptor(int fd) { m_fd = fd; } // This might actually be wanted, as it would be nice to allow the // File to decide if it needs to try creating the underlying file or // not. bool prepare(bool hashing, int prot, int flags); int protection() const { return m_protection; } void set_protection(int prot) { m_protection = prot; } uint64_t last_touched() const { return m_last_touched; } void set_last_touched(uint64_t t) { m_last_touched = t; } protected: void set_flags_protected(int flags) { m_flags |= flags; } void unset_flags_protected(int flags) { m_flags &= ~flags; } void set_frozen_path(const std::string& path) { m_frozen_path.reset(path); } void set_offset(uint64_t off) { m_offset = off; } void set_size_bytes(uint64_t size) { m_size = size; } void set_range(uint32_t chunkSize); void set_completed_protected(uint32_t v) { m_completed = v; } void inc_completed_protected() { m_completed++; } static void set_match_depth(File* left, File* right); void set_match_depth_prev(uint32_t l) { m_match_depth_prev = l; } void set_match_depth_next(uint32_t l) { m_match_depth_next = l; } private: File(const File&) = delete; File& operator=(const File&) = delete; bool resize_file() const; int m_fd{-1}; int m_protection{0}; int m_flags{0}; Path m_path; string_utf8 m_frozen_path; uint64_t m_offset{0}; uint64_t m_size{0}; uint64_t m_last_touched{0}; range_type m_range; uint32_t m_completed{0}; priority_enum m_priority{PRIORITY_NORMAL}; uint32_t m_match_depth_prev{0}; uint32_t m_match_depth_next{0}; }; inline bool File::is_valid_position(uint64_t p) const { return p >= m_offset && p < m_offset + m_size; } inline void File::set_flags(int flags) { set_flags_protected(flags & (flag_create_queued | flag_resize_queued | flag_fallocate | flag_prioritize_first | flag_prioritize_last | flag_attr_padding)); } inline void File::unset_flags(int flags) { unset_flags_protected(flags & (flag_create_queued | flag_resize_queued | flag_fallocate | flag_prioritize_first | flag_prioritize_last| flag_attr_padding)); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/file_list.cc000066400000000000000000000521651522271512000215360ustar00rootroot00000000000000#include "config.h" #include "torrent/data/file_list.h" #include #include #include #include #include #include #include #include "manager.h" #include "piece.h" #include "data/chunk.h" #include "data/memory_chunk.h" #include "data/socket_file.h" #include "torrent/exceptions.h" #include "torrent/path.h" #include "torrent/data/file.h" #include "torrent/data/file_manager.h" #include "torrent/utils/file_stat.h" #include "torrent/utils/log.h" #define LT_LOG_FL(log_level, log_fmt, ...) \ lt_log_print_data(LOG_STORAGE_##log_level, (&m_data), "file_list", log_fmt, __VA_ARGS__); namespace torrent { static void verify_file_list(const FileList* fl) { if (fl->empty()) throw internal_error("verify_file_list() 1.", fl->data()->hash()); if ((*fl->begin())->match_depth_prev() != 0 || (*fl->rbegin())->match_depth_next() != 0) throw internal_error("verify_file_list() 2.", fl->data()->hash()); for (auto itr = fl->begin(), last = fl->end() - 1; itr != last; itr++) if ((*itr)->match_depth_next() != (*(itr + 1))->match_depth_prev() || (*itr)->match_depth_next() >= (*itr)->path()->size()) throw internal_error("verify_file_list() 3.", fl->data()->hash()); } FileList::FileList() = default; FileList::~FileList() { // Can we skip close()? close(); base_type::clear(); m_torrent_size = 0; } bool FileList::is_valid_piece(const Piece& piece) const { return piece.index() < size_chunks() && piece.length() != 0 && // Make sure offset does not overflow 32 bits. piece.offset() + piece.length() >= piece.offset() && piece.offset() + piece.length() <= chunk_index_size(piece.index()); } bool FileList::is_root_dir_created() const { utils::FileStat fs; if (!fs.update(m_root_dir)) // return errno == EACCES return false; return fs.is_directory(); } bool FileList::is_multi_file() const { // Currently only check if we got just one file. In the future this // should be a bool, which will be set based on what flags are // passed when the torrent was loaded. return m_is_multi_file; } uint64_t FileList::completed_bytes() const { // Chunk size needs to be cast to a uint64_t for the below to work. uint64_t cs = chunk_size(); if (bitfield()->empty()) return bitfield()->is_all_set() ? size_bytes() : (completed_chunks() * cs); if (!bitfield()->get(size_chunks() - 1) || size_bytes() % cs == 0) { // The last chunk is not done, or the last chunk is the same size // as the others. return completed_chunks() * cs; } else { if (completed_chunks() == 0) throw internal_error("FileList::bytes_completed() completed_chunks() == 0.", data()->hash()); return (completed_chunks() - 1) * cs + size_bytes() % cs; } } uint64_t FileList::left_bytes() const { uint64_t left = size_bytes() - completed_bytes(); if (left > (uint64_t{1} << 60)) throw internal_error("FileList::bytes_left() is too large.", data()->hash()); if (completed_chunks() == size_chunks() && left != 0) throw internal_error("FileList::bytes_left() has an invalid size.", data()->hash()); return left; } uint32_t FileList::chunk_index_size(uint32_t index) const { if (index + 1 != size_chunks() || size_bytes() % chunk_size() == 0) return chunk_size(); else return size_bytes() % chunk_size(); } void FileList::set_root_dir(const std::string& path) { if (is_open()) throw input_error("Tried to change the root directory on an open download."); std::string::size_type last = path.find_last_not_of('/'); if (last == std::string::npos) m_root_dir = "."; else m_root_dir = path.substr(0, last + 1); } void FileList::set_max_file_size(uint64_t size) { if (is_open()) throw input_error("Tried to change the max file size for an open download."); m_max_file_size = size; } // This function should really ensure that we arn't dealing files // spread over multiple mount-points. uint64_t FileList::free_diskspace(cache_list& cache) const { uint64_t free_diskspace = std::numeric_limits::max(); for (const auto& link : m_indirect_links) { auto cache_itr = std::find_if(cache.begin(), cache.end(), [&link](const auto& cacheEntry) { return cacheEntry.first == link; }); if (cache_itr == cache.end()) { struct statvfs stat{}; auto fn = link.c_str(); if (statvfs(fn, &stat) == -1) continue; cache.push_back(std::make_pair(link, static_cast(stat.f_frsize) * stat.f_bavail)); cache_itr = std::prev(cache.end()); } if (cache_itr->second < free_diskspace) free_diskspace = cache_itr->second; } return free_diskspace != std::numeric_limits::max() ? free_diskspace : 0; } uint64_t FileList::free_diskspace_no_cache() const { cache_list cache; return free_diskspace(cache); } FileList::iterator_range FileList::split(iterator position, split_type* first, split_type* last) { if (is_open()) throw internal_error("FileList::split(...) is_open().", data()->hash()); if (first == last || position == end()) throw internal_error("FileList::split(...) invalid arguments.", data()->hash()); if (position != begin()) (*(position - 1))->set_match_depth_next(0); if (position + 1 != end()) (*(position + 1))->set_match_depth_prev(0); auto old_file = std::move(*position); uint64_t offset = old_file->offset(); size_type index = std::distance(begin(), position); size_type length = std::distance(first, last); // This is inefficient, but the simplest way to do it with unique_ptr vector i could find. for (size_type i = 0; i < length - 1; i++) base_type::insert(begin() + index, nullptr); position = begin() + index; auto itr = position; while (first != last) { auto new_file = std::make_unique(); new_file->set_offset(offset); new_file->set_size_bytes(std::get<0>(*first)); new_file->set_range(m_chunk_size); *new_file->mutable_path() = std::get<1>(*first); new_file->set_flags(std::get<2>(*first)); offset += std::get<0>(*first); *itr = std::move(new_file); itr++; first++; } if (offset != old_file->offset() + old_file->size_bytes()) throw internal_error("FileList::split(...) split size does not match the old size.", data()->hash()); return iterator_range(position, itr); } FileList::iterator FileList::merge(iterator first, iterator last, const Path& path) { auto new_file = std::make_unique(); // Set the path before deleting any iterators in case it refers to // one of the objects getting deleted. *(new_file->mutable_path()) = path; if (first == last) { if (first == end()) new_file->set_offset(m_torrent_size); else new_file->set_offset((*first)->offset()); first = base_type::insert(first, std::move(new_file)); } else { new_file->set_offset((*first)->offset()); for (auto itr = first; itr != last; ++itr) new_file->set_size_bytes(new_file->size_bytes() + (*itr)->size_bytes()); first = base_type::erase(first + 1, last) - 1; *first = std::move(new_file); } (*first)->set_range(m_chunk_size); if (first == begin()) (*first)->set_match_depth_prev(0); else File::set_match_depth(std::prev(first)->get(), first->get()); if (first + 1 == end()) (*first)->set_match_depth_next(0); else File::set_match_depth(first->get(), std::next(first)->get()); return first; } // If the user supplies an invalid range, it will bork in weird ways. void FileList::update_paths(iterator first, iterator last) { // Check if we're open? if (first == last) return; if (first != begin()) File::set_match_depth((first - 1)->get(), first->get()); while (first != last && ++first != end()) File::set_match_depth((first - 1)->get(), first->get()); verify_file_list(this); } bool FileList::make_root_path() { if (!is_open()) return false; return ::mkdir(m_root_dir.c_str(), 0777) == 0 || errno == EEXIST; } bool FileList::make_all_paths() { if (!is_open()) return false; Path dummyPath; const Path* lastPath = &dummyPath; for (auto& entry : *this) { // No need to create directories if the entry has already been // opened. if (entry->is_open()) continue; if (entry->path()->empty()) throw storage_error("Found an empty filename."); auto lastPathItr = lastPath->begin(); auto firstMismatch = entry->path()->begin(); // Couldn't find a suitable stl algo, need to write my own. while (true) { if (firstMismatch == entry->path()->end() || lastPathItr == lastPath->end()) break; if (firstMismatch->str() != lastPathItr->str()) break; lastPathItr++; firstMismatch++; } errno = 0; make_directory(entry->path()->begin(), entry->path()->end(), firstMismatch); lastPath = entry->path(); } return true; } // Initialize FileList and add a dummy file that may be split into // multiple files. void FileList::initialize(uint64_t torrentSize, uint32_t chunkSize) { if (sizeof(off_t) != 8) throw internal_error("Last minute panic; sizeof(off_t) != 8.", data()->hash()); if (chunkSize == 0) throw internal_error("FileList::initialize() chunk_size() == 0.", data()->hash()); m_chunk_size = chunkSize; m_torrent_size = torrentSize; m_root_dir = "."; m_data.mutable_completed_bitfield()->set_size_bits((size_bytes() + chunk_size() - 1) / chunk_size()); m_data.mutable_normal_priority()->insert(0, size_chunks()); m_data.set_wanted_chunks(size_chunks()); auto new_file = std::make_unique(); new_file->set_offset(0); new_file->set_size_bytes(torrentSize); new_file->set_range(m_chunk_size); base_type::push_back(std::move(new_file)); } struct file_list_cstr_less { bool operator () (const char* c1, const char* c2) const { return std::strcmp(c1, c2) < 0; } }; void FileList::open(bool hashing, int flags) { using path_set = std::set; LT_LOG_FL(INFO, "Opening.", 0); if (m_root_dir.empty()) throw internal_error("FileList::open() m_root_dir.empty().", data()->hash()); m_indirect_links.push_back(m_root_dir); Path lastPath; path_set pathSet; auto itr = end(); try { if (!(flags & open_no_create) && !make_root_path()) throw storage_error("Could not create directory '" + m_root_dir + "': " + std::strerror(errno)); for (auto& entry : *this) { // We no longer consider it an error to open a previously opened // FileList as we now use the same function to create // non-existent files. // // Since m_is_open is set, we know root dir wasn't changed, thus // we can keep the previously opened file. if (entry->is_open()) continue; if (entry->is_padding()) continue; // Update the path during open so that any changes to root dir // and file paths are properly handled. if (entry->path()->back().empty()) entry->set_frozen_path(std::string()); else entry->set_frozen_path(m_root_dir + entry->path()->as_string()); if (!pathSet.insert(entry->frozen_path().c_str()).second) throw storage_error("Duplicate filename found."); if (entry->size_bytes() > m_max_file_size) throw storage_error("File exceedes the configured max file size."); if (entry->path()->empty()) throw storage_error("Empty filename is not allowed."); // Handle directory creation outside of open_file, so we can do // it here if necessary. entry->set_flags_protected(File::flag_active); if (!open_file(&*entry, lastPath, hashing, flags)) { // This needs to check if the error was due to open_no_create // being set or not. if (!(flags & open_no_create)) // Also check if open_require_all_open is set. throw storage_error("Could not open file: " + std::string(std::strerror(errno))); // Don't set the lastPath as we haven't created the directory. continue; } lastPath = *entry->path(); } } catch (const local_error& e) { for (auto& entry : *this) { entry->unset_flags_protected(File::flag_active); manager->file_manager()->close(entry.get()); } if (itr == end()) { LT_LOG_FL(ERROR, "Failed to prepare file list: %s", e.what()); } else { LT_LOG_FL(ERROR, "Failed to prepare file '%s': %s", (*itr)->path()->as_string().c_str(), e.what()); } // Set to false here in case we tried to open the FileList for the // second time. m_is_open = false; throw; } m_is_open = true; m_frozen_root_dir.reset(m_root_dir); // For meta-downloads, if the file exists, we have to assume that // it is either 0 or 1 length or the correct size. If the size // turns out wrong later, a storage_error will be thrown elsewhere // to alert the user in this (unlikely) case. // // DEBUG: Make this depend on a flag... if (size_bytes() < 2) { utils::FileStat stat; // This probably recurses into open() once, but that is harmless. if (stat.update((*begin())->frozen_path().str()) && stat.size() > 1) return reset_filesize(stat.size()); } } void FileList::close() { if (!is_open()) return; LT_LOG_FL(INFO, "Closing.", 0); for (auto& entry : *this) { entry->unset_flags_protected(File::flag_active); manager->file_manager()->close(entry.get()); } m_is_open = false; m_indirect_links.clear(); m_data.mutable_completed_bitfield()->unallocate(); } void FileList::close_all_files() { if (!is_open()) return; LT_LOG_FL(INFO, "Closing all files.", 0); for (auto& entry : *this) manager->file_manager()->close(entry.get()); } void FileList::make_directory(Path::const_iterator path_begin, Path::const_iterator path_end, Path::const_iterator start_itr) { std::string path = m_root_dir; while (path_begin != path_end) { path += "/" + path_begin->str(); if (path_begin++ != start_itr) continue; start_itr++; utils::FileStat fileStat; if (fileStat.update_link(path) && fileStat.is_link() && std::find(m_indirect_links.begin(), m_indirect_links.end(), path) == m_indirect_links.end()) m_indirect_links.push_back(path); if (path_begin == path_end) break; if (::mkdir(path.c_str(), 0777) != 0 && errno != EEXIST) throw storage_error("Could not create directory '" + path + "': " + std::strerror(errno)); } } bool FileList::open_file(File* file_node, const Path& lastPath, bool hashing, int flags) { errno = 0; if (!(flags & open_no_create)) { const Path* path = file_node->path(); auto lastItr = lastPath.begin(); auto firstMismatch = path->begin(); // Couldn't find a suitable stl algo, need to write my own. while (firstMismatch != path->end() && lastItr != lastPath.end() && firstMismatch->str() == lastItr->str()) { lastItr++; firstMismatch++; } make_directory(path->begin(), path->end(), firstMismatch); } // Some torrents indicate an empty directory by having a path with // an empty last element. This entry must be zero length. if (file_node->path()->back().empty()) return file_node->size_bytes() == 0; utils::FileStat file_stat; if (file_stat.update(file_node->frozen_path().str()) && !file_stat.is_regular() && !file_stat.is_link()) { // Might also fail on other kinds of file types, but there's no suitable errno for all cases. errno = EISDIR; return false; } return file_node->prepare(hashing, MemoryChunk::prot_read, 0); } MemoryChunk FileList::create_chunk_part(FileList::iterator itr, uint64_t offset, uint32_t length, bool hashing, int prot) const { offset -= (*itr)->offset(); length = std::min(length, (*itr)->size_bytes() - offset); if ((*itr)->is_padding()) return SocketFile::create_padding_chunk(length, prot, MemoryChunk::map_shared); if (static_cast(offset) < 0) throw internal_error("FileList::chunk_part(...) caught a negative offset", data()->hash()); // Check that offset != length of file. if (!(*itr)->prepare(hashing, prot, 0)) return MemoryChunk(); auto mc = SocketFile((*itr)->file_descriptor()).create_chunk(offset, length, prot, MemoryChunk::map_shared); if (!mc.is_valid()) return MemoryChunk(); if (mc.size() == 0) throw internal_error("FileList::create_chunk(...) mc.size() == 0.", data()->hash()); if (mc.size() > length) throw internal_error("FileList::create_chunk(...) mc.size() > length.", data()->hash()); #ifdef USE_MADVISE // TODO: Update all uses of madvise to posix_madvise. if (hashing) { if (manager->file_manager()->advise_random_hashing()) madvise(mc.ptr(), mc.size(), MADV_RANDOM); } else { if (manager->file_manager()->advise_random()) madvise(mc.ptr(), mc.size(), MADV_RANDOM); } #endif return mc; } Chunk* FileList::create_chunk(uint64_t offset, uint32_t length, bool hashing, int prot) { if (offset + length > m_torrent_size) throw internal_error("Tried to access chunk out of range in FileList", data()->hash()); auto chunk = std::make_unique(); auto itr = std::find_if(begin(), end(), [offset](const value_type& file) { return file->is_valid_position(offset); }); for (; length != 0; ++itr) { if (itr == end()) throw internal_error("FileList could not find a valid file for chunk", data()->hash()); if ((*itr)->size_bytes() == 0) continue; MemoryChunk mc = create_chunk_part(itr, offset, length, hashing, prot); if (!mc.is_valid()) return nullptr; chunk->push_back(ChunkPart::MAPPED_MMAP, mc); chunk->back().set_file(itr->get(), offset - (*itr)->offset()); offset += mc.size(); length -= mc.size(); } if (chunk->empty()) return NULL; return chunk.release(); } Chunk* FileList::create_chunk_index(uint32_t index, int prot) { return create_chunk(static_cast(index) * chunk_size(), chunk_index_size(index), false, prot); } Chunk* FileList::create_hashing_chunk_index(uint32_t index, int prot) { return create_chunk(static_cast(index) * chunk_size(), chunk_index_size(index), true, prot); } void FileList::mark_completed(uint32_t index) { if (index >= size_chunks() || completed_chunks() >= size_chunks()) throw internal_error("FileList::mark_completed(...) received an invalid index.", data()->hash()); if (bitfield()->empty()) throw internal_error("FileList::mark_completed(...) bitfield is empty.", data()->hash()); if (bitfield()->size_bits() != size_chunks()) throw internal_error("FileList::mark_completed(...) bitfield is not the right size.", data()->hash()); if (bitfield()->get(index)) throw internal_error("FileList::mark_completed(...) received a chunk that has already been finished.", data()->hash()); if (bitfield()->size_set() >= bitfield()->size_bits()) throw internal_error("FileList::mark_completed(...) bitfield()->size_set() >= bitfield()->size_bits().", data()->hash()); LT_LOG_FL(DEBUG, "Done chunk: index:%" PRIu32 ".", index); m_data.mutable_completed_bitfield()->set(index); inc_completed(begin(), index); // TODO: Remember to validate 'wanted_chunks'. if (m_data.normal_priority()->has(index) || m_data.high_priority()->has(index)) { if (m_data.wanted_chunks() == 0) throw internal_error("FileList::mark_completed(...) m_data.wanted_chunks() == 0.", data()->hash()); m_data.set_wanted_chunks(m_data.wanted_chunks() - 1); } } FileList::iterator FileList::inc_completed(iterator firstItr, uint32_t index) { firstItr = std::find_if(firstItr, end(), [index](value_type& file) { return index < file->range_second(); }); auto lastItr = std::find_if(firstItr, end(), [index](value_type& file) { return index+1 < file->range_second(); }); if (firstItr == end()) throw internal_error("FileList::inc_completed() first == m_entryList->end().", data()->hash()); // TODO: Check if this works right for zero-length files. std::for_each(firstItr, lastItr == end() ? end() : (lastItr + 1), std::mem_fn(&File::inc_completed_protected)); return lastItr; } void FileList::update_completed() { if (!bitfield()->is_tail_cleared()) throw internal_error("Content::update_done() called but m_bitfield's tail isn't cleared.", data()->hash()); m_data.update_wanted_chunks(); if (bitfield()->is_all_set()) { for (auto& entry : *this) entry->set_completed_protected(entry->size_chunks()); } else { // Clear any old progress data from the entries as we don't clear // this on close, etc. for (auto& entry : *this) entry->set_completed_protected(0); if (bitfield()->is_all_unset()) return; auto entryItr = begin(); for (Bitfield::size_type index = 0; index < bitfield()->size_bits(); ++index) if (bitfield()->get(index)) entryItr = inc_completed(entryItr, index); } } // Used for metadata downloads. void FileList::reset_filesize(int64_t size) { LT_LOG_FL(INFO, "Resetting torrent size: size:%" PRIi64 ".", size); close(); m_chunk_size = size; m_torrent_size = size; (*begin())->set_size_bytes(size); (*begin())->set_range(m_chunk_size); m_data.mutable_completed_bitfield()->allocate(); m_data.mutable_completed_bitfield()->unset_all(); open(false, open_no_create); } } // namespace torrent libtorrent-0.16.17/src/torrent/data/file_list.h000066400000000000000000000155151522271512000213760ustar00rootroot00000000000000#ifndef LIBTORRENT_FILE_LIST_H #define LIBTORRENT_FILE_LIST_H #include #include #include #include #include #include #include #include namespace torrent { class Content; class Download; class DownloadConstructor; class DownloadMain; class DownloadWrapper; class Handshake; class LIBTORRENT_EXPORT FileList : private std::vector> { public: friend class Content; friend class Download; friend class DownloadConstructor; friend class DownloadMain; friend class DownloadWrapper; friend class Handshake; using base_type = std::vector>; using path_list = std::vector; using split_type = std::tuple; using cache_list = std::vector>; // The below are using-directives that make visible functions and // typedefs in the parent std::vector, only those listed below are // accessible. If you don't understand how this works, use google, // don't ask me. using base_type::value_type; using base_type::iterator; using base_type::const_iterator; using base_type::reverse_iterator; using base_type::const_reverse_iterator; using iterator_range = std::pair; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; using base_type::front; using base_type::back; using base_type::empty; using base_type::reserve; using base_type::at; using base_type::operator[]; FileList(); ~FileList(); bool is_open() const { return m_is_open; } bool is_done() const { return completed_chunks() == size_chunks(); } bool is_valid_piece(const Piece& piece) const; bool is_root_dir_created() const; // Check if the torrent is loaded as a multi-file torrent. This may // return true even for a torrent with just one file. bool is_multi_file() const; void set_multi_file(bool state) { m_is_multi_file = state; } size_t size_files() const { return base_type::size(); } uint64_t size_bytes() const { return m_torrent_size; } uint32_t size_chunks() const { return bitfield()->size_bits(); } uint32_t completed_chunks() const { return bitfield()->size_set(); } uint64_t completed_bytes() const; uint64_t left_bytes() const; uint32_t chunk_size() const { return m_chunk_size; } uint32_t chunk_index_size(uint32_t index) const; uint64_t chunk_index_position(uint32_t index) const { return index * chunk_size(); } const download_data* data() const { return &m_data; } const Bitfield* bitfield() const { return m_data.completed_bitfield(); } // You may only call set_root_dir after all nodes have been added. const std::string& root_dir() const { return m_root_dir; } void set_root_dir(const std::string& path); const string_utf8& frozen_root_dir() const { return m_frozen_root_dir; } uint64_t max_file_size() const { return m_max_file_size; } void set_max_file_size(uint64_t size); // If the files span multiple disks, the one with the least amount // of free diskspace will be returned. uint64_t free_diskspace(cache_list& cache) const; uint64_t free_diskspace_no_cache() const; // List of directories in the torrent that might be on different // volumes as they are links, including the root directory. Used by // 'free_diskspace()'. const path_list* indirect_links() const { return &m_indirect_links; } // The sum of the sizes in the range [first,last> must be equal to // the size of 'position'. Do not use the old pointer in 'position' // after this call. iterator_range split(iterator position, split_type* first, split_type* last); // Use an empty range to insert a zero length file. iterator merge(iterator first, iterator last, const Path& path); iterator merge(iterator_range range, const Path& path) { return merge(range.first, range.second, path); } void update_paths(iterator first, iterator last); bool make_root_path(); bool make_all_paths(); protected: static constexpr int open_no_create = (1 << 0); static constexpr int open_require_all_open = (1 << 1); void initialize(uint64_t torrentSize, uint32_t chunkSize) LIBTORRENT_NO_EXPORT; void open(bool hashing, int flags) LIBTORRENT_NO_EXPORT; void close() LIBTORRENT_NO_EXPORT; void close_all_files() LIBTORRENT_NO_EXPORT; download_data* mutable_data() { return &m_data; } // Before calling this function, make sure you clear errno. If // creating the chunk failed, NULL is returned and errno is set. Chunk* create_chunk_index(uint32_t index, int prot) LIBTORRENT_NO_EXPORT; Chunk* create_hashing_chunk_index(uint32_t index, int prot) LIBTORRENT_NO_EXPORT; void mark_completed(uint32_t index) LIBTORRENT_NO_EXPORT; iterator inc_completed(iterator firstItr, uint32_t index) LIBTORRENT_NO_EXPORT; void update_completed() LIBTORRENT_NO_EXPORT; // Used for meta downloads; we only know the // size after the first extension handshake. void reset_filesize(int64_t) LIBTORRENT_NO_EXPORT; private: bool open_file(File* node, const Path& lastPath, bool hashing, int flags) LIBTORRENT_NO_EXPORT; void make_directory(Path::const_iterator pathBegin, Path::const_iterator pathEnd, Path::const_iterator startItr) LIBTORRENT_NO_EXPORT; Chunk* create_chunk(uint64_t offset, uint32_t length, bool hashing, int prot) LIBTORRENT_NO_EXPORT; MemoryChunk create_chunk_part(FileList::iterator itr, uint64_t offset, uint32_t length, bool hashing, int prot) const LIBTORRENT_NO_EXPORT; download_data m_data; bool m_is_open{}; bool m_is_multi_file{}; uint64_t m_torrent_size{0}; uint32_t m_chunk_size{0}; uint64_t m_max_file_size{~uint64_t()}; std::string m_root_dir; string_utf8 m_frozen_root_dir; path_list m_indirect_links; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/file_list_iterator.cc000066400000000000000000000104761522271512000234460ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include "torrent/exceptions.h" #include "file.h" #include "file_list_iterator.h" namespace torrent { bool FileListIterator::is_file() const { return m_depth >= 0 && m_depth + 1 == static_cast((*m_position)->path()->size()); } bool FileListIterator::is_empty() const { return (*m_position)->path()->empty(); } bool FileListIterator::is_entering() const { return m_depth >= 0 && m_depth + 1 != static_cast((*m_position)->path()->size()); } FileListIterator& FileListIterator::operator ++() { int32_t size = (*m_position)->path()->size(); if (size == 0) { m_position++; return *this; } m_depth++; if (m_depth > size) throw internal_error("FileListIterator::operator ++() m_depth > size."); if (m_depth == size) m_depth = -(size - 1); if (m_depth == -static_cast((*m_position)->match_depth_next())) { m_depth = (*m_position)->match_depth_next(); m_position++; } return *this; } FileListIterator& FileListIterator::operator --() { // We're guaranteed that if m_depth != 0 then so is the path size, // so there's no need to check for it. if (m_depth == 0) { m_position--; // This ensures we properly start iterating the paths in a tree // without failing badly when size == 0. if ((*m_position)->path()->size() > 1) m_depth = -1; } else if (m_depth == static_cast((*m_position)->match_depth_prev())) { m_position--; // If only the last element differs, then we don't switch to // negative depth. Also make sure we skip the negative of the // current depth, as we index by the depth we're exiting from. if (m_depth + 1 != static_cast((*m_position)->path()->size())) m_depth = -(m_depth + 1); } else { auto size = static_cast((*m_position)->path()->size()); m_depth--; if (m_depth < -size) throw internal_error("FileListIterator::operator --() m_depth < -size."); if (m_depth == -size) m_depth = size - 1; } return *this; } FileListIterator& FileListIterator::forward_current_depth() { uint32_t baseDepth = depth(); if (!is_entering()) return ++(*this); // If the above test was false then we know there must be a // 'leaving' at baseDepth before the end of the list. do { ++(*this); } while (depth() > baseDepth); return *this; } FileListIterator& FileListIterator::backward_current_depth() { --(*this); if (is_entering() || is_file() || is_empty()) return *this; if (depth() == 0) throw internal_error("FileListIterator::backward_current_depth() depth() == 0."); uint32_t baseDepth = depth(); while (depth() >= baseDepth) --(*this); return *this; } } // namespace torrent libtorrent-0.16.17/src/torrent/data/file_list_iterator.h000066400000000000000000000100621522271512000232770ustar00rootroot00000000000000#ifndef LIBTORRENT_FILE_LIST_ITERATOR_H #define LIBTORRENT_FILE_LIST_ITERATOR_H #include #include #include namespace torrent { class File; // A special purpose iterator class for iterating through FileList as // a dired structure. class LIBTORRENT_EXPORT FileListIterator { public: using iterator = FileList::iterator; using reference = File*; using pointer = File**; FileListIterator() = default; explicit FileListIterator(iterator pos, uint32_t depth = 0) : m_position(pos), m_depth(depth) {} bool is_file() const; bool is_empty() const; bool is_entering() const; bool is_leaving() const { return m_depth < 0; } uint32_t depth() const { return std::abs(m_depth); } iterator base() const { return m_position; } reference file() const { return m_position->get(); } FileListIterator& operator ++(); FileListIterator& operator --(); FileListIterator operator ++(int); FileListIterator operator --(int); FileListIterator& forward_current_depth(); FileListIterator& backward_current_depth(); friend bool operator ==(const FileListIterator& left, const FileListIterator& right); friend bool operator !=(const FileListIterator& left, const FileListIterator& right); private: iterator m_position; int32_t m_depth; }; inline FileListIterator FileListIterator::operator ++(int) { FileListIterator tmp = *this; ++(*this); return tmp; } inline FileListIterator FileListIterator::operator --(int) { FileListIterator tmp = *this; --(*this); return tmp; } inline bool operator ==(const FileListIterator& left, const FileListIterator& right) { return left.m_position == right.m_position && left.m_depth == right.m_depth; } inline bool operator !=(const FileListIterator& left, const FileListIterator& right) { return left.m_position != right.m_position || left.m_depth != right.m_depth; } // Take a range as input and return the next entry at the same // directory depth as first. If the returned iterator equals 'last' or // is_leaving() == true then the search failed. class LIBTORRENT_EXPORT file_list_collapsed_iterator : private FileListIterator { public: using base_type = FileListIterator; using this_type = file_list_collapsed_iterator; using base_type::iterator; using base_type::reference; using base_type::pointer; using base_type::is_file; using base_type::is_empty; using base_type::is_entering; using base_type::is_leaving; using base_type::depth; using base_type::file; file_list_collapsed_iterator() = default; file_list_collapsed_iterator(const FileListIterator& src) : FileListIterator(src) {} explicit file_list_collapsed_iterator(iterator pos, uint32_t depth = 0) : FileListIterator(pos, depth) {} base_type base() const { return *static_cast(this); } this_type& operator ++() { base_type::forward_current_depth(); return *this; } this_type& operator --() { base_type::backward_current_depth(); return *this; } this_type operator ++(int); this_type operator --(int); friend bool operator ==(const file_list_collapsed_iterator& left, const file_list_collapsed_iterator& right); friend bool operator !=(const file_list_collapsed_iterator& left, const file_list_collapsed_iterator& right); }; inline bool operator ==(const file_list_collapsed_iterator& left, const file_list_collapsed_iterator& right) { return left.base() == right.base(); } inline bool operator !=(const file_list_collapsed_iterator& left, const file_list_collapsed_iterator& right) { return left.base() != right.base(); } inline file_list_collapsed_iterator file_list_collapsed_iterator::operator ++(int) { this_type tmp = *this; ++(*this); return tmp; } inline file_list_collapsed_iterator file_list_collapsed_iterator::operator --(int) { this_type tmp = *this; --(*this); return tmp; } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/file_manager.cc000066400000000000000000000043741522271512000221740ustar00rootroot00000000000000#include "config.h" #include "file_manager.h" #include #include #include #include #include "manager.h" #include "data/socket_file.h" #include "torrent/exceptions.h" #include "torrent/data/file.h" namespace torrent { FileManager::~FileManager() { assert(empty() && "FileManager::~FileManager() called but empty() != true."); } void FileManager::set_max_open_files(size_type s) { if (s < 4 || s > (1 << 16)) throw input_error("Max open files must be between 4 and 2^16."); m_max_open_files = s; while (size() > m_max_open_files) close_least_active(); } bool FileManager::open(value_type file, [[maybe_unused]] bool hashing, int prot, int flags) { if (file->is_padding()) return true; if (file->is_open()) close(file); if (size() > m_max_open_files) throw internal_error("FileManager::open_file(...) m_openSize > m_max_open_files."); if (size() == m_max_open_files) close_least_active(); SocketFile fd; if (!fd.open(file->frozen_path().str(), prot, flags)) { m_files_failed_counter++; return false; } file->set_protection(prot); file->set_file_descriptor(fd.fd()); #ifdef USE_POSIX_FADVISE if (hashing) { if (m_advise_random_hashing) posix_fadvise(fd.fd(), 0, 0, POSIX_FADV_RANDOM); } else { if (m_advise_random) posix_fadvise(fd.fd(), 0, 0, POSIX_FADV_RANDOM); } #endif base_type::push_back(file); // Consider storing the position of the file here. m_files_opened_counter++; return true; } void FileManager::close(value_type file) { if (!file->is_open()) return; if (file->is_padding()) return; SocketFile(file->file_descriptor()).close(); file->set_protection(0); file->set_file_descriptor(-1); auto itr = std::find(begin(), end(), file); if (itr == end()) throw internal_error("FileManager::close_file(...) itr == end()."); *itr = back(); base_type::pop_back(); m_files_closed_counter++; } void FileManager::close_least_active() { File* least = nullptr; uint64_t last = std::numeric_limits::max(); for (auto f : *this) { if (f->is_open() && f->last_touched() <= last) { last = f->last_touched(); least = f; } } if (least) close(least); } } // namespace torrent libtorrent-0.16.17/src/torrent/data/file_manager.h000066400000000000000000000041301522271512000220240ustar00rootroot00000000000000#ifndef LIBTORRENT_DATA_FILE_MANAGER_H #define LIBTORRENT_DATA_FILE_MANAGER_H #include #include namespace torrent { class File; class LIBTORRENT_EXPORT FileManager : private std::vector { public: using base_type = std::vector; using size_type = uint32_t; using base_type::value_type; using base_type::iterator; using base_type::reverse_iterator; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; FileManager() = default; ~FileManager(); size_type open_files() const { return base_type::size(); } size_type max_open_files() const { return m_max_open_files; } void set_max_open_files(size_type s); bool advise_random() const { return m_advise_random; } void set_advise_random(bool state) { m_advise_random = state; } bool advise_random_hashing() const { return m_advise_random_hashing; } void set_advise_random_hashing(bool state) { m_advise_random_hashing = state; } bool open(value_type file, bool hashing, int prot, int flags); void close(value_type file); // TODO: Close all files held by a download after hashing. Also flush all memory chunks. void close_least_active(); // Statistics: uint64_t files_opened_counter() const { return m_files_opened_counter; } uint64_t files_closed_counter() const { return m_files_closed_counter; } uint64_t files_failed_counter() const { return m_files_failed_counter; } private: FileManager(const FileManager&) = delete; FileManager& operator=(const FileManager&) = delete; size_type m_max_open_files{0}; bool m_advise_random{false}; bool m_advise_random_hashing{false}; uint64_t m_files_opened_counter{0}; uint64_t m_files_closed_counter{0}; uint64_t m_files_failed_counter{0}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/file_utils.cc000066400000000000000000000036001522271512000217110ustar00rootroot00000000000000#include "config.h" #include "exceptions.h" #include "file.h" #include "file_utils.h" namespace torrent { FileList::iterator file_split(FileList* fileList, FileList::iterator position, uint64_t maxSize, const std::string& suffix) { const Path* srcPath = (*position)->path(); uint64_t splitSize = ((*position)->size_bytes() + maxSize - 1) / maxSize; if (srcPath->empty() || (*position)->size_bytes() == 0) throw input_error("Tried to split a file with an empty path or zero length file."); if (splitSize > 1000) throw input_error("Tried to split a file into more than 1000 parts."); // Also replace dwnlctor's vector. auto splitList = new FileList::split_type[splitSize]; auto splitItr = splitList; const unsigned int name_size = srcPath->back().str().size() + suffix.size(); std::string name; name.reserve(name_size + 4); name += srcPath->back().str(); name += suffix; for (unsigned int i = 0; i != splitSize; ++i, ++splitItr) { if (i == splitSize - 1 && (*position)->size_bytes() % maxSize != 0) std::get<0>(*splitItr) = (*position)->size_bytes() % maxSize; else std::get<0>(*splitItr) = maxSize; name[name_size + 0] = '0' + (i / 100) % 10; name[name_size + 1] = '0' + (i / 10) % 10; name[name_size + 2] = '0' + (i / 1) % 10; name[name_size + 3] = '\0'; std::get<1>(*splitItr) = *srcPath; std::get<1>(*splitItr).back().reset(name); } return fileList->split(position, splitList, splitItr).second; } void file_split_all(FileList* fileList, uint64_t maxSize, const std::string& suffix) { if (maxSize == 0) throw input_error("Tried to split torrent files into zero sized chunks."); auto itr = fileList->begin(); while (itr != fileList->end()) if ((*itr)->size_bytes() > maxSize && !(*itr)->path()->empty()) itr = file_split(fileList, itr, maxSize, suffix); else itr++; } } // namespace torrent libtorrent-0.16.17/src/torrent/data/file_utils.h000066400000000000000000000010231522271512000215500ustar00rootroot00000000000000#ifndef LIBTORRENT_FILE_UTILS_H #define LIBTORRENT_FILE_UTILS_H #include #include namespace torrent { // Split 'position' into 'maxSize' sized files and return the iterator // after the last new entry. FileList::iterator file_split(FileList* fileList, FileList::iterator position, uint64_t maxSize, const std::string& suffix) LIBTORRENT_EXPORT; void file_split_all(FileList* fileList, uint64_t maxSize, const std::string& suffix) LIBTORRENT_EXPORT; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/piece.h000066400000000000000000000056651522271512000205160ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_PIECE_H #define LIBTORRENT_PIECE_H #include namespace torrent { class LIBTORRENT_EXPORT Piece { public: static constexpr uint32_t invalid_index = ~uint32_t(); Piece() = default; ~Piece() = default; Piece(const Piece&) = default; Piece& operator=(const Piece&) = default; Piece(uint32_t index, uint32_t offset, uint32_t length) : m_index(index), m_offset(offset), m_length(length) {} bool is_valid() const { return m_index != invalid_index; } uint32_t index() const { return m_index; } void set_index(uint32_t v) { m_index = v; } uint32_t offset() const { return m_offset; } void set_offset(uint32_t v) { m_offset = v; } uint32_t length() const { return m_length; } void set_length(uint32_t v) { m_length = v; } bool operator == (const Piece& p) const { return m_index == p.m_index && m_offset == p.m_offset && m_length == p.m_length; } bool operator != (const Piece& p) const { return m_index != p.m_index || m_offset != p.m_offset || m_length != p.m_length; } private: uint32_t m_index{invalid_index}; uint32_t m_offset{}; uint32_t m_length{}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/data/transfer_list.cc000066400000000000000000000212131522271512000224310ustar00rootroot00000000000000#include "config.h" #include "transfer_list.h" #include #include #include #include #include "data/chunk.h" #include "peer/peer_info.h" #include "block_failed.h" #include "block_transfer.h" #include "block_list.h" #include "exceptions.h" #include "piece.h" namespace torrent { TransferList::~TransferList() { assert(base_type::empty() && "TransferList::~TransferList() called on an non-empty object"); } TransferList::iterator TransferList::find(uint32_t index) { return std::find_if(begin(), end(), [index](BlockList* b) { return index == b->index(); }); } TransferList::const_iterator TransferList::find(uint32_t index) const { return std::find_if(begin(), end(), [index](BlockList* b) { return index == b->index(); }); } void TransferList::clear() { for (const auto& block_list : *this) { m_slot_canceled(block_list->index()); } for (const auto& block_list : *this) { delete block_list; } base_type::clear(); } TransferList::iterator TransferList::insert(const Piece& piece, uint32_t blockSize) { if (find(piece.index()) != end()) throw internal_error("Delegator::new_chunk(...) received an index that is already delegated."); auto blockList = new BlockList(piece, blockSize); m_slot_queued(piece.index()); return base_type::insert(end(), blockList); } // TODO: Create a destructor to ensure all blocklists have been cleared/invaldiated? TransferList::iterator TransferList::erase(iterator itr) { if (itr == end()) throw internal_error("TransferList::erase(...) itr == m_chunks.end()."); delete *itr; return base_type::erase(itr); } void TransferList::finished(BlockTransfer* transfer) { if (!transfer->is_valid()) throw internal_error("TransferList::finished(...) got transfer with wrong state."); uint32_t index = transfer->block()->index(); // Marks the transfer as complete and erases it. if (transfer->block()->completed(transfer)) m_slot_completed(index); } void TransferList::hash_succeeded(uint32_t index, Chunk* chunk) { auto blockListItr = find(index); if (!std::all_of((*blockListItr)->begin(), (*blockListItr)->end(), std::mem_fn(&Block::is_finished))) throw internal_error("TransferList::hash_succeeded(...) Finished blocks does not match size."); // The chunk should also be marked here or by the caller so that it // gets priority for syncing back to disk. if ((*blockListItr)->failed() != 0) mark_failed_peers(*blockListItr, chunk); // Add to a list of finished chunks indices with timestamps. This is // mainly used for torrent resume data on which chunks need to be // rehashed on crashes. // // We assume the chunk gets sync'ed within 10 minutes, so minimum // retention time of 30 minutes should be enough. The list only gets // pruned every 60 minutes, so any timer that reads values once // every 30 minutes is guaranteed to get them all as long as it is // ordered properly. m_completedList.emplace_back(this_thread::cached_time().count(), index); if (std::chrono::microseconds(m_completedList.front().first) + 60min < this_thread::cached_time()) { auto itr = std::find_if(m_completedList.begin(), m_completedList.end(), [](auto v) { return this_thread::cached_time() - 30min <= std::chrono::microseconds(v.first); }); m_completedList.erase(m_completedList.begin(), itr); } m_succeededCount++; erase(blockListItr); } struct transfer_list_compare_data { transfer_list_compare_data(Chunk* chunk, const Piece& p) : m_chunk(chunk), m_piece(p) { } bool operator () (BlockFailed::value_type failed) const { return m_chunk->compare_buffer(failed.first, m_piece.offset(), m_piece.length()); } Chunk* m_chunk; Piece m_piece; }; void TransferList::hash_failed(uint32_t index, Chunk* chunk) { auto blockListItr = find(index); if (blockListItr == end()) throw internal_error("TransferList::hash_failed(...) Could not find index."); if (!std::all_of((*blockListItr)->begin(), (*blockListItr)->end(), std::mem_fn(&Block::is_finished))) throw internal_error("TransferList::hash_failed(...) Finished blocks does not match size."); m_failedCount++; // Could propably also check promoted against size of the block // list. mark_and_disconnect_if_single_peer(*blockListItr); if ((*blockListItr)->attempt() == 0) { unsigned int promoted = update_failed(*blockListItr, chunk); if (promoted > 0 || promoted < (*blockListItr)->size()) { // Retry with the most popular blocks. (*blockListItr)->set_attempt(1); retry_most_popular(*blockListItr, chunk); // Also consider various other schemes, like using blocks from // only/mainly one peer. return; } } // TODO: Add a sanity check, e.g. >100 attempted tries. // Should we check if there's any peers whom have sent us bad data // before, and just clear those first? // Re-download the blocks. (*blockListItr)->do_all_failed(); } // update_failed(...) either increments the reference count of a // failed entry, or creates a new one if the data differs. unsigned int TransferList::update_failed(BlockList* blockList, Chunk* chunk) { unsigned int promoted = 0; blockList->inc_failed(); for (auto& transfer : *blockList) { if (transfer.failed_list() == NULL) transfer.set_failed_list(new BlockFailed()); auto failedItr = std::find_if(transfer.failed_list()->begin(), transfer.failed_list()->end(), transfer_list_compare_data(chunk, transfer.piece())); // TODO: If we get the different data from same peer, disconnect and mark that peer as // bad. (review code to make sure the peer gets disconnected) // // TODO: If we don't get any new data at all for any blocks (all blocks) for a long time, // disconnect all peers and mark them as bad. if (failedItr == transfer.failed_list()->end()) { // We've never encountered this data before, make a new entry. auto buffer = new char[transfer.piece().length()]; chunk->to_buffer(buffer, transfer.piece().offset(), transfer.piece().length()); transfer.failed_list()->emplace_back(buffer, 1); failedItr = transfer.failed_list()->end() - 1; // Count how many new data sets? } else { // Increment promoted when the entry's reference count becomes // larger than others, but not if it previously was the largest. auto maxItr = transfer.failed_list()->max_element(); if (maxItr->second == failedItr->second && maxItr != (transfer.failed_list()->reverse_max_element().base() - 1)) promoted++; failedItr->second++; } transfer.failed_list()->set_current(failedItr); transfer.leader()->set_failed_index(failedItr - transfer.failed_list()->begin()); } return promoted; } void TransferList::mark_failed_peers(BlockList* blockList, Chunk* chunk) { std::set badPeers; for (auto& block : *blockList) { // This chunk data is good, set it as current and // everyone who sent something else is a bad peer. block.failed_list()->set_current(std::find_if(block.failed_list()->begin(), block.failed_list()->end(), transfer_list_compare_data(chunk, block.piece()))); for (auto& transfer : *block.transfers()) if (transfer->failed_index() != block.failed_list()->current() && transfer->failed_index() != ~uint32_t()) badPeers.insert(transfer->peer_info()); } std::for_each(badPeers.begin(), badPeers.end(), m_slot_corrupt); } void TransferList::mark_and_disconnect_if_single_peer(BlockList* block_list) { PeerInfo* peer_info{}; auto itr = block_list->begin(); while (itr != block_list->end()) { for (auto& transfer : *itr->transfers()) { peer_info = transfer->peer_info(); break; } } while (itr != block_list->end()) { for (auto& transfer : *itr->transfers()) { if (transfer->peer_info() != peer_info) return; } } if (peer_info != nullptr) m_slot_corrupt(peer_info); } // Copy the stored data to the chunk from the failed entries with // largest reference counts. void TransferList::retry_most_popular(BlockList* blockList, Chunk* chunk) { for (auto& block : *blockList) { auto failedItr = block.failed_list()->reverse_max_element(); if (failedItr == block.failed_list()->rend()) throw internal_error("TransferList::retry_most_popular(...) No failed list entry found."); // The data is the same, so no need to copy. if (failedItr == block.failed_list()->current_reverse_iterator()) continue; // Change the leader to the currently held buffer? chunk->from_buffer(failedItr->first, block.piece().offset(), block.piece().length()); block.failed_list()->set_current(failedItr); } m_slot_completed(blockList->index()); } } // namespace torrent libtorrent-0.16.17/src/torrent/data/transfer_list.h000066400000000000000000000051131522271512000222740ustar00rootroot00000000000000#ifndef LIBTORRENT_TRANSFER_LIST_H #define LIBTORRENT_TRANSFER_LIST_H #include #include #include namespace torrent { class LIBTORRENT_EXPORT TransferList : public std::vector { public: using base_type = std::vector; using completed_list_type = std::vector>; using base_type::value_type; using base_type::reference; using base_type::difference_type; using base_type::iterator; using base_type::const_iterator; using base_type::reverse_iterator; using base_type::const_reverse_iterator; using base_type::size; using base_type::empty; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; TransferList() = default; ~TransferList(); TransferList(const TransferList&) = delete; TransferList& operator=(const TransferList&) = delete; iterator find(uint32_t index); const_iterator find(uint32_t index) const; const completed_list_type& completed_list() const { return m_completedList; } uint32_t succeeded_count() const { return m_succeededCount; } uint32_t failed_count() const { return m_failedCount; } // // Internal to libTorrent: // void clear(); iterator insert(const Piece& piece, uint32_t blockSize); iterator erase(iterator itr); void finished(BlockTransfer* transfer); void hash_succeeded(uint32_t index, Chunk* chunk); void hash_failed(uint32_t index, Chunk* chunk); using slot_chunk_index = std::function; using slot_peer_info = std::function; slot_chunk_index& slot_canceled() { return m_slot_canceled; } slot_chunk_index& slot_completed() { return m_slot_completed; } slot_chunk_index& slot_queued() { return m_slot_queued; } slot_peer_info& slot_corrupt() { return m_slot_corrupt; } private: static unsigned int update_failed(BlockList* blockList, Chunk* chunk); void mark_failed_peers(BlockList* blockList, Chunk* chunk); void mark_and_disconnect_if_single_peer(BlockList* blockList); void retry_most_popular(BlockList* blockList, Chunk* chunk); slot_chunk_index m_slot_canceled; slot_chunk_index m_slot_completed; slot_chunk_index m_slot_queued; slot_peer_info m_slot_corrupt; completed_list_type m_completedList; uint32_t m_succeededCount{0}; uint32_t m_failedCount{0}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/download.cc000066400000000000000000000400711522271512000204530ustar00rootroot00000000000000#include "config.h" #include #include #include "data/block_list.h" #include "data/chunk_list.h" #include "data/hash_queue.h" #include "data/hash_torrent.h" #include "download/chunk_selector.h" #include "download/chunk_statistics.h" #include "download/download_wrapper.h" #include "protocol/peer_connection_base.h" #include "protocol/peer_factory.h" #include "torrent/data/file.h" #include "torrent/download/choke_group.h" #include "torrent/download/choke_queue.h" #include "torrent/download_info.h" #include "torrent/net/socket_address.h" #include "torrent/peer/connection_list.h" #include "torrent/utils/log.h" #include "exceptions.h" #include "download.h" #include "object.h" #include "throttle.h" #define LT_LOG_THIS(log_level, log_fmt, ...) \ lt_log_print_info(LOG_TORRENT_##log_level, m_ptr->info(), "download", log_fmt, __VA_ARGS__); namespace torrent { const DownloadInfo* Download::info() const { return m_ptr->info(); } const download_data* Download::data() const { return m_ptr->data(); } void Download::open(int flags) { if (m_ptr->info()->is_open()) return; LT_LOG_THIS(INFO, "Opening torrent: flags:%0x.", flags); // Currently always open with no_create, as start will make sure // they are created. Need to fix this. m_ptr->main()->open(FileList::open_no_create); m_ptr->hash_checker()->hashing_ranges().insert(0, m_ptr->main()->file_list()->size_chunks()); // Mark the files by default to be created and resized. The client // should be allowed to pass a flag that will keep the old settings, // although loading resume data should really handle everything // properly. int fileFlags = File::flag_create_queued | File::flag_resize_queued; if (flags & open_enable_fallocate) fileFlags |= File::flag_fallocate; for (auto& file : *m_ptr->main()->file_list()) file->set_flags(fileFlags); } void Download::close(int flags) { if (m_ptr->info()->is_active()) stop(0); LT_LOG_THIS(INFO, "Closing torrent: flags:%0x.", flags); m_ptr->close(); } void Download::start(int flags) { DownloadInfo* info = m_ptr->info(); if (!m_ptr->hash_checker()->is_checked()) throw internal_error("Tried to start an unchecked download."); if (!info->is_open()) throw internal_error("Tried to start a closed download."); if (m_ptr->data()->mutable_completed_bitfield()->empty()) throw internal_error("Tried to start a download with empty bitfield."); if (info->is_active()) return; LT_LOG_THIS(INFO, "Starting torrent: flags:%0x.", flags); m_ptr->data()->verify_wanted_chunks("Download::start(...)"); if (m_ptr->connection_type() == CONNECTION_INITIAL_SEED) { if (!m_ptr->main()->start_initial_seeding()) set_connection_type(CONNECTION_SEED); } m_ptr->main()->start(flags); if ((flags & start_skip_tracker)) m_ptr->main()->tracker_controller().enable_dont_reset_stats(); else m_ptr->main()->tracker_controller().enable(); // Reset the uploaded/download baseline when we restart the download // so that broken trackers get the right uploaded ratio. if (!(flags & start_keep_baseline)) { info->set_uploaded_baseline(info->up_rate()->total()); info->set_completed_baseline(m_ptr->main()->file_list()->completed_bytes()); lt_log_print_info(LOG_TRACKER_EVENTS, info, "download", "Setting new baseline on start: uploaded:%" PRIu64 " completed:%" PRIu64 ".", info->uploaded_baseline(), info->completed_baseline()); } if (!(flags & start_skip_tracker)) m_ptr->main()->tracker_controller().send_start_event(); } void Download::stop(int flags) { if (!m_ptr->info()->is_active()) return; LT_LOG_THIS(INFO, "Stopping torrent: flags:%0x.", flags); m_ptr->main()->stop(); if (!(flags & stop_skip_tracker)) m_ptr->main()->tracker_controller().send_stop_event(); m_ptr->main()->tracker_controller().disable(); } // When session data is loaded, it includes a bitfield and list of files+mtime. // // As the download is opened it checks the mtimes of the files, if the file is missing or has wrong mtime then // the file's range is added to hashing_ranges() and the bitfield range is cleared. // // Hash check with try_quick never does any actual hashing, it only checks if the underlying file // exists and if it does it fails the whole try_quick hash check. bool Download::hash_check(bool try_quick) { if (m_ptr->hash_checker()->is_checking()) throw internal_error("Download::hash_check(...) called but the hash is already being checked."); if (!m_ptr->info()->is_open() || m_ptr->info()->is_active()) throw internal_error("Download::hash_check(...) called on a closed or active download."); if (m_ptr->hash_checker()->is_checked()) throw internal_error("Download::hash_check(...) called but already hash checked."); Bitfield* bitfield = m_ptr->data()->mutable_completed_bitfield(); LT_LOG_THIS(INFO, "Checking hash: allocated:%i try_quick:%i.", !bitfield->empty(), (int)try_quick); if (bitfield->empty()) { // The bitfield still hasn't been allocated, so no resume data was // given. bitfield->allocate(); bitfield->unset_all(); m_ptr->hash_checker()->hashing_ranges().insert(0, m_ptr->main()->file_list()->size_chunks()); } else if (!try_quick) { // Clear ranges we're about to recheck so mark_completed can re-set them. for (auto range : m_ptr->hash_checker()->hashing_ranges()) bitfield->unset_range(range.first, range.second); // TODO: Consider adding a sanity check above instead, and print out the files (size+range) of // the invalid marked bit. } m_ptr->main()->file_list()->update_completed(); return m_ptr->hash_checker()->start(try_quick); } // Propably not correct, need to clear content, etc. void Download::hash_stop() { if (!m_ptr->hash_checker()->is_checking()) return; LT_LOG_THIS(INFO, "Hashing stopped.", 0); m_ptr->hash_checker()->hashing_ranges().erase(0, m_ptr->hash_checker()->position()); m_ptr->hash_queue()->remove(m_ptr->data()); m_ptr->hash_checker()->clear(); } bool Download::is_hash_checked() const { return m_ptr->hash_checker()->is_checked(); } bool Download::is_hash_checking() const { return m_ptr->hash_checker()->is_checking(); } const std::string& Download::hash_error_message() const { return m_ptr->hash_checker()->error_message(); } void Download::set_pex_enabled(bool enabled) { if (enabled) m_ptr->info()->set_pex_enabled(); else m_ptr->info()->unset_flags(DownloadInfo::flag_pex_enabled); } Object* Download::bencode() { return m_ptr->bencode(); } const Object* Download::bencode() const { return m_ptr->bencode(); } FileList* Download::file_list() const { return m_ptr->main()->file_list(); } tracker::TrackerControllerWrapper Download::tracker_controller() { return m_ptr->main()->tracker_controller(); } const tracker::TrackerControllerWrapper Download::c_tracker_controller() const { return m_ptr->main()->tracker_controller(); } PeerList* Download::peer_list() { return m_ptr->main()->peer_list(); } const PeerList* Download::peer_list() const { return m_ptr->main()->peer_list(); } const TransferList* Download::transfer_list() const { return m_ptr->main()->delegator()->transfer_list(); } ConnectionList* Download::connection_list() { return m_ptr->main()->connection_list(); } const ConnectionList* Download::connection_list() const { return m_ptr->main()->connection_list(); } uint64_t Download::bytes_done() const { uint64_t a = m_ptr->main()->file_list()->completed_bytes(); for (auto list : *m_ptr->main()->delegator()->transfer_list()) a += std::accumulate(list->begin(), list->end(), uint64_t{}, [](auto sum, const auto& t) { return t.is_finished() ? sum + t.piece().length() : sum; }); return a; } uint32_t Download::chunks_hashed() const { return m_ptr->hash_checker()->position(); } const uint8_t* Download::chunks_seen() const { return !m_ptr->main()->chunk_statistics()->empty() ? &*m_ptr->main()->chunk_statistics()->begin() : NULL; } void Download::set_chunks_done(uint32_t chunks_done, uint32_t chunks_wanted) { if (m_ptr->info()->is_open() || !m_ptr->data()->mutable_completed_bitfield()->empty()) throw input_error("Download::set_chunks_done(...) Invalid state."); chunks_done = std::min(chunks_done, m_ptr->file_list()->size_chunks()); chunks_wanted = std::min(chunks_wanted, m_ptr->file_list()->size_chunks() - chunks_done); m_ptr->data()->mutable_completed_bitfield()->set_size_set(chunks_done); m_ptr->data()->set_wanted_chunks(chunks_wanted); } void Download::set_bitfield(bool allSet) { if (m_ptr->hash_checker()->is_checked() || m_ptr->hash_checker()->is_checking()) throw input_error("Download::set_bitfield(...) Download in invalid state."); Bitfield* bitfield = m_ptr->data()->mutable_completed_bitfield(); bitfield->allocate(); if (allSet) bitfield->set_all(); else bitfield->unset_all(); m_ptr->data()->update_wanted_chunks(); m_ptr->hash_checker()->hashing_ranges().clear(); } void Download::set_bitfield(const uint8_t* first, const uint8_t* last) { if (m_ptr->hash_checker()->is_checked() || m_ptr->hash_checker()->is_checking()) throw input_error("Download::set_bitfield(...) Download in invalid state."); if (std::distance(first, last) != static_cast(m_ptr->main()->file_list()->bitfield()->size_bytes())) throw input_error("Download::set_bitfield(...) Invalid length."); Bitfield* bitfield = m_ptr->data()->mutable_completed_bitfield(); bitfield->allocate(); std::memcpy(bitfield->begin(), first, bitfield->size_bytes()); bitfield->update(); m_ptr->data()->update_wanted_chunks(); m_ptr->hash_checker()->hashing_ranges().clear(); } void Download::update_range(int flags, uint32_t first, uint32_t last) { if (m_ptr->hash_checker()->is_checked() || m_ptr->hash_checker()->is_checking()) throw input_error("Download::clear_range(...) Download is hash checked/checking."); if (m_ptr->main()->file_list()->bitfield()->empty()) throw input_error("Download::clear_range(...) Bitfield is empty."); if (flags & update_range_recheck) m_ptr->hash_checker()->hashing_ranges().insert(first, last); if (flags & (update_range_clear | update_range_recheck)) { m_ptr->data()->mutable_completed_bitfield()->unset_range(first, last); m_ptr->data()->update_wanted_chunks(); } } void Download::sync_chunks() { m_ptr->main()->chunk_list()->sync_chunks_no_cache(ChunkList::sync_all | ChunkList::sync_force); } uint32_t Download::peers_complete() const { return m_ptr->main()->chunk_statistics()->complete(); } uint32_t Download::peers_accounted() const { return m_ptr->main()->chunk_statistics()->accounted(); } uint32_t Download::peers_currently_unchoked() const { return m_ptr->main()->choke_group()->up_queue()->size_unchoked(); } uint32_t Download::peers_currently_interested() const { return m_ptr->main()->choke_group()->up_queue()->size_total(); } uint32_t Download::size_pex() const { return m_ptr->main()->info()->size_pex(); } uint32_t Download::max_size_pex() const { return m_ptr->main()->info()->max_size_pex(); } bool Download::accepting_new_peers() const { return m_ptr->info()->is_accepting_new_peers(); } // DEPRECATE uint32_t Download::uploads_max() const { if (m_ptr->main()->up_group_entry()->max_slots() == DownloadInfo::unlimited) return 0; return m_ptr->main()->up_group_entry()->max_slots(); } uint32_t Download::uploads_min() const { // if (m_ptr->main()->up_group_entry()->min_slots() == DownloadInfo::unlimited) // return 0; return m_ptr->main()->up_group_entry()->min_slots(); } uint32_t Download::downloads_max() const { if (m_ptr->main()->down_group_entry()->max_slots() == DownloadInfo::unlimited) return 0; return m_ptr->main()->down_group_entry()->max_slots(); } uint32_t Download::downloads_min() const { // if (m_ptr->main()->down_group_entry()->min_slots() == DownloadInfo::unlimited) // return 0; return m_ptr->main()->down_group_entry()->min_slots(); } void Download::set_upload_throttle(Throttle* t) { if (m_ptr->info()->is_active()) throw internal_error("Download::set_upload_throttle() called on active download."); m_ptr->main()->set_upload_throttle(t->throttle_list()); } void Download::set_download_throttle(Throttle* t) { if (m_ptr->info()->is_active()) throw internal_error("Download::set_download_throttle() called on active download."); m_ptr->main()->set_download_throttle(t->throttle_list()); } void Download::send_completed() { m_ptr->main()->tracker_controller().send_completed_event(); } void Download::manual_request(bool force) { m_ptr->main()->tracker_controller().manual_request(force); } void Download::manual_cancel() { m_ptr->main()->tracker_controller().close(); } // DEPRECATE void Download::set_uploads_max(uint32_t v) { if (v > (1 << 16)) throw input_error("Max uploads must be between 0 and 2^16."); // For the moment, treat 0 as unlimited. m_ptr->main()->up_group_entry()->set_max_slots(v == 0 ? DownloadInfo::unlimited : v); m_ptr->main()->choke_group()->up_queue()->balance_entry(m_ptr->main()->up_group_entry()); } void Download::set_uploads_min(uint32_t v) { if (v > (1 << 16)) throw input_error("Min uploads must be between 0 and 2^16."); // For the moment, treat 0 as unlimited. m_ptr->main()->up_group_entry()->set_min_slots(v); m_ptr->main()->choke_group()->up_queue()->balance_entry(m_ptr->main()->up_group_entry()); } void Download::set_downloads_max(uint32_t v) { if (v > (1 << 16)) throw input_error("Max downloads must be between 0 and 2^16."); // For the moment, treat 0 as unlimited. m_ptr->main()->down_group_entry()->set_max_slots(v == 0 ? DownloadInfo::unlimited : v); m_ptr->main()->choke_group()->down_queue()->balance_entry(m_ptr->main()->down_group_entry()); } void Download::set_downloads_min(uint32_t v) { if (v > (1 << 16)) throw input_error("Min downloads must be between 0 and 2^16."); // For the moment, treat 0 as unlimited. m_ptr->main()->down_group_entry()->set_min_slots(v); m_ptr->main()->choke_group()->down_queue()->balance_entry(m_ptr->main()->down_group_entry()); } Download::ConnectionType Download::connection_type() const { return static_cast(m_ptr->connection_type()); } void Download::set_connection_type(ConnectionType t) { if (m_ptr->info()->is_meta_download()) { m_ptr->main()->connection_list()->slot_new_connection(&createPeerConnectionMetadata); return; } switch (t) { case CONNECTION_LEECH: m_ptr->main()->connection_list()->slot_new_connection(&createPeerConnectionDefault); break; case CONNECTION_SEED: m_ptr->main()->connection_list()->slot_new_connection(&createPeerConnectionSeed); break; case CONNECTION_INITIAL_SEED: if (info()->is_active() && m_ptr->main()->initial_seeding() == NULL) throw input_error("Can't switch to initial seeding: download is active."); m_ptr->main()->connection_list()->slot_new_connection(&createPeerConnectionInitialSeed); break; default: throw input_error("torrent::Download::set_connection_type(...) received an unknown type."); } m_ptr->set_connection_type(t); } heuristics_enum Download::upload_choke_heuristic() const { return m_ptr->main()->choke_group()->up_queue()->heuristics(); } void Download::set_upload_choke_heuristic(heuristics_enum t) { if (t >= HEURISTICS_MAX_SIZE) throw input_error("Invalid heuristics value."); m_ptr->main()->choke_group()->up_queue()->set_heuristics(t); } heuristics_enum Download::download_choke_heuristic() const { return m_ptr->main()->choke_group()->down_queue()->heuristics(); } void Download::set_download_choke_heuristic(heuristics_enum t) { if (t >= HEURISTICS_MAX_SIZE) throw input_error("Invalid heuristics value."); m_ptr->main()->choke_group()->down_queue()->set_heuristics(t); } void Download::update_priorities() { m_ptr->receive_update_priorities(); } void Download::add_peer(const sockaddr* sa, int port) { if (m_ptr->info()->is_private()) return; auto tmp = sa_copy(sa); sap_set_port(tmp, port); m_ptr->main()->add_peer(tmp.get()); } DownloadMain* Download::main() { return m_ptr->main(); } } // namespace torrent libtorrent-0.16.17/src/torrent/download.h000066400000000000000000000127111522271512000203150ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_H #define LIBTORRENT_DOWNLOAD_H #include #include #include #include #include #include #include namespace torrent { class ConnectionList; class DownloadInfo; class DownloadMain; class download_data; // Download is safe to copy and destory as it is just a pointer to an // internal class. class LIBTORRENT_EXPORT Download { public: static constexpr uint32_t numwanted_diabled = ~uint32_t(); // Start and open flags can be stored in the same integer, same for // stop and close flags. static constexpr int open_enable_fallocate = (1 << 0); static constexpr int start_no_create = (1 << 1); static constexpr int start_keep_baseline = (1 << 2); static constexpr int start_skip_tracker = (1 << 3); static constexpr int stop_skip_tracker = (1 << 0); Download(DownloadWrapper* d = NULL) : m_ptr(d) {} const DownloadInfo* info() const; const download_data* data() const; // Not active atm. Opens and prepares/closes the files. void open(int flags = 0); void close(int flags = 0); // When 'tryQuick' is true, it will only check if the chunks can be // mmaped and stops if one is encountered. If it doesn't find any // mappable chunks it will return true to indicate that it is // finished and a hash done signal has been queued. // // Chunk ranges that have valid resume data won't be checked. bool hash_check(bool tryQuick); void hash_stop(); // Start/stop the download. The torrent must be open. void start(int flags = 0); void stop(int flags = 0); // Does not check if the download has been removed. bool is_valid() const { return m_ptr; } bool is_hash_checked() const; bool is_hash_checking() const; const std::string& hash_error_message() const; void set_pex_enabled(bool enabled); Object* bencode(); const Object* bencode() const; tracker::TrackerControllerWrapper tracker_controller(); const tracker::TrackerControllerWrapper c_tracker_controller() const; FileList* file_list() const; PeerList* peer_list(); const PeerList* peer_list() const; const TransferList* transfer_list() const; ConnectionList* connection_list(); const ConnectionList* connection_list() const; // Bytes completed. uint64_t bytes_done() const; uint32_t chunks_hashed() const; const uint8_t* chunks_seen() const; // Set the number of finished chunks for closed torrents. void set_chunks_done(uint32_t chunks_done, uint32_t chunks_wanted); // Use the below to set the resume data and what chunk ranges need // to be hash checked. If they arn't called then by default it will // use an cleared bitfield and check the whole range. // // These must be called when is_open, !is_checked and !is_checking. void set_bitfield(bool allSet); void set_bitfield(const uint8_t* first, const uint8_t* last); static constexpr int update_range_recheck = (1 << 0); static constexpr int update_range_clear = (1 << 1); void update_range(int flags, uint32_t first, uint32_t last); // Temporary hack for syncing chunks to disk before hash resume is // saved. void sync_chunks(); uint32_t peers_complete() const; uint32_t peers_accounted() const; uint32_t peers_currently_unchoked() const; uint32_t peers_currently_interested() const; uint32_t size_pex() const; uint32_t max_size_pex() const; bool accepting_new_peers() const; uint32_t uploads_max() const; void set_uploads_max(uint32_t v); uint32_t uploads_min() const; void set_uploads_min(uint32_t v); uint32_t downloads_max() const; void set_downloads_max(uint32_t v); uint32_t downloads_min() const; void set_downloads_min(uint32_t v); void set_upload_throttle(Throttle* t); void set_download_throttle(Throttle* t); // Some temporary functions that are routed to // TrackerManager... Clean this up. void send_completed(); void manual_request(bool force); void manual_cancel(); enum ConnectionType { CONNECTION_LEECH, CONNECTION_SEED, CONNECTION_INITIAL_SEED, CONNECTION_METADATA, }; ConnectionType connection_type() const; void set_connection_type(ConnectionType t); heuristics_enum upload_choke_heuristic() const; void set_upload_choke_heuristic(heuristics_enum t); heuristics_enum download_choke_heuristic() const; void set_download_choke_heuristic(heuristics_enum t); // Call this when you want the modifications of the download priorities // in the entries to take effect. It is slightly expensive as it rechecks // all the peer bitfields to see if we are still interested. void update_priorities(); void add_peer(const sockaddr* addr, int port); DownloadWrapper* ptr() { return m_ptr; } DownloadMain* main(); private: DownloadWrapper* m_ptr; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/download/000077500000000000000000000000001522271512000201425ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/download/choke_group.cc000066400000000000000000000007611522271512000227620ustar00rootroot00000000000000#include "config.h" #include "choke_group.h" #include #include "download/download_main.h" #include "download_info.h" namespace torrent { uint64_t choke_group::up_rate() const { return std::accumulate(m_first, m_last, uint64_t{0}, [](uint64_t i, auto r) { return i + r.up_rate()->rate(); }); } uint64_t choke_group::down_rate() const { return std::accumulate(m_first, m_last, uint64_t{0}, [](uint64_t i, auto r) { return i + r.down_rate()->rate(); }); } } // namespace torrent libtorrent-0.16.17/src/torrent/download/choke_group.h000066400000000000000000000046501522271512000226250ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_CHOKE_GROUP_H #define LIBTORRENT_DOWNLOAD_CHOKE_GROUP_H #include #include #include #include #include // TODO: Separate out resource_manager_entry. #include namespace torrent { class choke_queue; class resource_manager_entry; class LIBTORRENT_EXPORT choke_group { public: enum tracker_mode_enum { TRACKER_MODE_NORMAL, TRACKER_MODE_AGGRESSIVE }; const std::string& name() const { return m_name; } void set_name(const std::string& name) { m_name = name; } tracker_mode_enum tracker_mode() const { return m_tracker_mode; } void set_tracker_mode(tracker_mode_enum tm) { m_tracker_mode = tm; } choke_queue* up_queue() { return &m_up_queue; } choke_queue* down_queue() { return &m_down_queue; } const choke_queue* c_up_queue() const { return &m_up_queue; } const choke_queue* c_down_queue() const { return &m_down_queue; } uint32_t up_requested() const { return std::min(m_up_queue.size_total(), m_up_queue.max_unchoked()); } uint32_t down_requested() const { return std::min(m_down_queue.size_total(), m_down_queue.max_unchoked()); } bool empty() const { return m_first == m_last; } uint32_t size() const { return std::distance(m_first, m_last); } uint64_t up_rate() const; uint64_t down_rate() const; unsigned int up_unchoked() const { return m_up_queue.size_unchoked(); } unsigned int down_unchoked() const { return m_down_queue.size_unchoked(); } // Internal: resource_manager_entry* first() { return m_first; } resource_manager_entry* last() { return m_last; } void set_first(resource_manager_entry* first) { m_first = first; } void set_last(resource_manager_entry* last) { m_last = last; } void inc_iterators() { m_first++; m_last++; } void dec_iterators() { m_first--; m_last--; } private: std::string m_name; tracker_mode_enum m_tracker_mode{TRACKER_MODE_NORMAL}; choke_queue m_up_queue; choke_queue m_down_queue{choke_queue::flag_unchoke_all_new}; resource_manager_entry* m_first{}; resource_manager_entry* m_last{}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/download/choke_queue.cc000066400000000000000000000613761522271512000227630ustar00rootroot00000000000000#include "config.h" #include "choke_queue.h" #include #include #include #include "protocol/peer_connection_base.h" #include "torrent/download/group_entry.h" #include "torrent/peer/connection_list.h" #include "torrent/peer/choke_status.h" #include "torrent/utils/log.h" // TODO: Add a different logging category. #define LT_LOG_THIS(log_fmt, ...) \ lt_log_print_subsystem(LOG_TORRENT_INFO, "choke_queue", log_fmt, __VA_ARGS__); namespace torrent { static bool choke_manager_less(choke_queue::value_type v1, choke_queue::value_type v2) { return v1.weight < v2.weight; } static inline bool should_connection_unchoke(choke_queue* cq, PeerConnectionBase* pcb) { return pcb->should_connection_unchoke(cq); } static inline void log_choke_changes_func_new(void* address, const char* title, int quota, int adjust) { lt_log_print(LOG_INSTRUMENTATION_CHOKE, "%p %i %s %i %i", address, 0, //lf->last_update(), title, quota, adjust); } choke_queue::~choke_queue() { assert(m_currently_unchoked == 0 && "choke_queue::~choke_queue() called but m_currently_unchoked != 0."); assert(m_currently_queued == 0 && "choke_queue::~choke_queue() called but m_currently_queued != 0."); } // 1 > 1 // 9 > 2 // 17 > 3 < 21 // 25 > 4 < 31 // 33 > 5 < 41 // 65 > 9 < 81 inline uint32_t choke_queue::max_alternate() const { if (m_currently_unchoked < 31) return (m_currently_unchoked + 7) / 8; else return (m_currently_unchoked + 9) / 10; } group_stats choke_queue::prepare_weights(group_stats gs) { // gs.sum_min_needed = 0; // gs.sum_max_needed = 0; // gs.sum_max_leftovers = 0; // Needs to reflect how many we can optimistically unchoke after choking unchoked connections? // // also remember to clear the queue/unchoked thingies. for (auto group : m_group_container) { m_heuristics_list[m_heuristics].slot_choke_weight(group->mutable_unchoked()->begin(), group->mutable_unchoked()->end()); std::sort(group->mutable_unchoked()->begin(), group->mutable_unchoked()->end(), choke_manager_less); m_heuristics_list[m_heuristics].slot_unchoke_weight(group->mutable_queued()->begin(), group->mutable_queued()->end()); std::sort(group->mutable_queued()->begin(), group->mutable_queued()->end(), choke_manager_less); // Aggregate the statistics... Remember to update them after // optimistic/pessimistic unchokes. gs.sum_min_needed += std::min({group->size_connections(), group->max_slots(), group->min_slots()}); uint32_t max_slots = std::min(group->size_connections(), group->max_slots()); gs.sum_max_needed += max_slots; gs.sum_max_leftovers += group->size_connections() - max_slots; // Counter for how many can choke/unchoke based on weights? // However one should never have zero weight for any weight group. } return gs; } group_stats choke_queue::retrieve_connections(group_stats gs, container_type* queued, container_type* unchoked) { for (auto entry : m_group_container) { unsigned int min_slots = std::min(entry->min_slots(), entry->max_slots()); lt_log_print(LOG_PEER_CHOKE_QUEUE, "Choke queue retrieve_connections; queued:%u unchoked:%u min_slots:%u max_slots:%u.", (unsigned)entry->queued()->size(), (unsigned)entry->unchoked()->size(), min_slots, entry->max_slots()); // Disable this after finding the flaw... ? // if (entry->unchoked()->size() > entry->max_slots()) { // unsigned int count = 0; // // Still needing choke/unchoke to fill min_size. // while (entry->unchoked()->size() > entry->max_slots() && !entry->unchoked()->empty()) // count += m_slotConnection(entry->unchoked()->back().connection, true); // // m_slotUnchoke(-count); // Move this? // gs.now_choked += entry->unchoked()->size(); // Need to add this... // } if (entry->unchoked()->size() < min_slots) { // Currently unchoked is less than min_slots, so don't give any // candidates for choking and also check if we can fill the // requirement by unchoking queued connections. unsigned int count = 0; // Still needing choke/unchoke to fill min_size. while (!entry->queued()->empty() && entry->unchoked()->size() < min_slots) count += m_slotConnection(entry->queued()->back().connection, false); gs.changed_unchoked += count; gs.now_unchoked += entry->unchoked()->size(); } else { // TODO: This only handles a single weight group, fixme. auto first = entry->unchoked()->begin() + min_slots; auto last = entry->unchoked()->end(); unchoked->insert(unchoked->end(), first, last); gs.now_unchoked += min_slots; } // TODO: Does not do optimistic unchokes if min_slots >= max_slots. if (entry->unchoked()->size() < entry->max_slots()) { // We can assume that at either we have no queued connections or // 'min_slots' has been reached. queued->insert(queued->end(), entry->queued()->end() - std::min(entry->queued()->size(), entry->max_slots() - entry->unchoked()->size()), entry->queued()->end()); } } return gs; } void choke_queue::rebuild_containers(container_type* queued, container_type* unchoked) { queued->clear(); unchoked->clear(); for (auto& group : m_group_container) { queued->insert(queued->end(), group->queued()->begin(), group->queued()->end()); unchoked->insert(unchoked->end(), group->unchoked()->begin(), group->unchoked()->end()); } } void choke_queue::balance() { LT_LOG_THIS("balancing queue: heuristics:%i currently_unchoked:%" PRIu32 " max_unchoked:%" PRIu32, m_heuristics, m_currently_unchoked, m_maxUnchoked); // Return if no balancing is needed. Don't return if is_unlimited() // as we might have just changed the value and have interested that // can be unchoked. // // TODO: Check if unlimited, in that case we don't need to balance // if we got no queued connections. if (m_currently_unchoked == m_maxUnchoked) return; container_type queued; container_type unchoked; group_stats gs{}; gs = prepare_weights(gs); gs = retrieve_connections(gs, &queued, &unchoked); if (gs.changed_unchoked != 0) m_slotUnchoke(gs.changed_unchoked); // If we have more unchoked than max global slots allow for, // 'can_unchoke' will be negative. // // Throws std::bad_function_call if 'set_slot_can_unchoke' is not // set. int can_unchoke = m_slotCanUnchoke(); int max_unchoked = std::min(m_maxUnchoked, uint32_t{1} << 20); int adjust = max_unchoked - static_cast(unchoked.size() + gs.now_unchoked); adjust = std::min(adjust, can_unchoke); log_choke_changes_func_new(this, "balance", m_maxUnchoked, adjust); int result = 0; if (adjust > 0) { result = adjust_choke_range(queued.begin(), queued.end(), &queued, &unchoked, adjust, false); } else if (adjust < 0) { // We can do the choking before the slot is called as this // choke_queue won't be unchoking the same peers due to the // call-back. result = -adjust_choke_range(unchoked.begin(), unchoked.end(), &unchoked, &queued, -adjust, true); } if (result != 0) m_slotUnchoke(result); LT_LOG_THIS("balanced queue: adjust:%i can_unchoke:%i queued:%zu unchoked:%zu result:%i", adjust, can_unchoke, queued.size(), unchoked.size(), result); } void choke_queue::balance_entry(group_entry* entry) { m_heuristics_list[m_heuristics].slot_choke_weight(entry->mutable_unchoked()->begin(), entry->mutable_unchoked()->end()); std::sort(entry->mutable_unchoked()->begin(), entry->mutable_unchoked()->end(), choke_manager_less); m_heuristics_list[m_heuristics].slot_unchoke_weight(entry->mutable_queued()->begin(), entry->mutable_queued()->end()); std::sort(entry->mutable_queued()->begin(), entry->mutable_queued()->end(), choke_manager_less); int count = 0; unsigned int min_slots = std::min(entry->min_slots(), entry->max_slots()); while (!entry->unchoked()->empty() && entry->unchoked()->size() > entry->max_slots()) count -= m_slotConnection(entry->unchoked()->back().connection, true); while (!entry->queued()->empty() && entry->unchoked()->size() < min_slots) count += m_slotConnection(entry->queued()->back().connection, false); m_slotUnchoke(count); } int choke_queue::cycle(uint32_t quota) { // TODO: This should not use the old values, but rather the number // of unchoked this round. // HACKKKKKK container_type queued; container_type unchoked; rebuild_containers(&queued, &unchoked); int oldSize = unchoked.size(); uint32_t alternate = max_alternate(); queued.clear(); unchoked.clear(); group_stats gs{}; gs = prepare_weights(gs); gs = retrieve_connections(gs, &queued, &unchoked); quota = std::min(quota, m_maxUnchoked); quota = quota - std::min(quota, gs.now_unchoked); uint32_t adjust = (unchoked.size() < quota) ? (quota - unchoked.size()) : 0; adjust = std::max(adjust, alternate); adjust = std::min(adjust, quota); log_choke_changes_func_new(this, "cycle", quota, adjust); lt_log_print(LOG_PEER_CHOKE_QUEUE, "Called cycle; quota:%u adjust:%i alternate:%i queued:%u unchoked:%u.", quota, adjust, alternate, (unsigned)queued.size(), (unsigned)unchoked.size()); uint32_t unchoked_count = adjust_choke_range(queued.begin(), queued.end(), &queued, &unchoked, adjust, false); if (unchoked.size() > quota) adjust_choke_range(unchoked.begin(), unchoked.end() - unchoked_count, &unchoked, &queued, unchoked.size() - quota, true); if (unchoked.size() > quota) throw internal_error("choke_queue::cycle() unchoked.size() > quota."); rebuild_containers(&queued, &unchoked); // Remove... lt_log_print(LOG_PEER_CHOKE_QUEUE, "After cycle; queued:%u unchoked:%u unchoked_count:%i old_size:%i.", (unsigned)queued.size(), (unsigned)unchoked.size(), unchoked_count, oldSize); return (static_cast(unchoked.size()) - oldSize); // + gs.changed_unchoke } void choke_queue::set_queued(PeerConnectionBase* pc, choke_status* base) { if (base->queued() || base->unchoked()) return; base->set_queued(true); if (base->snubbed()) return; base->entry()->connection_queued(pc); modify_currently_queued(1); if (!is_full() && (m_flags & flag_unchoke_all_new || m_slotCanUnchoke() > 0) && should_connection_unchoke(this, pc) && base->time_last_choke() + 10s < this_thread::cached_time()) { m_slotConnection(pc, false); m_slotUnchoke(1); } } void choke_queue::set_not_queued(PeerConnectionBase* pc, choke_status* base) { if (!base->queued()) return; base->set_queued(false); if (base->snubbed()) return; if (base->unchoked()) { m_slotConnection(pc, true); m_slotUnchoke(-1); } base->entry()->connection_unqueued(pc); modify_currently_queued(-1); } void choke_queue::set_snubbed(PeerConnectionBase* pc, choke_status* base) { if (base->snubbed()) return; base->set_snubbed(true); if (base->unchoked()) { m_slotConnection(pc, true); m_slotUnchoke(-1); } else if (!base->queued()) { return; } base->entry()->connection_unqueued(pc); modify_currently_queued(-1); base->set_queued(false); } void choke_queue::set_not_snubbed(PeerConnectionBase* pc, choke_status* base) { if (!base->snubbed()) return; base->set_snubbed(false); if (!base->queued()) return; if (base->unchoked()) throw internal_error("choke_queue::set_not_snubbed(...) base->unchoked()."); base->entry()->connection_queued(pc); modify_currently_queued(1); if (!is_full() && (m_flags & flag_unchoke_all_new || m_slotCanUnchoke() > 0) && should_connection_unchoke(this, pc) && base->time_last_choke() + 10s < this_thread::cached_time()) { m_slotConnection(pc, false); m_slotUnchoke(1); } } // We are no longer in m_connectionList. void choke_queue::disconnected(PeerConnectionBase* pc, choke_status* base) { if (base->snubbed()) { // Do nothing. } else if (base->unchoked()) { m_slotUnchoke(-1); base->entry()->connection_choked(pc); modify_currently_unchoked(-1); } else if (base->queued()) { base->entry()->connection_unqueued(pc); modify_currently_queued(-1); } base->set_queued(false); } // No need to do any choking as the next choke balancing will take // care of things. void choke_queue::move_connections(choke_queue* src, choke_queue* dest, [[maybe_unused]] DownloadMain* download, group_entry* base) { if (src != NULL) { auto itr = std::find(src->m_group_container.begin(), src->m_group_container.end(), base); if (itr == src->m_group_container.end()) throw internal_error("choke_queue::move_connections(...) could not find group."); std::swap(*itr, src->m_group_container.back()); src->m_group_container.pop_back(); } if (dest != NULL) { dest->m_group_container.push_back(base); } if (src == NULL || dest == NULL) return; src->modify_currently_queued(-base->queued()->size()); src->modify_currently_unchoked(-base->unchoked()->size()); dest->modify_currently_queued(base->queued()->size()); dest->modify_currently_unchoked(base->unchoked()->size()); } // // Heuristics: // static void choke_manager_allocate_slots(choke_queue::iterator first, choke_queue::iterator last, uint32_t max, const uint32_t* weights, choke_queue::target_type* target) { // Sorting the connections from the lowest to highest value. // TODO: std::sort(first, last, choke_manager_less); // 'weightTotal' only contains the weight of targets that have // connections to unchoke. When all connections are in a group are // to be unchoked, then the group's weight is removed. uint32_t weightTotal = 0; uint32_t unchoke = max; target[0].second = first; for (uint32_t i = 0; i < choke_queue::order_max_size; i++) { target[i].first = 0; target[i + 1].second = std::find_if(target[i].second, last, [i](auto& v) { return (i * choke_queue::order_base + (choke_queue::order_base - 1)) < v.weight; }); if (std::distance(target[i].second, target[i + 1].second) != 0) weightTotal += weights[i]; } // Spread available unchoke slots as long as we can give everyone an // equal share. while (weightTotal != 0 && unchoke / weightTotal > 0) { uint32_t base = unchoke / weightTotal; for (uint32_t itr = 0; itr < choke_queue::order_max_size; itr++) { uint32_t s = std::distance(target[itr].second, target[itr + 1].second); if (weights[itr] == 0 || target[itr].first >= s) continue; uint32_t u = std::min(s - target[itr].first, base * weights[itr]); unchoke -= u; target[itr].first += u; if (target[itr].first >= s) weightTotal -= weights[itr]; } } // Spread the remainder starting from a random position based on the // total weight. This will ensure that aggregated over time we // spread the unchokes equally according to the weight table. if (weightTotal != 0 && unchoke != 0) { uint32_t start = ::random() % weightTotal; unsigned int itr = 0; for ( ; ; itr++) { uint32_t s = std::distance(target[itr].second, target[itr + 1].second); if (weights[itr] == 0 || target[itr].first >= s) continue; if (start < weights[itr]) break; start -= weights[itr]; } for ( ; weightTotal != 0 && unchoke != 0; itr = (itr + 1) % choke_queue::order_max_size) { uint32_t s = std::distance(target[itr].second, target[itr + 1].second); if (weights[itr] == 0 || target[itr].first >= s) continue; uint32_t u = std::min({unchoke, s - target[itr].first, weights[itr] - start}); start = 0; unchoke -= u; target[itr].first += u; if (target[itr].first >= s) weightTotal -= weights[itr]; } } } template static bool range_is_contained(Itr first, Itr last, Itr lower_bound, Itr upper_bound) { return first >= lower_bound && last <= upper_bound && first <= last; } uint32_t choke_queue::adjust_choke_range(iterator first, iterator last, container_type* src_container, container_type* dest_container, uint32_t max, bool is_choke) { target_type target[order_max_size + 1]; if (is_choke) { // TODO: m_heuristics_list[m_heuristics].slot_choke_weight(first, last); choke_manager_allocate_slots(first, last, max, m_heuristics_list[m_heuristics].choke_weight, target); } else { // m_heuristics_list[m_heuristics].slot_unchoke_weight(first, last); choke_manager_allocate_slots(first, last, max, m_heuristics_list[m_heuristics].unchoke_weight, target); } if (lt_log_is_valid(LOG_INSTRUMENTATION_CHOKE)) { for (uint32_t i = 0; i < choke_queue::order_max_size; i++) lt_log_print(LOG_INSTRUMENTATION_CHOKE, "%p %i %s %u %u %zd", this, 0, //lf->last_update(), (const char*)"unchoke" + 2*is_choke, i, target[i].first, std::distance(target[i].second, target[i + 1].second)); } // Now do the actual unchoking. uint32_t count = 0; uint32_t skipped = 0; for (target_type* itr = target + order_max_size; itr != target; itr--) { uint32_t order_size = std::distance((itr - 1)->second, itr->second); uint32_t order_remaining = order_size - (itr - 1)->first; if ((itr - 1)->first > order_size) throw internal_error("choke_queue::adjust_choke_range(...) itr->first > std::distance((itr - 1)->second, itr->second)."); (itr - 1)->first += std::min(skipped, order_remaining); skipped -= std::min(skipped, order_remaining); auto first_adjust = itr->second - (itr - 1)->first; auto last_adjust = itr->second; if (!range_is_contained(first_adjust, last_adjust, src_container->begin(), src_container->end())) throw internal_error("choke_queue::adjust_choke_range(...) bad iterator range."); // We start by unchoking the highest priority in this group, and // if we find any peers we can't choke/unchoke we'll move them to // the last spot in the container and decrement 'last_adjust'. auto itr_adjust = last_adjust; while (itr_adjust != first_adjust) { itr_adjust--; // if (!is_choke && !should_connection_unchoke(this, itr_adjust->first)) { // Swap with end and continue if not done with group. Count how many? // std::iter_swap(itr_adjust, --last_adjust); // if (first_adjust == (itr - 1)->second) // skipped++; // else // first_adjust--; // continue; // } m_slotConnection(itr_adjust->connection, is_choke); count++; lt_log_print(LOG_INSTRUMENTATION_CHOKE, "%p %i %s %p %X %llu %llu", this, 0, //lf->last_update(), (const char*)"unchoke" + 2*is_choke, itr_adjust->connection, itr_adjust->weight, (long long unsigned int)itr_adjust->connection->up_rate()->rate(), (long long unsigned int)itr_adjust->connection->down_rate()->rate()); } // The 'target' iterators remain valid after erase since we're // removing them in reverse order. dest_container->insert(dest_container->end(), first_adjust, last_adjust); src_container->erase(first_adjust, last_adjust); } if (count > max) throw internal_error("choke_queue::adjust_choke_range(...) count > max."); return count; } // Note that these algorithms fail if the rate >= 2^30. // Need to add the recently unchoked check here? static void calculate_upload_choke(choke_queue::iterator first, choke_queue::iterator last) { while (first != last) { // Very crude version for now. // // This needs to give more weight to peers that haven't had time to unchoke us. uint32_t downloadRate = first->connection->peer_chunks()->download_throttle()->rate()->rate() / 16; first->weight = choke_queue::order_base - 1 - downloadRate; first++; } } static void calculate_upload_unchoke(choke_queue::iterator first, choke_queue::iterator last) { while (first != last) { if (first->connection->is_down_local_unchoked()) { uint32_t downloadRate = first->connection->peer_chunks()->download_throttle()->rate()->rate() / 16; // If the peer transmits at less than 1KB, we should consider it // to be a rather stingy peer, and should look for new ones. if (downloadRate < 2048 / 16) first->weight = downloadRate; else first->weight = 3 * choke_queue::order_base + downloadRate; } else { // This will be our optimistic unchoke queue, should be // semi-random. Give lower weights to known stingy peers. int order = 1 + first->connection->peer_info()->is_preferred(); first->weight = order * choke_queue::order_base + ::random() % (1 << 10); } first++; } } // Improved heuristics intended for seeding. static void calculate_upload_choke_seed(choke_queue::iterator first, choke_queue::iterator last) { while (first != last) { int order = 1; // + first->connection->peer_info()->is_preferred(); uint32_t upload_rate = first->connection->peer_chunks()->upload_throttle()->rate()->rate() / 16; first->weight = order * choke_queue::order_base - 1 - upload_rate; first++; } } static void calculate_upload_unchoke_seed(choke_queue::iterator first, choke_queue::iterator last) { while (first != last) { int order = first->connection->peer_info()->is_preferred(); first->weight = order * choke_queue::order_base + ::random() % (1 << 10); first++; } } // // New leeching choke algorithm: // // Order 0: Normal // Order 1: No choking of newly unchoked peers. static void calculate_choke_upload_leech_experimental(choke_queue::iterator first, choke_queue::iterator last) { while (first != last) { // Don't choke a peer that hasn't had time to unchoke us. if (first->connection->up_choke()->time_last_choke() + 50s > this_thread::cached_time()) { first->weight = 1 * choke_queue::order_base; first++; continue; } // Preferred peers will get 4 times higher weight. int multiplier = 1 + 3 * first->connection->peer_info()->is_preferred(); uint32_t download_rate = first->connection->peer_chunks()->download_throttle()->rate()->rate() / 64; uint32_t upload_rate = first->connection->peer_chunks()->upload_throttle()->rate()->rate() / (64 * 4); first->weight = choke_queue::order_base - 1 - (download_rate + upload_rate) * multiplier; first++; } } // Order 0: Optimistic unchokes. // Order 1: Normal unchokes. static void calculate_unchoke_upload_leech_experimental(choke_queue::iterator first, choke_queue::iterator last) { while (first != last) { // Consider checking for is_down_remote_unchoked(). if (first->connection->is_down_local_unchoked()) { int multiplier = 1 + 3 * first->connection->peer_info()->is_preferred(); uint32_t download_rate = first->connection->peer_chunks()->download_throttle()->rate()->rate() / 64; first->weight = choke_queue::order_base + download_rate * multiplier; } else { // This will be our optimistic unchoke queue, should be // semi-random. Give lower weights to known stingy peers and // higher weight for preferred ones. int base = (1 << 10); if (first->connection->peer_info()->is_preferred()) base *= 4; // else if () // base /= 8; first->weight = ::random() % base; } first++; } } // Fix this, but for now just use something simple. static void calculate_download_choke(choke_queue::iterator first, choke_queue::iterator last) { while (first != last) { // Very crude version for now. uint32_t downloadRate = first->connection->peer_chunks()->download_throttle()->rate()->rate() / 16; first->weight = choke_queue::order_base - 1 - downloadRate; first++; } } static void calculate_download_unchoke(choke_queue::iterator first, choke_queue::iterator last) { while (first != last) { // Very crude version for now. uint32_t downloadRate = first->connection->peer_chunks()->download_throttle()->rate()->rate() / 16; first->weight = downloadRate; first++; } } choke_queue::heuristics_type choke_queue::m_heuristics_list[HEURISTICS_MAX_SIZE] = { { &calculate_upload_choke, &calculate_upload_unchoke, { 1, 1, 1, 1 }, { 1, 3, 6, 9 } }, { &calculate_upload_choke_seed, &calculate_upload_unchoke_seed, { 1, 1, 1, 1 }, { 1, 3, 6, 9 } }, { &calculate_choke_upload_leech_experimental, &calculate_unchoke_upload_leech_experimental, { 32, 1, 1, 1 }, { 1, 6, 8, 16 } }, { &calculate_download_choke, &calculate_download_unchoke, { 1, 1, 1, 1 }, { 1, 1, 1, 1 } }, }; } // namespace torrent libtorrent-0.16.17/src/torrent/download/choke_queue.h000066400000000000000000000125021522271512000226100ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_CHOKE_QUEUE_H #define LIBTORRENT_DOWNLOAD_CHOKE_QUEUE_H #include #include #include #include #include #include #include #include namespace torrent { class choke_status; class group_entry; class ConnectionList; class PeerConnectionBase; class DownloadMain; struct group_stats { unsigned int sum_min_needed; unsigned int sum_max_needed; unsigned int sum_max_leftovers; unsigned int changed_choked; unsigned int changed_unchoked; unsigned int now_choked; unsigned int now_unchoked; }; class LIBTORRENT_EXPORT choke_queue { public: using slot_unchoke = std::function; using slot_can_unchoke = std::function; using slot_connection = std::function; using container_type = std::vector; using value_type = container_type::value_type; using iterator = container_type::iterator; using target_type = std::pair; using group_container_type = std::vector; using slot_weight = void (*)(iterator first, iterator last); static constexpr int flag_unchoke_all_new = 0x1; static constexpr uint32_t order_base = (1 << 30); static constexpr uint32_t order_max_size = 4; static constexpr uint32_t weight_size_bytes = order_max_size * sizeof(uint32_t); static constexpr uint32_t unlimited = ~uint32_t(); struct heuristics_type { slot_weight slot_choke_weight; slot_weight slot_unchoke_weight; uint32_t choke_weight[order_max_size]; uint32_t unchoke_weight[order_max_size]; }; using heuristics_enum = torrent::heuristics_enum; choke_queue(int flags = 0) : m_flags(flags) {} ~choke_queue(); bool is_full() const { return !is_unlimited() && size_unchoked() >= m_maxUnchoked; } bool is_unlimited() const { return m_maxUnchoked == unlimited; } uint32_t size_unchoked() const { return m_currently_unchoked; } uint32_t size_queued() const { return m_currently_queued; } uint32_t size_total() const { return size_unchoked() + size_queued(); } // This must be unsigned. uint32_t max_unchoked() const { return m_maxUnchoked; } int32_t max_unchoked_signed() const { return m_maxUnchoked; } void set_max_unchoked(uint32_t v) { m_maxUnchoked = v; } void balance(); void balance_entry(group_entry* entry); int cycle(uint32_t quota); // Assume interested state is already updated for the PCB and that // this gets called once every time the status changes. void set_queued(PeerConnectionBase* pc, choke_status* base); void set_not_queued(PeerConnectionBase* pc, choke_status* base); void set_snubbed(PeerConnectionBase* pc, choke_status* base); void set_not_snubbed(PeerConnectionBase* pc, choke_status* base); void disconnected(PeerConnectionBase* pc, choke_status* base); static void move_connections(choke_queue* src, choke_queue* dest, DownloadMain* download, group_entry* base); heuristics_enum heuristics() const { return m_heuristics; } void set_heuristics(heuristics_enum hs) { m_heuristics = hs; } void set_slot_unchoke(slot_unchoke s) { m_slotUnchoke = std::move(s); } void set_slot_can_unchoke(slot_can_unchoke s) { m_slotCanUnchoke = std::move(s); } void set_slot_connection(slot_connection s) { m_slotConnection = std::move(s); } // TODO: Consider putting this in queue_group. group_container_type& group_container() { return m_group_container; } void modify_currently_queued(int value) { m_currently_queued += value; } void modify_currently_unchoked(int value) { m_currently_unchoked += value; } private: choke_queue(const choke_queue&) = delete; choke_queue& operator=(const choke_queue&) = delete; group_stats prepare_weights(group_stats gs); group_stats retrieve_connections(group_stats gs, container_type* queued, container_type* unchoked); void rebuild_containers(container_type* queued, container_type* unchoked); inline uint32_t max_alternate() const; uint32_t adjust_choke_range(iterator first, iterator last, container_type* src_container, container_type* dest_container, uint32_t max, bool is_choke); static heuristics_type m_heuristics_list[HEURISTICS_MAX_SIZE]; int m_flags; heuristics_enum m_heuristics{HEURISTICS_MAX_SIZE}; uint32_t m_maxUnchoked{unlimited}; uint32_t m_currently_queued{0}; uint32_t m_currently_unchoked{0}; slot_unchoke m_slotUnchoke; slot_can_unchoke m_slotCanUnchoke; slot_connection m_slotConnection; group_container_type m_group_container; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/download/download_manager.cc000066400000000000000000000072731522271512000237630ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include "torrent/download_info.h" #include "torrent/exceptions.h" #include "download/download_wrapper.h" #include "download_manager.h" namespace torrent { DownloadManager::iterator DownloadManager::insert(DownloadWrapper* d) { if (find(d->info()->hash()) != end()) throw internal_error("Could not add torrent as it already exists."); return base_type::insert(end(), d); } DownloadManager::iterator DownloadManager::erase(DownloadWrapper* d) { auto itr = std::find(begin(), end(), d); if (itr == end()) throw internal_error("Tried to remove a torrent that doesn't exist"); delete *itr; return base_type::erase(itr); } void DownloadManager::clear() { while (!empty()) { delete base_type::back(); base_type::pop_back(); } } DownloadManager::iterator DownloadManager::find(const std::string& hash) { return find(*HashString::cast_from(hash)); } DownloadManager::iterator DownloadManager::find(const HashString& hash) { return std::find_if(begin(), end(), [hash](const auto& wrapper){ return hash == wrapper->info()->hash(); }); } DownloadManager::iterator DownloadManager::find(DownloadInfo* info) { return std::find_if(begin(), end(), [info](const auto& wrapper){ return info == wrapper->info(); }); } DownloadManager::iterator DownloadManager::find_chunk_list(ChunkList* cl) { return std::find_if(begin(), end(), [cl](const auto& wrapper){ return cl == wrapper->chunk_list(); }); } DownloadMain* DownloadManager::find_main(const char* hash) { auto hash_str = *HashString::cast_from(hash); auto itr = std::find_if(begin(), end(), [hash_str](const auto& wrapper){ return hash_str == wrapper->info()->hash(); }); if (itr == end()) return NULL; else return (*itr)->main(); } DownloadMain* DownloadManager::find_main_obfuscated(const char* hash) { auto hash_str = *HashString::cast_from(hash); auto itr = std::find_if(begin(), end(), [hash_str](const auto& wrapper){ return hash_str == wrapper->info()->hash_obfuscated(); }); if (itr == end()) return NULL; else return (*itr)->main(); } } // namespace torrent libtorrent-0.16.17/src/torrent/download/download_manager.h000066400000000000000000000066571522271512000236320ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_DOWNLOAD_MANAGER_H #define LIBTORRENT_DOWNLOAD_MANAGER_H #include #include #include namespace torrent { class ChunkList; class DownloadWrapper; class DownloadInfo; class DownloadMain; class LIBTORRENT_EXPORT DownloadManager : private std::vector { public: using base_type = std::vector; using value_type = base_type::value_type; using pointer = base_type::pointer; using const_pointer = base_type::const_pointer; using reference = base_type::reference; using const_reference = base_type::const_reference; using size_type = base_type::size_type; using iterator = base_type::iterator; using reverse_iterator = base_type::reverse_iterator; using const_iterator = base_type::const_iterator; using const_reverse_iterator = base_type::const_reverse_iterator; using base_type::empty; using base_type::size; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; DownloadManager() = default; ~DownloadManager() { clear(); } DownloadManager(const DownloadManager&) = default; DownloadManager& operator=(const DownloadManager&) = default; iterator find(const std::string& hash); iterator find(const HashString& hash); iterator find(DownloadInfo* info); iterator find_chunk_list(ChunkList* cl); DownloadMain* find_main(const char* hash); DownloadMain* find_main_obfuscated(const char* hash); // // Don't export: // iterator insert(DownloadWrapper* d) LIBTORRENT_NO_EXPORT; iterator erase(DownloadWrapper* d) LIBTORRENT_NO_EXPORT; void clear() LIBTORRENT_NO_EXPORT; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/download/group_entry.h000066400000000000000000000116571522271512000227020ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_DOWNLOAD_GROUP_ENTRY_H #define LIBTORRENT_DOWNLOAD_GROUP_ENTRY_H #include #include #include #include #include namespace torrent { class choke_queue; class PeerConnectionBase; struct weighted_connection { weighted_connection(PeerConnectionBase* pcb, uint32_t w) : connection(pcb), weight(w) {} bool operator == (const PeerConnectionBase* pcb) const { return pcb == connection; } bool operator != (const PeerConnectionBase* pcb) const { return pcb != connection; } PeerConnectionBase* connection; uint32_t weight; }; // TODO: Rename to choke_entry, create an new class called group entry? class group_entry { public: using container_type = std::vector; static constexpr uint32_t unlimited = ~uint32_t(); uint32_t size_connections() const { return m_queued.size() + m_unchoked.size(); } uint32_t max_slots() const { return m_max_slots; } uint32_t min_slots() const { return m_min_slots; } void set_max_slots(uint32_t s) { m_max_slots = s; } void set_min_slots(uint32_t s) { m_min_slots = s; } const container_type* queued() { return &m_queued; } const container_type* unchoked() { return &m_unchoked; } protected: friend class choke_queue; friend class PeerConnectionBase; container_type* mutable_queued() { return &m_queued; } container_type* mutable_unchoked() { return &m_unchoked; } void connection_unchoked(PeerConnectionBase* pcb); void connection_choked(PeerConnectionBase* pcb); void connection_queued(PeerConnectionBase* pcb); void connection_unqueued(PeerConnectionBase* pcb); private: uint32_t m_max_slots{unlimited}; uint32_t m_min_slots{0}; // After a cycle the end of the vector should have the // highest-priority connections, and any new connections get put at // the back so they should always good candidates for unchoking. container_type m_queued; container_type m_unchoked; }; inline void group_entry::connection_unchoked(PeerConnectionBase* pcb) { auto itr = std::find(m_unchoked.begin(), m_unchoked.end(), pcb); if (itr != m_unchoked.end()) throw internal_error("group_entry::connection_unchoked(pcb) failed."); m_unchoked.emplace_back(pcb, uint32_t()); } inline void group_entry::connection_queued(PeerConnectionBase* pcb) { auto itr = std::find(m_queued.begin(), m_queued.end(), pcb); if (itr != m_queued.end()) throw internal_error("group_entry::connection_queued(pcb) failed."); m_queued.emplace_back(pcb, uint32_t()); } inline void group_entry::connection_choked(PeerConnectionBase* pcb) { auto itr = std::find(m_unchoked.begin(), m_unchoked.end(), pcb); if (itr == m_unchoked.end()) throw internal_error("group_entry::connection_choked(pcb) failed."); std::swap(*itr, m_unchoked.back()); m_unchoked.pop_back(); } inline void group_entry::connection_unqueued(PeerConnectionBase* pcb) { auto itr = std::find(m_queued.begin(), m_queued.end(), pcb); if (itr == m_queued.end()) throw internal_error("group_entry::connection_unqueued(pcb) failed."); std::swap(*itr, m_queued.back()); m_queued.pop_back(); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/download/resource_manager.cc000066400000000000000000000323051522271512000237750ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include "download/download_main.h" #include "protocol/peer_connection_base.h" #include "torrent/download/choke_group.h" #include "torrent/download_info.h" #include "torrent/exceptions.h" #include "torrent/utils/log.h" #include "choke_queue.h" #include "resource_manager.h" #define LT_LOG_THIS(log_fmt, ...) \ lt_log_print_subsystem(LOG_TORRENT_INFO, "resource_manager", log_fmt, __VA_ARGS__); #define LT_LOG_ITR(log_fmt, ...) \ lt_log_print_info(LOG_TORRENT_INFO, itr->download()->info(), "resource_manager", log_fmt, __VA_ARGS__); namespace torrent { const Rate* resource_manager_entry::up_rate() const { return m_download->info()->up_rate(); } const Rate* resource_manager_entry::down_rate() const { return m_download->info()->down_rate(); } ResourceManager::~ResourceManager() { assert(m_currentlyUploadUnchoked == 0 && "ResourceManager::~ResourceManager() called but m_currentlyUploadUnchoked != 0."); assert(m_currentlyDownloadUnchoked == 0 && "ResourceManager::~ResourceManager() called but m_currentlyDownloadUnchoked != 0."); } // If called directly ensure a valid group has been selected. ResourceManager::iterator ResourceManager::insert(const resource_manager_entry& entry) { bool will_realloc = true; //size() == capacity(); auto itr = base_type::insert(find_group_end(entry.group()), entry); DownloadMain* download = itr->download(); download->set_choke_group(m_choke_groups.at(entry.group()).get()); if (will_realloc) { update_group_iterators(); } else { auto group_itr = m_choke_groups.begin() + itr->group(); (*group_itr)->set_last((*group_itr)->last() + 1); std::for_each(++group_itr, m_choke_groups.end(), std::mem_fn(&choke_group::inc_iterators)); } choke_queue::move_connections(NULL, group_at(entry.group())->up_queue(), download, download->up_group_entry()); choke_queue::move_connections(NULL, group_at(entry.group())->down_queue(), download, download->down_group_entry()); return itr; } void ResourceManager::update_group_iterators() { auto entry_itr = base_type::begin(); auto group_itr = m_choke_groups.begin(); while (group_itr != m_choke_groups.end()) { (*group_itr)->set_first(&*entry_itr); entry_itr = std::find_if(entry_itr, end(), [group_itr, this](value_type v) { return (std::distance(m_choke_groups.begin(), group_itr)) < v.group(); }); (*group_itr)->set_last(&*entry_itr); group_itr++; } } void ResourceManager::validate_group_iterators() { auto entry_itr = base_type::begin(); auto group_itr = m_choke_groups.begin(); while (group_itr != m_choke_groups.end()) { if ((*group_itr)->first() != &*entry_itr) throw internal_error("ResourceManager::receive_tick() invalid first iterator."); entry_itr = std::find_if(entry_itr, end(), [group_itr, this](value_type v) { return (std::distance(m_choke_groups.begin(), group_itr)) < v.group(); }); if ((*group_itr)->last() != &*entry_itr) throw internal_error("ResourceManager::receive_tick() invalid last iterator."); group_itr++; } } void ResourceManager::erase(DownloadMain* d) { auto itr = std::find_if(begin(), end(), [d](value_type e) { return d == e.download(); }); if (itr == end()) throw internal_error("ResourceManager::erase() itr == end()."); choke_queue::move_connections(group_at(itr->group())->up_queue(), NULL, d, d->up_group_entry()); choke_queue::move_connections(group_at(itr->group())->down_queue(), NULL, d, d->down_group_entry()); auto group_itr = m_choke_groups.begin() + itr->group(); (*group_itr)->set_last((*group_itr)->last() - 1); std::for_each(++group_itr, m_choke_groups.end(), std::mem_fn(&choke_group::dec_iterators)); base_type::erase(itr); } void ResourceManager::push_group(const std::string& name) { if (name.empty() || std::any_of(m_choke_groups.begin(), m_choke_groups.end(), [name](auto& g) { return name == g->name(); })) throw input_error("Duplicate name for choke group."); m_choke_groups.push_back(std::make_unique()); m_choke_groups.back()->set_name(name); if (base_type::empty()) { m_choke_groups.back()->set_first(nullptr); m_choke_groups.back()->set_last(nullptr); } else { m_choke_groups.back()->set_first(&*base_type::end()); m_choke_groups.back()->set_last(&*base_type::end()); } m_choke_groups.back()->up_queue()->set_heuristics(HEURISTICS_UPLOAD_LEECH); m_choke_groups.back()->down_queue()->set_heuristics(HEURISTICS_DOWNLOAD_LEECH); m_choke_groups.back()->up_queue()->set_slot_unchoke([this](int n) { receive_upload_unchoke(n); }); m_choke_groups.back()->down_queue()->set_slot_unchoke([this](int n) { receive_download_unchoke(n); }); m_choke_groups.back()->up_queue()->set_slot_can_unchoke([this] { return retrieve_upload_can_unchoke(); }); m_choke_groups.back()->down_queue()->set_slot_can_unchoke([this] { return retrieve_download_can_unchoke(); }); m_choke_groups.back()->up_queue()->set_slot_connection(&PeerConnectionBase::receive_upload_choke); m_choke_groups.back()->down_queue()->set_slot_connection(&PeerConnectionBase::receive_download_choke); } ResourceManager::iterator ResourceManager::find(const DownloadMain* d) { return std::find_if(begin(), end(), [d](value_type e) { return d == e.download(); }); } ResourceManager::iterator ResourceManager::find_throw(const DownloadMain* d) { auto itr = find(d); if (itr == end()) throw input_error("Could not find download in resource manager."); return itr; } ResourceManager::iterator ResourceManager::find_group_end(uint16_t group) { return std::find_if(begin(), end(), [group](value_type v) { return group < v.group(); }); } choke_group* ResourceManager::group_at(uint16_t grp) { if (grp >= m_choke_groups.size()) throw input_error("Choke group not found."); return m_choke_groups.at(grp).get(); } choke_group* ResourceManager::group_at_name(const std::string& name) { auto itr = std::find_if(m_choke_groups.begin(), m_choke_groups.end(), [name](auto& g) { return name == g->name(); }); if (itr == m_choke_groups.end()) throw input_error("Choke group not found."); return itr->get(); } int ResourceManager::group_index_of(const std::string& name) { auto itr = std::find_if(m_choke_groups.begin(), m_choke_groups.end(), [name](auto& g) { return name == g->name(); }); if (itr == m_choke_groups.end()) throw input_error("Choke group not found."); return std::distance(m_choke_groups.begin(), itr); } void ResourceManager::set_priority(iterator itr, uint16_t pri) { LT_LOG_ITR("set priority: %" PRIu16, 0); itr->set_priority(pri); } void ResourceManager::set_group(iterator itr, uint16_t grp) { if (itr->group() == grp) return; if (grp >= m_choke_groups.size()) throw input_error("Choke group not found."); choke_queue::move_connections(itr->download()->choke_group()->up_queue(), m_choke_groups.at(grp)->up_queue(), itr->download(), itr->download()->up_group_entry()); choke_queue::move_connections(itr->download()->choke_group()->down_queue(), m_choke_groups.at(grp)->down_queue(), itr->download(), itr->download()->down_group_entry()); auto group_src = m_choke_groups.begin() + itr->group(); auto group_dest = m_choke_groups.begin() + grp; resource_manager_entry entry = *itr; entry.set_group(grp); entry.download()->set_choke_group(m_choke_groups.at(entry.group()).get()); base_type::erase(itr); base_type::insert(find_group_end(entry.group()), entry); // Update the group iterators after the move. We know the groups are // not the same, so no need to check for that. if (group_dest < group_src) { (*group_dest)->set_last((*group_dest)->last() + 1); std::for_each(++group_dest, group_src, std::mem_fn(&choke_group::inc_iterators)); (*group_src)->set_first((*group_src)->first() + 1); } else { (*group_src)->set_last((*group_src)->last() - 1); std::for_each(++group_src, group_dest, std::mem_fn(&choke_group::dec_iterators)); (*group_dest)->set_first((*group_dest)->first() - 1); } } void ResourceManager::set_max_upload_unchoked(unsigned int m) { if (m > (1 << 20)) throw input_error("Max unchoked must be between 0 and 2^20."); m_maxUploadUnchoked = m; } void ResourceManager::set_max_download_unchoked(unsigned int m) { if (m > (1 << 20)) throw input_error("Max unchoked must be between 0 and 2^20."); m_maxDownloadUnchoked = m; } // The choking choke manager won't updated it's count until after // possibly multiple calls of this function. void ResourceManager::receive_upload_unchoke(int num) { LT_LOG_THIS("adjusting upload unchoked slots; current:%u adjusted:%i", m_currentlyUploadUnchoked, num); if (static_cast(m_currentlyUploadUnchoked) + num < 0) throw internal_error("ResourceManager::receive_upload_unchoke(...) received an invalid value."); m_currentlyUploadUnchoked += num; } void ResourceManager::receive_download_unchoke(int num) { LT_LOG_THIS("adjusting download unchoked slots; current:%u adjusted:%i", m_currentlyDownloadUnchoked, num); if (static_cast(m_currentlyDownloadUnchoked) + num < 0) throw internal_error("ResourceManager::receive_download_unchoke(...) received an invalid value."); m_currentlyDownloadUnchoked += num; } int ResourceManager::retrieve_upload_can_unchoke() const { if (m_maxUploadUnchoked == 0) return std::numeric_limits::max(); return static_cast(m_maxUploadUnchoked) - static_cast(m_currentlyUploadUnchoked); } int ResourceManager::retrieve_download_can_unchoke() const { if (m_maxDownloadUnchoked == 0) return std::numeric_limits::max(); return static_cast(m_maxDownloadUnchoked) - static_cast(m_currentlyDownloadUnchoked); } void ResourceManager::receive_tick() { validate_group_iterators(); m_currentlyUploadUnchoked += balance_unchoked(m_choke_groups.size(), m_maxUploadUnchoked, true); m_currentlyDownloadUnchoked += balance_unchoked(m_choke_groups.size(), m_maxDownloadUnchoked, false); auto up_unchoked = std::accumulate(m_choke_groups.begin(), m_choke_groups.end(), 0U, [](auto sum, auto& group) { return sum + group->up_unchoked(); }); auto down_unchoked = std::accumulate(m_choke_groups.begin(), m_choke_groups.end(), 0U, [](auto sum, auto& group) { return sum + group->down_unchoked(); }); if (m_currentlyUploadUnchoked != up_unchoked) throw torrent::internal_error("m_currentlyUploadUnchoked != m_choke_groups.back()->up_queue()->size_unchoked()"); if (m_currentlyDownloadUnchoked != down_unchoked) throw torrent::internal_error("m_currentlyDownloadUnchoked != m_choke_groups.back()->down_queue()->size_unchoked()"); } unsigned int ResourceManager::total_weight() const { // TODO: This doesn't take into account inactive downloads. unsigned int total = 0; for (const auto& resource : *this) total += resource.priority(); return total; } int ResourceManager::balance_unchoked(unsigned int weight, unsigned int max_unchoked, bool is_up) { int change = 0; if (max_unchoked == 0) { auto group_itr = m_choke_groups.begin(); while (group_itr != m_choke_groups.end()) { choke_queue* cm = is_up ? (*group_itr)->up_queue() : (*group_itr)->down_queue(); change += cm->cycle(std::numeric_limits::max()); group_itr++; } return change; } unsigned int quota = max_unchoked; // We put the downloads with fewest interested first so that those // with more interested will gain any unused slots from the // preceding downloads. Consider multiplying with priority. // // Consider skipping the leading zero interested downloads. Though // that won't work as they need to choke peers once their priority // is turned off. auto choke_groups = std::vector(m_choke_groups.size()); std::transform(m_choke_groups.begin(), m_choke_groups.end(), choke_groups.begin(), [](auto& g) { return g.get(); }); // Start with the group requesting fewest slots (relative to weight) // so that we only need to iterate through the list once allocating // slots. There will be no slots left unallocated unless all groups // have reached max slots allowed. if (is_up) { std::sort(choke_groups.begin(), choke_groups.end(), [](auto lhs, auto rhs) { return lhs->up_requested() < rhs->up_requested(); }); LT_LOG_THIS("balancing upload unchoked slots; current_unchoked:%u change:%i max_unchoked:%u", m_currentlyUploadUnchoked, change, max_unchoked); } else { std::sort(choke_groups.begin(), choke_groups.end(), [](auto lhs, auto rhs) { return lhs->down_requested() < rhs->down_requested(); }); LT_LOG_THIS("balancing download unchoked slots; current_unchoked:%u change:%i max_unchoked:%u", m_currentlyDownloadUnchoked, change, max_unchoked); } for (const auto& group : choke_groups) { choke_queue* cm = is_up ? group->up_queue() : group->down_queue(); // change += cm->cycle(weight != 0 ? (quota * itr->priority()) / weight : 0); change += cm->cycle(weight != 0 ? quota / weight : 0); quota -= cm->size_unchoked(); // weight -= itr->priority(); weight--; } if (weight != 0) throw internal_error("ResourceManager::balance_unchoked(...) weight did not reach zero."); return change; } } // namespace torrent libtorrent-0.16.17/src/torrent/download/resource_manager.h000066400000000000000000000111611522271512000236340ustar00rootroot00000000000000#ifndef LIBTORRENT_PEER_RESOURCE_MANAGER_H #define LIBTORRENT_PEER_RESOURCE_MANAGER_H #include #include #include #include namespace torrent { // This class will handle the division of various resources like // uploads. For now the weight is equal to the value of the priority. // // Although the ConnectionManager class keeps a tally of open sockets, // we still need to balance them across the different downloads so // ResourceManager will also keep track of those. // // Add unlimited handling later. class choke_group; class DownloadMain; class Rate; class ResourceManager; class LIBTORRENT_EXPORT resource_manager_entry { public: friend class ResourceManager; resource_manager_entry(DownloadMain* d = NULL, uint16_t pri = 0, uint16_t grp = 0) : m_download(d), m_priority(pri), m_group(grp) {} DownloadMain* download() { return m_download; } const DownloadMain* c_download() const { return m_download; } uint16_t priority() const { return m_priority; } uint16_t group() const { return m_group; } const Rate* up_rate() const; const Rate* down_rate() const; protected: void set_priority(uint16_t pri) { m_priority = pri; } void set_group(uint16_t grp) { m_group = grp; } private: DownloadMain* m_download; uint16_t m_priority; uint16_t m_group; }; class LIBTORRENT_EXPORT ResourceManager : private std::vector { public: using base_type = std::vector; using value_type = base_type::value_type; using iterator = base_type::iterator; using choke_group_list = std::vector>; using base_type::begin; using base_type::end; using base_type::size; using base_type::capacity; ResourceManager() = default; ~ResourceManager(); void insert(DownloadMain* d, uint16_t priority) { insert(value_type(d, priority)); } void erase(DownloadMain* d); void push_group(const std::string& name); iterator find(const DownloadMain* d); iterator find_throw(const DownloadMain* d); iterator find_group_end(uint16_t group); unsigned int group_size() const { return m_choke_groups.size(); } choke_group* group_back() { return m_choke_groups.back().get(); } choke_group* group_at(uint16_t grp); choke_group* group_at_name(const std::string& name); int group_index_of(const std::string& name); auto group_begin() { return m_choke_groups.begin(); } auto group_end() { return m_choke_groups.end(); } auto& entry_at(const DownloadMain* d) { return *find_throw(d); } static void set_priority(iterator itr, uint16_t pri); void set_group(iterator itr, uint16_t grp); // When setting this, make sure you choke peers, else change receive_can_unchoke. unsigned int currently_upload_unchoked() const { return m_currentlyUploadUnchoked; } unsigned int currently_download_unchoked() const { return m_currentlyDownloadUnchoked; } unsigned int max_upload_unchoked() const { return m_maxUploadUnchoked; } unsigned int max_download_unchoked() const { return m_maxDownloadUnchoked; } void set_max_upload_unchoked(unsigned int m); void set_max_download_unchoked(unsigned int m); void receive_upload_unchoke(int num); void receive_download_unchoke(int num); int retrieve_upload_can_unchoke() const; int retrieve_download_can_unchoke() const; void receive_tick(); private: ResourceManager(const ResourceManager&) = delete; ResourceManager& operator=(const ResourceManager&) = delete; iterator insert(const resource_manager_entry& entry); void update_group_iterators(); void validate_group_iterators(); unsigned int total_weight() const; int balance_unchoked(unsigned int weight, unsigned int max_unchoked, bool is_up); unsigned int m_currentlyUploadUnchoked{}; unsigned int m_currentlyDownloadUnchoked{}; unsigned int m_maxUploadUnchoked{}; unsigned int m_maxDownloadUnchoked{}; choke_group_list m_choke_groups; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/download/types.h000066400000000000000000000004231522271512000214560ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_TYPES_H #define LIBTORRENT_DOWNLOAD_TYPES_H namespace torrent { enum heuristics_enum { HEURISTICS_UPLOAD_LEECH, HEURISTICS_UPLOAD_SEED, HEURISTICS_UPLOAD_LEECH_EXPERIMENTAL, HEURISTICS_DOWNLOAD_LEECH, HEURISTICS_MAX_SIZE }; } #endif libtorrent-0.16.17/src/torrent/download_info.h000066400000000000000000000203561522271512000213340ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_INFO_H #define LIBTORRENT_DOWNLOAD_INFO_H #include #include #include #include #include #include #include namespace torrent { class FileList; class DownloadMain; // This will become a Download 'handle' of kinds. // TODO: Split into DownloadInfo and DownloadState. // TODO: Rename 'hash' to 'info_hash'. class LIBTORRENT_EXPORT DownloadInfo { public: using slot_stat_type = std::function; using signal_void_type = std::list>; using signal_string_type = std::list>; enum State { NONE, COMPLETED, STARTED, STOPPED }; static constexpr int flag_open = (1 << 0); static constexpr int flag_active = (1 << 1); static constexpr int flag_accepting_new_peers = (1 << 3); static constexpr int flag_accepting_seeders = (1 << 4); // Only used during leeching. static constexpr int flag_private = (1 << 5); static constexpr int flag_meta_download = (1 << 6); static constexpr int flag_pex_enabled = (1 << 7); static constexpr int flag_pex_active = (1 << 8); static constexpr int public_flags = flag_accepting_seeders; static constexpr uint32_t unlimited = ~uint32_t(); const string_utf8& name() const { return m_name; } void set_name(const std::string& s) { m_name.reset(s); } const HashString& hash() const { return m_hash; } const HashString& info_hash() const { return m_hash; } HashString& mutable_hash() { return m_hash; } const HashString& hash_obfuscated() const { return m_hash_obfuscated; } const HashString& info_hash_obfuscated() const { return m_hash_obfuscated; } HashString& mutable_hash_obfuscated() { return m_hash_obfuscated; } const HashString& local_id() const { return m_local_id; } HashString& mutable_local_id() { return m_local_id; } bool is_open() const { return m_flags & flag_open; } bool is_active() const { return m_flags & flag_active; } bool is_accepting_new_peers() const { return m_flags & flag_accepting_new_peers; } bool is_accepting_seeders() const { return m_flags & flag_accepting_seeders; } bool is_private() const { return m_flags & flag_private; } bool is_meta_download() const { return m_flags & flag_meta_download; } bool is_pex_enabled() const { return m_flags & flag_pex_enabled; } bool is_pex_active() const { return m_flags & flag_pex_active; } int flags() const { return m_flags; } void set_flags(int flags) { m_flags |= flags; } void unset_flags(int flags) { m_flags &= ~flags; } void change_flags(int flags, bool state) { if (state) set_flags(flags); else unset_flags(flags); } void public_set_flags(int flags) const { m_flags |= (flags & public_flags); } void public_unset_flags(int flags) const { m_flags &= ~(flags & public_flags); } void public_change_flags(int flags, bool state) const { if (state) public_set_flags(flags); else public_unset_flags(flags); } void set_private() { set_flags(flag_private); unset_flags(flag_pex_enabled); } void set_pex_enabled() { if (!is_private()) set_flags(flag_pex_enabled); } const Rate* up_rate() const { return &m_up_rate; } const Rate* down_rate() const { return &m_down_rate; } const Rate* skip_rate() const { return &m_skip_rate; } Rate* mutable_up_rate() const { return &m_up_rate; } Rate* mutable_down_rate() const { return &m_down_rate; } Rate* mutable_skip_rate() const { return &m_skip_rate; } uint64_t uploaded_baseline() const { return m_uploaded_baseline; } uint64_t uploaded_adjusted() const { return std::max(m_up_rate.total() - uploaded_baseline(), 0); } void set_uploaded_baseline(uint64_t b) { m_uploaded_baseline = b; } uint64_t completed_baseline() const { return m_completed_baseline; } uint64_t completed_adjusted() const { return std::max(m_slot_stat_completed() - completed_baseline(), 0); } void set_completed_baseline(uint64_t b) { m_completed_baseline = b; } size_t metadata_size() const { return m_metadata_size; } void set_metadata_size(size_t size) { m_metadata_size = size; } uint32_t size_pex() const { return m_size_pex; } void set_size_pex(uint32_t b) { m_size_pex = b; } uint32_t max_size_pex() const { return m_max_size_pex; } void set_max_size_pex(uint32_t b) { m_max_size_pex = b; } static uint32_t max_size_pex_list() { return 200; } // Unix epoche, 0 == unknown. uint32_t creation_date() const { return m_creation_date; } uint32_t load_date() const { return m_load_date; } uint32_t upload_unchoked() const { return m_upload_unchoked; } uint32_t download_unchoked() const { return m_download_unchoked; } // The list of addresses is guaranteed to be sorted and unique. signal_void_type& signal_tracker_success() const { return m_signal_tracker_success; } signal_string_type& signal_tracker_failed() const { return m_signal_tracker_failed; } // // Libtorrent internal: // void set_creation_date(uint32_t d) { m_creation_date = d; } void set_upload_unchoked(uint32_t num) { m_upload_unchoked = num; } void set_download_unchoked(uint32_t num) { m_download_unchoked = num; } slot_stat_type& slot_left() { return m_slot_stat_left; } slot_stat_type& slot_completed() { return m_slot_stat_completed; } protected: friend class DownloadMain; void set_load_date(uint32_t d) { m_load_date = d; } private: string_utf8 m_name; HashString m_hash{HashString::new_zero()}; HashString m_hash_obfuscated{HashString::new_zero()}; HashString m_local_id{HashString::new_zero()}; mutable int m_flags{flag_accepting_new_peers | flag_accepting_seeders | flag_pex_enabled | flag_pex_active}; mutable Rate m_up_rate{60}; mutable Rate m_down_rate{60}; mutable Rate m_skip_rate{60}; uint64_t m_uploaded_baseline{}; uint64_t m_completed_baseline{}; uint32_t m_size_pex{}; uint32_t m_max_size_pex{8}; size_t m_metadata_size{}; uint32_t m_creation_date{}; uint32_t m_load_date{}; uint32_t m_upload_unchoked{}; uint32_t m_download_unchoked{}; slot_stat_type m_slot_stat_left; slot_stat_type m_slot_stat_completed; mutable signal_void_type m_signal_tracker_success; mutable signal_string_type m_signal_tracker_failed; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/event.cc000066400000000000000000000056371522271512000177760ustar00rootroot00000000000000#include "config.h" #include "event.h" #include #include "torrent/exceptions.h" #include "torrent/net/fd.h" #include "torrent/net/socket_address.h" namespace torrent { Event::~Event() { assert(m_poll_event == nullptr && "Event::~Event() called with m_poll_event != nullptr."); } int Event::socket_type_or_zero() const { if (!is_open()) throw internal_error("Event::socket_type_or_zero() called on closed event."); int socktype{}; if (!fd_get_type(m_fileDesc, &socktype)) { if (errno != EBADF) throw internal_error("Event::socket_type_or_zero() error getting socket type"); return 0; } return socktype; } const char* Event::type_name() const { throw internal_error("Event::type_name() must be overridden in derived class."); } std::string Event::print_name_fd_str() const { return "name:" + std::string(type_name()) + " fd:" + std::to_string(file_descriptor()); } // TODO: Add "check_socket_valid_and_same". bool Event::update_socket_address() { if (!is_open()) throw internal_error("Event::update_socket_address() called on closed event."); int socktype = socket_type_or_zero(); if (socktype == 0 || (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)) return false; auto address = fd_get_socket_name(m_fileDesc); if (address == nullptr) { if (errno != EBADF && errno != ENOTCONN && errno != EINVAL) throw internal_error("Event::update_socket_address() error getting socket address"); return false; } m_socket_address = std::move(address); return true; } bool Event::update_peer_address() { if (!is_open()) throw internal_error("Event::update_peer_address() called on closed event."); int socktype = socket_type_or_zero(); if (socktype == 0 || (socktype != SOCK_STREAM && socktype != SOCK_DGRAM)) return false; auto address = fd_get_peer_name(m_fileDesc); if (address == nullptr) { if (errno != EBADF && errno != ENOTCONN && errno != EINVAL) throw internal_error("Event::update_peer_address() error getting peer address"); return false; } m_peer_address = std::move(address); return true; } bool Event::update_and_verify_socket_address() { auto old_address = std::move(m_socket_address); if (!update_socket_address()) { m_socket_address = std::move(old_address); return m_socket_address == nullptr; } if (old_address == nullptr) return true; if (!sap_equal(old_address, m_socket_address)) { m_socket_address = std::move(old_address); return false; } return true; } bool Event::update_and_verify_peer_address() { auto old_address = std::move(m_peer_address); if (!update_peer_address()) { m_peer_address = std::move(old_address); return m_peer_address == nullptr; } if (old_address == nullptr) return true; if (!sap_equal(old_address, m_peer_address)) { m_peer_address = std::move(old_address); return false; } return true; } } // namespace torrent libtorrent-0.16.17/src/torrent/event.h000066400000000000000000000042521522271512000176300ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_EVENT_H #define LIBTORRENT_TORRENT_EVENT_H #include #include #include namespace torrent { namespace system { class PollEvent; class PollInternal; } class LIBTORRENT_EXPORT Event { public: virtual ~Event(); bool is_open() const; bool is_polling() const; int file_descriptor() const; int socket_type_or_zero() const; auto peer_address() const; auto socket_address() const; std::string print_name_fd_str() const; virtual const char* type_name() const; // TODO: Make these protected. virtual void event_read() = 0; virtual void event_write() = 0; virtual void event_error() = 0; // TODO: Add bool event_fd_reused(). protected: friend class system::Poll; friend class system::PollInternal; friend class runtime::SocketManager; void set_file_descriptor(int fd); void set_socket_address(c_sa_unique_ptr address); bool update_socket_address(); bool update_peer_address(); // Returns true if the update failed and the address was null. bool update_and_verify_socket_address(); bool update_and_verify_peer_address(); std::shared_ptr m_poll_event; int m_fileDesc{-1}; private: // TODO: Add socket type to validation. c_sa_unique_ptr m_peer_address; c_sa_unique_ptr m_socket_address; }; inline bool Event::is_open() const { return file_descriptor() != -1; } inline bool Event::is_polling() const { return m_poll_event != nullptr; } inline int Event::file_descriptor() const { return m_fileDesc; } inline void Event::set_file_descriptor(int fd) { m_fileDesc = fd; } inline auto Event::peer_address() const { return m_peer_address.get(); } inline auto Event::socket_address() const { return m_socket_address.get(); } inline void Event::set_socket_address(c_sa_unique_ptr address) { m_socket_address = std::move(address); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/exceptions.cc000066400000000000000000000044571522271512000210350ustar00rootroot00000000000000#include "config.h" #include "torrent/exceptions.h" #include #include #include #include "torrent/hash_string.h" #include "torrent/utils/string_manip.h" #ifdef HAVE_BACKTRACE #include #endif namespace torrent { // Use actual functions, instead of inlined, for the ctor of // exceptions. This allows us to create breakpoints at throws. This is // limited to rarely thrown exceptions. void communication_error::initialize(const std::string& msg) { m_msg = msg; } void storage_error::initialize(const std::string& msg) { m_msg = msg; } void resource_error::initialize(const std::string& msg) { m_msg = msg; } void input_error::initialize(const std::string& msg) { m_msg = msg; } internal_error::internal_error(const char* msg, const std::string& context) { initialize(std::string(msg) + " [" + context + "]"); } internal_error::internal_error(const char* msg, const HashString& hash) { initialize(std::string(msg) + " [#" + utils::transform_to_hex_str(hash) + "]"); } const char* internal_error::what() const noexcept { return m_msg.c_str(); } const char* input_error::what() const noexcept { return m_msg.c_str(); } const char* resource_error::what() const noexcept { return m_msg.c_str(); } const char* storage_error::what() const noexcept { return m_msg.c_str(); } const char* communication_error::what() const noexcept { return m_msg.c_str(); } const char* connection_error::what() const noexcept { return std::strerror(m_errno); } const char* address_info_error::what() const noexcept { return ::gai_strerror(m_errno); } void internal_error::initialize(const std::string& msg) { m_msg = msg; #ifdef HAVE_BACKTRACE void* stack_ptrs[20]; int stack_size = ::backtrace(stack_ptrs, 20); char** stack_symbol_names = ::backtrace_symbols(stack_ptrs, stack_size); if (stack_symbol_names == nullptr) { m_backtrace = "backtrace_symbols failed"; return; } std::stringstream output; for (int i = 0; i < stack_size; ++i) { if (stack_symbol_names[i] != nullptr && stack_symbol_names[i] > (void*)0x1000) output << stack_symbol_names[i] << '\n'; else output << "stack_symbol: nullptr" << '\n'; } m_backtrace = output.str(); ::free(stack_symbol_names); #else m_backtrace = "stack dump not enabled"; #endif } } // namespace torrent libtorrent-0.16.17/src/torrent/exceptions.h000066400000000000000000000075251522271512000206760ustar00rootroot00000000000000// Note that some of the exception classes below are strictly speaking // _NOT DOING THE RIGHT THING_. One is really not supposed to use any // calls to malloc/new in an exception's ctor. // // Will be fixed next API update. #ifndef LIBTORRENT_EXCEPTIONS_H #define LIBTORRENT_EXCEPTIONS_H #include #include #include namespace torrent { // All exceptions inherit from std::exception to make catching // everything libtorrent related at the root easier. class LIBTORRENT_EXPORT base_error : public std::exception { }; // The library or application did some borking it shouldn't have, bug // tracking time! class LIBTORRENT_EXPORT internal_error : public base_error { public: internal_error(const char* msg) { initialize(msg); } internal_error(const char* msg, const std::string& context); internal_error(const char* msg, const HashString& hash); internal_error(const std::string& msg) { initialize(msg); } const char* what() const noexcept override; const std::string& backtrace() const noexcept { return m_backtrace; } void push_front(const std::string& msg) { m_msg = msg + m_msg; } private: // Use this function for breaking on throws. void initialize(const std::string& msg); std::string m_msg; std::string m_backtrace; }; // For some reason we couldn't talk with a protocol/tracker, migth be a // library bug, connection problem or bad input. class LIBTORRENT_EXPORT network_error : public base_error { }; class LIBTORRENT_EXPORT communication_error : public network_error { public: communication_error(const char* msg) { initialize(msg); } communication_error(const std::string& msg) { initialize(msg); } const char* what() const noexcept override; private: // Use this function for breaking on throws. void initialize(const std::string& msg); std::string m_msg; }; class LIBTORRENT_EXPORT connection_error : public network_error { public: connection_error(int err) : m_errno(err) {} const char* what() const noexcept override; int get_errno() const { return m_errno; } private: int m_errno; }; class LIBTORRENT_EXPORT address_info_error : public network_error { public: address_info_error(int err) : m_errno(err) {} const char* what() const noexcept override; int get_errno() const { return m_errno; } private: int m_errno; }; class LIBTORRENT_EXPORT close_connection : public network_error { }; class LIBTORRENT_EXPORT blocked_connection : public network_error { }; // Stuff like bad torrent file, disk space and permissions. class LIBTORRENT_EXPORT local_error : public base_error { }; class LIBTORRENT_EXPORT storage_error : public local_error { public: storage_error(const char* msg) { initialize(msg); } storage_error(const std::string& msg) { initialize(msg); } const char* what() const noexcept override; private: // Use this function for breaking on throws. void initialize(const std::string& msg); std::string m_msg; }; class LIBTORRENT_EXPORT resource_error : public local_error { public: resource_error(const char* msg) { initialize(msg); } resource_error(const std::string& msg) { initialize(msg); } const char* what() const noexcept override; private: // Use this function for breaking on throws. void initialize(const std::string& msg); std::string m_msg; }; class LIBTORRENT_EXPORT input_error : public local_error { public: input_error(const char* msg) { initialize(msg); } input_error(const std::string& msg) { initialize(msg); } const char* what() const noexcept override; private: // Use this function for breaking on throws. void initialize(const std::string& msg); std::string m_msg; }; class LIBTORRENT_EXPORT bencode_error : public input_error { public: using input_error::input_error; }; class LIBTORRENT_EXPORT shutdown_exception : public base_error { }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/hash_string.h000066400000000000000000000073101522271512000210160ustar00rootroot00000000000000// A fixed with char array used to store 20 byte with hashes. This // should really be replaced with std::array<20>. #ifndef LIBTORRENT_HASH_STRING_H #define LIBTORRENT_HASH_STRING_H #include #include #include #include namespace torrent { class LIBTORRENT_EXPORT HashString { public: using value_type = char; using reference = value_type&; using const_reference = const value_type&; using iterator = value_type*; using const_iterator = const value_type*; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; static constexpr size_type size_data = 20; static size_type size() { return size_data; } iterator begin() { return m_data; } const_iterator begin() const { return m_data; } iterator end() { return m_data + size(); } const_iterator end() const { return m_data + size(); } reverse_iterator rbegin() { return reverse_iterator(end()); } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } reference operator [] (size_type n) { return *(m_data + n); } const_reference operator [] (size_type n) const { return *(m_data + n); } value_type* data() { return m_data; } const value_type* data() const { return m_data; } const value_type* c_str() const { return m_data; } std::string str() const { return std::string(m_data, size_data); } void clear(int v = 0) { std::memset(data(), v, size()); } void assign(const value_type* src) { std::memcpy(data(), src, size()); } bool equal_to(const char* hash) const { return std::memcmp(m_data, hash, size()) == 0; } bool not_equal_to(const char* hash) const { return std::memcmp(m_data, hash, size()) != 0; } static HashString new_zero(); // It is the users responsibility to ensure src.length() >= // size_data. static const HashString* cast_from(const char* src) { return reinterpret_cast(src); } static const HashString* cast_from(const std::string& src) { return reinterpret_cast(src.c_str()); } static HashString* cast_from(char* src) { return reinterpret_cast(src); } private: char m_data[size_data]; }; inline HashString HashString::new_zero() { HashString hash; hash.clear(); return hash; } inline bool operator == (const HashString& one, const HashString& two) { return std::memcmp(one.begin(), two.begin(), HashString::size_data) == 0; } inline bool operator != (const HashString& one, const HashString& two) { return std::memcmp(one.begin(), two.begin(), HashString::size_data) != 0; } inline bool operator < (const HashString& one, const HashString& two) { return std::memcmp(one.begin(), two.begin(), HashString::size_data) < 0; } inline bool operator <= (const HashString& one, const HashString& two) { return std::memcmp(one.begin(), two.begin(), HashString::size_data) <= 0; } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/net/000077500000000000000000000000001522271512000171215ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/net/address_info.cc000066400000000000000000000020001522271512000220600ustar00rootroot00000000000000#include "config.h" #include "address_info.h" namespace torrent { int ai_get_addrinfo(const char* nodename, const char* servname, const addrinfo* hints, ai_unique_ptr& res) { addrinfo* ai; int err = ::getaddrinfo(nodename, servname, hints, &ai); if (err != 0) return err; res.reset(ai); return 0; } sa_unique_ptr ai_get_first_sa(const char* nodename, const char* servname, const addrinfo* hints) { ai_unique_ptr aip; if (ai_get_addrinfo(nodename, servname, hints, aip) != 0) return nullptr; return sa_copy(aip->ai_addr); } int ai_each_inet_inet6_first(const char* nodename, const ai_sockaddr_func& lambda) { int err; ai_unique_ptr ai; // TODO: Change to a single call using hints with both inet/inet6. if ((err = ai_get_addrinfo(nodename, NULL, ai_make_hint(0, PF_INET, SOCK_STREAM).get(), ai)) != 0 && (err = ai_get_addrinfo(nodename, NULL, ai_make_hint(0, PF_INET6, SOCK_STREAM).get(), ai)) != 0) return err; lambda(ai->ai_addr); return 0; } } // namespace torrent libtorrent-0.16.17/src/torrent/net/address_info.h000066400000000000000000000036541522271512000217420ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_ADDRESS_INFO_H #define LIBTORRENT_NET_ADDRESS_INFO_H #include #include #include #include #include #include #include namespace torrent { struct ai_deleter { void operator()(addrinfo* ai) const { if (ai != nullptr) ::freeaddrinfo(ai); } }; using ai_unique_ptr = std::unique_ptr; using c_ai_unique_ptr = std::unique_ptr; using ai_sockaddr_func = std::function; inline std::unique_ptr ai_make_hint(int flags, int family, int socktype); int ai_get_addrinfo(const char* nodename, const char* servname, const addrinfo* hints, ai_unique_ptr& res) LIBTORRENT_EXPORT; // Helper functions: // TODO: Consider servname "0". // TODO: ai_get_first_sa_err that returns a tuple? sa_unique_ptr ai_get_first_sa(const char* nodename, const char* servname = nullptr, const addrinfo* hints = nullptr) LIBTORRENT_EXPORT; int ai_each_inet_inet6_first(const char* nodename, const ai_sockaddr_func& lambda) LIBTORRENT_EXPORT; // Get all addrinfo's, iterate, etc. // // Safe conversion from unique_ptr arguments: // inline void aip_clear(ai_unique_ptr& aip) { *aip = addrinfo{}; } inline int aip_get_addrinfo(const char* nodename, const char* servname, const ai_unique_ptr& hints, ai_unique_ptr& res) { return ai_get_addrinfo(nodename, servname, hints.get(), res); } inline int aip_get_addrinfo(const char* nodename, const char* servname, const c_ai_unique_ptr& hints, ai_unique_ptr& res) { return ai_get_addrinfo(nodename, servname, hints.get(), res); } // // Implementations: // inline std::unique_ptr ai_make_hint(int flags, int family, int socktype) { auto aip = std::make_unique(); *aip = addrinfo{}; aip->ai_flags = flags; aip->ai_family = family; aip->ai_socktype = socktype; return aip; } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/net/connection_state.cc000066400000000000000000000021641522271512000227720ustar00rootroot00000000000000#include "config.h" #include "torrent/net/connection_state.h" #include "torrent/exceptions.h" #include "torrent/runtime/network_config.h" #include "torrent/utils/log.h" // #define LT_LOG_NOTICE(log_fmt, ...) \ // lt_log_print_subsystem(LOG_NOTICE, "net::network_config", log_fmt, __VA_ARGS__); namespace torrent::net { // function to check if we should retry other protocol immediately int connection_state_select_next_family(ConnectionState& state) { auto nc = runtime::network_config(); // int current = state.current_family(); // If current and no failures, keep using it. // Include prefer_ipv6 // switch (state.current_family()) { // case AF_UNSPEC: // if (nc->is_prefer_ipv6()) // return AF_INET6; // else // return AF_INET; // int preferred = nc->is_prefer_ipv6() ? AF_INET6 : AF_INET; // Check if last connection was successful, keep using // however we should always try the preferred again // If last connection failed, try the other family // If both families have failed, wait before trying again. } } // namespace torrent::net libtorrent-0.16.17/src/torrent/net/connection_state.h000066400000000000000000000030201522271512000226240ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_NET_CONNECTION_STATE_H #define LIBTORRENT_TORRENT_NET_CONNECTION_STATE_H #include namespace torrent::net { class LIBTORRENT_EXPORT ConnectionState { public: int current_family() const { return m_current_family; } int last_failure_family() const { return m_last_failure_family; } void set_current_family(int family); void set_last_failure_family(int family); int success_inet() const { return m_success_inet; } int success_inet6() const { return m_success_inet6; } int failures_inet() const { return m_failures_inet; } int failures_inet6() const { return m_failures_inet6; } private: int m_current_family{AF_UNSPEC}; int m_last_failure_family{AF_UNSPEC}; int m_success_inet{0}; int m_success_inet6{0}; int m_failures_inet{0}; int m_failures_inet6{0}; // c_sa_shared_ptr m_bind_address; // has connected to inet/inet6 // int m_attempts{0}; // int m_successes{0}; // int m_failures{0}; // int m_last_family{AF_UNSPEC}; // bool m_failed{false}; }; int select_next_address_family(ConnectionState& state); } // namespace torrent::net #endif libtorrent-0.16.17/src/torrent/net/fd.cc000066400000000000000000000376651522271512000200420ustar00rootroot00000000000000#include "config.h" #include "fd.h" #include #include #include #include #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/system/system.h" #include "torrent/utils/log.h" #if defined(USE_EPOLL) #include #elif defined(USE_KQUEUE) #include #include #else #error "Must enable at least one of either kqueue or epoll" #endif #ifdef USE_INOTIFY #include #endif #define LT_LOG(log_fmt, ...) \ lt_log_print(LOG_CONNECTION_FD, "fd: " log_fmt, __VA_ARGS__); #define LT_LOG_FLAG(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd: " log_fmt " : flags:0x%x", flags); #define LT_LOG_FLAG_ERROR(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd: " log_fmt " : flags:0x%x errno:%i message:'%s'", \ flags, errno, std::strerror(errno)); #define LT_LOG_FD(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt, fd); #define LT_LOG_FD_ERROR(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt " : errno:%i message:'%s'", \ fd, errno, std::strerror(errno)); #define LT_LOG_FD_SOCKADDR(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt " : address:%s", \ fd, sa_pretty_str(sa).c_str()); #define LT_LOG_FD_SOCKADDR_ERROR(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt " : address:%s errno:%i message:'%s'", \ fd, sa_pretty_str(sa).c_str(), errno, std::strerror(errno)); #define LT_LOG_FD_SAP(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt " : address:%s", \ fd, sap_pretty_str(sap).c_str()); #define LT_LOG_FD_FLAG(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt " : flags:0x%x", fd, flags); #define LT_LOG_FD_FLAG_ERROR(log_fmt) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt " : flags:0x%x errno:%i message:'%s'", \ fd, flags, errno, std::strerror(errno)); #define LT_LOG_FD_VALUE(log_fmt, value) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt " : value:%i", fd, (int)value); #define LT_LOG_FD_VALUE_ERROR(log_fmt, value) \ lt_log_print(LOG_CONNECTION_FD, "fd->%i: " log_fmt " : value:%i errno:%i message:'%s'", \ fd, (int)value, errno, std::strerror(errno)); namespace torrent { int fd__accept(int socket, sockaddr *address, socklen_t *address_len) { #ifdef HAVE_ACCEPT4 return ::accept4(socket, address, address_len, SOCK_CLOEXEC); #else return ::accept(socket, address, address_len); #endif } int fd__bind(int socket, const sockaddr *address, socklen_t address_len) { return ::bind(socket, address, address_len); } int fd__close(int fildes) { return ::close(fildes); } int fd__connect(int socket, const sockaddr *address, socklen_t address_len) { return ::connect(socket, address, address_len); } int fd__fcntl_int(int fildes, int cmd, int arg) { return ::fcntl(fildes, cmd, arg); } int fd__listen(int socket, int backlog) { return ::listen(socket, backlog); } int fd__setsockopt_int(int socket, int level, int option_name, int option_value) { return ::setsockopt(socket, level, option_name, &option_value, sizeof(int)); } int fd__socket(int domain, int type, int protocol) { #ifdef SOCK_CLOEXEC type |= SOCK_CLOEXEC; #endif return ::socket(domain, type, protocol); } int fd_open(fd_flags flags) { int domain; int protocol; if (!fd_valid_flags(flags)) throw internal_error("torrent::fd_open failed: invalid fd_flags"); if ((flags & fd_flag_stream)) { domain = SOCK_STREAM; protocol = IPPROTO_TCP; } else if ((flags & fd_flag_datagram)) { domain = SOCK_DGRAM; protocol = IPPROTO_UDP; } else { LT_LOG_FLAG("fd_open missing socket type"); errno = EINVAL; return -1; } int fd = -1; if (fd == -1 && !(flags & fd_flag_v4only)) { LT_LOG_FLAG("fd_open opening ipv6 socket"); fd = fd__socket(PF_INET6, domain, protocol); } if (fd == -1 && !(flags & fd_flag_v6only)) { LT_LOG_FLAG("fd_open opening ipv4 socket"); fd = fd__socket(PF_INET, domain, protocol); } if (fd == -1) { LT_LOG_FLAG_ERROR("fd_open failed to open socket"); return -1; } if ((flags & fd_flag_v6only) && !fd_set_v6only(fd, true)) { LT_LOG_FD_FLAG_ERROR("fd_open failed to set v6only"); fd__close(fd); return -1; } if ((flags & fd_flag_nonblock) && !fd_set_nonblock(fd)) { LT_LOG_FD_FLAG_ERROR("fd_open failed to set nonblock"); fd__close(fd); return -1; } if ((flags & fd_flag_reuse_address) && !fd_set_reuse_address(fd, true)) { LT_LOG_FD_FLAG_ERROR("fd_open failed to set reuse_address"); fd__close(fd); return -1; } LT_LOG_FD_FLAG("fd_open succeeded"); return fd; } int fd_open_family(fd_flags flags, int family) { if (family != AF_INET && family != AF_INET6) { LT_LOG_FLAG("fd_open_family invalid family"); errno = EINVAL; return -1; } if (family == AF_INET) flags |= fd_flag_v4; return fd_open(flags); } int fd_open_local(fd_flags flags) { if ((flags & ~(fd_flag_stream | fd_flag_nonblock | fd_flag_reuse_address))) throw internal_error("torrent::fd_open_local() invalid fd_flags"); if (!(flags & fd_flag_stream)) throw internal_error("torrent::fd_open_local() only stream sockets supported"); int fd = fd__socket(AF_LOCAL, SOCK_STREAM, 0); if (fd == -1) { LT_LOG_FLAG_ERROR("fd_open_local() failed to open socket"); return -1; } if ((flags & fd_flag_nonblock) && !fd_set_nonblock(fd)) { LT_LOG_FD_FLAG_ERROR("fd_open_local() failed to set nonblock"); fd__close(fd); return -1; } if ((flags & fd_flag_reuse_address) && !fd_set_reuse_address(fd, true)) { LT_LOG_FD_FLAG_ERROR("fd_open_local() failed to set reuse_address"); fd__close(fd); return -1; } LT_LOG_FD_FLAG("fd_open_local() succeeded"); return fd; } int fd_open_file(const std::string& path, int flags, mode_t mode) { int fd = ::open(path.c_str(), flags | O_CLOEXEC, mode); if (fd == -1) { LT_LOG_FLAG_ERROR("fd_open_file failed to open file"); return -1; } LT_LOG_FD_FLAG("fd_open_file succeeded"); return fd; } void fd_open_pipe(int& fd1, int& fd2) { int result[2]; #ifdef HAVE_PIPE2 int status = pipe2(result, O_CLOEXEC); #else int status = pipe(result); #endif if (status == -1) throw internal_error("torrent::fd_open_pipe failed: " + system::errno_enum_str(errno)); fd1 = result[0]; fd2 = result[1]; LT_LOG("fd_open_pipe succeeded : fd1:%i fd2:%i", fd1, fd2); } void fd_open_socket_pair(int& fd1, int& fd2) { int result[2]; int type = SOCK_STREAM; #ifdef SOCK_CLOEXEC type |= SOCK_CLOEXEC; #endif if (socketpair(AF_LOCAL, type, 0, result) == -1) throw internal_error("torrent::fd_open_socket_pair failed: " + system::errno_enum_str(errno)); fd1 = result[0]; fd2 = result[1]; LT_LOG("fd_open_socket_pair succeeded : fd1:%i fd2:%i", fd1, fd2); } int fd_open_epoll([[maybe_unused]] int size) { #ifdef USE_EPOLL #ifdef HAVE_EPOLL_CREATE1 int fd = epoll_create1(EPOLL_CLOEXEC); #else int fd = epoll_create(size); #endif if (fd == -1) throw internal_error("torrent::fd_open_epoll failed: " + system::errno_enum_str(errno)); return fd; #else throw internal_error("torrent::fd_open_epoll called but epoll support is not compiled in"); #endif } int fd_open_kqueue() { #ifdef USE_KQUEUE #ifdef HAVE_KQUEUE1 int fd = kqueue1(O_CLOEXEC); #else int fd = kqueue(); #endif if (fd == -1) throw internal_error("torrent::fd_open_kqueue failed: " + system::errno_enum_str(errno)); return fd; #else throw internal_error("torrent::fd_open_kqueue called but kqueue support is not compiled in"); #endif } int fd_open_inotify() { #ifdef USE_INOTIFY #ifdef HAVE_INOTIFY_INIT1 int fd = inotify_init1(IN_CLOEXEC); #else int fd = inotify_init(); #endif if (fd == -1) throw internal_error("torrent::fd_open_inotify failed: " + system::errno_enum_str(errno)); return fd; #else throw internal_error("torrent::fd_open_inotify called but inotify support is not compiled in"); #endif } void fd_close(int fd) { if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) throw internal_error("torrent::fd_close: tried to close stdin/out/err"); if (fd__close(fd) == -1) throw internal_error("torrent::fd_close: " + system::errno_enum_str(errno)); LT_LOG_FD("fd_close succeeded"); } int fd_accept(int fd) { int connection_fd = fd__accept(fd, nullptr, nullptr); if (connection_fd == -1) { LT_LOG_FD_ERROR("fd_accept() failed"); return -1; } LT_LOG_FD("fd_accept() succeeded"); return connection_fd; } fd_sap_tuple fd_sap_accept(int fd) { sa_inet_union sau{}; socklen_t sau_length = sizeof(sockaddr_in6); int connection_fd = fd__accept(fd, &sau.sa, &sau_length); if (connection_fd == -1) { LT_LOG_FD_ERROR("fd_sap_accept() failed"); return fd_sap_tuple{-1, nullptr}; } auto sap = sa_copy(&sau.sa); LT_LOG_FD_SAP("fd_sap_accept() succeeded"); return fd_sap_tuple{connection_fd, std::move(sap)}; } bool fd_bind(int fd, const sockaddr* sa) { if (fd__bind(fd, sa, sa_length(sa)) == -1) { LT_LOG_FD_SOCKADDR_ERROR("fd_bind() failed"); return false; } LT_LOG_FD_SOCKADDR("fd_bind() succeeded"); return true; } bool fd_bind_with_length(int fd, const sockaddr* sa, socklen_t length) { if (fd__bind(fd, sa, length) == -1) { LT_LOG_FD_SOCKADDR_ERROR("fd_bind() failed"); return false; } LT_LOG_FD_SOCKADDR("fd_bind() succeeded"); return true; } bool fd_connect(int fd, const sockaddr* sa) { if (fd__connect(fd, sa, sa_length(sa)) == 0) { LT_LOG_FD_SOCKADDR("fd_connect() succeeded"); return true; } if (errno == EINPROGRESS) { LT_LOG_FD_SOCKADDR("fd_connect() succeeded and in progress"); return true; } LT_LOG_FD_SOCKADDR_ERROR("fd_connect() failed"); return false; } bool fd_connect_with_family(int fd, const sockaddr* sa, int family) { switch (sa->sa_family) { case AF_UNSPEC: errno = EINVAL; LT_LOG_FD("fd_connect_with_family() cannot connect unspecified address"); return false; case AF_INET: if (family == AF_INET6) { LT_LOG_FD_SOCKADDR("fd_connect_with_family() connecting ipv4 using ipv6"); return fd_connect(fd, sa_to_v4mapped(sa).get()); } LT_LOG_FD_SOCKADDR("fd_connect_with_family() connecting ipv4"); return fd_connect(fd, sa); case AF_INET6: if (family == AF_INET) { if (sa_is_v4mapped(sa)) { LT_LOG_FD_SOCKADDR("fd_connect_with_family() connecting ipv4in6 as ipv4"); return fd_connect(fd, sa_from_v4mapped(sa).get()); } errno = EINVAL; LT_LOG_FD("fd_connect_with_family() cannot connect ipv6 address with ipv4 bind"); return false; } LT_LOG_FD_SOCKADDR("fd_connect_with_family() connecting ipv6"); return fd_connect(fd, sa); default: errno = EINVAL; LT_LOG_FD_VALUE("fd_connect_with_family() invalid sa_family", sa->sa_family); return false; } } bool fd_listen(int fd, int backlog) { if (fd__listen(fd, backlog) == -1) { LT_LOG_FD_VALUE_ERROR("fd_listen() failed", backlog); return false; } LT_LOG_FD_VALUE("fd_listen() succeeded", backlog); return true; } bool fd_get_nonblock(int fd, bool* value) { int flags = fd__fcntl_int(fd, F_GETFL, 0); if (flags == -1) { LT_LOG_FD_ERROR("fd_get_nonblock() failed"); return false; } *value = (flags & O_NONBLOCK) != 0; LT_LOG_FD_VALUE("fd_get_nonblock() succeeded", *value); return true; } c_sa_unique_ptr fd_get_peer_name(int fd) { sa_inet_union sau{}; sockaddr* sa = &sau.sa; socklen_t sau_length = sizeof(sockaddr_in6); if (getpeername(fd, sa, &sau_length) == -1) { LT_LOG_FD_ERROR("fd_get_peer_name() failed"); return nullptr; } if (sau_length < sa_length(sa)) { LT_LOG_FD_ERROR("fd_get_peer_name() returned invalid length"); return nullptr; } LT_LOG_FD_SOCKADDR("fd_get_peer_name() succeeded"); return sa_copy(sa); } bool fd_get_socket_error(int fd, int* value) { socklen_t length = sizeof(int); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, value, &length) == -1) { LT_LOG_FD_ERROR("fd_get_socket_error() failed"); return false; } LT_LOG_FD_VALUE("fd_get_socket_error() succeeded", *value); return true; } c_sa_unique_ptr fd_get_socket_name(int fd) { sa_inet_union sau{}; sockaddr* sa = &sau.sa; socklen_t sau_length = sizeof(sockaddr_in6); if (getsockname(fd, sa, &sau_length) == -1) { LT_LOG_FD_ERROR("fd_get_socket_name() failed"); return nullptr; } if (sau_length < sa_length(sa)) { LT_LOG_FD_ERROR("fd_get_socket_name() returned invalid length"); return nullptr; } LT_LOG_FD_SOCKADDR("fd_get_socket_name() succeeded"); return sa_copy(sa); } bool fd_get_type(int fd, int* value) { socklen_t length = sizeof(int); if (getsockopt(fd, SOL_SOCKET, SO_TYPE, value, &length) == -1) { LT_LOG_FD_ERROR("fd_get_type() failed"); return false; } LT_LOG_FD_VALUE("fd_get_type() succeeded", *value); return true; } bool fd_set_dont_route(int fd, bool state) { if (fd__setsockopt_int(fd, SOL_SOCKET, SO_DONTROUTE, state) == -1) { LT_LOG_FD_VALUE_ERROR("fd_set_dont_route() failed", state); return false; } LT_LOG_FD_VALUE("fd_set_dont_route() succeeded", state); return true; } bool fd_set_nonblock(int fd) { int flags = fd__fcntl_int(fd, F_GETFL, 0); if (flags == -1) { LT_LOG_FD_ERROR("fd_set_nonblock() failed reading flags"); return false; } if (fd__fcntl_int(fd, F_SETFL, flags | O_NONBLOCK) == -1) { LT_LOG_FD_ERROR("fd_set_nonblock() failed"); return false; } LT_LOG_FD("fd_set_nonblock() succeeded"); return true; } bool fd_set_reuse_address(int fd, bool state) { if (fd__setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, state) == -1) { LT_LOG_FD_VALUE_ERROR("fd_set_reuse_address() failed", state); return false; } LT_LOG_FD_VALUE("fd_set_reuse_address() succeeded", state); return true; } bool fd_set_priority(int fd, int family, int priority) { int level; int opt; switch (family) { case AF_INET: level = IPPROTO_IP; opt = IP_TOS; break; case AF_INET6: level = IPPROTO_IPV6; opt = IPV6_TCLASS; break; default: errno = EINVAL; LT_LOG_FD_VALUE("fd_set_priority invalid family", family); return false; } if (fd__setsockopt_int(fd, level, opt, priority) == -1) { LT_LOG_FD_VALUE_ERROR("fd_set_priority() failed", priority); return false; } LT_LOG_FD_VALUE("fd_set_priority() succeeded", priority); return true; } bool fd_set_tcp_nodelay(int fd) { if (fd__setsockopt_int(fd, IPPROTO_TCP, TCP_NODELAY, true) == -1) { LT_LOG_FD_VALUE_ERROR("fd_set_tcp_nodelay() failed", true); return false; } LT_LOG_FD_VALUE("fd_set_tcp_nodelay() succeeded", true); return true; } bool fd_set_v6only(int fd, bool state) { if (fd__setsockopt_int(fd, IPPROTO_IPV6, IPV6_V6ONLY, state) == -1) { LT_LOG_FD_VALUE_ERROR("fd_set_v6only() failed", state); return false; } LT_LOG_FD_VALUE("fd_set_v6only() succeeded", state); return true; } bool fd_set_send_buffer_size(int fd, uint32_t size) { int opt = size; if (fd__setsockopt_int(fd, SOL_SOCKET, SO_SNDBUF, opt) == -1) { LT_LOG_FD_VALUE_ERROR("fd_set_send_buffer_size() failed", opt); return false; } LT_LOG_FD_VALUE("fd_set_send_buffer_size() succeeded", opt); return true; } bool fd_set_receive_buffer_size(int fd, uint32_t size) { int opt = size; if (fd__setsockopt_int(fd, SOL_SOCKET, SO_RCVBUF, opt) == -1) { LT_LOG_FD_VALUE_ERROR("fd_set_receive_buffer_size() failed", opt); return false; } LT_LOG_FD_VALUE("fd_set_receive_buffer_size() succeeded", opt); return true; } } // namespace torrent libtorrent-0.16.17/src/torrent/net/fd.h000066400000000000000000000076071522271512000176750ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_FD_H #define LIBTORRENT_NET_FD_H #include #include #include #include namespace torrent { enum fd_flags : int { fd_flag_stream = 0x1, fd_flag_datagram = 0x10, fd_flag_nonblock = 0x20, fd_flag_reuse_address = 0x40, fd_flag_v4 = 0x80, // renamed fd_flag_v4only = 0x80, fd_flag_v6only = 0x100, fd_flag_all = 0x1ff, }; constexpr bool fd_valid_flags(fd_flags flags); int fd_open(fd_flags flags) LIBTORRENT_EXPORT; int fd_open_family(fd_flags flags, int family) LIBTORRENT_EXPORT; int fd_open_local(fd_flags flags) LIBTORRENT_EXPORT; int fd_open_file(const std::string& path, int flags, mode_t mode) LIBTORRENT_EXPORT; void fd_open_pipe(int& fd1, int& fd2) LIBTORRENT_EXPORT; void fd_open_socket_pair(int& fd1, int& fd2) LIBTORRENT_EXPORT; int fd_open_epoll(int size) LIBTORRENT_EXPORT; int fd_open_kqueue() LIBTORRENT_EXPORT; int fd_open_inotify() LIBTORRENT_EXPORT; void fd_close(int fd) LIBTORRENT_EXPORT; int fd_accept(int fd) LIBTORRENT_EXPORT; fd_sap_tuple fd_sap_accept(int fd) LIBTORRENT_EXPORT; bool fd_bind(int fd, const sockaddr* sa) LIBTORRENT_EXPORT; bool fd_bind_with_length(int fd, const sockaddr* sa, socklen_t length) LIBTORRENT_EXPORT; bool fd_connect(int fd, const sockaddr* sa) LIBTORRENT_EXPORT; bool fd_connect_with_family(int fd, const sockaddr* sa, int family) LIBTORRENT_EXPORT; bool fd_listen(int fd, int backlog) LIBTORRENT_EXPORT; bool fd_get_nonblock(int fd, bool* value) LIBTORRENT_EXPORT; c_sa_unique_ptr fd_get_peer_name(int fd) LIBTORRENT_EXPORT; bool fd_get_socket_error(int fd, int* value) LIBTORRENT_EXPORT; c_sa_unique_ptr fd_get_socket_name(int fd) LIBTORRENT_EXPORT; bool fd_get_type(int fd, int* value) LIBTORRENT_EXPORT; bool fd_set_dont_route(int fd, bool state) LIBTORRENT_EXPORT; bool fd_set_nonblock(int fd) LIBTORRENT_EXPORT; bool fd_set_reuse_address(int fd, bool state) LIBTORRENT_EXPORT; bool fd_set_priority(int fd, int family, int priority) LIBTORRENT_EXPORT; bool fd_set_tcp_nodelay(int fd) LIBTORRENT_EXPORT; bool fd_set_v6only(int fd, bool state) LIBTORRENT_EXPORT; bool fd_set_send_buffer_size(int fd, uint32_t size) LIBTORRENT_EXPORT; bool fd_set_receive_buffer_size(int fd, uint32_t size) LIBTORRENT_EXPORT; // Defined with gnu::weak so that we can override them in tests. [[gnu::weak]] int fd__accept(int socket, sockaddr *address, socklen_t *address_len) LIBTORRENT_EXPORT; [[gnu::weak]] int fd__bind(int socket, const sockaddr *address, socklen_t address_len) LIBTORRENT_EXPORT; [[gnu::weak]] int fd__close(int fildes) LIBTORRENT_EXPORT; [[gnu::weak]] int fd__connect(int socket, const sockaddr *address, socklen_t address_len) LIBTORRENT_EXPORT; [[gnu::weak]] int fd__fcntl_int(int fildes, int cmd, int arg) LIBTORRENT_EXPORT; [[gnu::weak]] int fd__listen(int socket, int backlog) LIBTORRENT_EXPORT; [[gnu::weak]] int fd__setsockopt_int(int socket, int level, int option_name, int option_value) LIBTORRENT_EXPORT; [[gnu::weak]] int fd__socket(int domain, int type, int protocol) LIBTORRENT_EXPORT; constexpr fd_flags operator |(fd_flags lhs, fd_flags rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } inline fd_flags& operator |=(fd_flags& lhs, fd_flags rhs) { return (lhs = lhs | rhs); } constexpr bool fd_valid_flags(fd_flags flags) { return ((flags & fd_flag_stream) || (flags & fd_flag_datagram)) && !((flags & fd_flag_stream) && (flags & fd_flag_datagram)) && !((flags & fd_flag_v4only) && (flags & fd_flag_v6only)) && !(flags & ~(fd_flag_all)); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/net/http_get.cc000066400000000000000000000073371522271512000212600ustar00rootroot00000000000000#include "config.h" #include "torrent/net/http_get.h" #include #include #include "net/curl_get.h" #include "net/curl_stack.h" #include "torrent/exceptions.h" #include "torrent/net/http_stack.h" #include "torrent/system/thread.h" namespace torrent::net { // TODO: Use Resolver for dns lookups? HttpGet::HttpGet() = default; HttpGet::HttpGet(std::string url, std::shared_ptr stream) : m_curl_get(new CurlGet(std::move(url), std::move(stream))) { } HttpGet::~HttpGet() = default; void HttpGet::close_and_keep_callbacks() { if (!is_valid()) throw torrent::internal_error("HttpGet::close() called on an invalid HttpGet object."); CurlGet::close_and_keep_callbacks(m_curl_get); } void HttpGet::close_and_cancel_callbacks(system::Thread* callback_thread) { if (!is_valid()) throw torrent::internal_error("HttpGet::close_and_cancel_callbacks() called on an invalid HttpGet object."); CurlGet::close_and_cancel_callbacks(m_curl_get, callback_thread); } void HttpGet::wait_for_close() { if (!is_valid()) throw torrent::internal_error("HttpGet::wait_for_close() called on an invalid HttpGet object."); m_curl_get->wait_for_close(); } bool HttpGet::try_wait_for_close() { if (!is_valid()) return false; return m_curl_get->try_wait_for_close(); } void HttpGet::reset(const std::string& url, std::shared_ptr stream) { m_curl_get = std::make_shared(); m_curl_get->reset(url, std::move(stream)); } std::string HttpGet::url() const { return m_curl_get->url(); } int64_t HttpGet::size_done() const { return m_curl_get->size_done(); } int64_t HttpGet::size_total() const { return m_curl_get->size_total(); } uint32_t HttpGet::timeout() const { return m_curl_get->timeout(); } void HttpGet::set_timeout(uint32_t seconds) { m_curl_get->set_timeout(seconds); } uint32_t HttpGet::max_file_size() const { return m_curl_get->max_file_size(); } void HttpGet::set_max_file_size(uint32_t bytes) { m_curl_get->set_max_file_size(bytes); } void HttpGet::set_redirect_only_http_https() { m_curl_get->set_redirect_only_http_https(); } void HttpGet::use_ipv4() { m_curl_get->set_initial_resolve(CurlGet::RESOLVE_IPV4); m_curl_get->set_retry_resolve(CurlGet::RESOLVE_NONE); } void HttpGet::use_ipv6() { m_curl_get->set_initial_resolve(CurlGet::RESOLVE_IPV6); m_curl_get->set_retry_resolve(CurlGet::RESOLVE_NONE); } void HttpGet::use_family(int family) { if (family == AF_INET) use_ipv4(); else if (family == AF_INET6) use_ipv6(); else throw torrent::internal_error("HttpGet::use_family() called with an invalid address family."); } void HttpGet::prefer_ipv4() { m_curl_get->set_initial_resolve(CurlGet::RESOLVE_IPV4); m_curl_get->set_retry_resolve(CurlGet::RESOLVE_IPV6); } void HttpGet::prefer_ipv6() { m_curl_get->set_initial_resolve(CurlGet::RESOLVE_IPV6); m_curl_get->set_retry_resolve(CurlGet::RESOLVE_IPV4); } void HttpGet::add_done_slot(system::Thread* thread, const std::function& fn) { if (m_curl_get == nullptr) throw torrent::internal_error("HttpGet::add_done_slot() called on an invalid HttpGet object."); m_curl_get->add_done_slot([thread, fn, curl_get = m_curl_get]() { thread->callback(curl_get->callback_id(), [fn]() { fn(); }); }); } void HttpGet::add_failed_slot(system::Thread* thread, const std::function& fn) { if (m_curl_get == nullptr) throw torrent::internal_error("HttpGet::add_failed_slot() called on an invalid HttpGet object."); m_curl_get->add_failed_slot([thread, fn, curl_get = m_curl_get](const std::string& error) { thread->callback(curl_get->callback_id(), [fn, error]() { fn(error); }); }); } } // namespace torrent::net libtorrent-0.16.17/src/torrent/net/http_get.h000066400000000000000000000054711522271512000211170ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_NET_HTTP_GET_H #define LIBTORRENT_TORRENT_NET_HTTP_GET_H #include #include #include #include #include #include namespace torrent::net { class CurlGet; class CurlStack; class HttpStack; class LIBTORRENT_EXPORT HttpGet { public: HttpGet(); HttpGet(std::string url, std::shared_ptr stream); ~HttpGet(); HttpGet(const HttpGet&) = default; HttpGet& operator=(const HttpGet&) = default; bool is_valid() const { return m_curl_get != nullptr; } // close_and_keep_callbacks() and close_and_cancel_callbacks() do not immediately close as they // use callbacks to thread_net. Some functions throw internal_error if they don't wait for close // to finish. void close_and_keep_callbacks(); void close_and_cancel_callbacks(system::Thread* callback_thread); // Always call before reset() if the HttpGet is being reused. void wait_for_close(); bool try_wait_for_close(); // Calling reset is not allowed while the HttpGet is in the stack. Does not clear slots or callbacks. void reset(const std::string& url, std::shared_ptr str); std::string url() const; int64_t size_done() const; int64_t size_total() const; uint32_t timeout() const; void set_timeout(uint32_t seconds); uint32_t max_file_size() const; void set_max_file_size(uint32_t bytes); void set_redirect_only_http_https(); void use_ipv4(); void use_ipv6(); void use_family(int family); void prefer_ipv4(); void prefer_ipv6(); // The slots add callbacks to the calling thread when triggered, and all slots will remain in the // thread's callback queue even if the underlying CurlGet is closed or deleted. // // Calling add_*_slot is not allowed while the HttpGet is in the stack. void add_done_slot(system::Thread* thread, const std::function& fn); void add_failed_slot(system::Thread* thread, const std::function& fn); // TODO: Add a closed_slot. bool operator<(const HttpGet& other) const; bool operator==(const HttpGet& other) const; protected: friend class HttpStack; auto curl_get() { return m_curl_get; } private: std::shared_ptr m_curl_get; }; inline bool HttpGet::operator<(const HttpGet& other) const { return m_curl_get < other.m_curl_get; } inline bool HttpGet::operator==(const HttpGet& other) const { return m_curl_get == other.m_curl_get; } } // namespace torrent::net #endif // TORRENT_NET_HTTP_GET_H libtorrent-0.16.17/src/torrent/net/http_stack.cc000066400000000000000000000133741522271512000216040ustar00rootroot00000000000000#include "config.h" #include "torrent/net/http_stack.h" #include #include #include #include "net/curl_get.h" #include "net/curl_stack.h" #include "net/thread_net.h" #include "torrent/exceptions.h" #include "torrent/net/http_get.h" #include "torrent/system/thread.h" namespace torrent::net { // TODO: This should be in a net/utils file. // TODO: Require scheme to also be returned / checked? bool verify_url_guess_scheme(const std::string& url) { auto curlu = std::unique_ptr(curl_url(), &curl_url_cleanup); return curl_url_set(curlu.get(), CURLUPART_URL, url.c_str(), CURLU_GUESS_SCHEME) == CURLUE_OK; } bool verify_no_path_query_fragment(const std::string& url) { auto curlu = std::unique_ptr(curl_url(), &curl_url_cleanup); if (curl_url_set(curlu.get(), CURLUPART_URL, url.c_str(), CURLU_NON_SUPPORT_SCHEME) != CURLUE_OK) return false; char* path_ptr{}; char* query_ptr{}; char* fragment_ptr{}; if (curl_url_get(curlu.get(), CURLUPART_PATH, &path_ptr, 0) == CURLUE_OK) { if (std::strcmp(path_ptr, "/") != 0) { curl_free(path_ptr); return false; } curl_free(path_ptr); } if (curl_url_get(curlu.get(), CURLUPART_QUERY, &query_ptr, 0) == CURLUE_OK) { curl_free(query_ptr); return false; } if (curl_url_get(curlu.get(), CURLUPART_FRAGMENT, &fragment_ptr, 0) == CURLUE_OK) { curl_free(fragment_ptr); return false; } return true; } std::string parse_uri_scheme(const std::string& url) { auto curlu = std::unique_ptr(curl_url(), &curl_url_cleanup); if (curl_url_set(curlu.get(), CURLUPART_URL, url.c_str(), CURLU_NON_SUPPORT_SCHEME) != CURLUE_OK) return {}; char* scheme_ptr{}; if (curl_url_get(curlu.get(), CURLUPART_SCHEME, &scheme_ptr, 0) != CURLUE_OK) return {}; std::string scheme(scheme_ptr); curl_free(scheme_ptr); return scheme; } std::pair parse_uri_host_port(const std::string& uri) { auto curlu = std::unique_ptr(curl_url(), &curl_url_cleanup); if (curl_url_set(curlu.get(), CURLUPART_URL, uri.c_str(), CURLU_NON_SUPPORT_SCHEME) != CURLUE_OK) return {"", 0}; char* host_ptr{}; char* port_ptr{}; if (curl_url_get(curlu.get(), CURLUPART_HOST, &host_ptr, 0) != CURLUE_OK) return {"", 0}; std::string host(host_ptr); curl_free(host_ptr); if (curl_url_get(curlu.get(), CURLUPART_PORT, &port_ptr, 0) != CURLUE_OK) return {host, 0}; uint16_t port{}; auto result = std::from_chars(port_ptr, port_ptr + std::strlen(port_ptr), port); curl_free(port_ptr); if (result.ec != std::errc()) return {"", 0}; return {host, port}; } std::pair parse_uri_user_password(const std::string& uri) { auto curlu = std::unique_ptr(curl_url(), &curl_url_cleanup); if (curl_url_set(curlu.get(), CURLUPART_URL, uri.c_str(), CURLU_NON_SUPPORT_SCHEME) != CURLUE_OK) return {"", ""}; char* user_ptr{}; char* password_ptr{}; if (curl_url_get(curlu.get(), CURLUPART_USER, &user_ptr, 0) != CURLUE_OK) return {"", ""}; std::string user(user_ptr); curl_free(user_ptr); if (curl_url_get(curlu.get(), CURLUPART_PASSWORD, &password_ptr, 0) != CURLUE_OK) return {user, ""}; std::string password(password_ptr); curl_free(password_ptr); return {user, password}; } HttpStack::HttpStack(system::Thread* thread) : m_stack(new CurlStack(thread)) { } HttpStack::~HttpStack() = default; void HttpStack::shutdown() { m_stack->shutdown(); } void HttpStack::clear_requests() { m_stack->thread()->callback([this]() { m_stack->clear_requests(); }); } void HttpStack::start_get(HttpGet& http_get) { if (!http_get.is_valid()) throw torrent::internal_error("HttpStack::start_get() called with an invalid HttpGet object."); CurlGet::start(http_get.curl_get(), m_stack.get()); } unsigned int HttpStack::size() const { return m_stack->size(); } unsigned int HttpStack::max_cache_connections() const { return m_stack->max_cache_connections(); } unsigned int HttpStack::max_host_connections() const { return m_stack->max_host_connections(); } unsigned int HttpStack::max_total_connections() const { return m_stack->max_total_connections(); } void HttpStack::set_max_cache_connections(unsigned int value) { m_stack->set_max_cache_connections(value); } void HttpStack::set_max_host_connections(unsigned int value) { m_stack->set_max_host_connections(value); } void HttpStack::set_max_total_connections(unsigned int value) { m_stack->set_max_total_connections(value); } std::string HttpStack::user_agent() const { return m_stack->user_agent(); } std::string HttpStack::http_capath() const { return m_stack->http_capath(); } std::string HttpStack::http_cacert() const { return m_stack->http_cacert(); } void HttpStack::set_user_agent(const std::string& s) { m_stack->set_user_agent(s); } void HttpStack::set_http_capath(const std::string& s) { m_stack->set_http_capath(s); } void HttpStack::set_http_cacert(const std::string& s) { m_stack->set_http_cacert(s); } bool HttpStack::ssl_verify_host() const { return m_stack->ssl_verify_host(); } bool HttpStack::ssl_verify_peer() const { return m_stack->ssl_verify_peer(); } void HttpStack::set_ssl_verify_host(bool s) { m_stack->set_ssl_verify_host(s); } void HttpStack::set_ssl_verify_peer(bool s) { m_stack->set_ssl_verify_peer(s); } long HttpStack::dns_timeout() const { return m_stack->dns_timeout(); } void HttpStack::set_dns_timeout(long timeout) { m_stack->set_dns_timeout(timeout); } // // Restricted methods: // void HttpStack::set_http_proxy(const std::string& s) { m_stack->set_http_proxy(s); } } // namespace torrent::net libtorrent-0.16.17/src/torrent/net/http_stack.h000066400000000000000000000036661522271512000214510ustar00rootroot00000000000000#ifndef TORRENT_NET_HTTP_GET_H #define TORRENT_NET_HTTP_GET_H #include #include #include namespace torrent { class ThreadNet; } namespace torrent::runtime { class ProxyManager; } namespace torrent::net { class CurlStack; class LIBTORRENT_EXPORT HttpStack { public: HttpStack(system::Thread* thread); ~HttpStack(); void shutdown(); void clear_requests(); void start_get(HttpGet& http_get); unsigned int size() const; unsigned int max_cache_connections() const; unsigned int max_host_connections() const; unsigned int max_total_connections() const; void set_max_cache_connections(unsigned int value); void set_max_host_connections(unsigned int value); void set_max_total_connections(unsigned int value); std::string user_agent() const; std::string http_capath() const; std::string http_cacert() const; void set_user_agent(const std::string& s); void set_http_capath(const std::string& s); void set_http_cacert(const std::string& s); bool ssl_verify_host() const; bool ssl_verify_peer() const; void set_ssl_verify_host(bool s); void set_ssl_verify_peer(bool s); long dns_timeout() const; void set_dns_timeout(long timeout); protected: friend class HttpGet; friend class torrent::ThreadNet; friend class torrent::runtime::ProxyManager; CurlStack* curl_stack() { return m_stack.get(); } void set_http_proxy(const std::string& s); private: HttpStack() = delete; HttpStack(const HttpStack&) = delete; HttpStack& operator=(const HttpStack&) = delete; std::unique_ptr m_stack; }; } // namespace torrent::net #endif // TORRENT_NET_HTTP_GET_H libtorrent-0.16.17/src/torrent/net/resolver.cc000066400000000000000000000121221522271512000212670ustar00rootroot00000000000000#include "config.h" #include "torrent/net/resolver.h" #include #include #include "net/thread_net.h" #include "net/dns_buffer.h" #include "net/dns_cache.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/system/callbacks.h" #include "torrent/system/thread.h" namespace torrent::net { void Resolver::init() { m_thread = torrent::system::Thread::self(); assert(m_thread != nullptr && "Resolver::m_thread is nullptr."); } void Resolver::resolve_both(system::callback_id& id, const std::string& hostname, int family, both_callback&& callback) { auto [sin, sin6] = try_lookup_numeric(hostname, family); if (sin || sin6) { m_thread->callback(id, [family, callback = std::move(callback), sin = std::move(sin), sin6 = std::move(sin6)]() mutable { int err = (!sin && family == AF_UNSPEC) ? EAI_NONAME : 0; int err6 = (!sin6 && family == AF_UNSPEC) ? EAI_NONAME : 0; callback(std::move(sin), err, std::move(sin6), err6); }); return; } auto cb = [this, id, hostname, family, callback = std::move(callback)]() mutable { auto fn = [this, id, callback = std::move(callback)](sin_shared_ptr sin, int err, sin6_shared_ptr sin6, int err6) mutable { m_thread->callback(id, std::bind(std::move(callback), std::move(sin), err, std::move(sin6), err6)); }; ThreadNet::thread_net()->dns_cache()->resolve(id.get(), hostname, family, std::move(fn)); }; net_thread::callback(id, std::move(cb)); } void Resolver::resolve_preferred(system::callback_id& id, const std::string& hostname, int family, int preferred, single_callback&& callback) { if (preferred != AF_INET && preferred != AF_INET6) throw internal_error("Resolver::resolve_preferred() invalid preferred family."); auto [sin, sin6] = try_lookup_numeric(hostname, family); if (sin || sin6) { sa_shared_ptr sa = sin ? sa_copy_in(sin.get()) : sa_copy_in6(sin6.get()); m_thread->callback(id, [callback = std::move(callback), sa = std::move(sa)]() mutable { callback(sa, 0); }); return; } auto cb = [this, id, hostname, family, preferred, callback = std::move(callback)] () mutable { auto fn = [this, id, preferred, callback = std::move(callback)](const auto& sin, int err, const auto& sin6, int err6) mutable { // 'result' copies the deleter from the unique_ptr returned by sa_copy_in. sa_shared_ptr result; int error{}; // TODO: Do we need to handle no-record differently? if (preferred == AF_INET) { if (err == 0 && sin != nullptr) result = sa_copy_in(sin.get()); else if (err6 == 0 && sin6 != nullptr) result = sa_copy_in6(sin6.get()); else error = err != 0 ? err : err6; } else { if (err6 == 0 && sin6 != nullptr) result = sa_copy_in6(sin6.get()); else if (err == 0 && sin != nullptr) result = sa_copy_in(sin.get()); else error = err6 != 0 ? err6 : err; } m_thread->callback(id, std::bind(std::move(callback), std::move(result), error)); }; ThreadNet::thread_net()->dns_cache()->resolve(id.get(), hostname, family, std::move(fn)); }; net_thread::callback(id, std::move(cb)); } void Resolver::resolve_specific(system::callback_id& id, const std::string& hostname, int family, single_callback&& callback) { if (family != AF_INET && family != AF_INET6) throw internal_error("Resolver::resolve_specific() invalid family."); auto [sin, sin6] = try_lookup_numeric(hostname, family); if (sin || sin6) { sa_shared_ptr sa = sin ? sa_copy_in(sin.get()) : sa_copy_in6(sin6.get()); m_thread->callback(id, [callback = std::move(callback), sa = std::move(sa)]() mutable { callback(sa, 0); }); return; } auto cb = [this, id, hostname, family, callback = std::move(callback)]() mutable { auto fn = [this, id, family, callback = std::move(callback)](const auto& sin, int err, const auto& sin6, int err6) mutable { sa_shared_ptr result; int error{}; if (family == AF_INET) { if (err == 0 && sin != nullptr) result = sa_copy_in(sin.get()); else error = err; } else { if (err6 == 0 && sin6 != nullptr) result = sa_copy_in6(sin6.get()); else error = err6; } m_thread->callback(id, std::bind(std::move(callback), std::move(result), error)); }; ThreadNet::thread_net()->dns_cache()->resolve(id.get(), hostname, family, std::move(fn)); }; net_thread::callback(id, std::move(cb)); } void Resolver::cancel(system::callback_id& id) { assert(m_thread != nullptr && std::this_thread::get_id() == m_thread->thread_id()); system::cancel_callback_and_wait(id, m_thread, net_thread::thread()); ThreadNet::thread_net()->dns_buffer()->cancel_safe(id.get()); system::cancel_callback_and_wait(id, m_thread, net_thread::thread()); } } // namespace torrent::net libtorrent-0.16.17/src/torrent/net/resolver.h000066400000000000000000000023011522271512000211270ustar00rootroot00000000000000#ifndef TORRENT_NET_RESOLVER_H #define TORRENT_NET_RESOLVER_H #include #include #include #include namespace torrent::net { class LIBTORRENT_EXPORT Resolver { public: using both_callback = std::function; using single_callback = std::function; Resolver() = default; ~Resolver() = default; void resolve_both(system::callback_id& id, const std::string& hostname, int family, both_callback&& callback); void resolve_preferred(system::callback_id& id, const std::string& hostname, int family, int preferred, single_callback&& callback); void resolve_specific(system::callback_id& id, const std::string& hostname, int family, single_callback&& callback); // Must be called from the owning thread. void cancel(system::callback_id& callback_id); protected: friend class system::Thread; void init(); private: Resolver(const Resolver&) = delete; Resolver& operator=(const Resolver&) = delete; system::Thread* m_thread{nullptr}; }; } // namespace torrent::net #endif libtorrent-0.16.17/src/torrent/net/socket_address.cc000066400000000000000000000550111522271512000224270ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" namespace torrent::net { void sa_free(const sockaddr* sa) { if (sa == nullptr) return; switch (sa->sa_family) { case AF_UNSPEC: delete reinterpret_cast(sa); break; case AF_INET: delete reinterpret_cast(sa); break; case AF_INET6: delete reinterpret_cast(sa); break; case AF_UNIX: delete reinterpret_cast(sa); break; default: throw internal_error("torrent::sa_free() invalid family type"); } } } // torrent::net namespace torrent { static constexpr uint32_t sin6_addr32_index(const sockaddr_in6* sa, unsigned int index) { return (sa->sin6_addr.s6_addr[index * 4 + 0] << 24) + (sa->sin6_addr.s6_addr[index * 4 + 1] << 16) + (sa->sin6_addr.s6_addr[index * 4 + 2] << 8) + (sa->sin6_addr.s6_addr[index * 4 + 3] << 0); } static in6_addr sin6_make_addr32(uint32_t addr0, uint32_t addr1, uint32_t addr2, uint32_t addr3) { uint32_t addr32[4]; addr32[0] = htonl(addr0); addr32[1] = htonl(addr1); addr32[2] = htonl(addr2); addr32[3] = htonl(addr3); return *reinterpret_cast(addr32); } bool sa_is_unspec(const sockaddr* sa) { return sa != nullptr && sa->sa_family == AF_UNSPEC; } bool sa_is_inet(const sockaddr* sa) { return sa != nullptr && sa->sa_family == AF_INET; } bool sa_is_inet6(const sockaddr* sa) { return sa != nullptr && sa->sa_family == AF_INET6; } bool sa_is_inet_inet6(const sockaddr* sa) { return sa != nullptr && (sa->sa_family == AF_INET || sa->sa_family == AF_INET6); } bool sa_is_any(const sockaddr* sa) { switch (sa->sa_family) { case AF_INET: return sin_is_any(reinterpret_cast(sa)); case AF_INET6: if (sa_is_v4mapped(sa)) return sin6_addr32_index(reinterpret_cast(sa), 3) == htonl(INADDR_ANY); return sin6_is_any(reinterpret_cast(sa)); default: return true; } } bool sin_is_any(const sockaddr_in* sa) { return sa->sin_addr.s_addr == htonl(INADDR_ANY); } bool sin6_is_any(const sockaddr_in6* sa) { return std::memcmp(&sa->sin6_addr, &in6addr_any, sizeof(in6_addr)) == 0; } bool sa_is_broadcast(const sockaddr* sa) { switch (sa->sa_family) { case AF_INET: return sin_is_broadcast(reinterpret_cast(sa)); case AF_INET6: if (sa_is_v4mapped(sa)) return sin6_addr32_index(reinterpret_cast(sa), 3) == htonl(INADDR_BROADCAST); return false; default: return false; } } bool sin_is_broadcast(const sockaddr_in* sa) { return sa->sin_addr.s_addr == htonl(INADDR_BROADCAST); } bool sa_is_loopback(const sockaddr* sa) { switch (sa->sa_family) { case AF_INET: return sin_is_loopback(reinterpret_cast(sa)); case AF_INET6: if (sa_is_v4mapped(sa)) { uint32_t v4_raw = sin6_addr32_index(reinterpret_cast(sa), 3); return (ntohl(v4_raw) >> 24) == 127; } return sin6_is_loopback(reinterpret_cast(sa)); default: return false; } } bool sin_is_loopback(const sockaddr_in* sa) { return (ntohl(sa->sin_addr.s_addr) >> 24) == 127; } bool sin6_is_loopback(const sockaddr_in6* sa) { return IN6_IS_ADDR_LOOPBACK(&sa->sin6_addr); } bool sa_is_v4mapped(const sockaddr* sa) { return sa != nullptr && sa->sa_family == AF_INET6 && sin6_is_v4mapped(reinterpret_cast(sa)); } bool sin6_is_v4mapped(const sockaddr_in6* sa) { return sa != nullptr && IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr); } bool sa_is_port_any(const sockaddr* sa) { return sa_port(sa) == 0; } size_t sa_length(const sockaddr* sa) { switch(sa->sa_family) { case AF_UNSPEC: return sizeof(sockaddr); case AF_INET: return sizeof(sockaddr_in); case AF_INET6: return sizeof(sockaddr_in6); case AF_UNIX: return sizeof(sockaddr_un); default: throw internal_error("torrent::sa_length() sockaddr is not a valid family"); } } sa_unique_ptr sa_make_unspec() { sa_unique_ptr sa(new sockaddr{}); sa->sa_family = AF_UNSPEC; return sa; } sa_unique_ptr sa_make_inet() { return sa_unique_ptr(reinterpret_cast(sin_make().release())); } sa_unique_ptr sa_make_inet_any() { return sa_unique_ptr(reinterpret_cast(sin_make_any().release())); } sa_unique_ptr sa_make_inet_h(uint32_t addr, uint16_t port) { return sa_unique_ptr(reinterpret_cast(sin_make_h(addr, port).release())); } sa_unique_ptr sa_make_inet_n(uint32_t addr, uint16_t port) { return sa_unique_ptr(reinterpret_cast(sin_make_n(addr, port).release())); } sa_unique_ptr sa_make_inet6() { return sa_unique_ptr(reinterpret_cast(sin6_make().release())); } sa_unique_ptr sa_make_inet6_any() { return sa_unique_ptr(reinterpret_cast(sin6_make_any().release())); } sa_unique_ptr sa_make_unix(const std::string& pathname) { if (!pathname.empty()) throw internal_error("torrent::sa_make_unix() function not implemented"); sun_unique_ptr sunp(new sockaddr_un{}); sunp->sun_family = AF_UNIX; // TODO: verify length, copy pathname return sa_unique_ptr(reinterpret_cast(sunp.release())); } sa_unique_ptr sa_convert(const sockaddr* sa) { if (sa == nullptr) return sa_make_unspec(); switch(sa->sa_family) { case AF_INET: return sa_copy_in(reinterpret_cast(sa)); case AF_INET6: if (sin6_is_v4mapped(reinterpret_cast(sa))) return sa_from_v4mapped_in6(reinterpret_cast(sa)); return sa_copy_in6(reinterpret_cast(sa)); case AF_UNSPEC: return sa_make_unspec(); default: throw internal_error("torrent::sa_convert() sockaddr is not a valid family"); } } sa_unique_ptr sa_copy(const sockaddr* sa) { if (sa == nullptr) throw internal_error("torrent::sa_copy() sockaddr is a nullptr"); switch(sa->sa_family) { case AF_INET: return sa_copy_in(reinterpret_cast(sa)); case AF_INET6: return sa_copy_in6(reinterpret_cast(sa)); case AF_UNSPEC: return sa_make_unspec(); default: throw internal_error("torrent::sa_copy() sockaddr is not a valid family"); } } sa_unique_ptr sa_copy_in(const sockaddr_in* sa) { sa_unique_ptr result(reinterpret_cast(new sockaddr_in)); std::memcpy(result.get(), sa, sizeof(sockaddr_in)); return result; } sa_unique_ptr sa_copy_in6(const sockaddr_in6* sa) { sa_unique_ptr result(reinterpret_cast(new sockaddr_in6)); std::memcpy(result.get(), sa, sizeof(sockaddr_in6)); return result; } sa_unique_ptr sa_copy_addr(const sockaddr* sa, uint16_t port) { if (sa == nullptr) throw internal_error("torrent::sa_copy_addr() sockaddr is a nullptr"); switch(sa->sa_family) { case AF_INET: return sa_copy_addr_in(reinterpret_cast(sa), port); case AF_INET6: return sa_copy_addr_in6(reinterpret_cast(sa), port); case AF_UNSPEC: return sa_make_unspec(); default: throw internal_error("torrent::sa_copy_addr() sockaddr is not a valid family"); } } sa_unique_ptr sa_copy_addr_in(const sockaddr_in* sa, uint16_t port) { sa_unique_ptr result(reinterpret_cast(new sockaddr_in{})); reinterpret_cast(result.get())->sin_family = AF_INET; reinterpret_cast(result.get())->sin_addr = sa->sin_addr; reinterpret_cast(result.get())->sin_port = htons(port); return result; } sa_unique_ptr sa_copy_addr_in6(const sockaddr_in6* sa, uint16_t port) { sa_unique_ptr result(reinterpret_cast(new sockaddr_in6{})); reinterpret_cast(result.get())->sin6_family = AF_INET6; std::memcpy(&reinterpret_cast(result.get())->sin6_addr, &sa->sin6_addr, sizeof(in6_addr)); reinterpret_cast(result.get())->sin6_port = htons(port); return result; } sa_unique_ptr sa_copy_unmapped(const sockaddr* sa) { if (sa == nullptr) throw internal_error("torrent::sa_copy_unmapped() sockaddr is a nullptr"); switch(sa->sa_family) { case AF_INET: return sa_copy_in(reinterpret_cast(sa)); case AF_INET6: if (sin6_is_v4mapped(reinterpret_cast(sa))) return sa_from_v4mapped_in6(reinterpret_cast(sa)); return sa_copy_in6(reinterpret_cast(sa)); case AF_UNSPEC: return sa_make_unspec(); default: throw internal_error("torrent::sa_copy_unmapped() sockaddr is not a valid family"); } } sin_unique_ptr sin_copy(const sockaddr_in* sa) { sin_unique_ptr result(new sockaddr_in); std::memcpy(result.get(), sa, sizeof(sockaddr_in)); return result; } sin6_unique_ptr sin6_copy(const sockaddr_in6* sa) { sin6_unique_ptr result(new sockaddr_in6); std::memcpy(result.get(), sa, sizeof(sockaddr_in6)); return result; } sin_unique_ptr sin_make() { sin_unique_ptr sa(new sockaddr_in{}); sa->sin_family = AF_INET; return sa; } sin_unique_ptr sin_make_any() { sin_unique_ptr sa(new sockaddr_in{}); sa->sin_family = AF_INET; sa->sin_addr.s_addr = htonl(INADDR_ANY); return sa; } sin_unique_ptr sin_make_h(uint32_t addr, uint16_t port) { sin_unique_ptr sa(new sockaddr_in{}); sa->sin_family = AF_INET; sa->sin_addr.s_addr = htonl(addr); sa->sin_port = htons(port); return sa; } sin_unique_ptr sin_make_n(uint32_t addr, uint16_t port) { sin_unique_ptr sa(new sockaddr_in{}); sa->sin_family = AF_INET; sa->sin_addr.s_addr = addr; sa->sin_port = port; return sa; } sin6_unique_ptr sin6_make() { sin6_unique_ptr sa(new sockaddr_in6{}); sa->sin6_family = AF_INET6; return sa; } sin6_unique_ptr sin6_make_any() { sin6_unique_ptr sa(new sockaddr_in6{}); sa->sin6_family = AF_INET6; sa->sin6_addr = in6addr_any; return sa; } sa_unique_ptr sa_from_v4mapped(const sockaddr* sa) { if (!sa_is_inet6(sa)) throw internal_error("torrent::sa_from_v4mapped() sockaddr is not inet6"); return sa_from_in(sin_from_v4mapped_in6(reinterpret_cast(sa))); } sa_unique_ptr sa_to_v4mapped(const sockaddr* sa) { if (!sa_is_inet(sa)) throw internal_error("torrent::sa_to_v4mapped() sockaddr is not inet"); return sa_from_in6(sin6_to_v4mapped_in(reinterpret_cast(sa))); } sin_unique_ptr sin_from_v4mapped_in6(const sockaddr_in6* sin6) { if (!sin6_is_v4mapped(sin6)) throw internal_error("torrent::sin6_is_v4mapped() sockaddr_in6 is not v4mapped"); sin_unique_ptr result = sin_make(); result->sin_addr.s_addr = reinterpret_cast(htonl(sin6_addr32_index(sin6, 3))); result->sin_port = sin6->sin6_port; return result; } sin6_unique_ptr sin6_to_v4mapped_in(const sockaddr_in* sin) { sin6_unique_ptr result = sin6_make(); result->sin6_addr = sin6_make_addr32(0, 0, 0xffff, ntohl(sin->sin_addr.s_addr)); result->sin6_port = sin->sin_port; return result; } sin_unique_ptr sin_from_sa(sa_unique_ptr sap) { if (!sap_is_inet(sap)) throw internal_error("torrent::sin_from_sa() sockaddr is nullptr or not inet"); return sin_unique_ptr(reinterpret_cast(sap.release())); } sin6_unique_ptr sin6_from_sa(sa_unique_ptr sap) { if (!sap_is_inet6(sap)) throw internal_error("torrent::sin6_from_sa() sockaddr is nullptr or not inet6"); return sin6_unique_ptr(reinterpret_cast(sap.release())); } c_sin_unique_ptr sin_from_c_sa(c_sa_unique_ptr sap) { if (!sap_is_inet(sap)) throw internal_error("torrent::sin_from_c_sa() sockaddr is nullptr or not inet"); return c_sin_unique_ptr(reinterpret_cast(sap.release())); } c_sin6_unique_ptr sin6_from_c_sa(c_sa_unique_ptr sap) { if (!sap_is_inet6(sap)) throw internal_error("torrent::sin6_from_c_sa() sockaddr is nullptr or not inet6"); return c_sin6_unique_ptr(reinterpret_cast(sap.release())); } void sa_clear_inet6(sockaddr_in6* sa) { *sa = sockaddr_in6{}; sa->sin6_family = AF_INET6; } uint16_t sa_port(const sockaddr* sa) { if (sa == nullptr) return 0; switch(sa->sa_family) { case AF_INET: return ntohs(reinterpret_cast(sa)->sin_port); case AF_INET6: return ntohs(reinterpret_cast(sa)->sin6_port); default: return 0; } } void sa_set_port(sockaddr* sa, uint16_t port) { switch(sa->sa_family) { case AF_INET: reinterpret_cast(sa)->sin_port = htons(port); return; case AF_INET6: reinterpret_cast(sa)->sin6_port = htons(port); return; default: throw internal_error("torrent::sa_set_port() invalid family type"); } } bool sa_equal(const sockaddr* lhs, const sockaddr* rhs) { switch(rhs->sa_family) { case AF_INET: case AF_INET6: case AF_UNSPEC: break; default: throw internal_error("torrent::sa_equal() rhs sockaddr is not a valid family"); } switch(lhs->sa_family) { case AF_INET: return lhs->sa_family == rhs->sa_family && sin_equal(reinterpret_cast(lhs), reinterpret_cast(rhs)); case AF_INET6: return lhs->sa_family == rhs->sa_family && sin6_equal(reinterpret_cast(lhs), reinterpret_cast(rhs)); case AF_UNSPEC: return lhs->sa_family == rhs->sa_family; default: throw internal_error("torrent::sa_equal() lhs sockaddr is not a valid family"); } } bool sin_equal(const sockaddr_in* lhs, const sockaddr_in* rhs) { return lhs->sin_port == rhs->sin_port && lhs->sin_addr.s_addr == rhs->sin_addr.s_addr; } bool sin6_equal(const sockaddr_in6* lhs, const sockaddr_in6* rhs) { return lhs->sin6_port == rhs->sin6_port && std::equal(lhs->sin6_addr.s6_addr, lhs->sin6_addr.s6_addr + 16, rhs->sin6_addr.s6_addr); } bool sa_equal_addr(const sockaddr* lhs, const sockaddr* rhs) { switch(rhs->sa_family) { case AF_INET: case AF_INET6: case AF_UNSPEC: break; default: throw internal_error("torrent::sa_equal_addr() rhs sockaddr is not a valid family"); } switch(lhs->sa_family) { case AF_INET: return lhs->sa_family == rhs->sa_family && sin_equal_addr(reinterpret_cast(lhs), reinterpret_cast(rhs)); case AF_INET6: return lhs->sa_family == rhs->sa_family && sin6_equal_addr(reinterpret_cast(lhs), reinterpret_cast(rhs)); case AF_UNSPEC: return lhs->sa_family == rhs->sa_family; default: throw internal_error("torrent::sa_equal_addr() lhs sockaddr is not a valid family"); } } bool sin_equal_addr(const sockaddr_in* lhs, const sockaddr_in* rhs) { return lhs->sin_addr.s_addr == rhs->sin_addr.s_addr; } bool sin6_equal_addr(const sockaddr_in6* lhs, const sockaddr_in6* rhs) { return std::equal(lhs->sin6_addr.s6_addr, lhs->sin6_addr.s6_addr + 16, rhs->sin6_addr.s6_addr); } bool sa_less(const sockaddr* lhs, const sockaddr* rhs) { if (lhs->sa_family != AF_INET && lhs->sa_family != AF_INET6) throw internal_error("torrent::sa_less() lhs sockaddr is not inet or inet6"); if (rhs->sa_family != AF_INET && rhs->sa_family != AF_INET6) throw internal_error("torrent::sa_less() rhs sockaddr is not inet or inet6"); if (lhs->sa_family != rhs->sa_family) return lhs->sa_family == AF_INET; if (lhs->sa_family == AF_INET) { const sockaddr_in* lsin = reinterpret_cast(lhs); const sockaddr_in* rsin = reinterpret_cast(rhs); if (lsin->sin_addr.s_addr != rsin->sin_addr.s_addr) return ntohl(lsin->sin_addr.s_addr) < ntohl(rsin->sin_addr.s_addr); return ntohs(lsin->sin_port) < ntohs(rsin->sin_port); } else { const sockaddr_in6* lsin6 = reinterpret_cast(lhs); const sockaddr_in6* rsin6 = reinterpret_cast(rhs); auto result = std::memcmp(&lsin6->sin6_addr, &rsin6->sin6_addr, sizeof(in6_addr)); if (result != 0) return result < 0; return ntohs(lsin6->sin6_port) < ntohs(rsin6->sin6_port); } } bool sa_less_addr(const sockaddr* lhs, const sockaddr* rhs) { if (lhs->sa_family != AF_INET && lhs->sa_family != AF_INET6) throw internal_error("torrent::sa_less_addr() lhs sockaddr is not inet or inet6"); if (rhs->sa_family != AF_INET && rhs->sa_family != AF_INET6) throw internal_error("torrent::sa_less_addr() rhs sockaddr is not inet or inet6"); if (lhs->sa_family != rhs->sa_family) return lhs->sa_family == AF_INET; if (lhs->sa_family == AF_INET) { const sockaddr_in* lsin = reinterpret_cast(lhs); const sockaddr_in* rsin = reinterpret_cast(rhs); return ntohl(lsin->sin_addr.s_addr) < ntohl(rsin->sin_addr.s_addr); } else { const sockaddr_in6* lsin6 = reinterpret_cast(lhs); const sockaddr_in6* rsin6 = reinterpret_cast(rhs); return std::memcmp(&lsin6->sin6_addr, &rsin6->sin6_addr, sizeof(in6_addr)) < 0; } } std::string sa_addr_str(const sockaddr* sa) { if (sa == nullptr) return ""; switch (sa->sa_family) { case AF_INET: return sin_addr_str(reinterpret_cast(sa)); case AF_INET6: return sin6_addr_str(reinterpret_cast(sa)); case AF_UNSPEC: return ""; default: throw internal_error("torrent::sa_addr_str() sockaddr is not a valid family"); } } std::string sin_addr_str(const sockaddr_in* sa) { char buffer[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, &sa->sin_addr, buffer, INET_ADDRSTRLEN) == nullptr) throw internal_error("torrent::sin_addr_str() inet_ntop failed"); return buffer; } std::string sin6_addr_str(const sockaddr_in6* sa) { char buffer[INET6_ADDRSTRLEN]; if (inet_ntop(AF_INET6, &sa->sin6_addr, buffer, INET6_ADDRSTRLEN) == nullptr) throw internal_error("torrent::sin6_addr_str() inet_ntop failed"); return buffer; } std::string sa_pretty_str(const sockaddr* sa) { if (sa == nullptr) return "null"; switch (sa->sa_family) { case AF_INET: return sin_pretty_str(reinterpret_cast(sa)); case AF_INET6: return sin6_pretty_str(reinterpret_cast(sa)); case AF_UNSPEC: return "unspec"; default: return "invalid"; } } std::string sin_pretty_str(const sockaddr_in* sa) { auto result = sin_addr_str(sa); if (sa->sin_port != 0) result += ':' + std::to_string(ntohs(sa->sin_port)); return result; } std::string sin_pretty_or_empty(const sockaddr_in* sa) { if (sa == nullptr) return ""; auto result = sin_addr_str(sa); if (sa->sin_port != 0) result += ':' + std::to_string(ntohs(sa->sin_port)); return result; } std::string sin6_pretty_str(const sockaddr_in6* sa) { auto result = "[" + sin6_addr_str(sa) + "]"; if (sa->sin6_port != 0) result += ':' + std::to_string(ntohs(sa->sin6_port)); return result; } std::string sin6_pretty_or_empty(const sockaddr_in6* sa) { if (sa == nullptr) return ""; auto result = "[" + sin6_addr_str(sa) + "]"; if (sa->sin6_port != 0) result += ':' + std::to_string(ntohs(sa->sin6_port)); return result; } // // Other types: // c_sa_shared_ptr sa_lookup_address(const std::string& address_str, int family) { if (address_str.empty()) return sa_make_unspec(); addrinfo hints = {}; hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; addrinfo* res; int err = ::getaddrinfo(address_str.c_str(), nullptr, &hints, &res); if (err != 0) throw input_error("Could not get address info: " + address_str + ": " + std::string(gai_strerror(err))); try { auto sa = sa_copy(res->ai_addr); ::freeaddrinfo(res); return sa; } catch (input_error& e) { ::freeaddrinfo(res); throw e; } } // Returns ptr if numeric of family, if numeric of other family return nullptr+false, else nullptr+true. std::tuple sa_lookup_numeric(const std::string& address_str, int family) { auto [sin, sin6] = try_lookup_numeric(address_str, AF_UNSPEC); switch (family) { case AF_UNSPEC: if (sin) return {sa_copy_in(sin.get()), true}; if (sin6) return {sa_copy_in6(sin6.get()), true}; return {nullptr, true}; case AF_INET: if (sin) return {sa_copy_in(sin.get()), true}; if (sin6) return {nullptr, false}; return {nullptr, true}; case AF_INET6: if (sin6) return {sa_copy_in6(sin6.get()), true}; if (sin) return {nullptr, false}; return {nullptr, true}; default: throw internal_error("torrent::sa_lookup_numeric() invalid family"); } } sin46_shared_pair try_lookup_numeric(const std::string& hostname, int family) { addrinfo hints{}; addrinfo* result{}; hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST; auto ret = ::getaddrinfo(hostname.c_str(), nullptr, &hints, &result); if (ret == EAI_NONAME || ret == EAI_ADDRFAMILY) return {nullptr, nullptr}; if (ret != 0) throw internal_error("getaddrinfo failed: " + std::string(gai_strerror(ret))); if (result->ai_family == AF_INET) { sin_shared_ptr sin_addr = sin_copy(reinterpret_cast(result->ai_addr)); ::freeaddrinfo(result); return {sin_addr, nullptr}; } if (result->ai_family == AF_INET6) { sin6_shared_ptr sin6_addr = sin6_copy(reinterpret_cast(result->ai_addr)); ::freeaddrinfo(result); return {nullptr, sin6_addr}; } ::freeaddrinfo(result); throw internal_error("getaddrinfo returned unsupported family"); } sa_inet_union sa_inet_union_from_sa(const sockaddr* sa) { sa_inet_union su{}; switch (sa->sa_family) { case AF_INET: su.inet = *reinterpret_cast(sa); return su; case AF_INET6: su.inet6 = *reinterpret_cast(sa); return su; default: throw internal_error("torrent::sa_inet_union_from_sa() sockaddr is not inet or inet6"); } } void sa_copy_to_inet_union(const sockaddr* sa, sa_inet_union& u) { switch (sa->sa_family) { case AF_UNSPEC: u.sa.sa_family = AF_UNSPEC; return; case AF_INET: u.inet = *reinterpret_cast(sa); return; case AF_INET6: u.inet6 = *reinterpret_cast(sa); return; default: throw internal_error("torrent::sa_copy_to_inet_union() sockaddr is not unspec, inet or inet6"); } } const char* family_str(int family) { switch (family) { case AF_UNSPEC: return "AF_UNSPEC"; case AF_INET: return "AF_INET"; case AF_INET6: return "AF_INET6"; case AF_UNIX: return "AF_UNIX"; default: return "AF_UNKNOWN"; } } } // namespace torrent libtorrent-0.16.17/src/torrent/net/socket_address.h000066400000000000000000000403701522271512000222730ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_SOCKET_ADDRESS_H #define LIBTORRENT_NET_SOCKET_ADDRESS_H #include #include #include namespace torrent { bool sa_is_unspec(const sockaddr* sa) LIBTORRENT_EXPORT; bool sa_is_inet(const sockaddr* sa) LIBTORRENT_EXPORT; bool sa_is_inet6(const sockaddr* sa) LIBTORRENT_EXPORT; bool sa_is_inet_inet6(const sockaddr* sa) LIBTORRENT_EXPORT; bool sa_is_any(const sockaddr* sa) LIBTORRENT_EXPORT; bool sin_is_any(const sockaddr_in* sa) LIBTORRENT_EXPORT; bool sin6_is_any(const sockaddr_in6* sa) LIBTORRENT_EXPORT; bool sa_is_broadcast(const sockaddr* sa) LIBTORRENT_EXPORT; bool sin_is_broadcast(const sockaddr_in* sa) LIBTORRENT_EXPORT; bool sa_is_loopback(const sockaddr* sa) LIBTORRENT_EXPORT; bool sin_is_loopback(const sockaddr_in* sa) LIBTORRENT_EXPORT; bool sin6_is_loopback(const sockaddr_in6* sa) LIBTORRENT_EXPORT; bool sa_is_v4mapped(const sockaddr* sa) LIBTORRENT_EXPORT; bool sin6_is_v4mapped(const sockaddr_in6* sa) LIBTORRENT_EXPORT; bool sa_is_port_any(const sockaddr* sa) LIBTORRENT_EXPORT; size_t sa_length(const sockaddr* sa) LIBTORRENT_EXPORT; sa_unique_ptr sa_make_unspec() LIBTORRENT_EXPORT; sa_unique_ptr sa_make_inet() LIBTORRENT_EXPORT; sa_unique_ptr sa_make_inet_any() LIBTORRENT_EXPORT; sa_unique_ptr sa_make_inet_h(uint32_t addr, uint16_t port) LIBTORRENT_EXPORT; sa_unique_ptr sa_make_inet_n(uint32_t addr, uint16_t port) LIBTORRENT_EXPORT; sa_unique_ptr sa_make_inet6() LIBTORRENT_EXPORT; sa_unique_ptr sa_make_inet6_any() LIBTORRENT_EXPORT; sa_unique_ptr sa_make_unix(const std::string& pathname) LIBTORRENT_EXPORT; sa_unique_ptr sa_convert(const sockaddr* sa) LIBTORRENT_EXPORT; sa_unique_ptr sa_copy(const sockaddr* sa) LIBTORRENT_EXPORT; sa_unique_ptr sa_copy_in(const sockaddr_in* sa) LIBTORRENT_EXPORT; sa_unique_ptr sa_copy_in6(const sockaddr_in6* sa) LIBTORRENT_EXPORT; sa_unique_ptr sa_copy_addr(const sockaddr* sa, uint16_t port = 0) LIBTORRENT_EXPORT; sa_unique_ptr sa_copy_addr_in(const sockaddr_in* sa, uint16_t port = 0) LIBTORRENT_EXPORT; sa_unique_ptr sa_copy_addr_in6(const sockaddr_in6* sa, uint16_t port = 0) LIBTORRENT_EXPORT; sa_unique_ptr sa_copy_unmapped(const sockaddr* sa) LIBTORRENT_EXPORT; sin_unique_ptr sin_copy(const sockaddr_in* sa) LIBTORRENT_EXPORT; sin6_unique_ptr sin6_copy(const sockaddr_in6* sa) LIBTORRENT_EXPORT; sin_unique_ptr sin_make() LIBTORRENT_EXPORT; sin_unique_ptr sin_make_any() LIBTORRENT_EXPORT; sin_unique_ptr sin_make_h(uint32_t addr, uint16_t port) LIBTORRENT_EXPORT; sin_unique_ptr sin_make_n(uint32_t addr, uint16_t port) LIBTORRENT_EXPORT; sin6_unique_ptr sin6_make() LIBTORRENT_EXPORT; sin6_unique_ptr sin6_make_any() LIBTORRENT_EXPORT; sa_unique_ptr sa_from_v4mapped(const sockaddr* sa) LIBTORRENT_EXPORT; sa_unique_ptr sa_to_v4mapped(const sockaddr* sa) LIBTORRENT_EXPORT; sa_unique_ptr sa_from_v4mapped_in6(const sockaddr_in6* sin6) LIBTORRENT_EXPORT; sa_unique_ptr sa_to_v4mapped_in(const sockaddr_in* sin) LIBTORRENT_EXPORT; sin_unique_ptr sin_from_v4mapped_in6(const sockaddr_in6* sin6) LIBTORRENT_EXPORT; sin6_unique_ptr sin6_to_v4mapped_in(const sockaddr_in* sin) LIBTORRENT_EXPORT; sa_unique_ptr sa_from_in(sin_unique_ptr sinp) LIBTORRENT_EXPORT; c_sa_unique_ptr sa_from_in(c_sin_unique_ptr sinp) LIBTORRENT_EXPORT; sa_unique_ptr sa_from_in6(sin6_unique_ptr sin6p) LIBTORRENT_EXPORT; c_sa_unique_ptr sa_from_in6(c_sin6_unique_ptr sin6p) LIBTORRENT_EXPORT; sin_unique_ptr sin_from_sa(sa_unique_ptr sap) LIBTORRENT_EXPORT; sin6_unique_ptr sin6_from_sa(sa_unique_ptr sap) LIBTORRENT_EXPORT; c_sin_unique_ptr sin_from_c_sa(c_sa_unique_ptr sap) LIBTORRENT_EXPORT; c_sin6_unique_ptr sin6_from_c_sa(c_sa_unique_ptr sap) LIBTORRENT_EXPORT; void sa_clear_inet6(sockaddr_in6* sa) LIBTORRENT_EXPORT; uint16_t sa_port(const sockaddr* sa) LIBTORRENT_EXPORT; void sa_set_port(sockaddr* sa, uint16_t port) LIBTORRENT_EXPORT; bool sa_equal(const sockaddr* lhs, const sockaddr* rhs) LIBTORRENT_EXPORT; bool sin_equal(const sockaddr_in* lhs, const sockaddr_in* rhs) LIBTORRENT_EXPORT; bool sin6_equal(const sockaddr_in6* lhs, const sockaddr_in6* rhs) LIBTORRENT_EXPORT; bool sa_equal_addr(const sockaddr* lhs, const sockaddr* rhs) LIBTORRENT_EXPORT; bool sin_equal_addr(const sockaddr_in* lhs, const sockaddr_in* rhs) LIBTORRENT_EXPORT; bool sin6_equal_addr(const sockaddr_in6* lhs, const sockaddr_in6* rhs) LIBTORRENT_EXPORT; bool sa_less(const sockaddr* lhs, const sockaddr* rhs) LIBTORRENT_EXPORT; bool sa_less_addr(const sockaddr* lhs, const sockaddr* rhs) LIBTORRENT_EXPORT; std::string sa_addr_str(const sockaddr* sa) LIBTORRENT_EXPORT; std::string sin_addr_str(const sockaddr_in* sa) LIBTORRENT_EXPORT; std::string sin6_addr_str(const sockaddr_in6* sa) LIBTORRENT_EXPORT; std::string sa_pretty_str(const sockaddr* sa) LIBTORRENT_EXPORT; std::string sin_pretty_str(const sockaddr_in* sa) LIBTORRENT_EXPORT; std::string sin_pretty_or_empty(const sockaddr_in* sa) LIBTORRENT_EXPORT; std::string sin6_pretty_str(const sockaddr_in6* sa) LIBTORRENT_EXPORT; std::string sin6_pretty_or_empty(const sockaddr_in6* sa) LIBTORRENT_EXPORT; // // Other types: // sa_inet_union sa_inet_union_from_sa(const sockaddr* sa) LIBTORRENT_EXPORT; void sa_copy_to_inet_union(const sockaddr* sa, sa_inet_union& u) LIBTORRENT_EXPORT; bool fd_sap_equal(const fd_sap_tuple& lhs, const fd_sap_tuple& rhs) LIBTORRENT_EXPORT; // // Safe conversion from unique_ptr arguments: // inline bool sap_is_unspec(const sa_unique_ptr& sap) { return sa_is_unspec(sap.get()); } inline bool sap_is_unspec(const c_sa_unique_ptr& sap) { return sa_is_unspec(sap.get()); } inline bool sap_is_inet(const sa_unique_ptr& sap) { return sa_is_inet(sap.get()); } inline bool sap_is_inet(const c_sa_unique_ptr& sap) { return sa_is_inet(sap.get()); } inline bool sap_is_inet6(const sa_unique_ptr& sap) { return sa_is_inet6(sap.get()); } inline bool sap_is_inet6(const c_sa_unique_ptr& sap) { return sa_is_inet6(sap.get()); } inline bool sap_is_inet_inet6(const sa_unique_ptr& sap) { return sa_is_inet_inet6(sap.get()); } inline bool sap_is_inet_inet6(const c_sa_unique_ptr& sap) { return sa_is_inet_inet6(sap.get()); } inline bool sap_is_any(const sa_unique_ptr& sap) { return sa_is_any(sap.get()); } inline bool sap_is_any(const c_sa_unique_ptr& sap) { return sa_is_any(sap.get()); } inline bool sinp_is_any(const sin_unique_ptr& sinp) { return sin_is_any(sinp.get()); } inline bool sinp_is_any(const c_sin_unique_ptr& sinp) { return sin_is_any(sinp.get()); } inline bool sinp6_is_any(const sin6_unique_ptr& sin6p) { return sin6_is_any(sin6p.get()); } inline bool sinp6_is_any(const c_sin6_unique_ptr& sin6p) { return sin6_is_any(sin6p.get()); } inline bool sap_is_broadcast(const sa_unique_ptr& sap) { return sa_is_broadcast(sap.get()); } inline bool sap_is_broadcast(const c_sa_unique_ptr& sap) { return sa_is_broadcast(sap.get()); } inline bool sinp_is_broadcast(const sin_unique_ptr& sap) { return sin_is_broadcast(sap.get()); } inline bool sinp_is_broadcast(const c_sin_unique_ptr& sap) { return sin_is_broadcast(sap.get()); } inline bool sap_is_loopback(const sa_unique_ptr& sap) { return sa_is_loopback(sap.get()); } inline bool sap_is_loopback(const c_sa_unique_ptr& sap) { return sa_is_loopback(sap.get()); } inline bool sinp_is_loopback(const sin_unique_ptr& sinp) { return sin_is_loopback(sinp.get()); } inline bool sinp_is_loopback(const c_sin_unique_ptr& sinp) { return sin_is_loopback(sinp.get()); } inline bool sinp6_is_loopback(const sin6_unique_ptr& sin6p) { return sin6_is_loopback(sin6p.get()); } inline bool sinp6_is_loopback(const c_sin6_unique_ptr& sin6p) { return sin6_is_loopback(sin6p.get()); } inline bool sap_is_v4mapped(const sa_unique_ptr& sap) { return sa_is_v4mapped(sap.get()); } inline bool sap_is_v4mapped(const c_sa_unique_ptr& sap) { return sa_is_v4mapped(sap.get()); } inline bool sinp6_is_v4mapped(const sin6_unique_ptr& sin6p) { return sin6_is_v4mapped(sin6p.get()); } inline bool sinp6_is_v4mapped(const c_sin6_unique_ptr& sin6p) { return sin6_is_v4mapped(sin6p.get()); } inline bool sap_is_port_any(const sa_unique_ptr& sap) { return sa_is_port_any(sap.get()); } inline bool sap_is_port_any(const c_sa_unique_ptr& sap) { return sa_is_port_any(sap.get()); } inline size_t sap_length(const sa_unique_ptr& sap) { return sa_length(sap.get()); } inline size_t sap_length(const c_sa_unique_ptr& sap) { return sa_length(sap.get()); } inline sa_unique_ptr sap_copy(const sa_unique_ptr& sap) { return sa_copy(sap.get()); } inline sa_unique_ptr sap_copy(const c_sa_unique_ptr& sap) { return sa_copy(sap.get()); } inline sa_unique_ptr sap_copy_addr(const sa_unique_ptr& sap, uint16_t port = 0) { return sa_copy_addr(sap.get(), port); } inline sa_unique_ptr sap_copy_addr(const c_sa_unique_ptr& sap, uint16_t port = 0) { return sa_copy_addr(sap.get(), port); } inline sa_unique_ptr sap_copy_in(const sin_unique_ptr& sinp) { return sa_copy_in(sinp.get()); } inline sa_unique_ptr sap_copy_in(const c_sin_unique_ptr& sinp) { return sa_copy_in(sinp.get()); } inline sa_unique_ptr sap_copy_in6(const sin6_unique_ptr& sin6p) { return sa_copy_in6(sin6p.get()); } inline sa_unique_ptr sap_copy_in6(const c_sin6_unique_ptr& sin6p) { return sa_copy_in6(sin6p.get()); } inline sin_unique_ptr sinp_copy(const sin_unique_ptr& sinp) { return sin_copy(sinp.get()); } inline sin_unique_ptr sinp_copy(const c_sin_unique_ptr& sinp) { return sin_copy(sinp.get()); } inline sin6_unique_ptr sin6p_copy(const sin6_unique_ptr& sin6p) { return sin6_copy(sin6p.get()); } inline sin6_unique_ptr sin6p_copy(const c_sin6_unique_ptr& sin6p) { return sin6_copy(sin6p.get()); } inline sa_unique_ptr sap_from_v4mapped(const sa_unique_ptr& sap) { return sa_from_v4mapped(sap.get()); } inline sa_unique_ptr sap_from_v4mapped(const c_sa_unique_ptr& sap) { return sa_from_v4mapped(sap.get()); } inline sa_unique_ptr sap_to_v4mapped(const sa_unique_ptr& sap) { return sa_to_v4mapped(sap.get()); } inline sa_unique_ptr sap_to_v4mapped(const c_sa_unique_ptr& sap) { return sa_to_v4mapped(sap.get()); } inline sin_unique_ptr sinp_from_v4mapped_in6(const sin6_unique_ptr& sin6p) { return sin_from_v4mapped_in6(sin6p.get()); } inline sin_unique_ptr sinp_from_v4mapped_in6(const c_sin6_unique_ptr& sin6p) { return sin_from_v4mapped_in6(sin6p.get()); } inline sin6_unique_ptr sin6p_to_v4mapped_in(const sin_unique_ptr& sinp) { return sin6_to_v4mapped_in(sinp.get()); } inline sin6_unique_ptr sin6p_to_v4mapped_in(const c_sin_unique_ptr& sinp) { return sin6_to_v4mapped_in(sinp.get()); } inline uint16_t sap_port(const sa_unique_ptr& sap) { return sa_port(sap.get()); } inline uint16_t sap_port(const c_sa_unique_ptr& sap) { return sa_port(sap.get()); } inline void sap_set_port(const sa_unique_ptr& sap, uint16_t port) { sa_set_port(sap.get(), port); } inline bool sap_equal(const sa_unique_ptr& lhs, const sa_unique_ptr& rhs) { return sa_equal(lhs.get(), rhs.get()); } inline bool sap_equal(const sa_unique_ptr& lhs, const c_sa_unique_ptr& rhs) { return sa_equal(lhs.get(), rhs.get()); } inline bool sap_equal(const c_sa_unique_ptr& lhs, const sa_unique_ptr& rhs) { return sa_equal(lhs.get(), rhs.get()); } inline bool sap_equal(const c_sa_unique_ptr& lhs, const c_sa_unique_ptr& rhs) { return sa_equal(lhs.get(), rhs.get()); } inline bool sinp_equal(const sin_unique_ptr& lhs, const sin_unique_ptr& rhs) { return sin_equal(lhs.get(), rhs.get()); } inline bool sinp_equal(const sin_unique_ptr& lhs, const c_sin_unique_ptr& rhs) { return sin_equal(lhs.get(), rhs.get()); } inline bool sinp_equal(const c_sin_unique_ptr& lhs, const sin_unique_ptr& rhs) { return sin_equal(lhs.get(), rhs.get()); } inline bool sinp_equal(const c_sin_unique_ptr& lhs, const c_sin_unique_ptr& rhs) { return sin_equal(lhs.get(), rhs.get()); } inline bool sin6p_equal(const sin6_unique_ptr& lhs, const sin6_unique_ptr& rhs) { return sin6_equal(lhs.get(), rhs.get()); } inline bool sin6p_equal(const sin6_unique_ptr& lhs, const c_sin6_unique_ptr& rhs) { return sin6_equal(lhs.get(), rhs.get()); } inline bool sin6p_equal(const c_sin6_unique_ptr& lhs, const sin6_unique_ptr& rhs) { return sin6_equal(lhs.get(), rhs.get()); } inline bool sin6p_equal(const c_sin6_unique_ptr& lhs, const c_sin6_unique_ptr& rhs) { return sin6_equal(lhs.get(), rhs.get()); } inline bool sap_equal_addr(const sa_unique_ptr& lhs, const sa_unique_ptr& rhs) { return sa_equal_addr(lhs.get(), rhs.get()); } inline bool sap_equal_addr(const sa_unique_ptr& lhs, const c_sa_unique_ptr& rhs) { return sa_equal_addr(lhs.get(), rhs.get()); } inline bool sap_equal_addr(const c_sa_unique_ptr& lhs, const sa_unique_ptr& rhs) { return sa_equal_addr(lhs.get(), rhs.get()); } inline bool sap_equal_addr(const c_sa_unique_ptr& lhs, const c_sa_unique_ptr& rhs) { return sa_equal_addr(lhs.get(), rhs.get()); } inline bool sinp_equal_addr(const sin_unique_ptr& lhs, const sin_unique_ptr& rhs) { return sin_equal_addr(lhs.get(), rhs.get()); } inline bool sinp_equal_addr(const sin_unique_ptr& lhs, const c_sin_unique_ptr& rhs) { return sin_equal_addr(lhs.get(), rhs.get()); } inline bool sinp_equal_addr(const c_sin_unique_ptr& lhs, const sin_unique_ptr& rhs) { return sin_equal_addr(lhs.get(), rhs.get()); } inline bool sinp_equal_addr(const c_sin_unique_ptr& lhs, const c_sin_unique_ptr& rhs) { return sin_equal_addr(lhs.get(), rhs.get()); } inline bool sin6p_equal_addr(const sin6_unique_ptr& lhs, const sin6_unique_ptr& rhs) { return sin6_equal_addr(lhs.get(), rhs.get()); } inline bool sin6p_equal_addr(const sin6_unique_ptr& lhs, const c_sin6_unique_ptr& rhs) { return sin6_equal_addr(lhs.get(), rhs.get()); } inline bool sin6p_equal_addr(const c_sin6_unique_ptr& lhs, const sin6_unique_ptr& rhs) { return sin6_equal_addr(lhs.get(), rhs.get()); } inline bool sin6p_equal_addr(const c_sin6_unique_ptr& lhs, const c_sin6_unique_ptr& rhs) { return sin6_equal_addr(lhs.get(), rhs.get()); } inline std::string sap_addr_str(const sa_unique_ptr& sap) { return sa_addr_str(sap.get()); } inline std::string sap_addr_str(const c_sa_unique_ptr& sap) { return sa_addr_str(sap.get()); } inline std::string sap_pretty_str(const sa_unique_ptr& sap) { return sa_pretty_str(sap.get()); } inline std::string sap_pretty_str(const c_sa_unique_ptr& sap) { return sa_pretty_str(sap.get()); } inline std::string sinp_pretty_str(const sin_unique_ptr& sinp) { return sin_pretty_str(sinp.get()); } inline std::string sinp_pretty_str(const c_sin_unique_ptr& sinp) { return sin_pretty_str(sinp.get()); } inline std::string sin6p_pretty_str(const sin6_unique_ptr& sin6p) { return sin6_pretty_str(sin6p.get()); } inline std::string sin6p_pretty_str(const c_sin6_unique_ptr& sin6p) { return sin6_pretty_str(sin6p.get()); } inline sa_inet_union sa_inet_union_from_sap(const sa_unique_ptr& sap) { return sa_inet_union_from_sa(sap.get()); } // // Implementations: // inline sa_unique_ptr sa_from_v4mapped_in6(const sockaddr_in6* sin6) { return sa_from_in(sin_from_v4mapped_in6(sin6)); } inline sa_unique_ptr sa_to_v4mapped_in(const sockaddr_in* sin) { return sa_from_in6(sin6_to_v4mapped_in(sin)); } inline sa_unique_ptr sa_from_in(sin_unique_ptr sinp) { return sa_unique_ptr(reinterpret_cast(sinp.release())); } inline c_sa_unique_ptr sa_from_in(c_sin_unique_ptr sinp) { return c_sa_unique_ptr(reinterpret_cast(sinp.release())); } inline sa_unique_ptr sa_from_in6(sin6_unique_ptr sin6p) { return sa_unique_ptr(reinterpret_cast(sin6p.release())); } inline c_sa_unique_ptr sa_from_in6(c_sin6_unique_ptr sin6p) { return c_sa_unique_ptr(reinterpret_cast(sin6p.release())); } inline bool fd_sap_equal(const fd_sap_tuple& lhs, const fd_sap_tuple& rhs) { return std::get<0>(lhs) == std::get<0>(rhs) && sap_equal(std::get<1>(lhs), std::get<1>(rhs)); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/net/socket_address_key.cc000066400000000000000000000001271522271512000232750ustar00rootroot00000000000000// Copyright (C) 2005-2014, Jari Sundell // All rights reserved. #include "config.h" libtorrent-0.16.17/src/torrent/net/socket_address_key.h000066400000000000000000000056101522271512000231410ustar00rootroot00000000000000// Copyright (C) 2005-2014, Jari Sundell // All rights reserved. #ifndef LIBTORRENT_UTILS_SOCKET_ADDRESS_KEY_H #define LIBTORRENT_UTILS_SOCKET_ADDRESS_KEY_H #include #include #include #include // Unique key for the socket address, excluding port numbers, etc. // TODO: Add include files... namespace torrent { class [[gnu::packed]] socket_address_key { public: // TODO: Disable default ctor? // socket_address_key(const sockaddr* sa) : m_sockaddr(sa) {} bool is_valid() const { return m_family != AF_UNSPEC; } // // Rename, add same family, valid inet4/6. // TODO: Make from_sockaddr an rvalue reference. static bool is_comparable_sockaddr(const sockaddr* sa); static socket_address_key from_sockaddr(const sockaddr* sa); static socket_address_key from_sin_addr(const sockaddr_in& sa); static socket_address_key from_sin6_addr(const sockaddr_in6& sa); bool operator < (const socket_address_key& sa) const; bool operator > (const socket_address_key& sa) const; bool operator == (const socket_address_key& sa) const; private: sa_family_t m_family; union { in_addr m_addr; in6_addr m_addr6; }; }; inline bool socket_address_key::is_comparable_sockaddr(const sockaddr* sa) { return sa != NULL && (sa->sa_family == AF_INET || sa->sa_family == AF_INET6); } // TODO: Require socket length? inline socket_address_key socket_address_key::from_sockaddr(const sockaddr* sa) { socket_address_key result{}; result.m_family = AF_UNSPEC; if (sa == NULL) return result; switch (sa->sa_family) { case AF_INET: // Using hardware order to allo for the use of operator < to // sort in lexical order. result.m_family = AF_INET; result.m_addr.s_addr = ntohl(reinterpret_cast(sa)->sin_addr.s_addr); break; case AF_INET6: result.m_family = AF_INET6; result.m_addr6 = reinterpret_cast(sa)->sin6_addr; break; default: break; } return result; } inline socket_address_key socket_address_key::from_sin_addr(const sockaddr_in& sa) { socket_address_key result{}; result.m_family = AF_INET; result.m_addr.s_addr = ntohl(sa.sin_addr.s_addr); return result; } inline socket_address_key socket_address_key::from_sin6_addr(const sockaddr_in6& sa) { socket_address_key result{}; result.m_family = AF_INET6; result.m_addr6 = sa.sin6_addr; return result; } inline bool socket_address_key::operator < (const socket_address_key& sa) const { return std::memcmp(this, &sa, sizeof(socket_address_key)) < 0; } inline bool socket_address_key::operator > (const socket_address_key& sa) const { return std::memcmp(this, &sa, sizeof(socket_address_key)) > 0; } inline bool socket_address_key::operator == (const socket_address_key& sa) const { return std::memcmp(this, &sa, sizeof(socket_address_key)) == 0; } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/net/types.h000066400000000000000000000065111522271512000204410ustar00rootroot00000000000000#ifndef LIBTORRENT_NET_TYPES_H #define LIBTORRENT_NET_TYPES_H #include #include #include #include #include #include struct sockaddr_in; struct sockaddr_in6; struct sockaddr_un; namespace torrent { namespace net { void sa_free(const sockaddr* sa) LIBTORRENT_EXPORT; struct sockaddr_deleter { constexpr sockaddr_deleter() noexcept = default; void operator()(const sockaddr* sa) const { net::sa_free(sa); } }; } // namespace torrent::net using sa_unique_ptr = std::unique_ptr; using sin_unique_ptr = std::unique_ptr; using sin6_unique_ptr = std::unique_ptr; using sun_unique_ptr = std::unique_ptr; using c_sa_unique_ptr = std::unique_ptr; using c_sin_unique_ptr = std::unique_ptr; using c_sin6_unique_ptr = std::unique_ptr; using c_sun_unique_ptr = std::unique_ptr; // Shared pointer objects of sockaddr type must use sa_free as deleter. using sa_shared_ptr = std::shared_ptr; using sin_shared_ptr = std::shared_ptr; using sin6_shared_ptr = std::shared_ptr; using sun_shared_ptr = std::shared_ptr; using c_sa_shared_ptr = std::shared_ptr; using c_sin_shared_ptr = std::shared_ptr; using c_sin6_shared_ptr = std::shared_ptr; using c_sun_shared_ptr = std::shared_ptr; using fd_sap_tuple = std::tuple; using sin46_shared_pair = std::pair; using resolver_callback = std::function; struct listen_result_type { int fd; sa_unique_ptr address; }; union sa_inet_union { // Ensure the largest member is first as it used for default list-initialization. sockaddr_in6 inet6; sockaddr_in inet; sockaddr sa; }; namespace net { namespace proxy { class Proxy; } // namespace torrent::net::proxy using proxy_ptr = std::unique_ptr; } // namespace torrent::net // // Helper functions: // // TODO: Move to a separate header file. // TODO: Rename sa_lookup_address. c_sa_shared_ptr sa_lookup_address(const std::string& address_str, int family) LIBTORRENT_EXPORT; std::tuple sa_lookup_numeric(const std::string& address_str, int family) LIBTORRENT_EXPORT; sin46_shared_pair try_lookup_numeric(const std::string& hostname, int family) LIBTORRENT_EXPORT; // TODO: Rename to family_enum and add family_enum_str. const char* family_str(int family) LIBTORRENT_EXPORT; inline std::string family_enum_str(int family) { return family_str(family); } namespace net { bool verify_url_guess_scheme(const std::string& url) LIBTORRENT_EXPORT; bool verify_no_path_query_fragment(const std::string& url) LIBTORRENT_EXPORT; std::string parse_uri_scheme(const std::string& url) LIBTORRENT_EXPORT; std::pair parse_uri_host_port(const std::string& uri) LIBTORRENT_EXPORT; std::pair parse_uri_user_password(const std::string& uri) LIBTORRENT_EXPORT; } // namespace net } // namespace torrent #endif libtorrent-0.16.17/src/torrent/object.cc000066400000000000000000000132031522271512000201070ustar00rootroot00000000000000#include "config.h" #include #include "object.h" #include "object_stream.h" namespace torrent { Object& Object::get_key(const std::string& k) { check_throw(TYPE_MAP); auto itr = _map().find(k); if (itr == _map().end()) throw bencode_error("Object operator [" + k + "] could not find element"); return itr->second; } const Object& Object::get_key(const std::string& k) const { check_throw(TYPE_MAP); auto itr = _map().find(k); if (itr == _map().end()) throw bencode_error("Object operator [" + k + "] could not find element"); return itr->second; } Object& Object::get_key(const char* k) { check_throw(TYPE_MAP); auto itr = _map().find(std::string(k)); if (itr == _map().end()) throw bencode_error("Object operator [" + std::string(k) + "] could not find element"); return itr->second; } const Object& Object::get_key(const char* k) const { check_throw(TYPE_MAP); auto itr = _map().find(std::string(k)); if (itr == _map().end()) throw bencode_error("Object operator [" + std::string(k) + "] could not find element"); return itr->second; } Object::map_insert_type Object::insert_preserve_type(const key_type& k, Object& b) { check_throw(TYPE_MAP); map_insert_type result = _map().insert(map_type::value_type(k, b)); if (!result.second && result.first->second.type() != b.type()) { result.first->second.move(b); result.second = true; } return result; } Object& Object::move(Object& src) noexcept { if (this == &src) return *this; *this = create_empty(src.type()); swap_same_type(*this, src); return *this; } Object& Object::swap(Object& src) noexcept { if (this == &src) return *this; if (type() != src.type()) { torrent::Object tmp = create_empty(src.type()); swap_same_type(tmp, src); src = create_empty(this->type()); swap_same_type(src, *this); *this = create_empty(tmp.type()); swap_same_type(*this, tmp); } else { swap_same_type(*this, src); } return *this; } Object& Object::merge_copy(const Object& object, uint32_t skip_mask, uint32_t maxDepth) { if (maxDepth == 0 || m_flags & skip_mask) return (*this = object); if (object.is_map()) { if (!is_map()) *this = create_map(); map_type& dest = as_map(); auto destItr = dest.begin(); for (const auto& map : object.as_map()) { destItr = std::find_if(destItr, dest.end(), [&map](const auto& v) { return map.first <= v.first; }); if (map.first < destItr->first) // destItr remains valid and pointing to the next possible // position. dest.insert(destItr, map); else destItr->second.merge_copy(map.second, maxDepth - 1); } // } else if (object.is_list()) { // if (!is_list()) // *this = create_list(); // list_type& dest = as_list(); // list_type::iterator destItr = dest.begin(); // list_type::const_iterator srcItr = object.as_list().begin(); // list_type::const_iterator srcLast = object.as_list().end(); // while (srcItr != srcLast) { // if (destItr == dest.end()) // destItr = dest.insert(destItr, *srcItr); // else // destItr->merge_copy(*srcItr, maxDepth - 1); // destItr++; // } } else { *this = object; } return *this; } Object& Object::operator = (const Object& src) { if (&src == this) return *this; clear(); // Need some more magic here? m_flags = src.m_flags & (mask_type | mask_public); switch (type()) { case TYPE_STRING: new (&_string()) string_type(src._string()); break; case TYPE_LIST: new (&_list()) list_type(src._list()); break; case TYPE_MAP: _map_ptr() = new map_type(src._map()); break; case TYPE_DICT_KEY: new (&_dict_key()) dict_key_type(src._dict_key()); _dict_key().second = new Object(*src._dict_key().second); break; default: t_pod = src.t_pod; break; } return *this; } Object object_create_normal(const raw_bencode& obj) { torrent::Object result; if (object_read_bencode_c(obj.begin(), obj.end(), &result, 128) != obj.end()) throw bencode_error("Invalid bencode data."); return result; } Object object_create_normal(const raw_list& obj) { torrent::Object result = Object::create_list(); raw_list::iterator first = obj.begin(); raw_list::iterator last = obj.end(); while (first != last) { auto& new_entry = result.as_list().emplace_back(); first = object_read_bencode_c(first, last, &new_entry, 128); // The unordered flag is inherited also from list elements who // have been marked as unordered, though e.g. unordered strings // in the list itself does not cause this flag to be set. if (new_entry.flags() & Object::flag_unordered) result.set_internal_flags(Object::flag_unordered); } return result; } Object object_create_normal(const raw_map& obj) { torrent::Object result = Object::create_map(); raw_list::iterator first = obj.begin(); raw_list::iterator last = obj.end(); Object::string_type prev; while (first != last) { raw_string raw_str = object_read_bencode_c_string(first, last); first = raw_str.end(); Object::string_type key_str = raw_str.as_string(); // We do not set flag_unordered if the first key was zero // length, while multiple zero length keys will trigger the // unordered_flag. if (key_str <= prev && !result.as_map().empty()) result.set_internal_flags(Object::flag_unordered); Object* value = &result.as_map()[key_str]; first = object_read_bencode_c(first, last, value, 128); if (value->flags() & Object::flag_unordered) result.set_internal_flags(Object::flag_unordered); key_str.swap(prev); } return result; } } // namespace torrent libtorrent-0.16.17/src/torrent/object.h000066400000000000000000000575741522271512000177740ustar00rootroot00000000000000#ifndef LIBTORRENT_OBJECT_H #define LIBTORRENT_OBJECT_H #include #include #include #include #include #include #include namespace torrent { class LIBTORRENT_EXPORT Object { public: using value_type = int64_t; using string_type = std::string; using list_type = std::vector; using map_type = std::map; using map_ptr_type = map_type*; using key_type = map_type::key_type; using dict_key_type = std::pair; using list_iterator = list_type::iterator; using list_const_iterator = list_type::const_iterator; using list_reverse_iterator = list_type::reverse_iterator; using list_const_reverse_iterator = list_type::const_reverse_iterator; using map_iterator = map_type::iterator; using map_const_iterator = map_type::const_iterator; using map_reverse_iterator = map_type::reverse_iterator; using map_const_reverse_iterator = map_type::const_reverse_iterator; using map_insert_type = std::pair; // Flags in the range of 0xffff0000 may be set by the user, however // 0x00ff0000 are reserved for keywords defined by libtorrent. static constexpr uint32_t mask_type = 0xff; static constexpr uint32_t mask_flags = ~mask_type; static constexpr uint32_t mask_internal = 0xffff; static constexpr uint32_t mask_public = ~mask_internal; static constexpr uint32_t flag_unordered = 0x100; // bencode dictionary was not sorted static constexpr uint32_t flag_static_data = 0x010000; // Object does not change across sessions. static constexpr uint32_t flag_session_data = 0x020000; // Object changed between sessions static constexpr uint32_t flag_function = 0x040000; // A function object static constexpr uint32_t flag_function_q1 = 0x080000; // A quoted function object static constexpr uint32_t flag_function_q2 = 0x100000; // A double-quoted function object static constexpr uint32_t flag_hex = 0x200000; // Hex-encoded string static constexpr uint32_t flag_base64 = 0x400000; // Base64-encoded string static constexpr uint32_t flag_as_binary = 0x800000; // Treat as binary data static constexpr uint32_t mask_function = 0x1C0000; // Mask for function objects. enum type_type { TYPE_NONE, TYPE_RAW_BENCODE, TYPE_RAW_STRING, TYPE_RAW_LIST, TYPE_RAW_MAP, TYPE_VALUE, TYPE_STRING, TYPE_LIST, TYPE_MAP, TYPE_DICT_KEY }; Object(const value_type v) : m_flags(TYPE_VALUE) { new (&_value()) value_type(v); } Object(const char* s) : m_flags(TYPE_STRING) { new (&_string()) string_type(s); } Object(const string_type& s) : m_flags(TYPE_STRING) { new (&_string()) string_type(s); } Object(const raw_bencode& r) : m_flags(TYPE_RAW_BENCODE) { new (&_raw_bencode()) raw_bencode(r); } Object(const raw_string& r) : m_flags(TYPE_RAW_STRING) { new (&_raw_string()) raw_string(r); } Object(const raw_list& r) : m_flags(TYPE_RAW_LIST) { new (&_raw_list()) raw_list(r); } Object(const raw_map& r) : m_flags(TYPE_RAW_MAP) { new (&_raw_map()) raw_map(r); } Object() {} ~Object() { clear(); } Object(const Object& b); Object& operator=(const Object& b); // TODO: Move this out of the class namespace, call them // make_object_. static Object create_empty(type_type t); static Object create_value() { return Object(value_type()); } static Object create_string() { return Object(string_type()); } static Object create_list() { Object tmp; tmp.m_flags = TYPE_LIST; new (&tmp._list()) list_type(); return tmp; } static Object create_map() { Object tmp; tmp.m_flags = TYPE_MAP; tmp._map_ptr() = new map_type(); return tmp; } static Object create_dict_key(); static Object create_raw_bencode(raw_bencode obj = raw_bencode()); static Object create_raw_string(raw_string obj = raw_string()); static Object create_raw_list(raw_list obj = raw_list()); static Object create_raw_map(raw_map obj = raw_map()); template static Object create_list_range(ForwardIterator first, ForwardIterator last); static const char* type_to_c_str(type_type t); static Object from_list(const list_type& src); // Clear should probably not be inlined due to size and not being // optimized away in pretty much any case. Might not work well in // cases where we pass constant rvalues. void clear(); type_type type() const { return static_cast(m_flags & mask_type); } uint32_t flags() const { return m_flags & mask_flags; } void set_flags(uint32_t f) { m_flags |= f & mask_public; } void unset_flags(uint32_t f) { m_flags &= ~(f & mask_public); } void set_internal_flags(uint32_t f) { m_flags |= f & (mask_internal & ~mask_type); } void unset_internal_flags(uint32_t f) { m_flags &= ~(f & (mask_internal & ~mask_type)); } // Add functions for setting/clearing the public flags. bool is_empty() const { return type() == TYPE_NONE; } bool is_not_empty() const { return type() != TYPE_NONE; } bool is_value() const { return type() == TYPE_VALUE; } bool is_string() const { return type() == TYPE_STRING; } bool is_string_empty() const { return type() != TYPE_STRING || _string().empty(); } bool is_list() const { return type() == TYPE_LIST; } bool is_map() const { return type() == TYPE_MAP; } bool is_dict_key() const { return type() == TYPE_DICT_KEY; } bool is_raw_bencode() const { return type() == TYPE_RAW_BENCODE; } bool is_raw_string() const { return type() == TYPE_RAW_STRING; } bool is_raw_list() const { return type() == TYPE_RAW_LIST; } bool is_raw_map() const { return type() == TYPE_RAW_MAP; } value_type& as_value() { check_throw(TYPE_VALUE); return _value(); } const value_type& as_value() const { check_throw(TYPE_VALUE); return _value(); } string_type& as_string() { check_throw(TYPE_STRING); return _string(); } const string_type& as_string() const { check_throw(TYPE_STRING); return _string(); } const string_type& as_string_c() const { check_throw(TYPE_STRING); return _string(); } const char* as_c_str() const { check_throw(TYPE_STRING); return _string().c_str(); } list_type& as_list() { check_throw(TYPE_LIST); return _list(); } const list_type& as_list() const { check_throw(TYPE_LIST); return _list(); } map_type& as_map() { check_throw(TYPE_MAP); return _map(); } const map_type& as_map() const { check_throw(TYPE_MAP); return _map(); } string_type& as_dict_key() { check_throw(TYPE_DICT_KEY); return _dict_key().first; } const string_type& as_dict_key() const { check_throw(TYPE_DICT_KEY); return _dict_key().first; } Object& as_dict_obj() { check_throw(TYPE_DICT_KEY); return *_dict_key().second; } const Object& as_dict_obj() const { check_throw(TYPE_DICT_KEY); return *_dict_key().second; } raw_bencode& as_raw_bencode() { check_throw(TYPE_RAW_BENCODE); return _raw_bencode(); } const raw_bencode& as_raw_bencode() const { check_throw(TYPE_RAW_BENCODE); return _raw_bencode(); } raw_string& as_raw_string() { check_throw(TYPE_RAW_STRING); return _raw_string(); } const raw_string& as_raw_string() const { check_throw(TYPE_RAW_STRING); return _raw_string(); } raw_list& as_raw_list() { check_throw(TYPE_RAW_LIST); return _raw_list(); } const raw_list& as_raw_list() const { check_throw(TYPE_RAW_LIST); return _raw_list(); } raw_map& as_raw_map() { check_throw(TYPE_RAW_MAP); return _raw_map(); } const raw_map& as_raw_map() const { check_throw(TYPE_RAW_MAP); return _raw_map(); } template T as_value_type(const char* err_msg) const { check_value_throw(err_msg); return _value(); } bool has_key(const key_type& k) const { check_throw(TYPE_MAP); return _map().find(k) != _map().end(); } bool has_key_value(const key_type& k) const { check_throw(TYPE_MAP); return check(_map().find(k), TYPE_VALUE); } bool has_key_string(const key_type& k) const { check_throw(TYPE_MAP); return check(_map().find(k), TYPE_STRING); } bool has_key_list(const key_type& k) const { check_throw(TYPE_MAP); return check(_map().find(k), TYPE_LIST); } bool has_key_map(const key_type& k) const { check_throw(TYPE_MAP); return check(_map().find(k), TYPE_MAP); } bool has_key_raw_bencode(const key_type& k) const { check_throw(TYPE_MAP); return check(_map().find(k), TYPE_RAW_BENCODE); } bool has_key_raw_string(const key_type& k) const { check_throw(TYPE_MAP); return check(_map().find(k), TYPE_RAW_STRING); } bool has_key_raw_list(const key_type& k) const { check_throw(TYPE_MAP); return check(_map().find(k), TYPE_RAW_LIST); } bool has_key_raw_map(const key_type& k) const { check_throw(TYPE_MAP); return check(_map().find(k), TYPE_RAW_MAP); } // Should have an interface for that returns pointer or something, // so we don't need to search twice. // Make these inline... Object& get_key(const key_type& k); const Object& get_key(const key_type& k) const; Object& get_key(const char* k); const Object& get_key(const char* k) const; template value_type& get_key_value(const T& k) { return get_key(k).as_value(); } template const value_type& get_key_value(const T& k) const { return get_key(k).as_value(); } template string_type& get_key_string(const T& k) { return get_key(k).as_string(); } template const string_type& get_key_string(const T& k) const { return get_key(k).as_string(); } template list_type& get_key_list(const T& k) { return get_key(k).as_list(); } template const list_type& get_key_list(const T& k) const { return get_key(k).as_list(); } template map_type& get_key_map(const T& k) { return get_key(k).as_map(); } template const map_type& get_key_map(const T& k) const { return get_key(k).as_map(); } Object& insert_key(const key_type& k, const Object& b) { check_throw(TYPE_MAP); return _map()[k] = b; } Object& insert_key_move(const key_type& k, Object& b) { check_throw(TYPE_MAP); return _map()[k].move(b); } // 'insert_preserve_*' inserts the object 'b' if the key 'k' does // not exist, else it returns the old entry. The type specific // versions also require the old entry to be of the same type. // // Consider making insert_preserve_* return std::pair or // something similar. map_insert_type insert_preserve_any(const key_type& k, const Object& b) { check_throw(TYPE_MAP); return _map().insert(map_type::value_type(k, b)); } map_insert_type insert_preserve_type(const key_type& k, Object& b); map_insert_type insert_preserve_copy(const key_type& k, Object b) { return insert_preserve_type(k, b); } void erase_key(const key_type& k) { check_throw(TYPE_MAP); _map().erase(k); } Object& insert_front(const Object& b) { check_throw(TYPE_LIST); return *_list().insert(_list().begin(), b); } Object& insert_back(const Object& b) { check_throw(TYPE_LIST); return *_list().insert(_list().end(), b); } // Copy and merge operations: Object& move(Object& b) noexcept; Object& swap(Object& b) noexcept; Object& swap_same_type(Object& b) noexcept; // Only map entries are merged. Object& merge_move(Object& object, uint32_t maxDepth = ~uint32_t()); Object& merge_copy(const Object& object, uint32_t skip_mask = flag_static_data, uint32_t maxDepth = ~uint32_t()); // Internal: static void swap_same_type(Object& left, Object& right) noexcept; private: bool check(map_type::const_iterator itr, type_type t) const; void check_throw(type_type t) const; template void check_value_throw(const char* err_msg) const; uint32_t m_flags{TYPE_NONE}; #ifndef HAVE_STDCXX_0X value_type& _value() { return t_value; } const value_type& _value() const { return t_value; } string_type& _string() { return t_string; } const string_type& _string() const { return t_string; } list_type& _list() { return t_list; } const list_type& _list() const { return t_list; } map_type& _map() { return *t_map; } const map_type& _map() const { return *t_map; } map_ptr_type& _map_ptr() { return t_map; } const map_ptr_type& _map_ptr() const { return t_map; } dict_key_type& _dict_key() { return t_dict_key; } const dict_key_type& _dict_key() const { return t_dict_key; } raw_object& _raw_object() { return t_raw_object; } const raw_object& _raw_object() const { return t_raw_object; } raw_bencode& _raw_bencode() { return t_raw_bencode; } const raw_bencode& _raw_bencode() const { return t_raw_bencode; } raw_string& _raw_string() { return t_raw_string; } const raw_string& _raw_string() const { return t_raw_string; } raw_list& _raw_list() { return t_raw_list; } const raw_list& _raw_list() const { return t_raw_list; } raw_map& _raw_map() { return t_raw_map; } const raw_map& _raw_map() const { return t_raw_map; } union pod_types { value_type t_value; raw_object t_raw_object; raw_bencode t_raw_bencode; raw_string t_raw_string; raw_list t_raw_list; raw_map t_raw_map; }; union { pod_types t_pod; value_type t_value; string_type t_string; list_type t_list; map_type* t_map; dict_key_type t_dict_key; raw_object t_raw_object; raw_bencode t_raw_bencode; raw_string t_raw_string; raw_list t_raw_list; raw_map t_raw_map; }; #else // #error "WTF we're testing C++11 now." value_type& _value() { return reinterpret_cast(t_pod); } const value_type& _value() const { return reinterpret_cast(t_pod); } string_type& _string() { return reinterpret_cast(t_string); } const string_type& _string() const { return reinterpret_cast(t_string); } list_type& _list() { return reinterpret_cast(t_list); } const list_type& _list() const { return reinterpret_cast(t_list); } map_type& _map() { return *reinterpret_cast(t_pod); } const map_type& _map() const { return *reinterpret_cast(t_pod); } map_ptr_type& _map_ptr() { return reinterpret_cast(t_pod); } const map_ptr_type& _map_ptr() const { return reinterpret_cast(t_pod); } dict_key_type& _dict_key() { return reinterpret_cast(t_pod); } const dict_key_type& _dict_key() const { return reinterpret_cast(t_pod); } raw_object& _raw_object() { return reinterpret_cast(t_pod); } const raw_object& _raw_object() const { return reinterpret_cast(t_pod); } raw_bencode& _raw_bencode() { return reinterpret_cast(t_pod); } const raw_bencode& _raw_bencode() const { return reinterpret_cast(t_pod); } raw_string& _raw_string() { return reinterpret_cast(t_pod); } const raw_string& _raw_string() const { return reinterpret_cast(t_pod); } raw_list& _raw_list() { return reinterpret_cast(t_pod); } const raw_list& _raw_list() const { return reinterpret_cast(t_pod); } raw_map& _raw_map() { return reinterpret_cast(t_pod); } const raw_map& _raw_map() const { return reinterpret_cast(t_pod); } union pod_types { value_type t_value; map_type* t_map; char t_raw_object[sizeof(raw_object)]; }; union { pod_types t_pod; char t_string[sizeof(string_type)]; char t_list[sizeof(list_type)]; char t_dict_key[sizeof(dict_key_type)]; }; #endif }; inline Object::Object(const Object& b) : m_flags(b.m_flags & (mask_type | mask_public)) { switch (type()) { case TYPE_NONE: case TYPE_RAW_BENCODE: case TYPE_RAW_STRING: case TYPE_RAW_LIST: case TYPE_RAW_MAP: case TYPE_VALUE: t_pod = b.t_pod; break; case TYPE_STRING: new (&_string()) string_type(b._string()); break; case TYPE_LIST: new (&_list()) list_type(b._list()); break; case TYPE_MAP: _map_ptr() = new map_type(b._map()); break; case TYPE_DICT_KEY: new (&_dict_key().first) string_type(b._dict_key().first); _dict_key().second = new Object(*b._dict_key().second); break; } } inline Object Object::create_empty(type_type t) { switch (t) { case TYPE_RAW_BENCODE: return create_raw_bencode(); case TYPE_RAW_STRING: return create_raw_string(); case TYPE_RAW_LIST: return create_raw_list(); case TYPE_RAW_MAP: return create_raw_map(); case TYPE_VALUE: return create_value(); case TYPE_STRING: return create_string(); case TYPE_LIST: return create_list(); case TYPE_MAP: return create_map(); case TYPE_DICT_KEY: return create_dict_key(); case TYPE_NONE: default: return torrent::Object(); } } inline Object object_create_raw_bencode_c_str(const char str[]) { return Object::create_raw_bencode(raw_bencode(str, strlen(str))); } // TODO: These do not preserve the flag... Object object_create_normal(const raw_bencode& obj) LIBTORRENT_EXPORT; Object object_create_normal(const raw_list& obj) LIBTORRENT_EXPORT; Object object_create_normal(const raw_map& obj) LIBTORRENT_EXPORT; inline Object object_create_normal(const raw_string& obj) { return torrent::Object(obj.as_string()); } inline Object Object::create_dict_key() { Object tmp; tmp.m_flags = TYPE_DICT_KEY; new (&tmp._dict_key()) dict_key_type(); tmp._dict_key().second = new Object(); return tmp; } inline Object Object::create_raw_bencode(raw_bencode obj) { Object tmp; tmp.m_flags = TYPE_RAW_BENCODE; new (&tmp._raw_bencode()) raw_bencode(obj); return tmp; } inline Object Object::create_raw_string(raw_string obj) { Object tmp; tmp.m_flags = TYPE_RAW_STRING; new (&tmp._raw_string()) raw_string(obj); return tmp; } inline Object Object::create_raw_list(raw_list obj) { Object tmp; tmp.m_flags = TYPE_RAW_LIST; new (&tmp._raw_list()) raw_list(obj); return tmp; } inline Object Object::create_raw_map(raw_map obj) { Object tmp; tmp.m_flags = TYPE_RAW_MAP; new (&tmp._raw_map()) raw_map(obj); return tmp; } inline Object object_create_normal(const Object& obj) { switch (obj.type()) { case Object::TYPE_RAW_BENCODE: return object_create_normal(obj.as_raw_bencode()); case Object::TYPE_RAW_STRING: return object_create_normal(obj.as_raw_string()); case Object::TYPE_RAW_LIST: return object_create_normal(obj.as_raw_list()); case Object::TYPE_RAW_MAP: return object_create_normal(obj.as_raw_map()); default: return obj; } } inline std::string object_create_string(const torrent::Object& obj) { switch (obj.type()) { case Object::TYPE_RAW_BENCODE: return obj.as_raw_bencode().as_raw_string().as_string(); case Object::TYPE_RAW_STRING: return obj.as_raw_string().as_string(); default: return obj.as_string(); } } template inline Object Object::create_list_range(ForwardIterator first, ForwardIterator last) { Object tmp; tmp.m_flags = TYPE_LIST; new (&tmp._list()) list_type(first, last); return tmp; } inline const char* Object::type_to_c_str(type_type t) { switch (t) { case TYPE_NONE: return "none"; case TYPE_RAW_BENCODE: return "raw_bencode"; case TYPE_RAW_STRING: return "raw_string"; case TYPE_RAW_LIST: return "raw_list"; case TYPE_RAW_MAP: return "raw_map"; case TYPE_VALUE: return "value"; case TYPE_STRING: return "string"; case TYPE_LIST: return "list"; case TYPE_MAP: return "map"; case TYPE_DICT_KEY: return "dict_key"; default: return "invalid"; } } inline Object Object::from_list(const list_type& src) { Object tmp; tmp.m_flags = TYPE_LIST; new (&tmp._list()) list_type(src); return tmp; } inline void Object::clear() { switch (type()) { case TYPE_STRING: _string().~string_type(); break; case TYPE_LIST: _list().~list_type(); break; case TYPE_MAP: delete _map_ptr(); break; case TYPE_DICT_KEY: delete _dict_key().second; _dict_key().~dict_key_type(); break; default: break; } // Only clear type? m_flags = TYPE_NONE; } inline void Object::swap_same_type(Object& left, Object& right) noexcept { std::swap(left.m_flags, right.m_flags); switch (left.type()) { case Object::TYPE_STRING: left._string().swap(right._string()); break; case Object::TYPE_LIST: left._list().swap(right._list()); break; case Object::TYPE_DICT_KEY: std::swap(left._dict_key().first, right._dict_key().first); std::swap(left._dict_key().second, right._dict_key().second); break; default: std::swap(left.t_pod, right.t_pod); break; } } inline void swap(Object& left, Object& right) noexcept { left.swap(right); } inline bool object_equal(const Object& left, const Object& right) { if (left.type() != right.type()) return false; switch (left.type()) { case Object::TYPE_NONE: return true; case Object::TYPE_VALUE: return left.as_value() == right.as_value(); case Object::TYPE_STRING: return left.as_string() == right.as_string(); default: return false; } } inline bool Object::check(map_type::const_iterator itr, type_type t) const { return itr != _map().end() && itr->second.type() == t; } inline void Object::check_throw(type_type t) const { if (t != type()) throw bencode_error("Wrong object type: expected: " + std::string(type_to_c_str(t)) + " actual: " + std::string(type_to_c_str(type()))); } template inline void Object::check_value_throw(const char* err_msg) const { if (!std::numeric_limits::is_integer) throw internal_error("Tried to check value with non-integer type."); if (!is_value()) throw bencode_error(err_msg); if (!(_value() >= std::numeric_limits::min() && _value() <= std::numeric_limits::max())) throw bencode_error(err_msg); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/object_raw_bencode.h000066400000000000000000000125221522271512000223040ustar00rootroot00000000000000#ifndef LIBTORRENT_OBJECT_RAW_BENCODE_H #define LIBTORRENT_OBJECT_RAW_BENCODE_H #include #include #include #include #include namespace torrent { class raw_bencode; class raw_string; class raw_list; class raw_map; // The base class for static constant version of Objects. This class // should never be used directly. class raw_object { public: using value_type = const char; using iterator = const char*; using const_iterator = const char*; using size_type = uint32_t; raw_object() = default; raw_object(value_type* src_data, size_type src_size) : m_data(src_data), m_size(src_size) {} bool empty() const { return m_size == 0; } size_type size() const { return m_size; } iterator begin() const { return m_data; } iterator end() const { return m_data + m_size; } value_type* data() const { return m_data; } bool operator == (const raw_object& rhs) const { return m_size == rhs.m_size && std::memcmp(m_data, rhs.m_data, m_size) == 0; } bool operator != (const raw_object& rhs) const { return m_size != rhs.m_size || std::memcmp(m_data, rhs.m_data, m_size) != 0; } protected: iterator m_data{}; size_type m_size{}; }; #define RAW_BENCODE_SET_USING \ using raw_object::value_type; \ using raw_object::iterator; \ using raw_object::const_iterator; \ using raw_object::size_type; \ using raw_object::empty; \ using raw_object::size; \ using raw_object::begin; \ using raw_object::end; \ using raw_object::data; \ \ bool operator == (const this_type& rhs) const { return raw_object::operator==(rhs); } \ bool operator != (const this_type& rhs) const { return raw_object::operator!=(rhs); } \ // A raw_bencode object shall always contain valid bencode data or be // empty. class raw_bencode : protected raw_object { public: using this_type = raw_bencode; RAW_BENCODE_SET_USING raw_bencode() = default; raw_bencode(value_type* src_data, size_type src_size) : raw_object(src_data, src_size) {} bool is_empty() const { return m_size == 0; } bool is_value() const { return m_size >= 3 && m_data[0] >= 'i'; } bool is_raw_string() const { return m_size >= 2 && m_data[0] >= '0' && m_data[0] <= '9'; } bool is_raw_list() const { return m_size >= 2 && m_data[0] >= 'l'; } bool is_raw_map() const { return m_size >= 2 && m_data[0] >= 'd'; } std::string as_value_string() const; raw_string as_raw_string() const; raw_list as_raw_list() const; raw_map as_raw_map() const; static raw_bencode from_c_str(const char* str) { return raw_bencode(str, std::strlen(str)); } }; class raw_string : protected raw_object { public: using this_type = raw_string; RAW_BENCODE_SET_USING raw_string() = default; raw_string(value_type* src_data, size_type src_size) : raw_object(src_data, src_size) {} std::string as_string() const { return std::string(m_data, m_size); } static raw_string from_c_str(const char* str) { return raw_string(str, std::strlen(str)); } static raw_string from_string(const std::string& str) { return raw_string(str.data(), str.size()); } }; class raw_list : protected raw_object { public: using this_type = raw_list; RAW_BENCODE_SET_USING raw_list() = default; raw_list(value_type* src_data, size_type src_size) : raw_object(src_data, src_size) {} static raw_list from_c_str(const char* str) { return raw_list(str, std::strlen(str)); } }; class raw_map : protected raw_object { public: using this_type = raw_map; RAW_BENCODE_SET_USING raw_map() = default; raw_map(value_type* src_data, size_type src_size) : raw_object(src_data, src_size) {} }; // // // inline std::string raw_bencode::as_value_string() const { if (!is_value()) throw bencode_error("Wrong object type."); return std::string(data() + 1, size() - 2); } inline raw_string raw_bencode::as_raw_string() const { if (!is_raw_string()) throw bencode_error("Wrong object type."); const_iterator itr = std::find(begin(), end(), ':'); if (itr == end()) throw internal_error("Invalid bencode in raw_bencode."); return raw_string(itr + 1, std::distance(itr + 1, end())); } inline raw_list raw_bencode::as_raw_list() const { if (!is_raw_list()) throw bencode_error("Wrong object type."); return raw_list(m_data + 1, m_size - 2); } inline raw_map raw_bencode::as_raw_map() const { if (!is_raw_map()) throw bencode_error("Wrong object type."); return raw_map(m_data + 1, m_size - 2); } // // Redo... // inline bool raw_bencode_equal(const raw_bencode& left, const raw_bencode& right) { return left.size() == right.size() && std::memcmp(left.begin(), right.begin(), left.size()) == 0; } template inline bool raw_bencode_equal(const tmpl_raw_object& left, const char* right, size_t right_size) { return left.size() == right_size && std::memcmp(left.begin(), right, right_size) == 0; } template inline bool raw_bencode_equal_c_str(const tmpl_raw_object& left, const char* right) { return raw_bencode_equal(left, right, strlen(right)); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/object_static_map.cc000066400000000000000000000020101522271512000223050ustar00rootroot00000000000000#include "config.h" #include "torrent/object_static_map.h" #include "utils/functional.h" namespace torrent { static_map_key_search_result find_key_match(const static_map_mapping_type* first, const static_map_mapping_type* last, const char* key_first, const char* key_last) { // unsigned int key_length = strlen(key); const static_map_mapping_type* itr = first; while (itr != last) { unsigned int base = ::utils::count_base(key_first, key_last, itr->key, itr->key + torrent::static_map_mapping_type::max_key_size); if (key_first[base] != '\0') { // Return not found here if we know the entry won't come after // this. itr++; continue; } if (itr->key[base] == '\0' || itr->key[base] == '*' || (itr->key[base] == ':' && itr->key[base + 1] == ':') || (itr->key[base] == '[' && itr->key[base + 1] == ']')) return static_map_key_search_result(itr, base); break; } return static_map_key_search_result(first, 0); } } // namespace torrent libtorrent-0.16.17/src/torrent/object_static_map.h000066400000000000000000000047511522271512000221650ustar00rootroot00000000000000#ifndef LIBTORRENT_OBJECT_STATIC_MAP_H #define LIBTORRENT_OBJECT_STATIC_MAP_H #include #include #include namespace torrent { struct static_map_mapping_type { static constexpr size_t max_key_size = 16; bool is_end() const { return key[0] == '\0'; } static bool is_not_key_char(char c) { return c == '\0' || c == ':' || c == '[' || c == '*'; } const char* find_key_end(const char* pos) const { return std::find_if(pos, key + max_key_size, &is_not_key_char); } uint32_t index; char key[max_key_size]; }; struct static_map_entry_type { torrent::Object object; }; template class static_map_type { public: using value_type = Object; using key_type = tmpl_key_type; using entry_type = static_map_entry_type; using mapping_type = static_map_mapping_type; typedef mapping_type key_list_type[tmpl_length]; typedef entry_type value_list_type[tmpl_length]; static constexpr size_t size = tmpl_length; static const key_list_type keys; entry_type* values() { return m_values; } const entry_type* values() const { return m_values; } Object& operator [] (key_type key) { return m_values[key].object; } const Object& operator [] (key_type key) const { return m_values[key].object; } private: value_list_type m_values; }; // // Helper functions/classes for parsing keys: // struct static_map_stack_type { void set_key_index(uint32_t start_index, uint32_t end_index, uint32_t delim_size, torrent::Object::type_type type = Object::TYPE_MAP) { key_index = start_index; next_key = end_index + delim_size; obj_type = type; } void clear() { key_index = 0; next_key = 0; obj_type = Object::TYPE_MAP; } uint32_t key_index; uint32_t next_key; Object::type_type obj_type; }; using static_map_key_search_result = std::pair; // Note that the key for both functions must be null-terminated at // 'key_last'. static_map_key_search_result find_key_match(const static_map_mapping_type* first, const static_map_mapping_type* last, const char* key_first, const char* key_last) LIBTORRENT_EXPORT; inline static_map_key_search_result find_key_match(const static_map_mapping_type* first, const static_map_mapping_type* last, const char* key) { return find_key_match(first, last, key, key + strlen(key)); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/object_stream.cc000066400000000000000000000572561522271512000215020ustar00rootroot00000000000000#include "config.h" #include "torrent/object_stream.h" #include #include #include #include #include "torrent/object.h" #include "torrent/object_static_map.h" #include "utils/functional.h" #include "utils/sha1.h" namespace torrent { static bool object_read_string(std::istream* input, std::string& str) { uint32_t size; *input >> size; if (input->fail() || input->get() != ':') return false; try { str.resize(size); } catch (const std::length_error&) { return false; } for (auto& c : str) { if (!input->good()) break; c = input->get(); } return !input->fail(); } static const char* object_read_bencode_c_value(const char* first, const char* last, int64_t& value) { if (first == last) return first; bool neg = false; if (*first == '-') { // Don't allow '-0', or '-' followed by non-numeral. if ((first + 1) == last || *(first + 1) <= '0' || *(first + 1) > '9') return first; neg = true; first++; } value = 0; while (first != last && *first >= '0' && *first <= '9') value = value * 10 + (*first++ - '0'); if (neg) value = -value; return first; } raw_string object_read_bencode_c_string(const char* first, const char* last) { // Set the most-significant bit so that if there are no numbers in // the input it will fail the length check, while "0" will shift the // bit out. unsigned int length = 0x1U << (std::numeric_limits::digits - 1); while (first != last && *first >= '0' && *first <= '9') length = length * 10 + (*first++ - '0'); if (length + 1 > static_cast(std::distance(first, last)) || length + 1 == 0 || *first++ != ':') throw torrent::bencode_error("Invalid bencode data."); return raw_string(first, length); } // Could consider making this non-recursive, but they seldomly are // deep enough to make that worth-while. void object_read_bencode(std::istream* input, Object* object, uint32_t depth) { int c; switch ((c = input->peek())) { case 'i': input->get(); *object = Object::create_value(); *input >> object->as_value(); if (input->get() != 'e') break; return; case 'l': input->get(); *object = Object::create_list(); if (++depth >= 1024) break; while (input->good()) { if (input->peek() == 'e') { input->get(); return; } auto& obj = object->as_list().emplace_back(); object_read_bencode(input, &obj, depth); // The unordered flag is inherited also from list elements who // have been marked as unordered, though e.g. unordered strings // in the list itself does not cause this flag to be set. if (obj.flags() & Object::flag_unordered) object->set_internal_flags(Object::flag_unordered); } break; case 'd': { input->get(); *object = Object::create_map(); if (++depth >= 1024) break; Object::string_type last; while (input->good()) { if (input->peek() == 'e') { input->get(); return; } Object::string_type str; if (!object_read_string(input, str)) break; // We do not set flag_unordered if the first key was zero // length, while multiple zero length keys will trigger the // unordered_flag. if (str <= last && !object->as_map().empty()) object->set_internal_flags(Object::flag_unordered); Object* value = &object->as_map()[str]; object_read_bencode(input, value, depth); if (value->flags() & Object::flag_unordered) object->set_internal_flags(Object::flag_unordered); str.swap(last); } break; } default: if (c >= '0' && c <= '9') { *object = Object::create_string(); if (object_read_string(input, object->as_string())) return; } break; } input->setstate(std::istream::failbit); object->clear(); } const char* object_read_bencode_c(const char* first, const char* last, Object* object, uint32_t depth) { if (first == last) throw torrent::bencode_error("Invalid bencode data."); switch (*first) { case 'i': *object = Object::create_value(); first = object_read_bencode_c_value(first + 1, last, object->as_value()); if (first == last || *first++ != 'e') break; return first; case 'l': if (++depth >= 1024) break; first++; *object = Object::create_list(); while (first != last) { if (*first == 'e') return first + 1; auto& obj = object->as_list().emplace_back(); first = object_read_bencode_c(first, last, &obj, depth); // The unordered flag is inherited also from list elements who // have been marked as unordered, though e.g. unordered strings // in the list itself does not cause this flag to be set. if (obj.flags() & Object::flag_unordered) object->set_internal_flags(Object::flag_unordered); } break; case 'd': { if (++depth >= 1024) break; first++; *object = Object::create_map(); Object::string_type prev; while (first != last) { if (*first == 'e') return first + 1; raw_string raw_str = object_read_bencode_c_string(first, last); first = raw_str.end(); Object::string_type str = raw_str.as_string(); // We do not set flag_unordered if the first key was zero // length, while multiple zero length keys will trigger the // unordered_flag. if (str <= prev && !object->as_map().empty()) object->set_internal_flags(Object::flag_unordered); Object* value = &object->as_map()[str]; first = object_read_bencode_c(first, last, value, depth); if (value->flags() & Object::flag_unordered) object->set_internal_flags(Object::flag_unordered); str.swap(prev); } break; } default: if (*first < '0' || *first > '9') throw torrent::bencode_error("Invalid bencode data."); raw_string raw_str = object_read_bencode_c_string(first, last); *object = raw_str.as_string(); return raw_str.end(); } object->clear(); throw torrent::bencode_error("Invalid bencode data."); } static bool object_is_not_digit(char c) { return c < '0' || c > '9'; } const char* object_read_bencode_skip_c(const char* first, const char* last) { char stack[128] = { 0 }; char* stack_itr = stack; while (first != last) { if (*first == 'e') { if (stack_itr-- == stack) throw torrent::bencode_error("Invalid bencode data."); first++; if (stack_itr == stack) return first; continue; } // Currently reading a dictionary, so ensure the first entry is a // string. if (*stack_itr && (first = object_read_bencode_c_string(first, last).end()) == last) break; switch (*first) { case 'i': if (first != last && *++first == '-') { if (first != last && *++first == '0') throw torrent::bencode_error("Invalid bencode data."); } first = std::find_if(first, last, &object_is_not_digit); if (first == last || *first++ != 'e') throw torrent::bencode_error("Invalid bencode data."); break; case 'l': case 'd': if (++stack_itr == stack + 128) throw torrent::bencode_error("Invalid bencode data."); *stack_itr = (*first++ == 'd'); continue; default: first = object_read_bencode_c_string(first, last).end(); } if (stack_itr == stack) return first; } throw torrent::bencode_error("Invalid bencode data."); } static const char* object_read_bencode_raw_c(const char* first, const char* last, torrent::Object* object, char type) { const char* tmp = first; first = object_read_bencode_skip_c(first, last); raw_bencode obj = raw_bencode(tmp, std::distance(tmp, first)); // if (obj.is_empty()) // throw torrent::bencode_error("Invalid bencode data."); switch (type) { case 'S': if (obj.is_raw_string()) *object = obj.as_raw_string(); break; case 'L': if (obj.is_raw_list()) *object = obj.as_raw_list(); break; case 'M': if (obj.is_raw_map()) *object = obj.as_raw_map(); break; default: *object = obj; } return first; } // Would be nice to have a straight stream to hash conversion. std::string object_sha1(const Object* object) { Sha1 sha; char buffer[1024]; sha.init(); object_write_bencode_c(&object_write_to_sha1, &sha, object_buffer_t(buffer, buffer + 1024), object); sha.final_c(buffer); return std::string(buffer, 20); } std::istream& operator >> (std::istream& input, Object& object) { std::locale locale = input.imbue(std::locale::classic()); object.clear(); object_read_bencode(&input, &object); input.imbue(locale); return input; } std::ostream& operator << (std::ostream& output, const Object& object) { object_write_bencode(&output, &object); return output; } struct object_write_data_t { object_write_t writeFunc; void* data; object_buffer_t buffer; char* pos; }; static void object_write_bencode_c_string(object_write_data_t* output, const char* srcData, uint32_t srcLength) { while (srcLength != 0) { uint32_t len = std::min(srcLength, std::distance(output->pos, output->buffer.second)); std::memcpy(output->pos, srcData, len); output->pos += len; if (output->pos == output->buffer.second) { output->buffer = output->writeFunc(output->data, output->buffer); output->pos = output->buffer.first; if (output->buffer.first == output->buffer.second) return; } srcData += len; srcLength -= len; } } static void object_write_bencode_c_char(object_write_data_t* output, char src) { if (output->pos == output->buffer.second) { output->buffer = output->writeFunc(output->data, output->buffer); output->pos = output->buffer.first; if (output->buffer.first == output->buffer.second) return; } *output->pos++ = src; } // A new wheel. Look, how shiny and new. static void object_write_bencode_c_value(object_write_data_t* output, int64_t src) { if (src == 0) return object_write_bencode_c_char(output, '0'); if (src < 0) { object_write_bencode_c_char(output, '-'); src = -src; } char buffer[20]; char* first = buffer + 20; // We don't need locale support, so just do this directly. while (src != 0) { *--first = '0' + src % 10; src /= 10; } object_write_bencode_c_string(output, first, 20 - std::distance(buffer, first)); } static void object_write_bencode_c_obj_value(object_write_data_t* output, int64_t value) { object_write_bencode_c_char(output, 'i'); object_write_bencode_c_value(output, value); object_write_bencode_c_char(output, 'e'); } static void object_write_bencode_c_obj_string(object_write_data_t* output, const char* data, uint32_t size) { object_write_bencode_c_value(output, size); object_write_bencode_c_char(output, ':'); object_write_bencode_c_string(output, data, size); } static void object_write_bencode_c_obj_string(object_write_data_t* output, const std::string& str) { object_write_bencode_c_obj_string(output, str.c_str(), str.size()); } static void object_write_bencode_c_object(object_write_data_t* output, const Object* object, uint32_t skip_mask) { switch (object->type()) { case Object::TYPE_NONE: break; case Object::TYPE_RAW_BENCODE: { raw_bencode raw = object->as_raw_bencode(); object_write_bencode_c_string(output, raw.begin(), raw.size()); break; } case Object::TYPE_RAW_STRING: { raw_string raw = object->as_raw_string(); object_write_bencode_c_value(output, raw.size()); object_write_bencode_c_char(output, ':'); object_write_bencode_c_string(output, raw.begin(), raw.size()); break; } case Object::TYPE_RAW_LIST: { raw_list raw = object->as_raw_list(); object_write_bencode_c_char(output, 'l'); object_write_bencode_c_string(output, raw.begin(), raw.size()); object_write_bencode_c_char(output, 'e'); break; } case Object::TYPE_RAW_MAP: { raw_map raw = object->as_raw_map(); object_write_bencode_c_char(output, 'd'); object_write_bencode_c_string(output, raw.begin(), raw.size()); object_write_bencode_c_char(output, 'e'); break; } case Object::TYPE_VALUE: object_write_bencode_c_obj_value(output, object->as_value()); break; case Object::TYPE_STRING: object_write_bencode_c_obj_string(output, object->as_string()); break; case Object::TYPE_LIST: object_write_bencode_c_char(output, 'l'); for (const auto& list : object->as_list()) { if (list.is_empty() || list.flags() & skip_mask) continue; object_write_bencode_c_object(output, &list, skip_mask); } object_write_bencode_c_char(output, 'e'); break; case Object::TYPE_MAP: object_write_bencode_c_char(output, 'd'); for (const auto& map : object->as_map()) { if (map.second.is_empty() || map.second.flags() & skip_mask) continue; object_write_bencode_c_obj_string(output, map.first); object_write_bencode_c_object(output, &map.second, skip_mask); } object_write_bencode_c_char(output, 'e'); break; case Object::TYPE_DICT_KEY: throw torrent::bencode_error("Cannot bencode internal dict_key type."); } } void object_write_bencode(std::ostream* output, const Object* object, uint32_t skip_mask) { char buffer[1024]; object_write_bencode_c(&object_write_to_stream, output, object_buffer_t(buffer, buffer + 1024), object, skip_mask); } object_buffer_t object_write_bencode(char* first, char* last, const Object* object, uint32_t skip_mask) { object_buffer_t buffer = object_buffer_t(first, last); return object_write_bencode_c(&object_write_to_buffer, &buffer, buffer, object, skip_mask); } object_buffer_t object_write_bencode_c(object_write_t writeFunc, void* data, object_buffer_t buffer, const Object* object, uint32_t skip_mask) { object_write_data_t output; output.writeFunc = writeFunc; output.data = data; output.buffer = buffer; output.pos = buffer.first; if (!(object->flags() & skip_mask)) object_write_bencode_c_object(&output, object, skip_mask); // Don't flush the buffer. if (output.pos == output.buffer.first) return output.buffer; return output.writeFunc(output.data, object_buffer_t(output.buffer.first, output.pos)); } object_buffer_t object_write_to_buffer([[maybe_unused]] void* data, object_buffer_t buffer) { if (buffer.first == buffer.second) throw internal_error("object_write_to_buffer(...) buffer overflow."); // Hmm... this does something weird... return object_buffer_t(buffer.second, buffer.second); } object_buffer_t object_write_to_sha1(void* data, object_buffer_t buffer) { static_cast(data)->update(buffer.first, std::distance(buffer.first, buffer.second)); return buffer; } object_buffer_t object_write_to_stream(void* data, object_buffer_t buffer) { static_cast(data)->write(buffer.first, std::distance(buffer.first, buffer.second)); if (static_cast(data)->bad()) return object_buffer_t(buffer.first, buffer.first); return buffer; } object_buffer_t object_write_to_size(void* data, object_buffer_t buffer) { *static_cast(data) += std::distance(buffer.first, buffer.second); return buffer; } // // static_map operations: // const char* static_map_read_bencode_c(const char* first, const char* last, static_map_entry_type* entry_values, const static_map_mapping_type* first_key, const static_map_mapping_type* last_key) { // Temp hack... validate that we got valid bencode data... // { // torrent::Object obj; // if (object_read_bencode_c(first, last, &obj) != last) { // std::string escaped = copy_escape_html(first, last); // char buffer[1024]; // sprintf(buffer, "Verified wrong, %u, '%u', '%s'.", std::distance(first, last), (unsigned int)*first, escaped.c_str()); // throw torrent::internal_error("Invalid bencode data."); // } // } if (first == last || *first++ != 'd') throw torrent::bencode_error("Invalid bencode data."); static_map_stack_type stack[8]; static_map_stack_type* stack_itr = stack; stack_itr->clear(); char current_key[static_map_mapping_type::max_key_size + 2] = ""; while (first != last) { // End a dictionary/list or the whole stream. if (*first == 'e') { first++; if (stack_itr == stack) return first; stack_itr--; continue; } raw_string raw_key = object_read_bencode_c_string(first, last); first = raw_key.end(); // Optimze this buy directly copying into 'current_key'. // // The max length of 'current_key' is one char more than the // mapping key so any bencode which exceeds that will always fail // to find a match. if (raw_key.size() >= static_map_mapping_type::max_key_size - stack_itr->next_key) { first = object_read_bencode_skip_c(first, last); continue; } memcpy(current_key + stack_itr->next_key, raw_key.data(), raw_key.size()); current_key[stack_itr->next_key + raw_key.size()] = '\0'; // Locate the right key. Optimize this by remembering previous // position... static_map_key_search_result key_search = find_key_match(first_key, last_key, current_key); // We're not interest in this object, skip it. if (key_search.second == 0) { first = object_read_bencode_skip_c(first, last); continue; } // Check if we're interested in any dictionaries/lists entries // under this key. // // Note that 'find_key_match' only returns 'key_search.second != // 0' for keys where the next characters are '\0', '::' or '[]'. switch (key_search.first->key[key_search.second]) { case '\0': first = object_read_bencode_c(first, last, &entry_values[key_search.first->index].object); first_key = key_search.first + 1; break; case '*': first = object_read_bencode_raw_c(first, last, &entry_values[key_search.first->index].object, key_search.first->key[key_search.second + 1]); first_key = key_search.first + 1; break; case ':': if (first == last) break; // The bencode object isn't a list. This should either skip it // or produce an error. if (*first++ != 'd') { first = object_read_bencode_skip_c(first - 1, last); break; } stack_itr++; stack_itr->set_key_index((stack_itr - 1)->next_key, key_search.second, 2); current_key[key_search.second] = ':'; current_key[key_search.second + 1] = ':'; break; case '[': { if (first == last) break; // The bencode object isn't a list. This should either skip it // or produce an error. if (*first++ != 'l') { first = object_read_bencode_skip_c(first - 1, last); break; } first_key = key_search.first; while (first != last) { if (*first == 'e') { first++; break; } if (first_key->key[key_search.second + 2] == '*') { first = object_read_bencode_raw_c(first, last, &entry_values[first_key->index].object, key_search.first->key[key_search.second + 1]); } else { first = object_read_bencode_c(first, last, &entry_values[first_key->index].object); } if (++first_key == last_key || strcmp(first_key->key, (first_key - 1)->key) != 0) { while (first != last) { if (*first == 'e') { first++; break; } first = object_read_bencode_skip_c(first, last); } break; } } break; } default: throw internal_error("static_map_read_bencode_c: key_search.first->key[base] returned invalid character."); } } throw torrent::bencode_error("Invalid bencode data."); } static void static_map_write_bencode_c_values(object_write_data_t* output, const static_map_entry_type* entry_values, const static_map_mapping_type* first_key, const static_map_mapping_type* last_key) { const char* prev_key = NULL; static_map_stack_type stack[8]; static_map_stack_type* stack_itr = stack; stack_itr->clear(); object_write_bencode_c_char(output, 'd'); while (first_key != last_key) { if (entry_values[first_key->index].object.is_empty()) { first_key++; continue; } // Compare the keys to see if they are part of the same // dictionaries/lists. unsigned int base_size = ::utils::count_base(first_key->key, first_key->key + stack_itr->next_key, prev_key, prev_key + stack_itr->next_key); while (base_size < stack_itr->next_key) { object_write_bencode_c_char(output, 'e'); stack_itr--; } const char* key_begin = first_key->key + stack_itr->next_key; do { const char* key_end = first_key->find_key_end(key_begin); if (stack_itr->obj_type == Object::TYPE_MAP) object_write_bencode_c_obj_string(output, key_begin, std::distance(key_begin, key_end)); // Check if '::' or '[' were found... if (*key_end == ':' && *(key_end + 1) == ':') { (++stack_itr)->set_key_index(std::distance(first_key->key, key_begin), std::distance(first_key->key, key_end), 2); key_begin = key_end + 2; object_write_bencode_c_char(output, 'd'); continue; } // Handle "foo[]..." entries. We iterate once for each "foo[]" // found in the key list. if (*key_end == '[' && *(key_end + 1) == ']') { (++stack_itr)->set_key_index(std::distance(first_key->key, key_begin), std::distance(first_key->key, key_end), 2, Object::TYPE_LIST); key_begin = key_end + 2; object_write_bencode_c_char(output, 'l'); continue; } // We have a leaf object. if (*key_end != '\0' && *key_end != '*') throw internal_error("static_map_type key is invalid."); object_write_bencode_c_object(output, &entry_values[first_key->index].object, 0); break; } while (true); prev_key = (first_key++)->key; } do { object_write_bencode_c_char(output, 'e'); } while (stack_itr-- != stack); } object_buffer_t static_map_write_bencode_c_wrap(object_write_t writeFunc, void* data, object_buffer_t buffer, const static_map_entry_type* entry_values, const static_map_mapping_type* first_key, const static_map_mapping_type* last_key) { object_write_data_t output; output.writeFunc = writeFunc; output.data = data; output.buffer = buffer; output.pos = buffer.first; static_map_write_bencode_c_values(&output, entry_values, first_key, last_key); // DEBUG: Remove this. // { // torrent::Object obj; // if (object_read_bencode_c(output.buffer.first, output.pos, &obj) != output.pos) { // std::string escaped = copy_escape_html(output.buffer.first, output.pos); // //char buffer[1024]; // // sprintf(buffer, "Verified wrong, %u, '%u', '%s'.", std::distanescaped.c_str()); // throw torrent::internal_error("Invalid bencode data generated: '" + escaped + "'"); // } // } // Don't flush the buffer. if (output.pos == output.buffer.first) return output.buffer; return output.writeFunc(output.data, object_buffer_t(output.buffer.first, output.pos)); } } // namespace torrent libtorrent-0.16.17/src/torrent/object_stream.h000066400000000000000000000077011522271512000213320ustar00rootroot00000000000000#ifndef LIBTORRENT_OBJECT_STREAM_H #define LIBTORRENT_OBJECT_STREAM_H #include #include #include namespace torrent { class raw_string; std::string object_sha1(const Object* object) LIBTORRENT_EXPORT; raw_string object_read_bencode_c_string(const char* first, const char* last) LIBTORRENT_EXPORT; // Assumes the stream's locale has been set to POSIX or C. Max depth // is 1024, this ensures files consisting of only 'l' don't segfault // the client. void object_read_bencode(std::istream* input, Object* object, uint32_t depth = 0) LIBTORRENT_EXPORT; const char* object_read_bencode_c(const char* first, const char* last, Object* object, uint32_t depth = 0) LIBTORRENT_EXPORT; const char* object_read_bencode_skip_c(const char* first, const char* last) LIBTORRENT_EXPORT; std::istream& operator >> (std::istream& input, Object& object) LIBTORRENT_EXPORT; std::ostream& operator << (std::ostream& output, const Object& object) LIBTORRENT_EXPORT; // object_buffer_t contains the start and end of the buffer. using object_buffer_t = std::pair; using object_write_t = object_buffer_t (*)(void* data, object_buffer_t buffer); // Assumes the stream's locale has been set to POSIX or C. void object_write_bencode(std::ostream* output, const Object* object, uint32_t skip_mask = 0) LIBTORRENT_EXPORT; object_buffer_t object_write_bencode(char* first, char* last, const Object* object, uint32_t skip_mask = 0) LIBTORRENT_EXPORT; object_buffer_t object_write_bencode_c(object_write_t writeFunc, void* data, object_buffer_t buffer, const Object* object, uint32_t skip_mask = 0) LIBTORRENT_EXPORT; // To char buffer. 'data' is NULL. object_buffer_t object_write_to_buffer(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; object_buffer_t object_write_to_sha1(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; object_buffer_t object_write_to_stream(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; // Measures bencode size, 'data' is uint64_t*. object_buffer_t object_write_to_size(void* data, object_buffer_t buffer) LIBTORRENT_EXPORT; // // static_map operations: // template class static_map_type; struct static_map_mapping_type; struct static_map_entry_type; // Convert buffer to static key map. Inlined because we don't want // a separate wrapper function for each template argument. template inline const char* static_map_read_bencode(const char* first, const char* last, static_map_type& object) { return static_map_read_bencode_c(first, last, object.values(), object.keys, object.keys + object.size); } template inline object_buffer_t static_map_write_bencode_c(object_write_t writeFunc, void* data, object_buffer_t buffer, const static_map_type& object) { return static_map_write_bencode_c_wrap(writeFunc, data, buffer, object.values(), object.keys, object.keys + object.size); } const char* static_map_read_bencode_c(const char* first, const char* last, static_map_entry_type* entry_values, const static_map_mapping_type* first_key, const static_map_mapping_type* last_key) LIBTORRENT_EXPORT; object_buffer_t static_map_write_bencode_c_wrap(object_write_t writeFunc, void* data, object_buffer_t buffer, const static_map_entry_type* entry_values, const static_map_mapping_type* first_key, const static_map_mapping_type* last_key) LIBTORRENT_EXPORT; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/path.cc000066400000000000000000000012451522271512000176000ustar00rootroot00000000000000#include "config.h" #include #include "path.h" namespace torrent { void Path::insert_path(iterator pos, const std::string& path) { std::string::const_iterator first = path.begin(); std::string::const_iterator last; while (first != path.end()) { last = std::find(first, path.end(), '/'); pos = insert(pos, string_utf8::from_string(std::string(first, last))); if (last == path.end()) return; first = last; first++; } } std::string Path::as_string() const { if (empty()) return std::string(); std::string s; for (const auto& c : *this) { s += '/'; s += c.str(); } return s; } } // namespace torrent libtorrent-0.16.17/src/torrent/path.h000066400000000000000000000022421522271512000174400ustar00rootroot00000000000000#ifndef LIBTORRENT_PATH_H #define LIBTORRENT_PATH_H #include #include #include #include namespace torrent { // Use a blank first path to get root and "." to get current dir. class LIBTORRENT_EXPORT Path : private std::vector { public: using base_type = std::vector; using const_iterator = base_type::const_iterator; using base_type::empty; using base_type::size; using base_type::front; using base_type::back; using base_type::begin; using base_type::end; using base_type::at; using base_type::operator[]; void insert_path(iterator pos, const std::string& path); void push_back(const std::string& path); // Return the path as a string with '/' deliminator. The deliminator // is only inserted between path elements. std::string as_string() const; base_type* base() { return this; } const base_type* base() const { return this; } }; inline void Path::push_back(const std::string& path) { insert_path(end(), path); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/peer/000077500000000000000000000000001522271512000172665ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/peer/choke_status.h000066400000000000000000000026451522271512000221420ustar00rootroot00000000000000#ifndef LIBTORRENT_DOWNLOAD_CHOKE_STATUS_H #define LIBTORRENT_DOWNLOAD_CHOKE_STATUS_H #include #include namespace torrent { class group_entry; class choke_status { public: group_entry* entry() const { return m_group_entry; } void set_entry(group_entry* grp_ent) { m_group_entry = grp_ent; } bool queued() const { return m_queued; } void set_queued(bool s) { m_queued = s; } bool choked() const { return !m_unchoked; } bool unchoked() const { return m_unchoked; } void set_unchoked(bool s) { m_unchoked = s; } bool snubbed() const { return m_snubbed; } void set_snubbed(bool s) { m_snubbed = s; } auto time_last_choke() const { return m_time_last_choke; } void set_time_last_choke(std::chrono::microseconds t) { m_time_last_choke = t; } private: // TODO: Use flags. group_entry* m_group_entry{}; bool m_queued{false}; bool m_unchoked{false}; bool m_snubbed{false}; std::chrono::microseconds m_time_last_choke{}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/peer/client_info.cc000066400000000000000000000076411522271512000220760ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #include "config.h" #include #include "client_info.h" namespace torrent { unsigned int ClientInfo::key_size(id_type id) { switch (id) { case TYPE_AZUREUS: return 2; case TYPE_COMPACT: case TYPE_MAINLINE: return 1; default: return 0; } } unsigned int ClientInfo::version_size(id_type id) { switch (id) { case TYPE_AZUREUS: return 4; case TYPE_COMPACT: case TYPE_MAINLINE: return 3; default: return 0; } } bool ClientInfo::less_intersects(const ClientInfo& left, const ClientInfo& right) { if (left.type() > right.type()) return false; else if (left.type() < right.type()) return true; int keyComp = std::memcmp(left.key(), right.key(), ClientInfo::max_key_size); return keyComp < 0 || (keyComp == 0 && std::memcmp(left.upper_version(), right.version(), ClientInfo::max_version_size) < 0); } bool ClientInfo::less_disjoint(const ClientInfo& left, const ClientInfo& right) { if (left.type() > right.type()) return false; else if (left.type() < right.type()) return true; int keyComp = std::memcmp(left.key(), right.key(), ClientInfo::max_key_size); return keyComp < 0 || (keyComp == 0 && std::memcmp(left.version(), right.upper_version(), ClientInfo::max_version_size) < 0); } bool ClientInfo::greater_intersects(const ClientInfo& left, const ClientInfo& right) { return less_intersects(right, left); } bool ClientInfo::greater_disjoint(const ClientInfo& left, const ClientInfo& right) { return less_disjoint(right, left); } bool ClientInfo::intersects(const ClientInfo& left, const ClientInfo& right) { return left.type() == right.type() && std::memcmp(left.key(), right.key(), ClientInfo::max_key_size) == 0 && std::memcmp(left.version(), right.upper_version(), ClientInfo::max_version_size) <= 0 && std::memcmp(left.upper_version(), right.version(), ClientInfo::max_version_size) >= 0; } inline bool ClientInfo::equal_to(const ClientInfo& left, const ClientInfo& right) { return left.type() == right.type() && std::memcmp(left.key(), right.key(), ClientInfo::max_key_size) == 0 && std::memcmp(left.version(), right.version(), ClientInfo::max_version_size) == 0 && std::memcmp(left.upper_version(), right.upper_version(), ClientInfo::max_version_size) == 0; } } // namespace torrent libtorrent-0.16.17/src/torrent/peer/client_info.h000066400000000000000000000104671522271512000217400ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_PEER_CLIENT_INFO_H #define LIBTORRENT_PEER_CLIENT_INFO_H #include namespace torrent { class LIBTORRENT_EXPORT ClientInfo { public: friend class ClientList; enum id_type { TYPE_UNKNOWN, TYPE_AZUREUS, TYPE_COMPACT, TYPE_MAINLINE, TYPE_MAX_SIZE }; static constexpr uint32_t max_key_size = 2; static constexpr uint32_t max_version_size = 4; id_type type() const { return m_type; } const char* key() const { return m_key; } const char* version() const { return m_version; } const char* upper_version() const { return m_upperVersion; } const char* short_description() const { return m_info; } void set_short_description(const char* str) { m_info = str; } static unsigned int key_size(id_type id); static unsigned int version_size(id_type id); // The intersect/disjoint postfix indicates what kind of equivalence // comparison we get when using !less && !greater. static bool less_intersects(const ClientInfo& left, const ClientInfo& right); static bool less_disjoint(const ClientInfo& left, const ClientInfo& right); static bool greater_intersects(const ClientInfo& left, const ClientInfo& right); static bool greater_disjoint(const ClientInfo& left, const ClientInfo& right); static bool intersects(const ClientInfo& left, const ClientInfo& right); static bool equal_to(const ClientInfo& left, const ClientInfo& right); protected: void set_type(id_type t) { m_type = t; } const char* info() const { return m_info; } void set_info(const char* ptr) { m_info = ptr; } char* mutable_key() { return m_key; } char* mutable_version() { return m_version; } char* mutable_upper_version() { return m_upperVersion; } private: id_type m_type; char m_key[max_key_size]; // The client version. The ClientInfo object in ClientList bounds // the versions that this object applies to. When the user searches // the ClientList for a client id, m_version will be set to the // actual client version. char m_version[max_version_size]; char m_upperVersion[max_version_size]; // We don't really care about cleaning up this as deleting an entry // form ClientList shouldn't happen. const char* m_info{}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/peer/client_list.cc000066400000000000000000000345151522271512000221160ustar00rootroot00000000000000#include "config.h" #include "torrent/peer/client_list.h" #include #include "torrent/exceptions.h" #include "torrent/hash_string.h" #include "torrent/utils/string_manip.h" namespace torrent { ClientList::ClientList() { insert(ClientInfo::TYPE_UNKNOWN, NULL, NULL, NULL); // Move this to a seperate initialize function in libtorrent. // Sorted by popularity to optimize search. This list is heavily // biased by my own prejudices, and not at all based on facts. // First batch of clients. // Updated list of clients. insert_helper(ClientInfo::TYPE_AZUREUS, "lt", NULL, NULL, "libTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "qB", NULL, NULL, "qBittorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "UT", NULL, NULL, "uTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "TR", NULL, NULL, "Transmission"); insert_helper(ClientInfo::TYPE_AZUREUS, "DE", NULL, NULL, "DelugeTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "AZ", NULL, NULL, "Vuze"); insert_helper(ClientInfo::TYPE_AZUREUS, "UM", NULL, NULL, "uTorrent Mac"); insert_helper(ClientInfo::TYPE_AZUREUS, "LT", NULL, NULL, "libtorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "BT", NULL, NULL, "Mainline"); insert_helper(ClientInfo::TYPE_MAINLINE, "M", NULL, NULL, "Mainline"); insert_helper(ClientInfo::TYPE_AZUREUS, "A2", NULL, NULL, "aria2"); insert_helper(ClientInfo::TYPE_AZUREUS, "BC", NULL, NULL, "BitComet"); insert_helper(ClientInfo::TYPE_AZUREUS, "XL", NULL, NULL, "Xunlei"); insert_helper(ClientInfo::TYPE_AZUREUS, "SD", NULL, NULL, "Xunlei"); // Other clients. insert_helper(ClientInfo::TYPE_AZUREUS, "7T", NULL, NULL, "aTorrent"); insert_helper(ClientInfo::TYPE_COMPACT, "A", NULL, NULL, "ABC"); insert_helper(ClientInfo::TYPE_AZUREUS, "A~", NULL, NULL, "Ares"); insert_helper(ClientInfo::TYPE_AZUREUS, "AG", NULL, NULL, "Ares"); insert_helper(ClientInfo::TYPE_AZUREUS, "AN", NULL, NULL, "Ares"); insert_helper(ClientInfo::TYPE_AZUREUS, "AR", NULL, NULL, "Ares"); // Ares is more likely than ArcticTorrent insert_helper(ClientInfo::TYPE_AZUREUS, "AT", NULL, NULL, "Artemis"); insert_helper(ClientInfo::TYPE_AZUREUS, "AV", NULL, NULL, "Avicora"); insert_helper(ClientInfo::TYPE_AZUREUS, "AX", NULL, NULL, "BitPump"); insert_helper(ClientInfo::TYPE_AZUREUS, "BB", NULL, NULL, "BitBuddy"); insert_helper(ClientInfo::TYPE_AZUREUS, "BE", NULL, NULL, "BitTorrent SDK"); insert_helper(ClientInfo::TYPE_AZUREUS, "BF", NULL, NULL, "BitFlu"); insert_helper(ClientInfo::TYPE_AZUREUS, "BG", NULL, NULL, "BTGetit"); insert_helper(ClientInfo::TYPE_AZUREUS, "BI", NULL, NULL, "BiglyBT"); insert_helper(ClientInfo::TYPE_AZUREUS, "bk", NULL, NULL, "BitKitten (libtorrent)"); insert_helper(ClientInfo::TYPE_AZUREUS, "BM", NULL, NULL, "BitMagnet"); insert_helper(ClientInfo::TYPE_AZUREUS, "BP", NULL, NULL, "BitTorrent Pro"); insert_helper(ClientInfo::TYPE_AZUREUS, "BR", NULL, NULL, "BitRocket"); insert_helper(ClientInfo::TYPE_AZUREUS, "BS", NULL, NULL, "BTSlave"); insert_helper(ClientInfo::TYPE_AZUREUS, "BW", NULL, NULL, "BitWombat"); insert_helper(ClientInfo::TYPE_AZUREUS, "BX", NULL, NULL, "Bittorrent X"); insert_helper(ClientInfo::TYPE_AZUREUS, "CB", NULL, NULL, "Shareaza Plus"); insert_helper(ClientInfo::TYPE_AZUREUS, "CD", NULL, NULL, "Enhanced CTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "cT", NULL, NULL, "CuteTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "CT", NULL, NULL, "CTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "DP", NULL, NULL, "Propogate Data Client"); insert_helper(ClientInfo::TYPE_AZUREUS, "EB", NULL, NULL, "EBit"); insert_helper(ClientInfo::TYPE_AZUREUS, "ES", NULL, NULL, "Electric Sheep"); insert_helper(ClientInfo::TYPE_AZUREUS, "FC", NULL, NULL, "FileCroc"); insert_helper(ClientInfo::TYPE_AZUREUS, "FD", NULL, NULL, "Free Download Manager"); insert_helper(ClientInfo::TYPE_AZUREUS, "FG", NULL, NULL, "FlashGet"); insert_helper(ClientInfo::TYPE_AZUREUS, "FL", NULL, NULL, "Flud"); insert_helper(ClientInfo::TYPE_AZUREUS, "FT", NULL, NULL, "FoxTorrent/RedSwoosh"); insert_helper(ClientInfo::TYPE_AZUREUS, "FW", NULL, NULL, "FrostWire"); insert_helper(ClientInfo::TYPE_AZUREUS, "FX", NULL, NULL, "Freebox BitTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "GR", NULL, NULL, "GetRight"); insert_helper(ClientInfo::TYPE_AZUREUS, "GS", NULL, NULL, "GSTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "GT", NULL, NULL, "go.torrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "HL", NULL, NULL, "Halite"); insert_helper(ClientInfo::TYPE_AZUREUS, "HN", NULL, NULL, "Hydranode"); insert_helper(ClientInfo::TYPE_AZUREUS, "IL", NULL, NULL, "iLivid"); insert_helper(ClientInfo::TYPE_AZUREUS, "JS", NULL, NULL, "JSTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "JT", NULL, NULL, "jTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "jT", NULL, NULL, "jTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "KG", NULL, NULL, "KGet"); insert_helper(ClientInfo::TYPE_AZUREUS, "KT", NULL, NULL, "KTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "LC", NULL, NULL, "LeechCraft"); insert_helper(ClientInfo::TYPE_AZUREUS, "LH", NULL, NULL, "LH-ABC"); insert_helper(ClientInfo::TYPE_AZUREUS, "LK", NULL, NULL, "linkage"); insert_helper(ClientInfo::TYPE_AZUREUS, "LP", NULL, NULL, "Lphant"); insert_helper(ClientInfo::TYPE_AZUREUS, "Lr", NULL, NULL, "LibreTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "LW", NULL, NULL, "LimeWire"); insert_helper(ClientInfo::TYPE_AZUREUS, "MG", NULL, NULL, "MediaGet"); insert_helper(ClientInfo::TYPE_AZUREUS, "MO", NULL, NULL, "MonoTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "MP", NULL, NULL, "MooPolice"); insert_helper(ClientInfo::TYPE_AZUREUS, "MR", NULL, NULL, "Miro"); insert_helper(ClientInfo::TYPE_AZUREUS, "MT", NULL, NULL, "MoonlightTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "NE", NULL, NULL, "BT Next Evolution"); insert_helper(ClientInfo::TYPE_AZUREUS, "NX", NULL, NULL, "Net Transport"); insert_helper(ClientInfo::TYPE_COMPACT, "O", NULL, NULL, "Osprey Permaseed"); insert_helper(ClientInfo::TYPE_AZUREUS, "OS", NULL, NULL, "OneSwarm"); insert_helper(ClientInfo::TYPE_AZUREUS, "OT", NULL, NULL, "OmegaTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "PC", NULL, NULL, "CacheLogic"); insert_helper(ClientInfo::TYPE_AZUREUS, "PI", NULL, NULL, "PicoTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "PT", NULL, NULL, "Popcorn Time"); insert_helper(ClientInfo::TYPE_AZUREUS, "PD", NULL, NULL, "Pando"); insert_helper(ClientInfo::TYPE_AZUREUS, "pX", NULL, NULL, "pHoeniX"); insert_helper(ClientInfo::TYPE_COMPACT, "Q", NULL, NULL, "BTQueue"); insert_helper(ClientInfo::TYPE_AZUREUS, "QD", NULL, NULL, "qqdownload"); insert_helper(ClientInfo::TYPE_AZUREUS, "QT", NULL, NULL, "Qt 4 Torrent"); insert_helper(ClientInfo::TYPE_COMPACT, "R", NULL, NULL, "Tribler"); insert_helper(ClientInfo::TYPE_AZUREUS, "RS", NULL, NULL, "Rufus"); insert_helper(ClientInfo::TYPE_AZUREUS, "RT", NULL, NULL, "Retriever"); insert_helper(ClientInfo::TYPE_AZUREUS, "RZ", NULL, NULL, "RezTorrent"); insert_helper(ClientInfo::TYPE_COMPACT, "S", NULL, NULL, "Shadow's client"); insert_helper(ClientInfo::TYPE_AZUREUS, "S~", NULL, NULL, "Shareaza alpha/beta"); insert_helper(ClientInfo::TYPE_AZUREUS, "SB", NULL, NULL, "SwiftBit"); insert_helper(ClientInfo::TYPE_AZUREUS, "SG", NULL, NULL, "GS Torrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "SK", NULL, NULL, "Spark"); insert_helper(ClientInfo::TYPE_AZUREUS, "SM", NULL, NULL, "SoMud"); insert_helper(ClientInfo::TYPE_AZUREUS, "SN", NULL, NULL, "ShareNET"); insert_helper(ClientInfo::TYPE_AZUREUS, "SP", NULL, NULL, "BitSpirit"); insert_helper(ClientInfo::TYPE_AZUREUS, "SS", NULL, NULL, "SwarmScope"); insert_helper(ClientInfo::TYPE_AZUREUS, "ST", NULL, NULL, "SymTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "st", NULL, NULL, "SharkTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "SZ", NULL, NULL, "Shareaza"); insert_helper(ClientInfo::TYPE_AZUREUS, "tT", NULL, NULL, "tTorrent"); insert_helper(ClientInfo::TYPE_COMPACT, "T", NULL, NULL, "BitTornado"); insert_helper(ClientInfo::TYPE_AZUREUS, "TB", NULL, NULL, "Torch"); insert_helper(ClientInfo::TYPE_AZUREUS, "TG", NULL, NULL, "Torrent GO"); insert_helper(ClientInfo::TYPE_AZUREUS, "TL", NULL, NULL, "Tribler"); insert_helper(ClientInfo::TYPE_AZUREUS, "TN", NULL, NULL, "Torrent.NET"); insert_helper(ClientInfo::TYPE_AZUREUS, "TS", NULL, NULL, "Torrentstorm"); insert_helper(ClientInfo::TYPE_AZUREUS, "TT", NULL, NULL, "TuoTu"); insert_helper(ClientInfo::TYPE_COMPACT, "U", NULL, NULL, "UPnP NAT BitTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "UE", NULL, NULL, "uTorrent Embedded"); insert_helper(ClientInfo::TYPE_AZUREUS, "UL", NULL, NULL, "uLeecher!"); insert_helper(ClientInfo::TYPE_AZUREUS, "UW", NULL, NULL, "uTorrent Web"); insert_helper(ClientInfo::TYPE_AZUREUS, "WD", NULL, NULL, "WebTorrent Desktop"); insert_helper(ClientInfo::TYPE_AZUREUS, "WT", NULL, NULL, "Bitlet"); insert_helper(ClientInfo::TYPE_AZUREUS, "WW", NULL, NULL, "WebTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "WY", NULL, NULL, "FireTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "VG", NULL, NULL, "Vagaa"); insert_helper(ClientInfo::TYPE_AZUREUS, "XC", NULL, NULL, "XTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "XF", NULL, NULL, "Xfplay"); insert_helper(ClientInfo::TYPE_AZUREUS, "XT", NULL, NULL, "XanTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "XX", NULL, NULL, "XTorrent"); insert_helper(ClientInfo::TYPE_AZUREUS, "ZO", NULL, NULL, "Zona"); insert_helper(ClientInfo::TYPE_AZUREUS, "ZT", NULL, NULL, "ZipTorrent"); } ClientList::iterator ClientList::insert(ClientInfo::id_type type, const char* key, const char* version, const char* upperVersion) { if (type >= ClientInfo::TYPE_MAX_SIZE) throw input_error("Invalid client info id type."); ClientInfo clientInfo; clientInfo.set_type(type); clientInfo.set_short_description("Unknown"); std::memset(clientInfo.mutable_key(), 0, ClientInfo::max_key_size); if (key == NULL) std::memset(clientInfo.mutable_key(), 0, ClientInfo::max_key_size); else std::memcpy(clientInfo.mutable_key(), key, ClientInfo::max_key_size); if (version != NULL) std::memcpy(clientInfo.mutable_version(), version, ClientInfo::max_version_size); else std::memset(clientInfo.mutable_version(), 0, ClientInfo::max_version_size); if (upperVersion != NULL) std::memcpy(clientInfo.mutable_upper_version(), upperVersion, ClientInfo::max_version_size); else std::memset(clientInfo.mutable_upper_version(), -1, ClientInfo::max_version_size); return base_type::insert(end(), clientInfo); } ClientList::iterator ClientList::insert_helper(ClientInfo::id_type type, const char* key, const char* version, const char* upperVersion, const char* shortDescription) { char newKey[ClientInfo::max_key_size]; std::memset(newKey, 0, ClientInfo::max_key_size); std::memcpy(newKey, key, ClientInfo::key_size(type)); auto itr = insert(type, newKey, version, upperVersion); itr->set_short_description(shortDescription); return itr; } // Make this properly honor const-ness. bool ClientList::retrieve_id(ClientInfo* dest, const HashString& id) const { if (id[0] == '-' && id[7] == '-' && std::isalpha(id[1]) && std::isalpha(id[2]) && std::isxdigit(id[3]) && std::isxdigit(id[4]) && std::isxdigit(id[5]) && std::isxdigit(id[6])) { dest->set_type(ClientInfo::TYPE_AZUREUS); dest->mutable_key()[0] = id[1]; dest->mutable_key()[1] = id[2]; for (int i = 0; i < 4; i++) dest->mutable_version()[i] = dest->mutable_upper_version()[i] = utils::hex_to_value_or_zero(id[3 + i]); } else if (std::isalpha(id[0]) && id[4] == '-' && std::isxdigit(id[1]) && std::isxdigit(id[2]) && std::isxdigit(id[3])) { dest->set_type(ClientInfo::TYPE_COMPACT); dest->mutable_key()[0] = id[0]; dest->mutable_key()[1] = '\0'; dest->mutable_version()[0] = dest->mutable_upper_version()[0] = utils::hex_to_value_or_zero(id[1]); dest->mutable_version()[1] = dest->mutable_upper_version()[1] = utils::hex_to_value_or_zero(id[2]); dest->mutable_version()[2] = dest->mutable_upper_version()[2] = utils::hex_to_value_or_zero(id[3]); dest->mutable_version()[3] = dest->mutable_upper_version()[3] = '\0'; } else if (std::isalpha(id[0]) && std::isdigit(id[1]) && id[2] == '-' && std::isdigit(id[3]) && (id[6] == '-' || id[7] == '-')) { dest->set_type(ClientInfo::TYPE_MAINLINE); dest->mutable_key()[0] = id[0]; dest->mutable_key()[1] = '\0'; dest->mutable_version()[0] = dest->mutable_upper_version()[0] = utils::hex_to_value_or_zero(id[1]); if (id[4] == '-' && std::isdigit(id[5]) && id[6] == '-') { dest->mutable_version()[1] = dest->mutable_upper_version()[1] = utils::hex_to_value_or_zero(id[3]); dest->mutable_version()[2] = dest->mutable_upper_version()[2] = utils::hex_to_value_or_zero(id[5]); dest->mutable_version()[3] = dest->mutable_upper_version()[3] = '\0'; } else if (std::isdigit(id[4]) && id[5] == '-' && std::isdigit(id[6]) && id[7] == '-') { dest->mutable_version()[1] = dest->mutable_upper_version()[1] = utils::hex_to_value_or_zero(id[3]) * 10 + utils::hex_to_value_or_zero(id[4]); dest->mutable_version()[2] = dest->mutable_upper_version()[2] = utils::hex_to_value_or_zero(id[6]); dest->mutable_version()[3] = dest->mutable_upper_version()[3] = '\0'; } else { *dest = *begin(); std::memset(dest->mutable_upper_version(), 0, ClientInfo::max_version_size); return false; } } else { // And then the incompatible idiots that make life difficult for us // others. (There's '3' schemes to choose from already...) // // Or not... // The first entry always contains the default ClientInfo. *dest = *begin(); std::memset(dest->mutable_upper_version(), 0, ClientInfo::max_version_size); return false; } auto itr = std::find_if(std::next(begin()), end(), [dest](const auto& client) { return ClientInfo::intersects(*dest, client); }); if (itr == end()) dest->set_info(begin()->info()); else dest->set_info(itr->info()); return true; } void ClientList::retrieve_unknown(ClientInfo* dest) const { *dest = *begin(); std::memset(dest->mutable_upper_version(), 0, ClientInfo::max_version_size); } } // namespace torrent libtorrent-0.16.17/src/torrent/peer/client_list.h000066400000000000000000000060741522271512000217570ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_PEER_CLIENT_LIST_H #define LIBTORRENT_PEER_CLIENT_LIST_H #include #include namespace torrent { class LIBTORRENT_EXPORT ClientList : private std::vector { public: using base_type = std::vector; using size_type = uint32_t; using base_type::value_type; using base_type::reference; using base_type::difference_type; using base_type::iterator; using base_type::reverse_iterator; using base_type::size; using base_type::empty; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; ClientList(); ~ClientList() = default; ClientList(const ClientList&) = delete; ClientList& operator=(const ClientList&) = delete; iterator insert(ClientInfo::id_type type, const char* key, const char* version, const char* upperVersion); // Helper functions which only require the key to be as long as the // key for that specific id type. iterator insert_helper(ClientInfo::id_type type, const char* key, const char* version, const char* upperVersion, const char* shortDescription); bool retrieve_id(ClientInfo* dest, const HashString& id) const; void retrieve_unknown(ClientInfo* dest) const; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/peer/connection_list.cc000066400000000000000000000140631522271512000227730ustar00rootroot00000000000000#include "config.h" #include "torrent/peer/connection_list.h" #include #include "download/download_main.h" #include "net/address_list.h" #include "protocol/peer_connection_base.h" #include "torrent/exceptions.h" #include "torrent/download_info.h" #include "torrent/download/choke_group.h" #include "torrent/download/choke_queue.h" #include "torrent/net/socket_address.h" #include "torrent/peer/peer.h" #include "torrent/peer/peer_info.h" #include "utils/functional.h" // When a peer is connected it should be removed from the list of // available peers. // // When a peer is disconnected the torrent should rebalance the choked // peers and connect to new ones if possible. namespace torrent { ConnectionList::ConnectionList(DownloadMain* download) : m_download(download) { } void ConnectionList::clear() { for (const auto& peer : *this) { delete peer->m_ptr(); } base_type::clear(); m_disconnectQueue.clear(); } bool ConnectionList::want_connection([[maybe_unused]] PeerInfo* p, Bitfield* bitfield) { if (m_download->file_list()->is_done() || m_download->initial_seeding() != NULL) return !bitfield->is_all_set(); if (!m_download->info()->is_accepting_seeders()) return !bitfield->is_all_set(); return true; } PeerConnectionBase* ConnectionList::insert(PeerInfo* peerInfo, int fd, Bitfield* bitfield, EncryptionInfo* encryptionInfo, ProtocolExtension* extensions) { if (size() >= m_maxSize) return NULL; PeerConnectionBase* peerConnection = m_slotNewConnection(encryptionInfo->is_encrypted()); if (peerConnection == NULL || bitfield == NULL) throw internal_error("ConnectionList::insert(...) received a NULL pointer."); peerInfo->set_connection(peerConnection); peerInfo->set_last_connection(this_thread::cached_seconds().count()); peerConnection->initialize(m_download, peerInfo, fd, bitfield, encryptionInfo, extensions); if (peerConnection->file_descriptor() == -1) { delete peerConnection; return NULL; } base_type::push_back(peerConnection); m_download->info()->change_flags(DownloadInfo::flag_accepting_new_peers, size() < m_maxSize); ::utils::slot_list_call(m_signalConnected, peerConnection); return peerConnection; } ConnectionList::iterator ConnectionList::erase(iterator pos, int flags) { if (pos < begin() || pos >= end()) throw internal_error("ConnectionList::erase(...) iterator out or range."); if (flags & disconnect_delayed) { m_disconnectQueue.push_back((*pos)->id()); this_thread::scheduler()->update_wait_for(&m_download->delay_disconnect_peers(), 0us); return pos; } PeerConnectionBase* peerConnection = (*pos)->m_ptr(); // The connection must be erased from the list before the signal is // emited otherwise some listeners might do stuff with the // assumption that the connection will remain in the list. *pos = base_type::back(); base_type::pop_back(); m_download->info()->change_flags(DownloadInfo::flag_accepting_new_peers, size() < m_maxSize); ::utils::slot_list_call(m_signalDisconnected, peerConnection); // Before of after the signal? peerConnection->cleanup(); peerConnection->mutable_peer_info()->set_connection(NULL); m_download->peer_list()->disconnected(peerConnection->mutable_peer_info(), PeerList::disconnect_set_time); // Delete after the signal to ensure the address of 'v' doesn't get // allocated for a different PCB in the signal. delete peerConnection; return pos; } void ConnectionList::erase(Peer* p, int flags) { erase(std::find(begin(), end(), p), flags); } void ConnectionList::erase(PeerInfo* peerInfo, int flags) { auto itr = std::find(begin(), end(), peerInfo->connection()); if (itr == end()) return; erase(itr, flags); } void ConnectionList::erase_remaining(iterator pos, int flags) { flags |= disconnect_quick; // Need to do it one connection at the time to ensure that when the // signal is emited everything is in a valid state. while (pos != end()) erase(--end(), flags); m_download->info()->change_flags(DownloadInfo::flag_accepting_new_peers, size() < m_maxSize); } void ConnectionList::erase_seeders() { erase_remaining(std::partition(begin(), end(), [](Peer* p) { return p->c_ptr()->is_not_seeder(); }), disconnect_unwanted); } void ConnectionList::disconnect_queued() { for (const auto& queue : m_disconnectQueue) { auto conn_itr = find(queue.c_str()); if (conn_itr != end()) erase(conn_itr, 0); } m_disconnectQueue.clear(); } struct connection_list_less { bool operator () (const Peer* p1, const Peer* p2) const { return sa_less(p1->peer_info()->socket_address(), p2->peer_info()->socket_address()); } bool operator () (const sa_inet_union& sa, const Peer* p2) const { return sa_less(&sa.sa, p2->peer_info()->socket_address()); } bool operator () (const Peer* p1, const sa_inet_union& sa) const { return sa_less(p1->peer_info()->socket_address(), &sa.sa); } }; ConnectionList::iterator ConnectionList::find(const char* id) { return std::find_if(begin(), end(), [id](Peer* p) { return *HashString::cast_from(id) == p->m_ptr()->peer_info()->id(); }); } ConnectionList::iterator ConnectionList::find(const sockaddr* sa) { return std::find_if(begin(), end(), [sa](Peer* p) { return sa_equal(p->m_ptr()->peer_info()->socket_address(), sa); }); } void ConnectionList::set_difference(AddressList* l) { std::sort(begin(), end(), connection_list_less()); l->erase(std::set_difference(l->begin(), l->end(), begin(), end(), l->begin(), connection_list_less()), l->end()); } void ConnectionList::set_min_size(size_type v) { if (v > (1 << 16)) throw input_error("Min peer connections must be between 0 and 2^16."); m_minSize = v; } void ConnectionList::set_max_size(size_type v) { if (v > (1 << 16)) throw input_error("Max peer connections must be between 0 and 2^16."); if (v == 0) v = choke_queue::unlimited; m_maxSize = v; m_download->info()->change_flags(DownloadInfo::flag_accepting_new_peers, size() < m_maxSize); //m_download->choke_group()->up_queue()->balance(); } } // namespace torrent libtorrent-0.16.17/src/torrent/peer/connection_list.h000066400000000000000000000075671522271512000226500ustar00rootroot00000000000000#ifndef LIBTORRENT_PEER_CONNECTION_LIST_H #define LIBTORRENT_PEER_CONNECTION_LIST_H #include #include #include #include #include namespace torrent { class AddressList; class Bitfield; class DownloadMain; class DownloadWrapper; class Peer; class PeerConnectionBase; class PeerInfo; class ProtocolExtension; class EncryptionInfo; class HandshakeManager; class LIBTORRENT_EXPORT ConnectionList : private std::vector { public: friend class DownloadMain; friend class DownloadWrapper; friend class HandshakeManager; using base_type = std::vector; using queue_type = std::vector; using size_type = uint32_t; using slot_peer_type = std::function; using signal_peer_type = std::list; using slot_new_conn_type = PeerConnectionBase* (*)(bool encrypted); using base_type::value_type; using base_type::reference; using base_type::difference_type; using base_type::iterator; using base_type::reverse_iterator; using base_type::const_iterator; using base_type::const_reverse_iterator; // using base_type::size; using base_type::empty; using base_type::front; using base_type::back; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; // Make sure any change here match PeerList's flags. static constexpr int disconnect_available = (1 << 0); static constexpr int disconnect_quick = (1 << 1); static constexpr int disconnect_unwanted = (1 << 2); static constexpr int disconnect_delayed = (1 << 3); ConnectionList(DownloadMain* download); ~ConnectionList() = default; ConnectionList(const ConnectionList&) = delete; ConnectionList& operator=(const ConnectionList&) = delete; // Make these protected? iterator erase(iterator pos, int flags); void erase(Peer* p, int flags); void erase(PeerInfo* peerInfo, int flags); void erase_remaining(iterator pos, int flags); void erase_seeders(); iterator find(const char* id); iterator find(const sockaddr* sa); size_type min_size() const { return m_minSize; } void set_min_size(size_type v); size_type size() const { return base_type::size(); } size_type max_size() const { return m_maxSize; } void set_max_size(size_type v); // Removes from 'l' addresses that are already connected to. Assumes // 'l' is sorted and unique. void set_difference(AddressList* l); signal_peer_type& signal_connected() { return m_signalConnected; } signal_peer_type& signal_disconnected() { return m_signalDisconnected; } // Move to protected: void slot_new_connection(slot_new_conn_type s) { m_slotNewConnection = s; } protected: // Does not do the usual cleanup done by 'erase'. void clear() LIBTORRENT_NO_EXPORT; bool want_connection(PeerInfo* p, Bitfield* bitfield); // Returns NULL if the connection was not added, the caller is then // responsible for cleaning up 'fd'. // // Clean this up, don't use this many arguments. PeerConnectionBase* insert(PeerInfo* p, int fd, Bitfield* bitfield, EncryptionInfo* encryptionInfo, ProtocolExtension* extensions) LIBTORRENT_NO_EXPORT; void disconnect_queued() LIBTORRENT_NO_EXPORT; private: DownloadMain* m_download; size_type m_minSize{50}; size_type m_maxSize{100}; signal_peer_type m_signalConnected; signal_peer_type m_signalDisconnected; slot_new_conn_type m_slotNewConnection; queue_type m_disconnectQueue; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/peer/peer.cc000066400000000000000000000054701522271512000205360ustar00rootroot00000000000000#include "config.h" #include "data/block_transfer.h" #include "download/download_main.h" #include "protocol/peer_chunks.h" #include "protocol/peer_connection_base.h" #include "torrent/download/choke_queue.h" #include "connection_list.h" #include "peer.h" #include "peer_info.h" #include "rate.h" namespace torrent { Peer::~Peer() = default; bool Peer::is_incoming() const { return peer_info()->is_incoming(); } bool Peer::is_encrypted() const { return c_ptr()->is_encrypted(); } bool Peer::is_obfuscated() const { return c_ptr()->is_obfuscated(); } bool Peer::is_up_choked() const { return c_ptr()->is_up_choked(); } bool Peer::is_up_interested() const { return c_ptr()->is_down_interested(); } bool Peer::is_down_choked() const { return !c_ptr()->is_down_remote_unchoked(); } bool Peer::is_down_choked_limited() const { return !c_ptr()->is_down_local_unchoked(); } bool Peer::is_down_queued() const { return c_ptr()->is_down_queued(); } bool Peer::is_down_interested() const { return c_ptr()->is_up_interested(); } bool Peer::is_snubbed() const { return c_ptr()->is_up_snubbed(); } bool Peer::is_banned() const { return m_peerInfo->failed_counter() >= 64; } void Peer::set_snubbed(bool v) { m_ptr()->set_upload_snubbed(v); } void Peer::set_banned(bool v) { m_peerInfo->set_failed_counter(v ? 64 : 0); } const HashString& Peer::id() const { return peer_info()->id(); } const char* Peer::options() const { return peer_info()->options(); } const sockaddr* Peer::address() const { return peer_info()->socket_address(); } const Rate* Peer::down_rate() const { return c_ptr()->c_peer_chunks()->download_throttle()->rate(); } const Rate* Peer::up_rate() const { return c_ptr()->c_peer_chunks()->upload_throttle()->rate(); } const Rate* Peer::peer_rate() const { return c_ptr()->c_peer_chunks()->peer_rate(); } const Bitfield* Peer::bitfield() const { return c_ptr()->c_peer_chunks()->bitfield(); } uint32_t Peer::incoming_queue_size() const { return c_ptr()->request_list()->queued_size(); } uint32_t Peer::outgoing_queue_size() const { return c_ptr()->c_peer_chunks()->upload_queue()->size(); } uint32_t Peer::chunks_done() const { return c_ptr()->c_peer_chunks()->bitfield()->size_set(); } uint32_t Peer::failed_counter() const { return peer_info()->failed_counter(); } const BlockTransfer* Peer::transfer() const { if (c_ptr()->request_list()->transfer() != NULL) return c_ptr()->request_list()->transfer(); else if (!c_ptr()->request_list()->queued_empty()) return c_ptr()->request_list()->queued_front(); else return NULL; } void Peer::disconnect(int flags) { m_ptr()->download()->connection_list()->erase(this, flags); } } // namespace torrent libtorrent-0.16.17/src/torrent/peer/peer.h000066400000000000000000000044141522271512000203750ustar00rootroot00000000000000#ifndef LIBTORRENT_PEER_H #define LIBTORRENT_PEER_H #include #include namespace torrent { class PeerConnectionBase; // == and = operators works as expected. // The Peer class is a wrapper around the internal peer class. This // peer class may be invalidated during a torrent::work call. So if // you keep a list or single instances in the client, you need to // listen to the appropriate signals from the download to keep up to // date. class LIBTORRENT_EXPORT Peer { public: virtual ~Peer(); Peer(const Peer&) = delete; Peer& operator=(const Peer&) = delete; // Does not check if it has been removed from the download. bool is_incoming() const; bool is_encrypted() const; bool is_obfuscated() const; bool is_up_choked() const; bool is_up_interested() const; bool is_down_choked() const; bool is_down_choked_limited() const; bool is_down_queued() const; bool is_down_interested() const; bool is_snubbed() const; bool is_banned() const; void set_snubbed(bool v); void set_banned(bool v); const HashString& id() const; const char* options() const; const sockaddr* address() const; const Rate* down_rate() const; const Rate* up_rate() const; const Rate* peer_rate() const; const Bitfield* bitfield() const; const BlockTransfer* transfer() const; uint32_t incoming_queue_size() const; uint32_t outgoing_queue_size() const; uint32_t chunks_done() const; uint32_t failed_counter() const; void disconnect(int flags); // // New interface: // const PeerInfo* peer_info() const { return m_peerInfo; } PeerConnectionBase* m_ptr() { return reinterpret_cast(this); } const PeerConnectionBase* c_ptr() const { return reinterpret_cast(this); } protected: Peer() = default; bool operator == (const Peer& p) const; PeerInfo* m_peerInfo; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/peer/peer_info.cc000066400000000000000000000013551522271512000215470ustar00rootroot00000000000000#include "config.h" #include "utils/instrumentation.h" #include #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/peer/peer_info.h" namespace torrent { // TODO: Use a safer socket address parameter. PeerInfo::PeerInfo(const sockaddr* address) { m_address = sa_copy(address); } PeerInfo::~PeerInfo() { // if (m_transferCounter != 0) // throw internal_error("PeerInfo::~PeerInfo() m_transferCounter != 0."); instrumentation_update(INSTRUMENTATION_TRANSFER_PEER_INFO_UNACCOUNTED, m_transferCounter); assert(!is_blocked() && "PeerInfo::~PeerInfo() peer is blocked."); } void PeerInfo::set_port(uint16_t port) { return sa_set_port(m_address.get(), port); } } // namespace torrent libtorrent-0.16.17/src/torrent/peer/peer_info.h000066400000000000000000000116221522271512000214070ustar00rootroot00000000000000#ifndef LIBTORRENT_PEER_INFO_H #define LIBTORRENT_PEER_INFO_H #include #include #include #include namespace torrent { class LIBTORRENT_EXPORT PeerInfo { public: friend class ConnectionList; friend class Handshake; friend class HandshakeManager; friend class InitialSeeding; friend class PeerList; friend class ProtocolExtension; static constexpr int flag_connected = (1 << 0); static constexpr int flag_incoming = (1 << 1); static constexpr int flag_handshake = (1 << 2); static constexpr int flag_blocked = (1 << 3); // For initial seeding. static constexpr int flag_restart = (1 << 4); static constexpr int flag_unwanted = (1 << 5); static constexpr int flag_preferred = (1 << 6); static constexpr int mask_ip_table = flag_unwanted | flag_preferred; PeerInfo(const sockaddr* address); ~PeerInfo(); bool is_connected() const { return m_flags & flag_connected; } bool is_incoming() const { return m_flags & flag_incoming; } bool is_handshake() const { return m_flags & flag_handshake; } bool is_blocked() const { return m_flags & flag_blocked; } bool is_restart() const { return m_flags & flag_restart; } bool is_unwanted() const { return m_flags & flag_unwanted; } bool is_preferred() const { return m_flags & flag_preferred; } int flags() const { return m_flags; } const HashString& id() const { return m_id; } const char* id_hex() const { return m_id_hex; } const ClientInfo& client_info() const { return m_clientInfo; } const char* options() const { return m_options; } const sockaddr* socket_address() const { return m_address.get(); } uint16_t listen_port() const { return m_listenPort; } uint32_t failed_counter() const { return m_failedCounter; } void set_failed_counter(uint32_t c) { m_failedCounter = c; } uint32_t transfer_counter() const { return m_transferCounter; } uint32_t last_connection() const { return m_lastConnection; } void set_last_connection(uint32_t tvsec) { m_lastConnection = tvsec; } uint32_t last_handshake() const { return m_lastHandshake; } void set_last_handshake(uint32_t tvsec) { m_lastHandshake = tvsec; } bool supports_dht() const { return m_options[7] & 0x01; } bool supports_extensions() const { return m_options[5] & 0x10; } // // Internal to libTorrent: // PeerConnectionBase* connection() { return m_connection; } void inc_transfer_counter(); void dec_transfer_counter(); protected: void set_flags(int flags) { m_flags |= flags; } void unset_flags(int flags) { m_flags &= ~flags; } HashString& mutable_id() { return m_id; } char* mutable_id_hex() { return m_id_hex; } ClientInfo& mutable_client_info() { return m_clientInfo; } void set_port(uint16_t port) LIBTORRENT_NO_EXPORT; void set_listen_port(uint16_t port) { m_listenPort = port; } char* set_options() { return m_options; } void set_connection(PeerConnectionBase* c) { m_connection = c; } private: PeerInfo(const PeerInfo&) = delete; PeerInfo& operator=(const PeerInfo&) = delete; // Replace id with a char buffer, or a cheap struct? int m_flags{0}; HashString m_id; char m_id_hex[40]; ClientInfo m_clientInfo; char m_options[8]; uint32_t m_failedCounter{0}; uint32_t m_transferCounter{0}; uint32_t m_lastConnection{0}; uint32_t m_lastHandshake{0}; uint16_t m_listenPort{0}; sa_unique_ptr m_address; PeerConnectionBase* m_connection{}; }; inline void PeerInfo::inc_transfer_counter() { if (m_transferCounter == ~uint32_t()) throw internal_error("PeerInfo::inc_transfer_counter() m_transferCounter overflow"); m_transferCounter++; } inline void PeerInfo::dec_transfer_counter() { if (m_transferCounter == 0) throw internal_error("PeerInfo::dec_transfer_counter() m_transferCounter underflow"); m_transferCounter--; } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/peer/peer_list.cc000066400000000000000000000312511522271512000215650ustar00rootroot00000000000000#include "config.h" #include "peer_list.h" #include #include "manager.h" #include "download/available_list.h" #include "torrent/download_info.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/peer/client_list.h" #include "torrent/peer/peer_info.h" #include "torrent/utils/log.h" #define LT_LOG_EVENTS(log_fmt, ...) \ lt_log_print_info(LOG_PEER_LIST_EVENTS, m_info, "peer_list", log_fmt, __VA_ARGS__); #define LT_LOG_ADDRESS(log_fmt, ...) \ lt_log_print_info(LOG_PEER_LIST_ADDRESS, m_info, "peer_list", log_fmt, __VA_ARGS__); namespace torrent { ipv4_table PeerList::m_ipv4_table; PeerList::PeerList() : m_available_list(new AvailableList) { } PeerList::~PeerList() { LT_LOG_EVENTS("deleting list total:%zu available:%zu", size(), m_available_list->size()); base_type::clear(); m_info = nullptr; } void PeerList::set_info(DownloadInfo* info) { m_info = info; LT_LOG_EVENTS("creating list", 0); } PeerInfo* PeerList::insert_address(const sockaddr* sa, int flags) { socket_address_key sock_key = socket_address_key::from_sockaddr(sa); if (sock_key.is_valid() && !socket_address_key::is_comparable_sockaddr(sa)) { LT_LOG_EVENTS("adding address: not comparable: %s", sa_pretty_str(sa).c_str()); return nullptr; } range_type range = base_type::equal_range(sock_key); auto addr_str = sa_addr_str(sa); auto port = sa_port(sa); // Do some special handling if we got a new port number but the // address was present. // // What we do depends on the flags, but for now just allow one // PeerInfo per address key and do nothing. if (range.first != range.second) { LT_LOG_EVENTS("adding address: already exists: %s", sa_pretty_str(sa).c_str()); return nullptr; } auto peer_info = std::make_unique(sa); peer_info->set_listen_port(port); if (sa->sa_family == AF_INET) { // IPv4 addresses are stored in host byte order in ipv4_table so they are comparable. uint32_t host_byte_order_ipv4_addr = ntohl(reinterpret_cast(sa)->sin_addr.s_addr); if(m_ipv4_table.defined(host_byte_order_ipv4_addr)) peer_info->set_flags(m_ipv4_table.at(host_byte_order_ipv4_addr) & PeerInfo::mask_ip_table); } else if (sa->sa_family == AF_INET6) { // Currently nothing to do for IPv6 addresses. } else { throw internal_error("PeerList::insert_address() only supports INET/INET6 addresses"); } manager->client_list()->retrieve_unknown(&peer_info->mutable_client_info()); if ((flags & address_available) && peer_info->listen_port() != 0) { m_available_list->insert_unique(sa); LT_LOG_EVENTS("adding address: available : %s", sa_pretty_str(sa).c_str()); } else { LT_LOG_EVENTS("adding address: unavailable : %s", sa_pretty_str(sa).c_str()); } auto itr = base_type::insert(range.second, value_type(sock_key, peer_info.release())); return itr->second.get(); } uint32_t PeerList::insert_available(const void* al) { auto address_list = static_cast(al); uint32_t inserted = 0; uint32_t invalid = 0; uint32_t unneeded = 0; uint32_t updated = 0; if (m_available_list->size() + address_list->size() > m_available_list->capacity()) m_available_list->reserve(m_available_list->size() + address_list->size() + 128); // Optimize this so that we don't traverse the tree for every // insert, since we know 'al' is sorted. auto avail_itr = m_available_list->begin(); auto avail_last = m_available_list->end(); for (const auto& addr : *address_list) { auto addr_str = sa_addr_str(&addr.sa); auto port = sa_port(&addr.sa); if (!socket_address_key::is_comparable_sockaddr(&addr.sa) || port == 0) { invalid++; LT_LOG_ADDRESS("adding available address: skipped invalid : %s", sa_pretty_str(&addr.sa).c_str()); continue; } // TODO: Verify we only want to check the address and not the port. avail_itr = std::find_if(avail_itr, avail_last, [&addr](auto& sa) { return sa_less_addr(&sa.sa, &addr.sa); }); if (avail_itr != avail_last && !sa_less_addr(&avail_itr->sa, &addr.sa)) { // The address is already in m_available_list, so don't bother // going further. unneeded++; continue; } socket_address_key sock_key = socket_address_key::from_sockaddr(&addr.sa); // Check if the peerinfo exists, if it does, check if we would // ever want to connect. Just update the timer for the last // availability notice if the peer isn't really ideal, but might // be used in an emergency. range_type range = base_type::equal_range(sock_key); if (range.first != range.second) { // Add some logic here to select the best PeerInfo, but for now // just assume the first one is the only one that exists. PeerInfo* peer_info = range.first->second.get(); if (peer_info->listen_port() == 0) peer_info->set_port(port); if (peer_info->connection() != nullptr || peer_info->last_handshake() + 600 > static_cast(this_thread::cached_seconds().count())) { updated++; continue; } // If the peer has sent us bad chunks or we just connected or // tried to do so a few minutes ago, only update its // availability timer. } // Should we perhaps add to available list even though we don't // want the peer, just to ensure we don't need to search for the // PeerInfo every time it gets reported. Though I'd assume it // won't happen often enough to be worth it. inserted++; m_available_list->insert_unique(&addr.sa); LT_LOG_ADDRESS("adding available address: %s", sa_pretty_str(&addr.sa).c_str()); } LT_LOG_EVENTS("inserted peers" " inserted:%" PRIu32 " invalid:%" PRIu32 " unneeded:%" PRIu32 " updated:%" PRIu32 " total:%" PRIuPTR " available:%" PRIuPTR, inserted, invalid, unneeded, updated, size(), m_available_list->size()); return inserted; } uint32_t PeerList::available_list_size() const { return m_available_list->size(); } // TODO: Rename connecting: PeerInfo* PeerList::connected(const sockaddr* sa, int flags) { // TODO: Rewrite to use new socket address api after fixing bug. // TODO: Handle 4in6 addresses. auto sock_key = socket_address_key::from_sockaddr(sa); auto addr_str = sa_addr_str(sa); auto port = sa_port(sa); if (!sock_key.is_valid() || !socket_address_key::is_comparable_sockaddr(sa)) return nullptr; int filter_value = 0; if (sa->sa_family == AF_INET) { uint32_t host_byte_order_ipv4_addr = ntohl(reinterpret_cast(sa)->sin_addr.s_addr); // IPv4 addresses stored in host byte order in ipv4_table so they are comparable. ntohl has been called if(m_ipv4_table.defined(host_byte_order_ipv4_addr)) filter_value = m_ipv4_table.at(host_byte_order_ipv4_addr); // We should also remove any PeerInfo objects already for this // address. if ((filter_value & PeerInfo::flag_unwanted)) { LT_LOG_EVENTS("connecting peer rejected: flagged as unwanted : %s", sa_pretty_str(sa).c_str()); return nullptr; } } else if (sa->sa_family == AF_INET6) { // Filtering not supported for IPv6 yet. } else { throw internal_error("PeerList::connected() with invalid address family"); } PeerInfo* peer_info; range_type range = base_type::equal_range(sock_key); if (range.first == range.second) { // Create a new entry. peer_info = new PeerInfo(sa); peer_info->set_flags(filter_value & PeerInfo::mask_ip_table); base_type::insert(range.second, value_type(sock_key, peer_info)); } else if (!range.first->second->is_connected()) { // Use an old entry. peer_info = range.first->second.get(); peer_info->set_port(port); } else { // Make sure we don't end up throwing away the port the host is // actually listening on, when there may be several simultaneous // connection attempts to/from different ports. // // This also ensure we can connect to peers running on the same // host as the tracker. // if (flags & connect_keep_handshakes && // range.first->second->is_handshake() && // range.first->second->socket_address()->port() != address->port()) // m_available_list->buffer()->push_back(*address); LT_LOG_EVENTS("connecting peer rejected: already connected (buggy, fixme) : %s", sa_pretty_str(sa).c_str()); // TODO: Verify this works properly, possibly add a check/flag // that allows the handshake manager to notify peer list if the // incoming connection was a duplicate peer hash. //return nullptr; peer_info = new PeerInfo(sa); peer_info->set_flags(filter_value & PeerInfo::mask_ip_table); base_type::insert(range.second, value_type(sock_key, peer_info)); } if ((flags & connect_filter_recent) && peer_info->last_handshake() + 600 > static_cast(this_thread::cached_seconds().count())) { LT_LOG_EVENTS("connecting peer rejected: recent handshake : %s", sa_pretty_str(sa).c_str()); return nullptr; } peer_info->set_flags(PeerInfo::flag_connected); peer_info->set_last_handshake(this_thread::cached_seconds().count()); if ((flags & connect_incoming)) { peer_info->set_flags(PeerInfo::flag_incoming); LT_LOG_EVENTS("connecting peer accepted: incoming : %s", sa_pretty_str(sa).c_str()); } else { peer_info->unset_flags(PeerInfo::flag_incoming); peer_info->set_listen_port(port); LT_LOG_EVENTS("connecting peer accepted: outgoing : %s", sa_pretty_str(sa).c_str()); } return peer_info; } // Make sure we properly clear port when disconnecting. void PeerList::disconnected(PeerInfo* p, int flags) { socket_address_key sock_key = socket_address_key::from_sockaddr(p->socket_address()); range_type range = base_type::equal_range(sock_key); auto itr = std::find_if(range.first, range.second, [p](auto& v) { return p == v.second.get(); }); if (itr == range.second) { if (std::none_of(base_type::begin(), base_type::end(), [p](auto& v){ return p == v.second.get(); })) throw internal_error("PeerList::disconnected(...) itr == range.second, doesn't exist."); else throw internal_error("PeerList::disconnected(...) itr == range.second, not in the range."); } disconnected(itr, flags); } PeerList::iterator PeerList::disconnected(iterator itr, int flags) { if (itr == base_type::end()) throw internal_error("PeerList::disconnected(...) itr == end()."); if (!itr->second->is_connected()) throw internal_error("PeerList::disconnected(...) !itr->is_connected()."); if (itr->second->transfer_counter() != 0) { // Currently we only log these as it only affects the culling of // peers. LT_LOG_EVENTS("disconnected with non-zero transfer counter (%" PRIu32 ") for peer %40s", itr->second->transfer_counter(), itr->second->id_hex()); } itr->second->unset_flags(PeerInfo::flag_connected); // Replace the socket address port with the listening port so that // future outgoing connections will connect to the right port. itr->second->set_port(0); if (flags & disconnect_set_time) itr->second->set_last_connection(this_thread::cached_seconds().count()); if (flags & disconnect_available && itr->second->listen_port() != 0) m_available_list->insert_unique(itr->second->socket_address()); // Do magic to get rid of unneeded entries. return ++itr; } uint32_t PeerList::cull_peers(int flags) { uint32_t counter = 0; uint32_t timer; if (flags & cull_old) timer = this_thread::cached_seconds().count() - 24 * 60 * 60; else timer = 0; for (auto itr = base_type::begin(); itr != base_type::end(); ) { if (itr->second->is_connected() || itr->second->transfer_counter() != 0 || // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! itr->second->last_connection() >= timer || (flags & cull_keep_interesting && (itr->second->failed_counter() != 0 || itr->second->is_blocked()))) { itr++; continue; } // ##################### TODO: LOG CULLING OF PEERS ###################### // *** AND STATS OF DISCONNECTING PEERS (the peer info...)... // The key is a pointer to a member in the value, although the key // shouldn't actually be used in erase (I think), just ot be safe // we delete it after erase. auto tmp = itr++; base_type::erase(tmp); counter++; } return counter; } uint32_t PeerList::insert_pex_list(const raw_string& pex_list) { if (pex_list.empty()) return true; AddressList l; l.parse_address_compact(pex_list); l.sort_and_unique(); LT_LOG_EVENTS("inserting pex list: %" PRIu32 " peers", l.size()); return insert_available(&l); } } // namespace torrent libtorrent-0.16.17/src/torrent/peer/peer_list.h000066400000000000000000000063301522271512000214270ustar00rootroot00000000000000#ifndef LIBTORRENT_PEER_LIST_H #define LIBTORRENT_PEER_LIST_H #include #include #include #include #include namespace torrent { class DownloadInfo; class ProtocolExtension; class raw_string; using ipv4_table = extents; class LIBTORRENT_EXPORT PeerList : private std::multimap> { public: friend class DownloadWrapper; friend class Handshake; friend class HandshakeManager; friend class ConnectionList; using base_type = std::multimap>; using range_type = std::pair; using base_type::value_type; using base_type::reference; using base_type::difference_type; using base_type::iterator; using base_type::reverse_iterator; using base_type::const_iterator; using base_type::const_reverse_iterator; using base_type::size; using base_type::empty; static constexpr int address_available = (1 << 0); static constexpr int connect_incoming = (1 << 0); static constexpr int connect_keep_handshakes = (1 << 1); static constexpr int connect_filter_recent = (1 << 2); // Make sure any change here match ConnectionList's flags. static constexpr int disconnect_available = (1 << 0); static constexpr int disconnect_quick = (1 << 1); static constexpr int disconnect_unwanted = (1 << 2); static constexpr int disconnect_set_time = (1 << 3); static constexpr int cull_old = (1 << 0); static constexpr int cull_keep_interesting = (1 << 1); PeerList(); ~PeerList(); PeerInfo* insert_address(const sockaddr* address, int flags); // This will be used internally only for the moment. uint32_t insert_available(const void* al) LIBTORRENT_NO_EXPORT; static ipv4_table* ipv4_filter() { return &m_ipv4_table; } const auto& available_list() { return m_available_list; } uint32_t available_list_size() const; uint32_t cull_peers(int flags); const_iterator begin() const { return base_type::begin(); } const_iterator end() const { return base_type::end(); } const_reverse_iterator rbegin() const { return base_type::rbegin(); } const_reverse_iterator rend() const { return base_type::rend(); } protected: friend class torrent::ProtocolExtension; void set_info(DownloadInfo* info) LIBTORRENT_NO_EXPORT; // Insert, or find a PeerInfo with socket address 'sa'. Returns end // if no more connections are allowed from that host. PeerInfo* connected(const sockaddr* sa, int flags) LIBTORRENT_NO_EXPORT; void disconnected(PeerInfo* p, int flags) LIBTORRENT_NO_EXPORT; iterator disconnected(iterator itr, int flags) LIBTORRENT_NO_EXPORT; uint32_t insert_pex_list(const raw_string& pex_list) LIBTORRENT_NO_EXPORT; private: PeerList(const PeerList&) = delete; PeerList& operator=(const PeerList&) = delete; static ipv4_table m_ipv4_table; DownloadInfo* m_info; std::unique_ptr m_available_list; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/rate.cc000066400000000000000000000016051522271512000175770ustar00rootroot00000000000000#include "config.h" #include "rate.h" #include "exceptions.h" namespace torrent { inline void Rate::discard_old() const { while (!m_container.empty() && m_container.back().first < this_thread::cached_seconds().count() - m_span) { m_current -= m_container.back().second; m_container.pop_back(); } } Rate::rate_type Rate::rate() const { discard_old(); return m_current / m_span; } void Rate::insert(rate_type bytes) { discard_old(); if (m_current > (rate_type{1} << 40) || bytes > (rate_type{1} << 28)) throw internal_error("Rate::insert(bytes) received out-of-bounds values.."); if (m_container.empty() || m_container.front().first != this_thread::cached_seconds().count()) m_container.emplace_front(this_thread::cached_seconds().count(), bytes); else m_container.front().second += bytes; m_total += bytes; m_current += bytes; } } // namespace torrent libtorrent-0.16.17/src/torrent/rate.h000066400000000000000000000073621522271512000174470ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_UTILS_RATE_H #define LIBTORRENT_UTILS_RATE_H #include #include namespace torrent { // Keep the current rate count up to date for each call to rate() and // insert(...). This requires a mutable since rate() can be const, but // is justified as we avoid iterating the deque for each call. class LIBTORRENT_EXPORT Rate { public: using timer_type = int32_t; using rate_type = uint64_t; using total_type = uint64_t; using value_type = std::pair; using queue_type = std::deque; Rate(timer_type span) : m_span(span) {} // Bytes per second. rate_type rate() const; // Total bytes transfered. total_type total() const { return m_total; } void set_total(total_type bytes) { m_total = bytes; } // Interval in seconds used to calculate the rate. timer_type span() const { return m_span; } void set_span(timer_type s) { m_span = s; } void insert(rate_type bytes); void reset_rate() { m_current = 0; m_container.clear(); } bool operator < (Rate& r) const { return rate() < r.rate(); } bool operator > (Rate& r) const { return rate() > r.rate(); } bool operator == (Rate& r) const { return rate() == r.rate(); } bool operator != (Rate& r) const { return rate() != r.rate(); } bool operator < (rate_type r) const { return rate() < r; } bool operator > (rate_type r) const { return rate() > r; } bool operator == (rate_type r) const { return rate() == r; } bool operator != (rate_type r) const { return rate() != r; } private: inline void discard_old() const; mutable queue_type m_container; mutable rate_type m_current{0}; total_type m_total{0}; timer_type m_span; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/runtime/000077500000000000000000000000001522271512000200165ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/runtime/network_config.cc000066400000000000000000000425421522271512000233520ustar00rootroot00000000000000#include "config.h" #include "torrent/runtime/network_config.h" #include #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/utils/log.h" // TODO: Add runtime category and add it to important/complete log outputs. #define LT_LOG_NOTICE(log_fmt, ...) \ lt_log_print_subsystem(LOG_NOTICE, "runtime::network_config", log_fmt, __VA_ARGS__); namespace torrent::runtime { namespace { c_sa_shared_ptr inet_any_value = sa_make_inet_any(); c_sa_shared_ptr inet6_any_value = sa_make_inet6_any(); } NetworkConfig::NetworkConfig() { m_bind_inet_address = sa_make_unspec(); m_bind_inet6_address = sa_make_unspec(); m_local_inet_address = sa_make_unspec(); m_local_inet6_address = sa_make_unspec(); } bool NetworkConfig::is_block_udp() const { auto guard = lock_guard(); return m_block_udp; } bool NetworkConfig::is_block_ipv4() const { auto guard = lock_guard(); return m_block_ipv4; } bool NetworkConfig::is_block_ipv6() const { auto guard = lock_guard(); return m_block_ipv6; } bool NetworkConfig::is_block_ipv4in6() const { auto guard = lock_guard(); return m_block_ipv4in6; } bool NetworkConfig::is_block_incoming() const { auto guard = lock_guard(); return m_block_incoming; } bool NetworkConfig::is_block_outgoing() const { auto guard = lock_guard(); return m_block_outgoing; } bool NetworkConfig::is_prefer_ipv6() const { auto guard = lock_guard(); return m_prefer_ipv6; } void NetworkConfig::set_block_ipv4(bool v) { auto guard = lock_guard(); m_block_ipv4 = v; notify_changes_unsafe(); } void NetworkConfig::set_block_ipv6(bool v) { auto guard = lock_guard(); m_block_ipv6 = v; notify_changes_unsafe(); } void NetworkConfig::set_block_ipv4in6(bool v) { auto guard = lock_guard(); m_block_ipv4in6 = v; notify_changes_unsafe(); } void NetworkConfig::set_block_outgoing(bool v) { auto guard = lock_guard(); m_block_outgoing = v; notify_changes_unsafe(); } void NetworkConfig::set_prefer_ipv6(bool v) { auto guard = lock_guard(); m_prefer_ipv6 = v; } int NetworkConfig::priority() const { auto guard = lock_guard(); return m_priority; } void NetworkConfig::set_priority(int p) { auto guard = lock_guard(); m_priority = p; } c_sa_shared_ptr NetworkConfig::bind_address_best_match() const { return generic_address_best_match(m_bind_inet_address, m_bind_inet6_address); } std::string NetworkConfig::bind_address_best_match_str() const { return generic_address_best_match_str(m_bind_inet_address, m_bind_inet6_address); } c_sa_shared_ptr NetworkConfig::bind_address_or_unspec_and_null() const { return generic_address_or_unspec_and_null(m_bind_inet_address, m_bind_inet6_address); } c_sa_shared_ptr NetworkConfig::bind_address_or_any_and_null() const { return generic_address_or_any_and_null(m_bind_inet_address, m_bind_inet6_address); } c_sa_shared_ptr NetworkConfig::bind_address_for_connect(int family) const { return generic_address_for_connect(family, false, m_bind_inet_address, m_bind_inet6_address); } c_sa_shared_ptr NetworkConfig::bind_address_for_udp_connect(int family) const { return generic_address_for_connect(family, true, m_bind_inet_address, m_bind_inet6_address); } c_sa_shared_ptr NetworkConfig::bind_inet_address() const { auto guard = lock_guard(); return m_bind_inet_address; } std::string NetworkConfig::bind_inet_address_str() const { auto guard = lock_guard(); return sa_addr_str(m_bind_inet_address.get()); } c_sa_shared_ptr NetworkConfig::bind_inet6_address() const { auto guard = lock_guard(); return m_bind_inet6_address; } std::string NetworkConfig::bind_inet6_address_str() const { auto guard = lock_guard(); return sa_addr_str(m_bind_inet6_address.get()); } std::tuple NetworkConfig::bind_addresses_or_null() const { auto guard = lock_guard(); return bind_addresses_or_null_unsafe(); } std::tuple NetworkConfig::bind_udp_addresses_or_null() const { auto guard = lock_guard(); if (m_block_udp) return {nullptr, nullptr}; return bind_addresses_or_null_unsafe(); } std::tuple NetworkConfig::bind_addresses_str() const { auto guard = lock_guard(); return {sa_addr_str(m_bind_inet_address.get()), sa_addr_str(m_bind_inet6_address.get())}; } c_sa_shared_ptr NetworkConfig::local_address_best_match() const { return generic_address_best_match(m_local_inet_address, m_local_inet6_address); } std::string NetworkConfig::local_address_best_match_str() const { return generic_address_best_match_str(m_local_inet_address, m_local_inet6_address); } c_sa_shared_ptr NetworkConfig::local_address_or_unspec_and_null() const { return generic_address_or_unspec_and_null(m_local_inet_address, m_local_inet6_address); } c_sa_shared_ptr NetworkConfig::local_address_or_any_and_null() const { return generic_address_or_any_and_null(m_local_inet_address, m_local_inet6_address); } c_sa_shared_ptr NetworkConfig::local_inet_address() const { auto guard = lock_guard(); return m_local_inet_address; } c_sa_shared_ptr NetworkConfig::local_inet_address_or_null() const { auto guard = lock_guard(); if (m_block_ipv4 || m_local_inet_address->sa_family == AF_UNSPEC) return nullptr; return m_local_inet_address; } std::string NetworkConfig::local_inet_address_str() const { auto guard = lock_guard(); return sa_addr_str(m_local_inet_address.get()); } c_sa_shared_ptr NetworkConfig::local_inet6_address() const { auto guard = lock_guard(); return m_local_inet6_address; } c_sa_shared_ptr NetworkConfig::local_inet6_address_or_null() const { auto guard = lock_guard(); if (m_block_ipv6 || m_local_inet6_address->sa_family == AF_UNSPEC) return nullptr; return m_local_inet6_address; } std::string NetworkConfig::local_inet6_address_str() const { auto guard = lock_guard(); return sa_addr_str(m_local_inet6_address.get()); } void NetworkConfig::set_bind_address(const sockaddr* sa) { auto guard = lock_guard(); set_generic_address_unsafe("bind", m_bind_inet_address, m_bind_inet6_address, sa); notify_changes_unsafe(); } void NetworkConfig::set_bind_address_str(const std::string& addr) { set_bind_address(sa_lookup_address(addr, AF_UNSPEC).get()); } void NetworkConfig::set_bind_inet_address(const sockaddr* sa) { auto guard = lock_guard(); set_generic_inet_address_unsafe("bind", m_bind_inet_address, sa); notify_changes_unsafe(); } void NetworkConfig::set_bind_inet_address_str(const std::string& addr) { set_bind_inet_address(sa_lookup_address(addr, AF_INET).get()); } void NetworkConfig::set_bind_inet6_address(const sockaddr* sa) { auto guard = lock_guard(); set_generic_inet6_address_unsafe("bind", m_bind_inet6_address, sa); notify_changes_unsafe(); } void NetworkConfig::set_bind_inet6_address_str(const std::string& addr) { set_bind_inet6_address(sa_lookup_address(addr, AF_INET6).get()); } void NetworkConfig::set_local_address(const sockaddr* sa) { if (sa_is_any(sa)) throw input_error("Tried to set local address to an any address."); auto guard = lock_guard(); set_generic_address_unsafe("local", m_local_inet_address, m_local_inet6_address, sa); } void NetworkConfig::set_local_address_str(const std::string& addr) { set_local_address(sa_lookup_address(addr, AF_UNSPEC).get()); } void NetworkConfig::set_local_inet_address(const sockaddr* sa) { if (sa_is_any(sa)) throw input_error("Tried to set local inet address to an any address."); auto guard = lock_guard(); set_generic_inet_address_unsafe("local", m_local_inet_address, sa); } void NetworkConfig::set_local_inet_address_str(const std::string& addr) { set_local_inet_address(sa_lookup_address(addr, AF_INET).get()); } void NetworkConfig::set_local_inet6_address(const sockaddr* sa) { if (sa_is_any(sa)) throw input_error("Tried to set local inet6 address to an any address."); auto guard = lock_guard(); set_generic_inet6_address_unsafe("local", m_local_inet6_address, sa); } void NetworkConfig::set_local_inet6_address_str(const std::string& addr) { set_local_inet6_address(sa_lookup_address(addr, AF_INET6).get()); } uint32_t NetworkConfig::encryption_options() const { auto guard = lock_guard(); return m_encryption_options; } void NetworkConfig::set_encryption_options(uint32_t opts) { auto guard = lock_guard(); m_encryption_options = opts; } int NetworkConfig::listen_backlog() const { auto guard = lock_guard(); return m_listen_backlog; } void NetworkConfig::set_listen_backlog(int backlog) { if (backlog < 1) throw input_error("Tried to set a listen backlog less than 1."); if (backlog > (1 << 16)) throw input_error("Tried to set a listen backlog greater than 65536."); auto guard = lock_guard(); m_listen_backlog = backlog; notify_changes_unsafe(); } uint16_t NetworkConfig::override_dht_port() const { auto guard = lock_guard(); return m_override_dht_port; } void NetworkConfig::set_override_dht_port(uint16_t port) { auto guard = lock_guard(); m_override_dht_port = port; } uint32_t NetworkConfig::send_buffer_size() const { auto guard = lock_guard(); return m_send_buffer_size; } void NetworkConfig::set_send_buffer_size(uint32_t s) { auto guard = lock_guard(); m_send_buffer_size = s; } uint32_t NetworkConfig::receive_buffer_size() const { auto guard = lock_guard(); return m_receive_buffer_size; } void NetworkConfig::set_receive_buffer_size(uint32_t s) { auto guard = lock_guard(); m_receive_buffer_size = s; } void NetworkConfig::subscribe_to_changes(void* target, const std::function& callback) { auto guard = lock_guard(); m_change_subscribers.push_back(std::make_pair(target, callback)); } void NetworkConfig::unsubscribe_from_changes(void* target) { auto guard = lock_guard(); auto itr = std::remove_if(m_change_subscribers.begin(), m_change_subscribers.end(), [target](auto& p) { return p.first == target; }); m_change_subscribers.erase(itr, m_change_subscribers.end()); } void NetworkConfig::notify_changes_unsafe() const { for (auto& p : m_change_subscribers) p.second(); } NetworkConfig::listen_addresses NetworkConfig::listen_addresses_unsafe() const { auto inet_address = m_block_ipv4 ? nullptr : m_bind_inet_address; auto inet6_address = m_block_ipv6 ? nullptr : m_bind_inet6_address; if (inet_address == nullptr && inet6_address == nullptr) return {}; if (inet_address == nullptr) { if (inet6_address->sa_family == AF_UNSPEC) return {nullptr, inet6_any_value, m_block_ipv4in6}; return {nullptr, inet6_address, m_block_ipv4in6}; } if (inet6_address == nullptr) { if (inet_address->sa_family == AF_UNSPEC) return {inet_any_value, nullptr, false}; return {inet_address, nullptr, false}; } if (inet_address->sa_family == AF_UNSPEC && inet6_address->sa_family == AF_UNSPEC) { if (m_block_ipv4in6) return {inet_any_value, inet6_any_value, true}; return {nullptr, inet6_any_value, false}; } if (inet_address->sa_family != AF_UNSPEC && inet6_address->sa_family != AF_UNSPEC) return {inet_address, inet6_address, m_block_ipv4in6}; if (inet_address->sa_family != AF_UNSPEC) return {inet_address, nullptr, false}; if (inet6_address->sa_family != AF_UNSPEC) return {nullptr, inet6_address, m_block_ipv4in6}; throw internal_error("NetworkConfig::listen_addresses_unsafe(): reached unreachable code."); } int NetworkConfig::listen_backlog_unsafe() const { return m_listen_backlog; } // // Helper Functions: // // The caller must make sure the address family is not blocked before making a connection. c_sa_shared_ptr NetworkConfig::generic_address_best_match(const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const { auto guard = lock_guard(); if (inet_address->sa_family == AF_UNSPEC) return inet6_address; if (inet6_address->sa_family == AF_UNSPEC) return inet_address; if (m_prefer_ipv6) { if (m_block_ipv6 && !m_block_ipv4) return inet_address; return inet6_address; } if (m_block_ipv4 && !m_block_ipv6) return inet6_address; return inet_address; } std::string NetworkConfig::generic_address_best_match_str(const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const { return sa_addr_str(generic_address_best_match(inet_address, inet6_address).get()); } // TODO: There's a race condition if block/local is changed after returning unspec, and the caller // checks if ipv4/6 is blocked. Create a function that locks NC and sets up and locals fd. c_sa_shared_ptr NetworkConfig::generic_address_or_unspec_and_null(const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const { auto guard = lock_guard(); if (m_block_ipv4 && m_block_ipv6) return nullptr; if (inet_address->sa_family == AF_UNSPEC && inet6_address->sa_family == AF_UNSPEC) return inet_address; if (inet_address->sa_family == AF_UNSPEC) { if (m_block_ipv6) return nullptr; return inet6_address; } if (inet6_address->sa_family == AF_UNSPEC) { if (m_block_ipv4) return nullptr; return inet_address; } if (m_prefer_ipv6 && !m_block_ipv6) return inet6_address; if (m_block_ipv4) return inet6_address; return inet_address; } c_sa_shared_ptr NetworkConfig::generic_address_or_any_and_null(const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const { auto address = generic_address_or_unspec_and_null(inet_address, inet6_address); if (address == nullptr) return nullptr; if (address->sa_family != AF_UNSPEC) return address; if (!m_block_ipv6) return inet6_any_value; if (!m_block_ipv4) return inet_any_value; throw internal_error("NetworkConfig::generic_address_or_any_and_null(): both ipv4 and ipv6 are blocked and returned unspec"); } c_sa_shared_ptr NetworkConfig::generic_address_for_connect(int family, bool is_udp, const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const { auto guard = lock_guard(); // if (m_block_outgoing) // return nullptr; if (is_udp && m_block_udp) return nullptr; switch (family) { case AF_INET: if (m_block_ipv4) return nullptr; if (inet_address->sa_family != AF_UNSPEC) return inet_address; if (inet6_address->sa_family != AF_UNSPEC) { if (m_block_ipv4in6) return nullptr; return inet6_address; } return inet_any_value; case AF_INET6: if (m_block_ipv6) return nullptr; if (inet6_address->sa_family != AF_UNSPEC) return inet6_address; if (inet_address->sa_family != AF_UNSPEC) return nullptr; return inet6_any_value; default: throw input_error("NetworkConfig::generic_address_for_connect() called with invalid address family"); } } // TODO: Move all management tasks here. void NetworkConfig::set_generic_address_unsafe(const char* category, c_sa_shared_ptr& inet_address, c_sa_shared_ptr& inet6_address, const sockaddr* sa) { if (sa->sa_family != AF_INET && sa->sa_family != AF_UNSPEC) throw input_error("Tried to set a " + std::string(category) + " address that is not an unspec/inet address."); if (sa_port(sa) != 0) throw input_error("Tried to set a " + std::string(category) + " address with a non-zero port."); LT_LOG_NOTICE("%s address : %s", category, sa_pretty_str(sa).c_str()); switch (sa->sa_family) { case AF_UNSPEC: inet_address = sa_make_unspec(); inet6_address = sa_make_unspec(); break; case AF_INET: inet_address = sa_copy(sa); inet6_address = sa_make_unspec(); break; case AF_INET6: inet_address = sa_make_unspec(); inet6_address = sa_copy(sa); break; default: throw internal_error("NetworkConfig::set_" + std::string(category) + "_address: invalid address family"); } // TODO: Warning if not matching block/prefer } void NetworkConfig::set_generic_inet_address_unsafe(const char* category, c_sa_shared_ptr& inet_address, const sockaddr* sa) { if (sa->sa_family != AF_INET && sa->sa_family != AF_UNSPEC) throw input_error("Tried to set a " + std::string(category) + " inet address that is not an unspec/inet address."); if (sa_port(sa) != 0) throw input_error("Tried to set a " + std::string(category) + " inet address with a non-zero port."); LT_LOG_NOTICE("%s inet address : %s", category, sa_pretty_str(sa).c_str()); inet_address = sa_copy(sa); } void NetworkConfig::set_generic_inet6_address_unsafe(const char* category, c_sa_shared_ptr& inet6_address, const sockaddr* sa) { if (sa->sa_family != AF_INET6 && sa->sa_family != AF_UNSPEC) throw input_error("Tried to set a " + std::string(category) + " inet6 address that is not an unspec/inet6 address."); if (sa_port(sa) != 0) throw input_error("Tried to set a " + std::string(category) + " inet6 address with a non-zero port."); LT_LOG_NOTICE("%s inet6 address : %s", category, sa_pretty_str(sa).c_str()); inet6_address = sa_copy(sa); } std::tuple NetworkConfig::bind_addresses_or_null_unsafe() const { auto inet_addr = m_bind_inet_address; auto inet6_addr = m_bind_inet6_address; if (inet_addr->sa_family != AF_UNSPEC && inet6_addr->sa_family == AF_UNSPEC) { inet6_addr = nullptr; } else if (inet6_addr->sa_family != AF_UNSPEC && inet_addr->sa_family == AF_UNSPEC) { inet_addr = nullptr; } if (m_block_ipv4) inet_addr = nullptr; if (m_block_ipv6) inet6_addr = nullptr; return {inet_addr, inet6_addr}; } } // namespace torrent::net libtorrent-0.16.17/src/torrent/runtime/network_config.h000066400000000000000000000205101522271512000232030ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_RUNTIME_NETWORK_CONFIG_H #define LIBTORRENT_TORRENT_RUNTIME_NETWORK_CONFIG_H #include #include #include #include #include namespace torrent::runtime { class ProxyManager; NetworkConfig* network_config() LIBTORRENT_EXPORT; class LIBTORRENT_EXPORT NetworkConfig { public: // TODO: Remove. static constexpr int iptos_default = 0; static constexpr int iptos_lowdelay = IPTOS_LOWDELAY; static constexpr int iptos_throughput = IPTOS_THROUGHPUT; static constexpr int iptos_reliability = IPTOS_RELIABILITY; static constexpr uint32_t encryption_none = 0; static constexpr uint32_t encryption_allow_incoming = 0x1; static constexpr uint32_t encryption_try_outgoing = 0x2; static constexpr uint32_t encryption_require = 0x3; static constexpr uint32_t encryption_require_RC4 = 0x4; static constexpr uint32_t encryption_enable_retry = 0x8; static constexpr uint32_t encryption_prefer_plaintext = 0x10; // Internal to libtorrent. static constexpr uint32_t encryption_retrying = 0x40; NetworkConfig(); // TODO: Move helper functions in rtorrent manager here. // TODO: Verify we attempt to connect to cached peers on startup, even before tracker requests. bool is_block_udp() const; bool is_block_ipv4() const; bool is_block_ipv6() const; bool is_block_ipv4in6() const; bool is_block_incoming() const; bool is_block_outgoing() const; bool is_prefer_ipv6() const; void set_block_ipv4(bool v); void set_block_ipv6(bool v); void set_block_ipv4in6(bool v); void set_block_outgoing(bool v); void set_prefer_ipv6(bool v); int priority() const; void set_priority(int p); // Use bind_address_best_match for display purposes only, or if you manually check blocking. // // If bind_address_or_null returns null, then available protocols are blocked. This means if the // user binds to e.g. inet6 and blocks ipv6, then no connections are possible. // // To bind one and keep the other protocol unbound, set the other address to either 0.0.0.0 or ::. c_sa_shared_ptr bind_address_best_match() const; std::string bind_address_best_match_str() const; c_sa_shared_ptr bind_address_or_unspec_and_null() const; c_sa_shared_ptr bind_address_or_any_and_null() const; c_sa_shared_ptr bind_address_for_connect(int family) const; c_sa_shared_ptr bind_address_for_udp_connect(int family) const; c_sa_shared_ptr bind_inet_address() const; std::string bind_inet_address_str() const; c_sa_shared_ptr bind_inet6_address() const; std::string bind_inet6_address_str() const; std::tuple bind_addresses_or_null() const; std::tuple bind_udp_addresses_or_null() const; std::tuple bind_addresses_str() const; c_sa_shared_ptr local_address_best_match() const; std::string local_address_best_match_str() const; c_sa_shared_ptr local_address_or_unspec_and_null() const; c_sa_shared_ptr local_address_or_any_and_null() const; c_sa_shared_ptr local_inet_address() const; c_sa_shared_ptr local_inet_address_or_null() const; std::string local_inet_address_str() const; c_sa_shared_ptr local_inet6_address() const; c_sa_shared_ptr local_inet6_address_or_null() const; std::string local_inet6_address_str() const; void set_bind_address(const sockaddr* sa); void set_bind_address_str(const std::string& addr); void set_bind_inet_address(const sockaddr* sa); void set_bind_inet_address_str(const std::string& addr); void set_bind_inet6_address(const sockaddr* sa); void set_bind_inet6_address_str(const std::string& addr); void set_local_address(const sockaddr* sa); void set_local_address_str(const std::string& addr); void set_local_inet_address(const sockaddr* sa); void set_local_inet_address_str(const std::string& addr); void set_local_inet6_address(const sockaddr* sa); void set_local_inet6_address_str(const std::string& addr); uint32_t encryption_options() const; void set_encryption_options(uint32_t opts); int listen_backlog() const; void set_listen_backlog(int backlog); uint16_t override_dht_port() const; void set_override_dht_port(uint16_t port); uint32_t send_buffer_size() const; void set_send_buffer_size(uint32_t s); uint32_t receive_buffer_size() const; void set_receive_buffer_size(uint32_t s); // The lock is held while the callback is called, so use Thread::callback(). void subscribe_to_changes(void* target, const std::function& callback); void unsubscribe_from_changes(void* target); protected: friend class torrent::ConnectionManager; friend class torrent::runtime::NetworkManager; friend class torrent::runtime::ProxyManager; using listen_addresses = std::tuple; using subscriber_list = std::vector>>; void lock() const { m_mutex.lock(); } auto lock_guard() const { return std::lock_guard(m_mutex); } void unlock() const { m_mutex.unlock(); } auto& mutex() const { return m_mutex; } void notify_changes_unsafe() const; listen_addresses listen_addresses_unsafe() const; int listen_backlog_unsafe() const; private: c_sa_shared_ptr generic_address_best_match(const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const; std::string generic_address_best_match_str(const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const; c_sa_shared_ptr generic_address_or_unspec_and_null(const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const; c_sa_shared_ptr generic_address_or_any_and_null(const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const; c_sa_shared_ptr generic_address_for_connect(int family, bool is_udp, const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const; c_sa_shared_ptr generic_address_for_family(int family, const c_sa_shared_ptr& inet_address, const c_sa_shared_ptr& inet6_address) const; void set_generic_address_unsafe(const char* category, c_sa_shared_ptr& inet_address, c_sa_shared_ptr& inet6_address, const sockaddr* sa); void set_generic_inet_address_unsafe(const char* category, c_sa_shared_ptr& inet_address, const sockaddr* sa); void set_generic_inet6_address_unsafe(const char* category, c_sa_shared_ptr& inet6_address, const sockaddr* sa); std::tuple bind_addresses_or_null_unsafe() const; mutable std::mutex m_mutex; bool m_block_ipv4{}; bool m_block_ipv6{}; bool m_block_ipv4in6{}; bool m_block_outgoing{}; bool m_prefer_ipv6{}; // Directly modified by ProxyManager. bool m_block_udp{}; bool m_block_incoming{}; // TODO: Rename m_tos_priority. int m_priority{iptos_throughput}; c_sa_shared_ptr m_bind_inet_address; c_sa_shared_ptr m_bind_inet6_address; c_sa_shared_ptr m_local_inet_address; c_sa_shared_ptr m_local_inet6_address; int m_encryption_options{encryption_none}; int m_listen_backlog{SOMAXCONN}; uint16_t m_override_dht_port{0}; uint32_t m_send_buffer_size{0}; uint32_t m_receive_buffer_size{0}; subscriber_list m_change_subscribers; }; } // namespace torrent::config #endif libtorrent-0.16.17/src/torrent/runtime/network_manager.cc000066400000000000000000000122001522271512000235030ustar00rootroot00000000000000#include "config.h" #include "torrent/runtime/network_manager.h" #include "net/listen.h" #include "torrent/exceptions.h" #include "torrent/runtime/network_config.h" #include "torrent/system/thread.h" #include "torrent/tracker/dht_controller.h" #include "torrent/utils/log.h" // TODO: Add runtime category and add it to important/complete log outputs. #define LT_LOG_NOTICE(log_fmt, ...) \ lt_log_print_subsystem(LOG_NOTICE, "runtime::network_manager", log_fmt, __VA_ARGS__); namespace torrent::runtime { NetworkManager::NetworkManager() : m_listen_inet(new Listen), m_listen_inet6(new Listen), m_dht_controller(new tracker::DhtController) { } NetworkManager::~NetworkManager() = default; bool NetworkManager::is_listening() const { auto guard = lock_guard(); return is_listening_unsafe(); } bool NetworkManager::is_dht_valid() const { // auto guard = lock_guard(); return m_dht_controller->is_valid(); } bool NetworkManager::is_dht_active() const { // auto guard = lock_guard(); return m_dht_controller->is_active(); } bool NetworkManager::is_dht_active_and_receiving_requests() const { // auto guard = lock_guard(); return m_dht_controller->is_active() && m_dht_controller->is_receiving_requests(); } // Cleanup should get called twice; when shutdown is initiated and on libtorrent cleanup. void NetworkManager::cleanup() { auto guard = lock_guard(); m_dht_controller->stop(); listen_close_unsafe(); } // TODO: Log here // TODO: Verify various combinations of addresses/block_ipv4in6 match tcp46/udp46 sockets // TODO: Fix DHT bool NetworkManager::listen_open(uint16_t begin, uint16_t end) { auto guard = lock_guard(); return listen_open_unsafe(begin, end); } void NetworkManager::listen_close() { auto guard = lock_guard(); listen_close_unsafe(); } void NetworkManager::listen_restart() { auto guard = lock_guard(); if (network_config()->is_block_incoming()) { listen_close_unsafe(); return; } // TODO: We don't properly re-open the listen socket if there's a change here. if (!is_listening_unsafe()) return; listen_close_unsafe(); try { listen_open_unsafe(m_listen_port, m_listen_port); } catch (const base_error& e) { LT_LOG_NOTICE("could not restart listen socket: %s", e.what()); } } uint16_t NetworkManager::listen_port() const { auto guard = lock_guard(); return m_listen_port; } uint16_t NetworkManager::listen_port_or_throw() const { auto guard = lock_guard(); if (m_listen_port == 0) throw input_error("Tried to get listen port but it is not set."); return m_listen_port; } uint16_t NetworkManager::dht_port() { // auto guard = lock_guard(); return m_dht_controller->port(); } // TODO: Make bootstrap nodes explicit, and when we add them also try adding as node if we're // already running. // TODO: Consider adding dht bootstrap nodes to network config. void NetworkManager::dht_add_bootstrap_node(std::string host, int port){ // auto guard = lock_guard(); m_dht_controller->add_bootstrap_node(std::move(host), port); } void NetworkManager::dht_add_peer_node([[maybe_unused]] const sockaddr* sa, [[maybe_unused]] int port) { // Ignore peer nodes as we shouldn't need them(?) // // Re-enable this if it causes issues. } bool NetworkManager::is_listening_unsafe() const { return m_listen_inet->is_open() || m_listen_inet6->is_open(); } bool NetworkManager::listen_open_unsafe(uint16_t begin, uint16_t end) { int backlog = 0; NetworkConfig::listen_addresses listen_addresses = {}; if (m_listen_inet->is_open() || m_listen_inet6->is_open()) throw internal_error("NetworkManager::open_listen(): Tried to open listen socket when one is already open."); { auto config_guard = runtime::network_config()->lock_guard(); backlog = runtime::network_config()->listen_backlog_unsafe(); listen_addresses = runtime::network_config()->listen_addresses_unsafe(); } auto& [inet_address, inet6_address, block_ipv4in6] = listen_addresses; if (inet_address == nullptr && inet6_address == nullptr) throw input_error("Neither IPv4 nor IPv6 listen address are suitable for opening listen sockets, check block_ipv4 and block_ipv6 settings."); if (inet_address != nullptr && inet6_address != nullptr) { if (!Listen::open_both(m_listen_inet.get(), m_listen_inet6.get(), inet_address.get(), inet6_address.get(), begin, end, backlog, block_ipv4in6)) return false; m_listen_port = m_listen_inet->port(); return true; } if (inet_address != nullptr) { if (!Listen::open_single(m_listen_inet.get(), inet_address.get(), begin, end, backlog, false)) return false; m_listen_port = m_listen_inet->port(); return true; } if (inet6_address != nullptr) { if (!Listen::open_single(m_listen_inet6.get(), inet6_address.get(), begin, end, backlog, block_ipv4in6)) return false; m_listen_port = m_listen_inet6->port(); return true; } throw internal_error("NetworkManager::open_listen(): reached unreachable code."); } void NetworkManager::listen_close_unsafe() { m_listen_inet->close(); m_listen_inet6->close(); } } // namespace torrent::runtime libtorrent-0.16.17/src/torrent/runtime/network_manager.h000066400000000000000000000052641522271512000233610ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_RUNTIME_NETWORK_MANAGER_H #define LIBTORRENT_TORRENT_RUNTIME_NETWORK_MANAGER_H #include #include namespace torrent { class Manager; namespace net::proxy { class Proxy; }; } // namespace torrent namespace torrent::runtime { NetworkManager* network_manager() LIBTORRENT_EXPORT; class LIBTORRENT_EXPORT NetworkManager { public: NetworkManager(); ~NetworkManager(); bool is_listening() const; bool is_dht_valid() const; bool is_dht_active() const; bool is_dht_active_and_receiving_requests() const; void cleanup(); // TODO: Change to have network_config hold the port range / random info. bool listen_open(uint16_t first, uint16_t last); void listen_close(); void listen_restart(); // Port number remains set after listen_close. uint16_t listen_port() const; uint16_t listen_port_or_throw() const; // TODO: Only allowed to be called from main thread (tracker thread when moved). // TODO: Move DHT on/off/auto handlig here. auto* dht_controller(); uint16_t dht_port(); void dht_add_bootstrap_node(std::string host, int port); void dht_add_peer_node(const sockaddr* sa, int port); protected: friend class torrent::Manager; friend class NetworkConfig; void lock() const { m_mutex.lock(); } auto lock_guard() const { return std::lock_guard(m_mutex); } void unlock() const { m_mutex.unlock(); } auto& mutex() const { return m_mutex; } bool is_listening_unsafe() const; auto listen_inet_unsafe() { return m_listen_inet.get(); } auto listen_inet6_unsafe() { return m_listen_inet6.get(); } // TODO: Rename updated_network_config() void restart_listen(); private: bool listen_open_unsafe(uint16_t first, uint16_t last); void listen_close_unsafe(); void perform_restart_listen(); mutable std::mutex m_mutex; std::unique_ptr m_listen_inet; std::unique_ptr m_listen_inet6; uint16_t m_listen_port{}; std::unique_ptr m_dht_controller; }; // We don't need locking for objects we initialize/destruct in the ctor/dtor. inline auto* NetworkManager::dht_controller() { return m_dht_controller.get(); } } // namespace torrent::runtime #endif // LIBTORRENT_TORRENT_RUNTIME_NETWORK_MANAGER_H libtorrent-0.16.17/src/torrent/runtime/proxy_manager.cc000066400000000000000000000132271522271512000232050ustar00rootroot00000000000000#include "config.h" #include "torrent/runtime/proxy_manager.h" #include #include "net/proxy/proxy.h" #include "net/proxy/proxy_http.h" #include "net/proxy/proxy_socks5.h" #include "torrent/exceptions.h" #include "torrent/net/http_stack.h" #include "torrent/net/socket_address.h" #include "torrent/runtime/network_config.h" namespace torrent::runtime { ProxyManager::ProxyManager() { m_create_proxy = [](auto*) { return nullptr; }; } void ProxyManager::set_proxy_url(const std::string& url) { if (url.empty()) return clear_proxy(); auto schema = net::parse_uri_scheme(url); auto [host, port] = net::parse_uri_host_port(url); auto [user, password] = net::parse_uri_user_password(url); // TODO: Verify there's no junk after the port number? if (schema.empty()) throw input_error("Proxy address must include a scheme."); if (host.empty()) throw input_error("Proxy address must include a host."); if (port == 0) throw input_error("Proxy address must include a port."); if (!net::verify_no_path_query_fragment(url)) throw input_error("Proxy address must not include a path, query, or fragment."); auto verify_user_password_or_empty = [schema, user, password]() { if (user.empty() || password.empty()) return; if (password.empty()) throw input_error("Proxy address for '" + schema + "://' must not include a user without a password."); if (!user.empty()) throw input_error("Proxy address for '" + schema + "://' must not include a password without a user."); }; auto verify_no_user_password = [schema, user, password]() { if (!user.empty() || !password.empty()) throw input_error("Proxy address for '" + schema + "://' must not include a user or password."); }; auto [proxy_sa, lookup_succeeded] = sa_lookup_numeric(host, AF_UNSPEC); if (!lookup_succeeded) throw input_error("Proxy address numeric lookup failed: " + host); create_proxy_func create_proxy_fn; auto proxy_union = sa_inet_union_from_sap(proxy_sa); sa_set_port(&proxy_union.sa, port); if (schema == "http") { verify_no_user_password(); create_proxy_fn = [proxy_union](auto* connect_sa) { return new net::proxy::ProxyHttp(&proxy_union.sa, sa_addr_str(connect_sa), sa_port(connect_sa)); }; // } else if (schema == "https") { // create_proxy = [host, port, user, password]() { // // return std::make_unique(host, port, user, password); // return nullptr; // }; // } else if (schema == "socks4") { // create_proxy = [host, port, user, password]() { // // return std::make_unique(host, port, user, password); // return nullptr; // }; // } else if (schema == "socks4a") { // create_proxy = [host, port, user, password]() { // // return std::make_unique(host, port, user, password); // return nullptr; // }; } else if (schema == "socks5" || schema == "socks5h") { // We currently don't support socks5h resolves, however since we only do sockaddr connects this // will be the same as socks5. verify_user_password_or_empty(); if (user.size() > 255) throw input_error("Proxy address for '" + schema + "://' username exceeds max protocol limits."); if (password.size() > 255) throw input_error("Proxy address for '" + schema + "://' password exceeds max protocol limits."); create_proxy_fn = [proxy_union](auto* connect_sa) { return new net::proxy::ProxySocks5(&proxy_union.sa, connect_sa); }; } else { throw input_error("Unsupported proxy scheme: " + schema); } update_proxy(url, create_proxy_fn); } void ProxyManager::set_http_proxy_url(const std::string& url) { auto schema = net::parse_uri_scheme(url); auto [host, port] = net::parse_uri_host_port(url); if (schema != "http" && schema != "https" && schema != "socks4" && schema != "socks4a" && schema != "socks5" && schema != "socks5h") throw input_error("Unsupported proxy scheme: " + schema); if (host.empty()) throw input_error("Proxy address must include a host."); auto guard = lock_guard(); m_http_proxy_url = url; net_thread::http_stack()->set_http_proxy(url); } net::proxy_ptr ProxyManager::create_proxy(const sockaddr* sa) { if (!m_has_proxy) return nullptr; assert(sa != nullptr); assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6); assert(!sa_is_any(sa) && sa_port(sa) != 0); auto guard = lock_guard(); if (m_create_proxy == nullptr) return nullptr; return net::proxy_ptr(m_create_proxy(sa)); } void ProxyManager::clear_proxy() { bool changed_blocks{}; { auto guard = lock_guard(); changed_blocks = (m_create_proxy != nullptr); m_has_proxy = false; m_proxy_url = ""; m_create_proxy = nullptr; if (m_http_proxy_url.empty()) net_thread::http_stack()->set_http_proxy(""); } if (changed_blocks) { auto nw_config = network_config(); auto guard = nw_config->lock_guard(); nw_config->m_block_udp = false; nw_config->m_block_incoming = false; nw_config->notify_changes_unsafe(); } } void ProxyManager::update_proxy(const std::string& url, create_proxy_func create_proxy_fn) { { auto guard = lock_guard(); m_has_proxy = true; m_proxy_url = url; m_create_proxy = create_proxy_fn; if (m_http_proxy_url.empty()) net_thread::http_stack()->set_http_proxy(url); } { auto nw_config = network_config(); auto guard = nw_config->lock_guard(); nw_config->m_block_udp = true; nw_config->m_block_incoming = true; nw_config->notify_changes_unsafe(); } } } // namespace torrent::runtime libtorrent-0.16.17/src/torrent/runtime/proxy_manager.h000066400000000000000000000031071522271512000230430ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_RUNTIME_PROXY_MANAGER_H #define LIBTORRENT_TORRENT_RUNTIME_PROXY_MANAGER_H #include #include namespace torrent::runtime { class LIBTORRENT_EXPORT ProxyManager { public: // Currently not needed, use if we allow hostnames in the future. // static constexpr uint32_t max_proxy/connect_url_length = 256; ProxyManager(); ~ProxyManager() = default; // For now, only add one address, derive type using libcurl's url schema names: // // Need to be thread-safe. std::string proxy_url(); void set_proxy_url(const std::string& url); std::string http_proxy_url(); void set_http_proxy_url(const std::string& url); net::proxy_ptr create_proxy(const sockaddr* connect_sa); private: using create_proxy_func = std::function; auto lock_guard(); void clear_proxy(); void update_proxy(const std::string& url, create_proxy_func create_proxy_fn); std::atomic m_has_proxy{}; align_cacheline std::mutex m_mutex; std::string m_proxy_url; std::string m_http_proxy_url; create_proxy_func m_create_proxy; }; ProxyManager* proxy_manager() LIBTORRENT_EXPORT; inline std::string ProxyManager::proxy_url() { return m_proxy_url; } inline std::string ProxyManager::http_proxy_url() { return m_http_proxy_url; } inline auto ProxyManager::lock_guard() { return std::scoped_lock(m_mutex); } } // namespace torrent::net::proxy #endif libtorrent-0.16.17/src/torrent/runtime/runtime.cc000066400000000000000000000012461522271512000220130ustar00rootroot00000000000000#include "config.h" #include "torrent/runtime/runtime.h" #include "manager.h" #include "protocol/handshake_manager.h" #include "torrent/runtime/network_manager.h" namespace torrent::runtime { const char* version() { return VERSION; } uint16_t listen_port() { return network_manager()->listen_port(); } void dht_add_peer_node(const sockaddr* sa, uint16_t port) { network_manager()->dht_add_peer_node(sa, port); } uint32_t total_handshakes() { return manager->handshake_manager()->size(); } } // namespace torrent::runtime libtorrent-0.16.17/src/torrent/runtime/runtime.h000066400000000000000000000017701522271512000216570ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_RUNTIME_RUNTIME_H #define LIBTORRENT_TORRENT_RUNTIME_RUNTIME_H #include namespace torrent::runtime { class ProxyManager; bool is_shutting_down() LIBTORRENT_EXPORT; bool is_quick_shutting_down() LIBTORRENT_EXPORT; void shutdown() LIBTORRENT_EXPORT; void quick_shutdown() LIBTORRENT_EXPORT; NetworkConfig* network_config() LIBTORRENT_EXPORT; NetworkManager* network_manager() LIBTORRENT_EXPORT; SocketManager* socket_manager() LIBTORRENT_EXPORT; ProxyManager* proxy_manager() LIBTORRENT_EXPORT; const char* version() LIBTORRENT_EXPORT; uint16_t listen_port() LIBTORRENT_EXPORT; // Must be called from main thread: // TODO: Move void dht_add_peer_node(const sockaddr* sa, uint16_t port) LIBTORRENT_EXPORT; // TODO: Move to network/socket_manager? uint32_t total_handshakes() LIBTORRENT_EXPORT; } // namespace torrent::runtime #endif libtorrent-0.16.17/src/torrent/runtime/socket_manager.cc000066400000000000000000000514361522271512000233200ustar00rootroot00000000000000#include "config.h" #include "torrent/runtime/socket_manager.h" #include #include #include "torrent/event.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/utils/log.h" #include "torrent/system/thread.h" #define LT_LOG(log_fmt, ...) \ lt_log_print(LOG_NET_SOCKET, "socket_manager: " log_fmt, __VA_ARGS__); namespace { uint32_t calculate_min_generic(uint32_t open_max) { if (open_max >= 16384) return 12288; else if (open_max >= 8096) return 6144; else if (open_max >= 1024) return 512; else return 256; } uint32_t calculate_reserved(uint32_t open_max) { if (open_max >= 16384) return 512; else if (open_max >= 8096) return 256; else if (open_max >= 1024) return 128; else return 64; } uint32_t calculate_http(uint32_t open_max) { if (open_max >= 16384) return 128; else if (open_max >= 8096) return 64; else if (open_max >= 1024) return 32; else return 16; } uint32_t calculate_internal(uint32_t open_max) { if (open_max >= 16384) return 64; else if (open_max >= 1024) return 32; else return 16; } uint32_t calculate_rpc(uint32_t open_max) { if (open_max >= 16384) return 64; else if (open_max >= 8096) return 48; else if (open_max >= 1024) return 32; else return 16; } uint32_t calculate_files(uint32_t open_max) { if (open_max >= 32768) return 4096; else if (open_max >= 16384) return 2048; else if (open_max >= 8096) return 1024; else if (open_max >= 1024) return 128; else return 64; } } // namespace namespace torrent::runtime { SocketManager::SocketManager() { // TODO: Set load factor a bit higher than default to account for peak usage during startup / etc. for (auto& alloc : m_category_max_alloc) alloc = category_max_alloc; } SocketManager::~SocketManager() { assert(m_socket_map.empty() && "SocketManager::~SocketManager(): socket map not empty on destruction."); } uint32_t SocketManager::size() { return m_managed_size + m_unmanaged_size; } uint32_t SocketManager::max_size() { return m_max_size; } uint32_t SocketManager::category_managed_size(category_t category) { return managed_size_unsafe(category); } uint32_t SocketManager::category_max_size(category_t category) { return max_size_unsafe(category); } uint32_t SocketManager::category_min_allocation(category_t category) { if (category == category_generic || static_cast(category) >= category_count) throw internal_error("SocketManager::category_min_allocation(): invalid category"); return m_category_min_alloc[static_cast(category)]; } uint32_t SocketManager::category_max_allocation(category_t category) { if (category == category_generic || static_cast(category) >= category_count) throw internal_error("SocketManager::category_max_allocation(): invalid category"); return m_category_max_alloc[static_cast(category)]; } void SocketManager::set_category_min_allocation(category_t category, uint32_t min_alloc) { if (category == category_generic || static_cast(category) >= category_count) throw internal_error("SocketManager::set_category_min_allocation(): invalid category"); auto guard = lock_guard(); m_category_min_alloc[static_cast(category)] = min_alloc; } void SocketManager::set_category_max_allocation(category_t category, uint32_t max_alloc) { if (category == category_generic || static_cast(category) >= category_count) throw internal_error("SocketManager::set_category_max_allocation(): invalid category"); auto guard = lock_guard(); m_category_max_alloc[static_cast(category)] = max_alloc; } void SocketManager::set_max_size_and_adjust(uint32_t max_open) { if (max_open < 512) throw input_error("set_max_size_and_adjust: max_open too low, minimum is 512"); auto guard = lock_guard(); m_max_size = max_open; adjust_allocation_unsafe(); } void SocketManager::adjust_allocation() { auto guard = lock_guard(); adjust_allocation_unsafe(); } void SocketManager::adjust_allocation_unsafe() { auto max_open = m_max_size.load(); uint32_t total_allocated{}; auto set_category = [this, &total_allocated](category_t category, uint32_t allocation) { allocation = std::max(allocation, m_category_min_alloc[static_cast(category)].load()); allocation = std::min(allocation, m_category_max_alloc[static_cast(category)].load()); total_allocated += allocation; max_size_unsafe(category) = allocation; }; set_category(category_internal, calculate_internal(max_open)); set_category(category_http, calculate_http(max_open)); set_category(category_rpc, calculate_rpc(max_open)); set_category(category_files, calculate_files(max_open)); total_allocated += calculate_reserved(max_open); auto min_generic = calculate_min_generic(max_open); if (total_allocated + min_generic + 8 > max_open) throw internal_error("set_max_size_and_adjust: total + min_generic + 8 reserve exceeds max_open : " + std::to_string(total_allocated) + " + " + std::to_string(min_generic) + " + 8 > " + std::to_string(max_open)); max_size_unsafe(category_generic) = max_open - total_allocated; notify_changes_unsafe(); } void SocketManager::subscribe_to_changes(void* target, const std::function& callback) { auto guard = lock_guard(); m_change_subscribers.push_back(std::make_pair(target, callback)); } void SocketManager::unsubscribe_from_changes(void* target) { auto guard = lock_guard(); auto itr = std::remove_if(m_change_subscribers.begin(), m_change_subscribers.end(), [target](auto& p) { return p.first == target; }); m_change_subscribers.erase(itr, m_change_subscribers.end()); } void SocketManager::notify_changes_unsafe() const { for (auto& p : m_change_subscribers) p.second(); } void SocketManager::account_new_socket_unsafe(socket_map::iterator itr, category_t category) { itr->second.category = category; m_managed_size++; managed_size_unsafe(category)++; } void SocketManager::account_remove_socket_unsafe(socket_map::iterator itr) { if (m_managed_size == 0) throw internal_error("SocketManager::account_remove_socket_unsafe(): m_managed_size underflow"); auto category = itr->second.category; if (managed_size_unsafe(category) == 0) throw internal_error("SocketManager::account_remove_socket_unsafe(): category managed size underflow"); m_managed_size--; managed_size_unsafe(category)--; } void SocketManager::add_unmanaged_socket() { m_unmanaged_size++; } void SocketManager::remove_unmanaged_socket() { uint32_t old_size = m_unmanaged_size.fetch_sub(1); if (old_size == 0) throw internal_error("SocketManager::remove_unmanaged_socket(): no unmanaged sockets to remove"); } // TODO: Add check to open_event_*. bool SocketManager::can_open_socket(category_t category) { auto guard = lock_guard(); return can_open_socket_unsafe(category); } bool SocketManager::can_open_socket_unsafe(category_t category) { if (size() >= max_size()) return false; if (max_size_unsafe(category) != 0) return managed_size_unsafe(category) < max_size_unsafe(category); return true; } void SocketManager::open_event_or_throw(Event* event, category_t category, std::function func) { auto guard = lock_guard(); if (event->is_open()) throw internal_error("SocketManager::open_event_or_throw(): event is already open"); func(); if (!event->is_open()) { LT_LOG("open_event_or_throw() : %s:%s : failed to open socket", this_thread::thread()->name(), event->type_name()); return; } auto fd = event->file_descriptor(); auto [itr, inserted] = m_socket_map.try_emplace(fd, SocketInfo{fd, event, this_thread::thread()}); if (!inserted) { // We don't allow conflicts for this function. LT_LOG("open_event_or_throw() : %s:%s:%i : tried to use an existing file descriptor : %s:%s", this_thread::thread()->name(), event->type_name(), fd, itr->second.thread->name(), itr->second.event->type_name()); throw internal_error("SocketManager::open_event_or_throw(): tried to use an existing file descriptor: " + std::string(itr->second.thread->name()) + ":" + std::string(itr->second.event->type_name()) + ":" + std::to_string(fd)); } LT_LOG("open_event_or_throw() : %s:%s:%i : opened socket", this_thread::thread()->name(), event->type_name(), fd); account_new_socket_unsafe(itr, category); } bool SocketManager::open_event_or_cleanup(Event* event, category_t category, std::function func, std::function cleanup) { auto guard = lock_guard(); if (event->is_open()) throw internal_error("SocketManager::open_event_or_cleanup(): event is already open"); if (!can_open_socket_unsafe(category)) { LT_LOG("open_event_or_cleanup() : %s:%s : cannot open socket, max open sockets reached", this_thread::thread()->name(), event->type_name()); cleanup(false); return false; } func(); if (!event->is_open()) { LT_LOG("open_event_or_cleanup() : %s:%s : failed to open socket", this_thread::thread()->name(), event->type_name()); cleanup(true); return false; } auto fd = event->file_descriptor(); auto [itr, inserted] = m_socket_map.try_emplace(fd, SocketInfo{fd, event, this_thread::thread()}); if (inserted) { LT_LOG("open_event_or_cleanup() : %s:%s:%i : opened socket", this_thread::thread()->name(), event->type_name(), fd); account_new_socket_unsafe(itr, category); return true; } if (!handle_reused_socket(itr)) { LT_LOG("open_event_or_cleanup() : %s:%s:%i : failed to reuse existing file descriptor : %s:%s", this_thread::thread()->name(), event->type_name(), fd, itr->second.thread->name(), itr->second.event->type_name()); cleanup(true); return false; } LT_LOG("open_event_or_cleanup() : %s:%s:%i : reused existing file descriptor : %s:%s", this_thread::thread()->name(), event->type_name(), fd, itr->second.thread->name(), itr->second.event->type_name()); throw internal_error("SocketManager::open_event_or_cleanup(): reuse of existing file descriptor not supported yet."); account_remove_socket_unsafe(itr); itr->second = SocketInfo{fd, event, this_thread::thread()}; account_new_socket_unsafe(itr, category); return true; } void SocketManager::close_event_or_throw(Event* event, std::function func) { auto guard = lock_guard(); if (!event->is_open()) throw internal_error("SocketManager::close_event_or_throw(): event is not open"); auto fd = event->file_descriptor(); auto itr = m_socket_map.find(fd); if (itr == m_socket_map.end()) { LT_LOG("close_event_or_throw() : %s:%s:%i : trying to close unknown socket", this_thread::thread()->name(), event->type_name(), fd); throw internal_error("SocketManager::close_event_or_throw(): trying to close unknown socket fd"); } if (itr->second.event != event) { LT_LOG("close_event_or_throw() : %s:%s:%i : event mismatch when trying to close socket : %s:%s", this_thread::thread()->name(), event->type_name(), fd, itr->second.thread->name(), itr->second.event->type_name()); throw internal_error("SocketManager::close_event_or_throw(): event mismatch when trying to close socket fd"); } func(); if (event->is_open()) throw internal_error("SocketManager::close_event_or_throw(): event is still open after close function"); LT_LOG("close_event_or_throw() : %s:%s:%i : closed socket", this_thread::thread()->name(), event->type_name(), fd); account_remove_socket_unsafe(itr); m_socket_map.erase(itr); } void SocketManager::register_event_or_throw(Event* event, category_t category, std::function func) { auto guard = lock_guard(); if (!event->is_open()) throw internal_error("SocketManager::register_event_or_throw(): event is not open"); auto fd = event->file_descriptor(); auto [itr, inserted] = m_socket_map.try_emplace(fd, SocketInfo{fd, event, this_thread::thread()}); if (!inserted) { LT_LOG("register_event_or_throw() : %s:%s:%i : tried to register an existing file descriptor : %s:%s", this_thread::thread()->name(), event->type_name(), fd, itr->second.thread->name(), itr->second.event->type_name()); throw internal_error("SocketManager::register_event_or_throw(): tried to register an existing file descriptor: " + std::string(this_thread::thread()->name()) + ":" + std::string(event->type_name()) + ":" + std::to_string(fd) + " -> " + std::string(itr->second.thread->name()) + ":" + std::string(itr->second.event->type_name())); } func(); LT_LOG("register_event_or_throw() : %s:%s:%i : registered socket", this_thread::thread()->name(), event->type_name(), fd); account_new_socket_unsafe(itr, category); } void SocketManager::unregister_event_or_throw(Event* event, std::function func) { auto guard = lock_guard(); if (!event->is_open()) throw internal_error("SocketManager::unregister_event_or_throw(): event is not open"); auto fd = event->file_descriptor(); auto itr = m_socket_map.find(fd); if (itr == m_socket_map.end()) { LT_LOG("unregister_event_or_throw() : %s:%s:%i : trying to unregister unknown socket", this_thread::thread()->name(), event->type_name(), fd); throw internal_error("SocketManager::unregister_event_or_throw(): trying to unregister unknown socket fd"); } if (itr->second.event != event) { LT_LOG("unregister_event_or_throw() : %s:%s:%i : event mismatch when trying to unregister socket : %s:%s", this_thread::thread()->name(), event->type_name(), fd, itr->second.thread->name(), itr->second.event->type_name()); throw internal_error("SocketManager::unregister_event_or_throw(): event mismatch when trying to unregister socket fd"); } func(); LT_LOG("unregister_event_or_throw() : %s:%s:%i : unregistered socket", this_thread::thread()->name(), event->type_name(), fd); account_remove_socket_unsafe(itr); m_socket_map.erase(itr); } // Always returns non-null if func() succeeds. Event* SocketManager::transfer_event(Event* event_from, std::function func) { auto guard = lock_guard(); if (!event_from->is_open()) throw internal_error("SocketManager::transfer_event(): source event is not open"); auto fd = event_from->file_descriptor(); auto itr = m_socket_map.find(fd); if (itr == m_socket_map.end()) { LT_LOG("transfer_event() : %s:%s:%i : trying to transfer unknown socket", this_thread::thread()->name(), event_from->type_name(), fd); throw internal_error("SocketManager::transfer_event(): trying to transfer unknown socket fd"); } if (itr->second.event != event_from) throw internal_error("SocketManager::transfer_event(): event mismatch when trying to transfer socket fd"); auto event_to = func(); if (event_to == nullptr) { // Transfer failed, the func is responsible for cleaning up event_from. LT_LOG("transfer_event() : %s:%s:%i : socket transfer function returned nullptr", this_thread::thread()->name(), event_from->type_name(), fd); account_remove_socket_unsafe(itr); m_socket_map.erase(itr); return nullptr; } if (!event_to->is_open()) throw internal_error("SocketManager::transfer_event(): target event is not open after transfer"); itr->second.event = event_to; LT_LOG("transfer_event() : %s:%s:%i : transferred socket : %s", this_thread::thread()->name(), event_from->type_name(), fd, event_to->type_name()); return event_to; } bool SocketManager::execute_if_not_present(int fd, std::function func) { auto guard = lock_guard(); if (m_socket_map.find(fd) != m_socket_map.end()) return false; func(); return true; } bool SocketManager::mark_event_active_or_fail(Event* event) { auto guard = lock_guard(); if (!event->is_open()) throw internal_error("SocketManager::mark_event_active(): event is not open"); auto fd = event->file_descriptor(); auto itr = m_socket_map.find(fd); if (itr == m_socket_map.end()) { LT_LOG("mark_event_active_or_fail() : %s:%s:%i : fd was likely reused, then closed", this_thread::thread()->name(), event->type_name(), fd); return false; } if (itr->second.event != event) { LT_LOG("mark_event_active_or_fail() : %s:%s:%i : fd has been reused and is active : %s:%s", this_thread::thread()->name(), event->type_name(), fd, itr->second.thread->name(), itr->second.event->type_name()); return false; } if (event->socket_address() != nullptr) { if (!event->update_and_verify_socket_address()) { LT_LOG("mark_event_active_or_fail() : %s:%s:%i : socket address verification failed", this_thread::thread()->name(), event->type_name(), fd); return false; } if (!event->update_and_verify_peer_address()) { LT_LOG("mark_event_active_or_fail() : %s:%s:%i : peer address verification failed", this_thread::thread()->name(), event->type_name(), fd); return false; } } itr->second.flags &= ~flag_inactive; LT_LOG("mark_event_active() : %s:%s:%i : marked socket active", this_thread::thread()->name(), event->type_name(), fd); return true; } void SocketManager::mark_event_inactive(Event* event, std::function func) { auto guard = lock_guard(); if (!event->is_open()) throw internal_error("SocketManager::mark_event_inactive(): event is not open"); auto fd = event->file_descriptor(); auto itr = m_socket_map.find(fd); if (itr == m_socket_map.end()) throw internal_error("SocketManager::mark_event_inactive(): trying to mark unknown socket fd inactive"); if (itr->second.event != event) throw internal_error("SocketManager::mark_event_inactive(): event mismatch when trying to mark socket fd inactive"); func(); itr->second.flags |= flag_inactive; LT_LOG("mark_event_inactive() : %s:%s:%i : marked socket inactive", this_thread::thread()->name(), event->type_name(), fd); } // The socket must be in read/write to avoid reuse before calling this. // // Returns false is the socket was not connected. // Some of this relies on no non-SocketManager managed sockets being added to this_thread's // poller. Or else cleanup that removes reused fd's can cause issues. // TODO: Rename inet? bool SocketManager::mark_stream_event_inactive(Event* event, std::function func, std::function on_reuse) { auto guard = lock_guard(); if (!event->is_open()) throw internal_error("SocketManager::mark_event_inactive(): event is not open"); auto fd = event->file_descriptor(); auto itr = m_socket_map.find(fd); if (itr == m_socket_map.end()) throw internal_error("SocketManager::mark_event_inactive(): trying to mark unknown socket fd inactive"); if (itr->second.event != event) throw internal_error("SocketManager::mark_event_inactive(): event mismatch when trying to mark socket fd inactive"); if (!event->update_and_verify_socket_address()) { LT_LOG("mark_stream_event_inactive() : %s:%s:%i : socket address verification failed", this_thread::thread()->name(), event->type_name(), event->file_descriptor()); on_reuse(); return false; } if (!event->update_and_verify_peer_address()) { LT_LOG("mark_stream_event_inactive() : %s:%s:%i : peer address verification failed", this_thread::thread()->name(), event->type_name(), event->file_descriptor()); // TODO: Check if socket is still valid? on_reuse(); return false; } if (!event->socket_address()) { LT_LOG("mark_stream_event_inactive() : %s:%s:%i : socket address is null after update", this_thread::thread()->name(), event->type_name(), event->file_descriptor()); on_reuse(); return false; } // TODO: Check if we got a valid socket_address (a must). LT_LOG("mark_stream_event_inactive() : %s:%s:%i : marking stream socket inactive : socket:%s peer:%s", this_thread::thread()->name(), event->type_name(), event->file_descriptor(), sa_pretty_str(event->socket_address()).c_str(), sa_pretty_str(event->peer_address()).c_str()); func(); itr->second.flags |= flag_inactive; LT_LOG("mark_event_inactive() : %s:%s:%i : marked socket inactive", this_thread::thread()->name(), event->type_name(), fd); return true; } // TODO: Finish implementation of reuse logic. (not as needed now that we check) bool SocketManager::handle_reused_socket(socket_map::iterator itr) { if (!(itr->second.flags & flag_inactive)) return false; // TODO: Add event to a closed list, which will be checked when mark_event_active is called. // // TODO: Until the, just disallow reuse. return false; // return true; } } // namespace torrent::runtime libtorrent-0.16.17/src/torrent/runtime/socket_manager.h000066400000000000000000000144141522271512000231550ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_RUNTIME_SOCKET_MANAGER_H #define LIBTORRENT_TORRENT_RUNTIME_SOCKET_MANAGER_H #include #include #include #include #include #include #include // A socket that is closed by the kernel while neitehr ready nor write polling will be considered // uninterested by the kernel, and possibly reused. // // Those sockets must be marked as 'inactive' in the socket manager, and when entering active state // it must check the socket manager if the socket is still valid. // // File and other socket types that do not unexpectedly get closed by the kernel do not need to be // managed. namespace torrent::runtime { SocketManager* socket_manager() LIBTORRENT_EXPORT; // Categories of allocated sockets within the global pool. // // When a category's max is non-zero, open operations additionally check that the category's // current usage has not reached its limit. enum class socket_manager_category_t : uint32_t { category_generic, // peer connections, uncategorized category_http, // HTTP/curl category_internal, // DHT, UDP tracker, thread interrupt, BT listen category_rpc, // SCGI/RPC category_files // open files (mmap, etc.) }; using enum socket_manager_category_t; struct SocketInfo { // TODO: Replace with Event*, and add thread to PollEvent? // TODO: Event should contain the owning thread, and not be included here. // TODO: Fd not needed here. int fd{-1}; Event* event{}; system::Thread* thread{}; int flags{}; socket_manager_category_t category{}; }; class LIBTORRENT_EXPORT SocketManager { public: static constexpr uint32_t category_count = 5; static constexpr uint32_t category_max_alloc = 1000000; static constexpr int flag_inactive = (1 << 0); using category_t = socket_manager_category_t; SocketManager(); ~SocketManager(); uint32_t size(); uint32_t max_size(); uint32_t category_managed_size(category_t category); uint32_t category_max_size(category_t category); uint32_t category_min_allocation(category_t category); uint32_t category_max_allocation(category_t category); void set_category_min_allocation(category_t category, uint32_t min_alloc); void set_category_max_allocation(category_t category, uint32_t max_alloc); void set_max_size_and_adjust(uint32_t max_open); void adjust_allocation(); void add_unmanaged_socket(); void remove_unmanaged_socket(); bool can_open_socket(category_t category); // TODO: Rename / change _or_throw to be more specific about what we throw, as it currently calls // internal errors. DHT server should probably throw resource_error instead? Or retry. (this // however hides errors for normal users) // // TODO: Finish implementation of reuse logic. // To avoid reuse race conditions, these calls must also add the Event to read/write polling. // Throw internal_error on conflicts, as the caller isn't expecting conflicts. void open_event_or_throw(Event* event, category_t category, std::function func); // Try to reuse existing socket, if that fails call cleanup. // // This is used for listen accept and other cases where the caller doesn't mind ignoring failures. bool open_event_or_cleanup(Event* event, category_t category, std::function func, std::function cleanup); void close_event_or_throw(Event* event, std::function func); // Event already opened the socket, just register it. void register_event_or_throw(Event* event, category_t category, std::function func); void unregister_event_or_throw(Event* event, std::function func); // The func must close event_from and the pointer must remain valid. Event* transfer_event(Event* event_from, std::function func); bool execute_if_not_present(int fd, std::function func); [[nodiscard]] bool mark_event_active_or_fail(Event* event); void mark_event_inactive(Event* event, std::function func); [[nodiscard]] bool mark_stream_event_inactive(Event* event, std::function func, std::function on_reuse); // The lock is held while the callback is called, so use Thread::callback(). void subscribe_to_changes(void* target, const std::function& callback); void unsubscribe_from_changes(void* target); protected: auto lock_guard() { return std::lock_guard(m_mutex); } private: using socket_map = std::unordered_map; using category_list = std::array, category_count>; using subscriber_list = std::vector>>; auto& managed_size_unsafe(category_t category); auto& max_size_unsafe(category_t category); void adjust_allocation_unsafe(); void notify_changes_unsafe() const; void account_new_socket_unsafe(socket_map::iterator itr, category_t category); void account_remove_socket_unsafe(socket_map::iterator itr); bool can_open_socket_unsafe(category_t category); bool handle_reused_socket(socket_map::iterator itr); std::atomic m_managed_size{}; std::atomic m_unmanaged_size{}; std::atomic m_max_size{}; category_list m_category_managed_size{}; category_list m_category_max_size{}; category_list m_category_min_alloc{}; category_list m_category_max_alloc{}; align_cacheline std::mutex m_mutex; subscriber_list m_change_subscribers; socket_map m_socket_map; }; inline auto& SocketManager::managed_size_unsafe(category_t category) { return m_category_managed_size[static_cast(category)]; } inline auto& SocketManager::max_size_unsafe(category_t category) { return m_category_max_size[static_cast(category)]; } } // namespace torrent::runtime #endif // LIBTORRENT_TORRENT_RUNTIME_SOCKET_MANAGER_H libtorrent-0.16.17/src/torrent/system/000077500000000000000000000000001522271512000176575ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/system/callbacks.h000066400000000000000000000055521522271512000217560ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_SYSTEM_CALLBACKS_H #define LIBTORRENT_TORRENT_SYSTEM_CALLBACKS_H #include #include #include namespace torrent::system { // Only two threads can share a callback id and safely use cancel_callback_and_wait(). // using callback_id = std::shared_ptr>; inline callback_id make_callback_id() { return std::make_shared>(0); } void cancel_callback_and_wait(callback_id& id, Thread* thread1, Thread* thread2) LIBTORRENT_EXPORT; } // namespace torrent::system // TODO: These should really be in runtime. namespace torrent::main_thread { void callback(std::function&& fn) LIBTORRENT_EXPORT; void callback(system::callback_id& id, std::function&& fn) LIBTORRENT_EXPORT; void callback_interrupt(std::function&& fn) LIBTORRENT_EXPORT; void callback_interrupt(system::callback_id& id, std::function&& fn) LIBTORRENT_EXPORT; void cancel_callback(system::callback_id& id) LIBTORRENT_EXPORT; void cancel_callback_and_wait(system::callback_id& id) LIBTORRENT_EXPORT; } // namespace torrent::main_thread namespace torrent::disk_thread { void callback(std::function&& fn) LIBTORRENT_EXPORT; void callback(system::callback_id& id, std::function&& fn) LIBTORRENT_EXPORT; void callback_interrupt(std::function&& fn) LIBTORRENT_EXPORT; void callback_interrupt(system::callback_id& id, std::function&& fn) LIBTORRENT_EXPORT; void cancel_callback(system::callback_id& id) LIBTORRENT_EXPORT; void cancel_callback_and_wait(system::callback_id& id) LIBTORRENT_EXPORT; } // namespace torrent::disk_thread namespace torrent::net_thread { void callback(std::function&& fn) LIBTORRENT_EXPORT; void callback(system::callback_id& id, std::function&& fn) LIBTORRENT_EXPORT; void callback_interrupt(std::function&& fn) LIBTORRENT_EXPORT; void callback_interrupt(system::callback_id& id, std::function&& fn) LIBTORRENT_EXPORT; void cancel_callback(system::callback_id& id) LIBTORRENT_EXPORT; void cancel_callback_and_wait(system::callback_id& id) LIBTORRENT_EXPORT; } // namespace torrent::tracker_thread namespace torrent::tracker_thread { void callback(std::function&& fn) LIBTORRENT_EXPORT; void callback(system::callback_id& id, std::function&& fn) LIBTORRENT_EXPORT; void cancel_callback(system::callback_id& id) LIBTORRENT_EXPORT; void cancel_callback_and_wait(system::callback_id& id) LIBTORRENT_EXPORT; } // namespace torrent::tracker_thread #endif libtorrent-0.16.17/src/torrent/system/poll.h000066400000000000000000000044521522271512000210030ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_SYSTEM_POLL_H #define LIBTORRENT_TORRENT_SYSTEM_POLL_H #include #include #include namespace torrent::system { class PollEvent; class PollInternal; class LIBTORRENT_EXPORT Poll { public: static std::unique_ptr create(); ~Poll(); // TODO: Make protected. unsigned int do_poll(std::chrono::microseconds timeout); void do_interrupt(); // The open max value is used when initializing libtorrent, it // should be less than or equal to sysconf(_SC_OPEN_MAX). uint32_t open_max() const; // Event::get_fd() is guaranteed to be valid and remain constant // from open(...) is called to close(...) returns. The implementor // of this class should not open nor close the file descriptor. void open(Event* event); void close(Event* event); // Functions for checking whetever the Event is listening to r/w/e? bool in_read(Event* event); bool in_write(Event* event); bool in_error(Event* event); // These functions may be called on 'event's that might, or might // not, already be in the set. void insert_read(Event* event); void insert_write(Event* event); void insert_error(Event* event); void remove_read(Event* event); void remove_write(Event* event); void remove_error(Event* event); void remove_and_close(Event* event); // Add one for HUP? Or would that be in event? protected: friend class Thread; void init_thread(); void cleanup_thread(); private: using poll_event_list = std::vector>; static constexpr int flag_polling = 0x1; static constexpr int flag_interrupted = 0x2; static constexpr int flag_state_mask = 0x3; Poll() = default; Poll(const Poll&) = delete; Poll& operator=(const Poll&) = delete; int poll(std::chrono::microseconds timeout); unsigned int process(); bool m_processing{false}; poll_event_list m_closed_events; std::unique_ptr m_internal; align_cacheline std::atomic m_polling_state{}; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/system/poll_epoll.cc000066400000000000000000000331171522271512000223340ustar00rootroot00000000000000#include "config.h" #ifdef USE_EPOLL #include "torrent/system/poll.h" #include #include #include #include "net/event_fd.h" #include "torrent/exceptions.h" #include "torrent/event.h" #include "torrent/net/fd.h" #include "torrent/system/thread.h" #include "torrent/utils/log.h" #define LT_LOG(log_fmt, ...) \ lt_log_print(LOG_SYSTEM_POLL, "epoll: " log_fmt, __VA_ARGS__); #define LT_LOG_EVENT(log_fmt, ...) \ lt_log_print(LOG_SYSTEM_POLL, "epoll->%i : %s : " log_fmt, event->file_descriptor(), event->type_name(), __VA_ARGS__); #ifdef DEBUG #define LT_LOG_DEBUG_DATA_FD(log_fmt, ...) \ lt_log_print(LOG_SYSTEM_POLL, "epoll->%i: " log_fmt, itr->data.fd, __VA_ARGS__); #else #define LT_LOG_DEBUG_DATA_FD(log_fmt, ...) #endif namespace torrent::system { class PollEvent { public: PollEvent(Event* event) : event(event) {} ~PollEvent() = default; uint32_t mask{}; Event* event{}; }; class PollInternal { public: using Table = std::map>; uint32_t event_mask(Event* event); void set_event_mask(Event* event, uint32_t mask); void flush(); void modify(torrent::Event* event, unsigned short op, uint32_t mask, uint32_t old_mask); int m_fd{}; unsigned int m_max_sockets{}; unsigned int m_max_events{}; unsigned int m_waiting_events{}; unsigned int m_callback_interrupt_backoff{}; Table m_table; std::unique_ptr m_events; net::EventFd m_wake_event; }; uint32_t PollInternal::event_mask(Event* event) { if (event->file_descriptor() == -1) throw internal_error("PollInternal::event_mask() invalid file descriptor for event: " + event->print_name_fd_str()); auto itr = m_table.find(event->file_descriptor()); if (itr == m_table.end()) throw internal_error("PollInternal::event_mask() event not found: " + event->print_name_fd_str()); if (event != itr->second->event) throw internal_error("PollInternal::event_mask() event mismatch: " + event->print_name_fd_str()); return itr->second->mask; } void PollInternal::set_event_mask(Event* event, uint32_t mask) { if (event->file_descriptor() == -1) throw internal_error("PollInternal::set_event_mask() invalid file descriptor for event: " + event->print_name_fd_str()); event->m_poll_event->mask = mask; } // See https://github.com/enki/libev/blob/master/ev_epoll.c for suggestions. void PollInternal::modify(Event* event, unsigned short op, uint32_t mask, uint32_t old_mask) { if (event_mask(event) == mask) return; LT_LOG_EVENT("modify event : op:%hx mask:%hx", op, mask); if (event->m_poll_event == nullptr) throw internal_error("PollInternal::modify(): event has no PollEvent associated: " + event->print_name_fd_str()); epoll_event e{}; e.data.ptr = event->m_poll_event.get(); e.events = mask; set_event_mask(event, mask); if (epoll_ctl(m_fd, op, event->file_descriptor(), &e)) { switch (op) { case EPOLL_CTL_ADD: LT_LOG_EVENT("modify event EPOLL_CTL_ADD failed: %s", std::strerror(errno)); throw internal_error("PollInternal::modify(): epoll_ctl(ADD) error for event: " + event->print_name_fd_str() + " : " + std::string(std::strerror(errno))); case EPOLL_CTL_MOD: if (errno == ENOENT) { LT_LOG_EVENT("modify event EPOLL_CTL_MOD failed with ENOENT", 0); // TODO: We might be getting removed because EPOLLERR is not considered being in the poll? // // We still seem to be getting error events (verify with libcurl idle poll). // // Add a test here to see if we're moving from no events to some events, and only retry in that case. if (old_mask & (EPOLLIN | EPOLLOUT)) { LT_LOG_EVENT("modify event EPOLL_CTL_MOD cannot retry as old mask had read/write", 0); throw internal_error("PollInternal::modify(): epoll_ctl(MOD) error for event: " + event->print_name_fd_str() + " : in read/write but got ENOENT"); } if (epoll_ctl(m_fd, EPOLL_CTL_ADD, event->file_descriptor(), &e) == 0) { LT_LOG_EVENT("modify event EPOLL_CTL_ADD retry after ENOENT succeeded", 0); return; } } LT_LOG_EVENT("modify event EPOLL_CTL_MOD failed: %s", std::strerror(errno)); throw internal_error("PollInternal::modify(): epoll_ctl(MOD) error for event: " + event->print_name_fd_str() + " : " + std::string(std::strerror(errno))); case EPOLL_CTL_DEL: LT_LOG_EVENT("modify event EPOLL_CTL_DEL failed: %s", std::strerror(errno)); throw internal_error("PollInternal::modify(): epoll_ctl(DEL) error for event: " + event->print_name_fd_str() + " : " + std::string(std::strerror(errno))); default: throw internal_error("PollInternal::modify(): unknown epoll_ctl operation: " + std::to_string(op) + " for event: " + event->print_name_fd_str()); } } } // // Poll: // std::unique_ptr Poll::create() { auto socket_open_max = sysconf(_SC_OPEN_MAX); if (socket_open_max == -1) throw internal_error("Poll::create() : sysconf(_SC_OPEN_MAX) failed : " + std::string(std::strerror(errno))); int fd = fd_open_epoll(socket_open_max); if (fd == -1) throw internal_error("Poll::create() : epoll_create() failed : " + std::string(std::strerror(errno))); auto poll = new Poll(); poll->m_internal = std::make_unique(); poll->m_internal->m_fd = fd; poll->m_internal->m_max_sockets = static_cast(socket_open_max); poll->m_internal->m_max_events = 1024; poll->m_internal->m_events = std::make_unique(poll->m_internal->m_max_events); return std::unique_ptr(poll); } Poll::~Poll() { assert(m_internal->m_table.empty() && "Poll::~Poll() called with non-empty event table."); ::close(m_internal->m_fd); m_internal->m_fd = -1; } void Poll::init_thread() { m_internal->m_wake_event.add_to_poll(); } void Poll::cleanup_thread() { if (m_internal->m_wake_event.is_open()) m_internal->m_wake_event.remove_from_poll(this); } unsigned int Poll::do_poll(std::chrono::microseconds timeout) { int status = poll(timeout); if (status == -1) { if (errno != EINTR) throw internal_error("Poll::work() " + std::string(std::strerror(errno))); return 0; } return process(); } void Poll::do_interrupt() { int expected_state = flag_polling; if (!m_polling_state.compare_exchange_strong(expected_state, flag_polling | flag_interrupted, std::memory_order_release, std::memory_order_relaxed)) return; m_internal->m_wake_event.send_signal(); } int Poll::poll(std::chrono::microseconds timeout) { auto previous_state = m_polling_state.fetch_or(flag_polling, std::memory_order_acquire); bool callback_interrupting{}; if (previous_state & flag_interrupted || system::Thread::self()->has_any_callbacks()) { auto backoff_shift = std::min(m_internal->m_callback_interrupt_backoff, 3u); if (backoff_shift > 0) timeout = std::min(1000us * (1 << (backoff_shift - 1)), timeout); else timeout = 0us; callback_interrupting = true; } int nfds = ::epoll_wait(m_internal->m_fd, m_internal->m_events.get(), m_internal->m_max_events, static_cast(timeout.count() / 1000)); m_polling_state.fetch_and(~flag_state_mask, std::memory_order_release); if (nfds == -1) return -1; if (nfds == 1 && callback_interrupting && m_internal->m_events[0].data.fd == m_internal->m_wake_event.file_descriptor()) m_internal->m_callback_interrupt_backoff++; else m_internal->m_callback_interrupt_backoff = 0; m_internal->m_waiting_events = nfds; return nfds; } // We check m_internal->m_table to make sure the Event is still listening to the // event, so it is safe to remove Event's while in working. // // TODO: Do we want to guarantee if the Event has been removed from // some event but not closed, it won't call that event? Think so... unsigned int Poll::process() { unsigned int count{}; m_processing = true; m_closed_events.clear(); for (epoll_event *itr = m_internal->m_events.get(), *last = m_internal->m_events.get() + m_internal->m_waiting_events; itr != last; ++itr) { if (system::Thread::self()->has_interrupt_callbacks()) system::Thread::self()->process_callbacks(true); auto* poll_event = static_cast(itr->data.ptr); if (itr->events & EPOLLERR) { count++; if (poll_event->event == nullptr) continue; // TODO: EPOLLERR is always reported, so we don't need error event registration. if (!(poll_event->mask & EPOLLERR)) throw internal_error("Poll::process() received error event for event not in error: " + poll_event->event->print_name_fd_str()); auto event_info = poll_event->event->print_name_fd_str(); poll_event->event->event_error(); if (poll_event->mask != 0) throw internal_error("Poll::process() event_error called but event mask not cleared: " + event_info); // We assume that the event gets closed if we get an error. continue; } if (itr->events & EPOLLIN && poll_event->mask & EPOLLIN) { poll_event->event->event_read(); count++; } if (itr->events & EPOLLOUT && poll_event->mask & EPOLLOUT) { poll_event->event->event_write(); count++; } } m_closed_events.clear(); m_processing = false; m_internal->m_waiting_events = 0; return count; } uint32_t Poll::open_max() const { return m_internal->m_max_sockets; } // TODO: Change open() to take initial filter state. // TODO: Make open register for at least error events. void Poll::open(Event* event) { LT_LOG_EVENT("open event", 0); if (event->file_descriptor() == -1) throw internal_error("Poll::open() invalid file descriptor for event: " + event->print_name_fd_str()); if (event->m_poll_event != nullptr) throw internal_error("Poll::open() called but the event is already associated with a poll: " + event->print_name_fd_str()); if (m_internal->m_table.find(event->file_descriptor()) != m_internal->m_table.end()) throw internal_error("Poll::open() event already exists: " + event->print_name_fd_str()); event->m_poll_event = std::make_shared(event); m_internal->m_table[event->file_descriptor()] = event->m_poll_event; } void Poll::close(Event* event) { LT_LOG_EVENT("close event", 0); auto* poll_event = event->m_poll_event.get(); if (poll_event == nullptr) return; if (poll_event->event != event) throw internal_error("Poll::close() event mismatch: " + event->print_name_fd_str()); if (m_internal->event_mask(event) != 0) throw internal_error("Poll::close() called but the file descriptor is active: " + event->print_name_fd_str()); if (m_internal->m_table.erase(event->file_descriptor()) == 0) throw internal_error("Poll::close() event not found: " + event->print_name_fd_str()); if (m_processing) m_closed_events.push_back(event->m_poll_event); poll_event->event = nullptr; event->m_poll_event = nullptr; } bool Poll::in_read(Event* event) { return m_internal->event_mask(event) & EPOLLIN; } bool Poll::in_write(Event* event) { return m_internal->event_mask(event) & EPOLLOUT; } bool Poll::in_error(Event* event) { return m_internal->event_mask(event) & EPOLLERR; } void Poll::insert_read(Event* event) { auto mask = m_internal->event_mask(event); if (mask & EPOLLIN) return; LT_LOG_EVENT("insert read", 0); m_internal->modify(event, mask ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, mask | EPOLLIN, mask); } void Poll::insert_write(Event* event) { auto mask = m_internal->event_mask(event); if (mask & EPOLLOUT) return; LT_LOG_EVENT("insert write", 0); m_internal->modify(event, mask ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, mask | EPOLLOUT, mask); } void Poll::insert_error(Event* event) { auto mask = m_internal->event_mask(event); if (mask & EPOLLERR) return; LT_LOG_EVENT("insert error", 0); m_internal->modify(event, mask ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, mask | EPOLLERR, mask); } void Poll::remove_read(Event* event) { auto mask = m_internal->event_mask(event); auto new_mask = mask & ~EPOLLIN; if (!(mask & EPOLLIN)) return; LT_LOG_EVENT("remove read", 0); m_internal->modify(event, new_mask ? EPOLL_CTL_MOD : EPOLL_CTL_DEL, new_mask, mask); } void Poll::remove_write(Event* event) { auto mask = m_internal->event_mask(event); auto new_mask = mask & ~EPOLLOUT; if (!(mask & EPOLLOUT)) return; LT_LOG_EVENT("remove write", 0); m_internal->modify(event, new_mask ? EPOLL_CTL_MOD : EPOLL_CTL_DEL, new_mask, mask); } void Poll::remove_error(Event* event) { auto mask = m_internal->event_mask(event); auto new_mask = mask & ~EPOLLERR; if (!(mask & EPOLLERR)) return; LT_LOG_EVENT("remove error", 0); m_internal->modify(event, new_mask ? EPOLL_CTL_MOD : EPOLL_CTL_DEL, new_mask, mask); } void Poll::remove_and_close(Event* event) { remove_read(event); remove_write(event); remove_error(event); close(event); } } // namespace torrent #endif // USE_EPOLL libtorrent-0.16.17/src/torrent/system/poll_kqueue.cc000066400000000000000000000326201522271512000225160ustar00rootroot00000000000000#include "config.h" #ifdef USE_KQUEUE #include "torrent/system/poll.h" #include #include #include #include #include #include "utils/log.h" #include "torrent/event.h" #include "torrent/exceptions.h" #include "torrent/net/fd.h" #include "torrent/system/thread.h" // TODO: Change to use unordered_map, and at regular intervals trim the size? #define LT_LOG(log_fmt, ...) \ lt_log_print(LOG_SYSTEM_POLL, "kqueue: " log_fmt, __VA_ARGS__); #define LT_LOG_EVENT(log_fmt, ...) \ lt_log_print(LOG_SYSTEM_POLL, "kqueue->%i : %s : " log_fmt, event->file_descriptor(), event->type_name(), __VA_ARGS__); #ifdef DEBUG #define LT_LOG_DEBUG(log_fmt, ...) \ lt_log_print(LOG_SYSTEM_POLL, "kqueue: " log_fmt, __VA_ARGS__); #define LT_LOG_DEBUG_IDENT(log_fmt, ...) \ lt_log_print(LOG_SYSTEM_POLL, "kqueue->%u : " log_fmt, static_cast(itr->ident), __VA_ARGS__); #else #define LT_LOG_DEBUG(log_fmt, ...) #define LT_LOG_DEBUG_IDENT(log_fmt, ...) #endif namespace torrent::system { class PollEvent { public: PollEvent(Event* e) : event(e) {} ~PollEvent() = default; uint32_t mask{}; Event* event{}; }; class PollInternal { public: using Table = std::map>; static constexpr uint32_t flag_read = 0x1; static constexpr uint32_t flag_write = 0x2; static constexpr uint32_t flag_error = 0x4; uint32_t event_mask(Event* event); void set_event_mask(Event* event, uint32_t mask); void flush(); void modify(torrent::Event* event, unsigned short op, short mask); inline void create_user_event(); inline void poke_user_event(); int m_fd; unsigned int m_max_sockets{}; unsigned int m_max_events{}; unsigned int m_waiting_events{}; unsigned int m_changed_events{}; unsigned int m_callback_interrupt_backoff{}; Table m_table; std::unique_ptr m_events; std::unique_ptr m_changes; }; uint32_t PollInternal::event_mask(Event* event) { // TODO: Replace `file_descriptor()` with m_event_poll. if (event->file_descriptor() == -1) throw internal_error("PollInternal::event_mask() invalid file descriptor for event: " + event->print_name_fd_str()); auto itr = m_table.find(event->file_descriptor()); if (itr == m_table.end()) throw internal_error("PollInternal::event_mask() event not found: " + event->print_name_fd_str()); if (event != itr->second->event) throw internal_error("PollInternal::event_mask() event mismatch: " + event->print_name_fd_str()); return itr->second->mask; } void PollInternal::set_event_mask(Event* event, uint32_t mask) { if (event->file_descriptor() == -1) throw internal_error("PollInternal::set_event_mask() invalid file descriptor for event: " + event->print_name_fd_str()); event->m_poll_event->mask = mask; } void PollInternal::flush() { if (m_changed_events == 0) return; LT_LOG_DEBUG("flushing events : changed:%u", m_changed_events); if (::kevent(m_fd, m_changes.get(), m_changed_events, nullptr, 0, nullptr) == -1) throw internal_error("PollInternal::flush() error: " + std::string(std::strerror(errno))); m_changed_events = 0; } void PollInternal::modify(Event* event, unsigned short op, short mask) { LT_LOG_EVENT("modify event : op:%hx mask:%hx changed:%u", op, mask, m_changed_events); // Flush the changed filters to the kernel if the buffer is full. if (m_changed_events == m_max_events) flush(); struct kevent* itr = m_changes.get() + (m_changed_events++); EV_SET(itr, event->file_descriptor(), mask, op, 0, 0, event->m_poll_event.get()); } inline void PollInternal::create_user_event() { struct kevent event{}; EV_SET(&event, 0, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, nullptr); if (::kevent(m_fd, &event, 1, nullptr, 0, nullptr) == -1) throw internal_error("PollInternal::create_user_event() error: " + std::string(std::strerror(errno))); } inline void PollInternal::poke_user_event() { struct kevent event{}; EV_SET(&event, 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, nullptr); if (::kevent(m_fd, &event, 1, nullptr, 0, nullptr) == -1) { if (m_fd == -1) return; // The poll was already closed, so ignore this error. throw internal_error("PollInternal::poke_user_event() error: " + std::string(std::strerror(errno))); } } std::unique_ptr Poll::create() { auto socket_open_max = sysconf(_SC_OPEN_MAX); if (socket_open_max == -1) throw internal_error("Poll::create() : sysconf(_SC_OPEN_MAX) failed : " + std::string(std::strerror(errno))); int fd = fd_open_kqueue(); if (fd == -1) throw internal_error("Poll::create() : kqueue() failed : " + std::string(std::strerror(errno))); auto poll = new Poll(); poll->m_internal = std::make_unique(); poll->m_internal->m_fd = fd; poll->m_internal->m_max_sockets = static_cast(socket_open_max); poll->m_internal->m_max_events = 1024; poll->m_internal->m_events = std::make_unique(poll->m_internal->m_max_events); poll->m_internal->m_changes = std::make_unique(poll->m_internal->m_max_events); poll->m_internal->create_user_event(); return std::unique_ptr(poll); } Poll::~Poll() { assert(m_internal->m_table.empty() && "Poll::~Poll() called with non-empty event table."); ::close(m_internal->m_fd); m_internal->m_fd = -1; } void Poll::init_thread() { } void Poll::cleanup_thread() { } unsigned int Poll::do_poll(std::chrono::microseconds timeout) { int status = poll(timeout); if (status == -1) { if (errno != EINTR) throw internal_error("Poll::do_poll() error: " + std::string(std::strerror(errno))); return 0; } return process(); } int Poll::poll(std::chrono::microseconds timeout) { auto previous_state = m_polling_state.fetch_or(flag_polling, std::memory_order_acquire); bool callback_interrupting{}; if (previous_state & flag_interrupted || system::Thread::self()->has_any_callbacks()) { auto backoff_shift = std::min(m_internal->m_callback_interrupt_backoff, 3u); if (backoff_shift > 0) timeout = std::min(1000us * (1 << (backoff_shift - 1)), timeout); else timeout = 0us; callback_interrupting = true; } timespec timeout_spec = { static_cast(timeout.count() / 1000000), static_cast(timeout.count() % 1000000) * 1000 }; int nfds = ::kevent(m_internal->m_fd, m_internal->m_changes.get(), m_internal->m_changed_events, m_internal->m_events.get(), m_internal->m_max_events, &timeout_spec); m_polling_state.fetch_and(~flag_state_mask, std::memory_order_release); // Clear the changed events even on fail as we might have received a // signal or similar, and the changed events have already been // consumed. // // There's a chance a bad changed event could make kevent return -1, // but it won't as long as there is room enough in m_internal->m_events. m_internal->m_changed_events = 0; if (nfds == -1) return -1; if (nfds == 1 && callback_interrupting && m_internal->m_events[0].filter == EVFILT_USER) m_internal->m_callback_interrupt_backoff++; else m_internal->m_callback_interrupt_backoff = 0; m_internal->m_waiting_events = nfds; return nfds; } void Poll::do_interrupt() { int expected_state = flag_polling; if (!m_polling_state.compare_exchange_strong(expected_state, flag_polling | flag_interrupted, std::memory_order_release, std::memory_order_relaxed)) return; m_internal->poke_user_event(); } unsigned int Poll::process() { unsigned int count{}; m_processing = true; m_closed_events.clear(); for (struct kevent *itr = m_internal->m_events.get(), *last = m_internal->m_events.get() + m_internal->m_waiting_events; itr != last; ++itr) { if (system::Thread::self()->has_interrupt_callbacks()) system::Thread::self()->process_callbacks(true); auto* poll_event = static_cast(itr->udata); if ((itr->flags & EV_ERROR)) { count++; if (poll_event->event == nullptr) continue; if (!(poll_event->mask & PollInternal::flag_error)) throw internal_error("Poll::process() received error event for event not in error: " + poll_event->event->print_name_fd_str()); auto event_info = poll_event->event->print_name_fd_str(); poll_event->event->event_error(); if (poll_event->mask != 0) throw internal_error("Poll::process() event_error called but event mask not cleared: " + event_info); // We assume that the event gets closed if we get an error. continue; } if (itr->filter == EVFILT_READ && (poll_event->mask & PollInternal::flag_read)) { count++; poll_event->event->event_read(); } else if (itr->filter == EVFILT_READ) { LT_LOG_DEBUG_IDENT("spurious read event, skipping", 0); } if (itr->filter == EVFILT_WRITE && (poll_event->mask & PollInternal::flag_write)) { count++; poll_event->event->event_write(); } else if (itr->filter == EVFILT_WRITE) { LT_LOG_DEBUG_IDENT("spurious write event, skipping", 0); } } m_closed_events.clear(); m_processing = false; m_internal->m_waiting_events = 0; return count; } uint32_t Poll::open_max() const { return m_internal->m_max_sockets; } void Poll::open(Event* event) { LT_LOG_EVENT("open event", 0); if (event->file_descriptor() == -1) throw internal_error("Poll::open() invalid file descriptor for event: " + event->print_name_fd_str()); if (event->m_poll_event != nullptr) throw internal_error("Poll::open() called but the event is already associated with a poll: " + event->print_name_fd_str()); if (m_internal->m_table.find(event->file_descriptor()) != m_internal->m_table.end()) throw internal_error("Poll::open() event already exists: " + event->print_name_fd_str()); event->m_poll_event = std::make_shared(event); m_internal->m_table[event->file_descriptor()] = event->m_poll_event; } void Poll::close(Event* event) { LT_LOG_EVENT("close event", 0); auto* poll_event = event->m_poll_event.get(); if (poll_event == nullptr) return; if (poll_event->event != event) throw internal_error("Poll::close() event mismatch: " + event->print_name_fd_str()); if (m_internal->event_mask(event) != 0) throw internal_error("Poll::close() called but the file descriptor is active: " + event->print_name_fd_str()); if (m_internal->m_table.erase(event->file_descriptor()) == 0) throw internal_error("Poll::close() event not found: " + event->print_name_fd_str()); m_internal->flush(); if (m_processing) m_closed_events.push_back(event->m_poll_event); poll_event->event = nullptr; event->m_poll_event = nullptr; } bool Poll::in_read(Event* event) { return m_internal->event_mask(event) & PollInternal::flag_read; } bool Poll::in_write(Event* event) { return m_internal->event_mask(event) & PollInternal::flag_write; } bool Poll::in_error(Event* event) { return m_internal->event_mask(event) & PollInternal::flag_error; } void Poll::insert_read(Event* event) { auto event_mask = m_internal->event_mask(event); if (event_mask & PollInternal::flag_read) return; LT_LOG_EVENT("insert read", 0); m_internal->set_event_mask(event, event_mask | PollInternal::flag_read); m_internal->modify(event, EV_ADD, EVFILT_READ); } void Poll::insert_write(Event* event) { auto event_mask = m_internal->event_mask(event); if (event_mask & PollInternal::flag_write) return; LT_LOG_EVENT("insert write", 0); m_internal->set_event_mask(event, event_mask | PollInternal::flag_write); m_internal->modify(event, EV_ADD, EVFILT_WRITE); } void Poll::insert_error(Event* event) { auto event_mask = m_internal->event_mask(event); if (event_mask & PollInternal::flag_error) return; LT_LOG_EVENT("insert error", 0); m_internal->set_event_mask(event, event_mask | PollInternal::flag_error); // Kqueue always monitors errors, no need to insert. } void Poll::remove_read(Event* event) { auto event_mask = m_internal->event_mask(event); if (!(event_mask & PollInternal::flag_read)) return; LT_LOG_EVENT("remove read", 0); m_internal->set_event_mask(event, event_mask & ~PollInternal::flag_read); m_internal->modify(event, EV_DELETE, EVFILT_READ); } void Poll::remove_write(Event* event) { auto event_mask = m_internal->event_mask(event); if (!(event_mask & PollInternal::flag_write)) return; LT_LOG_EVENT("remove write", 0); m_internal->set_event_mask(event, event_mask & ~PollInternal::flag_write); m_internal->modify(event, EV_DELETE, EVFILT_WRITE); } void Poll::remove_error(Event* event) { auto event_mask = m_internal->event_mask(event); if (!(event_mask & PollInternal::flag_error)) return; LT_LOG_EVENT("remove error", 0); m_internal->set_event_mask(event, event_mask & ~PollInternal::flag_error); // Kqueue always monitors errors, no need to remove. } void Poll::remove_and_close(Event* event) { LT_LOG_EVENT("remove and close", 0); remove_read(event); remove_write(event); remove_error(event); close(event); } } #endif // USE_KQUEUE libtorrent-0.16.17/src/torrent/system/system.cc000066400000000000000000000120131522271512000215070ustar00rootroot00000000000000#include "config.h" #include "torrent/system/system.h" #include #include #include #include "torrent/exceptions.h" #include "torrent/system/callbacks.h" #include "torrent/system/thread.h" namespace torrent::system { void cancel_callback_and_wait(callback_id& id, Thread* thread1, Thread* thread2) { if (this_thread::thread() == thread1) { thread1->cancel_callback_and_wait(id, thread2); return; } if (this_thread::thread() == thread2) { thread2->cancel_callback_and_wait(id, thread1); return; } throw internal_error("system::cancel_callback_and_wait() neither thread is self."); } const char* errno_enum(int status) { // The Open Group Base Specifications Issue 6 switch (status) { case 0: return "0"; case E2BIG: return "E2BIG"; case EACCES: return "EACCES"; case EADDRINUSE: return "EADDRINUSE"; case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; case EAFNOSUPPORT: return "EAFNOSUPPORT"; case EAGAIN: return "EAGAIN"; case EALREADY: return "EALREADY"; case EBADF: return "EBADF"; case EBADMSG: return "EBADMSG"; case EBUSY: return "EBUSY"; case ECANCELED: return "ECANCELED"; case ECHILD: return "ECHILD"; case ECONNABORTED: return "ECONNABORTED"; case ECONNREFUSED: return "ECONNREFUSED"; case ECONNRESET: return "ECONNRESET"; case EDEADLK: return "EDEADLK"; case EDESTADDRREQ: return "EDESTADDRREQ"; case EDOM: return "EDOM"; case EDQUOT: return "EDQUOT"; case EEXIST: return "EEXIST"; case EFAULT: return "EFAULT"; case EFBIG: return "EFBIG"; case EHOSTUNREACH: return "EHOSTUNREACH"; case EIDRM: return "EIDRM"; case EILSEQ: return "EILSEQ"; case EINPROGRESS: return "EINPROGRESS"; case EINTR: return "EINTR"; case EINVAL: return "EINVAL"; case EIO: return "EIO"; case EISCONN: return "EISCONN"; case EISDIR: return "EISDIR"; case ELOOP: return "ELOOP"; case EMFILE: return "EMFILE"; case EMLINK: return "EMLINK"; case EMSGSIZE: return "EMSGSIZE"; #if defined(EMULTIHOP) case EMULTIHOP: return "EMULTIHOP"; #endif case ENAMETOOLONG: return "ENAMETOOLONG"; case ENETDOWN: return "ENETDOWN"; case ENETRESET: return "ENETRESET"; case ENETUNREACH: return "ENETUNREACH"; case ENFILE: return "ENFILE"; case ENOBUFS: return "ENOBUFS"; case ENODATA: return "ENODATA"; case ENODEV: return "ENODEV"; case ENOENT: return "ENOENT"; case ENOEXEC: return "ENOEXEC"; case ENOLCK: return "ENOLCK"; case ENOLINK: return "ENOLINK"; case ENOMEM: return "ENOMEM"; case ENOMSG: return "ENOMSG"; case ENOPROTOOPT: return "ENOPROTOOPT"; case ENOSPC: return "ENOSPC"; case ENOSR: return "ENOSR"; case ENOSTR: return "ENOSTR"; case ENOSYS: return "ENOSYS"; case ENOTCONN: return "ENOTCONN"; case ENOTDIR: return "ENOTDIR"; case ENOTEMPTY: return "ENOTEMPTY"; case ENOTSOCK: return "ENOTSOCK"; case ENOTTY: return "ENOTTY"; case ENXIO: return "ENXIO"; case EOPNOTSUPP: return "EOPNOTSUPP"; case EOVERFLOW: return "EOVERFLOW"; case EPERM: return "EPERM"; case EPIPE: return "EPIPE"; case EPROTO: return "EPROTO"; case EPROTONOSUPPORT: return "EPROTONOSUPPORT"; case EPROTOTYPE: return "EPROTOTYPE"; case ERANGE: return "ERANGE"; case EROFS: return "EROFS"; case ESPIPE: return "ESPIPE"; case ESRCH: return "ESRCH"; case ESTALE: return "ESTALE"; case ETIME: return "ETIME"; case ETIMEDOUT: return "ETIMEDOUT"; case ETXTBSY: return "ETXTBSY"; case EXDEV: return "EXDEV"; default: // Handle potentially duplicate error numbers here. switch (status) { case ENOTSUP: return "ENOTSUP"; case EWOULDBLOCK: return "EWOULDBLOCK"; default: static thread_local char buffer[16]; std::snprintf(buffer, sizeof(buffer), "E%d", status); return buffer; }; }; } std::string errno_enum_str(int status) { return errno_enum(status); } const char* gai_enum_error(int status) { switch (status) { case EAI_AGAIN: return "EAI_AGAIN"; case EAI_BADFLAGS: return "EAI_BADFLAGS"; case EAI_FAIL: return "EAI_FAIL"; case EAI_FAMILY: return "EAI_FAMILY"; case EAI_MEMORY: return "EAI_MEMORY"; case EAI_NONAME: return "EAI_NONAME"; case EAI_OVERFLOW: return "EAI_OVERFLOW"; case EAI_SERVICE: return "EAI_SERVICE"; case EAI_SOCKTYPE: return "EAI_SOCKTYPE"; case EAI_SYSTEM: return "EAI_SYSTEM"; default: return "unknown"; } } std::string gai_enum_error_str(int status) { return std::string(gai_enum_error(status)); } } // namespace torrent::system libtorrent-0.16.17/src/torrent/system/system.h000066400000000000000000000007701522271512000213600ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_SYSTEM_SYSTEM_H #define LIBTORRENT_TORRENT_SYSTEM_SYSTEM_H #include #include #include #include namespace torrent::system { const char* errno_enum(int status) LIBTORRENT_EXPORT; std::string errno_enum_str(int status) LIBTORRENT_EXPORT; const char* gai_enum_error(int status) LIBTORRENT_EXPORT; std::string gai_enum_error_str(int status) LIBTORRENT_EXPORT; } // namespace torrent::system #endif libtorrent-0.16.17/src/torrent/system/thread.cc000066400000000000000000000300171522271512000214360ustar00rootroot00000000000000#include "config.h" #include "torrent/system/thread.h" #include #include #include #include #include #include "torrent/exceptions.h" #include "torrent/system/poll.h" #include "torrent/net/resolver.h" #include "torrent/runtime/socket_manager.h" #include "torrent/utils/chrono.h" #include "torrent/utils/log.h" #include "torrent/utils/scheduler.h" #include "utils/instrumentation.h" #include "utils/thread_internal.h" namespace torrent::system { thread_local Thread* Thread::m_self{}; Thread::~Thread() = default; Thread* Thread::self() { return m_self; } void Thread::init_thread() {} void Thread::init_thread_pre_start() {} void Thread::init_thread_post_local() {} void Thread::cleanup_thread() {} Thread::Thread() : m_instrumentation_index(INSTRUMENTATION_POLLING_DO_POLL_OTHERS - INSTRUMENTATION_POLLING_DO_POLL), m_poll(system::Poll::create()), m_scheduler(new utils::Scheduler) { m_cached_time = utils::time_since_epoch(); m_scheduler->set_cached_time(m_cached_time); } void Thread::start_thread() { if (m_poll == nullptr) throw internal_error("No poll object for thread defined."); if (!is_initialized()) throw internal_error("Called Thread::start_thread on an uninitialized object."); init_thread_pre_start(); #ifdef USE_PTHREAD_SETSTACKSIZE pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, DEFAULT_PTHREAD_STACKSIZE); if (pthread_create(&m_thread, &attr, &Thread::enter_event_loop, this)) { pthread_attr_destroy(&attr); throw internal_error("Failed to create thread."); } pthread_attr_destroy(&attr); #else if (pthread_create(&m_thread, nullptr, &Thread::enter_event_loop, this)) throw internal_error("Failed to create thread."); #endif while (m_state != STATE_ACTIVE) usleep(100); } // Each thread needs to check flag_do_shutdown in call_events() and decide how to cleanly shut down. void Thread::stop_thread_wait() { m_flags |= flag_do_shutdown; m_poll->do_interrupt(); pthread_join(m_thread, nullptr); assert(is_inactive()); } void Thread::callback(bool is_interrupt, std::function&& fn) { bool should_interrupt{}; { auto guard = std::scoped_lock(m_callbacks_lock); if (is_interrupt) { if (m_interrupt_callbacks.empty()) { m_has_interrupt_callbacks.store(true, std::memory_order_release); m_interrupt_callbacks.reserve(16); should_interrupt = true; } m_interrupt_callbacks.push_back({nullptr, std::move(fn), 0}); } else { if (m_callbacks.empty()) { m_has_callbacks.store(true, std::memory_order_release); m_callbacks.reserve(16); should_interrupt = true; } m_callbacks.push_back({nullptr, std::move(fn), 0}); } } if (should_interrupt) m_poll->do_interrupt(); } void Thread::callback(bool is_interrupt, system::callback_id& id, std::function&& fn) { assert(id != nullptr); // Ensure adding callbacks for the id are completed before cancel-wait can proceed. auto previous_id = id->fetch_add(1, std::memory_order_relaxed); if ((previous_id & 0x7) == 0x7) throw internal_error("Thread::callback() lower id overflow."); bool should_interrupt{}; { auto guard = std::scoped_lock(m_callbacks_lock); if (is_interrupt) { if (m_interrupt_callbacks.empty()) { m_has_interrupt_callbacks.store(true, std::memory_order_release); m_interrupt_callbacks.reserve(16); should_interrupt = true; } m_interrupt_callbacks.push_back({id, std::move(fn), previous_id & ~0x7}); } else { if (m_callbacks.empty()) { m_has_callbacks.store(true, std::memory_order_release); m_callbacks.reserve(16); should_interrupt = true; } m_callbacks.push_back({id, std::move(fn), previous_id & ~0x7}); } } id->fetch_sub(1, std::memory_order_release); id->notify_all(); if (should_interrupt) m_poll->do_interrupt(); } void Thread::cancel_callback(system::callback_id& id) { assert(id != nullptr); id->fetch_add(0x10, std::memory_order_release); } void Thread::cancel_callback_and_wait(system::callback_id& id) { assert(id != nullptr); while (true) { auto current_id = id->load(std::memory_order_acquire); auto counter = (current_id & 0x7); if (counter >= 2) { id->wait(current_id, std::memory_order_acquire); continue; } if (counter == 1) { // Check if we ourselves are the only ones running the callback for the id, if so then skip // the wait. if (m_self == nullptr || m_self->m_callback_processing_id != id) { id->wait(current_id, std::memory_order_acquire); continue; } } if (id->compare_exchange_weak(current_id, current_id + 0x10, std::memory_order_acquire)) break; } } // Only two threads can share a callback id and safely use cancel_callback_and_wait(). // // This still allows multiple threads to share a callback id, but only two can safely use // cancel_callback_and_wait(). // // Review the above since the deadlock-flag should cause synchronization of multiple callers. void Thread::cancel_callback_and_wait(callback_id& id, Thread* other_thread) { assert(id != nullptr); assert(this == m_self); assert(other_thread != m_self); if (id != m_callback_processing_id) { // No need to cancel self. other_thread->cancel_callback_and_wait(id); return; } auto wait_for_deadlock = [&id]() { auto current_id = id->load(std::memory_order_acquire); while (current_id & 0x8) { id->wait(current_id, std::memory_order_acquire); current_id = id->load(std::memory_order_acquire); } }; while (true) { auto pre_deadlock_id = id->load(std::memory_order_acquire); // The other thread is also trying to wait for cancel, so just wait. if (pre_deadlock_id & 0x8) { // Cancel callbacks just in case more were added after the deadlock bit was set. cancel_callback(id); wait_for_deadlock(); return; } if (id->compare_exchange_weak(pre_deadlock_id, pre_deadlock_id | 0x8, std::memory_order_acquire)) break; } // We are the first thread canceling, so cancel and notify. id->fetch_add(0x10, std::memory_order_release); id->fetch_and(~0x8, std::memory_order_release); id->notify_all(); } // Fix interrupting when shutting down thread. void Thread::interrupt() { m_poll->do_interrupt(); } void* Thread::enter_event_loop(void* thread) { auto t = static_cast(thread); if (t == nullptr) throw internal_error("Thread::enter_event_loop called with a null pointer thread"); t->init_thread_local(); t->init_thread_post_local(); t->event_loop(); t->cleanup_thread_local(); return nullptr; } void Thread::event_loop() { lt_log_print(LOG_SYSTEM_THREAD, "%s : starting thread event loop", name()); try { m_poll->init_thread(); while (true) { process_events(); instrumentation_update(INSTRUMENTATION_POLLING_DO_POLL, 1); instrumentation_update(instrumentation_enum(INSTRUMENTATION_POLLING_DO_POLL + m_instrumentation_index), 1); auto timeout = std::max(next_timeout(), std::chrono::microseconds(0)); timeout = m_scheduler->next_timeout(timeout); int event_count = m_poll->do_poll(timeout); instrumentation_update(INSTRUMENTATION_POLLING_EVENTS, event_count); instrumentation_update(instrumentation_enum(INSTRUMENTATION_POLLING_EVENTS + m_instrumentation_index), event_count); } } catch (const shutdown_exception&) { lt_log_print(LOG_SYSTEM_THREAD, "%s: Shutting down thread.", name()); } catch (const internal_error& e) { // Uncaught internal errors in threads cause the program to exit, and we need to flush the logs. if (this_thread::thread_id() != torrent::main_thread::thread_id()) log_cleanup(); m_poll->cleanup_thread(); throw; } m_poll->cleanup_thread(); auto previous_state = STATE_ACTIVE; if (!m_state.compare_exchange_strong(previous_state, STATE_INACTIVE)) throw internal_error("Thread::event_loop called on an object that is not in the active state."); } void Thread::init_thread_local() { #if defined(HAS_PTHREAD_SETNAME_NP_DARWIN) pthread_setname_np(name()); #elif defined(HAS_PTHREAD_SETNAME_NP_GENERIC) // Cannot use thread->m_thread here as it may not be set before pthread_create returns. pthread_setname_np(pthread_self(), name()); #endif m_self = this; m_thread = pthread_self(); m_thread_id = std::this_thread::get_id(); m_scheduler->set_thread_id(m_thread_id); set_cached_time(utils::time_since_epoch()); if (m_resolver) m_resolver->init(); auto previous_state = STATE_INITIALIZED; if (!m_state.compare_exchange_strong(previous_state, STATE_ACTIVE)) throw internal_error("Thread::init_thread_local() : " + std::string(name()) + " : called on an object that is not in the initialized state."); } void Thread::cleanup_thread_local() { lt_log_print(LOG_SYSTEM_THREAD, "%s : cleaning up thread local data", name()); cleanup_thread(); // TODO: Cleanup the resolver, scheduler, and poll objects. m_self = nullptr; } void Thread::process_events() { // TODO: We should call process_callbacks() here before and after call_events, however due to the // many different cached times in the code, we need to let each thread manage this themselves. set_cached_time(utils::time_since_epoch()); call_events(); set_cached_time(utils::time_since_epoch()); m_scheduler->perform(m_cached_time); } // Used for testing. void Thread::process_events_without_cached_time() { call_events(); m_scheduler->perform(m_cached_time); } void Thread::process_callbacks(bool only_interrupt) { m_has_interrupt_callbacks.store(false, std::memory_order_release); while (true) { std::vector callbacks; { auto guard = std::scoped_lock(m_callbacks_lock); callbacks.swap(m_interrupt_callbacks); if (only_interrupt) { if (callbacks.empty()) { m_has_interrupt_callbacks.store(false, std::memory_order_release); return; } } if (callbacks.empty()) callbacks.swap(m_callbacks); if (callbacks.empty()) { m_has_callbacks.store(false, std::memory_order_release); m_has_interrupt_callbacks.store(false, std::memory_order_release); return; } } for (auto& callback : callbacks) { if (callback.id == nullptr) { callback.fn(); continue; } auto previous_id = callback.id->fetch_add(1, std::memory_order_relaxed); if ((previous_id & 0x7) == 0x7) throw internal_error("Thread::process_callbacks() lower id overflow."); if ((previous_id & ~0x7) != callback.expected_id) { callback.id->fetch_sub(1, std::memory_order_release); callback.id->notify_all(); continue; } m_callback_processing_id = callback.id; callback.fn(); m_callback_processing_id = nullptr; callback.id->fetch_sub(1, std::memory_order_release); callback.id->notify_all(); } } } void Thread::set_cached_time(std::chrono::microseconds t) { m_cached_time = t; m_scheduler->set_cached_time(t); } } // namespace torrent::utils namespace torrent::this_thread { torrent::system::Thread* thread() { return system::ThreadInternal::thread(); } std::string thread_name_str() { return thread_name(); } std::thread::id thread_id() { return system::ThreadInternal::thread_id(); } std::chrono::microseconds cached_time() { return system::ThreadInternal::cached_time(); } std::chrono::seconds cached_seconds() { return system::ThreadInternal::cached_seconds(); } system::Poll* poll() { return system::ThreadInternal::poll(); } net::Resolver* resolver() { return system::ThreadInternal::resolver(); } utils::Scheduler* scheduler() { return system::ThreadInternal::scheduler(); } const char* thread_name() { auto thread = system::ThreadInternal::thread(); return thread != nullptr ? thread->name() : "unknown"; } } // namespace torrent::this_thread libtorrent-0.16.17/src/torrent/system/thread.h000066400000000000000000000133341522271512000213030ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_UTILS_THREAD_H #define LIBTORRENT_TORRENT_UTILS_THREAD_H #include #include #include #include #include #include #include #include namespace torrent::system { class ThreadInternal; class LIBTORRENT_EXPORT Thread { public: using pthread_func = void* (*)(void*); enum state_type { STATE_UNKNOWN, STATE_INITIALIZED, STATE_ACTIVE, STATE_INACTIVE }; static constexpr int flag_do_shutdown = 0x1; static constexpr int flag_did_shutdown = 0x2; // The ctor and dtor are called outside of the thread, so thread-specific initialization and // destruction should be done in init_thread() and cleanup_thread() respectively. Thread(); virtual ~Thread(); // TODO: Should we clear m_self after event_loop ends? static Thread* self(); virtual const char* name() const = 0; bool is_initialized() const { return state() == STATE_INITIALIZED; } bool is_active() const { return state() == STATE_ACTIVE; } bool is_inactive() const { return state() == STATE_INACTIVE; } bool has_do_shutdown() const { return (flags() & flag_do_shutdown); } bool has_did_shutdown() const { return (flags() & flag_did_shutdown); } pthread_t pthread() const { return m_thread; } std::thread::id thread_id() const { return m_thread_id; } state_type state() const { return m_state; } int flags() const { return m_flags; } // TODO: This shouldn't be atomic, and should be in torrent::this_thread::cached_time(). auto cached_time() const { return m_cached_time.load(); } virtual void init_thread(); void start_thread(); void stop_thread_wait(); void callback(std::function&& fn); void callback(system::callback_id& id, std::function&& fn); void callback_interrupt(std::function&& fn); void callback_interrupt(system::callback_id& id, std::function&& fn); void cancel_callback(system::callback_id& id); void cancel_callback_and_wait(system::callback_id& id); void cancel_callback_and_wait(callback_id& id, Thread* other_thread); void interrupt(); // TODO: Move to protected. void event_loop(); protected: friend class system::Poll; friend class ThreadInternal; net::Resolver* resolver() { return m_resolver.get(); } utils::Scheduler* scheduler() { return m_scheduler.get(); } bool has_callbacks() const { return m_has_callbacks.load(); } bool has_interrupt_callbacks() const { return m_has_interrupt_callbacks.load(); } bool has_any_callbacks() const { return has_callbacks() || has_interrupt_callbacks(); } static void* enter_event_loop(void* thread); virtual void call_events() = 0; virtual std::chrono::microseconds next_timeout() = 0; virtual void init_thread_pre_start(); void init_thread_local(); virtual void init_thread_post_local(); // It is assumed that any thread-specific resources no longer are accessed at the time // cleanup_thread is called, or that those resources remain safe to call. // // This mean that e.g. tracker::Manager never gets called once thread_tracker is stopped. virtual void cleanup_thread(); void cleanup_thread_local(); void process_events(); void process_events_without_cached_time(); void process_callbacks(bool only_interrupt = false); void set_cached_time(std::chrono::microseconds t); struct callback_type { callback_id id; std::function fn; uint32_t expected_id; }; void callback(bool is_interrupt, std::function&& fn); void callback(bool is_interrupt, system::callback_id& id, std::function&& fn); static thread_local Thread* m_self; // TODO: Remove m_thread. pthread_t m_thread{}; std::atomic m_thread_id; std::atomic m_state{STATE_UNKNOWN}; std::atomic m_flags{0}; // TODO: Optimize these into an int. std::atomic m_has_callbacks{}; std::atomic m_has_interrupt_callbacks{}; // TODO: Make it so only thread_this can access m_cached_time. std::atomic m_cached_time; align_cacheline int m_instrumentation_index; std::unique_ptr m_poll; std::unique_ptr m_resolver; std::unique_ptr m_scheduler; align_cacheline std::mutex m_callbacks_lock; std::vector m_callbacks; std::vector m_interrupt_callbacks; // Only data used in self thread below: align_cacheline callback_id m_callback_processing_id{}; }; inline void Thread::callback(std::function&& fn) { callback(false, std::move(fn)); } inline void Thread::callback(system::callback_id& id, std::function&& fn) { callback(false, id, std::move(fn)); } inline void Thread::callback_interrupt(std::function&& fn) { callback(true, std::move(fn)); } inline void Thread::callback_interrupt(system::callback_id& id, std::function&& fn) { callback(true, id, std::move(fn)); } } // namespace torrent::system #endif libtorrent-0.16.17/src/torrent/throttle.cc000066400000000000000000000050141522271512000205070ustar00rootroot00000000000000#include "config.h" #include "throttle.h" #include #include "exceptions.h" #include "net/throttle_internal.h" #include "net/throttle_list.h" namespace torrent { // Plans: // // Make ThrottleList do a callback when it needs more? This would // allow us to remove us from the task scheduler when we're full. Also // this would let us be abit more flexible with the interval. Throttle* Throttle::create_throttle() { auto throttle = new ThrottleInternal(ThrottleInternal::flag_root); throttle->m_maxRate = 0; throttle->m_throttleList = new ThrottleList(); return throttle; } void Throttle::destroy_throttle(Throttle* throttle) { delete throttle->m_ptr()->m_throttleList; delete throttle->m_ptr(); } Throttle* Throttle::create_slave() { return m_ptr()->create_slave(); } bool Throttle::is_throttled() const { return m_maxRate != 0 && m_maxRate < UINT_MAX; } void Throttle::set_max_rate(uint64_t v) { if (v == m_maxRate) return; if (v > (UINT_MAX - 1)) throw input_error("Throttle rate must be between 0 and 4294967295."); uint64_t oldRate = m_maxRate; m_maxRate = v; m_throttleList->set_min_chunk_size(calculate_min_chunk_size()); m_throttleList->set_max_chunk_size(calculate_max_chunk_size()); if (!m_ptr()->is_root()) return; if (oldRate == 0) m_ptr()->enable(); else if (m_maxRate == 0) m_ptr()->disable(); } const Rate* Throttle::rate() const { return m_throttleList->rate_slow(); } uint32_t Throttle::calculate_min_chunk_size() const { // Just for each modification, make this into a function, rather // than if-else chain. if (m_maxRate <= (8 << 10)) return (1 << 9); else if (m_maxRate <= (32 << 10)) return (2 << 9); else if (m_maxRate <= (64 << 10)) return (3 << 9); else if (m_maxRate <= (128 << 10)) return (4 << 9); else if (m_maxRate <= (512 << 10)) return (8 << 9); else if (m_maxRate <= (2048 << 10)) return (16 << 9); else return (32 << 9); } uint32_t Throttle::calculate_max_chunk_size() const { // Make this return a lower value for very low throttle settings. return calculate_min_chunk_size() * 4; } uint32_t Throttle::calculate_interval() const { uint32_t rate = m_throttleList->rate_slow()->rate(); if (rate < 1024) return 10 * 100000; // At least two max chunks per tick. uint32_t interval = (5 * m_throttleList->max_chunk_size()) / rate; if (interval == 0) return 1 * 100000; else if (interval > 10) return 10 * 100000; else return interval * 100000; } } // namespace torrent libtorrent-0.16.17/src/torrent/throttle.h000066400000000000000000000023121522271512000203470ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_THROTTLE_H #define LIBTORRENT_TORRENT_THROTTLE_H #include namespace torrent { class ThrottleList; class ThrottleInternal; class LIBTORRENT_EXPORT Throttle { public: static Throttle* create_throttle(); static void destroy_throttle(Throttle* throttle); Throttle* create_slave(); bool is_throttled() const; // 0 == UNLIMITED. uint64_t max_rate() const { return m_maxRate; } void set_max_rate(uint64_t v); const Rate* rate() const; ThrottleList* throttle_list() { return m_throttleList; } protected: Throttle() = default; ~Throttle() = default; ThrottleInternal* m_ptr() { return reinterpret_cast(this); } const ThrottleInternal* c_ptr() const { return reinterpret_cast(this); } uint32_t calculate_min_chunk_size() const LIBTORRENT_NO_EXPORT; uint32_t calculate_max_chunk_size() const LIBTORRENT_NO_EXPORT; uint32_t calculate_interval() const LIBTORRENT_NO_EXPORT; uint64_t m_maxRate; ThrottleList* m_throttleList; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/torrent.cc000066400000000000000000000142331522271512000203420ustar00rootroot00000000000000#include "config.h" #include "torrent/torrent.h" #include #include #include #include "manager.h" #include "runtime.h" #include "thread_main.h" #include "data/thread_disk.h" #include "net/thread_net.h" #include "torrent/exceptions.h" #include "torrent/system/poll.h" #include "torrent/runtime/network_manager.h" #include "torrent/runtime/socket_manager.h" #include "tracker/thread_tracker.h" #include "utils/instrumentation.h" // TODO: Refactor these uses. #include "download/download_constructor.h" #include "download/download_manager.h" #include "download/download_wrapper.h" #include "torrent/download/resource_manager.h" #include "protocol/peer_factory.h" #include "torrent/download_info.h" #include "torrent/object.h" #include "torrent/object_stream.h" #include "torrent/throttle.h" #include "torrent/peer/connection_list.h" namespace torrent { namespace { std::string generate_random(size_t length) { std::random_device rd; std::mt19937 mt(rd()); using bytes_randomizer = std::independent_bits_engine; bytes_randomizer bytes(mt); std::string s; s.reserve(length); std::generate_n(std::back_inserter(s), length, std::ref(bytes)); return s; } } void initialize_main_thread() { ThreadMain::create_thread(); Runtime::initialize(); ThreadMain::thread_main()->init_thread(); } void initialize() { if (manager != nullptr) throw internal_error("torrent::initialize(...) called but the library has already been initialized"); instrumentation_initialize(); curl_global_init(CURL_GLOBAL_ALL); manager = new Manager; ThreadDisk::create_thread(); ThreadNet::create_thread(); ThreadTracker::create_thread(); runtime::socket_manager()->set_max_size_and_adjust(this_thread::poll()->open_max()); ThreadMain::thread_main()->init_after_setup(); disk_thread::thread()->init_thread(); net_thread::thread()->init_thread(); tracker_thread::thread()->init_thread(); disk_thread::thread()->start_thread(); net_thread::thread()->start_thread(); tracker_thread::thread()->start_thread(); } // Clean up and close stuff. Stopping all torrents and waiting for // them to finish is not required, but recommended. void cleanup() { if (manager == nullptr) throw internal_error("torrent::cleanup() called but the library is not initialized."); // Might need to wait for the threads to finish? runtime::network_manager()->cleanup(); tracker_thread::thread()->stop_thread_wait(); disk_thread::thread()->stop_thread_wait(); net_thread::thread()->stop_thread_wait(); ThreadTracker::destroy_thread(); ThreadDisk::destroy_thread(); ThreadNet::destroy_thread(); Runtime::cleanup(); manager->cleanup(); Runtime::destroy(); delete manager; manager = nullptr; curl_global_cleanup(); } ChunkManager* chunk_manager() { return manager->chunk_manager(); } ClientList* client_list() { return manager->client_list(); } FileManager* file_manager() { return manager->file_manager(); } ResourceManager* resource_manager() { return manager->resource_manager(); } Throttle* down_throttle_global() { return manager->download_throttle(); } Throttle* up_throttle_global() { return manager->upload_throttle(); } const Rate* down_rate() { return manager->download_throttle()->rate(); } const Rate* up_rate() { return manager->upload_throttle()->rate(); } Download download_add(Object* object, uint32_t tracker_key) { auto download = std::make_unique(); DownloadConstructor ctor; ctor.set_download(download.get()); ctor.initialize(*object); std::string infoHash; if (download->info()->is_meta_download()) infoHash = object->get_key("info").get_key("pieces").as_string(); else infoHash = object_sha1(&object->get_key("info")); if (manager->download_manager()->find(infoHash) != manager->download_manager()->end()) throw input_error("Info hash already used by another torrent."); if (!download->info()->is_meta_download()) { char buffer[1024]; uint64_t metadata_size = 0; object_write_bencode_c(&object_write_to_size, &metadata_size, object_buffer_t(buffer, buffer + sizeof(buffer)), &object->get_key("info")); download->main()->set_metadata_size(metadata_size); } std::string local_id = PEER_NAME + generate_random(20 - std::string(PEER_NAME).size()); download->set_hash_queue(ThreadMain::thread_main()->hash_queue()); download->initialize(infoHash, local_id, tracker_key); // Add trackers, etc, after setting the info hash so that log // entries look sane. ctor.parse_tracker(*object); // Default PeerConnection factory functions. download->main()->connection_list()->slot_new_connection(&createPeerConnectionDefault); // Consider move as much as possible into this function // call. Anything that won't cause possible torrent creation errors // go in there. manager->initialize_download(download.get()); download->set_bencode(object); return Download(download.release()); } void download_remove(Download d) { manager->cleanup_download(d.ptr()); } // Add all downloads to dlist. Make sure it's cleared. void download_list(DList& dlist) { dlist.insert(dlist.end(), manager->download_manager()->begin(), manager->download_manager()->end()); } // Make sure you check that it's valid. Download download_find(const std::string& infohash) { return *manager->download_manager()->find(infohash); } uint32_t download_priority(Download d) { auto itr = manager->resource_manager()->find(d.ptr()->main()); if (itr == manager->resource_manager()->end()) throw internal_error("torrent::download_priority(...) could not find the download in the resource manager."); return itr->priority(); } // TODO: Remove this. void download_set_priority(Download d, uint32_t pri) { auto itr = manager->resource_manager()->find(d.ptr()->main()); if (itr == manager->resource_manager()->end()) throw internal_error("torrent::download_set_priority(...) could not find the download in the resource manager."); if (pri > 1024) throw internal_error("torrent::download_set_priority(...) received an invalid priority."); ResourceManager::set_priority(itr, pri); } } // namespace torrent libtorrent-0.16.17/src/torrent/torrent.h000066400000000000000000000036231522271512000202050ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_H #define LIBTORRENT_TORRENT_H #include #include #include #include namespace torrent { // Make sure you seed srandom and srand48 if available. void initialize_main_thread()LIBTORRENT_EXPORT; void initialize() LIBTORRENT_EXPORT; // Clean up and close stuff. Stopping all torrents and waiting for // them to finish is not required, but recommended. void cleanup() LIBTORRENT_EXPORT; ChunkManager* chunk_manager() LIBTORRENT_EXPORT; ClientList* client_list() LIBTORRENT_EXPORT; FileManager* file_manager() LIBTORRENT_EXPORT; ResourceManager* resource_manager() LIBTORRENT_EXPORT; // TODO: Move to main_thread? Throttle* down_throttle_global() LIBTORRENT_EXPORT; Throttle* up_throttle_global() LIBTORRENT_EXPORT; const Rate* down_rate() LIBTORRENT_EXPORT; const Rate* up_rate() LIBTORRENT_EXPORT; using DList = std::list; // Will always return a valid Download. On errors it // throws. // // The Object must be on the heap allocated with 'new'. If // 'download_add' throws the client must handle the deletion, else it // is done by 'download_remove'. // // Might consider redesigning that... Download download_add(Object* s, uint32_t tracker_key) LIBTORRENT_EXPORT; void download_remove(Download d) LIBTORRENT_EXPORT; // Add all downloads to dlist. The client is responsible for clearing // it before the call. void download_list(DList& dlist) LIBTORRENT_EXPORT; // Make sure you check the returned Download's is_valid(). Download download_find(const std::string& infohash) LIBTORRENT_EXPORT; uint32_t download_priority(Download d) LIBTORRENT_EXPORT; void download_set_priority(Download d, uint32_t pri) LIBTORRENT_EXPORT; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/tracker/000077500000000000000000000000001522271512000177665ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/tracker/dht_controller.cc000066400000000000000000000104641522271512000233240ustar00rootroot00000000000000#include "config.h" #include "dht_controller.h" #include "dht/dht_router.h" #include "src/manager.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/network_manager.h" #include "torrent/system/callbacks.h" #include "torrent/utils/log.h" #define LT_LOG(log_fmt, ...) \ lt_log_print_subsystem(torrent::LOG_DHT_CONTROLLER, "dht_controller", log_fmt, __VA_ARGS__); namespace torrent::tracker { DhtController::DhtController() = default; DhtController::~DhtController() { stop(); } bool DhtController::is_valid() { auto lock = std::lock_guard(m_lock); return m_router != nullptr; } bool DhtController::is_active() { auto lock = std::lock_guard(m_lock); return m_router && m_router->is_active(); } bool DhtController::is_receiving_requests() { auto lock = std::lock_guard(m_lock); return m_receive_requests; } uint16_t DhtController::port() { auto lock = std::lock_guard(m_lock); return m_port; } void DhtController::initialize(const Object& dht_cache) { auto lock = std::lock_guard(m_lock); if (m_router != nullptr) throw internal_error("DhtController::initialize() called with DHT already active."); LT_LOG("initializing", 0); try { m_router = std::make_unique(dht_cache); } catch (const torrent::local_error& e) { LT_LOG("initialization failed : %s", e.what()); } } bool DhtController::start() { auto lock = std::lock_guard(m_lock); if (m_router == nullptr) throw internal_error("DhtController::start() called without initializing first."); auto port = runtime::network_config()->override_dht_port(); if (port == 0) port = runtime::network_manager()->listen_port_or_throw(); LT_LOG("starting : port:%d", port); try { m_router->start(port); m_port = port; } catch (const torrent::local_error& e) { LT_LOG("start failed : %s", e.what()); return false; } return true; } void DhtController::stop() { auto lock = std::lock_guard(m_lock); if (!m_router) return; LT_LOG("stopping", 0); m_router->stop(); m_port = 0; } void DhtController::set_receive_requests(bool state) { auto lock = std::lock_guard(m_lock); m_receive_requests = state; } void DhtController::add_bootstrap_node(std::string host, int port) { auto lock = std::lock_guard(m_lock); if (m_router) m_router->add_bootstrap_contact(std::move(host), port); } void DhtController::add_node(const sockaddr* sa, int port) { auto lock = std::lock_guard(m_lock); if (m_router) m_router->contact(sa, port); } Object* DhtController::store_cache(Object* container) { auto lock = std::lock_guard(m_lock); if (!m_router) throw internal_error("DhtController::store_cache() called but DHT not initialized."); return m_router->store_cache(container); } DhtController::statistics_type DhtController::get_statistics() { auto lock = std::lock_guard(m_lock); if (!m_router) throw internal_error("DhtController::get_statistics() called but DHT not initialized."); return m_router->get_statistics(); } void DhtController::reset_statistics() { auto lock = std::lock_guard(m_lock); if (!m_router) throw internal_error("DhtController::reset_statistics() called but DHT not initialized."); m_router->reset_statistics(); } // We don't care about the tracker or download being deleted as that's a rare edge-case that's // unnessesary to optimize for. // // Instead we depend on the callbacks from DHT to check if weak_ptr is expired. void DhtController::announce(const HashString& info_hash, std::weak_ptr weak_tracker) { main_thread::callback([this, info_hash, weak_tracker] { auto lock = std::lock_guard(m_lock); if (!m_router) throw internal_error("DhtController::announce() called but DHT not initialized."); m_router->announce(info_hash, weak_tracker); }); } void DhtController::cancel_announce(const HashString& info_hash, std::weak_ptr weak_tracker) { main_thread::callback([this, info_hash, weak_tracker] { auto lock = std::lock_guard(m_lock); if (!m_router) throw internal_error("DhtController::cancel_announce() called but DHT not initialized."); m_router->cancel_announce(info_hash, weak_tracker); }); } } // namespace torrent::tracker libtorrent-0.16.17/src/torrent/tracker/dht_controller.h000066400000000000000000000045401522271512000231640ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_DHT_CONTROLLER_H #define LIBTORRENT_TRACKER_DHT_CONTROLLER_H #include #include #include namespace torrent { class TrackerDht; } // namespace torrent namespace torrent::tracker { class LIBTORRENT_EXPORT DhtController { public: struct statistics_type { // Cycle; 0=inactive, 1=initial bootstrapping, 2 and up=normal operation unsigned int cycle{}; // DHT query statistics. unsigned int queries_received{}; unsigned int queries_sent{}; unsigned int replies_received{}; unsigned int errors_received{}; unsigned int errors_caught{}; // DHT node info. unsigned int num_nodes{}; unsigned int num_buckets{}; // DHT tracker info. unsigned int num_peers{}; unsigned int max_peers{}; unsigned int num_trackers{}; }; DhtController(); ~DhtController(); // Thread-safe: bool is_valid(); bool is_active(); bool is_receiving_requests(); void set_receive_requests(bool state); // TODO: This is the active port, move this to NetworkConfig? (or make this unavailable) uint16_t port(); // Main thread: void initialize(const Object& dht_cache); bool start(); void stop(); // Store DHT cache in the given container and return the container. Object* store_cache(Object* container); // Add a node by host (from a torrent file), or by address from explicit add_node // command or the BT PORT message. void add_bootstrap_node(std::string host, int port); void add_node(const sockaddr* sa, int port); statistics_type get_statistics(); void reset_statistics(); protected: friend class torrent::TrackerDht; // Called from tracker_thread. void announce(const HashString& info_hash, std::weak_ptr weak_tracker); void cancel_announce(const HashString& info_hash, std::weak_ptr weak_tracker); private: std::mutex m_lock; uint16_t m_port{0}; bool m_receive_requests{true}; std::unique_ptr m_router; }; } // namespace torrent::tracker #endif // LIBTORRENT_TRACKER_DHT_CONTROLLER_H libtorrent-0.16.17/src/torrent/tracker/manager.cc000066400000000000000000000155171522271512000217200ustar00rootroot00000000000000#include "config.h" #include #include #include "torrent/download_info.h" #include "torrent/exceptions.h" #include "torrent/tracker/manager.h" #include "torrent/tracker/tracker.h" #include "torrent/utils/log.h" #include "torrent/system/callbacks.h" #include "torrent/system/thread.h" #include "torrent/utils/string_manip.h" #include "tracker/tracker_controller.h" #include "tracker/tracker_list.h" #include "tracker/tracker_worker.h" #define LT_LOG_TRACKER_EVENTS(log_fmt, ...) \ lt_log_print_subsystem(LOG_TRACKER_EVENTS, "tracker_manager", log_fmt, __VA_ARGS__); // TODO: Add leak check for trackers that are requesting when deleted, yet never got moved to delete // queue. namespace torrent::tracker { Manager::Manager() = default; // This doesn't ensure newly deleted torrents finish their stopped announce in case we shut down // immediately after, however this is an edge-case that's not worth adding complexity to handle. Manager::~Manager() { assert(std::this_thread::get_id() == tracker_thread::thread_id()); { auto guard = std::scoped_lock(m_lock); m_trackers_to_delete.insert(m_trackers_to_delete.end(), m_trackers_to_wait.begin(), m_trackers_to_wait.end()); m_trackers_to_wait.clear(); } process_delete_trackers(); } TrackerControllerWrapper Manager::add_controller(DownloadInfo* download_info, std::shared_ptr controller) { assert(std::this_thread::get_id() == main_thread::thread_id()); if (download_info->hash() == HashString::new_zero()) throw internal_error("tracker::Manager::add(...) invalid info_hash."); auto guard = std::scoped_lock(m_lock); auto wrapper = TrackerControllerWrapper(download_info->hash(), std::move(controller)); auto result = m_controllers.insert(wrapper); if (!result.second) throw internal_error("tracker::Manager::add_controller(...) controller already exists."); LT_LOG_TRACKER_EVENTS("added controller: info_hash:%s", utils::transform_to_hex_str(download_info->hash()).c_str()); return wrapper; } void Manager::remove_controller(TrackerControllerWrapper controller) { assert(std::this_thread::get_id() == main_thread::thread_id()); auto guard = std::scoped_lock(m_lock); // We assume there are other references to the controller, so gracefully close it. if (m_controllers.erase(controller) != 1) throw internal_error("tracker::Manager::remove_controller(...) controller not found or has multiple references."); // TrackerList is already cleared by DownloadWrapper. // for (auto& tracker : *controller.get()->tracker_list()) // remove_events(tracker.get_worker()); LT_LOG_TRACKER_EVENTS("removed controller: info_hash:%s", utils::transform_to_hex_str(controller.info_hash()).c_str()); } void Manager::send_event(tracker::Tracker& tracker, TrackerParams params, tracker::TrackerState::event_enum new_event) { assert(std::this_thread::get_id() == main_thread::thread_id()); auto weak_ptr = tracker.get_weak_ptr(); tracker_thread::thread()->callback(tracker.get_worker()->callback_id(), [weak_ptr, params, new_event]() { auto tracker = weak_ptr.lock(); if (tracker == nullptr) return; tracker->mark_starting_request(); tracker->send_event(params, new_event); }); } void Manager::send_scrape(tracker::Tracker& tracker, TrackerParams params) { assert(std::this_thread::get_id() == main_thread::thread_id()); auto weak_ptr = tracker.get_weak_ptr(); tracker_thread::thread()->callback(tracker.get_worker()->callback_id(), [weak_ptr, params]() { auto tracker = weak_ptr.lock(); if (tracker == nullptr) return; tracker->send_scrape(params); }); } void Manager::add_event(std::weak_ptr weak_ptr, std::weak_ptr tl_keeper, std::function&& event) { auto tracker = tracker::Tracker::from_weak_ptr(weak_ptr); if (!tracker.is_valid()) return; main_thread::thread()->callback(tracker.get_worker()->callback_id(), [weak_ptr, tl_keeper, event = std::move(event)]() mutable { auto tracker = tracker::Tracker::from_weak_ptr(weak_ptr); if (!tracker.is_valid()) return; auto tl_keeper_shared = tl_keeper.lock(); if (!tl_keeper_shared) return; event(tracker); }); } void Manager::add_event_or_update(std::weak_ptr weak_ptr, std::weak_ptr tl_keeper, std::function&& event) { auto tracker = tracker::Tracker::from_weak_ptr(weak_ptr); if (!tracker.is_valid()) return tracker_thread::manager()->update_tracker(std::move(tracker)); main_thread::thread()->callback(tracker.get_worker()->callback_id(), [weak_ptr, tl_keeper, event = std::move(event)]() mutable { auto tracker = tracker::Tracker::from_weak_ptr(weak_ptr); if (!tracker.is_valid()) return; auto tl_keeper_shared = tl_keeper.lock(); if (!tl_keeper_shared) return tracker_thread::manager()->update_tracker(std::move(tracker)); event(tracker); }); } void Manager::update_tracker(const Tracker& tracker) { auto guard = std::scoped_lock(m_lock); // There might have been an old callback queued before tracker list got deleted, so wait for the // currently processing request to finish. // // TrackerWorker::remove_events() would have removed the event, so don't check. if (tracker.is_requesting_not_dht_scrape_disownable()) return; auto itr = std::find(m_trackers_to_wait.begin(), m_trackers_to_wait.end(), tracker); if (itr == m_trackers_to_wait.end()) return; if (m_trackers_to_delete.empty()) tracker_thread::thread()->callback([this] { process_delete_trackers(); }); m_trackers_to_wait.erase(itr); m_trackers_to_delete.push_back(tracker); } void Manager::delete_tracker(Tracker tracker) { auto guard = std::scoped_lock(m_lock); if (tracker.is_requesting_not_dht_scrape_disownable()) { m_trackers_to_wait.push_back(tracker); return; } if (m_trackers_to_delete.empty()) tracker_thread::thread()->callback([this] { process_delete_trackers(); }); m_trackers_to_delete.push_back(tracker); } void Manager::delete_trackers(std::vector&& trackers) { auto guard = std::scoped_lock(m_lock); for (auto& tracker : trackers) { if (tracker.is_requesting_not_dht_scrape()) { m_trackers_to_wait.push_back(std::move(tracker)); continue; } if (m_trackers_to_delete.empty()) tracker_thread::thread()->callback([this] { process_delete_trackers(); }); m_trackers_to_delete.push_back(std::move(tracker)); } } void Manager::process_delete_trackers() { assert(std::this_thread::get_id() == tracker_thread::thread_id()); std::vector trackers; { auto guard = std::scoped_lock(m_lock); trackers = std::move(m_trackers_to_delete); } for (auto& tracker : trackers) tracker.get_worker()->cleanup(); } } // namespace torrent::tracker libtorrent-0.16.17/src/torrent/tracker/manager.h000066400000000000000000000040201522271512000215450ustar00rootroot00000000000000// Thread-safe manager for all trackers loaded by the client. #ifndef LIBTORRENT_TRACKER_MANAGER_H #define LIBTORRENT_TRACKER_MANAGER_H #include #include #include #include #include namespace torrent { class TrackerWorker; } namespace torrent::tracker { class LIBTORRENT_EXPORT Manager { public: Manager(); ~Manager(); protected: friend class torrent::DownloadMain; friend class torrent::DownloadWrapper; friend class torrent::TrackerList; friend class torrent::TrackerWorker; friend class torrent::ThreadTracker; // TODO: Add flag to indicate we're shutting down, and delete all disownable trackers. // Main thread: TrackerControllerWrapper add_controller(DownloadInfo* download_info, std::shared_ptr controller); void remove_controller(TrackerControllerWrapper controller); void send_event(tracker::Tracker& tracker, TrackerParams params, tracker::TrackerState::event_enum new_event); void send_scrape(tracker::Tracker& tracker, TrackerParams params); // Any thread: void add_event(std::weak_ptr weak_ptr, std::weak_ptr tl_keeper, std::function&& event); void add_event_or_update(std::weak_ptr weak_ptr, std::weak_ptr tl_keeper, std::function&& event); void update_tracker(const Tracker& tracker); void delete_tracker(Tracker tracker); void delete_trackers(std::vector&& trackers); private: Manager(const Manager&) = delete; Manager& operator=(const Manager&) = delete; void process_delete_trackers(); std::mutex m_lock; std::set m_controllers; std::vector m_trackers_to_wait; std::vector m_trackers_to_delete; }; } // namespace torrent::tracker #endif // LIBTORRENT_TRACKER_MANAGER_H libtorrent-0.16.17/src/torrent/tracker/tracker.cc000066400000000000000000000114521522271512000217330ustar00rootroot00000000000000#include "config.h" #include "torrent/tracker/tracker.h" #include "tracker/tracker_dht.h" #include "tracker/tracker_worker.h" #include "torrent/runtime/network_manager.h" namespace torrent::tracker { // TODO: Handle !is_valid() errors. Tracker::Tracker(std::shared_ptr&& worker) : m_worker(std::move(worker)) { } bool Tracker::is_enabled() const { auto lock_guard = m_worker->lock_guard(); return m_worker->m_state.is_enabled(); } bool Tracker::is_requesting() const { auto lock_guard = m_worker->lock_guard(); return m_worker->m_state.is_requesting() || m_worker->m_state.is_starting_request(); } bool Tracker::is_requesting_not_scrape() const { auto lock_guard = m_worker->lock_guard(); if (m_worker->m_state.latest_event() == tracker::TrackerState::EVENT_SCRAPE) return false; return m_worker->m_state.is_requesting() || m_worker->m_state.is_starting_request(); } bool Tracker::is_requesting_not_dht() const { auto lock_guard = m_worker->lock_guard(); if (m_worker->type() == tracker_enum::TRACKER_DHT) return false; return m_worker->m_state.is_requesting() || m_worker->m_state.is_starting_request(); } bool Tracker::is_requesting_not_dht_scrape() const { auto lock_guard = m_worker->lock_guard(); if (m_worker->type() == tracker_enum::TRACKER_DHT) return false; if (m_worker->m_state.latest_event() == tracker::TrackerState::EVENT_SCRAPE) return false; return m_worker->m_state.is_requesting() || m_worker->m_state.is_starting_request(); } bool Tracker::is_requesting_not_dht_scrape_disownable() const { auto lock_guard = m_worker->lock_guard(); if (m_worker->type() == tracker_enum::TRACKER_DHT) return false; if (m_worker->m_state.latest_event() == tracker::TrackerState::EVENT_SCRAPE) return false; if (m_worker->m_state.is_disownable()) return false; return m_worker->m_state.is_requesting() || m_worker->m_state.is_starting_request(); } bool Tracker::is_extra_tracker() const { auto lock_guard = m_worker->lock_guard(); return m_worker->m_state.is_extra_tracker(); } bool Tracker::is_in_use() const { auto lock_guard = m_worker->lock_guard(); return m_worker->m_state.is_in_use(); } bool Tracker::is_usable() const { auto lock_guard = m_worker->lock_guard(); if (m_worker->type() == tracker_enum::TRACKER_DHT && !runtime::network_manager()->is_dht_active()) return false; return m_worker->m_state.is_enabled(); } bool Tracker::is_scrapable() const { auto lock_guard = m_worker->lock_guard(); return m_worker->m_state.is_scrapable(); } bool Tracker::is_disownable() const { auto lock_guard = m_worker->lock_guard(); return m_worker->m_state.is_disownable(); } bool Tracker::can_request_state() const { auto lock_guard = m_worker->lock_guard(); if (m_worker->type() == tracker_enum::TRACKER_DHT && !runtime::network_manager()->is_dht_active()) return false; if (!m_worker->m_state.is_enabled()) return false; if (m_worker->m_state.is_requesting()) return m_worker->m_state.latest_event() == tracker::TrackerState::EVENT_SCRAPE; return true; } tracker_enum Tracker::type() const { return m_worker->type(); } const std::string& Tracker::url() const { return m_worker->info().url; } uint32_t Tracker::group() const { return m_worker->info().group; } std::string Tracker::tracker_id() const { return m_worker->tracker_id_safe(); } void Tracker::enable() { { auto lock_guard = m_worker->lock_guard(); if (m_worker->m_state.is_enabled()) return; m_worker->m_state.m_flags |= tracker::TrackerState::flag_enabled; } if (m_worker->m_slot_enabled) m_worker->m_slot_enabled(); } void Tracker::disable() { { auto lock_guard = m_worker->lock_guard(); if (!m_worker->m_state.is_enabled()) return; m_worker->m_state.m_flags &= ~tracker::TrackerState::flag_enabled; } // TODO: This should be called through manager? It works atm as the trackers are all running on main // thread. // m_worker->close(); if (m_worker->m_slot_disabled) m_worker->m_slot_disabled(); } tracker::TrackerState Tracker::state() const { auto lock_guard = m_worker->lock_guard(); return m_worker->state(); } std::string Tracker::status() const { auto lock_guard = m_worker->lock_guard(); if (m_worker->type() != tracker_enum::TRACKER_DHT) return ""; auto tracker = dynamic_cast(m_worker.get()); return "[" + std::string(TrackerDht::states[tracker->dht_state()]) + ": " + std::to_string(tracker->replied()) + "/" + std::to_string(tracker->contacted()) + " nodes replied]"; } void Tracker::lock_and_call_state(const std::function& fn) const { auto lock_guard = m_worker->lock_guard(); fn(m_worker->state()); } void Tracker::clear_stats() { m_worker->lock_and_clear_stats(); } } // namespace torrent::tracker libtorrent-0.16.17/src/torrent/tracker/tracker.h000066400000000000000000000045331522271512000215770ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_TRACKER_H #define LIBTORRENT_TRACKER_TRACKER_H #include #include #include #include #include class TrackerTest; namespace torrent { class ThreadTracker; } // namespace torrent namespace torrent::tracker { class LIBTORRENT_EXPORT Tracker { public: bool is_valid() const { return m_worker != nullptr; } bool is_enabled() const; bool is_requesting() const; bool is_requesting_not_scrape() const; bool is_requesting_not_dht() const; bool is_requesting_not_dht_scrape() const; bool is_requesting_not_dht_scrape_disownable() const; bool is_extra_tracker() const; bool is_in_use() const; bool is_usable() const; bool is_scrapable() const; bool is_disownable() const; bool can_request_state() const; tracker_enum type() const; const std::string& url() const; uint32_t group() const; std::string tracker_id() const; void enable(); void disable(); TrackerState state() const; std::string status() const; void lock_and_call_state(const std::function& fn) const; bool operator< (const Tracker& rhs) const { return m_worker < rhs.m_worker; } bool operator==(const Tracker& rhs) const { return m_worker == rhs.m_worker; } protected: friend class Manager; friend class TrackerControllerWrapper; friend class torrent::ThreadTracker; friend class torrent::TrackerList; friend class ::TrackerTest; Tracker(std::shared_ptr&& worker); static Tracker from_weak_ptr(const std::weak_ptr& worker) { return Tracker(worker.lock()); } TrackerWorker* get_worker() { return m_worker.get(); } auto get_weak_ptr() const { return std::weak_ptr(m_worker); } void close(); void clear_stats(); private: std::shared_ptr m_worker; }; } // namespace torrent::tracker #endif libtorrent-0.16.17/src/torrent/tracker/tracker_state.cc000066400000000000000000000025541522271512000231360ustar00rootroot00000000000000#include "config.h" #include "torrent/tracker/tracker_state.h" namespace torrent::tracker { std::chrono::seconds TrackerState::success_time_next() const { if (m_counters.success_counter == 0) return {}; return m_counters.success_time_last + std::max(m_normal_interval, min_normal_interval); } std::chrono::seconds TrackerState::failed_time_next() const { if (m_counters.failed_counter == 0) return std::min(m_min_interval, min_min_interval); if (m_min_interval > min_min_interval) return m_counters.failed_time_last + m_min_interval; auto shift = std::min(m_counters.failed_counter - 1, uint32_t(6)); return m_counters.failed_time_last + std::min((5 << shift) * 1s, min_min_interval); } std::chrono::seconds TrackerState::activity_time_last() const { if (m_counters.failed_counter != 0) return m_counters.failed_time_last; return m_counters.success_time_last; } std::chrono::seconds TrackerState::activity_time_next() const { if (m_counters.failed_counter != 0) return failed_time_next(); return success_time_next(); } std::chrono::seconds TrackerState::activity_time_next_minimum() const { if (m_counters.failed_counter != 0) return failed_time_next(); if (m_counters.success_counter == 0) return {}; return m_counters.success_time_last + std::max(m_min_interval, min_min_interval); } } // namespace torrent::tracker libtorrent-0.16.17/src/torrent/tracker/tracker_state.h000066400000000000000000000141521522271512000227750ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_TRACKER_STATE_H #define LIBTORRENT_TRACKER_TRACKER_STATE_H #include #include class TrackerTest; namespace torrent { class TrackerDht; class TrackerHttp; class TrackerList; class TrackerWorker; namespace tracker { class TrackerState; class TrackerUdp; struct TrackerParams { int32_t numwant{-1}; uint64_t uploaded_adjusted{}; uint64_t completed_adjusted{}; uint64_t download_left{}; }; class TrackerCounters { private: friend class TrackerState; std::chrono::seconds success_time_last{}; uint32_t success_counter{}; std::chrono::seconds failed_time_last{}; uint32_t failed_counter{}; std::chrono::seconds scrape_time_last{}; uint32_t scrape_counter{}; }; class LIBTORRENT_EXPORT TrackerState { public: enum event_enum { EVENT_NONE, EVENT_COMPLETED, EVENT_STARTED, EVENT_STOPPED, EVENT_SCRAPE }; static constexpr int flag_enabled = 0x1; static constexpr int flag_deleted = 0x2; static constexpr int flag_requesting = 0x4; static constexpr int flag_starting_request = 0x8; static constexpr int flag_extra_tracker = 0x10; static constexpr int flag_scrapable = 0x20; static constexpr int flag_disownable = 0x40; static constexpr std::chrono::seconds default_min_interval = 600s; static constexpr std::chrono::seconds min_min_interval = 300s; static constexpr std::chrono::seconds max_min_interval = 4 * 3600s; static constexpr std::chrono::seconds default_normal_interval = 1800s; static constexpr std::chrono::seconds min_normal_interval = 600s; static constexpr std::chrono::seconds max_normal_interval = 8 * 3600s; int flags() const { return m_flags; } bool is_enabled() const { return (m_flags & flag_enabled); } bool is_deleted() const { return (m_flags & flag_deleted); } bool is_requesting() const { return (m_flags & flag_requesting); } bool is_starting_request() const { return (m_flags & flag_starting_request); } bool is_extra_tracker() const { return (m_flags & flag_extra_tracker); } bool is_in_use() const { return is_enabled() && m_counters.success_counter != 0; } bool is_scrapable() const { return (m_flags & flag_scrapable); } bool is_disownable() const { return (m_flags & flag_disownable); } std::chrono::seconds normal_interval() const { return m_normal_interval; } std::chrono::seconds min_interval() const { return m_min_interval; } event_enum latest_event() const { return m_latest_event; } uint32_t latest_new_peers() const { return m_latest_new_peers; } uint32_t latest_sum_peers() const { return m_latest_sum_peers; } std::chrono::seconds success_time_next() const; std::chrono::seconds success_time_last() const { return m_counters.success_time_last; } uint32_t success_counter() const { return m_counters.success_counter; } std::chrono::seconds failed_time_next() const; std::chrono::seconds failed_time_last() const { return m_counters.failed_time_last; } uint32_t failed_counter() const { return m_counters.failed_counter; } std::chrono::seconds activity_time_last() const; std::chrono::seconds activity_time_next() const; std::chrono::seconds activity_time_next_minimum() const; std::chrono::seconds scrape_time_last() const { return m_counters.scrape_time_last; } uint32_t scrape_counter() const { return m_counters.scrape_counter; } uint32_t scrape_complete() const { return m_scrape_complete; } uint32_t scrape_incomplete() const { return m_scrape_incomplete; } uint32_t scrape_downloaded() const { return m_scrape_downloaded; } protected: friend class TrackerUdp; friend class torrent::TrackerDht; friend class torrent::TrackerHttp; friend class torrent::TrackerList; friend class torrent::TrackerWorker; friend class torrent::tracker::Tracker; friend class ::TrackerTest; void clear_stats(); void set_normal_interval(std::chrono::seconds v); void set_min_interval(std::chrono::seconds v); void add_success_request(std::chrono::seconds time); void add_failed_request(std::chrono::seconds time); void add_scrape_request(std::chrono::seconds time); int m_flags{}; std::chrono::seconds m_normal_interval{min_normal_interval}; std::chrono::seconds m_min_interval{min_min_interval}; event_enum m_latest_event{EVENT_NONE}; uint32_t m_latest_new_peers{}; uint32_t m_latest_new_peers_delta{}; uint32_t m_latest_sum_peers{}; TrackerCounters m_counters; uint32_t m_scrape_complete{}; uint32_t m_scrape_incomplete{}; uint32_t m_scrape_downloaded{}; }; inline void TrackerState::clear_stats() { m_latest_new_peers = 0; m_latest_sum_peers = 0; m_counters.success_counter = 0; m_counters.failed_counter = 0; m_counters.scrape_counter = 0; } inline void TrackerState::set_normal_interval(std::chrono::seconds v) { m_normal_interval = std::min(std::max(min_normal_interval, v), max_normal_interval); } inline void TrackerState::set_min_interval(std::chrono::seconds v) { m_min_interval = std::min(std::max(min_min_interval, v), max_min_interval); } inline void TrackerState::add_success_request(std::chrono::seconds time) { m_counters.success_time_last = time; m_counters.success_counter++; m_counters.failed_counter = 0; } inline void TrackerState::add_failed_request(std::chrono::seconds time) { m_counters.failed_time_last = time; m_counters.failed_counter++; } inline void TrackerState::add_scrape_request(std::chrono::seconds time) { m_counters.scrape_time_last = time; m_counters.scrape_counter++; } } // namespace tracker } // namespace torrent #endif // LIBTORRENT_TRACKER_TRACKER_STATE_H libtorrent-0.16.17/src/torrent/tracker/wrappers.cc000066400000000000000000000106031522271512000221400ustar00rootroot00000000000000#include "config.h" #include "torrent/tracker/wrappers.h" #include #include "tracker/tracker_list.h" #include "tracker/tracker_controller.h" namespace torrent::tracker { // Must be called from main thread. bool TrackerControllerWrapper::is_active() const { return m_ptr->is_active(); } bool TrackerControllerWrapper::is_requesting() const { return m_ptr->is_requesting(); } bool TrackerControllerWrapper::is_failure_mode() const { return m_ptr->is_failure_mode(); } bool TrackerControllerWrapper::is_promiscuous_mode() const { return m_ptr->is_promiscuous_mode(); } bool TrackerControllerWrapper::has_active_trackers() const { return m_ptr->tracker_list()->has_active(); } bool TrackerControllerWrapper::has_active_trackers_not_dht() const { return m_ptr->tracker_list()->has_active_not_dht(); } bool TrackerControllerWrapper::has_active_trackers_not_dht_scrape_disownable() const { return m_ptr->tracker_list()->has_active_not_dht_scrape_disownable(); } bool TrackerControllerWrapper::has_active_trackers_not_scrape() const { return m_ptr->tracker_list()->has_active_not_scrape(); } bool TrackerControllerWrapper::has_usable_trackers() const { return m_ptr->tracker_list()->has_usable(); } uint32_t TrackerControllerWrapper::key() const { return m_ptr->tracker_list()->key(); } int32_t TrackerControllerWrapper::numwant() const { return m_ptr->tracker_list()->numwant(); } void TrackerControllerWrapper::set_numwant(int32_t n) { m_ptr->tracker_list()->set_numwant(n); } uint32_t TrackerControllerWrapper::size() const { return m_ptr->tracker_list()->size(); } Tracker TrackerControllerWrapper::at(uint32_t index) const { // TODO: This function should make it a hard exception if the index is out of range, and have a // different function that can return invalid trackers. return m_ptr->tracker_list()->at(index); } void TrackerControllerWrapper::add_extra_tracker(uint32_t group, const std::string& url) { m_ptr->tracker_list()->insert_url(group, url, true); } uint32_t TrackerControllerWrapper::seconds_to_next_timeout() const { return m_ptr->seconds_to_next_timeout(); } uint32_t TrackerControllerWrapper::seconds_to_next_scrape() const { return m_ptr->seconds_to_next_scrape(); } void TrackerControllerWrapper::manual_request(bool request_now) { m_ptr->manual_request(request_now); } void TrackerControllerWrapper::scrape_request(uint32_t seconds_to_request) { m_ptr->scrape_request(seconds_to_request); } void TrackerControllerWrapper::cycle_group(uint32_t group) { m_ptr->tracker_list()->cycle_group(group); } // Algorithms: Tracker TrackerControllerWrapper::find_if(const std::function& f) { for (auto& tracker : *m_ptr->tracker_list()) { if (f(tracker)) return tracker; } return Tracker(std::shared_ptr()); } void TrackerControllerWrapper::for_each(const std::function& f) { for (auto& tracker : *m_ptr->tracker_list()) f(tracker); } Tracker TrackerControllerWrapper::c_find_if(const std::function& f) const { for (auto& tracker : *m_ptr->tracker_list()) { if (f(tracker)) return tracker; } return Tracker(std::shared_ptr()); } void TrackerControllerWrapper::c_for_each(const std::function& f) const { for (auto& tracker : *m_ptr->tracker_list()) f(tracker); } // Private: void TrackerControllerWrapper::enable() { m_ptr->enable(); } void TrackerControllerWrapper::enable_dont_reset_stats() { m_ptr->enable(TrackerController::enable_dont_reset_stats); } void TrackerControllerWrapper::disable() { m_ptr->disable(); } void TrackerControllerWrapper::close() { m_ptr->close(); } void TrackerControllerWrapper::send_start_event() { m_ptr->send_start_event(); } void TrackerControllerWrapper::send_stop_event() { m_ptr->send_stop_event(); } void TrackerControllerWrapper::send_completed_event() { m_ptr->send_completed_event(); } void TrackerControllerWrapper::send_update_event() { m_ptr->send_update_event(); } void TrackerControllerWrapper::start_requesting() { m_ptr->start_requesting(); } void TrackerControllerWrapper::stop_requesting() { m_ptr->stop_requesting(); } void TrackerControllerWrapper::set_slots(slot_address_list success, slot_string failure) { m_ptr->slot_success() = std::move(success); m_ptr->slot_failure() = std::move(failure); } } // namespace torrent::tracker libtorrent-0.16.17/src/torrent/tracker/wrappers.h000066400000000000000000000072431522271512000220100ustar00rootroot00000000000000// Temporary classes for thread-safe tracker related classes. // // These will be renamed after movimg the old torrent/tracker_* classes to src/tracker. #ifndef LIBTORRENT_TRACKER_WRAPPER_H #define LIBTORRENT_TRACKER_WRAPPER_H #include #include #include #include // TODO: Rename to TrackerController when torrent::TrackerController is moved. namespace torrent::tracker { class LIBTORRENT_EXPORT TrackerControllerWrapper { public: using ptr_type = std::shared_ptr; using slot_string = std::function; using slot_address_list = std::function; TrackerControllerWrapper() = default; TrackerControllerWrapper(const HashString& info_hash, std::shared_ptr&& controller); bool is_valid() const { return m_ptr != nullptr; } bool is_active() const; bool is_requesting() const; bool is_failure_mode() const; bool is_promiscuous_mode() const; bool has_active_trackers() const; bool has_active_trackers_not_dht() const; bool has_active_trackers_not_dht_scrape_disownable() const; bool has_active_trackers_not_scrape() const; bool has_usable_trackers() const; const HashString& info_hash() const { return m_info_hash; } uint32_t key() const; int32_t numwant() const; void set_numwant(int32_t n); uint32_t size() const; Tracker at(uint32_t index) const; void add_extra_tracker(uint32_t group, const std::string& url); uint32_t seconds_to_next_timeout() const; uint32_t seconds_to_next_scrape() const; void manual_request(bool request_now); void scrape_request(uint32_t seconds_to_request); void cycle_group(uint32_t group); // Algorithms: Tracker find_if(const std::function& f); void for_each(const std::function& f); Tracker c_find_if(const std::function& f) const; void c_for_each(const std::function& f) const; bool operator<(const TrackerControllerWrapper& rhs) const; protected: friend class torrent::Download; friend class torrent::DownloadMain; friend class torrent::DownloadWrapper; friend class Manager; TrackerController* get() { return m_ptr.get(); } const TrackerController* get() const { return m_ptr.get(); } ptr_type& get_shared() { return m_ptr; } void enable(); void enable_dont_reset_stats(); void disable(); void close(); void send_start_event(); void send_stop_event(); void send_completed_event(); void send_update_event(); void start_requesting(); void stop_requesting(); void set_slots(slot_address_list success, slot_string failure); private: ptr_type m_ptr; HashString m_info_hash; }; inline TrackerControllerWrapper::TrackerControllerWrapper(const HashString& info_hash, std::shared_ptr&& controller) : m_ptr(std::move(controller)), m_info_hash(info_hash) { } inline bool TrackerControllerWrapper::operator<(const TrackerControllerWrapper& rhs) const { return this->get() < rhs.get(); } } // namespace torrent::tracker #endif // LIBTORRENT_TRACKER_TRACKER_WRAPPER_H libtorrent-0.16.17/src/torrent/types/000077500000000000000000000000001522271512000174775ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/types/string_utf8.cc000066400000000000000000000034421522271512000222650ustar00rootroot00000000000000#include "config.h" #include "string_utf8.h" #include "torrent/object.h" #include "torrent/utils/string_manip.h" namespace torrent { void string_utf8::reset(const std::string& str) { m_hex.clear(); m_base64.clear(); m_str = str; m_is_utf8 = utils::is_valid_utf8(str); } Object string_utf8::object_hex() const { auto obj = Object(hex()); obj.set_flags(Object::flag_hex); return obj; } Object string_utf8::object_base64() const { auto obj = Object(base64()); obj.set_flags(Object::flag_base64); return obj; } Object string_utf8::object_as_binary() const { auto obj = Object(m_str); obj.set_flags(Object::flag_as_binary); return obj; } Object string_utf8::object_hex_as_binary() const { auto obj = Object(hex()); obj.set_flags(Object::flag_hex | Object::flag_as_binary); return obj; } Object string_utf8::object_base64_as_binary() const { auto obj = Object(base64()); obj.set_flags(Object::flag_base64 | Object::flag_as_binary); return obj; } Object string_utf8::object_utf8_or_hex() const { if (!m_is_utf8) { auto obj = Object(hex()); obj.set_flags(Object::flag_hex | Object::flag_as_binary); return obj; } return Object(m_str); } Object string_utf8::object_utf8_or_base64() const { if (!m_is_utf8) { auto obj = Object(base64()); obj.set_flags(Object::flag_base64 | Object::flag_as_binary); return obj; } return Object(m_str); } Object string_utf8::object_utf8_or_as_binary() const { if (!m_is_utf8) { auto obj = Object(m_str); obj.set_flags(Object::flag_as_binary); return obj; } return Object(m_str); } void string_utf8::reset_hex() const { m_hex = utils::transform_to_hex_str(m_str); } void string_utf8::reset_base64() const { m_base64 = utils::transform_to_base64(m_str); } } // namespace torrent libtorrent-0.16.17/src/torrent/types/string_utf8.h000066400000000000000000000040201522271512000221200ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_TYPES_STRING_UTF8_H #define LIBTORRENT_TORRENT_TYPES_STRING_UTF8_H #include #include namespace torrent { class LIBTORRENT_EXPORT string_utf8 { public: string_utf8() = default; ~string_utf8() = default; static string_utf8 from_string(const std::string& str); bool empty() const; bool is_utf8() const; const std::string& str() const; const char* c_str() const; // Generates a cached encoded strings, not thread-safe. const std::string& hex() const; const std::string& base64() const; Object object_hex() const; Object object_base64() const; Object object_as_binary() const; Object object_hex_as_binary() const; Object object_base64_as_binary() const; // Mark non-utf8 strings as binary Object object_utf8_or_hex() const; Object object_utf8_or_base64() const; Object object_utf8_or_as_binary() const; void reset(const std::string& str); private: void reset_hex() const; void reset_base64() const; bool m_is_utf8{true}; std::string m_str; mutable std::string m_hex; mutable std::string m_base64; }; inline bool string_utf8::empty() const { return m_str.empty(); } inline bool string_utf8::is_utf8() const { return m_is_utf8; } inline const std::string& string_utf8::str() const { return m_str; } inline const char* string_utf8::c_str() const { return m_str.c_str(); } inline string_utf8 string_utf8::from_string(const std::string& str) { string_utf8 result; result.reset(str); return result; } inline const std::string& string_utf8::hex() const { if (!m_str.empty() && m_hex.empty()) reset_hex(); return m_hex; } inline const std::string& string_utf8::base64() const { if (!m_str.empty() && m_base64.empty()) reset_base64(); return m_base64; } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/000077500000000000000000000000001522271512000174735ustar00rootroot00000000000000libtorrent-0.16.17/src/torrent/utils/chrono.h000066400000000000000000000037571522271512000211500ustar00rootroot00000000000000#ifndef TORRENT_UTILS_CHRONO_H #define TORRENT_UTILS_CHRONO_H #include #include using namespace std::chrono_literals; namespace torrent::utils { std::chrono::microseconds ceil_seconds(std::chrono::microseconds t); std::chrono::seconds cast_seconds(std::chrono::microseconds t); std::chrono::microseconds time_since_epoch(); uint32_t next_timeout_seconds(uint32_t next_timeout, std::chrono::seconds current); uint64_t next_timeout_seconds(std::chrono::seconds next_timeout, std::chrono::seconds current); // Implementation: inline std::chrono::seconds cast_seconds(std::chrono::microseconds t) { return std::chrono::duration_cast(t); } inline std::chrono::microseconds ceil_seconds(std::chrono::microseconds t) { // C++17 // return std::chrono::duration_cast(t.ceil()); auto seconds = std::chrono::duration_cast(t + 1s - 1us); return std::chrono::duration_cast(seconds); } inline std::chrono::seconds ceil_cast_seconds(std::chrono::microseconds t) { return std::chrono::duration_cast(t + 1s - 1us); } inline std::chrono::microseconds time_since_epoch() { return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); } inline uint32_t next_timeout_seconds(uint32_t next_timeout, std::chrono::seconds current) { auto next_timeout_chrono = std::chrono::seconds(next_timeout); if (next_timeout_chrono <= current) return 0; return (next_timeout_chrono - current).count(); } inline uint64_t next_timeout_seconds(std::chrono::seconds next_timeout, std::chrono::seconds current) { if (next_timeout <= current) return 0; return (next_timeout - current).count(); } template void next_timeout_seconds(T& next_timeout, std::chrono::seconds current) = delete; } // namespace torrent::utils #endif // TORRENT_UTILS_CHRONO_H libtorrent-0.16.17/src/torrent/utils/directory_events.cc000066400000000000000000000103611522271512000233730ustar00rootroot00000000000000#include "config.h" #include "directory_events.h" #include #include #include #include #include #ifdef USE_INOTIFY #include #endif #include "torrent/exceptions.h" #include "torrent/net/fd.h" #include "torrent/system/poll.h" namespace torrent { namespace { [[maybe_unused]] std::string normalize_directory_event_path(const std::string& path) { char* resolved = ::realpath(path.c_str(), nullptr); if (resolved == nullptr) throw input_error("Could not resolve watch directory '" + path + "': " + std::string(std::strerror(errno))); std::string normalized(resolved); std::free(resolved); while (normalized.size() > 1 && normalized.back() == '/') normalized.pop_back(); return normalized; } [[maybe_unused]] bool check_flags_conflict(int lhs, int rhs) { return ((lhs & directory_events::flag_on_ready) && (rhs & directory_events::flag_on_added)) || ((rhs & directory_events::flag_on_ready) && (lhs & directory_events::flag_on_added)); } } // namespace directory_events::directory_events() { m_fileDesc = -1; } bool directory_events::open() { if (is_open()) return true; errno = 0; #ifdef USE_INOTIFY m_fileDesc = fd_open_inotify(); if (is_open() && !fd_set_nonblock(m_fileDesc)) { fd_close(m_fileDesc); m_fileDesc = -1; } #else errno = ENODEV; #endif if (!is_open()) return false; this_thread::poll()->open(this); this_thread::poll()->insert_read(this); return true; } void directory_events::close() { if (!is_open()) return; this_thread::poll()->remove_read(this); this_thread::poll()->close(this); ::close(m_fileDesc); m_fileDesc = -1; m_wd_list.clear(); } void directory_events::notify_on(const std::string& path, [[maybe_unused]] int flags, [[maybe_unused]] const watch_descriptor::slot_string& slot) { if (path.empty()) throw input_error("Cannot add notification event for empty paths."); #ifdef USE_INOTIFY std::string normalized_path = normalize_directory_event_path(path); for (const auto& wd : m_wd_list) { if (check_flags_conflict(wd.flags, flags) && wd.canonical_path == normalized_path) throw input_error("Conflicting directory watch modes for watch directories: " + wd.canonical_path + " : " + normalized_path); } int in_flags = IN_MASK_ADD; #ifdef IN_EXCL_UNLINK in_flags |= IN_EXCL_UNLINK; #endif #ifdef IN_ONLYDIR in_flags |= IN_ONLYDIR; #endif if ((flags & flag_on_added)) in_flags |= (IN_CREATE | IN_MOVED_TO); if ((flags & flag_on_updated)) in_flags |= IN_CLOSE_WRITE; if ((flags & flag_on_ready)) in_flags |= (IN_MOVED_TO | IN_CLOSE_WRITE); if ((flags & flag_on_removed)) in_flags |= (IN_DELETE | IN_MOVED_FROM); int result = inotify_add_watch(m_fileDesc, normalized_path.c_str(), in_flags); if (result == -1) throw input_error("Call to inotify_add_watch(...) failed: " + std::string(std::strerror(errno))); auto& wd = m_wd_list.emplace_back(); wd.descriptor = result; wd.flags = flags; wd.canonical_path = normalized_path; wd.path = path + (path.back() != '/' ? "/" : ""); wd.slot = slot; #else throw input_error("No support for inotify."); #endif } void directory_events::event_read() { #ifdef USE_INOTIFY char buffer[2048]; ssize_t result = ::read(m_fileDesc, buffer, 2048); if (result < static_cast(sizeof(struct inotify_event))) return; auto event = reinterpret_cast(buffer); while (event + 1 <= reinterpret_cast(buffer + result)) { auto next_event = reinterpret_cast(event) + sizeof(struct inotify_event) + event->len; if (event->len == 0 || next_event > buffer + result) return; auto itr = std::find_if(m_wd_list.begin(), m_wd_list.end(), [event](const auto& w) { return event->wd == w.descriptor; }); if (itr != m_wd_list.end()) { std::string sname(event->name); if (sname.size() >= 8 && sname.compare(sname.size() - 8, 8, ".torrent") == 0) itr->slot(itr->path + event->name); } event = reinterpret_cast(next_event); } #endif } void directory_events::event_write() { } void directory_events::event_error() { } } // namespace torrent libtorrent-0.16.17/src/torrent/utils/directory_events.h000066400000000000000000000021771522271512000232430ustar00rootroot00000000000000#ifndef LIBTORRENT_DIRECTORY_EVENTS_H #define LIBTORRENT_DIRECTORY_EVENTS_H #include #include #include #include namespace torrent { struct watch_descriptor { using slot_string = std::function; int descriptor; int flags; std::string canonical_path; std::string path; slot_string slot; }; class LIBTORRENT_EXPORT directory_events : public Event { public: static constexpr int flag_on_added = 0x1; static constexpr int flag_on_removed = 0x2; static constexpr int flag_on_updated = 0x3; static constexpr int flag_on_ready = 0x4; directory_events(); const char* type_name() const override { return "directory_events"; } bool open(); void close(); void notify_on(const std::string& path, int flags, const watch_descriptor::slot_string& slot); void event_read() override; void event_write() override; void event_error() override; private: std::vector m_wd_list; }; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/extents.h000066400000000000000000000134261522271512000213440ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_EXTENTS_H #define LIBTORRENT_UTILS_EXTENTS_H #include #include namespace torrent { template > class extents { public: using key_type = Address; // start address using mapped_value_type = Value; // The value mapped to the ip range using mapped_type = std::pair; // End address, value mapped to ip range using range_map_type = std::map; // The map itself void insert(key_type address_start, key_type address_end, mapped_value_type value); bool defined(key_type address_start, key_type address_end); bool defined(key_type address); key_type get_matching_key(key_type address_start, key_type address_end); // throws error on not defined. test with defined() mapped_value_type at(key_type address_start, key_type address_end); // throws error on not defined. test with defined() mapped_value_type at(key_type address); // throws error on not defined. test with defined() unsigned int sizeof_data() const; range_map_type range_map; }; ////////////////////////////////////////////////////////////////////////////////// // INSERT O(log N) assuming no overlapping ranges ///////////////////////////////////////////////////////////////////////////////// template void extents::insert(key_type address_start, key_type address_end, mapped_value_type value) { //we allow overlap ranges though not 100% overlap but only if mapped values are the same. first remove any overlap range that has a different value. typename range_map_type::iterator iter = range_map.upper_bound(address_start); if( iter != range_map.begin() ) { iter--; } bool ignore_due_to_total_overlap = false; while( iter->first <= address_end && iter != range_map.end() ) { key_type delete_key = iter->first; bool do_delete_due_to_overlap = iter->first <= address_end && (iter->second).first >= address_start && (iter->second).second != value; bool do_delete_due_to_total_overlap = address_start <= iter->first && address_end >= (iter->second).first; iter++; if(do_delete_due_to_overlap || do_delete_due_to_total_overlap) { range_map.erase (delete_key); } else if (iter != range_map.end()) { ignore_due_to_total_overlap = ignore_due_to_total_overlap || ( iter->first <= address_start && (iter->second).first >= address_end ); } } if(!ignore_due_to_total_overlap) { mapped_type entry; entry.first = address_end; entry.second = value; range_map.insert( std::pair(address_start, entry) ); } } ////////////////////////////////////////////////////////////////////// // DEFINED O(log N) assuming no overlapping ranges ////////////////////////////////////////////////////////////////////// template bool extents::defined(key_type address_start, key_type address_end) { bool defined = false; auto iter = range_map.upper_bound(address_start); if( iter != range_map.begin() ) { iter--; } while( iter->first <= address_end && !defined && iter != range_map.end() ) { defined = iter->first <= address_end && (iter->second).first >= address_start; iter++; } return defined; } template bool extents::defined(key_type address) { return defined(address, address); } ////////////////////////////////////////////////////////////////////// // GET_MATCHING_KEY O(log N) assuming no overlapping ranges ////////////////////////////////////////////////////////////////////// template typename extents::key_type extents::get_matching_key(key_type address_start, key_type address_end) { key_type key{}; bool defined = false; auto iter = range_map.upper_bound(address_start); if( iter != range_map.begin() ) { iter--; } while( iter->first <= address_end && !defined && iter != range_map.end() ) { defined = iter->first <= address_end && (iter->second).first >= address_start; if(defined) key = iter->first; iter++; } // this will cause exception to be thrown if(!defined) { std::out_of_range e("nothing defined for specified key"); throw e; } return key; } ////////////////////////////////////////////////////////////////////// // AT O(log N) assuming no overlapping ranges ////////////////////////////////////////////////////////////////////// template typename extents::mapped_value_type extents::at(key_type address_start, key_type address_end) { key_type key = get_matching_key(address_start, address_end); mapped_type entry = range_map.at(key); return entry.second; } template typename extents::mapped_value_type extents::at(key_type address) { return at(address, address); } ////////////////////////////////////////////////////////////////////// // SIZEOF_DATA O(1) ////////////////////////////////////////////////////////////////////// template unsigned int extents::sizeof_data() const { // we don't know overhead on map, so this won't be accurate. just estimate. unsigned int entry_size = sizeof(key_type) + sizeof(mapped_type); return entry_size * range_map.size(); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/file_stat.h000066400000000000000000000035171522271512000216240ustar00rootroot00000000000000#ifndef TORRENT_UTILS_FILE_STAT_H #define TORRENT_UTILS_FILE_STAT_H #include #include #include namespace torrent::utils { class FileStat { public: bool update(int fd) { return fstat(fd, &m_stat) == 0; } bool update(const char* filename) { return stat(filename, &m_stat) == 0; } bool update(const std::string& filename) { return update(filename.c_str()); } bool update_link(const char* filename) { return lstat(filename, &m_stat) == 0; } bool update_link(const std::string& filename) { return update_link(filename.c_str()); } bool is_regular() const { return S_ISREG(m_stat.st_mode); } bool is_directory() const { return S_ISDIR(m_stat.st_mode); } bool is_character() const { return S_ISCHR(m_stat.st_mode); } bool is_block() const { return S_ISBLK(m_stat.st_mode); } bool is_fifo() const { return S_ISFIFO(m_stat.st_mode); } bool is_link() const { return S_ISLNK(m_stat.st_mode); } bool is_socket() const { return S_ISSOCK(m_stat.st_mode); } off_t size() const { return m_stat.st_size; } time_t access_time() const { return m_stat.st_atime; } time_t change_time() const { return m_stat.st_ctime; } time_t modified_time() const { return m_stat.st_mtime; } private: struct stat m_stat; }; } // namespace torrent::utils #endif // TORRENT_UTILS_FILE_STAT_H libtorrent-0.16.17/src/torrent/utils/log.cc000066400000000000000000000302671522271512000205730ustar00rootroot00000000000000#include "config.h" #include "torrent/utils/log.h" #include #include #include #include #include #include #include #include #include #include "torrent/exceptions.h" #include "torrent/hash_string.h" #include "torrent/utils/string_manip.h" namespace torrent { struct log_cache_entry { using outputs_type = log_group::outputs_type; bool equal_outputs(const outputs_type& out) const { return out == outputs; } void allocate(unsigned int count) { cache_first = new log_slot[count]; cache_last = cache_first + count; } void clear() { delete [] cache_first; cache_first = NULL; cache_last = NULL; } outputs_type outputs; log_slot* cache_first; log_slot* cache_last; }; struct log_stream_output { log_stream_output(std::ostream* stream) : stream(stream) {} std::mutex mutex; std::unique_ptr stream; }; struct log_gz_output { log_gz_output(const char* filename, bool append) : gz_file(gzopen(filename, append ? "a" : "w"), gzclose) {} bool is_valid() const { return gz_file != Z_NULL; } // bool set_buffer(unsigned size) { return gzbuffer(gz_file.get(), size) == 0; } std::unique_ptr gz_file; }; using log_cache_list = std::vector; using log_child_list = std::vector>; log_group_list log_groups; log_output_list log_outputs; static log_cache_list log_cache; static log_child_list log_children; static std::mutex log_mutex; static const char log_level_char[] = { 'C', 'E', 'W', 'N', 'I', 'D' }; // Removing logs always triggers a check if we got any un-used // log_output objects. static void log_update_child_cache(int index) { auto first = std::find_if(log_children.begin(), log_children.end(), [index](const auto& pair) { return pair >= std::make_pair(index, 0); }); if (first == log_children.end()) return; const log_group::outputs_type& outputs = log_groups[index].cached_outputs(); while (first != log_children.end() && first->first == index) { if ((outputs & log_groups[first->second].cached_outputs()) != outputs) { log_groups[first->second].set_cached_outputs(outputs | log_groups[first->second].cached_outputs()); log_update_child_cache(first->second); } first++; } // If we got any circular connections re-do the update to ensure all // children have our new outputs. if (outputs != log_groups[index].cached_outputs()) log_update_child_cache(index); } static void log_rebuild_cache() { std::for_each(log_groups.begin(), log_groups.end(), std::mem_fn(&log_group::clear_cached_outputs)); for (int i = 0; i < LOG_GROUP_MAX_SIZE; i++) log_update_child_cache(i); // Clear the cache... std::for_each(log_cache.begin(), log_cache.end(), std::mem_fn(&log_cache_entry::clear)); log_cache.clear(); for (auto& idx : log_groups) { const log_group::outputs_type& use_outputs = idx.cached_outputs(); if (use_outputs == 0) { idx.set_cached(NULL, NULL); continue; } auto cache_itr = std::find_if(log_cache.begin(), log_cache.end(), [&use_outputs](const auto& entry) { return entry.equal_outputs(use_outputs); }); if (cache_itr == log_cache.end()) { cache_itr = log_cache.insert(log_cache.end(), log_cache_entry()); cache_itr->outputs = use_outputs; cache_itr->allocate(use_outputs.count()); log_slot* dest_itr = cache_itr->cache_first; for (size_t index = 0; index < log_outputs.size(); index++) { if (use_outputs[index]) *dest_itr++ = log_outputs[index].second; } } idx.set_cached(cache_itr->cache_first, cache_itr->cache_last); } } void log_group::internal_print(const HashString* hash, const char* subsystem, const void* dump_data, size_t dump_size, const char* fmt, ...) { va_list ap; const unsigned int buffer_size = 4096; char buffer[buffer_size]; char* first = buffer; if (subsystem != NULL) { if (hash != NULL) { first = utils::transform_to_hex(*hash, first, first + 40); first += snprintf(first, 4096 - (first - buffer), "->%s: ", subsystem); } else { first += snprintf(first, 4096 - (first - buffer), "%s: ", subsystem); } } va_start(ap, fmt); int count = vsnprintf(first, 4096 - (first - buffer), fmt, ap); first += std::min(count, buffer_size - 1); va_end(ap); if (count <= 0) return; auto lock = std::scoped_lock(log_mutex); std::for_each(m_first, m_last, [this, &buffer, first](const auto& elem) { return elem(buffer, std::distance(buffer, first), std::distance(log_groups.begin(), this)); }); if (dump_data != NULL) { std::for_each(m_first, m_last, [dump_data, dump_size](const auto& log) { return log(static_cast(dump_data), dump_size, -1); }); } } #define LOG_CASCADE(parent) LOG_CHILDREN_CASCADE(parent, parent) #define LOG_LINK(parent, child) log_children.emplace_back(parent, child) #define LOG_CHILDREN_CASCADE(parent, subgroup) \ log_children.emplace_back(parent + LOG_ERROR, subgroup + LOG_CRITICAL); \ log_children.emplace_back(parent + LOG_WARN, subgroup + LOG_ERROR); \ log_children.emplace_back(parent + LOG_NOTICE, subgroup + LOG_WARN); \ log_children.emplace_back(parent + LOG_INFO, subgroup + LOG_NOTICE); \ log_children.emplace_back(parent + LOG_DEBUG, subgroup + LOG_INFO); #define LOG_CHILDREN_SUBGROUP(parent, subgroup) \ log_children.emplace_back(parent + LOG_CRITICAL, subgroup + LOG_CRITICAL); \ log_children.emplace_back(parent + LOG_ERROR, subgroup + LOG_ERROR); \ log_children.emplace_back(parent + LOG_WARN, subgroup + LOG_WARN); \ log_children.emplace_back(parent + LOG_NOTICE, subgroup + LOG_NOTICE); \ log_children.emplace_back(parent + LOG_INFO, subgroup + LOG_INFO); \ log_children.emplace_back(parent + LOG_DEBUG, subgroup + LOG_DEBUG); void log_initialize() { auto lock = std::scoped_lock(log_mutex); LOG_CASCADE(LOG_CRITICAL); LOG_CASCADE(LOG_STORAGE_CRITICAL); LOG_CASCADE(LOG_TORRENT_CRITICAL); LOG_CHILDREN_CASCADE(LOG_CRITICAL, LOG_STORAGE_CRITICAL); LOG_CHILDREN_CASCADE(LOG_CRITICAL, LOG_TORRENT_CRITICAL); LOG_LINK(LOG_CONNECTION, LOG_CONNECTION_BIND); LOG_LINK(LOG_CONNECTION, LOG_CONNECTION_FD); LOG_LINK(LOG_CONNECTION, LOG_CONNECTION_FILTER); LOG_LINK(LOG_CONNECTION, LOG_CONNECTION_HANDSHAKE); LOG_LINK(LOG_CONNECTION, LOG_CONNECTION_LISTEN); LOG_LINK(LOG_DHT, LOG_DHT_ERROR); LOG_LINK(LOG_DHT, LOG_DHT_CONTROLLER); LOG_LINK(LOG_DHT_ALL, LOG_DHT_ERROR); LOG_LINK(LOG_DHT_ALL, LOG_DHT_CONTROLLER); LOG_LINK(LOG_DHT_ALL, LOG_DHT_NODE); LOG_LINK(LOG_DHT_ALL, LOG_DHT_ROUTER); LOG_LINK(LOG_DHT_ALL, LOG_DHT_SERVER); std::sort(log_children.begin(), log_children.end()); log_rebuild_cache(); } void log_cleanup() { auto lock = std::scoped_lock(log_mutex); std::fill(log_groups.begin(), log_groups.end(), log_group()); log_outputs.clear(); log_children.clear(); std::for_each(log_cache.begin(), log_cache.end(), std::mem_fn(&log_cache_entry::clear)); log_cache.clear(); } static log_output_list::iterator log_find_output_name(const char* name) { auto itr = log_outputs.begin(); auto last = log_outputs.end(); while (itr != last && itr->first != name) itr++; return itr; } void log_open_output(const char* name, const log_slot& slot) { auto lock = std::scoped_lock(log_mutex); if (log_outputs.size() >= log_group::max_size_outputs()) { throw input_error("Cannot open more than 64 log output handlers."); } auto itr = log_find_output_name(name); if (itr == log_outputs.end()) { log_outputs.emplace_back(name, slot); } else { // by replacing the "write" slot binding, the old file gets closed // (handles are shared pointers) itr->second = slot; } log_rebuild_cache(); } void log_close_output(const char* name) { auto lock = std::scoped_lock(log_mutex); auto itr = log_find_output_name(name); if (itr != log_outputs.end()) log_outputs.erase(itr); } void log_add_group_output(int group, const char* name) { auto lock = std::scoped_lock(log_mutex); auto itr = log_find_output_name(name); size_t index = std::distance(log_outputs.begin(), itr); if (itr == log_outputs.end()) throw input_error("Log name not found: '" + std::string(name) + "'"); if (index >= log_group::max_size_outputs()) throw input_error("Cannot add more log group outputs."); log_groups[group].set_output_at(index, true); log_rebuild_cache(); } void log_remove_group_output([[maybe_unused]] int group, [[maybe_unused]] const char* name) { } // The log_children list is since we build the output // cache by crawling from child to parent. void log_add_child(int group, int child) { auto lock = std::scoped_lock(log_mutex); if (std::find(log_children.begin(), log_children.end(), std::make_pair(group, child)) != log_children.end()) return; log_children.emplace_back(group, child); std::sort(log_children.begin(), log_children.end()); log_rebuild_cache(); } void log_remove_child([[maybe_unused]] int group, [[maybe_unused]] int child) { // Remove from all groups, then modify all outputs. } // TODO: Add lock for file writes. static void log_file_write(const std::unique_ptr& outfile, const char* data, size_t length, int group) { // Add group name, data, etc as flags. // Normal groups are nul-terminated strings. if (group >= LOG_NON_CASCADING) { *outfile << this_thread::cached_seconds().count() << ' ' << data << '\n'; } else if (group >= 0) { *outfile << this_thread::cached_seconds().count() << ' ' << log_level_char[group % 6] << ' ' << data << '\n'; } else if (group == -1) { *outfile << "---DUMP---" << length << "---" << '\n'; if (length != 0) { outfile->rdbuf()->sputn(data, length); *outfile << '\n'; } *outfile << "---END---" << '\n'; } } static void log_gz_file_write(const std::shared_ptr& outfile, const char* data, size_t length, int group) { char buffer[64]; // Normal groups are nul-terminated strings. if (group >= 0) { int buffer_length = snprintf(buffer, 64, (group >= LOG_NON_CASCADING) ? ("%" PRIi64 " ") : ("%" PRIi64 " %c "), static_cast(this_thread::cached_seconds().count()), log_level_char[group % 6]); if (buffer_length > 0) gzwrite(outfile->gz_file.get(), buffer, buffer_length); gzwrite(outfile->gz_file.get(), data, length); gzwrite(outfile->gz_file.get(), "\n", 1); } else if (group == -1) { int buffer_length = snprintf(buffer, 64, "---DUMP---%zu---\n", length); gzwrite(outfile->gz_file.get(), buffer, buffer_length); if (length != 0) gzwrite(outfile->gz_file.get(), data, length); gzwrite(outfile->gz_file.get(), "---END---\n", sizeof("---END---\n") - 1); } } void log_open_file_output(const char* name, const char* filename, bool append, bool flush) { std::ios_base::openmode mode = std::ofstream::out; if (append) mode |= std::ofstream::app; auto outfile = std::make_shared(new std::ofstream(filename, mode)); if (!outfile->stream->good()) throw input_error("Could not open log file '" + std::string(filename) + "'."); if (flush) log_open_output(name, [outfile](auto d, auto l, auto g) { auto lock = std::lock_guard(outfile->mutex); log_file_write(outfile->stream, d, l, g); outfile->stream->flush(); }); else log_open_output(name, [outfile](auto d, auto l, auto g) { auto lock = std::lock_guard(outfile->mutex); log_file_write(outfile->stream, d, l, g); }); } void log_open_gz_file_output(const char* name, const char* filename, bool append) { auto outfile = std::make_shared(filename, append); if (!outfile->is_valid()) throw input_error("Could not open log gzip file '" + std::string(filename) + "'."); // if (!outfile->set_buffer(1 << 14)) // throw input_error("Could not set gzip log file buffer size."); log_open_output(name, [outfile](auto d, auto l, auto g) { log_gz_file_write(outfile, d, l, g); }); } } // namespace torrent libtorrent-0.16.17/src/torrent/utils/log.h000066400000000000000000000145731522271512000204370ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_LOG_H #define LIBTORRENT_UTILS_LOG_H #include #include #include #include #include #include namespace torrent { enum { LOG_CRITICAL, LOG_ERROR, LOG_WARN, LOG_NOTICE, LOG_INFO, LOG_DEBUG, LOG_STORAGE_CRITICAL, LOG_STORAGE_ERROR, LOG_STORAGE_WARN, LOG_STORAGE_NOTICE, LOG_STORAGE_INFO, LOG_STORAGE_DEBUG, LOG_TORRENT_CRITICAL, LOG_TORRENT_ERROR, LOG_TORRENT_WARN, LOG_TORRENT_NOTICE, LOG_TORRENT_INFO, LOG_TORRENT_DEBUG, LOG_NON_CASCADING, // TODO: Deprecate, not used. And add connection_poll and move to system. LOG_CONNECTION, LOG_CONNECTION_BIND, LOG_CONNECTION_FD, LOG_CONNECTION_FILTER, LOG_CONNECTION_HANDSHAKE, LOG_CONNECTION_LISTEN, LOG_DHT, LOG_DHT_ALL, LOG_DHT_ERROR, LOG_DHT_CONTROLLER, LOG_DHT_NODE, LOG_DHT_ROUTER, LOG_DHT_SERVER, LOG_INSTRUMENTATION_MEMORY, LOG_INSTRUMENTATION_MINCORE, LOG_INSTRUMENTATION_CHOKE, LOG_INSTRUMENTATION_POLLING, LOG_INSTRUMENTATION_TRANSFERS, LOG_MOCK_CALLS, LOG_NET_DNS, LOG_NET_HTTP, LOG_NET_SOCKET, LOG_PEER_CHOKE_QUEUE, LOG_PEER_LIST_EVENTS, LOG_PEER_LIST_ADDRESS, LOG_PROTOCOL_PIECE_EVENTS, LOG_PROTOCOL_METADATA_EVENTS, LOG_PROTOCOL_NETWORK_ERRORS, LOG_PROTOCOL_STORAGE_ERRORS, LOG_RESUME_DATA, LOG_RPC_EVENTS, LOG_RPC_DUMP, LOG_SESSION_EVENTS, LOG_STORAGE, LOG_SYSTEM, LOG_SYSTEM_POLL, LOG_SYSTEM_THREAD, LOG_TRACKER_DUMP, LOG_TRACKER_EVENTS, LOG_TRACKER_REQUESTS, LOG_UI_EVENTS, LOG_GROUP_MAX_SIZE }; #define lt_log_is_valid(log_group) (torrent::log_groups[log_group].valid()) #define lt_log_print(log_group, ...) \ { if (torrent::log_groups[log_group].valid()) \ torrent::log_groups[log_group].internal_print(NULL, NULL, NULL, 0, __VA_ARGS__); } #define lt_log_print_hash(log_group, log_hash, log_subsystem, ...) \ { if (torrent::log_groups[log_group].valid()) \ torrent::log_groups[log_group].internal_print(&log_hash, log_subsystem, NULL, 0, __VA_ARGS__); } #define lt_log_print_info(log_group, log_info, log_subsystem, ...) \ { if (torrent::log_groups[log_group].valid()) \ torrent::log_groups[log_group].internal_print(&log_info->hash(), log_subsystem, NULL, 0, __VA_ARGS__); } #define lt_log_print_data(log_group, log_data, log_subsystem, ...) \ { if (torrent::log_groups[log_group].valid()) \ torrent::log_groups[log_group].internal_print(&log_data->hash(), log_subsystem, NULL, 0, __VA_ARGS__); } #define lt_log_print_dump(log_group, log_dump_data, log_dump_size, ...) \ { if (torrent::log_groups[log_group].valid()) \ torrent::log_groups[log_group].internal_print(NULL, NULL, log_dump_data, log_dump_size, __VA_ARGS__); } #define lt_log_print_hash_dump(log_group, log_dump_data, log_dump_size, log_hash, log_subsystem, ...) \ { if (torrent::log_groups[log_group].valid()) \ torrent::log_groups[log_group].internal_print(&log_hash, log_subsystem, log_dump_data, log_dump_size, __VA_ARGS__); } #define lt_log_print_info_dump(log_group, log_dump_data, log_dump_size, log_info, log_subsystem, ...) \ { if (torrent::log_groups[log_group].valid()) \ torrent::log_groups[log_group].internal_print(&log_info->hash(), log_subsystem, log_dump_data, log_dump_size, __VA_ARGS__); } #define lt_log_print_subsystem(log_group, log_subsystem, ...) \ { if (torrent::log_groups[log_group].valid()) \ torrent::log_groups[log_group].internal_print(NULL, log_subsystem, NULL, 0, __VA_ARGS__); } using log_slot = std::function; class LIBTORRENT_EXPORT log_group { public: using outputs_type = std::bitset<64>; log_group() { m_outputs.reset(); m_cached_outputs.reset(); } bool valid() const { return m_first != NULL; } bool empty() const { return m_first == NULL; } size_t size_outputs() const { return std::distance(m_first, m_last); } static size_t max_size_outputs() { return 64; } // // Internal: // void internal_print(const HashString* hash, const char* subsystem, const void* dump_data, size_t dump_size, const char* fmt, ...); const outputs_type& outputs() const { return m_outputs; } const outputs_type& cached_outputs() const { return m_cached_outputs; } void clear_cached_outputs() { m_cached_outputs = m_outputs; } void set_outputs(const outputs_type& val) { m_outputs = val; } void set_cached_outputs(const outputs_type& val) { m_cached_outputs = val; } void set_output_at(size_t index, bool val) { m_outputs[index] = val; } void set_cached(log_slot* f, log_slot* l) { m_first = f; m_last = l; } private: outputs_type m_outputs; outputs_type m_cached_outputs; log_slot* m_first{}; log_slot* m_last{}; }; using log_group_list = std::array; using log_output_list = std::vector>; extern log_group_list log_groups LIBTORRENT_EXPORT; extern log_output_list log_outputs LIBTORRENT_EXPORT; void log_initialize() LIBTORRENT_EXPORT; void log_cleanup() LIBTORRENT_EXPORT; void log_open_output(const char* name, const log_slot& slot) LIBTORRENT_EXPORT; void log_close_output(const char* name) LIBTORRENT_EXPORT; void log_close_output_str(const std::string& name) LIBTORRENT_EXPORT; void log_add_group_output(int group, const char* name) LIBTORRENT_EXPORT; void log_remove_group_output(int group, const char* name) LIBTORRENT_EXPORT; void log_add_child(int group, int child) LIBTORRENT_EXPORT; void log_remove_child(int group, int child) LIBTORRENT_EXPORT; void log_open_file_output(const char* name, const char* filename, bool append = false, bool flush = false) LIBTORRENT_EXPORT; void log_open_gz_file_output(const char* name, const char* filename, bool append = false) LIBTORRENT_EXPORT; // // Implementation: // inline void log_close_output_str(const std::string& name) { log_close_output(name.c_str()); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/log_buffer.cc000066400000000000000000000021451522271512000221160ustar00rootroot00000000000000#include "config.h" #include "log_buffer.h" #include #include "log.h" namespace torrent { // Rename function/args? log_buffer::const_iterator log_buffer::find_older(int32_t older_than) { if (empty() || !back().is_younger_than(older_than)) return end(); return std::find_if(begin(), end(), [older_than](const auto& entry) { return entry.is_younger_or_same(older_than); }); } void log_buffer::lock_and_push_log(const char* data, size_t length, int group) { if (group < 0) return; lock(); if (size() >= max_size()) base_type::pop_front(); base_type::push_back(log_entry(this_thread::cached_seconds().count(), group % 6, std::string(data, length))); if (m_slot_update) m_slot_update(); unlock(); } static void log_buffer_deleter(log_buffer* lb) { delete lb; } log_buffer_ptr log_open_log_buffer(const char* name) { // TODO: Deregister when deleting. auto buffer = log_buffer_ptr(new log_buffer, &log_buffer_deleter); log_open_output(name, [b = buffer.get()](auto d, auto l, auto g) { b->lock_and_push_log(d, l, g); }); return buffer; } } // namespace torrent libtorrent-0.16.17/src/torrent/utils/log_buffer.h000066400000000000000000000036721522271512000217660ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_UTILS_LOG_BUFFER_H #define LIBTORRENT_TORRENT_UTILS_LOG_BUFFER_H #include #include #include #include #include #include #include namespace torrent { struct log_entry { log_entry(int32_t t, int32_t grp, std::string msg) : timestamp(t), group(grp), message(std::move(msg)) {} bool is_older_than(int32_t t) const { return timestamp < t; } bool is_younger_than(int32_t t) const { return timestamp > t; } bool is_younger_or_same(int32_t t) const { return timestamp >= t; } int32_t timestamp; int32_t group; std::string message; }; class LIBTORRENT_EXPORT log_buffer : private std::deque { public: using base_type = std::deque; using slot_void = std::function; using base_type::iterator; using base_type::const_iterator; using base_type::reverse_iterator; using base_type::const_reverse_iterator; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; using base_type::front; using base_type::back; using base_type::empty; using base_type::size; unsigned int max_size() const { return m_max_size; } // Always lock before calling any function. void lock() { m_lock.lock(); } void unlock() { m_lock.unlock(); } const_iterator find_older(int32_t older_than); void lock_and_set_update_slot(const slot_void& slot) { lock(); m_slot_update = slot; unlock(); } void lock_and_push_log(const char* data, size_t length, int group); private: std::mutex m_lock; unsigned int m_max_size{200}; slot_void m_slot_update; }; typedef std::unique_ptr> log_buffer_ptr; log_buffer_ptr log_open_log_buffer(const char* name) LIBTORRENT_EXPORT; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/option_strings.cc000066400000000000000000000164351522271512000230740ustar00rootroot00000000000000#include "config.h" #include #include #include "torrent/download.h" #include "torrent/exceptions.h" #include "torrent/object.h" #include "torrent/download/choke_group.h" #include "torrent/download/choke_queue.h" #include "torrent/peer/peer_info.h" #include "torrent/runtime/network_config.h" #include "torrent/utils/option_strings.h" namespace torrent { struct option_single { unsigned int size; const char* const* name; }; struct option_pair { const char* name; unsigned int value; }; constexpr option_pair option_list_connection_type[] = { { "leech", Download::CONNECTION_LEECH }, { "seed", Download::CONNECTION_SEED }, { "initial_seed", Download::CONNECTION_INITIAL_SEED }, { "metadata", Download::CONNECTION_METADATA }, { NULL, 0 } }; constexpr option_pair option_list_heuristics[] = { { "upload_leech", HEURISTICS_UPLOAD_LEECH }, { "upload_leech_experimental", HEURISTICS_UPLOAD_LEECH_EXPERIMENTAL }, { "upload_seed", HEURISTICS_UPLOAD_SEED }, { "download_leech", HEURISTICS_DOWNLOAD_LEECH }, { "invalid", HEURISTICS_MAX_SIZE }, { NULL, 0 } }; constexpr option_pair option_list_heuristics_download[] = { { "download_leech", HEURISTICS_DOWNLOAD_LEECH }, { NULL, 0 } }; constexpr option_pair option_list_heuristics_upload[] = { { "upload_leech", HEURISTICS_UPLOAD_LEECH }, { "upload_leech_experimental", HEURISTICS_UPLOAD_LEECH_EXPERIMENTAL }, { "upload_seed", HEURISTICS_UPLOAD_SEED }, { NULL, 0 } }; constexpr option_pair option_list_encryption[] = { { "none", runtime::NetworkConfig::encryption_none }, { "allow_incoming", runtime::NetworkConfig::encryption_allow_incoming }, { "try_outgoing", runtime::NetworkConfig::encryption_try_outgoing }, { "require", runtime::NetworkConfig::encryption_require }, { "require_RC4", runtime::NetworkConfig::encryption_require_RC4 }, { "require_rc4", runtime::NetworkConfig::encryption_require_RC4 }, { "enable_retry", runtime::NetworkConfig::encryption_enable_retry }, { "prefer_plaintext", runtime::NetworkConfig::encryption_prefer_plaintext }, { NULL, 0 } }; constexpr option_pair option_list_ip_filter[] = { { "unwanted", PeerInfo::flag_unwanted }, { "preferred", PeerInfo::flag_preferred }, { NULL, 0 } }; constexpr option_pair option_list_ip_tos[] = { { "default", runtime::NetworkConfig::iptos_default }, { "lowdelay", runtime::NetworkConfig::iptos_lowdelay }, { "throughput", runtime::NetworkConfig::iptos_throughput }, { "reliability", runtime::NetworkConfig::iptos_reliability }, { NULL, 0 } }; constexpr option_pair option_list_tracker_mode[] = { { "normal", choke_group::TRACKER_MODE_NORMAL }, { "aggressive", choke_group::TRACKER_MODE_AGGRESSIVE }, { NULL, 0 } }; constexpr const char* option_list_handshake_connection[] = { "none", "incoming", "outgoing_normal", "outgoing_encrypted", "outgoing_proxy", "success", "dropped", "failed", "retry_plaintext", "retry_encrypted", nullptr }; constexpr const char* option_list_log_group[] = { "critical", "error", "warn", "notice", "info", "debug", "storage_critical", "storage_error", "storage_warn", "storage_notice", "storage_info", "storage_debug", "torrent_critical", "torrent_error", "torrent_warn", "torrent_notice", "torrent_info", "torrent_debug", "__non_cascading__", "connection", "connection_bind", "connection_fd", "connection_filter", "connection_hanshake", "connection_listen", "dht", "dht_all", "dht_error", "dht_controller", "dht_node", "dht_router", "dht_server", "instrumentation_memory", "instrumentation_mincore", "instrumentation_choke", "instrumentation_polling", "instrumentation_transfers", "mock_calls", "net_dns", "net_http", "net_socket", "peer_choke_queue", "peer_list_events", "peer_list_address", "protocol_piece_events", "protocol_metadata_events", "protocol_network_errors", "protocol_storage_errors", "resume_data", "rpc_events", "rpc_dump", "session_events", "storage", "system", "system_poll", "system_thread", "tracker_dump", "tracker_events", "tracker_requests", "ui_events", NULL }; constexpr const char* option_list_socket_category[] = { "generic", "http", "internal", "rpc", "files", NULL }; constexpr const char* option_list_tracker_event[] = { "updated", "completed", "started", "stopped", "scrape", NULL }; constexpr std::array option_pair_lists{ option_list_connection_type, option_list_heuristics, option_list_heuristics_download, option_list_heuristics_upload, option_list_encryption, option_list_ip_filter, option_list_ip_tos, option_list_tracker_mode, }; static_assert(option_pair_lists.size() == OPTION_START_COMPACT); #define OPTION_SINGLE_ENTRY(single_name) \ option_single{ sizeof(single_name) / sizeof(const char*) - 1, single_name } constexpr std::array option_single_lists{ OPTION_SINGLE_ENTRY(option_list_handshake_connection), OPTION_SINGLE_ENTRY(option_list_log_group), OPTION_SINGLE_ENTRY(option_list_socket_category), OPTION_SINGLE_ENTRY(option_list_tracker_event), }; static_assert(option_single_lists.size() == OPTION_SINGLE_SIZE); int option_find_string(option_enum opt_enum, const char* name) { if (opt_enum < OPTION_START_COMPACT) { auto itr = option_pair_lists[opt_enum]; do { if (std::strcmp(itr->name, name) == 0) return itr->value; } while ((++itr)->name != NULL); } else if (opt_enum < OPTION_MAX_SIZE) { auto itr = option_single_lists[opt_enum - OPTION_START_COMPACT].name; do { if (std::strcmp(*itr, name) == 0) return std::distance(option_single_lists[opt_enum - OPTION_START_COMPACT].name, itr); } while (*++itr != NULL); } throw input_error("invalid option name : enum:" + std::to_string(opt_enum) + " name:'" + std::string(name) + "'"); } const char* option_to_c_str(option_enum opt_enum, unsigned int value, const char* not_found) { if (opt_enum < OPTION_START_COMPACT) { auto itr = option_pair_lists[opt_enum]; do { if (itr->value == value) return itr->name; } while ((++itr)->name != nullptr); } else if (opt_enum < OPTION_MAX_SIZE) { if (value < option_single_lists[opt_enum - OPTION_START_COMPACT].size) return option_single_lists[opt_enum - OPTION_START_COMPACT].name[value]; } return not_found; } const char* option_to_c_str_or_throw(option_enum opt_enum, unsigned int value, const char* not_found) { const char* result = option_to_c_str(opt_enum, value, nullptr); if (result == nullptr) throw input_error(std::string(not_found) + " : enum:" + std::to_string(opt_enum) + " value:" + std::to_string(value)); return result; } torrent::Object option_list_strings(option_enum opt_enum) { Object::list_type result; if (opt_enum < OPTION_START_COMPACT) { auto itr = option_pair_lists[opt_enum]; while (itr->name != NULL) result.emplace_back(std::string(itr++->name)); } else if (opt_enum < OPTION_MAX_SIZE) { auto itr = option_single_lists[opt_enum - OPTION_START_COMPACT].name; while (*itr != NULL) result.emplace_back(std::string(*itr++)); } return Object::from_list(result); } } // namespace torrent libtorrent-0.16.17/src/torrent/utils/option_strings.h000066400000000000000000000042741522271512000227340ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_OPTION_STRINGS_H #define LIBTORRENT_UTILS_OPTION_STRINGS_H #include #include namespace torrent { class Object; enum option_enum { OPTION_CONNECTION_TYPE, OPTION_CHOKE_HEURISTICS, OPTION_CHOKE_HEURISTICS_DOWNLOAD, OPTION_CHOKE_HEURISTICS_UPLOAD, OPTION_ENCRYPTION, OPTION_IP_FILTER, OPTION_IP_TOS, OPTION_TRACKER_MODE, OPTION_HANDSHAKE_CONNECTION, OPTION_LOG_GROUP, OPTION_SOCKET_CATEGORY, OPTION_TRACKER_EVENT, OPTION_MAX_SIZE, OPTION_START_COMPACT = OPTION_HANDSHAKE_CONNECTION, OPTION_SINGLE_SIZE = OPTION_MAX_SIZE - OPTION_START_COMPACT }; int option_find_string(option_enum opt_enum, const char* name) LIBTORRENT_EXPORT; inline int option_find_string_str(option_enum opt_enum, const std::string& name) { return option_find_string(opt_enum, name.c_str()); } const char* option_to_c_str(option_enum opt_enum, unsigned int value, const char* not_found = "invalid") LIBTORRENT_EXPORT; const char* option_to_c_str_or_throw(option_enum opt_enum, unsigned int value, const char* not_found = "Invalid option value") LIBTORRENT_EXPORT; std::string option_to_str(option_enum opt_enum, unsigned int value); std::string option_to_str(option_enum opt_enum, unsigned int value, const char* not_found); std::string option_to_str_or_throw(option_enum opt_enum, unsigned int value); std::string option_to_str_or_throw(option_enum opt_enum, unsigned int value, const char* not_found); torrent::Object option_list_strings(option_enum opt_enum) LIBTORRENT_EXPORT; // // Implementation: // inline std::string option_to_str(option_enum opt_enum, unsigned int value) { return option_to_c_str(opt_enum, value); } inline std::string option_to_str(option_enum opt_enum, unsigned int value, const char* not_found) { return option_to_c_str(opt_enum, value, not_found); } inline std::string option_to_str_or_throw(option_enum opt_enum, unsigned int value) { return option_to_c_str_or_throw(opt_enum, value); } inline std::string option_to_str_or_throw(option_enum opt_enum, unsigned int value, const char* not_found) { return option_to_c_str_or_throw(opt_enum, value, not_found); } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/random.cc000066400000000000000000000012671522271512000212700ustar00rootroot00000000000000#include "config.h" #include "random.h" #include "torrent/exceptions.h" namespace torrent { template std::enable_if_t, T> static random_uniform_template(T min, T max) { if (min > max) throw internal_error("random_uniform: min > max"); if (min == max) return min; static thread_local std::mt19937 mt(std::random_device{}()); return std::uniform_int_distribution(min, max)(mt); } uint16_t random_uniform_uint16(uint16_t min, uint16_t max) { return random_uniform_template(min, max); } uint32_t random_uniform_uint32(uint32_t min, uint32_t max) { return random_uniform_template(min, max); } } // namespace torrent libtorrent-0.16.17/src/torrent/utils/random.h000066400000000000000000000010371522271512000211250ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_UTILS_RANDOM_H #define LIBTORRENT_TORRENT_UTILS_RANDOM_H #include #include #include namespace torrent { [[gnu::weak]] LIBTORRENT_EXPORT uint16_t random_uniform_uint16(uint16_t min = std::numeric_limits::min(), uint16_t max = std::numeric_limits::max()); [[gnu::weak]] LIBTORRENT_EXPORT uint32_t random_uniform_uint32(uint32_t min = std::numeric_limits::min(), uint32_t max = std::numeric_limits::max()); } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/ranges.h000066400000000000000000000137061522271512000211320ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_RANGES_H #define LIBTORRENT_UTILS_RANGES_H #include #include namespace torrent { template class ranges : private std::vector > { public: using base_type = std::vector>; using bound_type = RangesType; using value_type = typename base_type::value_type; using reference = typename base_type::reference; using iterator = typename base_type::iterator; using const_iterator = typename base_type::const_iterator; using reverse_iterator = typename base_type::reverse_iterator; using base_type::clear; using base_type::empty; using base_type::size; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; using base_type::front; using base_type::back; void insert(bound_type first, bound_type last) { insert(std::make_pair(first, last)); } void erase(bound_type first, bound_type last) { erase(std::make_pair(first, last)); } void insert(value_type r); void erase(value_type r); // Find the first ranges that has an end greater than index. iterator find(bound_type index); const_iterator find(bound_type index) const; // Use find with no closest match. bool has(bound_type index) const; size_t intersect_distance(bound_type first, bound_type last) const; size_t intersect_distance(value_type range) const; static ranges create_union(const ranges& left, const ranges& right); }; template void ranges::insert(value_type r) { if (r.first >= r.second) return; auto first = std::find_if(begin(), end(), [r](const value_type v) { return r.first <= v.second; }); if (first == end() || r.second < first->first) { // The new range is before the first, after the last or between // two ranges. base_type::insert(first, r); } else { first->first = std::min(r.first, first->first); first->second = std::max(r.second, first->second); auto last = std::find_if(first, end(), [first](const value_type v) { return first->second < v.second; }); if (last != end() && first->second >= last->first) first->second = (last++)->second; base_type::erase(first + 1, last); } } template void ranges::erase(value_type r) { if (r.first >= r.second) return; auto first = std::find_if(begin(), end(), [r](const value_type v) { return r.first < v.second; }); auto last = std::find_if(first, end(), [r](const value_type v) { return r.second < v.second; }); if (first == end()) return; if (first == last) { if (r.first > first->first) { std::swap(first->first, r.second); base_type::insert(first, value_type(r.second, r.first)); } else if (r.second > first->first) { first->first = r.second; } } else { if (r.first > first->first) (first++)->second = r.first; if (last != end() && r.second > last->first) last->first = r.second; base_type::erase(first, last); } } // Find the first ranges that has an end greater than index. template inline typename ranges::iterator ranges::find(bound_type index) { return std::find_if(begin(), end(), [index](const value_type v) { return index < v.second; }); } template inline typename ranges::const_iterator ranges::find(bound_type index) const { return std::find_if(begin(), end(), [index](const value_type v) { return index < v.second; }); } // Use find with no closest match. template bool ranges::has(bound_type index) const { auto itr = find(index); return itr != end() && index >= itr->first; } template size_t ranges::intersect_distance(bound_type first, bound_type last) const { return intersect_distance(std::make_pair(first, last)); } // The total length of all the extents within the bounds of 'range'. template size_t ranges::intersect_distance(value_type range) const { auto first = find(range.first); if (first == end() || range.second <= first->first) return 0; size_t dist = std::min(range.second, first->second) - std::max(range.first, first->first); while (++first != end() && range.second > first->first) dist += std::min(range.second, first->second) - first->first; return dist; } template ranges ranges::create_union(const ranges& left, const ranges& right) { if (left.empty()) return right; if (right.empty()) return left; ranges result; auto left_itr = left.begin(); auto left_last = left.end(); auto right_itr = right.begin(); auto right_last = right.end(); if (left_itr->first < right_itr->first) result.base_type::push_back(*left_itr++); else result.base_type::push_back(*right_itr++); while (left_itr != left_last && right_itr != right_last) { value_type next; if (left_itr->first < right_itr->first) next = *left_itr++; else next = *right_itr++; if (next.first <= result.back().second) result.back().second = std::max(next.second, result.back().second); else result.base_type::push_back(next); } // Only one of these while loops will be triggered. for (; left_itr != left_last; left_itr++) { if (left_itr->first <= result.back().second) result.back().second = std::max(left_itr->second, result.back().second); else result.base_type::push_back(*left_itr); } for (; right_itr != right_last; right_itr++) { if (right_itr->first <= result.back().second) result.back().second = std::max(right_itr->second, result.back().second); else result.base_type::push_back(*right_itr); } return result; } } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/resume.cc000066400000000000000000000466161522271512000213170ustar00rootroot00000000000000#include "config.h" #include "resume.h" #include "data/file.h" #include "data/file_list.h" #include "data/transfer_list.h" #include "download/download_main.h" #include "net/address_list.h" #include "peer/peer_info.h" #include "peer/peer_list.h" #include "torrent/common.h" #include "torrent/bitfield.h" #include "torrent/download.h" #include "torrent/download_info.h" #include "torrent/object.h" #include "torrent/tracker/tracker.h" #include "torrent/utils/file_stat.h" #include "torrent/utils/log.h" #include "tracker/tracker_list.h" #define LT_LOG_LOAD(log_fmt, ...) \ lt_log_print_info(LOG_RESUME_DATA, download.info(), "resume_load", log_fmt, __VA_ARGS__); #define LT_LOG_LOAD_INVALID(log_fmt, ...) \ lt_log_print_info(LOG_RESUME_DATA, download.info(), "resume_load", "invalid resume data: " log_fmt, __VA_ARGS__); #define LT_LOG_LOAD_FILE(log_fmt, ...) \ lt_log_print_info(LOG_RESUME_DATA, download.info(), "resume_load", "file[%u]: " log_fmt, \ file_index, __VA_ARGS__); #define LT_LOG_SAVE(log_fmt, ...) \ lt_log_print_info(LOG_RESUME_DATA, download.info(), "resume_save", log_fmt, __VA_ARGS__); #define LT_LOG_SAVE_FILE(log_fmt, ...) \ lt_log_print_info(LOG_RESUME_DATA, download.info(), "resume_save", "file[%u]: " log_fmt, \ file_index, __VA_ARGS__); namespace torrent { void resume_load_progress(Download download, const Object& object) { if (!object.has_key_list("files")) { LT_LOG_LOAD("could not find 'files' key", 0); return; } const Object::list_type& files = object.get_key_list("files"); if (files.size() != download.file_list()->size_files()) { LT_LOG_LOAD_INVALID("number of resumable files does not match files in torrent", 0); return; } if (!resume_load_bitfield(download, object)) return; auto filesItr = files.begin(); FileList* fileList = download.file_list(); for (auto listItr = fileList->begin(), listLast = fileList->end(); listItr != listLast; ++listItr, ++filesItr) { if ( (*listItr)->is_padding()) continue; unsigned int file_index = std::distance(fileList->begin(), listItr); utils::FileStat fs; if (!filesItr->has_key_value("mtime")) { LT_LOG_LOAD_FILE("no mtime found, file:create|resize range:clear|recheck", 0); // If 'mtime' is erased, it means we should start hashing and // downloading the file as if it was a new torrent. (*listItr)->set_flags(File::flag_create_queued | File::flag_resize_queued); download.update_range(Download::update_range_recheck | Download::update_range_clear, (*listItr)->range().first, (*listItr)->range().second); continue; } int64_t mtimeValue = filesItr->get_key_value("mtime"); bool fileExists = fs.update(fileList->root_dir() + (*listItr)->path()->as_string()); // The default action when we have 'mtime' is not to create nor // resize the file. (*listItr)->unset_flags(File::flag_create_queued | File::flag_resize_queued); if (mtimeValue == ~int64_t{0} || mtimeValue == ~int64_t{1}) { // If 'mtime' is ~0 it means we haven't gotten around to // creating the file. // // Else if it is ~1 it means the file doesn't exist nor do we // want to create it. // // When 'mtime' is ~2 we need to recheck the hash without // creating the file. It will just fail on the mtime check // later, so we don't need to handle it explicitly. if (mtimeValue == ~int64_t{0}) { LT_LOG_LOAD_FILE("file not created by client, file:create|resize range:clear|(recheck)", 0); (*listItr)->set_flags(File::flag_create_queued | File::flag_resize_queued); } else { LT_LOG_LOAD_FILE("do not create file, file:- range:clear|(recheck)", 0); } // Ensure the bitfield range is cleared so that stray resume // data doesn't get counted. download.update_range(Download::update_range_clear | (fileExists ? Download::update_range_recheck : 0), (*listItr)->range().first, (*listItr)->range().second); continue; } // If the file is the wrong size, queue resize and clear resume // data for that file. if (static_cast(fs.size()) != (*listItr)->size_bytes()) { if (fs.size() == 0) { LT_LOG_LOAD_FILE("zero-length file found, file:resize range:clear|recheck", 0); } else { LT_LOG_LOAD_FILE("file has the wrong size, file:resize range:clear|recheck", 0); } (*listItr)->set_flags(File::flag_resize_queued); download.update_range(Download::update_range_clear | Download::update_range_recheck, (*listItr)->range().first, (*listItr)->range().second); continue; } // An 'mtime' of ~3 means the resume data was written while the // torrent was actively downloading, and thus we need to recheck // chunks that might not have been completely written to disk. // // This gets handled below, so just skip to the next file. if (mtimeValue == ~int64_t{3}) { LT_LOG_LOAD_FILE("file was downloading", 0); continue; } // An 'mtime' of ~2 indicates that the resume data was made by an // old rtorrent version which does not include 'uncertain_pieces' // field, and thus can't be relied upon. // // If the 'mtime' is an actual mtime we check to see if it matches // the file, else clear the range. This should be set only for // files that have completed and got no indices in // TransferList::completed_list(). if (mtimeValue == ~int64_t{2} || mtimeValue != fs.modified_time()) { LT_LOG_LOAD_FILE("resume data doesn't include uncertain pieces, range:clear|recheck", 0); download.update_range(Download::update_range_clear | Download::update_range_recheck, (*listItr)->range().first, (*listItr)->range().second); continue; } LT_LOG_LOAD_FILE("no recheck needed", 0); } resume_load_uncertain_pieces(download, object); } void resume_save_progress(Download download, Object& object) { // We don't remove the old hash data since it might still be valid, // just that the client didn't finish the check this time. if (!download.is_hash_checked()) { LT_LOG_SAVE("hash not checked, no progress saved", 0); return; } download.sync_chunks(); // If syncing failed, invalidate all resume data and return. if (!download.is_hash_checked()) { LT_LOG_SAVE("sync failed, invalidating resume data", 0); if (!object.has_key_list("files")) return; Object::list_type& files = object.get_key_list("files"); for (auto& file : files) file.insert_key("mtime", ~int64_t{2}); return; } resume_save_bitfield(download, object); auto& files = object.insert_preserve_copy("files", Object::create_list()).first->second.as_list(); auto filesItr = files.begin(); FileList* fileList = download.file_list(); for (auto listItr = fileList->begin(), listLast = fileList->end(); listItr != listLast; ++listItr, ++filesItr) { unsigned int file_index = std::distance(fileList->begin(), listItr); if (filesItr == files.end()) filesItr = files.insert(filesItr, Object::create_map()); else if (!filesItr->is_map()) *filesItr = Object::create_map(); filesItr->insert_key("completed", static_cast((*listItr)->completed_chunks())); utils::FileStat fs; bool fileExists = fs.update(fileList->root_dir() + (*listItr)->path()->as_string()); if (!fileExists) { if ((*listItr)->is_create_queued()) { // ~0 means the file still needs to be created. filesItr->insert_key("mtime", ~int64_t{0}); LT_LOG_SAVE_FILE("file not created, create queued", 0); } else { // ~1 means the file shouldn't be created. filesItr->insert_key("mtime", ~int64_t{1}); LT_LOG_SAVE_FILE("file not created, create not queued", 0); } // } else if ((*listItr)->completed_chunks() == (*listItr)->size_chunks()) { } else if (fileList->bitfield()->is_all_set()) { // Currently only checking if we're finished. This needs to be // smarter when it comes to downloading partial torrents, etc. // This assumes the syncs are properly called before // resume_save_progress gets called after finishing a torrent. filesItr->insert_key("mtime", static_cast(fs.modified_time())); LT_LOG_SAVE_FILE("file completed, mtime:%" PRIi64, (int64_t)fs.modified_time()); } else if (!download.info()->is_active()) { // When stopped, all chunks should have received sync, thus the // file's mtime will be correct. (We hope) filesItr->insert_key("mtime", static_cast(fs.modified_time())); LT_LOG_SAVE_FILE("file inactive and assumed sync'ed, mtime:%" PRIi64, (int64_t)fs.modified_time()); } else { // If the torrent isn't done and we've not shut down, then set // 'mtime' to ~3 so as to indicate that the 'mtime' is not to be // trusted, yet we have a partial bitfield for the file. filesItr->insert_key("mtime", ~int64_t{3}); LT_LOG_SAVE_FILE("file actively downloading", 0); } } } void resume_clear_progress([[maybe_unused]] Download download, Object& object) { object.erase_key("bitfield"); } bool resume_load_bitfield(Download download, const Object& object) { if (object.has_key_string("bitfield")) { const Object::string_type& bitfield = object.get_key_string("bitfield"); if (bitfield.size() != download.file_list()->bitfield()->size_bytes()) { LT_LOG_LOAD_INVALID("size of resumable bitfield does not match bitfield size of torrent", 0); return false; } LT_LOG_LOAD("restoring partial bitfield", 0); download.set_bitfield(reinterpret_cast(bitfield.c_str()), (reinterpret_cast(bitfield.c_str()) + bitfield.size())); } else if (object.has_key_value("bitfield")) { Object::value_type chunksDone = object.get_key_value("bitfield"); if (chunksDone == download.file_list()->bitfield()->size_bits()) { LT_LOG_LOAD("restoring completed bitfield", 0); download.set_bitfield(true); } else if (chunksDone == 0) { LT_LOG_LOAD("restoring empty bitfield", 0); download.set_bitfield(false); } else { LT_LOG_LOAD_INVALID("restoring empty bitfield", 0); return false; } } else { LT_LOG_LOAD_INVALID("valid 'bitfield' not found", 0); return false; } return true; } void resume_save_bitfield(Download download, Object& object) { const Bitfield* bitfield = download.file_list()->bitfield(); if (bitfield->is_all_set() || bitfield->is_all_unset()) { LT_LOG_SAVE("uniform bitfield, saving size only", 0); object.insert_key("bitfield", bitfield->size_set()); } else { LT_LOG_SAVE("saving bitfield", 0); object.insert_key("bitfield", std::string(bitfield->begin(), bitfield->end())); } } void resume_load_uncertain_pieces(Download download, const Object& object) { // Don't rehash when loading resume data within the same session. if (!object.has_key_string("uncertain_pieces")) { LT_LOG_LOAD("no uncertain pieces marked", 0); return; } if (!object.has_key_value("uncertain_pieces.timestamp") || object.get_key_value("uncertain_pieces.timestamp") >= static_cast(download.info()->load_date())) { LT_LOG_LOAD_INVALID("invalid information on uncertain pieces", 0); return; } const Object::string_type& uncertain = object.get_key_string("uncertain_pieces"); LT_LOG_LOAD("found %zu uncertain pieces", uncertain.size() / 2); const char* itr = uncertain.c_str(); const char* last = uncertain.c_str() + uncertain.size(); while (itr + sizeof(uint32_t) <= last) { // Fix this so it does full ranges. download.update_range(Download::update_range_recheck | Download::update_range_clear, ntohl(*reinterpret_cast(itr)), ntohl(*reinterpret_cast(itr)) + 1); itr += sizeof(uint32_t); } } void resume_save_uncertain_pieces(Download download, Object& object) { // Add information on what chunks might still not have been properly // written to disk. object.erase_key("uncertain_pieces"); object.erase_key("uncertain_pieces.timestamp"); const TransferList::completed_list_type& completedList = download.transfer_list()->completed_list(); auto itr = std::find_if(completedList.begin(), completedList.end(), [](const auto& v) { return this_thread::cached_time() - 15min <= std::chrono::microseconds(v.first); }); if (itr == completedList.end()) return; std::vector buffer; buffer.reserve(std::distance(itr, completedList.end())); while (itr != completedList.end()) buffer.push_back((itr++)->second); std::sort(buffer.begin(), buffer.end()); for (unsigned int& itr2 : buffer) itr2 = htonl(itr2); object.insert_key("uncertain_pieces.timestamp", this_thread::cached_seconds().count()); Object::string_type& completed = object.insert_key("uncertain_pieces", std::string()).as_string(); completed.append(reinterpret_cast(&buffer.front()), buffer.size() * sizeof(uint32_t)); } bool resume_check_target_files(Download download, [[maybe_unused]] const Object& object) { FileList* fileList = download.file_list(); if (!fileList->is_open()) return false; if (!fileList->is_root_dir_created()) return false; if (fileList->is_multi_file()) { // Here we should probably check all/most of the files within the // torrent. But for now just return true, as the root dir is // usually created for each (multi) torrent. // int failed = 0; // int exists = 0; // for (FileList::const_iterator itr = fileList->begin(), last = fileList->end(); itr != last; itr++) { // if (!(*itr)->is_previously_created()) // continue; // if ((*itr)->is_created()) // exists++; // else // failed++; // } // return failed >= exists; return true; } else { // We consider empty file lists as being valid. return fileList->empty() || fileList->front()->is_created(); } } void resume_load_file_priorities(Download download, const Object& object) { if (!object.has_key_list("files")) return; const Object::list_type& files = object.get_key_list("files"); auto filesItr = files.begin(); auto filesLast = files.end(); FileList* fileList = download.file_list(); for (auto listItr = fileList->begin(), listLast = fileList->end(); listItr != listLast; ++listItr, ++filesItr) { if (filesItr == filesLast) return; // Update the priority from the fast resume data. if (filesItr->has_key_value("priority") && filesItr->get_key_value("priority") >= 0 && filesItr->get_key_value("priority") <= PRIORITY_HIGH) (*listItr)->set_priority(static_cast(filesItr->get_key_value("priority"))); if (filesItr->has_key_value("completed")) { auto completed = filesItr->get_key_value("completed"); if (completed < 0 || completed > (*listItr)->size_chunks()) { LT_LOG_LOAD_INVALID("invalid completed chunks value: %" PRIi64 ", resetting to 0", completed); completed = 0; } (*listItr)->set_completed_chunks(completed); } } } void resume_save_file_priorities(Download download, Object& object) { auto& files = object.insert_preserve_copy("files", Object::create_list()).first->second.as_list(); auto filesItr = files.begin(); FileList* fileList = download.file_list(); for (auto listItr = fileList->begin(), listLast = fileList->end(); listItr != listLast; ++listItr, ++filesItr) { if (filesItr == files.end()) filesItr = files.insert(filesItr, Object::create_map()); else if (!filesItr->is_map()) *filesItr = Object::create_map(); filesItr->insert_key("priority", static_cast((*listItr)->priority())); } } void resume_load_addresses(Download download, const Object& object) { if (!object.has_key_list("peers")) return; PeerList* peerList = download.peer_list(); // TODO: Add support for inet6. for (const auto& key : object.get_key_list("peers")) { if (!key.is_map() || !key.has_key_string("inet") || !key.has_key_value("failed") || !key.has_key_value("last")) continue; if (key.get_key_value("last") > this_thread::cached_seconds().count()) continue; auto& inet_str = key.get_key_string("inet"); if (inet_str.size() != sizeof(SocketAddressCompact)) continue; int flags = 0; auto compact_sa = reinterpret_cast(inet_str.c_str()); sa_inet_union sa = *compact_sa; if (compact_sa->port != 0) flags |= PeerList::address_available; PeerInfo* peerInfo = peerList->insert_address(&sa.sa, flags); if (peerInfo == NULL) continue; peerInfo->set_failed_counter(key.get_key_value("failed")); peerInfo->set_last_connection(key.get_key_value("last")); } // Tell rTorrent to harvest addresses. } void resume_save_addresses(Download download, Object& object) { auto& dest = object.insert_key("peers", Object::create_list()); for (const auto& dlp : *download.peer_list()) { // Add some checks, like see if there's anything interesting to // save, etc. Or if we can reconnect to it at some later time. // // This should really ensure that if called on a torrent that has // been closed for a while, it won't throw out perfectly good // entries. Object& peer = dest.insert_back(Object::create_map()); auto sa = dlp.second->socket_address(); if (sa->sa_family == AF_INET) peer.insert_key("inet", SocketAddressCompact(reinterpret_cast(sa)).str()); peer.insert_key("failed", dlp.second->failed_counter()); peer.insert_key("last", dlp.second->is_connected() ? this_thread::cached_seconds().count() : dlp.second->last_connection()); } } void resume_load_tracker_settings(Download download, const Object& object) { if (!object.has_key_map("trackers")) return; auto& src = object.get_key("trackers"); auto tracker_list = download.main()->tracker_list(); for (const auto& map : src.as_map()) { if (!map.second.has_key("extra_tracker") || map.second.get_key_value("extra_tracker") == 0 || !map.second.has_key("group")) continue; if (tracker_list->find_url(map.first) != tracker_list->end()) continue; download.main()->tracker_list()->insert_url(map.second.get_key_value("group"), map.first); } for (auto tracker : *tracker_list) { if (!src.has_key_map(tracker.url())) continue; const Object& trackerObject = src.get_key(tracker.url()); if (trackerObject.has_key_value("enabled") && trackerObject.get_key_value("enabled") == 0) tracker.disable(); else tracker.enable(); } } void resume_save_tracker_settings(Download download, Object& object) { auto& dest = object.insert_preserve_copy("trackers", Object::create_map()).first->second; auto tracker_list = download.main()->tracker_list(); for (const auto& tracker : *tracker_list) { Object& trackerObject = dest.insert_key(tracker.url(), Object::create_map()); trackerObject.insert_key("enabled", Object(static_cast(tracker.is_enabled()))); if (tracker.is_extra_tracker()) { trackerObject.insert_key("extra_tracker", Object(int64_t{1})); trackerObject.insert_key("group", tracker.group()); } } } } // namespace torrent libtorrent-0.16.17/src/torrent/utils/resume.h000066400000000000000000000067021522271512000211510ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY // Various functions for loading and saving various states of a // download to an Object. // // These functions use only the public interface, and thus the client // may choose to replace these with their own resume code. // Should propably move this into a sub-directory. #ifndef LIBTORRENT_UTILS_RESUME_H #define LIBTORRENT_UTILS_RESUME_H #include namespace torrent { // When saving resume data for a torrent that is currently active, set // 'onlyCompleted' to ensure that a crash, etc, will cause incomplete // files to be hashed. void resume_load_progress(Download download, const Object& object) LIBTORRENT_EXPORT; void resume_save_progress(Download download, Object& object) LIBTORRENT_EXPORT; void resume_clear_progress(Download download, Object& object) LIBTORRENT_EXPORT; bool resume_load_bitfield(Download download, const Object& object) LIBTORRENT_EXPORT; void resume_save_bitfield(Download download, Object& object) LIBTORRENT_EXPORT; // Do not call 'resume_load_uncertain_pieces' directly. void resume_load_uncertain_pieces(Download download, const Object& object) LIBTORRENT_EXPORT; void resume_save_uncertain_pieces(Download download, Object& object) LIBTORRENT_EXPORT; bool resume_check_target_files(Download download, const Object& object) LIBTORRENT_EXPORT; void resume_load_file_priorities(Download download, const Object& object) LIBTORRENT_EXPORT; void resume_save_file_priorities(Download download, Object& object) LIBTORRENT_EXPORT; void resume_load_addresses(Download download, const Object& object) LIBTORRENT_EXPORT; void resume_save_addresses(Download download, Object& object) LIBTORRENT_EXPORT; void resume_load_tracker_settings(Download download, const Object& object) LIBTORRENT_EXPORT; void resume_save_tracker_settings(Download download, Object& object) LIBTORRENT_EXPORT; } // namespace torrent #endif libtorrent-0.16.17/src/torrent/utils/scheduler.cc000066400000000000000000000120761522271512000217660ustar00rootroot00000000000000#include "config.h" #include "torrent/utils/scheduler.h" #include #include #include "torrent/exceptions.h" #include "torrent/utils/chrono.h" namespace torrent::utils { static constexpr auto compare = [](const std::unique_ptr& a, const std::unique_ptr& b) { return a->time > b->time; }; SchedulerEntry::~SchedulerEntry() { assert(!is_scheduled() && "SchedulerEntry::~SchedulerEntry() called on a scheduled item."); m_slot = nullptr; } Scheduler::time_type Scheduler::next_timeout(Scheduler::time_type max_timeout) { while (!m_heap.empty() && m_heap.front()->entry == nullptr) { std::ranges::pop_heap(m_heap, compare); m_heap.pop_back(); } if (m_heap.empty()) return max_timeout; auto timeout = m_heap.front()->time - m_cached_time; if (timeout >= max_timeout) return max_timeout; return std::max(timeout, Scheduler::time_type()); } // We can't make erase/update part of SchedulerItem in case another thread tries to call the // scheduler, which is not thread-safe. void Scheduler::erase(SchedulerEntry* entry) { assert(m_thread_id == std::thread::id() || m_thread_id == std::this_thread::get_id()); if (!entry->is_scheduled()) return; // Check is_valid() after is_schedulerd() so that it is safe to call // erase on untouched instances. if (!entry->is_valid()) throw torrent::internal_error("Scheduler::erase(...) called on an invalid entry."); if (entry->m_handle->scheduler != this) throw torrent::internal_error("Scheduler::erase(...) called on an entry that is in another scheduler."); entry->m_handle->entry = nullptr; entry->set_handle(nullptr); } void Scheduler::push_entry(SchedulerEntry* entry, time_type time) { auto handle = std::make_unique(SchedulerHandle{entry, this, time}); entry->set_handle(handle.get()); m_heap.push_back(std::move(handle)); std::ranges::push_heap(m_heap, compare); } void Scheduler::wait_until(SchedulerEntry* entry, Scheduler::time_type time) { assert(m_thread_id == std::thread::id() || m_thread_id == std::this_thread::get_id()); if (time == Scheduler::time_type()) throw torrent::internal_error("Scheduler::wait_until(...) received a bad timer."); if (time < Scheduler::time_type(365 * 24h)) throw torrent::internal_error("Scheduler::wait_until(...) received a too small timer."); if (!entry->is_valid()) throw torrent::internal_error("Scheduler::wait_until(...) called on an invalid entry."); if (entry->is_scheduled()) throw torrent::internal_error("Scheduler::wait_until(...) called on an already scheduled entry."); push_entry(entry, time); } void Scheduler::wait_for(SchedulerEntry* entry, Scheduler::time_type time) { if (time > Scheduler::time_type(10 * 365 * 24h)) throw torrent::internal_error("Scheduler::wait_after(...) received a too large timer."); wait_until(entry, m_cached_time + time); } void Scheduler::wait_for_ceil_seconds(SchedulerEntry* entry, Scheduler::time_type time) { if (time > Scheduler::time_type(10 * 365 * 24h)) throw torrent::internal_error("Scheduler::wait_after_ceil_seconds(...) received a too large timer."); wait_until(entry, ceil_seconds(m_cached_time + time)); } void Scheduler::update_wait_until(SchedulerEntry* entry, Scheduler::time_type time) { assert(m_thread_id == std::thread::id() || m_thread_id == std::this_thread::get_id()); if (time == Scheduler::time_type()) throw torrent::internal_error("Scheduler::update_wait(...) received a bad timer."); if (time < Scheduler::time_type(365 * 24h)) throw torrent::internal_error("Scheduler::update_wait(...) received a too small timer."); if (!entry->is_valid()) throw torrent::internal_error("Scheduler::update_wait(...) called on an invalid entry."); if (entry->is_scheduled()) { if (entry->m_handle->scheduler != this) throw torrent::internal_error("Scheduler::update_wait(...) called on an entry that is in another scheduler."); entry->m_handle->entry = nullptr; push_entry(entry, time); return; } push_entry(entry, time); } void Scheduler::update_wait_for(SchedulerEntry* entry, Scheduler::time_type time) { if (time > Scheduler::time_type(10 * 365 * 24h)) throw torrent::internal_error("Scheduler::update_wait_after(...) received a too large timer."); update_wait_until(entry, m_cached_time + time); } void Scheduler::update_wait_for_ceil_seconds(SchedulerEntry* entry, Scheduler::time_type time) { if (time > Scheduler::time_type(10 * 365 * 24h)) throw torrent::internal_error("Scheduler::update_wait_after_ceil_seconds(...) received a too large timer."); update_wait_until(entry, ceil_seconds(m_cached_time + time)); } void Scheduler::perform(Scheduler::time_type current_time) { while (!m_heap.empty() && m_heap.front()->time <= current_time) { std::ranges::pop_heap(m_heap, compare); auto handle = std::move(m_heap.back()); m_heap.pop_back(); if (handle->entry == nullptr) continue; handle->entry->set_handle(nullptr); handle->entry->slot()(); } } } // namespace torrent::utils libtorrent-0.16.17/src/torrent/utils/scheduler.h000066400000000000000000000056451522271512000216340ustar00rootroot00000000000000#ifndef TORRENT_UTILS_SCHEDULER_H #define TORRENT_UTILS_SCHEDULER_H #include #include #include #include #include #include namespace torrent::utils { class SchedulerEntry; class Scheduler; struct SchedulerHandle { SchedulerEntry* entry{}; Scheduler* scheduler{}; std::chrono::microseconds time{}; }; class LIBTORRENT_EXPORT Scheduler { public: using time_type = std::chrono::microseconds; ~Scheduler() = default; bool empty() const { return m_heap.empty(); } time_type next_timeout(time_type max_timeout); void erase(SchedulerEntry* entry); void wait_until(SchedulerEntry* entry, time_type time); void wait_for(SchedulerEntry* entry, time_type time); void wait_for_ceil_seconds(SchedulerEntry* entry, time_type time); void update_wait_until(SchedulerEntry* entry, time_type time); void update_wait_for(SchedulerEntry* entry, time_type time); void update_wait_for_ceil_seconds(SchedulerEntry* entry, time_type time); protected: friend class system::Thread; void perform(time_type time); void set_thread_id(std::thread::id id) { m_thread_id = id; } void set_cached_time(time_type t) { m_cached_time = t; } private: using heap_type = std::vector>; void push_entry(SchedulerEntry* entry, time_type time); std::atomic m_thread_id; align_cacheline time_type m_cached_time{}; heap_type m_heap; }; class LIBTORRENT_EXPORT SchedulerEntry { public: using slot_type = std::function; using time_type = std::chrono::microseconds; SchedulerEntry() = default; ~SchedulerEntry(); bool is_valid() const { return m_slot != nullptr; } bool is_scheduled() const { return m_handle != nullptr; } slot_type& slot() { return m_slot; } time_type time_or_zero() const { return m_handle ? m_handle->time : time_type{}; } protected: friend class Scheduler; SchedulerHandle* handle() const { return m_handle; } void set_handle(SchedulerHandle* h) { m_handle = h; } private: SchedulerEntry(const SchedulerEntry&) = delete; SchedulerEntry& operator=(const SchedulerEntry&) = delete; slot_type m_slot; SchedulerHandle* m_handle{}; }; class LIBTORRENT_EXPORT ExternalScheduler : public Scheduler { public: void external_perform(time_type time) { perform(time); } void external_set_thread_id(std::thread::id id) { set_thread_id(id); } void external_set_cached_time(time_type t) { set_cached_time(t); } }; } // namespace torrent::utils #endif // TORRENT_UTILS_SCHEDULER_H libtorrent-0.16.17/src/torrent/utils/string_manip.cc000066400000000000000000000107141522271512000224770ustar00rootroot00000000000000#include "config.h" #include "torrent/utils/string_manip.h" #include #include #include #include "torrent/common.h" namespace torrent::utils { bool is_valid_utf8(const std::string& str) { auto itr = str.begin(); const auto end = str.end(); while (itr != end) { int num{}; auto byte = static_cast(*itr); // Verify leading byte. if ((byte & 0x80) == 0x00) num = 1; else if ((byte & 0xE0) == 0xC0) num = 2; else if ((byte & 0xF0) == 0xE0) num = 3; else if ((byte & 0xF8) == 0xF0) num = 4; else return false; ++itr; // Check continuation bytes. for (int i = 1; i < num; ++i) { if (itr == end) return false; if ((static_cast(*itr) & 0xC0) != 0x80) return false; ++itr; } } return true; } std::string_view trim_spaces(std::string_view s) { auto first = std::find_if(s.begin(), s.end(), [](unsigned char ch) { return ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r' && ch != '\f' && ch != '\v'; }); auto last = std::find_if(s.rbegin(), std::make_reverse_iterator(first), [](unsigned char ch) { return ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r' && ch != '\f' && ch != '\v'; }).base(); if (first >= last) return std::string_view(); return std::string_view(first, last - first); } std::string trim_spaces_str(std::string_view s) { return std::string(trim_spaces(s)); } std::string string_with_escape_codes(const std::string& str) { std::string result; for (auto c : str) { if (c < ' ' || c > '~') { result += '%'; result += value_to_hex1(c); result += value_to_hex0(c); continue; } result += c; } return result; } std::string sanitize_string(const std::string& str) { std::string result; bool unprintable{}; bool space{}; for (auto c : str) { if (c < ' ' || c > '~') { if (c == '\n' || c == '\r' || c == '\t') { if (!space && !unprintable) result += ' '; space = true; continue; } if (!unprintable) result += '*'; unprintable = true; space = false; continue; } result += c; unprintable = false; space = false; } return trim_spaces_str(result); } std::string sanitize_string_with_escape_codes(const std::string& str) { std::string result; bool space{}; for (auto c : str) { if (c < ' ' || c > '~') { if (c == '\n' || c == '\r' || c == '\t') { if (!space) result += ' '; space = true; continue; } result += '%'; result += value_to_hex1(c); result += value_to_hex0(c); space = false; continue; } result += c; space = false; } return trim_spaces_str(result); } std::string sanitize_string_with_tags(const std::string& str) { bool in_tag{}; std::string result; std::string sanitized = sanitize_string(str); for (auto c : sanitized) { if (c == '>') { in_tag = false; continue; } if (in_tag || c == '<') { in_tag = true; continue; } result += c; } result = trim_spaces_str(result); if (result.empty()) return trim_spaces_str(sanitized); return result; } std::string transform_to_base64(const std::string& src) { if (src.empty()) return {}; std::string result((4 * ((src.size() + 2) / 3)), '\0'); int actual_length = EVP_EncodeBlock(reinterpret_cast(result.data()), reinterpret_cast(src.data()), src.size()); result.resize(actual_length); return result; } std::optional> transform_from_base64_unsafe(const std::string& src) { if (src.empty()) return std::vector{}; if (src.length() % 4) return std::nullopt; std::vector bytes((src.length() * 3) / 4); int decoded_len = EVP_DecodeBlock(bytes.data(), reinterpret_cast(src.data()), src.length()); if (decoded_len <= 0) return std::nullopt; if (src.back() == '=') decoded_len--; if (src.length() > 1 && src[src.length() - 2] == '=') decoded_len--; // If the input contains extra padding characters, this could cause negative decoded_len. if (decoded_len < 0) return std::nullopt; bytes.resize(decoded_len); return bytes; } } // namespace torrent::utils libtorrent-0.16.17/src/torrent/utils/string_manip.h000066400000000000000000000234651522271512000223500ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_UTILS_STRING_MANIP_H #define LIBTORRENT_TORRENT_UTILS_STRING_MANIP_H #include #include #include #include #include #include namespace torrent::utils { // TODO: Add a copy_escape_html() version that copies to a perfect sized std::string. // TODO: Consider forward declaring these functions, and instantiating the required specializations in the .cc file. bool is_valid_utf8(const std::string& str) LIBTORRENT_EXPORT; std::string_view trim_spaces(std::string_view s) LIBTORRENT_EXPORT; std::string trim_spaces_str(std::string_view s) LIBTORRENT_EXPORT; std::string string_with_escape_codes(const std::string& str) LIBTORRENT_EXPORT; std::string sanitize_string(const std::string& str) LIBTORRENT_EXPORT; std::string sanitize_string_with_escape_codes(const std::string& str) LIBTORRENT_EXPORT; std::string sanitize_string_with_tags(const std::string& str) LIBTORRENT_EXPORT; std::string transform_to_base64(const std::string& src) LIBTORRENT_EXPORT; std::optional> transform_from_base64_unsafe(const std::string& src) LIBTORRENT_EXPORT; char hex_to_value_or_zero(char c); char hex_to_value_or_error(char c); char value_to_hex0(char value); char value_to_hex1(char value); template std::string copy_escape_html_str(const Container& src); template std::string copy_escape_html_str(SrcItr src_first, SrcItr src_last); template DestItr copy_escape_html(SrcItr src_first, SrcItr src_last, DestItr dst_first, DestItr dst_last); template DestItr copy_escape_html(const SrcContainer& src, DestItr dst_first, DestItr dst_last); template typename DstContainer::iterator copy_escape_html(const SrcContainer& src, DstContainer& dst); template typename DstContainer::iterator copy_escape_html(SrcItr src_first, SrcItr src_last, DstContainer& dst); template DestItr transform_from_hex(SrcItr src_first, SrcItr src_last, DestItr dst_first, DestItr dst_last); template DestItr transform_from_hex(const SrcContainer& src, DestItr dst_first, DestItr dst_last); template typename DstContainer::iterator transform_from_hex(const SrcContainer& src, DstContainer& dst); template typename DstContainer::iterator transform_from_hex(SrcItr src_first, SrcItr src_last, DstContainer& dst); template DestItr transform_to_hex(SrcItr src_first, SrcItr src_last, DestItr dst_first, DestItr dst_last); template DestItr transform_to_hex(const SrcContainer& src, DestItr dst_first, DestItr dst_last); template typename DstContainer::iterator transform_to_hex(const SrcContainer& src, DstContainer& dst); template typename DstContainer::iterator transform_to_hex(SrcItr src_first, SrcItr src_last, DstContainer& dst); template std::string transform_to_hex_str(const Container& src); template std::string transform_to_hex_str(SrcItr src_first, SrcItr src_last); // // Conversion between hex char and value. // // Could optimize this abit. inline char hex_to_value_or_zero(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return 10 + c - 'A'; if (c >= 'a' && c <= 'f') return 10 + c - 'a'; return 0; } inline char hex_to_value_or_error(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return 10 + c - 'A'; if (c >= 'a' && c <= 'f') return 10 + c - 'a'; return -1; } inline char value_to_hex0(char value) { value = value & 0x0f; if (value < 10) return '0' + value; return 'A' + (value - 10); } inline char value_to_hex1(char value) { value = (value >> 4) & 0x0f; if (value < 10) return '0' + value; return 'A' + (value - 10); } // // Escape all characters that are not alphanumeric or '-' with %XX. // template std::string copy_escape_html_str(const Container& src) { return copy_escape_html_str(src.begin(), src.end()); } template std::string copy_escape_html_str(SrcItr src_first, SrcItr src_last) { std::string dest; dest.reserve(std::distance(src_first, src_last) * 3); while (src_first != src_last) { if ((*src_first >= 'A' && *src_first <= 'Z') || (*src_first >= 'a' && *src_first <= 'z') || (*src_first >= '0' && *src_first <= '9') || *src_first == '-') { dest += *src_first; } else { dest += '%'; dest += value_to_hex1(*src_first); dest += value_to_hex0(*src_first); } ++src_first; } return dest; } template DestItr copy_escape_html(SrcItr src_first, SrcItr src_last, DestItr dst_first, DestItr dst_last) { while (src_first != src_last) { if ((*src_first >= 'A' && *src_first <= 'Z') || (*src_first >= 'a' && *src_first <= 'z') || (*src_first >= '0' && *src_first <= '9') || *src_first == '-') { if (dst_first == dst_last) break; else *(dst_first++) = *src_first; } else { if (dst_first == dst_last) break; else *(dst_first++) = '%'; if (dst_first == dst_last) break; else *(dst_first++) = value_to_hex1(*src_first); if (dst_first == dst_last) break; else *(dst_first++) = value_to_hex0(*src_first); } ++src_first; } return dst_first; } template DestItr copy_escape_html(const SrcContainer& src, DestItr dst_first, DestItr dst_last) { return copy_escape_html(src.begin(), src.end(), dst_first, dst_last); } template typename DstContainer::iterator copy_escape_html(const SrcContainer& src, DstContainer& dst) { return copy_escape_html(src.begin(), src.end(), dst.begin(), dst.end()); } template typename DstContainer::iterator copy_escape_html(SrcItr src_first, SrcItr src_last, DstContainer& dst) { return copy_escape_html(src_first, src_last, dst.begin(), dst.end()); } // // Transform a sequence of bytes from/to a hex string. // template DestItr transform_from_hex(SrcItr src_first, SrcItr src_last, DestItr dst_first, DestItr dst_last) { auto dst_first_start = dst_first; while (src_first != src_last) { if (dst_first == dst_last) break; char high = hex_to_value_or_error(*src_first++); if (src_first == src_last) break; if (dst_first == dst_last || high == -1) return dst_first_start; char low = hex_to_value_or_error(*src_first++); if (dst_first == dst_last || low == -1) return dst_first_start; *dst_first++ = (high << 4) + low; } if (src_first != src_last) return dst_first_start; return dst_first; } template DestItr transform_from_hex(const SrcContainer& src, DestItr dst_first, DestItr dst_last) { return transform_from_hex(src.begin(), src.end(), dst_first, dst_last); } template typename DstContainer::iterator transform_from_hex(const SrcContainer& src, DstContainer& dst) { return transform_from_hex(src.begin(), src.end(), dst.begin(), dst.end()); } template typename DstContainer::iterator transform_from_hex(SrcItr src_first, SrcItr src_last, DstContainer& dst) { return transform_from_hex(src_first, src_last, dst.begin(), dst.end()); } template DestItr transform_to_hex(SrcItr src_first, SrcItr src_last, DestItr dst_first, DestItr dst_last) { if (std::distance(src_first, src_last) * 2 != std::distance(dst_first, dst_last)) throw internal_error("transform_to_hex() incorrect destination size"); while (src_first != src_last) { if (dst_first == dst_last) break; *(dst_first++) = value_to_hex1(*src_first); if (dst_first == dst_last) break; *(dst_first++) = value_to_hex0(*src_first); ++src_first; } return dst_first; } template DestItr transform_to_hex(const SrcContainer& src, DestItr dst_first, DestItr dst_last) { return transform_to_hex(src.begin(), src.end(), dst_first, dst_last); } template typename DstContainer::iterator transform_to_hex(const SrcContainer& src, DstContainer& dst) { return transform_to_hex(src.begin(), src.end(), dst.begin(), dst.end()); } template typename DstContainer::iterator transform_to_hex(SrcItr src_first, SrcItr src_last, DstContainer& dst) { return transform_to_hex(src_first, src_last, dst.begin(), dst.end()); } template std::string transform_to_hex_str(const Container& src) { return transform_to_hex_str(src.begin(), src.end()); } template std::string transform_to_hex_str(SrcItr src_first, SrcItr src_last) { std::string dest; dest.reserve(std::distance(src_first, src_last) * 2); while (src_first != src_last) { dest += value_to_hex1(*src_first); dest += value_to_hex0(*src_first); ++src_first; } return dest; } } // namespace torrent::utils #endif // LIBTORRENT_TORRENT_UTILS_STRING_MANIP_H libtorrent-0.16.17/src/torrent/utils/unordered_vector.h000066400000000000000000000031621522271512000232170ustar00rootroot00000000000000#ifndef LIBTORRENT_TORRENT_UTILS_UNORDERED_VECTOR_H #define LIBTORRENT_TORRENT_UTILS_UNORDERED_VECTOR_H #include namespace torrent::utils { template class unordered_vector : private std::vector<_Tp> { public: typedef std::vector<_Tp> Base; typedef typename Base::value_type value_type; typedef typename Base::pointer pointer; typedef typename Base::const_pointer const_pointer; typedef typename Base::reference reference; typedef typename Base::const_reference const_reference; typedef typename Base::size_type size_type; typedef typename Base::difference_type difference_type; typedef typename Base::allocator_type allocator_type; typedef typename Base::iterator iterator; typedef typename Base::reverse_iterator reverse_iterator; typedef typename Base::const_iterator const_iterator; typedef typename Base::const_reverse_iterator const_reverse_iterator; using Base::clear; using Base::empty; using Base::size; using Base::reserve; using Base::front; using Base::back; using Base::begin; using Base::end; using Base::rbegin; using Base::rend; using Base::push_back; using Base::pop_back; // Use the range erase function, the single element erase gets // overloaded. using Base::erase; iterator erase(iterator position); private: }; template typename unordered_vector<_Tp>::iterator unordered_vector<_Tp>::erase(iterator position) { // We don't need to check if position == end - 1 since we then copy // to the position we pop later. *position = std::move(Base::back()); Base::pop_back(); return position; } } #endif libtorrent-0.16.17/src/torrent/utils/uri_parser.cc000066400000000000000000000151471522271512000221650ustar00rootroot00000000000000#include "config.h" #include "torrent/utils/uri_parser.h" #include #include #include #include namespace torrent::utils { static inline bool is_unreserved_uri_char(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~'; } static inline bool is_valid_uri_query_char(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' || c == ':' || c == '&' || c == '=' || c == '/' || c == '%'; } static inline bool is_unreserved_uri_query_char(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' || c == ':' || c == '=' || c == '/' || c == '%'; } static inline bool is_not_unreserved_uri_char(char c) { return !is_unreserved_uri_char(c); } static inline bool is_not_valid_uri_query_char(char c) { return !is_valid_uri_query_char(c); } static inline bool is_not_unreserved_uri_query_char(char c) { return !is_unreserved_uri_query_char(c); } template static inline char value_to_hexchar(Value v) { v >>= pos * 4; v &= 0xf; if (v < 0xA) return '0' + v; else return 'A' + v - 0xA; } template static inline std::string::const_iterator uri_string_copy_until(std::string::const_iterator first, std::string::const_iterator last, std::string& result, Ftor check) { std::string::const_iterator next = std::find_if(first, last, check); result = std::string(first, next); return next; } static inline void uri_parse_throw_error(const char* error_msg, char invalid_char) { std::string error_str = std::string(error_msg); error_str += value_to_hexchar<1>(invalid_char); error_str += value_to_hexchar<0>(invalid_char); throw uri_error(error_str); } void uri_parse_str(std::string uri, uri_state& state) { if (state.state != uri_state::state_empty) throw uri_error("uri_state.state is not uri_state::state_empty"); state.uri.swap(uri); state.state = uri_state::state_invalid; std::string::const_iterator first = state.uri.begin(); std::string::const_iterator last = state.uri.end(); // Parse scheme: first = uri_string_copy_until(first, last, state.scheme, &is_not_unreserved_uri_char); if (first == last) goto uri_parse_success; if (*first++ != ':') uri_parse_throw_error("could not find ':' after scheme, found character 0x", *--first); // Parse resource: first = uri_string_copy_until(first, last, state.resource, &is_not_unreserved_uri_char); if (first == last) goto uri_parse_success; if (*first++ != '?') uri_parse_throw_error("could not find '?' after resource, found character 0x", *--first); // Parse query: first = uri_string_copy_until(first, last, state.query, &is_not_valid_uri_query_char); if (first == last) goto uri_parse_success; if (*first++ != '#') uri_parse_throw_error("could not find '#' after query, found character 0x", *--first); uri_parse_success: state.state = uri_state::state_valid; return; } void uri_parse_c_str(const char* uri, uri_state& state) { uri_parse_str(std::string(uri), state); } // * Letters (A-Z and a-z), numbers (0-9) and the characters // '.','-','~' and '_' are left as-is // * SPACE is encoded as '+' or "%20" // * All other characters are encoded as %HH hex representation with // any non-ASCII characters first encoded as UTF-8 (or other // specified encoding) void uri_parse_query_str(std::string query, uri_query_state& state) { if (state.state != uri_query_state::state_empty) throw uri_error("uri_query_state.state is not uri_query_state::state_empty"); state.query.swap(query); state.state = uri_state::state_invalid; std::string::const_iterator first = state.query.begin(); std::string::const_iterator last = state.query.end(); while (first != last) { std::string element; first = uri_string_copy_until(first, last, element, &is_not_unreserved_uri_query_char); if (first != last && *first++ != '&') { std::string invalid_hex; invalid_hex += value_to_hexchar<1>(*--first); invalid_hex += value_to_hexchar<0>(*first); throw uri_error("query element contains invalid character 0x" + invalid_hex); } state.elements.push_back(element); } state.state = uri_state::state_valid; } std::string uri_generate_scrape_url(std::string uri) { size_t delim_slash = uri.rfind('/'); if (delim_slash == std::string::npos || uri.find("/announce", delim_slash) != delim_slash) throw input_error("Tried to make scrape url from invalid uri."); return uri.replace(delim_slash, sizeof("/announce") - 1, "/scrape"); } bool uri_can_scrape(const std::string& uri) { // TODO: Replace with uri parsers above. size_t delim_slash = uri.rfind('/'); if (delim_slash == std::string::npos) return false; // TODO: This should be more robust. return uri.find("/announce", delim_slash) == delim_slash; } bool uri_has_query(const std::string& uri) { // TODO: Replace with uri parsers above. size_t delim_options = uri.rfind('?'); if (delim_options == std::string::npos) return false; return uri.find('/', delim_options) == std::string::npos; } int uri_detect_numeric(const std::string& uri) { CURLU *url = curl_url(); char *host; if (curl_url_set(url, CURLUPART_URL, uri.c_str(), 0) != CURLUE_OK) { curl_url_cleanup(url); return AF_UNSPEC; } if (curl_url_get(url, CURLUPART_HOST, &host, 0) != CURLUE_OK) { curl_url_cleanup(url); return AF_UNSPEC; } char buf[16]; int family = AF_UNSPEC; size_t host_len = strlen(host); if (host_len >= 2 && host[0] == '[' && host[host_len - 1] == ']') { host[host_len - 1] = '\0'; if (::inet_pton(AF_INET6, host + 1, buf) == 1) family = AF_INET6; } else { if (::inet_pton(AF_INET, host, buf) == 1) family = AF_INET; } curl_free(host); curl_url_cleanup(url); return family; } std::string uri_escape_html(const char* first, const char* last) { std::string result; result.reserve(std::distance(first, last) * 3); while (first != last) { if ((*first >= 'A' && *first <= 'Z') || (*first >= 'a' && *first <= 'z') || (*first >= '0' && *first <= '9') || *first == '-') { result.push_back(*first); } else { result.push_back('%'); result.push_back(value_to_hexchar<1>(*first)); result.push_back(value_to_hexchar<0>(*first)); } ++first; } return result; } } // namespace torrent::utils libtorrent-0.16.17/src/torrent/utils/uri_parser.h000066400000000000000000000037151522271512000220250ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_URI_PARSER_H #define LIBTORRENT_UTILS_URI_PARSER_H #include #include #include #include namespace torrent::utils { using uri_resource_list = std::vector; using uri_query_list = std::vector; struct uri_base_state { static constexpr int state_empty = 0; static constexpr int state_valid = 1; static constexpr int state_invalid = 2; int state{state_empty}; }; struct uri_state : uri_base_state { std::string uri; std::string scheme; std::string resource; std::string query; std::string fragment; }; struct uri_resource_state : public uri_base_state { std::string resource; uri_resource_list path; }; struct uri_query_state : public uri_base_state { std::string query; uri_query_list elements; }; class uri_error : public ::torrent::input_error { public: using input_error::input_error; }; void uri_parse_str(std::string uri, uri_state& state) LIBTORRENT_EXPORT; void uri_parse_c_str(const char* uri, uri_state& state) LIBTORRENT_EXPORT; void uri_parse_resource(std::string query, uri_query_state& state) LIBTORRENT_EXPORT; void uri_parse_resource_authority(std::string query, uri_query_state& state) LIBTORRENT_EXPORT; void uri_parse_resource_path(std::string query, uri_query_state& state) LIBTORRENT_EXPORT; void uri_parse_query_str(std::string query, uri_query_state& state) LIBTORRENT_EXPORT; void uri_parse_query_c_str(const char* query, uri_query_state& state) LIBTORRENT_EXPORT; std::string uri_generate_scrape_url(std::string uri) LIBTORRENT_EXPORT; bool uri_can_scrape(const std::string& uri) LIBTORRENT_EXPORT; bool uri_has_query(const std::string& uri) LIBTORRENT_EXPORT; int uri_detect_numeric(const std::string& uri) LIBTORRENT_EXPORT; std::string uri_escape_html(const char* first, const char* last) LIBTORRENT_EXPORT; } // namespace torrent::utils #endif libtorrent-0.16.17/src/tracker/000077500000000000000000000000001522271512000162715ustar00rootroot00000000000000libtorrent-0.16.17/src/tracker/thread_tracker.cc000066400000000000000000000071661522271512000215740ustar00rootroot00000000000000#include "config.h" #include "tracker/thread_tracker.h" #include #include "tracker/udp_router.h" #include "torrent/exceptions.h" #include "torrent/net/resolver.h" #include "torrent/runtime/network_config.h" #include "torrent/system/callbacks.h" #include "torrent/tracker/manager.h" #include "utils/instrumentation.h" namespace torrent { namespace tracker_thread { system::Thread* thread() { return ThreadTracker::thread_base(); } std::thread::id thread_id() { return ThreadTracker::thread_base()->thread_id(); } void callback(std::function&& fn) { ThreadTracker::thread_base()->callback(std::move(fn)); } void callback(system::callback_id& id, std::function&& fn) { ThreadTracker::thread_base()->callback(id, std::move(fn)); } void cancel_callback(system::callback_id& id) { ThreadTracker::thread_base()->cancel_callback(id); } void cancel_callback_and_wait(system::callback_id& id) { ThreadTracker::thread_base()->cancel_callback_and_wait(id); } tracker::Manager* manager() { return ThreadTracker::thread_tracker()->tracker_manager(); } } // namespace tracker ThreadTracker* ThreadTracker::m_thread_tracker{}; ThreadTracker::~ThreadTracker() = default; void ThreadTracker::create_thread() { assert(m_thread_tracker == nullptr); m_thread_tracker = new ThreadTracker(); m_thread_tracker->m_events_callback_id = system::make_callback_id(); m_thread_tracker->m_resolver = std::make_unique(); m_thread_tracker->m_tracker_manager = std::make_unique(); m_thread_tracker->m_udp_inet_router = std::make_unique(); m_thread_tracker->m_udp_inet6_router = std::make_unique(); } void ThreadTracker::destroy_thread() { try { delete m_thread_tracker; m_thread_tracker = nullptr; } catch (...) { m_thread_tracker = nullptr; } } ThreadTracker* ThreadTracker::thread_tracker() { return m_thread_tracker; } void ThreadTracker::init_thread() { m_state = STATE_INITIALIZED; m_instrumentation_index = INSTRUMENTATION_POLLING_DO_POLL_TRACKER - INSTRUMENTATION_POLLING_DO_POLL; } void ThreadTracker::init_thread_post_local() { m_thread_tracker->m_udp_inet_router->open(AF_INET); m_thread_tracker->m_udp_inet6_router->open(AF_INET6); runtime::network_config()->subscribe_to_changes(this, [this]() { callback(m_events_callback_id, [this]() { m_udp_inet_router->updated_network_config(AF_INET); m_udp_inet6_router->updated_network_config(AF_INET6); }); }); } void ThreadTracker::cleanup_thread() { runtime::network_config()->unsubscribe_from_changes(this); cancel_callback(m_events_callback_id); m_tracker_manager.reset(); m_udp_inet_router->close(); m_udp_inet6_router->close(); m_udp_inet_router.reset(); m_udp_inet6_router.reset(); } void ThreadTracker::call_events() { // lt_log_print_locked(torrent::LOG_THREAD_NOTICE, "Got ThreadTracker tick."); // TODO: Consider moving this into timer events instead. if ((m_flags & flag_do_shutdown)) { if ((m_flags & flag_did_shutdown)) throw internal_error("Already trigged shutdown."); m_flags |= flag_did_shutdown; throw shutdown_exception(); } // TODO: Do we need to process scheduled events here? process_callbacks(); } std::chrono::microseconds ThreadTracker::next_timeout() { return std::chrono::microseconds(10s); } } // namespace torrent libtorrent-0.16.17/src/tracker/thread_tracker.h000066400000000000000000000030611522271512000214240ustar00rootroot00000000000000#ifndef LIBTORRENT_THREAD_TRACKER_H #define LIBTORRENT_THREAD_TRACKER_H #include "torrent/common.h" #include "torrent/system/thread.h" namespace torrent { namespace tracker { class Manager; class UdpRouter; } // namespace tracker class LIBTORRENT_EXPORT ThreadTracker : public system::Thread { public: ~ThreadTracker() override; static void create_thread(); static void destroy_thread(); static ThreadTracker* thread_tracker(); static system::Thread* thread_base() { return thread_tracker(); } const char* name() const override { return "rtorrent-tracker"; } void init_thread() override; void init_thread_post_local() override; void cleanup_thread() override; // TODO: Make protected? tracker::Manager* tracker_manager() { return m_tracker_manager.get(); } auto udp_inet_router() { return m_udp_inet_router.get(); } auto udp_inet6_router() { return m_udp_inet6_router.get(); } protected: friend class Manager; void call_events() override; std::chrono::microseconds next_timeout() override; private: ThreadTracker() = default; static ThreadTracker* m_thread_tracker; system::callback_id m_events_callback_id; std::unique_ptr m_tracker_manager; std::unique_ptr m_udp_inet_router; std::unique_ptr m_udp_inet6_router; }; } // namespace torrent #endif // LIBTORRENT_THREAD_TRACKER_H libtorrent-0.16.17/src/tracker/tracker_controller.cc000066400000000000000000000433531522271512000225060ustar00rootroot00000000000000#include "config.h" #include "tracker/tracker_controller.h" #include "torrent/exceptions.h" #include "torrent/download_info.h" #include "torrent/utils/log.h" #include "torrent/utils/chrono.h" #include "tracker/tracker_list.h" #define LT_LOG_TRACKER_EVENTS(log_fmt, ...) \ lt_log_print_info(LOG_TRACKER_EVENTS, m_tracker_list->info(), "tracker_controller", log_fmt, __VA_ARGS__); namespace torrent { // End temp hacks... void TrackerController::update_timeout(uint32_t seconds_to_next) { if (!(m_flags & flag_active)) throw internal_error("TrackerController cannot set timeout when inactive."); if (seconds_to_next == 0) { this_thread::scheduler()->update_wait_for(&m_task_timeout, 0s); return; } this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_timeout, std::chrono::seconds(seconds_to_next)); } void TrackerController::update_timeout_next_to_request() { auto itr = m_tracker_list->find_next_to_request(m_tracker_list->begin()); if (itr == m_tracker_list->end()) return; std::chrono::seconds next_timeout{}; itr->lock_and_call_state([&next_timeout](auto& state) { next_timeout = state.activity_time_next(); }); if (next_timeout <= this_thread::cached_seconds()) update_timeout(0); else update_timeout((next_timeout - this_thread::cached_seconds()).count()); } inline tracker::TrackerState::event_enum TrackerController::current_send_event() const { switch ((m_flags & mask_send)) { case flag_send_start: return tracker::TrackerState::EVENT_STARTED; case flag_send_stop: return tracker::TrackerState::EVENT_STOPPED; case flag_send_completed: return tracker::TrackerState::EVENT_COMPLETED; case flag_send_update: default: return tracker::TrackerState::EVENT_NONE; } } TrackerController::TrackerController(TrackerList* trackers) : m_tracker_list(trackers) { m_task_timeout.slot() = [this] { do_timeout(); }; m_task_scrape.slot() = [this] { do_scrape(); }; } TrackerController::~TrackerController() { this_thread::scheduler()->erase(&m_task_timeout); this_thread::scheduler()->erase(&m_task_scrape); } bool TrackerController::is_timeout_queued() const { return m_task_timeout.is_scheduled(); } bool TrackerController::is_scrape_queued() const { return m_task_scrape.is_scheduled(); } int64_t TrackerController::next_timeout() const { return m_task_timeout.time_or_zero().count(); } int64_t TrackerController::next_scrape() const { return m_task_scrape.time_or_zero().count(); } // seconds_to_next_timeout/scrape() is for display purposes only, and returns 0 if the // timeout/scrape is unscheduled. uint32_t TrackerController::seconds_to_next_timeout() const { auto timeout = std::max(m_task_timeout.time_or_zero() - this_thread::cached_time(), std::chrono::microseconds{}); // LT_LOG_TRACKER_EVENTS("seconds_to_next_timeout() : %" PRId64, timeout.count()); // LT_LOG_TRACKER_EVENTS("seconds_to_next_timeout() : %" PRId64, utils::ceil_cast_seconds(timeout).count()); return utils::ceil_cast_seconds(timeout).count(); } uint32_t TrackerController::seconds_to_next_scrape() const { auto timeout = std::max(m_task_scrape.time_or_zero() - this_thread::cached_time(), std::chrono::microseconds{}); return utils::ceil_cast_seconds(timeout).count(); } void TrackerController::manual_request([[maybe_unused]] bool request_now) { if (!m_task_timeout.is_scheduled()) return; // Add functions to get the lowest timeout, etc... send_update_event(); } void TrackerController::scrape_request(uint32_t seconds_to_request) { if (seconds_to_request == 0) { this_thread::scheduler()->update_wait_for(&m_task_scrape, 0s); return; } this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_scrape, std::chrono::seconds(seconds_to_request)); } // The send_*_event() functions tries to ensure the relevant trackers // receive the event. // // When we just want more peers the start_requesting() function is // used. This is all independent of the regular updates sent to the // trackers. void TrackerController::send_start_event() { // This will now be 'lazy', rather than a definite event. We tell // the controller that a 'start' event should be sent, and it will // send it when the tracker controller get's enabled. // If the controller is already running, we insert this new event. // Return, or something, if already active and sending? if (m_flags & flag_send_start) { // Do we just return, or bork? At least we need to check to see // that there's something requesting 'start' event or fail hard. } m_flags &= ~mask_send; m_flags |= flag_send_start; if (!(m_flags & flag_active) || !m_tracker_list->has_usable()) { LT_LOG_TRACKER_EVENTS("sending start event : queued", 0); return; } // Start with requesting from the first tracker. Add timer to // catch when we don't get any response within the first few // seconds, at which point we go promiscious. // Do we use the old 'focus' thing?... Rather react on no reply, // go into promiscious. LT_LOG_TRACKER_EVENTS("sending start event : requesting", 0); close(); // std::any_of(m_tracker_list->begin(), m_tracker_list->end(), [&](tracker::Tracker& tracker) { // if (!tracker.is_usable()) // return false; // m_tracker_list->send_event(tracker, tracker::TrackerState::EVENT_STARTED); // return true; // }); bool found_usable = false; for (auto tracker : *m_tracker_list) { if (!tracker.is_usable()) continue; if (found_usable) { m_flags |= flag_promiscuous_mode; update_timeout(3); break; } m_tracker_list->send_event(tracker, tracker::TrackerState::EVENT_STARTED); found_usable = true; } } void TrackerController::send_stop_event() { if (m_flags & flag_send_stop) { // Do we just return, or bork? At least we need to check to see // that there's something requesting 'start' event or fail hard. } m_flags &= ~mask_send; if (!(m_flags & flag_active) || !m_tracker_list->has_usable()) { LT_LOG_TRACKER_EVENTS("sending stop event : skipped stopped event as no tracker needs it", 0); return; } m_flags |= flag_send_stop; LT_LOG_TRACKER_EVENTS("sending stop event : requesting", 0); close(); for (auto tracker : *m_tracker_list) { if (!tracker.is_in_use()) continue; m_tracker_list->send_event(tracker, tracker::TrackerState::EVENT_STOPPED); } // Timer... } void TrackerController::send_completed_event() { if (m_flags & flag_send_completed) { // Do we just return, or bork? At least we need to check to see // that there's something requesting 'start' event or fail hard. } m_flags &= ~mask_send; m_flags |= flag_send_completed; if (!(m_flags & flag_active) || !m_tracker_list->has_usable()) { LT_LOG_TRACKER_EVENTS("sending completed event : queued", 0); return; } LT_LOG_TRACKER_EVENTS("sending completed event : requesting", 0); close(); for (auto tracker : *m_tracker_list) { if (!tracker.is_in_use()) continue; m_tracker_list->send_event(tracker, tracker::TrackerState::EVENT_COMPLETED); } // Timer... } void TrackerController::send_update_event() { if (!(m_flags & flag_active) || !m_tracker_list->has_usable()) return; if ((m_flags & mask_send) && m_tracker_list->has_active()) return; // We can lose a state here... if (!(m_flags & mask_send)) m_flags |= flag_send_update; LT_LOG_TRACKER_EVENTS("sending update event : requesting", 0); for (auto tracker : *m_tracker_list) { if (!tracker.is_usable()) continue; m_tracker_list->send_event(tracker, tracker::TrackerState::EVENT_NONE); break; } } // Currently being used by send_event, fixme. void TrackerController::close() { m_flags &= ~(flag_requesting | flag_promiscuous_mode); this_thread::scheduler()->erase(&m_task_timeout); } void TrackerController::enable(int enable_flags) { if ((m_flags & flag_active)) return; // Clearing send stop here in case we cycle disable/enable too // fast. In the future do this based on flags passed. m_flags |= flag_active; m_flags &= ~flag_send_stop; if (!(enable_flags & enable_dont_reset_stats)) m_tracker_list->clear_stats(); LT_LOG_TRACKER_EVENTS("enabled : trackers:%zu", m_tracker_list->size()); // Adding of the tracker requests gets done after the caller has had // a chance to override the default behavior. update_timeout(0); } void TrackerController::disable() { if (!(m_flags & flag_active)) return; // Disable other flags?... m_flags &= ~(flag_active | flag_requesting | flag_promiscuous_mode); this_thread::scheduler()->erase(&m_task_timeout); LT_LOG_TRACKER_EVENTS("disabled : trackers:%zu", m_tracker_list->size()); } void TrackerController::start_requesting() { if ((m_flags & flag_requesting)) return; m_flags |= flag_requesting; if ((m_flags & flag_active)) update_timeout(0); LT_LOG_TRACKER_EVENTS("started requesting", 0); } void TrackerController::stop_requesting() { if (!(m_flags & flag_requesting)) return; m_flags &= ~flag_requesting; LT_LOG_TRACKER_EVENTS("stopped requesting", 0); } uint32_t tracker_next_timeout(const tracker::Tracker& tracker, int controller_flags) { if ((controller_flags & TrackerController::flag_requesting)) return tracker_next_timeout_promiscuous(tracker); auto state = tracker.state(); if (tracker.is_requesting_not_scrape() || !tracker.is_usable()) return ~uint32_t(); if ((controller_flags & TrackerController::flag_promiscuous_mode)) return utils::next_timeout_seconds(state.activity_time_next_minimum(), this_thread::cached_seconds()); if ((controller_flags & TrackerController::flag_send_update)) return tracker_next_timeout_update(tracker); return utils::next_timeout_seconds(state.activity_time_next(), this_thread::cached_seconds()); } uint32_t tracker_next_timeout_update(const tracker::Tracker& tracker) { // TODO: Rewrite to be in tracker thread or atomic tracker state. if (tracker.is_requesting_not_scrape() || !tracker.is_usable()) return ~uint32_t(); // Make sure we don't request _too_ often, check last activity. // int32_t last_activity = this_thread::cached_seconds().count() - tracker.activity_time_last(); return utils::next_timeout_seconds(tracker.state().activity_time_next_minimum(), this_thread::cached_seconds()); } uint32_t tracker_next_timeout_promiscuous(const tracker::Tracker& tracker) { // TODO: Rewrite to be in tracker thread or atomic tracker state. auto tracker_state = tracker.state(); // TODO: Get from tracker_state. if (tracker.is_requesting_not_scrape() || !tracker.is_usable()) return ~uint32_t(); std::chrono::seconds interval{}; if (tracker_state.failed_counter() != 0) { if (tracker_state.failed_time_last() == 0s) throw internal_error("tracker_next_timeout_promiscuous(...) called but tracker_state.failed_counter() != 0 and tracker_state.failed_time_last() == 0."); interval = tracker_state.failed_time_next() - tracker_state.failed_time_last(); } else { interval = tracker_state.normal_interval(); } auto min_interval = std::max(tracker_state.min_interval(), 300s); auto use_interval = std::min(interval, min_interval); auto since_last = this_thread::cached_seconds() - tracker_state.activity_time_last(); auto result = std::max(use_interval - since_last, 0s); lt_log_print(LOG_TRACKER_EVENTS, "tracker_next_timeout_promiscuous: min_interval:%" PRId64 " use_interval:%" PRId64 " since_last:%" PRId64 " failed_counter:%d result:%" PRId64, (int64_t)min_interval.count(), (int64_t)use_interval.count(), (int64_t)since_last.count(), tracker_state.failed_counter(), (int64_t)result.count()); return result.count(); } static TrackerList::iterator tracker_find_preferred(TrackerList::iterator first, TrackerList::iterator last, uint32_t* next_timeout) { auto preferred = last; uint32_t preferred_time_last = ~uint32_t(); for (; first != last; first++) { uint32_t tracker_timeout = tracker_next_timeout_promiscuous(*first); if (tracker_timeout != 0) { *next_timeout = std::min(tracker_timeout, *next_timeout); continue; } uint64_t activity_time_last; first->lock_and_call_state([&](const tracker::TrackerState& state) { activity_time_last = state.activity_time_last().count(); }); if (activity_time_last < preferred_time_last) { preferred = first; preferred_time_last = activity_time_last; } } return preferred; } void TrackerController::do_timeout() { this_thread::scheduler()->erase(&m_task_timeout); if (!(m_flags & flag_active) || !m_tracker_list->has_usable()) return; tracker::TrackerState::event_enum send_event = current_send_event(); if ((m_flags & (flag_promiscuous_mode | flag_requesting))) { uint32_t next_timeout = ~uint32_t(); auto itr = m_tracker_list->begin(); while (itr != m_tracker_list->end()) { uint32_t group = itr->group(); if (m_tracker_list->has_active_not_scrape_in_group(group)) { itr = m_tracker_list->end_group(group); continue; } auto group_end = m_tracker_list->end_group(itr->group()); auto preferred = itr; // TODO: Rewrite to be in tracker thread or atomic tracker state. auto tracker_state = itr->state(); if (!itr->is_usable() || tracker_state.failed_counter()) { // The selected tracker in the group is either disabled or not // reachable, try the others to find a new one to use. preferred = tracker_find_preferred(preferred, group_end, &next_timeout); } else { uint32_t tracker_timeout = tracker_next_timeout_promiscuous(*preferred); if (tracker_timeout != 0) { next_timeout = std::min(tracker_timeout, next_timeout); preferred = group_end; } } if (preferred != group_end) m_tracker_list->send_event(*preferred, send_event); itr = group_end; } if (next_timeout != ~uint32_t()) update_timeout(next_timeout); // TODO: Send for start/completed also? } else { auto itr = m_tracker_list->find_next_to_request(m_tracker_list->begin()); if (itr == m_tracker_list->end()) return; std::chrono::seconds next_timeout{}; itr->lock_and_call_state([&next_timeout](auto& state) { next_timeout = state.activity_time_next(); }); if (next_timeout <= this_thread::cached_seconds()) m_tracker_list->send_event(*itr, send_event); else update_timeout((next_timeout - this_thread::cached_seconds()).count()); } if (m_slot_timeout) m_slot_timeout(); } void TrackerController::do_scrape() { auto itr = m_tracker_list->begin(); while (itr != m_tracker_list->end()) { uint32_t group = itr->group(); if (m_tracker_list->has_active_in_group(group)) { itr = m_tracker_list->end_group(group); continue; } auto group_end = m_tracker_list->end_group(itr->group()); while (itr != group_end) { if (itr->is_scrapable() && itr->is_usable()) { m_tracker_list->send_scrape(*itr); break; } itr++; } itr = group_end; } } uint32_t TrackerController::receive_success(const tracker::Tracker& tracker, TrackerController::address_list* l) { if (!(m_flags & flag_active)) return m_slot_success(l); // if () { m_flags &= ~(mask_send | flag_promiscuous_mode | flag_failure_mode); // } // If we still have active trackers, skip the timeout. // Calculate the next timeout according to a list of in-use // trackers, with the first timeout as the interval. if ((m_flags & flag_requesting)) update_timeout(30); else if (!m_tracker_list->has_active()) { std::chrono::seconds normal_interval; tracker.lock_and_call_state([&](const tracker::TrackerState& state) { normal_interval = state.normal_interval(); }); // TODO: Instead find the lowest timeout, correct timeout? update_timeout(normal_interval.count()); } return m_slot_success(l); } void TrackerController::receive_failure(const tracker::Tracker& tracker, const std::string& msg) { if (!(m_flags & flag_active)) { m_slot_failure(msg); return; } // if (tracker == nullptr) { // m_slot_failure(msg); // return; // } // TODO: This makes no sense, it should take into consideration how long it was since last success. // int32_t failed_counter; // int32_t success_counter; // tracker.lock_and_call_state([&](const tracker::TrackerState& state) { // failed_counter = state.failed_counter(); // success_counter = state.success_counter(); // }); // if (failed_counter == 1 && success_counter > 0) // m_flags |= flag_failure_mode; m_flags |= flag_failure_mode; do_timeout(); m_slot_failure(msg); } void TrackerController::receive_scrape([[maybe_unused]] const tracker::Tracker& tracker) const { if (!(m_flags & flag_active)) { return; } } uint32_t TrackerController::receive_new_peers(address_list* l) { return m_slot_success(l); } void TrackerController::receive_tracker_enabled(const tracker::Tracker& tb) { // TODO: This won't be needed if we rely only on Tracker::m_enable, // rather than a virtual function. if (!m_tracker_list->has_usable()) return; if ((m_flags & flag_active)) { if (!m_task_timeout.is_scheduled() && !m_tracker_list->has_active()) { // TODO: Figure out the proper timeout to use here based on when the // tracker last connected, etc. update_timeout(0); } } if (m_slot_tracker_enabled) m_slot_tracker_enabled(tb); } void TrackerController::receive_tracker_disabled(const tracker::Tracker& tb) { if ((m_flags & flag_active) && !m_task_timeout.is_scheduled()) update_timeout(0); if (m_slot_tracker_disabled) m_slot_tracker_disabled(tb); } } // namespace torrent libtorrent-0.16.17/src/tracker/tracker_controller.h000066400000000000000000000107741522271512000223510ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_CONTROLLER_H #define LIBTORRENT_TRACKER_CONTROLLER_H #include #include #include "torrent/tracker/tracker_state.h" #include "torrent/utils/scheduler.h" // TODO: Remove all unused functions and slots, move to src/tracker. Then add a // TrackerControllerWrapper that download and api uses. namespace torrent { class TrackerController { public: using address_list = AddressList; using slot_void = std::function; using slot_string = std::function; using slot_address_list = std::function; using slot_tracker = std::function; static constexpr int flag_send_update = 0x1; static constexpr int flag_send_completed = 0x2; static constexpr int flag_send_start = 0x4; static constexpr int flag_send_stop = 0x8; static constexpr int flag_active = 0x10; static constexpr int flag_requesting = 0x20; static constexpr int flag_failure_mode = 0x40; static constexpr int flag_promiscuous_mode = 0x80; static constexpr int mask_send = flag_send_update | flag_send_start | flag_send_stop | flag_send_completed; static constexpr int enable_dont_reset_stats = 0x1; TrackerController(TrackerList* trackers); ~TrackerController(); int flags() const { return m_flags; } bool is_active() const { return m_flags & flag_active; } bool is_requesting() const { return m_flags & flag_requesting; } bool is_failure_mode() const { return m_flags & flag_failure_mode; } bool is_promiscuous_mode() const { return m_flags & flag_promiscuous_mode; } bool is_timeout_queued() const; bool is_scrape_queued() const; TrackerList* tracker_list() { return m_tracker_list; } TrackerList* tracker_list() const { return m_tracker_list; } int64_t next_timeout() const; int64_t next_scrape() const; uint32_t seconds_to_next_timeout() const; uint32_t seconds_to_next_scrape() const; void manual_request(bool request_now); void scrape_request(uint32_t seconds_to_request); void send_start_event(); void send_stop_event(); void send_completed_event(); void send_update_event(); void close(); void enable(int enable_flags = 0); void disable(); void start_requesting(); void stop_requesting(); uint32_t receive_success(const tracker::Tracker& tracker, address_list* l); void receive_failure(const tracker::Tracker& tracker, const std::string& msg); void receive_scrape(const tracker::Tracker& tracker) const; uint32_t receive_new_peers(address_list* l); void receive_tracker_enabled(const tracker::Tracker& tb); void receive_tracker_disabled(const tracker::Tracker& tb); slot_void& slot_timeout() { return m_slot_timeout; } slot_address_list& slot_success() { return m_slot_success; } slot_string& slot_failure() { return m_slot_failure; } slot_tracker& slot_tracker_enabled() { return m_slot_tracker_enabled; } slot_tracker& slot_tracker_disabled() { return m_slot_tracker_disabled; } private: TrackerController(const TrackerController&) = delete; TrackerController& operator=(const TrackerController&) = delete; void do_timeout(); void do_scrape(); void update_timeout(uint32_t seconds_to_next); void update_timeout_next_to_request(); inline tracker::TrackerState::event_enum current_send_event() const; int m_flags{0}; TrackerList* m_tracker_list; slot_void m_slot_timeout; slot_address_list m_slot_success; slot_string m_slot_failure; slot_tracker m_slot_tracker_enabled; slot_tracker m_slot_tracker_disabled; utils::SchedulerEntry m_task_timeout; utils::SchedulerEntry m_task_scrape; }; uint32_t tracker_next_timeout(const tracker::Tracker& tracker, int controller_flags); uint32_t tracker_next_timeout_update(const tracker::Tracker& tracker); uint32_t tracker_next_timeout_promiscuous(const tracker::Tracker& tracker); } // namespace torrent #endif libtorrent-0.16.17/src/tracker/tracker_dht.cc000066400000000000000000000131171522271512000210750ustar00rootroot00000000000000#include "config.h" #include "tracker/tracker_dht.h" #include #include "dht/dht_router.h" #include "manager.h" #include "torrent/exceptions.h" #include "torrent/runtime/network_manager.h" #include "torrent/tracker/dht_controller.h" #include "torrent/system/callbacks.h" #include "torrent/utils/log.h" #include "torrent/utils/option_strings.h" #define LT_LOG(log_fmt, ...) \ lt_log_print_hash(LOG_TRACKER_REQUESTS, info().info_hash, "tracker_dht", "%p : " log_fmt, static_cast(this), __VA_ARGS__); namespace torrent { TrackerDht::TrackerDht(const TrackerInfo& info, int flags) : TrackerWorker(info, flags) { if (!runtime::network_manager()->dht_controller()->is_valid()) throw internal_error("Trying to add DHT tracker with no DHT manager."); m_delay_clear_state.slot() = [this] { m_dht_state = state_idle; update_requesting_state(); }; } tracker_enum TrackerDht::type() const { return TRACKER_DHT; } void TrackerDht::send_event(tracker::TrackerParams params, tracker::TrackerState::event_enum new_state) { assert(!m_weak_tracker.expired()); LT_LOG("sending event : state:%s dht_state:%s replied:%d contacted:%d", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, new_state), states[m_dht_state], m_replied.load(), m_contacted.load()); close(); lock_and_set_latest_event(new_state); if (new_state == tracker::TrackerState::EVENT_STOPPED) return; if (!runtime::network_manager()->is_dht_active()) return receive_failed("DHT is not enabled."); m_params = params; m_dht_state = state_searching; update_requesting_state(); runtime::network_manager()->dht_controller()->announce(info().info_hash, m_weak_tracker); state().set_normal_interval(20 * 60s); state().set_min_interval(0s); } void TrackerDht::send_scrape([[maybe_unused]] tracker::TrackerParams params) { throw internal_error("Tracker type DHT does not support scrape."); } void TrackerDht::close() { assert(std::this_thread::get_id() == tracker_thread::thread_id()); assert(!m_weak_tracker.expired()); LT_LOG("closing event : dht_state:%s replied:%d contacted:%d", states[m_dht_state], m_replied.load(), m_contacted.load()); this_thread::scheduler()->erase(&m_delay_clear_state); runtime::network_manager()->dht_controller()->cancel_announce(info().info_hash, m_weak_tracker); // TODO: Moved check from send_event(), verify if this is correct. // if (m_dht_state != state_idle) // throw internal_error("TrackerDht::send_state cancel_announce did not cancel announce."); remove_events(); update_requesting_state(); } void TrackerDht::cleanup() { assert(std::this_thread::get_id() == tracker_thread::thread_id()); assert(!m_weak_tracker.expired()); this_thread::scheduler()->erase(&m_delay_clear_state); // This still queues a cancel request, however 'this' is never accessed. // // Even if we accidentally create a new TrackerDht with the same address, the cancel request will // not do anything harmful. runtime::network_manager()->dht_controller()->cancel_announce(info().info_hash, m_weak_tracker); auto guard = lock_guard(); state().m_flags |= tracker::TrackerState::flag_deleted; state().m_flags &= ~tracker::TrackerState::flag_requesting; state().m_flags &= ~tracker::TrackerState::flag_starting_request; } // TODO: We don't really need to track announcing state in Tracker? void TrackerDht::set_dht_announce_state() { assert(std::this_thread::get_id() == tracker_thread::thread_id()); this_thread::scheduler()->wait_for_ceil_seconds(&m_delay_clear_state, 2min); m_dht_state = state_announcing; update_requesting_state(); } void TrackerDht::receive_peers(AddressList&& address_list) { LT_LOG("received peers : dht_state:%s replied:%d contacted:%d size:%" PRIu32, states[m_dht_state], m_replied.load(), m_contacted.load(), address_list.size()); m_slot_new_peers(std::move(address_list)); } void TrackerDht::receive_success() { LT_LOG("received success : dht_state:%s replied:%d contacted:%d", states[m_dht_state], m_replied.load(), m_contacted.load()); m_dht_state = state_idle; update_requesting_state(); m_slot_success({}); } void TrackerDht::receive_failed(const char* msg) { LT_LOG("received failure : dht_state:%s replied:%d contacted:%d msg:%s", states[m_dht_state], m_replied.load(), m_contacted.load(), msg); m_dht_state = state_idle; update_requesting_state(); m_slot_failure(msg); } void TrackerDht::receive_progress(int replied, int contacted) { LT_LOG("received progress : dht_state:%s replied:%d contacted:%d", states[m_dht_state], replied, contacted); m_replied = replied; m_contacted = contacted; } void TrackerDht::add_event(std::weak_ptr weak_tracker, std::function&& event) { auto tracker = weak_tracker.lock(); if (tracker == nullptr) return; tracker_thread::callback(tracker->callback_id(), [weak_tracker, event = std::move(event)]() mutable { auto tracker = weak_tracker.lock(); if (tracker == nullptr) return; event(tracker.get()); }); } void TrackerDht::update_requesting_state() { assert(std::this_thread::get_id() == tracker_thread::thread_id()); auto guard = lock_guard(); if (m_dht_state != state_announcing) this_thread::scheduler()->erase(&m_delay_clear_state); state().m_flags &= ~tracker::TrackerState::flag_starting_request; if (m_dht_state != state_idle) state().m_flags |= tracker::TrackerState::flag_requesting; else state().m_flags &= ~tracker::TrackerState::flag_requesting; } } // namespace torrent libtorrent-0.16.17/src/tracker/tracker_dht.h000066400000000000000000000047501522271512000207420ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_TRACKER_DHT_H #define LIBTORRENT_TRACKER_TRACKER_DHT_H #include #include #include "torrent/object.h" #include "torrent/utils/scheduler.h" #include "tracker/tracker_worker.h" namespace torrent { namespace dht { class DhtAnnounce; } // Until we make throttle and rate thread-safe, we keep dht router in main thread. // // Both implemenation will require that interacting with dht router is thread safe, so we still need // to lock dht router. class TrackerDht : public TrackerWorker { public: TrackerDht(const TrackerInfo& info, int flags = 0); enum state_type { state_idle, state_searching, state_announcing, }; static constexpr std::array states{ "Idle", "Searching", "Announcing" }; tracker_enum type() const override; void send_event(tracker::TrackerParams params, tracker::TrackerState::event_enum new_state) override; void send_scrape(tracker::TrackerParams params) override; void close() override; void set_weak_tracker(std::weak_ptr weak_tracker); state_type dht_state() const; void set_dht_announce_state(); int replied() const; int contacted() const; bool has_peers_unsafe() const; void receive_peers(AddressList&& address_list); void receive_success(); void receive_failed(const char* msg); void receive_progress(int replied, int contacted); protected: friend class torrent::dht::DhtAnnounce; static void add_event(std::weak_ptr weak_tracker, std::function&& event); private: void cleanup() override; void update_requesting_state(); std::weak_ptr m_weak_tracker; tracker::TrackerParams m_params; std::atomic m_dht_state{state_idle}; std::atomic m_replied; std::atomic m_contacted; utils::SchedulerEntry m_delay_clear_state; }; inline void TrackerDht::set_weak_tracker(std::weak_ptr weak_tracker) { m_weak_tracker = std::move(weak_tracker); } inline TrackerDht::state_type TrackerDht::dht_state() const { return m_dht_state; } inline int TrackerDht::replied() const { return m_replied; } inline int TrackerDht::contacted() const { return m_contacted; } } // namespace torrent #endif libtorrent-0.16.17/src/tracker/tracker_http.cc000066400000000000000000000463161522271512000213040ustar00rootroot00000000000000#include "config.h" #include "tracker/tracker_http.h" #include #include #include #include "net/address_list.h" #include "torrent/exceptions.h" #include "torrent/object_stream.h" #include "torrent/net/http_stack.h" #include "torrent/net/socket_address.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/runtime.h" #include "torrent/utils/log.h" #include "torrent/utils/option_strings.h" #include "torrent/utils/string_manip.h" #include "torrent/utils/uri_parser.h" #include "manager.h" #define LT_LOG(log_fmt, ...) \ lt_log_print_hash(LOG_TRACKER_REQUESTS, info().info_hash, "tracker_http", "%p : " log_fmt, static_cast(this), __VA_ARGS__); #define LT_LOG_DUMP(log_dump_data, log_dump_size, log_fmt, ...) \ lt_log_print_hash_dump(LOG_TRACKER_DUMP, log_dump_data, log_dump_size, info().info_hash, \ "tracker_http", "%p : " log_fmt, static_cast(this), __VA_ARGS__); namespace torrent { TrackerHttp::TrackerHttp(const TrackerInfo& raw_info, int flags) : TrackerWorker(raw_info, utils::uri_can_scrape(raw_info.url) ? (flags | tracker::TrackerState::flag_scrapable) : flags) { m_get.reset(raw_info.url, nullptr); m_delay_scrape.slot() = [this] { delayed_send_scrape(); }; auto [hostname, port] = net::parse_uri_host_port(raw_info.url); if (hostname.empty()) return; auto [sa_inet, sa_inet6] = try_lookup_numeric(hostname, AF_UNSPEC); if (sa_inet) m_hostname_family = AF_INET; else if (sa_inet6) m_hostname_family = AF_INET6; else m_hostname_family = AF_UNSPEC; } tracker_enum TrackerHttp::type() const { return TRACKER_HTTP; } void TrackerHttp::send_event(tracker::TrackerParams params, tracker::TrackerState::event_enum new_state) { close_directly(); this_thread::scheduler()->erase(&m_delay_scrape); lock_and_set_latest_event(new_state); auto [current_family, next_family] = request_families(); m_params = params; m_current_family = current_family; m_next_family = next_family; m_last_success = false; m_last_error_message = ""; if (m_current_family == AF_UNSPEC) { LT_LOG("send event : no valid address family available : state:%s url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, new_state), info().url.c_str()); return receive_failed("No valid address family available."); } send_event_unsafe(new_state); } void TrackerHttp::send_scrape(tracker::TrackerParams params) { if (m_requested_scrape || runtime::is_shutting_down()) return; m_params = params; m_requested_scrape = true; if (m_data != nullptr) { LT_LOG("scrape requested, but tracker is busy : url:%s", info().url.c_str()); return; } LT_LOG("scrape requested : url:%s", info().url.c_str()); this_thread::scheduler()->update_wait_for_ceil_seconds(&m_delay_scrape, 10s); } void TrackerHttp::close() { LT_LOG("closing event : state:%s url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state().latest_event()), info().url.c_str()); this_thread::scheduler()->erase(&m_delay_scrape); m_requested_scrape = false; close_directly(); update_requesting_state(); } void TrackerHttp::close_directly() { if (m_data == nullptr) { // LT_LOG("closing directly (already closed) : state:%s url:%s", // option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state().latest_event()), info().url.c_str()); remove_events(); return; } remove_events(); m_get.close_and_cancel_callbacks(this_thread::thread()); m_data.reset(); } void TrackerHttp::cleanup() { LT_LOG("cleaning up : state:%s url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state().latest_event()), info().url.c_str()); close_directly(); this_thread::scheduler()->erase(&m_delay_scrape); auto guard = lock_guard(); state().m_flags |= tracker::TrackerState::flag_deleted; state().m_flags &= ~tracker::TrackerState::flag_requesting; state().m_flags &= ~tracker::TrackerState::flag_starting_request; } void TrackerHttp::update_requesting_state() { auto guard = lock_guard(); state().m_flags &= ~tracker::TrackerState::flag_starting_request; if (m_data != nullptr) state().m_flags |= tracker::TrackerState::flag_requesting; else state().m_flags &= ~tracker::TrackerState::flag_requesting; } void TrackerHttp::send_event_unsafe(tracker::TrackerState::event_enum state) { // TODO: When retrying next protocol, recheck network_config (do in caller) auto request_url = request_announce_url(state, m_current_family); m_data = std::make_unique(); update_requesting_state(); m_get.try_wait_for_close(); m_get.reset(request_url, m_data); m_get.use_family(m_current_family); m_get.set_max_file_size(1 << 20); m_get.set_redirect_only_http_https(); m_get.add_done_slot(tracker_thread::thread(), [this] { receive_done(); }); m_get.add_failed_slot(tracker_thread::thread(), [this](const auto& str) { receive_signal_failed(str); }); LT_LOG("sending event : state:%s family:%s url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state), family_str(m_current_family), info().url.c_str()); LT_LOG_DUMP(request_url.c_str(), request_url.size(), "sending event : state:%s family:%s up_adj:%" PRIu64 " completed_adj:%" PRIu64 " left_adj:%" PRIu64, option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state), family_str(m_current_family), m_params.uploaded_adjusted, m_params.completed_adjusted, m_params.download_left); net_thread::http_stack()->start_get(m_get); } void TrackerHttp::send_scrape_unsafe() { auto request_url = request_prefix(utils::uri_generate_scrape_url(info().url)).str(); m_data = std::make_unique(); update_requesting_state(); m_get.try_wait_for_close(); m_get.reset(request_url, m_data); m_get.use_family(m_current_family); m_get.add_done_slot(tracker_thread::thread(), [this] { receive_done(); }); m_get.add_failed_slot(tracker_thread::thread(), [this](const auto& str) { receive_signal_failed(str); }); LT_LOG("sending scrape : family:%s url:%s", family_str(m_current_family), info().url.c_str()); LT_LOG_DUMP(request_url.c_str(), request_url.size(), "tracker scrape", 0); net_thread::http_stack()->start_get(m_get); } bool TrackerHttp::send_next_family(bool scrape) { m_current_family = m_next_family; m_next_family = AF_UNSPEC; if (m_current_family == AF_UNSPEC) return false; auto state = lock_and_latest_event(); // TODO: If stopped state, don't bother if the other protocol hasn't been confirmed to work. (add vars to track this) if ((m_current_family == AF_INET && runtime::network_config()->is_block_ipv4()) || (m_current_family == AF_INET6 && runtime::network_config()->is_block_ipv6())) return false; if (scrape) send_scrape_unsafe(); else send_event_unsafe(state); return true; } // We delay scrape for 10 seconds after any tracker activity to ensure all callbacks are process // before starting. void TrackerHttp::delayed_send_scrape() { if (m_data != nullptr) throw internal_error("TrackerHttp::delayed_send_scrape() called while busy"); // close_directly(); if (runtime::is_shutting_down()) return; lock_and_set_latest_event(tracker::TrackerState::EVENT_SCRAPE); auto [current_family, next_family] = request_families(); m_current_family = current_family; m_next_family = next_family; m_last_success = false; m_last_error_message = ""; if (m_current_family == AF_UNSPEC) { LT_LOG("send scrape : no valid address family available : url:%s", info().url.c_str()); return; } send_scrape_unsafe(); } std::stringstream TrackerHttp::request_prefix(const std::string& url) { std::stringstream stream; stream.imbue(std::locale::classic()); stream << url << (utils::uri_has_query(url) ? '&' : '?') << "info_hash=" << utils::copy_escape_html_str(info().info_hash); return stream; } std::string TrackerHttp::request_announce_url(tracker::TrackerState::event_enum state, int family) { auto tracker_id = this->tracker_id_safe(); auto local_id = utils::copy_escape_html_str(info().local_id); auto s = request_prefix(info().url); s << "&peer_id=" << local_id << "&compact=1"; if (info().key) s << "&key=" << std::hex << std::setw(8) << std::setfill('0') << info().key << std::dec; if (!tracker_id.empty()) s << "&trackerid=" << utils::copy_escape_html_str(tracker_id); auto local_inet_address = runtime::network_config()->local_inet_address_or_null(); auto local_inet6_address = runtime::network_config()->local_inet6_address_or_null(); if (local_inet_address != nullptr) { if (family == AF_INET6) s << "&ip=" << sa_addr_str(local_inet_address.get()); s << "&ipv4=" << sa_addr_str(local_inet_address.get()); } if (local_inet6_address != nullptr) { if (family == AF_INET) s << "&ip=" << sa_addr_str(local_inet6_address.get()); s << "&ipv6=" << sa_addr_str(local_inet6_address.get()); } if (m_params.numwant >= 0 && state != tracker::TrackerState::EVENT_STOPPED) s << "&numwant=" << m_params.numwant; s << "&port=" << runtime::listen_port() << "&uploaded=" << m_params.uploaded_adjusted << "&downloaded=" << m_params.completed_adjusted << "&left=" << m_params.download_left; switch (state) { case tracker::TrackerState::EVENT_STARTED: s << "&event=started"; break; case tracker::TrackerState::EVENT_STOPPED: s << "&event=stopped"; break; case tracker::TrackerState::EVENT_COMPLETED: s << "&event=completed"; break; default: break; } return s.str(); } std::tuple TrackerHttp::request_families() { bool is_block_ipv4 = runtime::network_config()->is_block_ipv4(); bool is_block_ipv6 = runtime::network_config()->is_block_ipv6(); bool is_prefer_ipv6 = runtime::network_config()->is_prefer_ipv6(); if (m_hostname_family == AF_INET) { if (is_block_ipv4) return {AF_UNSPEC, AF_UNSPEC}; return {AF_INET, AF_UNSPEC}; } if (m_hostname_family == AF_INET6) { if (is_block_ipv6) return {AF_UNSPEC, AF_UNSPEC}; return {AF_INET6, AF_UNSPEC}; } // If both IPv4 and IPv6 are blocked, we cannot send the request. // // TODO: Properly handle this case without throwing an error. if (is_block_ipv4 && is_block_ipv6) return {AF_UNSPEC, AF_UNSPEC}; // TODO: When sending 'stopped', only send to confirmed working protocol. // TODO: Don't constantly retry family that keeps failing. if (is_block_ipv4) return {AF_INET6, AF_UNSPEC}; else if (is_block_ipv6) return {AF_INET, AF_UNSPEC}; else if (is_prefer_ipv6) return {AF_INET6, AF_INET}; else return {AF_INET, AF_INET6}; } void TrackerHttp::update_tracker_id_unsafe(const std::string& id) { if (id.empty()) return; set_tracker_id_unsafe(id); } void TrackerHttp::receive_done() { if (m_data == nullptr) throw internal_error("TrackerHttp::receive_done() called on an invalid object"); LT_LOG("received reply : state:%s url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state().latest_event()), info().url.c_str()); if (lt_log_is_valid(LOG_TRACKER_DUMP)) { std::string dump = m_data->str(); LT_LOG_DUMP(dump.c_str(), dump.size(), "tracker reply", 0); } Object b; *m_data >> b; if (m_data->fail()) { auto dump = utils::sanitize_string_with_tags(m_data->str()); if (dump.empty()) return receive_failed("Could not parse bencoded data, empty reply"); return receive_failed("Could not parse bencoded data: " + dump.substr(0,99)); } if (!b.is_map()) return receive_failed("Root not a bencoded map"); if (b.has_key("failure reason")) { if (state().latest_event() != tracker::TrackerState::EVENT_SCRAPE) process_failure(b); return receive_failed("Failure reason \"" + (b.get_key("failure reason").is_string() ? b.get_key_string("failure reason") : std::string("failure reason not a string")) + "\""); } if (b.has_key_string("warning message")) { auto& msg = b.get_key("warning message").as_string(); if (msg.find("unregistered") != std::string::npos || msg.find("not registered") != std::string::npos || msg.find("torrent cannot be found") != std::string::npos) { LT_LOG("tracker warning treated as failure : url:%s : %s", info().url.c_str(), msg.c_str()); return receive_failed("Tracker warning: " + msg); } LT_LOG("tracker warning : url:%s : %s", info().url.c_str(), msg.c_str()); } if (state().latest_event() == tracker::TrackerState::EVENT_SCRAPE) { m_requested_scrape = false; process_scrape(b); return; } process_success(b); if (m_requested_scrape && m_data == nullptr) this_thread::scheduler()->update_wait_for_ceil_seconds(&m_delay_scrape, 10s); } void TrackerHttp::receive_signal_failed(const std::string& msg) { return receive_failed(msg); } void TrackerHttp::receive_failed(const std::string& msg) { if (m_data == nullptr) { LT_LOG("received failure with no data : state:%s url:%s : %s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state().latest_event()), info().url.c_str(), msg.c_str()); update_requesting_state(); m_slot_failure(msg); return; } if (lt_log_is_valid(LOG_TRACKER_DUMP)) { std::string dump = m_data->str(); LT_LOG_DUMP(dump.c_str(), dump.size(), "received failure", 0); } close_directly(); if (state().latest_event() == tracker::TrackerState::EVENT_SCRAPE) { if (send_next_family(true)) { update_requesting_state(); return; } LT_LOG("received scrape failure : url:%s : %s", info().url.c_str(), msg.c_str()); m_requested_scrape = false; m_slot_scrape_failure(msg); return; } if (send_next_family()) { m_last_success = false; m_last_error_message = msg; return; } // Update gets called in send_next_family(). update_requesting_state(); if (m_last_success) { LT_LOG("received failure : previous family succeeded : url:%s : %s", info().url.c_str(), msg.c_str()); m_slot_success(AddressList()); } else if (!m_last_error_message.empty()) { LT_LOG("received failure : previous family also failed : url:%s : %s /// %s", info().url.c_str(), msg.c_str(), m_last_error_message.c_str()); m_slot_failure(msg + " /// " + m_last_error_message); } else { LT_LOG("received failure : url:%s : %s", info().url.c_str(), msg.c_str()); m_slot_failure(msg); } if (m_requested_scrape && m_data == nullptr) this_thread::scheduler()->wait_for_ceil_seconds(&m_delay_scrape, 10s); } void TrackerHttp::process_failure(const Object& object) { auto guard = lock_guard(); if (object.has_key_string("tracker id")) update_tracker_id_unsafe(object.get_key_string("tracker id")); if (object.has_key_value("interval")) state().set_normal_interval(object.get_key_value("interval") * 1s); if (object.has_key_value("min interval")) state().set_min_interval(object.get_key_value("min interval") * 1s); if (object.has_key_value("complete") && object.has_key_value("incomplete")) { state().m_scrape_complete = std::max(object.get_key_value("complete"), 0); state().m_scrape_incomplete = std::max(object.get_key_value("incomplete"), 0); state().add_scrape_request(this_thread::cached_seconds()); } if (object.has_key_value("downloaded")) state().m_scrape_downloaded = std::max(object.get_key_value("downloaded"), 0); } void TrackerHttp::process_success(const Object& object) { { auto guard = lock_guard(); if (object.has_key_string("tracker id")) update_tracker_id_unsafe(object.get_key_string("tracker id")); if (object.has_key_value("interval")) state().set_normal_interval(object.get_key_value("interval") * 1s); else state().set_normal_interval(tracker::TrackerState::default_normal_interval); if (object.has_key_value("min interval")) state().set_min_interval(object.get_key_value("min interval") * 1s); else state().set_min_interval(tracker::TrackerState::default_min_interval); if (object.has_key_value("complete") && object.has_key_value("incomplete")) { state().m_scrape_complete = std::max(object.get_key_value("complete"), 0); state().m_scrape_incomplete = std::max(object.get_key_value("incomplete"), 0); state().add_scrape_request(this_thread::cached_seconds()); } if (object.has_key_value("downloaded")) state().m_scrape_downloaded = std::max(object.get_key_value("downloaded"), 0); LT_LOG("tracker reply : state:%s url:%s : interval:%" PRId64 " min_interval:%" PRId64 " complete:%u incomplete:%u downloaded:%u", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state().latest_event()), info().url.c_str(), (int64_t)state().normal_interval().count(), (int64_t)state().min_interval().count(), state().scrape_complete(), state().scrape_incomplete(), state().scrape_downloaded()); } AddressList address_list; bool has_peer_fields = false; if (object.has_key("peers")) { try { // Due to some trackers sending the wrong type when no peers are // available, don't bork on it. if (object.get_key("peers").is_string()) address_list.parse_address_compact(object.get_key_string("peers")); else if (object.get_key("peers").is_list()) address_list.parse_address_normal(object.get_key_list("peers")); } catch (const bencode_error& e) { return receive_failed(e.what()); } has_peer_fields = true; } if (object.has_key_string("peers6")) { address_list.parse_address_compact_ipv6(object.get_key_string("peers6")); has_peer_fields = true; } if (!has_peer_fields && state().latest_event() != tracker::TrackerState::EVENT_STOPPED) return receive_failed("No peers returned"); close_directly(); if (send_next_family()) { m_last_success = true; m_last_error_message = ""; m_slot_new_peers(std::move(address_list)); return; } update_requesting_state(); m_slot_success(std::move(address_list)); } void TrackerHttp::process_scrape(const Object& object) { if (!object.has_key_map("files")) return receive_failed("Tracker scrape does not have files entry."); // Add better validation here... const Object& files = object.get_key("files"); if (!files.has_key_map(info().info_hash.str())) return receive_failed("Tracker scrape replay did not contain infohash."); const Object& stats = files.get_key(info().info_hash.str()); { auto guard = lock_guard(); if (stats.has_key_value("complete")) state().m_scrape_complete = std::max(stats.get_key_value("complete"), 0); if (stats.has_key_value("incomplete")) state().m_scrape_incomplete = std::max(stats.get_key_value("incomplete"), 0); if (stats.has_key_value("downloaded")) state().m_scrape_downloaded = std::max(stats.get_key_value("downloaded"), 0); LT_LOG("tracker scrape for %zu torrents : complete:%u incomplete:%u downloaded:%u", files.as_map().size(), state().m_scrape_complete, state().m_scrape_incomplete, state().m_scrape_downloaded); } close_directly(); update_requesting_state(); m_slot_scrape_success(); } } // namespace torrent libtorrent-0.16.17/src/tracker/tracker_http.h000066400000000000000000000042531522271512000211400ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_TRACKER_HTTP_H #define LIBTORRENT_TRACKER_TRACKER_HTTP_H #include #include #include #include "tracker/tracker_worker.h" #include "torrent/net/http_get.h" #include "torrent/tracker/tracker_state.h" #include "torrent/utils/scheduler.h" namespace torrent { class Http; class TrackerHttp : public TrackerWorker { public: static constexpr uint32_t http_timeout = 60; TrackerHttp(const TrackerInfo& info, int flags = 0); tracker_enum type() const override; void send_event(tracker::TrackerParams params, tracker::TrackerState::event_enum new_state) override; void send_scrape(tracker::TrackerParams params) override; void close() override; private: void close_directly(); void cleanup() override; void update_requesting_state(); void send_event_unsafe(tracker::TrackerState::event_enum state); void send_scrape_unsafe(); bool send_next_family(bool scrape = false); void delayed_send_scrape(); std::stringstream request_prefix(const std::string& url); std::string request_announce_url(tracker::TrackerState::event_enum state, int family); std::tuple request_families(); void update_tracker_id_unsafe(const std::string& id); void receive_done(); void receive_signal_failed(const std::string& msg); void receive_failed(const std::string& msg); void process_failure(const Object& object); void process_success(const Object& object); void process_scrape(const Object& object); tracker::TrackerParams m_params; net::HttpGet m_get; std::shared_ptr m_data; int m_hostname_family{}; int m_current_family{}; int m_next_family{}; bool m_last_success{}; std::string m_last_error_message; bool m_requested_scrape{}; utils::SchedulerEntry m_delay_scrape; }; } // namespace torrent #endif libtorrent-0.16.17/src/tracker/tracker_list.cc000066400000000000000000000364771522271512000213070ustar00rootroot00000000000000#include "config.h" #include "tracker/tracker_list.h" #include #include #include #include "net/address_list.h" #include "torrent/exceptions.h" #include "torrent/download_info.h" #include "torrent/runtime/network_manager.h" #include "torrent/tracker/manager.h" #include "torrent/tracker/tracker.h" #include "torrent/utils/log.h" #include "torrent/utils/option_strings.h" #include "tracker/thread_tracker.h" #include "tracker/tracker_dht.h" #include "tracker/tracker_http.h" #include "tracker/tracker_udp.h" #define LT_LOG(log_fmt, ...) \ lt_log_print_hash(LOG_TRACKER_EVENTS, info()->info_hash(), "tracker_list", log_fmt, __VA_ARGS__); namespace torrent { TrackerList::TrackerList() : m_state(DownloadInfo::STOPPED), m_lifetime_keeper(std::make_shared(0)) { } TrackerList::~TrackerList() { m_slot_success = nullptr; m_slot_failed = nullptr; m_slot_scrape_success = nullptr; m_slot_scrape_failed = nullptr; m_slot_tracker_enabled = nullptr; m_slot_tracker_disabled = nullptr; } bool TrackerList::has_active() const { return std::any_of(begin(), end(), [](auto& tracker) { return tracker.is_requesting(); }); } bool TrackerList::has_active_not_dht() const { return std::any_of(begin(), end(), [](auto& tracker) { return tracker.is_requesting_not_dht(); }); } bool TrackerList::has_active_not_dht_scrape_disownable() const { return std::any_of(begin(), end(), [](auto& tracker) { return tracker.is_requesting_not_dht_scrape_disownable(); }); } bool TrackerList::has_active_not_scrape() const { return std::any_of(begin(), end(), [](auto& tracker) { return tracker.is_requesting_not_scrape(); }); } bool TrackerList::has_active_in_group(uint32_t group) const { return std::any_of(begin_group(group), end_group(group), [](auto& tracker) { return tracker.is_requesting(); }); } bool TrackerList::has_active_not_scrape_in_group(uint32_t group) const { return std::any_of(begin_group(group), end_group(group), [](auto& tracker) { return tracker.is_requesting_not_scrape(); }); } bool TrackerList::has_usable() const { return std::any_of(begin(), end(), [](auto& tracker) { return tracker.is_usable(); }); } void TrackerList::clear() { // Keeper must be deleted before we remove and possibly delete any trackers or the events will // still execute. m_lifetime_keeper.reset(); // Make sure the tracker_list is cleared before the trackers are deleted. auto trackers = std::move(*static_cast(this)); tracker_thread::manager()->delete_trackers(std::move(trackers)); } void TrackerList::clear_stats() { std::for_each(begin(), end(), std::mem_fn(&tracker::Tracker::clear_stats)); } void TrackerList::send_event(tracker::Tracker& tracker, tracker::TrackerState::event_enum event) { if (!tracker.is_valid()) throw internal_error("TrackerList::send_event(...) tracker is invalid."); if (find(tracker) == end()) throw internal_error("TrackerList::send_event(...) tracker not found."); if (!tracker.is_usable() || event == tracker::TrackerState::EVENT_SCRAPE) return; if (tracker.is_requesting()) { if (tracker.state().latest_event() == event) return; if (tracker.state().latest_event() != tracker::TrackerState::EVENT_SCRAPE) { if (event == tracker::TrackerState::EVENT_NONE) return; } } LT_LOG("sending %s : requester:%p url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, event), tracker.get_worker(), tracker.url().c_str()); tracker::TrackerParams params; params.numwant = m_numwant; params.uploaded_adjusted = m_info->uploaded_adjusted(); params.completed_adjusted = m_info->completed_adjusted(); params.download_left = m_info->slot_left()(); tracker_thread::manager()->send_event(tracker, params, event); } void TrackerList::send_scrape(tracker::Tracker& tracker) { if (!tracker.is_valid()) throw internal_error("TrackerList::send_scrape(...) tracker is invalid."); if (find(tracker) == end()) throw internal_error("TrackerList::send_scrape(...) tracker not found."); if (tracker.is_requesting() || !tracker.is_usable()) return; if (!tracker.is_scrapable()) return; auto timeout = std::chrono::seconds(tracker.state().scrape_time_last()) + 600s; if (timeout > this_thread::cached_time()) return; LT_LOG("sending scrape : requester:%p url:%s", tracker.get_worker(), tracker.url().c_str()); tracker::TrackerParams params; params.numwant = m_numwant; params.uploaded_adjusted = m_info->uploaded_adjusted(); params.completed_adjusted = m_info->completed_adjusted(); params.download_left = m_info->slot_left()(); tracker_thread::manager()->send_scrape(tracker, params); } TrackerList::iterator TrackerList::insert(const tracker::Tracker& tracker) { auto itr = base_type::insert(end_group(tracker.group()), tracker); // These slots are called from within the worker thread, so we need to // use proper signal passing to the main thread. // // The weak_ptr should always return a valid shared_ptr, as the tracker thread will hold a // shared_ptr. auto weak_ptr = itr->get_weak_ptr(); auto worker = itr->get_worker(); auto lifetime_keeper = std::weak_ptr(m_lifetime_keeper); // TODO: enable/disable slots are called from main-thread, and should not use events? Or rather, // remove them from tracker::Tracker. // TODO: Remove use of 'this'. worker->m_slot_enabled = [this, lifetime_keeper, weak_ptr]() { tracker_thread::manager()->add_event(weak_ptr, lifetime_keeper, [this](auto& tracker) { if (!m_slot_tracker_enabled) return; m_slot_tracker_enabled(std::move(tracker)); }); }; worker->m_slot_disabled = [this, lifetime_keeper, weak_ptr]() { tracker_thread::manager()->add_event(weak_ptr, lifetime_keeper, [this](auto& tracker) { if (!m_slot_tracker_disabled) return; m_slot_tracker_disabled(std::move(tracker)); }); }; worker->m_slot_success = [this, lifetime_keeper, weak_ptr](AddressList&& l) { tracker_thread::manager()->add_event_or_update(weak_ptr, lifetime_keeper, [this, l = std::move(l)](auto& tracker) { if (!m_slot_success) return tracker_thread::manager()->update_tracker(std::move(tracker)); receive_success(std::move(tracker), const_cast(&l)); }); }; worker->m_slot_failure = [this, lifetime_keeper, weak_ptr](const std::string& msg) { tracker_thread::manager()->add_event_or_update(weak_ptr, lifetime_keeper, [this, msg](auto& tracker) { if (!m_slot_failed) return tracker_thread::manager()->update_tracker(std::move(tracker)); receive_failed(std::move(tracker), msg); }); }; worker->m_slot_scrape_success = [this, lifetime_keeper, weak_ptr]() { tracker_thread::manager()->add_event(weak_ptr, lifetime_keeper, [this](auto& tracker) { if (!m_slot_scrape_success) return; receive_scrape_success(std::move(tracker)); }); }; worker->m_slot_scrape_failure = [this, lifetime_keeper, weak_ptr](const std::string& msg) { tracker_thread::manager()->add_event(weak_ptr, lifetime_keeper, [this, msg](auto& tracker) { if (!m_slot_scrape_failed) return; receive_scrape_failed(std::move(tracker), msg); }); }; worker->m_slot_new_peers = [this, lifetime_keeper, weak_ptr](AddressList&& l) { main_thread::thread()->callback([this, lifetime_keeper, l = std::move(l)]() { auto tl_keeper = lifetime_keeper.lock(); if (!tl_keeper || !m_slot_new_peers) return; receive_new_peers(const_cast(&l)); }); }; LT_LOG("added tracker : requester:%p group:%u url:%s", worker, itr->group(), itr->url().c_str()); if (m_slot_tracker_enabled) m_slot_tracker_enabled(tracker); return itr; } // TODO: Use proper flags for insert options. void TrackerList::insert_url(unsigned int group, const std::string& url, bool extra_tracker) { int flags = tracker::TrackerState::flag_enabled; if (extra_tracker) flags |= tracker::TrackerState::flag_extra_tracker; TrackerInfo tracker_info; tracker_info.info_hash = m_info->hash(); tracker_info.obfuscated_hash = m_info->hash_obfuscated(); tracker_info.local_id = m_info->local_id(); tracker_info.url = url; tracker_info.group = group; tracker_info.key = m_key; std::shared_ptr worker; if (std::strncmp("http://", url.c_str(), 7) == 0 || std::strncmp("https://", url.c_str(), 8) == 0) { worker = std::make_shared(tracker_info, flags); } else if (std::strncmp("udp://", url.c_str(), 6) == 0) { worker = std::make_shared(tracker_info, flags); } else if (std::strncmp("dht://", url.c_str(), 6) == 0 && runtime::network_manager()->is_dht_valid()) { // TODO: Don't check TrackerDht::is_allowed(). auto dht_tracker = std::make_shared(tracker_info, flags); dht_tracker->set_weak_tracker(dht_tracker); worker = std::move(dht_tracker); } else { LT_LOG("could find matching tracker protocol : url:%s", url.c_str()); if (extra_tracker) throw torrent::input_error("could find matching tracker protocol (url:" + url + ")"); return; } insert(tracker::Tracker(std::move(worker))); } TrackerList::iterator TrackerList::find_url(const std::string& url) { return std::find_if(begin(), end(), [&url](const auto& tracker) { return tracker.url() == url; }); } TrackerList::iterator TrackerList::find_next_to_request(iterator itr) { auto preferred = itr = std::find_if(itr, end(), std::mem_fn(&tracker::Tracker::can_request_state)); if (preferred == end()) return end(); // TODO: Get only the required state values. auto preferred_state = (*preferred).state(); if (preferred_state.failed_counter() == 0) return preferred; while (++itr != end()) { if (!(*itr).can_request_state()) continue; auto itr_state = (*itr).state(); if (itr_state.failed_counter() != 0) { if (preferred_state.failed_counter() == 0) continue; if (itr_state.failed_time_next() < preferred_state.activity_time_next()) { preferred = itr; preferred_state = (*itr).state(); } continue; } if (itr_state.activity_time_next() < preferred_state.activity_time_next()) { preferred = itr; preferred_state = (*itr).state(); } } return preferred; } TrackerList::iterator TrackerList::begin_group(unsigned int group) { return std::find_if(begin(), end(), [group](const tracker::Tracker& t) { return group <= t.group(); }); } TrackerList::const_iterator TrackerList::begin_group(unsigned int group) const { return std::find_if(begin(), end(), [group](const tracker::Tracker& t) { return group <= t.group(); }); } TrackerList::size_type TrackerList::size_group() const { return !empty() ? back().group() + 1 : 0; } void TrackerList::cycle_group(unsigned int group) { auto first = begin_group(group); if (first == end() || first->group() != group) return; auto last = first; while (last != end() && last->group() == group) ++last; std::rotate(first, std::next(first), last); } TrackerList::iterator TrackerList::promote(iterator itr) { auto first = begin_group((*itr).group()); if (first == end()) throw internal_error("torrent::TrackerList::promote(...) Could not find beginning of group."); std::iter_swap(first, itr); return first; } void TrackerList::randomize_group_entries() { static std::random_device rd; static std::mt19937 rng(rd()); auto itr = begin(); while (itr != end()) { auto tmp = end_group((*itr).group()); std::shuffle(itr, tmp, rng); itr = tmp; } } void TrackerList::receive_success(tracker::Tracker tracker, AddressList* l) { assert(std::this_thread::get_id() == main_thread::thread_id()); LT_LOG("received %zu peers : requester:%p group:%u url:%s", l->size(), tracker.get_worker(), tracker.group(), tracker.url().c_str()); auto itr = find(tracker); if (itr == end()) throw internal_error("TrackerList::receive_success(...) called but the iterator is invalid."); // Promote the tracker to the front of the group since it was // successfull. promote(itr); l->sort_and_unique(); // TODO: Update staate in TrackerWorker. { auto guard = tracker.get_worker()->lock_guard(); tracker.get_worker()->state().add_success_request(this_thread::cached_seconds()); tracker.get_worker()->state().m_latest_sum_peers = l->size(); } if (!m_slot_success) return; auto new_peers = m_slot_success(tracker, l); { auto guard = tracker.get_worker()->lock_guard(); tracker.get_worker()->state().m_latest_new_peers = new_peers + tracker.get_worker()->state().m_latest_new_peers_delta; tracker.get_worker()->state().m_latest_new_peers_delta = 0; } } void TrackerList::receive_failed(tracker::Tracker tracker, const std::string& msg) { assert(std::this_thread::get_id() == main_thread::thread_id()); LT_LOG("received failure : requester:%p group:%u url:%s msg:'%s'", tracker.get_worker(), tracker.group(), tracker.url().c_str(), msg.c_str()); if (find(tracker) == end()) throw internal_error("TrackerList::receive_failed(...) called but the iterator is invalid."); { auto guard = tracker.get_worker()->lock_guard(); tracker.get_worker()->state().add_failed_request(this_thread::cached_seconds()); tracker.get_worker()->state().m_latest_new_peers_delta = 0; } if (m_slot_failed) m_slot_failed(tracker, msg); } void TrackerList::receive_scrape_success(tracker::Tracker tracker) { assert(std::this_thread::get_id() == main_thread::thread_id()); LT_LOG("received scrape success : requester:%p group:%u url:%s", tracker.get_worker(), tracker.group(), tracker.url().c_str()); if (find(tracker) == end()) throw internal_error("TrackerList::receive_scrape_success(...) called but the iterator is invalid."); { auto guard = tracker.get_worker()->lock_guard(); tracker.get_worker()->state().add_scrape_request(this_thread::cached_seconds()); } if (m_slot_scrape_success) m_slot_scrape_success(tracker); } void TrackerList::receive_scrape_failed(tracker::Tracker tracker, const std::string& msg) { assert(std::this_thread::get_id() == main_thread::thread_id()); LT_LOG("received scrape failure : requester:%p group:%u url:%s msg:'%s'", tracker.get_worker(), tracker.group(), tracker.url().c_str(), msg.c_str()); if (find(tracker) == end()) throw internal_error("TrackerList::receive_scrape_failed(...) called but the iterator is invalid."); if (m_slot_scrape_failed) m_slot_scrape_failed(tracker, msg); } void TrackerList::receive_new_peers(AddressList* l) { assert(std::this_thread::get_id() == main_thread::thread_id()); // TODO: Use weak_ptr argument, then do proper logging if the tracker is still valid. Move more of // this logic to TrackerWorker. LT_LOG("received new peers : size:%zu", l->size()); l->sort_and_unique(); m_slot_new_peers(l); // auto tracker = tracker::Tracker::from_weak_ptr(weak_ptr); // if (tracker.is_valid()) { // auto guard = tracker.get_worker()->lock_guard(); // tracker.get_worker()->state().m_latest_new_peers_delta += new_peers; // } } } // namespace torrent libtorrent-0.16.17/src/tracker/tracker_list.h000066400000000000000000000130021522271512000211240ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_LIST_H #define LIBTORRENT_TRACKER_LIST_H // TODO: Reduce includes, don't inline everything. #include #include #include #include #include #include struct TestTrackerListWrapper; namespace torrent { // The tracker list will contain a list of tracker, divided into subgroups. // // Each group must be randomized before we start. // // When starting the tracker request, always start from the beginning and iterate if the request // failed. // // Upon request success move the tracker to the beginning of the subgroup and start from the // beginning of the whole list. class LIBTORRENT_EXPORT TrackerList : private std::vector { public: using base_type = std::vector; using base_type::value_type; using base_type::iterator; using base_type::const_iterator; using base_type::reverse_iterator; using base_type::const_reverse_iterator; using base_type::size; using base_type::empty; using base_type::begin; using base_type::end; using base_type::rbegin; using base_type::rend; using base_type::front; using base_type::back; using base_type::at; TrackerList(); ~TrackerList(); bool has_active() const; bool has_active_not_dht() const; bool has_active_not_dht_scrape_disownable() const; bool has_active_not_scrape() const; bool has_active_in_group(uint32_t group) const; bool has_active_not_scrape_in_group(uint32_t group) const; bool has_usable() const; void clear(); void clear_stats(); iterator insert(const tracker::Tracker& tracker); void insert_url(unsigned int group, const std::string& url, bool extra_tracker = false); // TODO: Move these to controller / tracker. // TODO: CHECK PEX CAUSES PEER CONNECTS. void send_event(tracker::Tracker& tracker, tracker::TrackerState::event_enum new_event); void send_scrape(tracker::Tracker& tracker); const DownloadInfo* info() const { return m_info; } int state() const { return m_state; } uint32_t key() const { return m_key; } int32_t numwant() const { return m_numwant; } void set_numwant(int32_t n) { m_numwant = n; } iterator find(const tracker::Tracker& tb) { return std::find(begin(), end(), tb); } iterator find_url(const std::string& url); iterator find_next_to_request(iterator itr); iterator begin_group(unsigned int group); const_iterator begin_group(unsigned int group) const; iterator end_group(unsigned int group) { return begin_group(group + 1); } const_iterator end_group(unsigned int group) const { return begin_group(group + 1); } size_type size_group() const; void cycle_group(unsigned int group); iterator promote(iterator itr); void randomize_group_entries(); // TODO: Make protected. void receive_success(tracker::Tracker tracker, AddressList* l); void receive_failed(tracker::Tracker tracker, const std::string& msg); void receive_scrape_success(tracker::Tracker tracker); void receive_scrape_failed(tracker::Tracker tracker, const std::string& msg); void receive_new_peers(AddressList* l); auto& slot_success() { return m_slot_success; } auto& slot_failure() { return m_slot_failed; } auto& slot_scrape_success() { return m_slot_scrape_success; } auto& slot_scrape_failure() { return m_slot_scrape_failed; } auto& slot_new_peers() { return m_slot_new_peers; } auto& slot_tracker_enabled() { return m_slot_tracker_enabled; } auto& slot_tracker_disabled() { return m_slot_tracker_disabled; } protected: friend class DownloadWrapper; friend struct ::TestTrackerListWrapper; void set_info(DownloadInfo* info) { m_info = info; } void set_state(int s) { m_state = s; } void set_key(uint32_t k) { m_key = k; } private: TrackerList(const TrackerList&) = delete; TrackerList& operator=(const TrackerList&) = delete; DownloadInfo* m_info{}; int m_state{}; // TODO: Key should be part of download static info. uint32_t m_key{0}; std::atomic m_numwant{-1}; std::function m_slot_success; std::function m_slot_failed; std::function m_slot_scrape_success; std::function m_slot_scrape_failed; std::function m_slot_new_peers; std::function m_slot_tracker_enabled; std::function m_slot_tracker_disabled; std::shared_ptr m_lifetime_keeper; }; } // namespace torrent #endif libtorrent-0.16.17/src/tracker/tracker_udp.cc000066400000000000000000000344721522271512000211150ustar00rootroot00000000000000#include "config.h" #include "tracker/tracker_udp.h" #include "net/address_list.h" #include "torrent/net/resolver.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/runtime.h" #include "torrent/system/system.h" #include "torrent/utils/log.h" #include "torrent/utils/option_strings.h" #include "tracker/thread_tracker.h" #include "tracker/udp_router.h" #define LT_LOG(log_fmt, ...) \ lt_log_print_hash(LOG_TRACKER_REQUESTS, info().info_hash, "tracker_udp", "%p : " log_fmt, static_cast(this), __VA_ARGS__); #define LT_LOG_DUMP(log_dump_data, log_dump_size, log_fmt, ...) \ lt_log_print_hash_dump(LOG_TRACKER_DUMP, log_dump_data, log_dump_size, info().info_hash, \ "tracker_udp", "%p : " log_fmt, static_cast(this), __VA_ARGS__); namespace torrent::tracker { TrackerUdp::TrackerUdp(const TrackerInfo& raw_info, int flags) : TrackerWorker(raw_info, flags) { if (info().key == 0) throw internal_error("TrackerUdp cannot be created with key 0."); auto [hostname, port] = net::parse_uri_host_port(info().url); m_hostname = std::move(hostname); m_port = port; } tracker_enum TrackerUdp::type() const { return TRACKER_UDP; } void TrackerUdp::send_event(tracker::TrackerParams params, tracker::TrackerState::event_enum new_state) { LT_LOG("sending event : state:%s url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, new_state), info().url.c_str()); close_directly(); lock_and_set_latest_event(new_state); if (m_hostname.empty()) return handle_setup_error("cannot send tracker event, hostname is empty"); if (m_port == 0) return handle_setup_error("cannot send tracker event, port is 0"); m_params = params; m_send_state = new_state; connect_family(AF_INET); connect_family(AF_INET6); LT_LOG("started announce : state:%s url:%s inet_tx:%u inet6_tx:%u", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, new_state), info().url.c_str(), m_inet_state.transaction_id, m_inet6_state.transaction_id); if (m_inet_state.transaction_id == 0 && m_inet6_state.transaction_id == 0) return handle_setup_error("cannot send tracker event, no available network protocol(s)"); update_requesting_state(); } void TrackerUdp::send_scrape([[maybe_unused]] tracker::TrackerParams params) { throw internal_error("Tracker type UDP does not support scrape."); } void TrackerUdp::close() { LT_LOG("closing event : state:%s url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state().latest_event()), info().url.c_str()); close_directly(); update_requesting_state(); } void TrackerUdp::close_directly() { if (m_inet_state.transaction_id != 0) ThreadTracker::thread_tracker()->udp_inet_router()->disconnect(m_inet_state.transaction_id); if (m_inet6_state.transaction_id != 0) ThreadTracker::thread_tracker()->udp_inet6_router()->disconnect(m_inet6_state.transaction_id); m_inet_state = family_state{}; m_inet6_state = family_state{}; } void TrackerUdp::cleanup() { LT_LOG("cleaning up : state:%s url:%s", option_to_c_str_or_throw(OPTION_TRACKER_EVENT, state().latest_event()), info().url.c_str()); close_directly(); remove_events(); auto guard = lock_guard(); state().m_flags |= tracker::TrackerState::flag_deleted; state().m_flags &= ~tracker::TrackerState::flag_requesting; state().m_flags &= ~tracker::TrackerState::flag_starting_request; } void TrackerUdp::reset_family_with_error(int family, const std::string& msg) { // Don't clear packet_sent to ensure disownable flag remains set. switch (family) { case AF_INET: if (m_inet_state.transaction_id == 0) return; // TODO: Should we throw? m_inet_state.transaction_id = 0; m_inet_state.connection_id = 0; break; case AF_INET6: if (m_inet6_state.transaction_id == 0) return; // TODO: Should we throw? m_inet6_state.transaction_id = 0; m_inet6_state.connection_id = 0; break; default: throw internal_error("TrackerUdp::reset_family_with_error() called with invalid address family."); } if (m_inet_state.transaction_id != 0 || m_inet6_state.transaction_id != 0) return; // TODO: Save message. LT_LOG("closing with error : hostname:%s port:%u : %s", m_hostname.c_str(), m_port, msg.c_str()); remove_events(); update_requesting_state(); m_slot_failure(msg); } void TrackerUdp::update_requesting_state() { auto guard = lock_guard(); state().m_flags &= ~tracker::TrackerState::flag_starting_request; if (m_inet_state.transaction_id != 0 || m_inet6_state.transaction_id != 0) state().m_flags |= tracker::TrackerState::flag_requesting; else state().m_flags &= ~tracker::TrackerState::flag_requesting; if (state().latest_event() == tracker::TrackerState::EVENT_STOPPED && (m_inet_state.packet_sent || m_inet6_state.packet_sent)) state().m_flags |= tracker::TrackerState::flag_disownable; else state().m_flags &= ~tracker::TrackerState::flag_disownable; } tracker::UdpRouter* TrackerUdp::router_for_family(int family) { switch (family) { case AF_INET: return ThreadTracker::thread_tracker()->udp_inet_router(); case AF_INET6: return ThreadTracker::thread_tracker()->udp_inet6_router(); default: throw internal_error("TrackerUdp::router_for_family() called with invalid address family."); } } TrackerUdp::family_state& TrackerUdp::state_for_family(int family) { switch (family) { case AF_INET: return m_inet_state; case AF_INET6: return m_inet6_state; default: throw internal_error("TrackerUdp::state_for_family() called with invalid address family."); } } void TrackerUdp::connect_family(int family) { if (state_for_family(family).transaction_id != 0) throw internal_error("TrackerUdp::connect_family() called but transaction id is not 0."); auto params = tracker::UdpRouter::connection_params{ [this, family](uint32_t id, auto& buffer) { prepare_connect(family, id, buffer); }, [this, family](uint32_t id, auto& buffer) { return process_connect(family, id, buffer); }, [this, family](uint32_t id, int errno_err, int gai_err) { handle_udp_error(family, id, errno_err, gai_err); }, [this, family](uint32_t id) { state_for_family(family).transaction_id = id; }, nullptr, }; router_for_family(family)->connect(m_hostname, m_port, params); } int TrackerUdp::process_header(int family, uint32_t action, buffer_type& buffer) { if (buffer.size_end() < 8) return 0; uint32_t read_action = buffer.read_32(); uint32_t transaction_id = buffer.read_32(); if (transaction_id != state_for_family(family).transaction_id) return 0; if (read_action == 3) { process_error(family, transaction_id, buffer); return -1; } if (read_action != action) return 0; return 1; } void TrackerUdp::prepare_connect(int family, uint32_t id, buffer_type& buffer) { if (state_for_family(family).transaction_id == 0) throw internal_error("TrackerUdp::prepare_connect() called but transaction id is 0."); if (id != state_for_family(family).transaction_id) throw internal_error("TrackerUdp::prepare_connect() called with wrong transaction id."); buffer.write_64(magic_connection_id); buffer.write_32(0); buffer.write_32(id); LT_LOG_DUMP(buffer.begin(), buffer.size_end(), "prepare connect : family:%s id:%" PRIx32, family_str(family), id); } bool TrackerUdp::process_connect(int family, uint32_t id, buffer_type& buffer) { LT_LOG_DUMP(buffer.begin(), buffer.size_end(), "process connect : family:%s id:%" PRIx32, family_str(family), id); switch (process_header(family, 0, buffer)) { case -1: return false; case 0: return true; }; if (buffer.size_end() < 16) return handle_parse_error(family, id, "invalid connect response size"); state_for_family(family).connection_id = buffer.read_64(); if (state_for_family(family).connection_id == 0) return handle_parse_error(family, id, "connection id is 0"); auto params = tracker::UdpRouter::connection_params{ [this, family](uint32_t id, auto& buffer) { prepare_announce(family, id, buffer); }, [this, family](uint32_t id, auto& buffer) { return process_announce(family, id, buffer); }, [this, family](uint32_t id, int errno_err, int gai_err) { handle_udp_error(family, id, errno_err, gai_err); }, [this, family](uint32_t id) { state_for_family(family).transaction_id = id; }, [this, family](uint32_t id) { process_announce_packet_sent(family, id); }, }; router_for_family(family)->transfer(id, params); return true; } void TrackerUdp::prepare_announce(int family, uint32_t id, buffer_type& buffer) { if (state_for_family(family).transaction_id == 0) throw internal_error("TrackerUdp::prepare_announce() called but transaction id is 0."); if (id != state_for_family(family).transaction_id) throw internal_error("TrackerUdp::prepare_announce() called with wrong transaction id."); buffer.write_64(state_for_family(family).connection_id); buffer.write_32(1); buffer.write_32(state_for_family(family).transaction_id); buffer.write_range(info().info_hash.begin(), info().info_hash.end()); buffer.write_range(info().local_id.begin(), info().local_id.end()); buffer.write_64(m_params.completed_adjusted); buffer.write_64(m_params.download_left); buffer.write_64(m_params.uploaded_adjusted); buffer.write_32(m_send_state); buffer.write_32_n([family]() -> uint32_t { if (family != AF_INET) return 0; auto local_address = runtime::network_config()->local_inet_address(); if (local_address == nullptr || local_address->sa_family != AF_INET) return 0; return reinterpret_cast(local_address.get())->sin_addr.s_addr; }()); buffer.write_32(info().key); buffer.write_32(m_params.numwant); buffer.write_16(runtime::listen_port()); if (buffer.size_end() != 98) throw internal_error("TrackerUdp::prepare_announce() unexpected buffer size."); LT_LOG_DUMP(buffer.begin(), buffer.size_end(), "prepare announce : state:%s family:%s id:%" PRIx32 " up_adj:%" PRIu64 " completed_adj:%" PRIu64 " left_adj:%" PRIu64, option_to_c_str_or_throw(OPTION_TRACKER_EVENT, m_send_state), family_str(family), state_for_family(family).transaction_id, m_params.uploaded_adjusted, m_params.completed_adjusted, m_params.download_left); } bool TrackerUdp::process_announce(int family, uint32_t id, buffer_type& buffer) { LT_LOG_DUMP(buffer.begin(), buffer.size_end(), "process announce : state:%s family:%s id:%" PRIx32, option_to_c_str_or_throw(OPTION_TRACKER_EVENT, m_send_state), family_str(family), id); switch (process_header(family, 1, buffer)) { case -1: return false; case 0: return true; }; if (buffer.size_end() < 20) return handle_parse_error(family, id, "invalid announce response size"); { auto guard = lock_guard(); state().set_normal_interval(buffer.read_32() * 1s); state().set_min_interval(tracker::TrackerState::default_min_interval); state().m_scrape_incomplete = buffer.read_32(); // leechers state().m_scrape_complete = buffer.read_32(); // seeders state().add_scrape_request(this_thread::cached_seconds()); } AddressList l; // TODO: This might not handle IPv4-mapped IPv6 addresses correctly. switch (family) { case AF_INET: std::copy(reinterpret_cast(buffer.position()), reinterpret_cast(buffer.end() - buffer.remaining() % sizeof(SocketAddressCompact)), std::back_inserter(l)); m_inet_state.transaction_id = 0; m_inet_state.connection_id = 0; break; case AF_INET6: std::copy(reinterpret_cast(buffer.position()), reinterpret_cast(buffer.end() - buffer.remaining() % sizeof(SocketAddressCompact6)), std::back_inserter(l)); m_inet6_state.transaction_id = 0; m_inet6_state.connection_id = 0; break; default: throw internal_error("TrackerUdp::process_announce_output() m_current_address is not inet or inet6."); } if (m_inet_state.transaction_id != 0 || m_inet6_state.transaction_id != 0) { LT_LOG("received announce response : family:%s hostname:%s port:%u peers:%zu", family_str(family), m_hostname.c_str(), m_port, l.size()); m_slot_new_peers(std::move(l)); return false; } LT_LOG("received announce success : family:%s hostname:%s port:%u peers:%zu", family_str(family), m_hostname.c_str(), m_port, l.size()); remove_events(); update_requesting_state(); m_slot_success(std::move(l)); return false; } void TrackerUdp::process_announce_packet_sent(int family, [[maybe_unused]] uint32_t id) { state_for_family(family).packet_sent = true; { auto guard = lock_guard(); if (state().latest_event() == tracker::TrackerState::EVENT_STOPPED) state().m_flags |= tracker::TrackerState::flag_disownable; } } void TrackerUdp::process_error(int family, [[maybe_unused]] uint32_t id, buffer_type& buffer) { std::string msg(buffer.position(), buffer.end()); if (msg.empty()) msg = "empty error message"; reset_family_with_error(family, "tracker message: " + msg); } void TrackerUdp::handle_setup_error(const std::string& msg) { LT_LOG("setup error : hostname:%s port:%u : %s", m_hostname.c_str(), m_port, msg.c_str()); if (m_inet_state.transaction_id != 0 || m_inet6_state.transaction_id != 0) throw internal_error("TrackerUdp::handle_setup_error() called but inet/inet6 transaction id is not 0."); remove_events(); update_requesting_state(); m_slot_failure(msg); } bool TrackerUdp::handle_parse_error(int family, [[maybe_unused]] uint32_t id, const std::string& msg) { reset_family_with_error(family, "parse error: " + msg); return false; } void TrackerUdp::handle_udp_error(int family, [[maybe_unused]] uint32_t id, int errno_err, int gai_err) { std::string msg = "network error: "; // TODO: Add flag to reset_family_with_error() to indicate the error is low-siginificance if it's // gai, so we show the other. if (errno_err != 0) msg += system::errno_enum(errno_err); else if (gai_err != 0) msg += system::gai_enum_error(gai_err); else msg += "unknown error"; reset_family_with_error(family, msg); } } // namespace torrent libtorrent-0.16.17/src/tracker/tracker_udp.h000066400000000000000000000043531522271512000207520ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_TRACKER_UDP_H #define LIBTORRENT_TRACKER_TRACKER_UDP_H #include "net/protocol_buffer.h" #include "tracker/tracker_worker.h" namespace torrent::tracker { class UdpRouter; class TrackerUdp : public TrackerWorker { public: TrackerUdp(const TrackerInfo& info, int flags = 0); tracker_enum type() const override; void send_event(TrackerParams params, TrackerState::event_enum new_state) override; void send_scrape(TrackerParams params) override; void close() override; private: static constexpr uint64_t magic_connection_id = 0x0000041727101980ll; using buffer_type = ProtocolBuffer<512>; struct family_state { uint32_t transaction_id{}; uint64_t connection_id{}; bool packet_sent{}; }; void close_directly(); void cleanup() override; void reset_family_with_error(int family, const std::string& msg); void update_requesting_state(); UdpRouter* router_for_family(int family); family_state& state_for_family(int family); void connect_family(int family); int process_header(int family, uint32_t action, buffer_type& buffer); void prepare_connect(int family, uint32_t id, buffer_type& buffer); bool process_connect(int family, uint32_t id, buffer_type& buffer); void prepare_announce(int family, uint32_t id, buffer_type& buffer); bool process_announce(int family, uint32_t id, buffer_type& buffer); void process_announce_packet_sent(int family, uint32_t id); void process_error(int family, uint32_t id, buffer_type& buffer); void handle_setup_error(const std::string& msg); bool handle_parse_error(int family, uint32_t id, const std::string& msg); void handle_udp_error(int family, uint32_t id, int errno_err, int gai_err); std::string m_hostname; uint16_t m_port{}; TrackerParams m_params; int m_send_state{}; family_state m_inet_state{}; family_state m_inet6_state{}; }; } // namespace torrent::tracker #endif libtorrent-0.16.17/src/tracker/tracker_worker.cc000066400000000000000000000014611522271512000216260ustar00rootroot00000000000000#include "config.h" #include "tracker_worker.h" #include "torrent/exceptions.h" #include "torrent/system/callbacks.h" namespace torrent { TrackerWorker::TrackerWorker(TrackerInfo info, int flags) : m_callback_id(system::make_callback_id()), m_info(info) { m_state.m_flags = flags; } TrackerWorker::~TrackerWorker() noexcept(false) { if (!m_state.is_deleted()) throw internal_error("TrackerWorker destroyed without being marked as deleted."); } void TrackerWorker::mark_starting_request() { if (type() == TRACKER_DHT) return; auto guard = lock_guard(); m_state.m_flags |= tracker::TrackerState::flag_starting_request; } void TrackerWorker::remove_events() { system::cancel_callback_and_wait(m_callback_id, main_thread::thread(), tracker_thread::thread()); } } // namespace torrent libtorrent-0.16.17/src/tracker/tracker_worker.h000066400000000000000000000102031522271512000214620ustar00rootroot00000000000000// TrackerWorker is the base class for trackers and should be thread-safe. #ifndef LIBTORRENT_TRACKER_TRACKER_WORKER_H #define LIBTORRENT_TRACKER_TRACKER_WORKER_H #include #include #include #include #include "torrent/common.h" #include "torrent/hash_string.h" #include "torrent/tracker/tracker_state.h" class TrackerTest; namespace torrent { namespace tracker { class Manager; } struct TrackerInfo { HashString info_hash{HashString::new_zero()}; HashString obfuscated_hash{HashString::new_zero()}; HashString local_id{HashString::new_zero()}; std::string url; uint32_t group{}; uint32_t key{}; }; class TrackerWorker { public: TrackerWorker(TrackerInfo info, int flags = 0); virtual ~TrackerWorker() noexcept(false); // Public members do not require locking: const TrackerInfo& info() const { return m_info; } virtual tracker_enum type() const = 0; virtual void send_event(tracker::TrackerParams params, tracker::TrackerState::event_enum state) = 0; virtual void send_scrape(tracker::TrackerParams params) = 0; protected: friend class TrackerList; friend class tracker::Tracker; friend class tracker::Manager; friend class ::TrackerTest; // TODO: Review if close() is ever used anymore. virtual void close() = 0; virtual void cleanup() = 0; void lock_and_clear_stats(); auto lock_and_latest_event(); void lock_and_set_latest_event(tracker::TrackerState::event_enum new_state); void lock() const { m_mutex.lock(); } auto lock_guard() const { return std::scoped_lock(m_mutex); } void unlock() const { m_mutex.unlock(); } // Protected members that require locking: std::string tracker_id_safe() const; void set_tracker_id_safe(const std::string& id); void set_tracker_id_unsafe(const std::string& id); void mark_starting_request(); auto& callback_id() { return m_callback_id; } void remove_events(); tracker::TrackerState& state() { return m_state; } const tracker::TrackerState& state() const { return m_state; } // Do not lock when calling these functions/slots: // // TODO: Review, these should be called locked? Yes. // // The slots should put a work order into the tracker controller thread, which will be correctly // ordered as it pertains to a single tracker's slot calls. std::function m_slot_enabled; std::function m_slot_disabled; std::function m_slot_success; std::function m_slot_failure; std::function m_slot_scrape_success; std::function m_slot_scrape_failure; std::function m_slot_new_peers; private: TrackerWorker(const TrackerWorker&) = delete; TrackerWorker& operator=(const TrackerWorker&) = delete; system::callback_id m_callback_id; align_cacheline mutable std::mutex m_mutex; TrackerInfo m_info; tracker::TrackerState m_state{}; std::string m_tracker_id; align_cacheline bool __force_new_cacheline; }; inline void TrackerWorker::lock_and_clear_stats() { auto guard = lock_guard(); m_state.clear_stats(); } inline auto TrackerWorker::lock_and_latest_event() { auto guard = lock_guard(); return m_state.m_latest_event; } inline void TrackerWorker::lock_and_set_latest_event(tracker::TrackerState::event_enum new_state) { auto guard = lock_guard(); m_state.m_latest_event = new_state; } inline std::string TrackerWorker::tracker_id_safe() const { auto guard = lock_guard(); return m_tracker_id; } inline void TrackerWorker::set_tracker_id_safe(const std::string& id) { auto guard = lock_guard(); m_tracker_id = id; } inline void TrackerWorker::set_tracker_id_unsafe(const std::string& id) { m_tracker_id = id; } } // namespace torrent #endif // LIBTORRENT_TRACKER_TRACKER_WORKER_H libtorrent-0.16.17/src/tracker/udp_router.cc000066400000000000000000000424061522271512000207760ustar00rootroot00000000000000#include "config.h" #include "udp_router.h" #include #include #include #include "torrent/net/fd.h" #include "torrent/net/resolver.h" #include "torrent/net/socket_address.h" #include "torrent/runtime/network_config.h" #include "torrent/runtime/socket_manager.h" #include "torrent/system/callbacks.h" #include "torrent/system/poll.h" #include "torrent/system/system.h" #include "torrent/utils/log.h" #define LT_LOG(log_fmt, ...) \ lt_log_print_subsystem(LOG_TRACKER_REQUESTS, "udp_router", log_fmt, __VA_ARGS__); // lt_log_print_subsystem(LOG_TRACKER_REQUESTS, "udp_router", "%p : " log_fmt, static_cast(this), __VA_ARGS__); // TODO: Add m_connections::iterator to info so we don't need to look them up. We should be able to // replace unordered_map with map then to reduce memory usage after initial startup tracker rush. namespace torrent::tracker { UdpRouter::UdpRouter() : m_resolver_callback_id(system::make_callback_id()) { std::random_device rd; std::mt19937 mt(rd()); m_random_engine.seed(mt()); m_task_timeout.slot() = [this] { receive_timeout(); }; } UdpRouter::~UdpRouter() { // this_thread::resolver()->cancel(m_resolver_callback_id); } void UdpRouter::open(int family) { if (is_open()) throw internal_error("UdpRouter::open() called but router is already open."); if (family != AF_INET && family != AF_INET6) throw internal_error("UdpRouter::open() called with unsupported family."); m_thread = this_thread::thread(); auto bind_address = runtime::network_config()->bind_address_for_udp_connect(family); if (bind_address == nullptr) { LT_LOG("could not open udp router : blocked or invalid bind address : family:%s", family_str(family)); return; } if (bind_address->sa_family != family) throw internal_error("UdpRouter::open() bind address family does not match requested family."); int fd = fd_open_family(fd_flag_datagram | fd_flag_nonblock, family); if (fd == -1) { LT_LOG("opening router failed : open failed : family:%s errno:%s", family_str(family), system::errno_enum_str(errno).c_str()); return; } if (!fd_bind(fd, bind_address.get())) { LT_LOG("opening router failed : bind failed : family:%s bind_address:%s errno:%s", family_str(family), sa_pretty_str(bind_address.get()).c_str(), system::errno_enum_str(errno).c_str()); fd_close(fd); return; } set_file_descriptor(fd); set_socket_address(sa_copy(bind_address.get())); runtime::socket_manager()->register_event_or_throw(this, runtime::category_internal, [this]() { this_thread::poll()->open(this); this_thread::poll()->insert_read(this); this_thread::poll()->insert_error(this); }); LT_LOG("opened udp router : family:%s bind_address:%s", family_str(family), sa_pretty_str(bind_address.get()).c_str()); } void UdpRouter::close() { if (!is_open()) return; assert(m_thread == this_thread::thread()); this_thread::scheduler()->erase(&m_task_timeout); // Check if we're running in unittests. if (this_thread::resolver() != nullptr && net_thread::thread() != nullptr) this_thread::resolver()->cancel(m_resolver_callback_id); runtime::socket_manager()->unregister_event_or_throw(this, [this]() { this_thread::poll()->remove_and_close(this); fd_close(file_descriptor()); set_file_descriptor(-1); set_socket_address(sa_make_unspec()); }); // TODO: Do we just let timeout expire connections, or do we send errors? // TODO: Send error to all? LT_LOG("closed udp router", 0); } void UdpRouter::updated_network_config(int family) { assert(m_thread == this_thread::thread()); auto bind_address = runtime::network_config()->bind_address_for_udp_connect(family); if (bind_address == nullptr) { LT_LOG("closing udp router due to invalid or blocked bind address : family:%s", family_str(family)); close(); return; } if (bind_address->sa_family != family) throw internal_error("UdpRouter::updated_network_config() got bind address with wrong family."); if (sa_equal(bind_address.get(), socket_address())) return; LT_LOG("udp router bind address changed : old:%s new:%s", sa_pretty_str(socket_address()).c_str(), sa_pretty_str(bind_address.get()).c_str()); close(); open(family); } void UdpRouter::connect(c_sa_shared_ptr address, connection_params params) { if (!is_open()) return; assert(m_thread == this_thread::thread()); assert(address != nullptr); if (address != nullptr && address->sa_family != router_family()) throw internal_error("UdpRouter::connect() called with unsupported address family."); if (sa_port(address.get()) == 0) throw internal_error("UdpRouter::connect() called with address with port 0."); auto itr = connect_unsafe(std::move(address), params); try_write_with_queues(itr->first, &itr->second); } void UdpRouter::connect(const std::string hostname, uint16_t port, connection_params params) { if (!is_open()) return; assert(m_thread == this_thread::thread()); assert(!hostname.empty()); assert(port != 0); auto [sa, sa_compatible] = sa_lookup_numeric(hostname, router_family()); if (!sa_compatible) return; if (sa != nullptr) { sap_set_port(sa, port); connect(std::move(sa), params); return; } auto itr = connect_unsafe(nullptr, params); auto fn = [this, id = itr->first, port](c_sin_shared_ptr sin, int err, c_sin6_shared_ptr sin6, int err6) { resolved_hostname(id, port, sin, err, sin6, err6); }; this_thread::resolver()->resolve_both(m_resolver_callback_id, hostname, router_family(), std::move(fn)); } void UdpRouter::transfer(uint32_t id, connection_params params) { assert(m_thread == this_thread::thread()); auto itr = m_connections.find(id); if (itr == m_connections.end()) throw internal_error("UdpRouter::transfer_connection() called with invalid connection ID."); if (itr->second.address == nullptr) throw internal_error("UdpRouter::transfer_connection() called but connection does not have an address."); auto new_itr = connect_unsafe(std::move(itr->second.address), params); disconnect_unsafe(itr); try_write_with_queues(new_itr->first, &new_itr->second); } void UdpRouter::disconnect(uint32_t id) { assert(m_thread == this_thread::thread()); auto itr = m_connections.find(id); if (itr == m_connections.end()) throw internal_error("UdpRouter::disconnect() called with invalid connection ID."); if (itr->second.address == nullptr) { assert(itr->second.queue_ptr == nullptr); assert(itr->second.timeout_ptr == nullptr); // Indicate to resolved_hostname() that the connection has been disconnected. itr->second.prepare = nullptr; itr->second.process = nullptr; itr->second.failure = nullptr; return; } disconnect_unsafe(itr); } UdpRouter::connection_map::iterator UdpRouter::connect_unsafe(c_sa_shared_ptr address, connection_params params) { assert(is_open()); assert(address == nullptr || address->sa_family == router_family()); assert(params.prepare); assert(params.process); assert(params.failure); assert(params.connected); connection_map::iterator itr; while (true) { uint32_t id = m_random_engine(); if (id == 0) continue; auto [new_itr, inserted] = m_connections.try_emplace(id, connection_info{}); if (inserted) { itr = new_itr; break; } } itr->second.address = std::move(address); itr->second.prepare = std::move(params.prepare); itr->second.process = std::move(params.process); itr->second.failure = std::move(params.failure); itr->second.packet_sent = std::move(params.packet_sent); params.connected(itr->first); return itr; } void UdpRouter::disconnect_unsafe(connection_map::iterator itr) { assert(itr != m_connections.end()); if (itr->second.queue_ptr != nullptr) { *itr->second.queue_ptr = nullptr; itr->second.queue_ptr = nullptr; } clear_timeout(&itr->second); m_connections.erase(itr); } void UdpRouter::disconnect_failure_unsafe(connection_map::iterator itr, int errno_err, int gai_err) { assert(itr != m_connections.end()); auto id = itr->first; auto failure_fn = std::move(itr->second.failure); disconnect_unsafe(itr); failure_fn(id, errno_err, gai_err); } void UdpRouter::resolved_hostname(uint32_t id, uint16_t port, c_sin_shared_ptr& sin, int err, c_sin6_shared_ptr& sin6, int err6) { auto itr = m_connections.find(id); if (itr == m_connections.end()) { LT_LOG("received hostname for unknown connection : id:%" PRIx32, id); return; } if (itr->second.address != nullptr) throw internal_error("UdpRouter::resolved_hostname() called but connection already has an address."); if (itr->second.failure == nullptr) { disconnect_unsafe(itr); return; } auto error_fn = [this, id, itr](int err) { LT_LOG("failed to resolve hostname : id:%" PRIx32 " error:%s", id, system::gai_enum_error(err)); disconnect_failure_unsafe(itr, 0, err); }; sa_unique_ptr sa; switch (router_family()) { case AF_INET: if (sin == nullptr && err == 0) throw internal_error("UdpRouter::resolved_hostname() sin == nullptr but err == 0"); if (err != 0) return error_fn(err); sa = sa_copy_in(sin.get()); break; case AF_INET6: if (sin6 == nullptr && err6 == 0) throw internal_error("UdpRouter::resolved_hostname() sin6 == nullptr but err6 == 0"); if (err6 != 0) return error_fn(err6); sa = sa_copy_in6(sin6.get()); break; default: throw internal_error("UdpRouter::resolved_hostname() called but router socket address has unsupported family."); }; sap_set_port(sa, port); itr->second.address = std::move(sa); try_write_with_queues(id, &itr->second); } bool UdpRouter::try_write_with_queues(uint32_t id, connection_info* info) { clear_timeout(info); int err = try_write(id, info); if (err == EAGAIN) { queue_write(id, info); return false; } if (err != 0) return false; queue_timeout(id, info); return true; } // Returns EAGAIN for all temporary errors. int UdpRouter::try_write(uint32_t id, connection_info* info) { if (!is_open()) throw internal_error("UdpRouter::try_write() called but router is not open."); if (info->retry_count >= 3) throw internal_error("UdpRouter::try_write() called but retry_count is not 0."); int retries{}; while (true) { int err = do_write(id, info); if (err == 0) break; if (err == EINTR) { if (++retries > 5) return EAGAIN; continue; } if (err == EAGAIN || err == EWOULDBLOCK || err == EINTR) return EAGAIN; // To properly handle this, try_write() returning true means we don't touch the connection again. if (err == EHOSTUNREACH || err == ENETUNREACH || err == EADDRNOTAVAIL || err == EINVAL || err == EACCES || err == EPERM) { if (err == EINVAL || err == EACCES || err == EPERM) LT_LOG("failed to write datagram : address:%s errno:%s", sa_pretty_str(info->address.get()).c_str(), system::errno_enum_str(err).c_str()); disconnect_failure_unsafe(m_connections.find(id), err, 0); return err; } LT_LOG("failed to write datagram : address:%s errno:%s", sa_pretty_str(info->address.get()).c_str(), system::errno_enum_str(err).c_str()); // TODO: Need to be handled differently. throw internal_error("UdpRouter::try_write() failed to write datagram : " + family_enum_str(socket_address()->sa_family) + " : " + sa_pretty_str(info->address.get()) + " : " + system::errno_enum_str(err)); } if (info->packet_sent) info->packet_sent(id); return 0; } int UdpRouter::do_write(uint32_t id, connection_info* info) { assert(info->address != nullptr); m_buffer.reset(); info->prepare(id, m_buffer); if (m_buffer.size_end() == 0) throw internal_error("UdpRouter::do_write() prepare function did not write any data."); auto address = info->address.get(); auto bytes_written = write_datagram_sa(m_buffer.begin(), m_buffer.size_end(), address); if (bytes_written == -1) { if (errno == 0) throw internal_error("UdpRouter::do_write() write_datagram_sa() failed with errno 0"); return errno; } if (bytes_written != m_buffer.size_end()) { LT_LOG("failed to write datagram : address:%s : only %d of %d bytes written", sa_pretty_str(address).c_str(), bytes_written, m_buffer.size_end()); throw internal_error("UdpRouter::try_write() did not write entire datagram."); } return 0; } void UdpRouter::clear_timeout(connection_info* info) { if (info->timeout_ptr == nullptr) return; *info->timeout_ptr = nullptr; info->timeout_ptr = nullptr; } void UdpRouter::queue_timeout(uint32_t id, connection_info* info) { if (info->timeout_ptr != nullptr) throw internal_error("UdpRouter::queue_timeout() called for connection that is already queued for timeout."); auto retry_count = ++info->retry_count; auto timeout_time = this_thread::cached_seconds() + retry_count * 15s; if (m_timeout_queue.empty()) this_thread::scheduler()->wait_until(&m_task_timeout, timeout_time); m_timeout_queue.emplace_back(id, timeout_time, info); info->timeout_ptr = &std::get<2>(m_timeout_queue.back()); } void UdpRouter::queue_write(uint32_t id, connection_info* info) { if (info->queue_ptr != nullptr) throw internal_error("UdpRouter::queue_write() called for connection that is already queued for writing."); if (m_write_queue.empty()) this_thread::poll()->insert_write(this); m_write_queue.emplace_back(id, info); info->queue_ptr = &m_write_queue.back().second; } uint32_t UdpRouter::peek_transaction_id(buffer_type& buffer) const { if (buffer.size_end() < 8) return 0; buffer.read_32(); uint32_t transaction_id = buffer.read_32(); buffer.reset_position(); return transaction_id; } void UdpRouter::receive_timeout() { auto now = this_thread::cached_seconds(); while (!m_timeout_queue.empty()) { auto [id, timeout_time, info] = m_timeout_queue.front(); if (timeout_time > now) break; if (info && info->timeout_ptr != &std::get<2>(m_timeout_queue.front())) throw internal_error("UdpRouter::receive_timeout() timeout queue entry does not match connection info."); m_timeout_queue.pop_front(); if (info == nullptr) continue; if (info->failure == nullptr) throw internal_error("UdpRouter::receive_timeout() connection info failure callback is null."); info->timeout_ptr = nullptr; if (info->retry_count >= 3) { disconnect_failure_unsafe(m_connections.find(id), ETIMEDOUT, 0); continue; } if (info->queue_ptr != nullptr) throw internal_error("UdpRouter::receive_timeout() connection is currently queued for writing."); try_write_with_queues(id, info); } if (!m_timeout_queue.empty()) this_thread::scheduler()->update_wait_until(&m_task_timeout, std::get<1>(m_timeout_queue.front())); } void UdpRouter::event_read() { while (true) { sa_inet_union from_sa; m_buffer.reset(); auto bytes_read = read_datagram_sa(m_buffer.begin(), m_buffer.reserved(), &from_sa.sa, sizeof(from_sa)); if (bytes_read == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; if (errno == EINTR) continue; LT_LOG("failed to read datagram : errno:%s", system::errno_enum_str(errno).c_str()); throw internal_error("UdpRouter::event_read() failed to read datagram: " + system::errno_enum_str(errno)); } if (bytes_read == 0) throw internal_error("UdpRouter::event_read() read datagram of length 0"); m_buffer.set_end(bytes_read); uint32_t transaction_id = peek_transaction_id(m_buffer); if (transaction_id == 0) { // LT_LOG("received datagram with invalid transaction ID : address:%s", sa_pretty_str(&from_sa.sa).c_str()); continue; } // It's quicker to do transaction-id lookup and then verify the address. auto itr = m_connections.find(transaction_id); if (itr == m_connections.end()) { // LT_LOG("received datagram with unknown transaction ID : address:%s transaction_id:%" PRIx32, sa_pretty_str(&from_sa.sa).c_str(), transaction_id); continue; } // While the id is random, the tracker can still send brute-force it so check that we're did not // disconnect before hostname resolved. if (itr->second.address == nullptr) continue; if (!sa_equal(&from_sa.sa, itr->second.address.get())) continue; if (!itr->second.process(transaction_id, m_buffer)) disconnect_unsafe(itr); } } void UdpRouter::event_write() { while (!m_write_queue.empty()) { auto [id, info] = m_write_queue.front(); if (info == nullptr) { m_write_queue.pop_front(); continue; } assert(info->timeout_ptr == nullptr); int err = try_write(id, info); // EWOULDBLOCK/EINTR are returned as EAGAIN. if (err == EAGAIN) break; if (err != 0) { assert(std::get<1>(m_write_queue.front()) == nullptr); m_write_queue.pop_front(); continue; } m_write_queue.pop_front(); info->queue_ptr = nullptr; queue_timeout(id, info); } if (m_write_queue.empty()) this_thread::poll()->remove_write(this); } void UdpRouter::event_error() { LT_LOG("socket error event triggered", 0); throw internal_error("UdpRouter::event_error() triggered"); } } // namespace torrent::tracker libtorrent-0.16.17/src/tracker/udp_router.h000066400000000000000000000107671522271512000206450ustar00rootroot00000000000000#ifndef LIBTORRENT_TRACKER_UDP_ROUTER_H #define LIBTORRENT_TRACKER_UDP_ROUTER_H #include #include #include #include #include #include #include #include "net/protocol_buffer.h" #include "net/socket_datagram.h" #include "torrent/utils/scheduler.h" namespace torrent::tracker { class UdpRouter : public SocketDatagram { public: using buffer_type = ProtocolBuffer<512>; using prepare_func = std::function; using process_func = std::function; using failure_func = std::function; using connected_func = std::function; using packet_sent_func = std::function; struct connection_params { prepare_func prepare; process_func process; failure_func failure; connected_func connected; packet_sent_func packet_sent; }; UdpRouter(); ~UdpRouter(); const char* type_name() const override { return "udp_router"; } // TODO: Listen to network_config updates and reopen if necessary. // TODO: Add callback subscription for network_config updates. void open(int family); void close(); void updated_network_config(int family); // TODO: Add option for single-try. // These may call prepare_fn with the new id before returning, except for when hostname lookup is // required. // // When process_fn is called, return false to disconnect and true to do nothing. (E.g. unknown // packet or transferred connection) void connect(c_sa_shared_ptr address, connection_params params); void connect(const std::string hostname, uint16_t port, connection_params params); void transfer(uint32_t id, connection_params params); void disconnect(uint32_t id); private: UdpRouter(const UdpRouter&) = delete; UdpRouter& operator=(const UdpRouter&) = delete; struct connection_info; using random_engine = std::independent_bits_engine; using connection_map = std::unordered_map; using write_queue_type = std::deque>; using timeout_queue_type = std::deque>; // TODO: Add itr to self in connection_map. struct connection_info { c_sa_shared_ptr address; prepare_func prepare{}; process_func process{}; failure_func failure{}; // Do not change the connection in this callback. packet_sent_func packet_sent{}; unsigned int retry_count{}; connection_info** queue_ptr{}; connection_info** timeout_ptr{}; }; connection_map::iterator connect_unsafe(c_sa_shared_ptr address, connection_params params); void disconnect_unsafe(connection_map::iterator itr); void disconnect_failure_unsafe(connection_map::iterator itr, int errno_err, int gai_err); int router_family() const; void resolved_hostname(uint32_t id, uint16_t port, c_sin_shared_ptr& sin, int err, c_sin6_shared_ptr& sin6, int err6); bool try_write_with_queues(uint32_t id, connection_info* info); [[nodiscard]] int try_write(uint32_t id, connection_info* info); [[nodiscard]] int do_write(uint32_t id, connection_info* info); void clear_timeout(connection_info* info); void queue_timeout(uint32_t id, connection_info* info); void queue_write(uint32_t id, connection_info* info); uint32_t peek_transaction_id(buffer_type& buffer) const; void receive_timeout(); void event_read() override; void event_write() override; void event_error() override; // TODO: Change Thread/Scheduler callbacks to use deque, and create callback handles. system::Thread* m_thread; random_engine m_random_engine; connection_map m_connections; write_queue_type m_write_queue; timeout_queue_type m_timeout_queue; utils::SchedulerEntry m_task_timeout; buffer_type m_buffer; system::callback_id m_resolver_callback_id; }; inline int UdpRouter::router_family() const { return socket_address()->sa_family; } } // namespace torrent::tracker #endif // LIBTORRENT_TRACKER_UDP_ROUTER_H libtorrent-0.16.17/src/utils/000077500000000000000000000000001522271512000157765ustar00rootroot00000000000000libtorrent-0.16.17/src/utils/diffie_hellman.cc000066400000000000000000000036461522271512000212440ustar00rootroot00000000000000#include "config.h" #include "diffie_hellman.h" #include "torrent/exceptions.h" #include #include #include #pragma GCC diagnostic ignored "-Wdeprecated-declarations" namespace { void dh_free(void* dh) { DH_free(static_cast(dh)); } auto dh_get(const torrent::DiffieHellman::dh_ptr& dh) { return static_cast(dh.get()); } bool dh_set_pg(torrent::DiffieHellman::dh_ptr& dh, BIGNUM* dh_p, BIGNUM* dh_g) { return DH_set0_pqg(static_cast(dh.get()), dh_p, nullptr, dh_g); } const BIGNUM* dh_get_pub_key(const torrent::DiffieHellman::dh_ptr& dh) { const BIGNUM *pub_key; DH_get0_key(dh_get(dh), &pub_key, nullptr); return pub_key; } } // namespace namespace torrent { DiffieHellman::DiffieHellman(const unsigned char *prime, int primeLength, const unsigned char *generator, int generatorLength) : m_dh(DH_new(), &dh_free) { BIGNUM* dh_p = BN_bin2bn(prime, primeLength, nullptr); BIGNUM* dh_g = BN_bin2bn(generator, generatorLength, nullptr); if (dh_p == nullptr || dh_g == nullptr || !dh_set_pg(m_dh, dh_p, dh_g)) throw internal_error("Could not generate Diffie-Hellman parameters"); DH_generate_key(dh_get(m_dh)); } bool DiffieHellman::is_valid() const { return dh_get_pub_key(m_dh) != nullptr; } bool DiffieHellman::compute_secret(const unsigned char *pubkey, unsigned int length) { BIGNUM* k = BN_bin2bn(pubkey, length, nullptr); m_secret = std::make_unique(DH_size(dh_get(m_dh))); m_size = DH_compute_key(reinterpret_cast(m_secret.get()), k, dh_get(m_dh)); BN_free(k); return m_size != -1; } void DiffieHellman::store_pub_key(unsigned char* dest, unsigned int length) { std::memset(dest, 0, length); const BIGNUM *pub_key = dh_get_pub_key(m_dh); if (static_cast(length) >= BN_num_bytes(pub_key)) BN_bn2bin(pub_key, dest + length - BN_num_bytes(pub_key)); } } // namespace torrent libtorrent-0.16.17/src/utils/diffie_hellman.h000066400000000000000000000020561522271512000211000ustar00rootroot00000000000000#ifndef LIBTORRENT_DIFFIE_HELLMAN_H #define LIBTORRENT_DIFFIE_HELLMAN_H #include "config.h" #include #include namespace torrent { class DiffieHellman { public: using secret_ptr = std::unique_ptr; using dh_ptr = std::unique_ptr; DiffieHellman(const unsigned char prime[], int primeLength, const unsigned char generator[], int generatorLength); ~DiffieHellman() = default; DiffieHellman(const DiffieHellman&) = delete; DiffieHellman& operator=(const DiffieHellman&) = delete; bool is_valid() const; bool compute_secret(const unsigned char pubkey[], unsigned int length); void store_pub_key(unsigned char* dest, unsigned int length); unsigned int size() const { return m_size; } const char* c_str() const { return m_secret.get(); } std::string secret_str() const { return std::string(m_secret.get(), m_size); } private: dh_ptr m_dh; secret_ptr m_secret; int m_size{0}; }; } // namespace torrent #endif libtorrent-0.16.17/src/utils/functional.h000066400000000000000000000036471522271512000203230ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_FUNCTIONAL_H #define LIBTORRENT_UTILS_FUNCTIONAL_H #include #include #include namespace utils { // Container must be a list as the entries may be removed during the call. template inline void slot_list_call(const Container& slot_list, Args&&... args) { if (slot_list.empty()) return; auto first = slot_list.begin(); auto next = slot_list.begin(); auto args_tuple = std::forward_as_tuple(std::forward(args)...); while (++next != slot_list.end()) { std::apply(*first, args_tuple); first = next; } std::apply(*first, args_tuple); } // // Count the number of elements from the start of the containers to the first inequal element. // template struct compare_base { bool operator () (const _Value& complete, const _Value& base) const { return !complete.compare(0, base.size(), base); } }; template typename std::iterator_traits<_InputIter1>::difference_type count_base(_InputIter1 __first1, _InputIter1 __last1, _InputIter2 __first2, _InputIter2 __last2) { typename std::iterator_traits<_InputIter1>::difference_type __n = 0; for ( ;__first1 != __last1 && __first2 != __last2; ++__first1, ++__first2, ++__n) if (*__first1 != *__first2) return __n; return __n; } template _Return make_base(_InputIter __first, _InputIter __last, _Ftor __ftor) { if (__first == __last) return ""; _Return __base = __ftor(*__first++); for ( ;__first != __last; ++__first) { auto __pos = count_base(__base.begin(), __base.end(), __ftor(*__first).begin(), __ftor(*__first).end()); if (__pos < (typename std::iterator_traits<_InputIter>::difference_type)__base.size()) __base.resize(__pos); } return __base; } } // namespace utils #endif libtorrent-0.16.17/src/utils/instrumentation.cc000066400000000000000000000215741522271512000215610ustar00rootroot00000000000000#include "config.h" #include "instrumentation.h" namespace torrent { std::array instrumentation_values; static int64_t instrumentation_fetch_and_clear(instrumentation_enum type) { #ifdef LT_INSTRUMENTATION return instrumentation_values[type].exchange(0); #else return 0; #endif } void instrumentation_tick() { lt_log_print(LOG_INSTRUMENTATION_MEMORY, "%" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64, instrumentation_values[INSTRUMENTATION_MEMORY_CHUNK_USAGE].load(), instrumentation_values[INSTRUMENTATION_MEMORY_CHUNK_COUNT].load(), instrumentation_values[INSTRUMENTATION_MEMORY_HASHING_CHUNK_USAGE].load(), instrumentation_values[INSTRUMENTATION_MEMORY_HASHING_CHUNK_COUNT].load(), instrumentation_values[INSTRUMENTATION_MEMORY_BITFIELDS].load()); lt_log_print(LOG_INSTRUMENTATION_MINCORE, "%" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64, instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_INCORE_TOUCHED), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_INCORE_NEW), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_NOT_INCORE_TOUCHED), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_NOT_INCORE_NEW), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_INCORE_BREAK), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_SYNC_SUCCESS), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_SYNC_FAILED), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_SYNC_NOT_SYNCED), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_SYNC_NOT_DEALLOCATED), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_ALLOC_FAILED), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_ALLOCATIONS), instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_DEALLOCATIONS)); lt_log_print(LOG_INSTRUMENTATION_POLLING, "%" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64, instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_INTERRUPT_POKE), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_INTERRUPT_READ_EVENT), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_DO_POLL), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_DO_POLL_MAIN), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_DO_POLL_DISK), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_DO_POLL_OTHERS), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_EVENTS), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_EVENTS_MAIN), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_EVENTS_DISK), instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_EVENTS_OTHERS)); lt_log_print(LOG_INSTRUMENTATION_TRANSFERS, "%" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64 " %" PRIi64, instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_DELEGATED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_DOWNLOADING), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_FINISHED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_SKIPPED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNKNOWN), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_ADDED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_MOVED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_REMOVED), instrumentation_values[INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_TOTAL].load(), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_ADDED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_MOVED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_REMOVED), instrumentation_values[INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_TOTAL].load(), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_ADDED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_MOVED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_REMOVED), instrumentation_values[INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_TOTAL].load(), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_ADDED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_MOVED), instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_REMOVED), instrumentation_values[INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_TOTAL].load(), instrumentation_values[INSTRUMENTATION_TRANSFER_PEER_INFO_UNACCOUNTED].load()); } void instrumentation_reset() { instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_INCORE_TOUCHED); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_INCORE_NEW); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_NOT_INCORE_TOUCHED); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_NOT_INCORE_NEW); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_INCORE_BREAK); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_SYNC_SUCCESS); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_SYNC_FAILED); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_SYNC_NOT_SYNCED); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_SYNC_NOT_DEALLOCATED); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_ALLOC_FAILED); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_ALLOCATIONS); instrumentation_fetch_and_clear(INSTRUMENTATION_MINCORE_DEALLOCATIONS); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_INTERRUPT_POKE); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_INTERRUPT_READ_EVENT); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_DO_POLL); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_DO_POLL_MAIN); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_DO_POLL_DISK); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_DO_POLL_OTHERS); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_EVENTS); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_EVENTS_MAIN); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_EVENTS_DISK); instrumentation_fetch_and_clear(INSTRUMENTATION_POLLING_EVENTS_OTHERS); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_DELEGATED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_DOWNLOADING); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_FINISHED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_SKIPPED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNKNOWN); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_ADDED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_MOVED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_REMOVED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_ADDED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_MOVED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_REMOVED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_ADDED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_MOVED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_REMOVED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_ADDED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_MOVED); instrumentation_fetch_and_clear(INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_REMOVED); } } // namespace torrent libtorrent-0.16.17/src/utils/instrumentation.h000066400000000000000000000064221522271512000214160ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_INSTRUMENTATION_H #define LIBTORRENT_UTILS_INSTRUMENTATION_H #include #include #include #include "torrent/common.h" #include "torrent/utils/log.h" namespace torrent { enum instrumentation_enum { INSTRUMENTATION_MEMORY_BITFIELDS, INSTRUMENTATION_MEMORY_CHUNK_USAGE, INSTRUMENTATION_MEMORY_CHUNK_COUNT, INSTRUMENTATION_MEMORY_HASHING_CHUNK_USAGE, INSTRUMENTATION_MEMORY_HASHING_CHUNK_COUNT, INSTRUMENTATION_MINCORE_INCORE_TOUCHED, INSTRUMENTATION_MINCORE_INCORE_NEW, INSTRUMENTATION_MINCORE_NOT_INCORE_TOUCHED, INSTRUMENTATION_MINCORE_NOT_INCORE_NEW, INSTRUMENTATION_MINCORE_INCORE_BREAK, INSTRUMENTATION_MINCORE_SYNC_SUCCESS, INSTRUMENTATION_MINCORE_SYNC_FAILED, INSTRUMENTATION_MINCORE_SYNC_NOT_SYNCED, INSTRUMENTATION_MINCORE_SYNC_NOT_DEALLOCATED, INSTRUMENTATION_MINCORE_ALLOC_FAILED, INSTRUMENTATION_MINCORE_ALLOCATIONS, INSTRUMENTATION_MINCORE_DEALLOCATIONS, INSTRUMENTATION_POLLING_INTERRUPT_POKE, INSTRUMENTATION_POLLING_INTERRUPT_READ_EVENT, INSTRUMENTATION_POLLING_DO_POLL, INSTRUMENTATION_POLLING_DO_POLL_MAIN, INSTRUMENTATION_POLLING_DO_POLL_DISK, INSTRUMENTATION_POLLING_DO_POLL_NET, INSTRUMENTATION_POLLING_DO_POLL_OTHERS, INSTRUMENTATION_POLLING_DO_POLL_TRACKER, INSTRUMENTATION_POLLING_EVENTS, INSTRUMENTATION_POLLING_EVENTS_MAIN, INSTRUMENTATION_POLLING_EVENTS_DISK, INSTRUMENTATION_POLLING_EVENTS_NET, INSTRUMENTATION_POLLING_EVENTS_OTHERS, INSTRUMENTATION_POLLING_EVENTS_TRACKER, INSTRUMENTATION_TRANSFER_REQUESTS_DELEGATED, INSTRUMENTATION_TRANSFER_REQUESTS_DOWNLOADING, INSTRUMENTATION_TRANSFER_REQUESTS_FINISHED, INSTRUMENTATION_TRANSFER_REQUESTS_SKIPPED, INSTRUMENTATION_TRANSFER_REQUESTS_UNKNOWN, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED, INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_ADDED, INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_MOVED, INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_REMOVED, INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_TOTAL, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_ADDED, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_MOVED, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_REMOVED, INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_TOTAL, INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_ADDED, INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_MOVED, INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_REMOVED, INSTRUMENTATION_TRANSFER_REQUESTS_STALLED_TOTAL, INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_ADDED, INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_MOVED, INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_REMOVED, INSTRUMENTATION_TRANSFER_REQUESTS_CHOKED_TOTAL, INSTRUMENTATION_TRANSFER_PEER_INFO_UNACCOUNTED, INSTRUMENTATION_MAX_SIZE }; extern std::array instrumentation_values; void instrumentation_initialize(); void instrumentation_update(instrumentation_enum type, int64_t change); void instrumentation_tick(); void instrumentation_reset(); // // Implementation: // inline void instrumentation_initialize() { std::fill(instrumentation_values.begin(), instrumentation_values.end(), int64_t()); } inline void instrumentation_update([[maybe_unused]] instrumentation_enum type, [[maybe_unused]] int64_t change) { #ifdef LT_INSTRUMENTATION instrumentation_values[type] += change; #endif } } // namespace torrent #endif libtorrent-0.16.17/src/utils/partial_queue.h000066400000000000000000000105741522271512000210160ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_PARTIAL_QUEUE_H #define LIBTORRENT_UTILS_PARTIAL_QUEUE_H #include #include #include #include #include namespace torrent::utils { // First step, don't allow overflowing to the next layer. Only disable // the above layers for now. // We also include 0 in a single layer as some chunk may be available // only through seeders. class PartialQueue { public: typedef uint8_t key_type; typedef uint32_t mapped_type; typedef uint16_t size_type; typedef std::pair size_pair_type; static constexpr size_type num_layers = 8; PartialQueue() = default; ~PartialQueue() = default; bool is_full() const { return m_ceiling == 0; } bool is_layer_full(size_type l) const { return m_layers[l].second >= m_max_layer_size; } bool is_enabled() const { return m_data != NULL; } // Add check to see if we can add more. Also make it possible to // check how full we are in the lower parts so the caller knows when // he can stop searching. // // Though propably not needed, as we must continue til the first // layer is full. size_type max_size() const { return m_max_layer_size * num_layers; } size_type max_layer_size() const { return m_max_layer_size; } // Must be less that or equal to (max size_type) / num_layers. void enable(size_type ls); void disable(); void clear(); // Safe to call while pop'ing and it will not reuse pop'ed indices // so it is guaranteed to reach max_size at some point. This will // ensure that the user needs to refill with new data at regular // intervals. bool insert(key_type key, mapped_type value); // Only call this when pop'ing as it moves the index. bool prepare_pop(); mapped_type pop(); private: PartialQueue(const PartialQueue&) = delete; PartialQueue& operator=(const PartialQueue&) = delete; static size_type ceiling(size_type layer) { return (2 << layer) - 1; } std::unique_ptr m_data; size_type m_max_layer_size{}; size_type m_index{}; size_type m_ceiling{}; std::array m_layers; }; inline void PartialQueue::enable(size_type ls) { if (ls == 0) throw std::logic_error("PartialQueue::enable(...) ls == 0."); m_data.reset(new mapped_type[ls * num_layers]); m_max_layer_size = ls; } inline void PartialQueue::disable() { m_data.reset(); m_max_layer_size = 0; } inline void PartialQueue::clear() { if (m_data == NULL) return; m_index = 0; m_ceiling = ceiling(num_layers - 1); m_layers = {}; } inline bool PartialQueue::insert(key_type key, mapped_type value) { if (key >= m_ceiling) return false; size_type idx = 0; // Hmm... since we already check the 'm_ceiling' above, we only need // to find the target layer. Could this be calculated directly? while (key >= ceiling(idx)) ++idx; m_index = std::min(m_index, idx); // Currently don't allow overflow. if (is_layer_full(idx)) throw std::logic_error("PartialQueue::insert(...) layer already full."); //return false; m_data.get()[m_max_layer_size * idx + m_layers[idx].second] = value; m_layers[idx].second++; if (is_layer_full(idx)) // Set the ceiling to 0 when layer 0 is full so no more values can // be inserted. m_ceiling = idx > 0 ? ceiling(idx - 1) : 0; return true; } // is_empty() will iterate to the first layer with un-popped elements // and return true, else return false when it reaches a overflowed or // the last layer. inline bool PartialQueue::prepare_pop() { while (m_layers[m_index].first == m_layers[m_index].second) { if (is_layer_full(m_index) || m_index + 1 == num_layers) return false; m_index++; } return true; } inline PartialQueue::mapped_type PartialQueue::pop() { if (m_index >= num_layers || m_layers[m_index].first >= m_layers[m_index].second) throw std::logic_error("PartialQueue::pop() bad state."); return m_data.get()[m_index * m_max_layer_size + m_layers[m_index].first++]; } } // namespace torrent::utils #endif // LIBTORRENT_UTILS_PARTIAL_QUEUE_H libtorrent-0.16.17/src/utils/queue_buckets.h000066400000000000000000000237101522271512000210160ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_QUEUE_BUCKETS_H #define LIBTORRENT_QUEUE_BUCKETS_H #include #include #include #include namespace torrent { template class queue_buckets : private std::array, Constants::bucket_count> { public: using queue_type = std::deque; using base_type = std::array; using constants = Constants; using value_type = typename queue_type::value_type; using size_type = typename queue_type::size_type; using difference_type = typename queue_type::difference_type; using iterator = typename queue_type::iterator; using const_iterator = typename queue_type::const_iterator; size_type queue_size(int idx) const { return queue_at(idx).size(); } bool queue_empty(int idx) const { return queue_at(idx).empty(); } bool empty() const; iterator begin(int idx) { return queue_at(idx).begin(); } iterator end(int idx) { return queue_at(idx).end(); } const_iterator begin(int idx) const { return queue_at(idx).begin(); } const_iterator end(int idx) const { return queue_at(idx).end(); } value_type front(int idx) { return queue_at(idx).front(); } value_type back(int idx) { return queue_at(idx).back(); } value_type front(int idx) const { return queue_at(idx).front(); } value_type back(int idx) const { return queue_at(idx).back(); } void pop_front(int idx); void pop_back(int idx); value_type pop_and_front(int idx); value_type pop_and_back(int idx); void push_front(int idx, const value_type& value_type); void push_back(int idx, const value_type& value_type); value_type take(int idx, iterator itr); void clear(int idx); void destroy(int idx, iterator begin, iterator end); void move_to(int src_idx, iterator src_begin, iterator src_end, int dst_idx); void move_all_to(int src_idx, int dst_idx); private: queue_type& queue_at(int idx) { return base_type::operator[](idx); } const queue_type& queue_at(int idx) const { return base_type::operator[](idx); } }; // // Helper methods: // template void queue_bucket_for_all_in_queue(QueueBucket& queues, int idx, Ftor ftor) { std::for_each(queues.begin(idx), queues.end(idx), ftor); } template void queue_bucket_for_all_in_queue(const QueueBucket& queues, int idx, Ftor ftor) { std::for_each(queues.begin(idx), queues.end(idx), ftor); } template inline typename QueueBucket::iterator queue_bucket_find_if_in_queue(QueueBucket& queues, int idx, Ftor ftor) { return std::find_if(queues.begin(idx), queues.end(idx), ftor); } template inline typename QueueBucket::const_iterator queue_bucket_find_if_in_queue(const QueueBucket& queues, int idx, Ftor ftor) { return std::find_if(queues.begin(idx), queues.end(idx), ftor); } template inline std::pair queue_bucket_find_if_in_any(QueueBucket& queues, Ftor ftor) { for (int i = 0; i < QueueBucket::constants::bucket_count; i++) { auto itr = std::find_if(queues.begin(i), queues.end(i), ftor); if (itr != queues.end(i)) return std::make_pair(i, itr); } return std::make_pair(QueueBucket::constants::bucket_count, queues.end(QueueBucket::constants::bucket_count - 1)); } template inline std::pair queue_bucket_find_if_in_any(const QueueBucket& queues, Ftor ftor) { for (int i = 0; i < QueueBucket::constants::bucket_count; i++) { typename QueueBucket::const_iterator itr = std::find_if(queues.begin(i), queues.end(i), ftor); if (itr != queues.end(i)) return std::make_pair(i, itr); } return std::make_pair(QueueBucket::constants::bucket_count, queues.end(QueueBucket::constants::bucket_count - 1)); } // // Implementation: // // TODO: Consider renaming bucket to queue. // TODO: when adding removal/etc of element or ranges do logging on if // it hit the first element or had to search. template inline bool queue_buckets::empty() const { for (int i = 0; i < constants::bucket_count; i++) if (!queue_empty(i)) return false; return true; } template inline void queue_buckets::pop_front(int idx) { queue_at(idx).pop_front(); instrumentation_update(constants::instrumentation_removed[idx], 1); instrumentation_update(constants::instrumentation_total[idx], -1); } template inline void queue_buckets::pop_back(int idx) { queue_at(idx).pop_back(); instrumentation_update(constants::instrumentation_removed[idx], 1); instrumentation_update(constants::instrumentation_total[idx], -1); } template inline typename queue_buckets::value_type queue_buckets::pop_and_front(int idx) { value_type v = queue_at(idx).front(); pop_front(idx); return v; } template inline typename queue_buckets::value_type queue_buckets::pop_and_back(int idx) { value_type v = queue_at(idx).back(); pop_back(idx); return v; } template inline void queue_buckets::push_front(int idx, const value_type& value) { queue_at(idx).push_front(value); instrumentation_update(constants::instrumentation_added[idx], 1); instrumentation_update(constants::instrumentation_total[idx], 1); } template inline void queue_buckets::push_back(int idx, const value_type& value) { queue_at(idx).push_back(value); instrumentation_update(constants::instrumentation_added[idx], 1); instrumentation_update(constants::instrumentation_total[idx], 1); } template inline typename queue_buckets::value_type queue_buckets::take(int idx, iterator itr) { value_type v = *itr; queue_at(idx).erase(itr); instrumentation_update(constants::instrumentation_removed[idx], 1); instrumentation_update(constants::instrumentation_total[idx], -1); // TODO: Add 'taken' instrumentation. return v; } template inline void queue_buckets::clear(int idx) { destroy(idx, begin(idx), end(idx)); } template inline void queue_buckets::destroy(int idx, iterator begin, iterator end) { difference_type difference = std::distance(begin, end); instrumentation_update(constants::instrumentation_removed[idx], difference); instrumentation_update(constants::instrumentation_total[idx], -difference); // Consider moving these to a temporary dequeue before releasing: std::for_each(begin, end, std::function(&constants::template destroy)); queue_at(idx).erase(begin, end); } template inline void queue_buckets::move_to(int src_idx, iterator src_begin, iterator src_end, int dst_idx) { difference_type difference = std::distance(src_begin, src_end); instrumentation_update(constants::instrumentation_moved[src_idx], difference); instrumentation_update(constants::instrumentation_total[src_idx], -difference); instrumentation_update(constants::instrumentation_added[dst_idx], difference); instrumentation_update(constants::instrumentation_total[dst_idx], difference); // TODO: Check for better move operations: if (queue_at(dst_idx).empty() && src_begin == queue_at(src_idx).begin() && src_end == queue_at(src_idx).end()) { queue_at(dst_idx).swap(queue_at(src_idx)); } else { queue_at(dst_idx).insert(queue_at(dst_idx).end(), src_begin, src_end); queue_at(src_idx).erase(src_begin, src_end); } } template inline void queue_buckets::move_all_to(int src_idx, int dst_idx) { move_to(src_idx, queue_at(src_idx).begin(), queue_at(src_idx).end(), dst_idx); } } // namespace torrent #endif // LIBTORRENT_QUEUE_BUCKETS_H libtorrent-0.16.17/src/utils/rc4.h000066400000000000000000000045641522271512000166500ustar00rootroot00000000000000// libTorrent - BitTorrent library // Copyright (C) 2005-2011, Jari Sundell // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations // including the two. // // You must obey the GNU General Public License in all respects for // all of the code used other than OpenSSL. If you modify file(s) // with this exception, you may extend this exception to your version // of the file(s), but you are not obligated to do so. If you do not // wish to do so, delete this exception statement from your version. // If you delete this exception statement from all source files in the // program, then also delete it here. // // Contact: Jari Sundell // // Skomakerveien 33 // 3185 Skoppum, NORWAY #ifndef LIBTORRENT_RC4_H #define LIBTORRENT_RC4_H #include "config.h" #include namespace torrent { class RC4 { public: RC4() { } #pragma GCC diagnostic ignored "-Wdeprecated-declarations" RC4(const unsigned char key[], int len) { RC4_set_key(&m_key, len, key); } void crypt(const void* indata, void* outdata, unsigned int length) { ::RC4(&m_key, length, static_cast(indata), static_cast(outdata)); } void crypt(void* data, unsigned int length) { ::RC4(&m_key, length, static_cast(data), static_cast(data)); } private: RC4_KEY m_key; }; }; #endif libtorrent-0.16.17/src/utils/sha1.h000066400000000000000000000034601522271512000170060ustar00rootroot00000000000000#ifndef LIBTORRENT_HASH_COMPUTE_H #define LIBTORRENT_HASH_COMPUTE_H #include #include #include #include "torrent/exceptions.h" namespace torrent { struct sha1_deleter { constexpr sha1_deleter() noexcept = default; void operator()(const EVP_MD_CTX* ctx) const { EVP_MD_CTX_free(const_cast(ctx)); } }; class Sha1 { public: void init(); void update(const void* data, unsigned int length); void final_c(void* buffer); private: std::unique_ptr m_ctx; }; inline void Sha1::init() { if (m_ctx == nullptr) m_ctx.reset(EVP_MD_CTX_new()); else EVP_MD_CTX_reset(m_ctx.get()); if (EVP_DigestInit(m_ctx.get(), EVP_sha1()) == 0) throw internal_error("Sha1::init() failed to initialize SHA-1 context."); } inline void Sha1::update(const void* data, unsigned int length) { if (EVP_DigestUpdate(m_ctx.get(), data, length) == 0) throw internal_error("Sha1::update() failed to update SHA-1 context."); } inline void Sha1::final_c(void* buffer) { if (EVP_DigestFinal(m_ctx.get(), static_cast(buffer), nullptr) == 0) throw internal_error("Sha1::final_c() failed to finalize SHA-1 context."); } inline void sha1_salt(const char* salt, unsigned int saltLength, const char* key, unsigned int keyLength, void* out) { Sha1 sha1; sha1.init(); sha1.update(salt, saltLength); sha1.update(key, keyLength); sha1.final_c(static_cast(out)); } inline void sha1_salt(const char* salt, unsigned int saltLength, const char* key1, unsigned int key1Length, const char* key2, unsigned int key2Length, void* out) { Sha1 sha1; sha1.init(); sha1.update(salt, saltLength); sha1.update(key1, key1Length); sha1.update(key2, key2Length); sha1.final_c(static_cast(out)); } } // namespace torrent #endif libtorrent-0.16.17/src/utils/thread_internal.h000066400000000000000000000017011522271512000213110ustar00rootroot00000000000000#ifndef LIBTORRENT_UTILS_THREAD_INTERNAL_H #define LIBTORRENT_UTILS_THREAD_INTERNAL_H #include "torrent/common.h" #include "torrent/system/thread.h" namespace torrent::system { class ThreadInternal { public: static auto* thread() { return Thread::m_self; } static auto thread_id() { return Thread::m_self->thread_id(); } static std::chrono::microseconds cached_time() { return Thread::m_self->m_cached_time; } static std::chrono::seconds cached_seconds() { return utils::cast_seconds(Thread::m_self->m_cached_time); } static system::Poll* poll() { return Thread::m_self->m_poll.get(); } static auto* scheduler() { return Thread::m_self->m_scheduler.get(); } static net::Resolver* resolver() { return Thread::m_self->m_resolver.get(); } }; } // namespace torrent::utils #endif // LIBTORRENT_UTILS_THREAD_INTERNAL_H libtorrent-0.16.17/test/000077500000000000000000000000001522271512000150265ustar00rootroot00000000000000libtorrent-0.16.17/test/Makefile.am000066400000000000000000000105761522271512000170730ustar00rootroot00000000000000TESTS = \ LibTorrent_Test_Torrent_Net \ LibTorrent_Test_Torrent_Utils \ LibTorrent_Test_Torrent \ LibTorrent_Test_Data \ LibTorrent_Test_Net \ LibTorrent_Test_Tracker \ LibTorrent_Test check_PROGRAMS = $(TESTS) CPPUNIT_CFLAGS += -DLT_CPPUNIT_TESTING # This can cause duplicate symbols, so export anything that causes issues. # LibTorrent_Test_LDADD = ../src/libtorrent.la LibTorrent_Test_LDADD = \ ../src/libtorrent.la \ ../src/libtorrent_other.la \ ../src/torrent/libtorrent_torrent.la LibTorrent_Test_Torrent_Net_LDADD = $(LibTorrent_Test_LDADD) LibTorrent_Test_Torrent_Utils_LDADD = $(LibTorrent_Test_LDADD) LibTorrent_Test_Torrent_LDADD = $(LibTorrent_Test_LDADD) LibTorrent_Test_Data_LDADD = $(LibTorrent_Test_LDADD) LibTorrent_Test_Net_LDADD = $(LibTorrent_Test_LDADD) LibTorrent_Test_Tracker_LDADD = $(LibTorrent_Test_LDADD) LibTorrent_Test_Common = \ main.cc \ helpers/expect_fd.h \ helpers/expect_utils.h \ helpers/mock_compare.h \ helpers/mock_function.cc \ helpers/mock_function.h \ helpers/network.cc \ helpers/network.h \ helpers/progress_listener.cc \ helpers/progress_listener.h \ helpers/protectors.cc \ helpers/protectors.h \ helpers/test_fixture.cc \ helpers/test_fixture.h \ helpers/test_main_thread.cc \ helpers/test_main_thread.h \ helpers/test_thread.cc \ helpers/test_thread.h \ helpers/test_utils.h \ helpers/tracker_test.cc \ helpers/tracker_test.h \ helpers/utils.h LibTorrent_Test_Torrent_Net_SOURCES = $(LibTorrent_Test_Common) \ torrent/net/test_address_info.cc \ torrent/net/test_address_info.h \ torrent/net/test_fd.cc \ torrent/net/test_fd.h \ torrent/net/test_socket_address.cc \ torrent/net/test_socket_address.h LibTorrent_Test_Torrent_Utils_SOURCES = $(LibTorrent_Test_Common) \ torrent/utils/test_extents.cc \ torrent/utils/test_extents.h \ torrent/utils/test_log.cc \ torrent/utils/test_log.h \ torrent/utils/test_log_buffer.cc \ torrent/utils/test_log_buffer.h \ torrent/utils/directory_events_test.cc \ torrent/utils/directory_events_test.h \ torrent/utils/test_option_strings.cc \ torrent/utils/test_option_strings.h \ torrent/utils/test_queue_buckets.cc \ torrent/utils/test_queue_buckets.h \ torrent/utils/test_thread_base.cc \ torrent/utils/test_thread_base.h \ torrent/utils/test_uri_parser.cc \ torrent/utils/test_uri_parser.h LibTorrent_Test_Torrent_SOURCES = $(LibTorrent_Test_Common) \ torrent/object_test.cc \ torrent/object_test.h \ torrent/object_test_utils.cc \ torrent/object_test_utils.h \ torrent/object_static_map_test.cc \ torrent/object_static_map_test.h \ torrent/object_stream_test.cc \ torrent/object_stream_test.h \ torrent/test_tracker_controller.cc \ torrent/test_tracker_controller.h \ torrent/test_tracker_controller_features.cc \ torrent/test_tracker_controller_features.h \ torrent/test_tracker_controller_requesting.cc \ torrent/test_tracker_controller_requesting.h \ torrent/test_tracker_list.cc \ torrent/test_tracker_list.h \ torrent/test_tracker_list_features.cc \ torrent/test_tracker_list_features.h \ torrent/test_tracker_timeout.cc \ torrent/test_tracker_timeout.h LibTorrent_Test_Data_SOURCES = $(LibTorrent_Test_Common) \ data/test_chunk_list.cc \ data/test_chunk_list.h \ data/test_hash_check_queue.cc \ data/test_hash_check_queue.h \ data/test_hash_queue.cc \ data/test_hash_queue.h LibTorrent_Test_Net_SOURCES = $(LibTorrent_Test_Common) LibTorrent_Test_Tracker_SOURCES = $(LibTorrent_Test_Common) \ tracker/test_tracker_http.cc \ tracker/test_tracker_http.h LibTorrent_Test_SOURCES = $(LibTorrent_Test_Common) \ \ rak/ranges_test.cc \ rak/ranges_test.h \ \ protocol/test_request_list.cc \ protocol/test_request_list.h LibTorrent_Test_Torrent_Net_CXXFLAGS = $(CPPUNIT_CFLAGS) LibTorrent_Test_Torrent_Net_LDFLAGS = $(CPPUNIT_LIBS) LibTorrent_Test_Torrent_Utils_CXXFLAGS = $(CPPUNIT_CFLAGS) LibTorrent_Test_Torrent_Utils_LDFLAGS = $(CPPUNIT_LIBS) LibTorrent_Test_Torrent_CXXFLAGS = $(CPPUNIT_CFLAGS) LibTorrent_Test_Torrent_LDFLAGS = $(CPPUNIT_LIBS) LibTorrent_Test_Data_CXXFLAGS = $(CPPUNIT_CFLAGS) LibTorrent_Test_Data_LDFLAGS = $(CPPUNIT_LIBS) LibTorrent_Test_Net_CXXFLAGS = $(CPPUNIT_CFLAGS) LibTorrent_Test_Net_LDFLAGS = $(CPPUNIT_LIBS) LibTorrent_Test_Tracker_CXXFLAGS = $(CPPUNIT_CFLAGS) LibTorrent_Test_Tracker_LDFLAGS = $(CPPUNIT_LIBS) LibTorrent_Test_CXXFLAGS = $(CPPUNIT_CFLAGS) LibTorrent_Test_LDFLAGS = $(CPPUNIT_LIBS) AM_CPPFLAGS = -I$(srcdir) -I$(top_srcdir) -I$(top_srcdir)/src libtorrent-0.16.17/test/data/000077500000000000000000000000001522271512000157375ustar00rootroot00000000000000libtorrent-0.16.17/test/data/test_chunk_list.cc000066400000000000000000000112321522271512000214470ustar00rootroot00000000000000#include "config.h" #include "test_chunk_list.h" #include "torrent/chunk_manager.h" #include "torrent/exceptions.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_chunk_list, "data"); torrent::Chunk* func_create_chunk(uint32_t index, [[maybe_unused]] int prot_flags) { // Do proper handling of prot_flags... char* memory_part1 = (char*)mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (memory_part1 == MAP_FAILED) throw torrent::internal_error("func_create_chunk() failed: " + std::string(strerror(errno))); std::memset(memory_part1, index, 10); torrent::Chunk* chunk = new torrent::Chunk(); chunk->push_back(torrent::ChunkPart::MAPPED_MMAP, torrent::MemoryChunk(memory_part1, memory_part1, memory_part1 + 10, torrent::MemoryChunk::prot_read, 0)); if (chunk == NULL) throw torrent::internal_error("func_create_chunk() failed: chunk == NULL."); return chunk; } uint64_t func_free_diskspace([[maybe_unused]] torrent::ChunkList* chunk_list) { return 0; } void func_storage_error([[maybe_unused]] torrent::ChunkList* chunk_list, [[maybe_unused]] const std::string& message) { } void test_chunk_list::test_basic() { torrent::ChunkManager chunk_manager; torrent::ChunkList chunk_list; CPPUNIT_ASSERT(chunk_list.flags() == 0); CPPUNIT_ASSERT(chunk_list.chunk_size() == 0); chunk_list.set_chunk_size(1 << 16); chunk_list.set_manager(&chunk_manager); chunk_list.resize(32); CPPUNIT_ASSERT(chunk_list.size() == 32); CPPUNIT_ASSERT(chunk_list.chunk_size() == (1 << 16)); for (unsigned int i = 0; i < 32; i++) CPPUNIT_ASSERT(chunk_list[i].index() == i); } void test_chunk_list::test_get_release() { SETUP_CHUNK_LIST(); CPPUNIT_ASSERT(!(*chunk_list)[0].is_valid()); torrent::ChunkHandle handle_0 = chunk_list->get(0, torrent::ChunkList::get_not_hashing); CPPUNIT_ASSERT(handle_0.object() != NULL); CPPUNIT_ASSERT(handle_0.object()->index() == 0); CPPUNIT_ASSERT(handle_0.index() == 0); CPPUNIT_ASSERT(!handle_0.is_writable()); CPPUNIT_ASSERT(!handle_0.is_blocking()); CPPUNIT_ASSERT((*chunk_list)[0].is_valid()); CPPUNIT_ASSERT((*chunk_list)[0].references() == 1); CPPUNIT_ASSERT((*chunk_list)[0].writable() == 0); CPPUNIT_ASSERT((*chunk_list)[0].blocking() == 0); chunk_list->release(&handle_0, torrent::ChunkList::release_default); torrent::ChunkHandle handle_1 = chunk_list->get(1, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_writable); CPPUNIT_ASSERT(handle_1.object() != NULL); CPPUNIT_ASSERT(handle_1.object()->index() == 1); CPPUNIT_ASSERT(handle_1.index() == 1); CPPUNIT_ASSERT(handle_1.is_writable()); CPPUNIT_ASSERT(!handle_1.is_blocking()); CPPUNIT_ASSERT((*chunk_list)[1].is_valid()); CPPUNIT_ASSERT((*chunk_list)[1].references() == 1); CPPUNIT_ASSERT((*chunk_list)[1].writable() == 1); CPPUNIT_ASSERT((*chunk_list)[1].blocking() == 0); chunk_list->release(&handle_1, torrent::ChunkList::release_default); torrent::ChunkHandle handle_2 = chunk_list->get(2, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking); CPPUNIT_ASSERT(handle_2.object() != NULL); CPPUNIT_ASSERT(handle_2.object()->index() == 2); CPPUNIT_ASSERT(handle_2.index() == 2); CPPUNIT_ASSERT(!handle_2.is_writable()); CPPUNIT_ASSERT(handle_2.is_blocking()); CPPUNIT_ASSERT((*chunk_list)[2].is_valid()); CPPUNIT_ASSERT((*chunk_list)[2].references() == 1); CPPUNIT_ASSERT((*chunk_list)[2].writable() == 0); CPPUNIT_ASSERT((*chunk_list)[2].blocking() == 1); chunk_list->release(&handle_2, torrent::ChunkList::release_default); // Test ro->wr, etc. CLEANUP_CHUNK_LIST(); } // Make sure we can't go into writable when blocking, etc. void test_chunk_list::test_blocking() { SETUP_CHUNK_LIST(); torrent::ChunkHandle handle_0_ro = chunk_list->get(0, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking); CPPUNIT_ASSERT(handle_0_ro.is_valid()); // Test writable, etc, on blocking without get_nonblock using a // timer on other thread. // torrent::ChunkHandle handle_1 = chunk_list->get(0, torrent::ChunkList::get_writable); torrent::ChunkHandle handle_0_rw = chunk_list->get(0, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_writable | torrent::ChunkList::get_nonblock); CPPUNIT_ASSERT(!handle_0_rw.is_valid()); CPPUNIT_ASSERT(handle_0_rw.error_number() == EAGAIN); chunk_list->release(&handle_0_ro, torrent::ChunkList::release_default); handle_0_rw = chunk_list->get(0, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_writable); CPPUNIT_ASSERT(handle_0_rw.is_valid()); chunk_list->release(&handle_0_rw, torrent::ChunkList::release_default); CLEANUP_CHUNK_LIST(); } // TODO: Add tests for get_hashing, etc. libtorrent-0.16.17/test/data/test_chunk_list.h000066400000000000000000000026121522271512000213130ustar00rootroot00000000000000#include "helpers/test_fixture.h" class test_chunk_list : public test_fixture { CPPUNIT_TEST_SUITE(test_chunk_list); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_get_release); CPPUNIT_TEST(test_blocking); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_get_release(); void test_blocking(); }; #include "data/chunk_list.h" torrent::Chunk* func_create_chunk(uint32_t index, int prot_flags); uint64_t func_free_diskspace(torrent::ChunkList* chunk_list); void func_storage_error(torrent::ChunkList* chunk_list, const std::string& message); #define SETUP_CHUNK_LIST() \ torrent::ChunkManager* chunk_manager = new torrent::ChunkManager; \ torrent::ChunkList* chunk_list = new torrent::ChunkList; \ chunk_list->set_manager(chunk_manager); \ chunk_list->slot_create_chunk() = std::bind(&func_create_chunk, std::placeholders::_1, std::placeholders::_2); \ chunk_list->slot_free_diskspace() = std::bind(&func_free_diskspace, chunk_list); \ chunk_list->slot_storage_error() = std::bind(&func_storage_error, chunk_list, std::placeholders::_1); \ chunk_list->set_chunk_size(1 << 16); \ chunk_list->resize(32); #define CLEANUP_CHUNK_LIST() \ delete chunk_list; \ delete chunk_manager; libtorrent-0.16.17/test/data/test_hash_check_queue.cc000066400000000000000000000135731522271512000226020ustar00rootroot00000000000000#include "config.h" #include "test_hash_check_queue.h" #include "helpers/test_thread.h" #include "helpers/test_utils.h" #include #include #include "data/chunk_handle.h" #include "data/thread_disk.h" #include "utils/sha1.h" #include "torrent/chunk_manager.h" #include "torrent/exceptions.h" #include "torrent/system/callbacks.h" #include "test_chunk_list.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_hash_check_queue, "data"); namespace { pthread_mutex_t done_chunks_lock = PTHREAD_MUTEX_INITIALIZER; void chunk_done(done_chunks_type* done_chunks, torrent::HashChunk* hash_chunk, const torrent::HashString& hash_value) { pthread_mutex_lock(&done_chunks_lock); (*done_chunks)[hash_chunk->handle().index()] = hash_value; pthread_mutex_unlock(&done_chunks_lock); } } // namespace torrent::HashString hash_for_index(uint32_t index) { char buffer[10]; std::memset(buffer, index, 10); torrent::Sha1 sha1; torrent::HashString hash; sha1.init(); sha1.update(buffer, 10); sha1.final_c(hash.data()); return hash; } bool verify_hash(const done_chunks_type* done_chunks, int index, const torrent::HashString& hash) { pthread_mutex_lock(&done_chunks_lock); done_chunks_type::const_iterator itr = done_chunks->find(index); if (itr == done_chunks->end()) { pthread_mutex_unlock(&done_chunks_lock); return false; } bool matches = itr->second == hash; pthread_mutex_unlock(&done_chunks_lock); if (!matches) { // std::cout << "chunk compare: " << index << " " // << torrent::hash_string_to_hex_str(itr->second) << ' ' << torrent::hash_string_to_hex_str(hash) << ' ' // << (itr != done_chunks->end() && itr->second == hash) // << std::endl; throw torrent::internal_error("Could not verify hash..."); } return true; } void test_hash_check_queue::test_single() { SETUP_CHUNK_LIST(); torrent::HashCheckQueue hash_queue; done_chunks_type done_chunks; hash_queue.slot_chunk_done() = std::bind(&chunk_done, &done_chunks, std::placeholders::_1, std::placeholders::_2); torrent::ChunkHandle handle_0 = chunk_list->get(0, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking); hash_queue.push_back(new torrent::HashChunk(handle_0)); // TODO: Add a way to freeze disk_thread so we can verify the state of the queue. // CPPUNIT_ASSERT(hash_queue.size() == 1); // CPPUNIT_ASSERT(hash_queue.front()->handle().is_blocking()); // CPPUNIT_ASSERT(hash_queue.front()->handle().object() == &((*chunk_list)[0])); torrent::disk_thread::callback([&hash_queue] { hash_queue.perform(); }); CPPUNIT_ASSERT(wait_for_true([&done_chunks] { return verify_hash(&done_chunks, 0, hash_for_index(0)); })); // Should not be needed... Also verify that HashChunk gets deleted. chunk_list->release(&handle_0, torrent::ChunkList::release_default); CLEANUP_CHUNK_LIST(); } void test_hash_check_queue::test_multiple() { SETUP_CHUNK_LIST(); torrent::HashCheckQueue hash_queue; done_chunks_type done_chunks; hash_queue.slot_chunk_done() = std::bind(&chunk_done, &done_chunks, std::placeholders::_1, std::placeholders::_2); handle_list handles; for (unsigned int i = 0; i < 20; i++) { handles.push_back(chunk_list->get(i, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking)); hash_queue.push_back(new torrent::HashChunk(handles.back())); // CPPUNIT_ASSERT(hash_queue.size() == i + 1); // CPPUNIT_ASSERT(hash_queue.back()->handle().is_blocking()); // CPPUNIT_ASSERT(hash_queue.back()->handle().object() == &((*chunk_list)[i])); } torrent::disk_thread::callback([&hash_queue] { hash_queue.perform(); }); CPPUNIT_ASSERT(wait_for_true([&done_chunks] { for (unsigned int i = 0; i < 20; i++) { if (!verify_hash(&done_chunks, i, hash_for_index(i))) return false; } return true; })); for (unsigned int i = 0; i < 20; i++) chunk_list->release(&handles[i], torrent::ChunkList::release_default); CLEANUP_CHUNK_LIST(); } void test_hash_check_queue::test_erase() { // SETUP_CHUNK_LIST(); // torrent::HashCheckQueue hash_queue; // done_chunks_type done_chunks; // hash_queue.slot_chunk_done() = std::bind(&chunk_done, &done_chunks, std::placeholders::_1, std::placeholders::_2); // handle_list handles; // for (unsigned int i = 0; i < 20; i++) { // handles.push_back(chunk_list->get(i, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking)); // hash_queue.push_back(new torrent::HashChunk(handles.back())); // CPPUNIT_ASSERT(hash_queue.size() == i + 1); // CPPUNIT_ASSERT(hash_queue.back()->handle().is_blocking()); // CPPUNIT_ASSERT(hash_queue.back()->handle().object() == &((*chunk_list)[i])); // } // hash_queue.perform(); // for (unsigned int i = 0; i < 20; i++) { // CPPUNIT_ASSERT(done_chunks.find(i) != done_chunks.end()); // CPPUNIT_ASSERT(done_chunks[i] == hash_for_index(i)); // // Should not be needed... // chunk_list->release(&handles[i]); // } // CLEANUP_CHUNK_LIST(); } void test_hash_check_queue::test_thread_interrupt() { SETUP_CHUNK_LIST(); torrent::HashCheckQueue* hash_queue = torrent::ThreadDisk::thread_disk()->hash_check_queue(); done_chunks_type done_chunks; hash_queue->slot_chunk_done() = std::bind(&chunk_done, &done_chunks, std::placeholders::_1, std::placeholders::_2); for (int i = 0; i < 1000; i++) { pthread_mutex_lock(&done_chunks_lock); done_chunks.erase(0); pthread_mutex_unlock(&done_chunks_lock); torrent::ChunkHandle handle_0 = chunk_list->get(0, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking); hash_queue->push_back(new torrent::HashChunk(handle_0)); torrent::disk_thread::thread()->interrupt(); CPPUNIT_ASSERT(wait_for_true(std::bind(&verify_hash, &done_chunks, 0, hash_for_index(0)))); chunk_list->release(&handle_0, torrent::ChunkList::release_default); } CLEANUP_CHUNK_LIST(); } libtorrent-0.16.17/test/data/test_hash_check_queue.h000066400000000000000000000016631522271512000224410ustar00rootroot00000000000000#ifndef TEST_DATA_HASH_CHECK_QUEUE_H #define TEST_DATA_HASH_CHECK_QUEUE_H #include #include #include "data/hash_queue_node.h" #include "data/hash_check_queue.h" #include "helpers/test_main_thread.h" #include "torrent/hash_string.h" class test_hash_check_queue : public TestFixtureWithMainAndDiskThread { CPPUNIT_TEST_SUITE(test_hash_check_queue); CPPUNIT_TEST(test_single); CPPUNIT_TEST(test_multiple); CPPUNIT_TEST(test_erase); CPPUNIT_TEST(test_thread_interrupt); CPPUNIT_TEST_SUITE_END(); public: void test_single(); void test_multiple(); void test_erase(); void test_thread_interrupt(); }; typedef std::map done_chunks_type; typedef std::vector handle_list; torrent::HashString hash_for_index(uint32_t index); bool verify_hash(const done_chunks_type* done_chunks, int index, const torrent::HashString& hash); #endif // TEST_DATA_HASH_CHECK_QUEUE_H libtorrent-0.16.17/test/data/test_hash_queue.cc000066400000000000000000000112221522271512000214320ustar00rootroot00000000000000#include "config.h" #include "test_hash_queue.h" #include #include "data/hash_queue.h" #include "data/hash_queue_node.h" #include "torrent/chunk_manager.h" #include "torrent/exceptions.h" #include "torrent/hash_string.h" #include "data/thread_disk.h" #include "test_chunk_list.h" #include "test_hash_check_queue.h" #include "helpers/test_thread.h" #include "helpers/test_utils.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_hash_queue, "data"); typedef std::map done_chunks_type; namespace { void chunk_done(torrent::ChunkList* chunk_list, done_chunks_type* done_chunks, torrent::ChunkHandle handle, const char* hash_value) { if (hash_value != NULL) (*done_chunks)[handle.index()] = *torrent::HashString::cast_from(hash_value); chunk_list->release(&handle, torrent::ChunkList::release_default); } bool check_for_chunk_done(torrent::HashQueue* hash_queue, done_chunks_type* done_chunks, int index) { hash_queue->work(); return done_chunks->find(index) != done_chunks->end(); } } // namespace #define SETUP_HASH_QUEUE() \ done_chunks_type done_chunks; \ auto hash_queue = std::make_unique(); \ \ torrent::ThreadDisk::thread_disk()->hash_check_queue()->slot_chunk_done() = [&](auto hc, const auto& hv) { \ hash_queue->chunk_done(hc, hv); \ }; void test_hash_queue::test_single() { SETUP_CHUNK_LIST(); SETUP_HASH_QUEUE(); torrent::ChunkHandle handle_0 = chunk_list->get(0, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking); hash_queue->push_back(handle_0, NULL, std::bind(&chunk_done, chunk_list, &done_chunks, std::placeholders::_1, std::placeholders::_2)); CPPUNIT_ASSERT(hash_queue->size() == 1); CPPUNIT_ASSERT(hash_queue->front().handle().is_blocking()); CPPUNIT_ASSERT(hash_queue->front().handle().object() == &((*chunk_list)[0])); hash_queue->work(); CPPUNIT_ASSERT(wait_for_true(std::bind(&check_for_chunk_done, hash_queue.get(), &done_chunks, 0))); CPPUNIT_ASSERT(done_chunks[0] == hash_for_index(0)); // chunk_list->release(&handle_0); CPPUNIT_ASSERT(torrent::ThreadDisk::thread_disk()->hash_check_queue()->empty()); hash_queue.reset(); CLEANUP_CHUNK_LIST(); } void test_hash_queue::test_multiple() { SETUP_CHUNK_LIST(); SETUP_HASH_QUEUE(); for (unsigned int i = 0; i < 20; i++) { hash_queue->push_back(chunk_list->get(i, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking), NULL, std::bind(&chunk_done, chunk_list, &done_chunks, std::placeholders::_1, std::placeholders::_2)); CPPUNIT_ASSERT(hash_queue->size() == i + 1); CPPUNIT_ASSERT(hash_queue->back().handle().is_blocking()); CPPUNIT_ASSERT(hash_queue->back().handle().object() == &((*chunk_list)[i])); } for (unsigned int i = 0; i < 20; i++) { CPPUNIT_ASSERT(wait_for_true(std::bind(&check_for_chunk_done, hash_queue.get(), &done_chunks, i))); CPPUNIT_ASSERT(done_chunks[i] == hash_for_index(i)); } CPPUNIT_ASSERT(torrent::ThreadDisk::thread_disk()->hash_check_queue()->empty()); hash_queue.reset(); CLEANUP_CHUNK_LIST(); } void test_hash_queue::test_erase() { SETUP_CHUNK_LIST(); SETUP_HASH_QUEUE(); for (unsigned int i = 0; i < 20; i++) { hash_queue->push_back(chunk_list->get(i, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking), NULL, std::bind(&chunk_done, chunk_list, &done_chunks, std::placeholders::_1, std::placeholders::_2)); CPPUNIT_ASSERT(hash_queue->size() == i + 1); } hash_queue->remove(NULL); CPPUNIT_ASSERT(hash_queue->empty()); CPPUNIT_ASSERT(torrent::ThreadDisk::thread_disk()->hash_check_queue()->empty()); hash_queue.reset(); CLEANUP_CHUNK_LIST(); } void test_hash_queue::test_erase_stress() { SETUP_CHUNK_LIST(); SETUP_HASH_QUEUE(); for (unsigned int i = 0; i < 1000; i++) { for (unsigned int i = 0; i < 20; i++) { hash_queue->push_back(chunk_list->get(i, torrent::ChunkList::get_not_hashing | torrent::ChunkList::get_blocking), NULL, std::bind(&chunk_done, chunk_list, &done_chunks, std::placeholders::_1, std::placeholders::_2)); CPPUNIT_ASSERT(hash_queue->size() == i + 1); } hash_queue->remove(NULL); CPPUNIT_ASSERT(hash_queue->empty()); } CPPUNIT_ASSERT(torrent::ThreadDisk::thread_disk()->hash_check_queue()->empty()); hash_queue.reset(); CLEANUP_CHUNK_LIST(); } // Test erase of different id's. // Current code doesn't work well if we remove a hash... libtorrent-0.16.17/test/data/test_hash_queue.h000066400000000000000000000006251522271512000213010ustar00rootroot00000000000000#include "helpers/test_main_thread.h" class test_hash_queue : public TestFixtureWithMainAndDiskThread { CPPUNIT_TEST_SUITE(test_hash_queue); CPPUNIT_TEST(test_single); CPPUNIT_TEST(test_multiple); CPPUNIT_TEST(test_erase); CPPUNIT_TEST(test_erase_stress); CPPUNIT_TEST_SUITE_END(); public: void test_single(); void test_multiple(); void test_erase(); void test_erase_stress(); }; libtorrent-0.16.17/test/helpers/000077500000000000000000000000001522271512000164705ustar00rootroot00000000000000libtorrent-0.16.17/test/helpers/expect_fd.h000066400000000000000000000103321522271512000206010ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPER_EXPECT_FD_H #define LIBTORRENT_HELPER_EXPECT_FD_H #include "helpers/mock_function.h" #include #include #include #include typedef std::vector sap_cache_type; inline const sockaddr* sap_cache_copy_addr_c_ptr(sap_cache_type& sap_cache, const torrent::c_sa_unique_ptr& sap, uint16_t port = 0) { sap_cache.push_back(torrent::sap_copy_addr(sap, port)); return sap_cache.back().get(); } inline void expect_event_open_re(int idx) { mock_expect(&torrent::this_thread::event_open_and_count, mock_compare_map::begin_pointer() + idx); mock_expect(&torrent::this_thread::event_insert_read, mock_compare_map::begin_pointer() + idx); mock_expect(&torrent::this_thread::event_insert_error, mock_compare_map::begin_pointer() + idx); } inline void expect_event_closed_fd(int idx, int fd) { mock_expect(&torrent::fd__close, 0, fd); mock_expect(&torrent::this_thread::event_closed_and_count, mock_compare_map::begin_pointer() + idx); } inline void expect_fd_inet_tcp(int fd) { mock_expect(&torrent::fd__socket, fd, (int)PF_INET, (int)SOCK_STREAM, (int)IPPROTO_TCP); } inline void expect_fd_inet6_tcp(int fd) { mock_expect(&torrent::fd__socket, fd, (int)PF_INET6, (int)SOCK_STREAM, (int)IPPROTO_TCP); } inline void expect_fd_inet_tcp_nonblock(int fd) { mock_expect(&torrent::fd__socket, fd, (int)PF_INET, (int)SOCK_STREAM, (int)IPPROTO_TCP); mock_expect(&torrent::fd__fcntl_int, 0, fd, F_SETFL, O_NONBLOCK); } inline void expect_fd_inet6_tcp_nonblock(int fd) { mock_expect(&torrent::fd__socket, fd, (int)PF_INET6, (int)SOCK_STREAM, (int)IPPROTO_TCP); mock_expect(&torrent::fd__fcntl_int, 0, fd, F_SETFL, O_NONBLOCK); } inline void expect_fd_inet_tcp_nonblock_reuseaddr(int fd) { mock_expect(&torrent::fd__socket, fd, (int)PF_INET, (int)SOCK_STREAM, (int)IPPROTO_TCP); mock_expect(&torrent::fd__fcntl_int, 0, fd, F_SETFL, O_NONBLOCK); mock_expect(&torrent::fd__setsockopt_int, 0, fd, (int)SOL_SOCKET, (int)SO_REUSEADDR, (int)true); } inline void expect_fd_inet6_tcp_nonblock_reuseaddr(int fd) { mock_expect(&torrent::fd__socket, fd, (int)PF_INET6, (int)SOCK_STREAM, (int)IPPROTO_TCP); mock_expect(&torrent::fd__fcntl_int, 0, fd, F_SETFL, O_NONBLOCK); mock_expect(&torrent::fd__setsockopt_int, 0, fd, (int)SOL_SOCKET, (int)SO_REUSEADDR, (int)true); } inline void expect_fd_inet6_tcp_v6only_nonblock(int fd) { mock_expect(&torrent::fd__socket, fd, (int)PF_INET6, (int)SOCK_STREAM, (int)IPPROTO_TCP); mock_expect(&torrent::fd__setsockopt_int, 0, fd, (int)IPPROTO_IPV6, (int)IPV6_V6ONLY, (int)true); mock_expect(&torrent::fd__fcntl_int, 0, fd, F_SETFL, O_NONBLOCK); } inline void expect_fd_inet6_tcp_v6only_nonblock_reuseaddr(int fd) { mock_expect(&torrent::fd__socket, fd, (int)PF_INET6, (int)SOCK_STREAM, (int)IPPROTO_TCP); mock_expect(&torrent::fd__setsockopt_int, 0, fd, (int)IPPROTO_IPV6, (int)IPV6_V6ONLY, (int)true); mock_expect(&torrent::fd__fcntl_int, 0, fd, F_SETFL, O_NONBLOCK); mock_expect(&torrent::fd__setsockopt_int, 0, fd, (int)SOL_SOCKET, (int)SO_REUSEADDR, (int)true); } inline void expect_fd_bind_connect(int fd, const torrent::c_sa_unique_ptr& bind_sap, const torrent::c_sa_unique_ptr& connect_sap) { mock_expect(&torrent::fd__bind, 0, fd, bind_sap.get(), (socklen_t)torrent::sap_length(bind_sap)); mock_expect(&torrent::fd__connect, 0, fd, connect_sap.get(), (socklen_t)torrent::sap_length(connect_sap)); } inline void expect_fd_bind_fail_range(int fd, sap_cache_type& sap_cache, const torrent::c_sa_unique_ptr& sap, uint16_t first_port, uint16_t last_port) { do { mock_expect(&torrent::fd__bind, -1, fd, sap_cache_copy_addr_c_ptr(sap_cache, sap, first_port), (socklen_t)torrent::sap_length(sap)); } while (first_port++ != last_port); } inline void expect_fd_bind_listen(int fd, const torrent::c_sa_unique_ptr& sap) { mock_expect(&torrent::fd__bind, 0, fd, sap.get(), (socklen_t)torrent::sap_length(sap)); mock_expect(&torrent::fd__listen, 0, fd, SOMAXCONN); } inline void expect_fd_connect(int fd, const torrent::c_sa_unique_ptr& sap) { mock_expect(&torrent::fd__connect, 0, fd, sap.get(), (socklen_t)torrent::sap_length(sap)); } #endif libtorrent-0.16.17/test/helpers/expect_utils.h000066400000000000000000000005211522271512000213470ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPER_EXPECT_UTILS_H #define LIBTORRENT_HELPER_EXPECT_UTILS_H #include "helpers/mock_function.h" #include inline void expect_random_uniform_uint16(uint16_t result, uint16_t first, uint16_t last) { mock_expect(&torrent::random_uniform_uint16, result, first, last); } #endif libtorrent-0.16.17/test/helpers/mock_compare.h000066400000000000000000000061471522271512000213100ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPERS_MOCK_COMPARE_H #define LIBTORRENT_HELPERS_MOCK_COMPARE_H #include #include #include #include #include // Compare arguments to mock functions with what is expected. The lhs // are the expected arguments, rhs are the ones called with. template inline bool mock_compare_arg(Arg lhs, Arg rhs) { return lhs == rhs; } template std::enable_if_t mock_compare_tuple(const std::tuple& lhs, const std::tuple& rhs) { return mock_compare_arg(std::get(lhs), std::get(rhs)) ? 0 : 1; } template std::enable_if_t<1 < I, int> mock_compare_tuple(const std::tuple& lhs, const std::tuple& rhs) { auto res = mock_compare_tuple(lhs, rhs); if (res != 0) return res; return mock_compare_arg(std::get(lhs), std::get(rhs)) ? 0 : I; } //template ::value, int>::type = 0> template struct mock_compare_map { typedef std::map values_type; static T* begin_pointer() { return reinterpret_cast(0x1000); } static T* end_pointer() { return reinterpret_cast(0x2000); } static bool is_key(const T* k) { return k >= begin_pointer() && k < end_pointer(); } static bool has_key(const T* k) { return values.find(k) != values.end(); } static bool has_value(const T* v) { return std::find_if(values.begin(), values.end(), [v](typename values_type::value_type& kv) { return v == kv.second; }) != values.end(); } static const T* get(const T* k) { auto itr = values.find(k); CPPUNIT_ASSERT_MESSAGE("mock_compare_map get failed, not inserted", itr != values.end()); return itr->second; } static values_type values; }; template typename mock_compare_map::values_type mock_compare_map::values; template void mock_compare_add(T* v) { mock_compare_map::add_value(v); } // // Specialize: // constexpr int mock_compare_gt_two_int = -0xFD30; template <> inline bool mock_compare_arg(int lhs, int rhs) { if (lhs == mock_compare_gt_two_int) return rhs > 2; if (rhs == mock_compare_gt_two_int) return lhs > 2; return lhs == rhs; } template <> inline bool mock_compare_arg(sockaddr* lhs, sockaddr* rhs) { return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs); } template <> inline bool mock_compare_arg(const sockaddr* lhs, const sockaddr* rhs) { return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs); } template <> inline bool mock_compare_arg(torrent::Event* lhs, torrent::Event* rhs) { if (mock_compare_map::is_key(lhs)) { if (!mock_compare_map::has_value(rhs)) { mock_compare_map::values[lhs] = rhs; return true; } return mock_compare_map::has_key(lhs) && mock_compare_map::get(lhs) == rhs; } return lhs == rhs; } #endif libtorrent-0.16.17/test/helpers/mock_function.cc000066400000000000000000000163231522271512000216420ustar00rootroot00000000000000#include "config.h" #include "test/helpers/mock_function.h" #include #include #include #include "torrent/event.h" #include "torrent/net/socket_address.h" #include "torrent/net/fd.h" #include "torrent/utils/log.h" #include "torrent/utils/random.h" #define MOCK_CLEANUP_MAP(MOCK_FUNC) \ CPPUNIT_ASSERT_MESSAGE("expected mock function calls not completed for '" #MOCK_FUNC "'", mock_cleanup_map(&MOCK_FUNC) || ignore_assert); #define MOCK_LOG(log_fmt, ...) \ lt_log_print(torrent::LOG_MOCK_CALLS, "%s: " log_fmt, __func__, __VA_ARGS__); namespace { void mock_clear(bool ignore_assert) { MOCK_CLEANUP_MAP(torrent::fd__accept); MOCK_CLEANUP_MAP(torrent::fd__bind); MOCK_CLEANUP_MAP(torrent::fd__close); MOCK_CLEANUP_MAP(torrent::fd__connect); MOCK_CLEANUP_MAP(torrent::fd__fcntl_int); MOCK_CLEANUP_MAP(torrent::fd__listen); MOCK_CLEANUP_MAP(torrent::fd__setsockopt_int); MOCK_CLEANUP_MAP(torrent::fd__socket); MOCK_CLEANUP_MAP(torrent::random_uniform_uint16); MOCK_CLEANUP_MAP(torrent::random_uniform_uint32); mock_compare_map::values.clear(); } } // namespace void mock_init() { log_add_group_output(torrent::LOG_MOCK_CALLS, "test_output"); mock_clear(true); } void mock_cleanup() { mock_clear(false); } void mock_redirect_defaults([[maybe_unused]] mock_redirect_flags flags) { mock_redirect(torrent::fd__bind, std::function([](int socket, const sockaddr *address, socklen_t address_len) { return ::bind(socket, address, address_len); })); mock_redirect(torrent::fd__close, std::function([](int fildes) { return ::close(fildes); })); mock_redirect(torrent::fd__fcntl_int, std::function([](int fildes, int cmd, int arg) { return ::fcntl(fildes, cmd, arg); })); mock_redirect(torrent::fd__setsockopt_int, std::function([](int socket, int level, int option_name, int option_value) { return ::setsockopt(socket, level, option_name, &option_value, sizeof(int)); })); mock_redirect(torrent::fd__socket, std::function([](int domain, int type, int protocol) { return ::socket(domain, type, protocol); })); } namespace torrent { // // Mock functions for 'torrent/net/fd.h': // int fd__accept(int socket, sockaddr *address, socklen_t *address_len) { MOCK_LOG("entry socket:%i address:%s address_len:%u", socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)(*address_len)); auto ret = mock_call(__func__, &torrent::fd__accept, socket, address, address_len); MOCK_LOG("exit socket:%i address:%s address_len:%u", socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)(*address_len)); return ret; } int fd__bind(int socket, const sockaddr *address, socklen_t address_len) { MOCK_LOG("socket:%i address:%s address_len:%u", socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)address_len); return mock_call(__func__, &torrent::fd__bind, socket, address, address_len); } int fd__close(int fildes) { MOCK_LOG("filedes:%i", fildes); return mock_call(__func__, &torrent::fd__close, fildes); } int fd__connect(int socket, const sockaddr *address, socklen_t address_len) { MOCK_LOG("socket:%i address:%s address_len:%u", socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)address_len); return mock_call(__func__, &torrent::fd__connect, socket, address, address_len); } int fd__fcntl_int(int fildes, int cmd, int arg) { MOCK_LOG("filedes:%i cmd:%i arg:%i", fildes, cmd, arg); return mock_call(__func__, &torrent::fd__fcntl_int, fildes, cmd, arg); } int fd__listen(int socket, int backlog) { MOCK_LOG("socket:%i backlog:%i", socket, backlog); return mock_call(__func__, &torrent::fd__listen, socket, backlog); } int fd__setsockopt_int(int socket, int level, int option_name, int option_value) { MOCK_LOG("socket:%i level:%i option_name:%i option_value:%i", socket, level, option_name, option_value); return mock_call(__func__, &torrent::fd__setsockopt_int, socket, level, option_name, option_value); } int fd__socket(int domain, int type, int protocol) { MOCK_LOG("domain:%i type:%i protocol:%i", domain, type, protocol); return mock_call(__func__, &torrent::fd__socket, domain, type, protocol); } // // Mock functions for 'torrent/common.h': // namespace this_thread { void event_open(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_open, event); } void event_open_and_count(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_open_and_count, event); } void event_close_and_count(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_close_and_count, event); } void event_closed_and_count(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_closed_and_count, event); } void event_insert_read(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_insert_read, event); } void event_insert_write(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_insert_write, event); } void event_insert_error(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_insert_error, event); } void event_remove_read(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_remove_read, event); } void event_remove_write(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_remove_write, event); } void event_remove_error(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_remove_error, event); } void event_remove_and_close(Event* event) { MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name()); return mock_call(__func__, &torrent::this_thread::event_remove_and_close, event); } } // // Mock functions for 'torrent/utils/random.h': // uint16_t random_uniform_uint16(uint16_t min, uint16_t max) { MOCK_LOG("min:%" PRIu16 " max:%" PRIu16, min, max); return mock_call(__func__, &torrent::random_uniform_uint16, min, max); } uint32_t random_uniform_uint32(uint32_t min, uint32_t max) { MOCK_LOG("min:%" PRIu32 " max:%" PRIu32, min, max); return mock_call(__func__, &torrent::random_uniform_uint32, min, max); } } libtorrent-0.16.17/test/helpers/mock_function.h000066400000000000000000000133061522271512000215020ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPERS_MOCK_FUNCTION_H #define LIBTORRENT_HELPERS_MOCK_FUNCTION_H #include #include #include #include #include #include #include #include #include "test/helpers/mock_compare.h" namespace torrent { extern int fd__accept(int socket, sockaddr *address, socklen_t *address_len); extern int fd__bind(int socket, const sockaddr *address, socklen_t address_len); extern int fd__close(int fildes); extern int fd__connect(int socket, const sockaddr *address, socklen_t address_len); extern int fd__fcntl_int(int fildes, int cmd, int arg); extern int fd__listen(int socket, int backlog); extern int fd__setsockopt_int(int socket, int level, int option_name, int option_value); extern int fd__socket(int domain, int type, int protocol); } enum mock_redirect_flags { mock_redirect_all = ~0, }; void mock_init(); void mock_cleanup(); void mock_redirect_defaults(mock_redirect_flags flags = mock_redirect_all); template struct align_cacheline mock_function_map { typedef std::tuple call_type; typedef std::vector call_list_type; typedef std::map func_map_type; typedef std::function function_type; typedef std::map redirect_map_type; static std::mutex mutex; static func_map_type functions; static redirect_map_type redirects; static bool cleanup(void* fn) { std::lock_guard lock(mutex); redirects.erase(fn); return functions.erase(fn) == 0; } static R ret_erase(void* fn) { auto itr = functions.find(fn); auto ret = std::get<0>(itr->second.front()); itr->second.erase(itr->second.begin()); if (itr->second.empty()) functions.erase(itr); return ret; } }; template std::mutex mock_function_map::mutex; template typename mock_function_map::func_map_type mock_function_map::functions; template typename mock_function_map::redirect_map_type mock_function_map::redirects; struct mock_void {}; template struct mock_function_type { typedef mock_function_map type; static int compare_expected(typename type::call_type lhs, Args... rhs) { return mock_compare_tuple(lhs, std::make_tuple(rhs...)); } static R ret_erase(void* fn) { return type::ret_erase(fn); } static bool has_redirect(void* fn) { std::lock_guard lock(type::mutex); return type::redirects.find(fn) != type::redirects.end(); } static R call_redirect(void* fn, Args... args) { std::lock_guard lock(type::mutex); return type::redirects.find(fn)->second(args...); } }; template struct mock_function_type { typedef mock_function_map type; static int compare_expected(typename type::call_type lhs, Args... rhs) { return mock_compare_tuple(lhs, std::make_tuple(rhs...)); } static void ret_erase(void* fn) { type::ret_erase(fn); } static bool has_redirect(void* fn) { std::lock_guard lock(type::mutex); return type::redirects.find(fn) != type::redirects.end(); } static void call_redirect(void* fn, Args... args) { std::lock_guard lock(type::mutex); type::redirects.find(fn)->second(args...); } }; template bool mock_cleanup_map(R fn(Args...)) { return mock_function_type::type::cleanup(reinterpret_cast(fn)); } template void mock_expect(R fn(Args...), R ret, Args... args) { typedef mock_function_map mock_map; std::lock_guard lock(mock_map::mutex); mock_map::functions[reinterpret_cast(fn)].push_back(std::tuple(ret, args...)); } template void mock_expect(void fn(Args...), Args... args) { typedef mock_function_map mock_map; std::lock_guard lock(mock_map::mutex); mock_map::functions[reinterpret_cast(fn)].push_back(std::tuple(mock_void(), args...)); } template void mock_redirect(R fn(Args...), std::function func) { typedef mock_function_map mock_map; std::lock_guard lock(mock_map::mutex); mock_map::redirects[reinterpret_cast(fn)] = func; } template auto mock_call_direct(std::string name, R fn(Args...), Args... args) -> decltype(fn(args...)) { typedef mock_function_type mock_type; auto guard = std::scoped_lock(mock_type::type::mutex); auto itr = mock_type::type::functions.find(reinterpret_cast(fn)); CPPUNIT_ASSERT_MESSAGE(("mock_call expected function calls exhausted by '" + name + "'").c_str(), itr != mock_type::type::functions.end()); auto mismatch_arg = mock_type::compare_expected(itr->second.front(), args...); CPPUNIT_ASSERT_MESSAGE(("mock_call expected function call argument " + std::to_string(mismatch_arg) + " mismatch for '" + name + "'").c_str(), mismatch_arg == 0); return mock_type::ret_erase(reinterpret_cast(fn)); } template auto mock_call(std::string name, R fn(Args...), Args... args) -> decltype(fn(args...)) { typedef mock_function_type mock_type; if (mock_type::has_redirect(reinterpret_cast(fn))) return mock_type::call_redirect(reinterpret_cast(fn), args...); return mock_call_direct(name, fn, args...); } #endif libtorrent-0.16.17/test/helpers/network.cc000066400000000000000000000011461522271512000204720ustar00rootroot00000000000000#include "config.h" #include "test/helpers/network.h" #include #include "torrent/event.h" bool check_event_is_readable(torrent::Event* event, std::chrono::microseconds timeout) { int fd = event->file_descriptor(); fd_set read_fds; FD_ZERO(&read_fds); FD_SET(fd, &read_fds); struct timeval t = { static_cast(std::chrono::duration_cast(timeout).count()), static_cast(timeout.count() % 1000000) }; int result = select(fd + 1, &read_fds, nullptr, nullptr, &t); CPPUNIT_ASSERT(result != -1); return FD_ISSET(fd, &read_fds); } libtorrent-0.16.17/test/helpers/network.h000066400000000000000000000215301522271512000203330ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPER_NETWORK_H #define LIBTORRENT_HELPER_NETWORK_H #include #include #include #include "torrent/common.h" #include "torrent/net/address_info.h" #include "torrent/utils/chrono.h" bool check_event_is_readable(torrent::Event* event, std::chrono::microseconds timeout); // // Socket addresses // #define TEST_DEFAULT_SA \ auto sin_any = wrap_ai_get_first_sa("0.0.0.0"); \ auto sin_any_5000 = wrap_ai_get_first_sa("0.0.0.0", "5000"); \ auto sin_any_5005 = wrap_ai_get_first_sa("0.0.0.0", "5005"); \ auto sin_bc = wrap_ai_get_first_sa("255.255.255.255"); \ auto sin_bc_5000 = wrap_ai_get_first_sa("255.255.255.255", "5000"); \ auto sin_bnd = wrap_ai_get_first_sa("123.123.123.123"); \ auto sin_1 = wrap_ai_get_first_sa("1.2.3.4"); \ auto sin_1_5000 = wrap_ai_get_first_sa("1.2.3.4", "5000"); \ auto sin_1_5005 = wrap_ai_get_first_sa("1.2.3.4", "5005"); \ auto sin_1_5100 = wrap_ai_get_first_sa("1.2.3.4", "5100"); \ auto sin_2 = wrap_ai_get_first_sa("4.3.2.1"); \ auto sin_2_5000 = wrap_ai_get_first_sa("4.3.2.1", "5000"); \ auto sin_2_5100 = wrap_ai_get_first_sa("4.3.2.1", "5100"); \ \ auto sin6_any = wrap_ai_get_first_sa("::"); \ auto sin6_any_5000 = wrap_ai_get_first_sa("::", "5000"); \ auto sin6_any_5005 = wrap_ai_get_first_sa("::", "5005"); \ auto sin6_bnd = wrap_ai_get_first_sa("ff01::123"); \ auto sin6_1 = wrap_ai_get_first_sa("ff01::1"); \ auto sin6_1_5000 = wrap_ai_get_first_sa("ff01::1", "5000"); \ auto sin6_1_5005 = wrap_ai_get_first_sa("ff01::1", "5005"); \ auto sin6_1_5100 = wrap_ai_get_first_sa("ff01::1", "5100"); \ auto sin6_2 = wrap_ai_get_first_sa("ff02::2"); \ auto sin6_2_5000 = wrap_ai_get_first_sa("ff02::2", "5000"); \ auto sin6_2_5100 = wrap_ai_get_first_sa("ff02::2", "5100"); \ auto sin6_v4_1 = wrap_ai_get_first_sa("::ffff:1.2.3.4"); \ auto sin6_v4_1_5000 = wrap_ai_get_first_sa("::ffff:1.2.3.4", "5000"); \ auto sin6_v4_any = wrap_ai_get_first_sa("::ffff:0.0.0.0"); \ auto sin6_v4_any_5000 = wrap_ai_get_first_sa("::ffff:0.0.0.0", "5000"); \ auto sin6_v4_bc = wrap_ai_get_first_sa("::ffff:255.255.255.255"); \ auto sin6_v4_bc_5000 = wrap_ai_get_first_sa("::ffff:255.255.255.255", "5000"); \ auto sin6_v4_bnd = wrap_ai_get_first_sa("::ffff:123.123.123.123"); \ \ auto c_sin_any = wrap_ai_get_first_c_sa("0.0.0.0"); \ auto c_sin_any_5000 = wrap_ai_get_first_c_sa("0.0.0.0", "5000"); \ auto c_sin_any_5005 = wrap_ai_get_first_c_sa("0.0.0.0", "5005"); \ auto c_sin_any_5010 = wrap_ai_get_first_c_sa("0.0.0.0", "5010"); \ auto c_sin_any_6881 = wrap_ai_get_first_c_sa("0.0.0.0", "6881"); \ auto c_sin_any_6900 = wrap_ai_get_first_c_sa("0.0.0.0", "6900"); \ auto c_sin_any_6999 = wrap_ai_get_first_c_sa("0.0.0.0", "6999"); \ auto c_sin_bc = wrap_ai_get_first_c_sa("255.255.255.255"); \ auto c_sin_bc_5000 = wrap_ai_get_first_c_sa("255.255.255.255", "5000"); \ auto c_sin_bnd = wrap_ai_get_first_c_sa("123.123.123.123"); \ auto c_sin_bnd_5000 = wrap_ai_get_first_c_sa("123.123.123.123", "5000"); \ auto c_sin_bnd_6881 = wrap_ai_get_first_c_sa("123.123.123.123", "6881"); \ auto c_sin_bnd_6900 = wrap_ai_get_first_c_sa("123.123.123.123", "6900"); \ auto c_sin_bnd_6999 = wrap_ai_get_first_c_sa("123.123.123.123", "6999"); \ auto c_sin_1 = wrap_ai_get_first_c_sa("1.2.3.4"); \ auto c_sin_1_5000 = wrap_ai_get_first_c_sa("1.2.3.4", "5000"); \ auto c_sin_1_5005 = wrap_ai_get_first_c_sa("1.2.3.4", "5005"); \ auto c_sin_1_5010 = wrap_ai_get_first_c_sa("1.2.3.4", "5010"); \ auto c_sin_1_6881 = wrap_ai_get_first_c_sa("1.2.3.4", "6881"); \ auto c_sin_1_6900 = wrap_ai_get_first_c_sa("1.2.3.4", "6900"); \ auto c_sin_1_6999 = wrap_ai_get_first_c_sa("1.2.3.4", "6999"); \ auto c_sin_2 = wrap_ai_get_first_c_sa("4.3.2.1"); \ auto c_sin_2_5000 = wrap_ai_get_first_c_sa("4.3.2.1", "5000"); \ auto c_sin_2_5100 = wrap_ai_get_first_c_sa("4.3.2.1", "5100"); \ \ auto c_sin6_any = wrap_ai_get_first_c_sa("::"); \ auto c_sin6_any_5000 = wrap_ai_get_first_c_sa("::", "5000"); \ auto c_sin6_any_5005 = wrap_ai_get_first_c_sa("::", "5005"); \ auto c_sin6_any_5010 = wrap_ai_get_first_c_sa("::", "5010"); \ auto c_sin6_any_6881 = wrap_ai_get_first_c_sa("::", "6881"); \ auto c_sin6_any_6900 = wrap_ai_get_first_c_sa("::", "6900"); \ auto c_sin6_any_6999 = wrap_ai_get_first_c_sa("::", "6999"); \ auto c_sin6_bnd = wrap_ai_get_first_c_sa("ff01::123"); \ auto c_sin6_bnd_5000 = wrap_ai_get_first_c_sa("ff01::123", "5000"); \ auto c_sin6_bnd_6881 = wrap_ai_get_first_c_sa("ff01::123", "6881"); \ auto c_sin6_bnd_6900 = wrap_ai_get_first_c_sa("ff01::123", "6900"); \ auto c_sin6_bnd_6999 = wrap_ai_get_first_c_sa("ff01::123", "6999"); \ auto c_sin6_v4_1_5000 = wrap_ai_get_first_c_sa("::ffff:1.2.3.4", "5000"); \ auto c_sin6_1 = wrap_ai_get_first_c_sa("ff01::1"); \ auto c_sin6_1_5000 = wrap_ai_get_first_c_sa("ff01::1", "5000"); \ auto c_sin6_1_5005 = wrap_ai_get_first_c_sa("ff01::1", "5005"); \ auto c_sin6_1_5010 = wrap_ai_get_first_c_sa("ff01::1", "5010"); \ auto c_sin6_1_5100 = wrap_ai_get_first_c_sa("ff01::1", "5100"); \ auto c_sin6_1_6881 = wrap_ai_get_first_c_sa("ff01::1", "6881"); \ auto c_sin6_1_6900 = wrap_ai_get_first_c_sa("ff01::1", "6900"); \ auto c_sin6_1_6999 = wrap_ai_get_first_c_sa("ff01::1", "6999"); \ auto c_sin6_2 = wrap_ai_get_first_c_sa("ff02::2"); \ auto c_sin6_2_5000 = wrap_ai_get_first_c_sa("ff02::2", "5000"); \ auto c_sin6_2_5100 = wrap_ai_get_first_c_sa("ff02::2", "5100"); inline bool compare_sin6_addr(in6_addr lhs, in6_addr rhs) { return std::equal(lhs.s6_addr, lhs.s6_addr + 16, rhs.s6_addr); } inline bool compare_listen_result(const torrent::listen_result_type& lhs, int rhs_fd, const torrent::c_sa_unique_ptr& rhs_sap) { return lhs.fd == rhs_fd && ((lhs.address && rhs_sap) || ((lhs.address && rhs_sap) && torrent::sap_equal(lhs.address, rhs_sap))); } inline torrent::sa_unique_ptr wrap_ai_get_first_sa(const char* nodename, const char* servname = nullptr, const addrinfo* hints = nullptr) { auto sa = torrent::ai_get_first_sa(nodename, servname, hints); CPPUNIT_ASSERT_MESSAGE(("wrap_ai_get_first_sa: nodename:'" + std::string(nodename) + "'").c_str(), sa != nullptr); return sa; } inline torrent::c_sa_unique_ptr wrap_ai_get_first_c_sa(const char* nodename, const char* servname = nullptr, const addrinfo* hints = nullptr) { auto sa = torrent::ai_get_first_sa(nodename, servname, hints); CPPUNIT_ASSERT_MESSAGE(("wrap_ai_get_first_sa: nodename:'" + std::string(nodename) + "'").c_str(), sa != nullptr); return torrent::c_sa_unique_ptr(sa.release()); } // // Address info tests: // typedef std::function test_ai_ref; enum ai_flags_enum : int { aif_none = 0x0, aif_inet = 0x1, aif_inet6 = 0x2, aif_any = 0x4, }; constexpr ai_flags_enum operator | (ai_flags_enum a, ai_flags_enum b) { return static_cast(static_cast(a) | static_cast(b)); } template inline bool test_valid_ai_ref(test_ai_ref ftor, uint16_t port = 0) { torrent::ai_unique_ptr ai; if (int err = ftor(ai)) { std::cout << std::endl << "valid_ai_ref got error '" << gai_strerror(err) << "'" << std::endl; return false; } if ((ai_flags & aif_inet) && !torrent::sa_is_inet(ai->ai_addr)) return false; if ((ai_flags & aif_inet6) && !torrent::sa_is_inet6(ai->ai_addr)) return false; if (!!(ai_flags & aif_any) == !torrent::sa_is_any(ai->ai_addr)) return false; if (torrent::sa_port(ai->ai_addr) != port) return false; return true; } inline bool test_valid_ai_ref_err(test_ai_ref ftor, int expect_err) { torrent::ai_unique_ptr ai; int err = ftor(ai); if (err != expect_err) { std::cout << std::endl << "ai_ref_err got wrong error, expected '" << gai_strerror(expect_err) << "', got '" << gai_strerror(err) << "'" << std::endl; return false; } return true; } #endif libtorrent-0.16.17/test/helpers/progress_listener.cc000066400000000000000000000032631522271512000225540ustar00rootroot00000000000000#include "config.h" #include "progress_listener.h" #include #include #include #include #include #include "torrent/utils/log.h" #include "torrent/utils/log_buffer.h" #include static std::string get_test_path(const test_list_type& tl) { if (tl.size() < 2) return ""; return std::accumulate(std::next(tl.begin()), std::prev(tl.end()), std::string(), [](std::string result, CppUnit::Test* test) { return std::move(result) + test->getName() + "::"; }); } void progress_listener::startTest(CppUnit::Test *test) { std::cout << get_test_path(m_test_path) << test->getName() << std::flush; torrent::log_cleanup(); m_last_test_failed = false; m_current_log_buffer = torrent::log_open_log_buffer("test_output"); } void progress_listener::addFailure(const CppUnit::TestFailure &failure) { // AddFailure is called for parent test suits, so only deal with leafs. if (m_current_log_buffer == nullptr) return; std::cout << " : " << (failure.isError() ? "error" : "assertion") << std::flush; m_last_test_failed = true; m_failures.push_back(failure_type{ failure.failedTestName(), std::move(m_current_log_buffer) }); } void progress_listener::endTest(CppUnit::Test *test) { std::cout << (m_last_test_failed ? "" : " : OK") << std::endl; m_current_log_buffer.reset(); torrent::log_cleanup(); } void progress_listener::startSuite(CppUnit::Test *suite) { m_test_path.push_back(suite); if (suite->countTestCases() > 0) std::cout << std::endl << get_test_path(m_test_path) << suite->getName() << ":" << std::endl; } void progress_listener::endSuite(CppUnit::Test *suite) { m_test_path.pop_back(); } libtorrent-0.16.17/test/helpers/progress_listener.h000066400000000000000000000027311522271512000224150ustar00rootroot00000000000000#include #include #include #include #include #include "torrent/utils/log_buffer.h" struct failure_type { std::string name; torrent::log_buffer_ptr log; }; typedef std::unique_ptr test_failure_ptr; typedef std::vector test_list_type; typedef std::vector failure_list_type; class progress_listener : public CppUnit::TestListener { public: progress_listener() : m_last_test_failed(false) {} void startTest(CppUnit::Test *test) override; void addFailure(const CppUnit::TestFailure &failure) override; void endTest(CppUnit::Test *test) override; void startSuite(CppUnit::Test *suite) override; void endSuite(CppUnit::Test *suite) override; //Called by a TestRunner before running the test. // void startTestRun(CppUnit::Test *test, CppUnit::TestResult *event_manager) override; // Called by a TestRunner after running the test. // void endTestRun(CppUnit::Test *test, CppUnit::TestResult *event_manager) override; const failure_list_type& failures() { return m_failures; } failure_list_type&& move_failures() { return std::move(m_failures); } private: progress_listener(const progress_listener& rhs) = delete; void operator =(const progress_listener& rhs) = delete; test_list_type m_test_path; failure_list_type m_failures; bool m_last_test_failed; torrent::log_buffer_ptr m_current_log_buffer; }; libtorrent-0.16.17/test/helpers/protectors.cc000066400000000000000000000021251522271512000212030ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include bool ExceptionProtector::protect(const CppUnit::Functor& functor, const CppUnit::ProtectorContext& context) { try { return functor(); } catch (const CppUnit::Exception& failure) { reportFailure( context, failure ); } catch (const torrent::base_error& e) { std::string short_description("uncaught exception of base type torrent::base_error: " + std::string(typeid(e).name())); CppUnit::Message message(short_description, e.what()); reportError(context, message); } catch (const std::exception& e) { std::string short_description("uncaught exception of type "); short_description += CppUnit::TypeInfoHelper::getClassName(typeid(e)); CppUnit::Message message(short_description, e.what()); reportError(context, message); } catch (...) { reportError(context, CppUnit::Message("uncaught exception of unknown type")); } return false; } libtorrent-0.16.17/test/helpers/protectors.h000066400000000000000000000005471522271512000210530ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPER_PROTECTORS_H #define LIBTORRENT_HELPER_PROTECTORS_H #include #include #include class ExceptionProtector : public CppUnit::Protector { public: bool protect(const CppUnit::Functor &functor, const CppUnit::ProtectorContext &context) override; }; #endif // LIBTORRENT_HELPER_PROTECTORS_H libtorrent-0.16.17/test/helpers/test_fixture.cc000066400000000000000000000005211522271512000215220ustar00rootroot00000000000000#include "config.h" #include "test_fixture.h" #include "torrent/utils/log.h" #include void test_fixture::setUp() { mock_init(); log_add_group_output(torrent::LOG_CONNECTION_BIND, "test_output"); log_add_group_output(torrent::LOG_CONNECTION_FD, "test_output"); } void test_fixture::tearDown() { mock_cleanup(); } libtorrent-0.16.17/test/helpers/test_fixture.h000066400000000000000000000004311522271512000213640ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPER_TEST_FIXTURE_H #define LIBTORRENT_HELPER_TEST_FIXTURE_H #include #include "test/helpers/mock_function.h" class test_fixture : public CppUnit::TestFixture { public: void setUp() override; void tearDown() override; }; #endif libtorrent-0.16.17/test/helpers/test_main_thread.cc000066400000000000000000000114751522271512000223210ustar00rootroot00000000000000#include "config.h" #include "test_main_thread.h" #include #include "runtime.h" #include "thread_main.h" #include "data/thread_disk.h" #include "net/thread_net.h" #include "test/helpers/mock_function.h" #include "torrent/exceptions.h" #include "torrent/net/resolver.h" #include "torrent/utils/log.h" #include "torrent/utils/scheduler.h" #include "tracker/thread_tracker.h" std::unique_ptr TestMainThread::create() { // Needs to be called before Thread is created. mock_redirect_defaults(); auto main_thread = std::unique_ptr(new TestMainThread()); torrent::ThreadMain::set_thread_base(main_thread.get()); return main_thread; } std::unique_ptr TestMainThread::create_with_mock() { auto main_thread = std::unique_ptr(new TestMainThread()); torrent::ThreadMain::set_thread_base(main_thread.get()); return main_thread; } void TestMainThread::destroy() { torrent::ThreadMain::set_thread_base(nullptr); } void TestMainThread::init_thread() { m_resolver = std::make_unique(); m_state = STATE_INITIALIZED; //m_instrumentation_index = INSTRUMENTATION_POLLING_DO_POLL_MAIN - INSTRUMENTATION_POLLING_DO_POLL; init_thread_local(); } void TestMainThread::cleanup_thread() { } void TestMainThread::test_set_cached_time(std::chrono::microseconds t) { set_cached_time(365 * 24h + t); } void TestMainThread::test_add_cached_time(std::chrono::microseconds t) { set_cached_time(cached_time() + t); } void TestMainThread::test_process_events_without_cached_time() { process_events_without_cached_time(); } void TestMainThread::call_events() { process_callbacks(); } std::chrono::microseconds TestMainThread::next_timeout() { return 10min; } void TestFixtureWithMainThread::setUp() { test_fixture::setUp(); m_main_thread = TestMainThread::create(); torrent::Runtime::initialize(); m_main_thread->init_thread(); } void TestFixtureWithMainThread::tearDown() { torrent::Runtime::cleanup(); m_main_thread.reset(); TestMainThread::destroy(); test_fixture::tearDown(); } void TestFixtureWithMainAndDiskThread::setUp() { test_fixture::setUp(); m_main_thread = TestMainThread::create(); torrent::Runtime::initialize(); m_main_thread->init_thread(); // m_hash_check_queue.slot_chunk_done() binds to main_thread(). torrent::ThreadDisk::create_thread(); torrent::disk_thread::thread()->init_thread(); torrent::disk_thread::thread()->start_thread(); signal(SIGUSR1, [](auto){}); } void TestFixtureWithMainAndDiskThread::tearDown() { torrent::disk_thread::thread()->stop_thread_wait(); torrent::Runtime::cleanup(); torrent::ThreadDisk::destroy_thread(); TestMainThread::destroy(); m_main_thread.reset(); test_fixture::tearDown(); } void TestFixtureWithMainAndTrackerThread::setUp() { test_fixture::setUp(); m_main_thread = TestMainThread::create(); torrent::Runtime::initialize(); m_main_thread->init_thread(); log_add_group_output(torrent::LOG_TRACKER_EVENTS, "test_output"); log_add_group_output(torrent::LOG_TRACKER_REQUESTS, "test_output"); torrent::ThreadTracker::create_thread(); torrent::tracker_thread::thread()->init_thread(); torrent::tracker_thread::thread()->start_thread(); } void TestFixtureWithMainAndTrackerThread::tearDown() { torrent::tracker_thread::thread()->stop_thread_wait(); torrent::Runtime::cleanup(); torrent::ThreadTracker::destroy_thread(); TestMainThread::destroy(); m_main_thread.reset(); test_fixture::tearDown(); } void TestFixtureWithMainNetTrackerThread::setUp() { test_fixture::setUp(); m_main_thread = TestMainThread::create(); torrent::Runtime::initialize(); m_main_thread->init_thread(); log_add_group_output(torrent::LOG_TRACKER_EVENTS, "test_output"); log_add_group_output(torrent::LOG_TRACKER_REQUESTS, "test_output"); torrent::ThreadNet::create_thread(); torrent::ThreadTracker::create_thread(); torrent::net_thread::thread()->init_thread(); torrent::tracker_thread::thread()->init_thread(); torrent::net_thread::thread()->start_thread(); torrent::tracker_thread::thread()->start_thread(); } void TestFixtureWithMainNetTrackerThread::tearDown() { torrent::tracker_thread::thread()->stop_thread_wait(); torrent::net_thread::thread()->stop_thread_wait(); torrent::Runtime::cleanup(); torrent::ThreadTracker::destroy_thread(); torrent::ThreadNet::destroy_thread(); TestMainThread::destroy(); m_main_thread.reset(); test_fixture::tearDown(); } void TestFixtureWithMockAndMainThread::setUp() { test_fixture::setUp(); m_main_thread = TestMainThread::create_with_mock(); torrent::Runtime::initialize(); m_main_thread->init_thread(); } void TestFixtureWithMockAndMainThread::tearDown() { torrent::Runtime::cleanup(); TestMainThread::destroy(); m_main_thread.reset(); test_fixture::tearDown(); } libtorrent-0.16.17/test/helpers/test_main_thread.h000066400000000000000000000036211522271512000221550ustar00rootroot00000000000000#ifndef TEST_HELPERS_TEST_MAIN_THREAD_H #define TEST_HELPERS_TEST_MAIN_THREAD_H #include #include "test/helpers/test_fixture.h" #include "test/helpers/test_thread.h" #include "torrent/common.h" #include "torrent/system/thread.h" class TestMainThread : public torrent::system::Thread { public: static std::unique_ptr create(); static std::unique_ptr create_with_mock(); static void destroy(); const char* name() const override { return "rtorrent test main"; } void init_thread() override; void cleanup_thread() override; void test_set_cached_time(std::chrono::microseconds t); void test_add_cached_time(std::chrono::microseconds t); void test_process_events_without_cached_time(); private: TestMainThread() = default; void call_events() override; std::chrono::microseconds next_timeout() override; }; class TestFixtureWithMainThread : public test_fixture { public: void setUp() override; void tearDown() override; std::unique_ptr m_main_thread; }; class TestFixtureWithMainAndDiskThread : public test_fixture { public: void setUp() override; void tearDown() override; std::unique_ptr m_main_thread; }; class TestFixtureWithMainAndTrackerThread : public test_fixture { public: void setUp() override; void tearDown() override; std::unique_ptr m_main_thread; }; class TestFixtureWithMainNetTrackerThread : public test_fixture { public: void setUp() override; void tearDown() override; std::unique_ptr m_main_thread; }; class TestFixtureWithMockAndMainThread : public test_fixture { public: void setUp() override; void tearDown() override; std::unique_ptr m_main_thread; }; #endif // TEST_HELPERS_TEST_MAIN_THREAD_H libtorrent-0.16.17/test/helpers/test_thread.cc000066400000000000000000000034241522271512000213100ustar00rootroot00000000000000#include "config.h" #include "test_thread.h" #include #include "test/helpers/mock_function.h" #include "torrent/exceptions.h" #include "torrent/system/poll.h" const int test_thread::test_flag_pre_stop; const int test_thread::test_flag_long_timeout; const int test_thread::test_flag_do_work; const int test_thread::test_flag_pre_poke; const int test_thread::test_flag_post_poke; std::unique_ptr test_thread::create() { // Needs to be called before Thread is created. mock_redirect_defaults(); auto thread = new test_thread(); return std::unique_ptr(thread); } test_thread::test_thread() : m_test_state(TEST_NONE), m_test_flags(0) { } test_thread::~test_thread() { if (is_active()) stop_thread_wait(); } void test_thread::init_thread() { m_state = STATE_INITIALIZED; m_test_state = TEST_PRE_START; } void test_thread::cleanup_thread() { } void test_thread::call_events() { m_loop_count++; if ((m_test_flags & test_flag_pre_stop) && m_test_state == TEST_PRE_START && m_state == STATE_ACTIVE) m_test_state = TEST_PRE_STOP; if ((m_flags & flag_do_shutdown)) { if ((m_flags & flag_did_shutdown)) throw torrent::internal_error("Already trigged shutdown."); m_flags |= flag_did_shutdown; throw torrent::shutdown_exception(); } if ((m_test_flags & test_flag_pre_poke)) { } if ((m_test_flags & test_flag_do_work)) { usleep(10 * 1000); // TODO: Don't just sleep, as that give up core. m_test_flags &= ~test_flag_do_work; } if ((m_test_flags & test_flag_post_poke)) { } process_callbacks(); } std::chrono::microseconds test_thread::next_timeout() { if ((m_test_flags & test_flag_long_timeout)) return std::chrono::microseconds(10s); else return std::chrono::microseconds(100ms); } libtorrent-0.16.17/test/helpers/test_thread.h000066400000000000000000000036341522271512000211550ustar00rootroot00000000000000#ifndef TEST_HELPERS_TEST_THREAD_H #define TEST_HELPERS_TEST_THREAD_H #include #include #include "test/helpers/test_utils.h" #include "torrent/common.h" #include "torrent/system/thread.h" class test_thread : public torrent::system::Thread { public: enum test_state { TEST_NONE, TEST_PRE_START, TEST_PRE_STOP, TEST_STOP }; static const int test_flag_pre_stop = 0x1; static const int test_flag_long_timeout = 0x2; static const int test_flag_do_work = 0x100; static const int test_flag_pre_poke = 0x200; static const int test_flag_post_poke = 0x400; static std::unique_ptr create(); ~test_thread() override; int test_state() const { return m_test_state; } bool is_state(int state) const { return m_state == state; } bool is_test_state(int state) const { return m_test_state == state; } bool is_test_flags(int flags) const { return (m_test_flags & flags) == flags; } bool is_not_test_flags(int flags) const { return !(m_test_flags & flags); } // Loop count increments twice each loop. int loop_count() const { return m_loop_count; } const char* name() const override { return "test_thread"; } void init_thread() override; void cleanup_thread() override; void set_pre_stop() { m_test_flags |= test_flag_pre_stop; } void set_test_flag(int flags) { m_test_flags |= flags; } auto* internal_poll() const { return m_poll.get(); } private: test_thread(); void call_events() override; std::chrono::microseconds next_timeout() override; align_cacheline std::atomic_int m_test_state; std::atomic_int m_test_flags; std::atomic_int m_loop_count{0}; }; #endif libtorrent-0.16.17/test/helpers/test_utils.h000066400000000000000000000010271522271512000210400ustar00rootroot00000000000000#ifndef LIBTORRENT_TEST_UTILS_H #define LIBTORRENT_TEST_UTILS_H #include #include inline bool wait_for_true(std::function test_function) { int i = 100; do { if (test_function()) return true; usleep(10 * 1000); } while (--i); return false; } inline bool wait_for_not_true(std::function test_function) { int i = 100; do { if (!test_function()) return true; usleep(10 * 1000); } while (--i); return false; } #endif // LIBTORRENT_TEST_UTILS_H libtorrent-0.16.17/test/helpers/tracker_test.cc000066400000000000000000000205621522271512000214760ustar00rootroot00000000000000#include "config.h" #include "test/helpers/tracker_test.h" #include "net/address_list.h" #include "test/torrent/test_tracker_list.h" #include // TrackerTest does not fully support threaded tracker yet, so review the code if such a requirement // is needed. uint32_t return_new_peers = 0xdeadbeef; torrent::tracker::Tracker TrackerTest::new_tracker([[maybe_unused]] torrent::TrackerList* parent, uint32_t group, const std::string& url, int flags) { auto tracker_info = torrent::TrackerInfo{ // .info_hash = m_info->hash(), // .obfuscated_hash = m_info->hash_obfuscated(), // .local_id = m_info->local_id(), // .key = m_key }; tracker_info.url = url; tracker_info.group = group; return torrent::tracker::Tracker(std::make_shared(std::move(tracker_info), flags)); } void TrackerTest::insert_tracker(torrent::TrackerList* parent, int group, torrent::tracker::Tracker tracker) { // Insert into partent then override slots. tracker.get_worker()->m_info.group = group; parent->insert(tracker); tracker.get_worker()->m_slot_enabled = [parent, tracker]() { if (parent->slot_tracker_enabled()) parent->slot_tracker_enabled()(tracker); }; tracker.get_worker()->m_slot_disabled = [parent, tracker]() { if (parent->slot_tracker_disabled()) parent->slot_tracker_disabled()(tracker); }; tracker.get_worker()->m_slot_success = [parent, tracker](torrent::AddressList&& l) { auto t = tracker; parent->receive_success(std::move(t), &l); }; tracker.get_worker()->m_slot_failure = [parent, tracker](const std::string& msg) { auto t = tracker; parent->receive_failed(std::move(t), msg); }; tracker.get_worker()->m_slot_scrape_success = [parent, tracker]() { auto t = tracker; parent->receive_scrape_success(std::move(t)); }; tracker.get_worker()->m_slot_scrape_failure = [parent, tracker](const std::string& msg) { auto t = tracker; parent->receive_scrape_failed(std::move(t), msg); }; } void TrackerTest::set_success(uint32_t time_last) { set_success(std::chrono::seconds(time_last)); } void TrackerTest::set_success(std::chrono::seconds time_last) { auto guard = lock_guard(); state().add_success_request(time_last); state().set_normal_interval(torrent::tracker::TrackerState::default_normal_interval); state().set_min_interval(torrent::tracker::TrackerState::default_min_interval); } void TrackerTest::set_failed(uint32_t time_last) { set_failed(std::chrono::seconds(time_last)); } void TrackerTest::set_failed(std::chrono::seconds time_last) { auto guard = lock_guard(); state().add_failed_request(time_last); // state().m_normal_interval = 0; // state().m_min_interval = 0; } void TrackerTest::set_latest_new_peers(uint32_t peers) { auto guard = lock_guard(); state().m_latest_new_peers = peers; } void TrackerTest::set_latest_sum_peers(uint32_t peers) { auto guard = lock_guard(); state().m_latest_sum_peers = peers; } void TrackerTest::set_new_normal_interval(uint32_t timeout) { auto guard = lock_guard(); state().set_normal_interval(timeout * 1s); } void TrackerTest::set_new_min_interval(uint32_t timeout) { auto guard = lock_guard(); state().set_min_interval(timeout * 1s); } void TrackerTest::send_event([[maybe_unused]] torrent::tracker::TrackerParams params, torrent::tracker::TrackerState::event_enum new_state) { // Trackers close on-going requests when new state is sent. m_busy = true; m_open = true; m_requesting_state = new_state; lock_and_set_latest_event(new_state); auto guard = lock_guard(); state().m_flags |= torrent::tracker::TrackerState::flag_starting_request; state().m_flags |= torrent::tracker::TrackerState::flag_requesting; } void TrackerTest::send_scrape([[maybe_unused]] torrent::tracker::TrackerParams params) { // We ignore scrapes if we're already making a request. // if (m_open) // return; m_busy = true; m_open = true; m_requesting_state = torrent::tracker::TrackerState::EVENT_SCRAPE; lock_and_set_latest_event(torrent::tracker::TrackerState::EVENT_SCRAPE); auto guard = lock_guard(); state().m_flags |= torrent::tracker::TrackerState::flag_requesting; } void TrackerTest::close() { m_busy = false; m_open = false; m_requesting_state = -1; auto guard = lock_guard(); state().m_flags &= ~torrent::tracker::TrackerState::flag_requesting; state().m_flags &= ~torrent::tracker::TrackerState::flag_starting_request; } void TrackerTest::cleanup() { close(); auto guard = lock_guard(); state().m_flags |= torrent::tracker::TrackerState::flag_deleted; state().m_flags &= ~torrent::tracker::TrackerState::flag_requesting; state().m_flags &= ~torrent::tracker::TrackerState::flag_starting_request; } bool TrackerTest::trigger_success(uint32_t new_peers, uint32_t sum_peers) { // C++20 allows notify_all() on atomic variables. for (int i = 0; i != 100 && !m_busy; i++) std::this_thread::sleep_for(10ms); CPPUNIT_ASSERT(m_busy && "TrackerTest::trigger_success: m_busy"); CPPUNIT_ASSERT(is_open() && "TrackerTest::trigger_success: is_open()"); torrent::AddressList address_list; for (unsigned int i = 0; i != sum_peers; i++) { torrent::sa_inet_union sa{}; sa.inet.sin_family = AF_INET; sa.inet.sin_port = htons(0x100 + i); address_list.push_back(sa); } address_list.sort_and_unique(); return trigger_success(&address_list, new_peers); } bool TrackerTest::trigger_success(torrent::AddressList* address_list, uint32_t new_peers) { // C++20 allows notify_all() on atomic variables. for (int i = 0; i != 100 && !m_busy; i++) std::this_thread::sleep_for(10ms); CPPUNIT_ASSERT(m_busy && "TrackerTest::trigger_success: m_busy"); CPPUNIT_ASSERT(is_open() && "TrackerTest::trigger_success: is_open()"); CPPUNIT_ASSERT(address_list != nullptr && "TrackerTest::trigger_success: address_list == nullptr"); m_busy = false; m_open = !(state().flags() & flag_close_on_done); { auto guard = lock_guard(); state().m_flags &= ~torrent::tracker::TrackerState::flag_requesting; state().m_flags &= ~torrent::tracker::TrackerState::flag_starting_request; } return_new_peers = new_peers; if (state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE) { if (m_slot_scrape_success) m_slot_scrape_success(); } else { { auto guard = lock_guard(); state().set_normal_interval(torrent::tracker::TrackerState::default_normal_interval); state().set_min_interval(torrent::tracker::TrackerState::default_min_interval); } if (m_slot_success) m_slot_success(std::move(*address_list)); } m_requesting_state = -1; return true; } bool TrackerTest::trigger_failure() { // C++20 allows notify_all() on atomic variables. for (int i = 0; i != 100 && !m_busy; i++) std::this_thread::sleep_for(10ms); CPPUNIT_ASSERT(m_busy && "TrackerTest::trigger_failure: m_busy"); CPPUNIT_ASSERT(is_open() && "TrackerTest::trigger_failure: is_open()"); m_busy = false; m_open = !(state().flags() & flag_close_on_done); { auto guard = lock_guard(); state().m_flags &= ~torrent::tracker::TrackerState::flag_requesting; state().m_flags &= ~torrent::tracker::TrackerState::flag_starting_request; } return_new_peers = 0; if (state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE) { if (m_slot_scrape_failure) m_slot_scrape_failure("failed"); } else { { auto guard = lock_guard(); state().set_normal_interval(0s); state().set_min_interval(0s); } if (m_slot_failure) m_slot_failure("failed"); } m_requesting_state = -1; return true; } bool TrackerTest::trigger_scrape() { // C++20 allows notify_all() on atomic variables. for (int i = 0; i != 100 && !m_busy; i++) std::this_thread::sleep_for(10ms); if (!m_busy || !is_open()) return false; if (state().latest_event() != torrent::tracker::TrackerState::EVENT_SCRAPE) return false; return trigger_success(); } int TrackerTest::count_active(torrent::TrackerList* parent) { std::this_thread::sleep_for(500ms); return std::count_if(parent->begin(), parent->end(), [](auto& tracker) { return tracker.is_requesting(); }); } int TrackerTest::count_usable(torrent::TrackerList* parent) { std::this_thread::sleep_for(500ms); return std::count_if(parent->begin(), parent->end(), [](auto& tracker) { return tracker.is_usable(); }); } libtorrent-0.16.17/test/helpers/tracker_test.h000066400000000000000000000103741522271512000213400ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPER_TRACKER_TEST_H #define LIBTORRENT_HELPER_TRACKER_TEST_H #include #include "tracker/tracker_list.h" #include "tracker/tracker_worker.h" class TrackerTest : public torrent::TrackerWorker { public: static const int flag_close_on_done = 0x100; static const int flag_scrape_on_success = 0x200; // TODO: Clean up tracker related enums. TrackerTest(torrent::TrackerInfo info, int flags = torrent::tracker::TrackerState::flag_enabled); bool is_open() const { return m_open; } torrent::tracker_enum type() const override { return (torrent::tracker_enum)(torrent::TRACKER_DHT + 1); } int requesting_state() const { return m_requesting_state; } bool trigger_success(uint32_t new_peers = 0, uint32_t sum_peers = 0); bool trigger_success(torrent::AddressList* address_list, uint32_t new_peers = 0); bool trigger_failure(); bool trigger_scrape(); void set_close_on_done(bool state); void set_scrape_on_success(bool state); void set_scrapable(); void set_success(uint32_t time_last); void set_success(std::chrono::seconds time_last); void set_failed(uint32_t time_last); void set_failed(std::chrono::seconds time_last); void set_latest_new_peers(uint32_t peers); void set_latest_sum_peers(uint32_t peers); void set_new_normal_interval(uint32_t timeout); void set_new_min_interval(uint32_t timeout); void send_event(torrent::tracker::TrackerParams params, torrent::tracker::TrackerState::event_enum new_state) override; void send_scrape(torrent::tracker::TrackerParams params) override; void close() override; void cleanup() override; static torrent::tracker::Tracker new_tracker(torrent::TrackerList* parent, uint32_t group, const std::string& url, int flags = torrent::tracker::TrackerState::flag_enabled); static void insert_tracker(torrent::TrackerList* parent, int group, torrent::tracker::Tracker tracker); torrent::tracker::TrackerState* state_ptr() { return &state(); } torrent::tracker::TrackerState& test_state() { return state(); } static TrackerTest* test_worker(torrent::tracker::Tracker& tracker); static int test_flags(torrent::tracker::Tracker& tracker); static torrent::tracker::TrackerState& test_state(torrent::tracker::Tracker& tracker); static int count_active(torrent::TrackerList* parent); static int count_usable(torrent::TrackerList* parent); private: align_cacheline std::atomic m_busy{false}; std::atomic m_open{false}; std::atomic m_requesting_state{-1}; }; inline TrackerTest::TrackerTest(torrent::TrackerInfo info, int flags) : torrent::TrackerWorker(std::move(info), flags) { state().m_flags |= flag_close_on_done; } inline void TrackerTest::set_close_on_done(bool s) { if (s) state().m_flags |= flag_close_on_done; else state().m_flags &= ~flag_close_on_done; } inline void TrackerTest::set_scrape_on_success(bool s) { if (s) state().m_flags |= flag_scrape_on_success; else state().m_flags &= ~flag_scrape_on_success; } inline void TrackerTest::set_scrapable() { state().m_flags |= torrent::tracker::TrackerState::flag_scrapable; } inline TrackerTest* TrackerTest::test_worker(torrent::tracker::Tracker& tracker) { return dynamic_cast(tracker.get_worker()); } inline int TrackerTest::test_flags(torrent::tracker::Tracker& tracker) { return dynamic_cast(tracker.get_worker())->state().flags(); } inline torrent::tracker::TrackerState& TrackerTest::test_state(torrent::tracker::Tracker& tracker) { return dynamic_cast(tracker.get_worker())->state(); } extern uint32_t return_new_peers; inline uint32_t increment_value(int* value) { (*value)++; return return_new_peers; } inline void increment_value_void(int* value) { (*value)++; } inline unsigned int increment_value_uint(int* value) { (*value)++; return return_new_peers; } #endif // LIBTORRENT_HELPER_TRACKER_TEST_H libtorrent-0.16.17/test/helpers/utils.h000066400000000000000000000032171522271512000200040ustar00rootroot00000000000000#ifndef LIBTORRENT_HELPER_UTILS_H #define LIBTORRENT_HELPER_UTILS_H #include #include #include #include static void dump_failure_log(const failure_type& failure) { if (failure.log->empty()) return; std::cout << std::endl << failure.name << std::endl; // Doesn't print dump messages as log_buffer drops them. std::for_each(failure.log->begin(), failure.log->end(), [](const torrent::log_entry& entry) { std::cout << entry.timestamp << ' ' << entry.message << '\n'; }); std::cout << std::flush; } static void dump_failures(const failure_list_type& failures) { if (failures.empty()) return; std::cout << std::endl << "=================" << std::endl << "Failed Test Logs:" << std::endl << "=================" << std::endl; std::for_each(failures.begin(), failures.end(), [](const failure_type& failure) { dump_failure_log(failure); }); std::cout << std::endl; } static void add_tests(CppUnit::TextUi::TestRunner& runner, const char* c_test_names) { if (c_test_names == NULL || std::string(c_test_names).empty()) { runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); return; } const std::string& test_names(c_test_names); size_t pos = 0; size_t next = 0; while ((next = test_names.find(',', pos)) < test_names.size()) { runner.addTest(CppUnit::TestFactoryRegistry::getRegistry(test_names.substr(pos, next - pos)).makeTest()); pos = next + 1; } runner.addTest(CppUnit::TestFactoryRegistry::getRegistry(test_names.substr(pos)).makeTest()); } #endif libtorrent-0.16.17/test/main.cc000066400000000000000000000051311522271512000162610ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_BACKTRACE #include #endif #include "helpers/progress_listener.h" #include "helpers/protectors.h" #include "helpers/utils.h" CPPUNIT_REGISTRY_ADD_TO_DEFAULT("torrent/net"); CPPUNIT_REGISTRY_ADD_TO_DEFAULT("torrent/utils"); CPPUNIT_REGISTRY_ADD_TO_DEFAULT("torrent"); CPPUNIT_REGISTRY_ADD_TO_DEFAULT("data"); CPPUNIT_REGISTRY_ADD_TO_DEFAULT("net"); CPPUNIT_REGISTRY_ADD_TO_DEFAULT("tracker"); namespace { void do_test_panic(int signum) { signal(signum, SIG_DFL); std::cout << std::endl << std::endl << "Caught " << strsignal(signum) << ", dumping stack:" << std::endl << std::endl; #ifdef HAVE_BACKTRACE void* stackPtrs[20]; // Print the stack and exit. int stackSize = backtrace(stackPtrs, 20); char** stackStrings = backtrace_symbols(stackPtrs, stackSize); for (int i = 0; i < stackSize; ++i) std::cout << stackStrings[i] << std::endl; #else std::cout << "Stack dump not enabled." << std::endl; #endif std::cout << std::endl; torrent::log_cleanup(); std::abort(); } void register_signal_handlers() { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = &do_test_panic; if (sigaction(SIGSEGV, &sa, NULL) == -1) { std::cout << "Could not register signal handlers." << std::endl; exit(-1); } } } // namespace int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) { register_signal_handlers(); CppUnit::TestResult controller; CppUnit::TestResultCollector result; progress_listener progress; controller.addListener(&result); controller.addListener(&progress); controller.popProtector(); controller.pushProtector(new ExceptionProtector()); CppUnit::TextUi::TestRunner runner; add_tests(runner, std::getenv("TEST_NAME")); try { std::cout << "Running "; runner.run( controller ); // TODO: Make outputter. dump_failures(progress.failures()); // Print test in a compiler compatible format. CppUnit::CompilerOutputter outputter( &result, std::cerr ); outputter.write(); } catch (const std::invalid_argument& e) { // Test path not resolved std::cerr << std::endl << "ERROR: " << e.what() << std::endl; return 1; } return result.wasSuccessful() ? 0 : 1; } libtorrent-0.16.17/test/protocol/000077500000000000000000000000001522271512000166675ustar00rootroot00000000000000libtorrent-0.16.17/test/protocol/test_request_list.cc000066400000000000000000000214301522271512000227600ustar00rootroot00000000000000#include "config.h" #include "test/protocol/test_request_list.h" #include "download/delegator.h" #include "protocol/peer_chunks.h" #include "protocol/request_list.h" #include "test/helpers/network.h" #include "test/helpers/test_main_thread.h" #include "torrent/exceptions.h" #include "torrent/peer/peer_info.h" CPPUNIT_TEST_SUITE_REGISTRATION(TestRequestList); static uint32_t chunk_index_size([[maybe_unused]] uint32_t index) { return 1 << 10; } static void transfer_list_void() { // std::cout << "list_void" << std::endl; } static void transfer_list_completed(torrent::TransferList* transfer_list, uint32_t index) { torrent::TransferList::iterator itr = transfer_list->find(index); // std::cout << "list_completed:" << index << " found: " << (itr != transfer_list->end()) << std::endl; CPPUNIT_ASSERT(itr != transfer_list->end()); transfer_list->erase(itr); } struct RequestListGuard { RequestListGuard(torrent::RequestList* rl) : request_list(rl) {} ~RequestListGuard() { if (request_list == nullptr || completed) return; request_list->clear(); request_list->delegator()->transfer_list()->clear(); } bool completed{false}; torrent::RequestList* request_list; }; #define SETUP_DELEGATOR(fpc_prefix) \ auto delegator = std::make_unique(); \ delegator->slot_chunk_find() = std::bind(&fpc_prefix ## _find_peer_chunk, std::placeholders::_1, std::placeholders::_2); \ delegator->slot_chunk_size() = std::bind(&chunk_index_size, std::placeholders::_1); \ delegator->transfer_list()->slot_canceled() = std::bind(&transfer_list_void); \ delegator->transfer_list()->slot_queued() = std::bind(&transfer_list_void); \ delegator->transfer_list()->slot_completed() = std::bind(&transfer_list_completed, delegator->transfer_list(), std::placeholders::_1); \ delegator->transfer_list()->slot_corrupt() = std::bind(&transfer_list_void); // Set bitfield size... #define SETUP_PEER_CHUNKS() \ auto peer_info = std::make_unique(wrap_ai_get_first_sa("1.2.3.4", "5000").get()); \ auto peer_chunks = std::make_unique(); \ peer_chunks->set_peer_info(peer_info.get()); #define SETUP_REQUEST_LIST() \ auto request_list = std::make_unique(); \ RequestListGuard request_list_guard(request_list.get()); \ request_list->set_delegator(delegator.get()); \ request_list->set_peer_chunks(peer_chunks.get()); #define SETUP_ALL(fpc_prefix) \ auto test_main_thread = TestMainThread::create(); \ test_main_thread->init_thread(); \ test_main_thread->test_set_cached_time(0s); \ SETUP_DELEGATOR(basic); \ SETUP_PEER_CHUNKS(); \ SETUP_REQUEST_LIST(); #define SETUP_ALL_WITH_3(fpc_prefix) \ SETUP_ALL(fpc_prefix); \ auto pieces = request_list->delegate(3); \ CPPUNIT_ASSERT(pieces.size() == 3); \ const torrent::Piece* piece_1 = pieces[0]; \ const torrent::Piece* piece_2 = pieces[1]; \ const torrent::Piece* piece_3 = pieces[2]; \ CPPUNIT_ASSERT(piece_1 && piece_2 && piece_3); #define CLEAR_TRANSFERS(fpc_prefix) \ delegator->transfer_list()->clear(); \ request_list_guard.completed = true; // // // #define VERIFY_QUEUE_SIZES(s_0, s_1, s_2, s_3) \ CPPUNIT_ASSERT(request_list->queued_size() == s_0); \ CPPUNIT_ASSERT(request_list->unordered_size() == s_1); \ CPPUNIT_ASSERT(request_list->stalled_size() == s_2); \ CPPUNIT_ASSERT(request_list->choked_size() == s_3); #define VERIFY_PIECE_IS_LEADER(piece) \ CPPUNIT_ASSERT(request_list->transfer() != NULL); \ CPPUNIT_ASSERT(request_list->transfer()->is_leader()); \ CPPUNIT_ASSERT(request_list->transfer()->peer_info() != NULL); \ CPPUNIT_ASSERT(request_list->transfer()->peer_info() == peer_info.get()); #define VERIFY_TRANSFER_COUNTER(transfer, count) \ CPPUNIT_ASSERT(transfer != NULL); \ CPPUNIT_ASSERT(transfer->peer_info() != NULL); \ CPPUNIT_ASSERT(transfer->peer_info()->transfer_counter() == count); // // Basic tests: // static uint32_t basic_find_peer_chunk([[maybe_unused]] torrent::PeerChunks* peerChunk, [[maybe_unused]] bool highPriority) { static int next_index = 0; return next_index++; } void TestRequestList::test_basic() { SETUP_ALL(basic); CPPUNIT_ASSERT(!request_list->is_downloading()); CPPUNIT_ASSERT(!request_list->is_interested_in_active()); VERIFY_QUEUE_SIZES(0, 0, 0, 0); CPPUNIT_ASSERT(request_list->calculate_pipe_size(1024 * 0) == 2); CPPUNIT_ASSERT(request_list->calculate_pipe_size(1024 * 10) == 12); CPPUNIT_ASSERT(request_list->transfer() == NULL); } void TestRequestList::test_single_request() { SETUP_ALL(basic); auto pieces = request_list->delegate(1); CPPUNIT_ASSERT(pieces.size() == 1); const torrent::Piece* piece = pieces[0]; // std::cout << piece->index() << ' ' << piece->offset() << ' ' << piece->length() << std::endl; // std::cout << peer_info->transfer_counter() << std::endl; CPPUNIT_ASSERT(request_list->downloading(*piece)); VERIFY_PIECE_IS_LEADER(*piece); VERIFY_TRANSFER_COUNTER(request_list->transfer(), 1); request_list->transfer()->adjust_position(piece->length()); CPPUNIT_ASSERT(request_list->transfer()->is_finished()); request_list->finished(); CPPUNIT_ASSERT(peer_info->transfer_counter() == 0); } void TestRequestList::test_single_canceled() { SETUP_ALL(basic); auto pieces = request_list->delegate(1); CPPUNIT_ASSERT(pieces.size() == 1); const torrent::Piece* piece = pieces[0]; // std::cout << piece->index() << ' ' << piece->offset() << ' ' << piece->length() << std::endl; // std::cout << peer_info->transfer_counter() << std::endl; CPPUNIT_ASSERT(request_list->downloading(*piece)); VERIFY_PIECE_IS_LEADER(*piece); // REMOVE VERIFY_TRANSFER_COUNTER(request_list->transfer(), 1); // REMOVE request_list->transfer()->adjust_position(piece->length() / 2); CPPUNIT_ASSERT(!request_list->transfer()->is_finished()); request_list->skipped(); // The transfer remains in Block until it gets the block has a // transfer trigger finished. // TODO: We need to have a way of clearing disowned transfers, then // make transfer list delete Block's(?) with those before dtor... CPPUNIT_ASSERT(peer_info->transfer_counter() == 0); CLEAR_TRANSFERS(); } void TestRequestList::test_choke_normal() { SETUP_ALL_WITH_3(basic); VERIFY_QUEUE_SIZES(3, 0, 0, 0); request_list->choked(); test_main_thread->test_set_cached_time(1s); test_main_thread->test_process_events_without_cached_time(); VERIFY_QUEUE_SIZES(0, 0, 0, 3); test_main_thread->test_set_cached_time(1s + 6s); test_main_thread->test_process_events_without_cached_time(); VERIFY_QUEUE_SIZES(0, 0, 0, 0); CLEAR_TRANSFERS(); } void TestRequestList::test_choke_unchoke_discard() { SETUP_ALL_WITH_3(basic); request_list->choked(); test_main_thread->test_set_cached_time(5s); test_main_thread->test_process_events_without_cached_time(); request_list->unchoked(); test_main_thread->test_set_cached_time(5s + 5s); test_main_thread->test_process_events_without_cached_time(); VERIFY_QUEUE_SIZES(0, 0, 0, 3); test_main_thread->test_set_cached_time(5s + 60s); test_main_thread->test_process_events_without_cached_time(); VERIFY_QUEUE_SIZES(0, 0, 0, 0); CLEAR_TRANSFERS(); } void TestRequestList::test_choke_unchoke_transfer() { SETUP_ALL_WITH_3(basic); request_list->choked(); test_main_thread->test_set_cached_time(5s); test_main_thread->test_process_events_without_cached_time(); request_list->unchoked(); test_main_thread->test_set_cached_time(5s + 5s); test_main_thread->test_process_events_without_cached_time(); CPPUNIT_ASSERT(request_list->downloading(*piece_1)); request_list->transfer()->adjust_position(piece_1->length()); request_list->finished(); test_main_thread->test_set_cached_time(10s + 50s); test_main_thread->test_process_events_without_cached_time(); CPPUNIT_ASSERT(request_list->downloading(*piece_2)); request_list->transfer()->adjust_position(piece_2->length()); request_list->finished(); test_main_thread->test_set_cached_time(60s + 50s); test_main_thread->test_process_events_without_cached_time(); CPPUNIT_ASSERT(request_list->downloading(*piece_3)); request_list->transfer()->adjust_position(piece_3->length()); request_list->finished(); VERIFY_QUEUE_SIZES(0, 0, 0, 0); CLEAR_TRANSFERS(); } libtorrent-0.16.17/test/protocol/test_request_list.h000066400000000000000000000011061522271512000226200ustar00rootroot00000000000000#include "test/helpers/test_fixture.h" class TestRequestList : public test_fixture { CPPUNIT_TEST_SUITE(TestRequestList); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_single_request); CPPUNIT_TEST(test_single_canceled); CPPUNIT_TEST(test_choke_normal); CPPUNIT_TEST(test_choke_unchoke_discard); CPPUNIT_TEST(test_choke_unchoke_transfer); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_single_request(); void test_single_canceled(); void test_choke_normal(); void test_choke_unchoke_discard(); void test_choke_unchoke_transfer(); }; libtorrent-0.16.17/test/rak/000077500000000000000000000000001522271512000156035ustar00rootroot00000000000000libtorrent-0.16.17/test/rak/ranges_test.cc000066400000000000000000000077641522271512000204460ustar00rootroot00000000000000#include "config.h" #include #include "ranges_test.h" CPPUNIT_TEST_SUITE_REGISTRATION(RangesTest); template bool verify_ranges(const torrent::ranges& ranges) { typename torrent::ranges::const_iterator first = ranges.begin(); typename torrent::ranges::const_iterator last = ranges.begin(); if (first == last) return true; if (first->second <= first->first) return false; Type boundary = first++->second; do { if (first->first <= boundary) return false; if (first->second <= first->first) return false; } while (++first != last); return true; } void RangesTest::test_basic() { } void RangesTest::test_intersect() { torrent::ranges range; CPPUNIT_ASSERT(verify_ranges(range)); CPPUNIT_ASSERT(range.intersect_distance(0, 0) == 0); CPPUNIT_ASSERT(range.intersect_distance(0, 10) == 0); range.insert(0, 5); CPPUNIT_ASSERT(verify_ranges(range)); CPPUNIT_ASSERT(range.intersect_distance(0, 5) == 5); CPPUNIT_ASSERT(range.intersect_distance(0, 10) == 5); CPPUNIT_ASSERT(range.intersect_distance(-5, 5) == 5); CPPUNIT_ASSERT(range.intersect_distance(-5, 10) == 5); CPPUNIT_ASSERT(range.intersect_distance(2, 2) == 0); CPPUNIT_ASSERT(range.intersect_distance(1, 4) == 3); CPPUNIT_ASSERT(range.intersect_distance(1, 10) == 4); CPPUNIT_ASSERT(range.intersect_distance(-5, 4) == 4); range.insert(10, 15); CPPUNIT_ASSERT(verify_ranges(range)); CPPUNIT_ASSERT(range.intersect_distance(0, 15) == 10); CPPUNIT_ASSERT(range.intersect_distance(0, 20) == 10); CPPUNIT_ASSERT(range.intersect_distance(-5, 15) == 10); CPPUNIT_ASSERT(range.intersect_distance(-5, 20) == 10); CPPUNIT_ASSERT(range.intersect_distance(2, 12) == 5); CPPUNIT_ASSERT(range.intersect_distance(1, 14) == 8); CPPUNIT_ASSERT(range.intersect_distance(1, 20) == 9); CPPUNIT_ASSERT(range.intersect_distance(-5, 14) == 9); } void RangesTest::test_create_union() { torrent::ranges range_1l; torrent::ranges range_1r; torrent::ranges range_1u; // Empty: range_1u = torrent::ranges::create_union(range_1l, range_1r); CPPUNIT_ASSERT(verify_ranges(range_1u)); CPPUNIT_ASSERT(range_1u.intersect_distance(-5, 20) == 0); range_1l.insert(0, 5); // Left one entry: range_1u = torrent::ranges::create_union(range_1l, range_1r); CPPUNIT_ASSERT(verify_ranges(range_1u)); CPPUNIT_ASSERT(range_1u.intersect_distance(-5, 20) == 5); // Right one entry: range_1u = torrent::ranges::create_union(range_1r, range_1l); CPPUNIT_ASSERT(verify_ranges(range_1u)); CPPUNIT_ASSERT(range_1u.intersect_distance(-5, 20) == 5); range_1r.insert(10, 15); // Both one entry: range_1u = torrent::ranges::create_union(range_1l, range_1r); // std::cout << "range_1u.intersect_distance(-5, 20) = " << range_1u.intersect_distance(-5, 20) << std::endl; CPPUNIT_ASSERT(verify_ranges(range_1u)); CPPUNIT_ASSERT(range_1u.intersect_distance(-5, 20) == 10); range_1r.insert(4, 6); // Overlap: range_1u = torrent::ranges::create_union(range_1l, range_1r); // std::cout << "range_1u.intersect_distance(-5, 20) = " << range_1u.intersect_distance(-5, 20) << std::endl; CPPUNIT_ASSERT(verify_ranges(range_1u)); CPPUNIT_ASSERT(range_1u.intersect_distance(-5, 20) == 11); range_1r.insert(9, 10); // Boundary: range_1u = torrent::ranges::create_union(range_1l, range_1r); CPPUNIT_ASSERT(verify_ranges(range_1u)); CPPUNIT_ASSERT(range_1u.intersect_distance(-5, 20) == 12); // Trailing ranges left. range_1l.insert(25, 30); range_1l.insert(35, 40); range_1u = torrent::ranges::create_union(range_1l, range_1r); CPPUNIT_ASSERT(verify_ranges(range_1u)); CPPUNIT_ASSERT(range_1u.intersect_distance(-5, 50) == 22); // Trailing ranges right. range_1r.insert(37, 45); range_1r.insert(50, 55); range_1u = torrent::ranges::create_union(range_1l, range_1r); CPPUNIT_ASSERT(verify_ranges(range_1u)); CPPUNIT_ASSERT(range_1u.intersect_distance(-5, 60) == 32); } libtorrent-0.16.17/test/rak/ranges_test.h000066400000000000000000000006111522271512000202700ustar00rootroot00000000000000#include #include #include "torrent/utils/ranges.h" class RangesTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(RangesTest); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_intersect); CPPUNIT_TEST(test_create_union); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_intersect(); void test_create_union(); }; libtorrent-0.16.17/test/test_log.G7o76H000066400000000000000000000000001522271512000174770ustar00rootroot00000000000000libtorrent-0.16.17/test/torrent/000077500000000000000000000000001522271512000165235ustar00rootroot00000000000000libtorrent-0.16.17/test/torrent/net/000077500000000000000000000000001522271512000173115ustar00rootroot00000000000000libtorrent-0.16.17/test/torrent/net/test_address_info.cc000066400000000000000000000107151522271512000233230ustar00rootroot00000000000000#include "config.h" #include "test_address_info.h" #include "helpers/network.h" #include "torrent/net/address_info.h" #include "torrent/net/socket_address.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_address_info, "torrent/net"); void test_address_info::test_basic() { CPPUNIT_ASSERT(test_valid_ai_ref (std::bind(torrent::ai_get_addrinfo, "0.0.0.0", nullptr, nullptr, std::placeholders::_1))); CPPUNIT_ASSERT(test_valid_ai_ref(std::bind(torrent::ai_get_addrinfo, "::", nullptr, nullptr, std::placeholders::_1))); CPPUNIT_ASSERT(test_valid_ai_ref (std::bind(torrent::ai_get_addrinfo, "1.1.1.1", nullptr, nullptr, std::placeholders::_1))); CPPUNIT_ASSERT(test_valid_ai_ref(std::bind(torrent::ai_get_addrinfo, "ff01::1", nullptr, nullptr, std::placeholders::_1))); CPPUNIT_ASSERT(test_valid_ai_ref(std::bind(torrent::ai_get_addrinfo, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", nullptr, nullptr, std::placeholders::_1))); CPPUNIT_ASSERT(test_valid_ai_ref (std::bind(torrent::ai_get_addrinfo, "1.1.1.1", "22123", nullptr, std::placeholders::_1), 22123)); CPPUNIT_ASSERT(test_valid_ai_ref(std::bind(torrent::ai_get_addrinfo, "2001:db8:a::", "22123", nullptr, std::placeholders::_1), 22123)); CPPUNIT_ASSERT(test_valid_ai_ref (std::bind(torrent::ai_get_addrinfo, "localhost", nullptr, nullptr, std::placeholders::_1))); // Glibc 2.28+ returns EAI_NONAME for invalid IPv4 addresses, which likely should only happen when // using AI_NUMERICHOST. CPPUNIT_ASSERT(test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "1.1.1.300", nullptr, nullptr, std::placeholders::_1), EAI_NONAME) || test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "1.1.1.300", nullptr, nullptr, std::placeholders::_1), EAI_NODATA) || test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "1.1.1.300", nullptr, nullptr, std::placeholders::_1), EAI_AGAIN)); CPPUNIT_ASSERT(test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "2001:db8:a::22123", nullptr, nullptr, std::placeholders::_1), EAI_NONAME) || test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "2001:db8:a::22123", nullptr, nullptr, std::placeholders::_1), EAI_NODATA) || test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "2001:db8:a::22123", nullptr, nullptr, std::placeholders::_1), EAI_AGAIN) || test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "2001:db8:a::22123", nullptr, nullptr, std::placeholders::_1), EAI_ADDRFAMILY) || test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "2001:db8:a::22123", nullptr, nullptr, std::placeholders::_1), EAI_FAIL)); } void test_address_info::test_numericserv() { CPPUNIT_ASSERT(test_valid_ai_ref (std::bind(torrent::ai_get_addrinfo, "1.1.1.1", nullptr, torrent::ai_make_hint(AI_NUMERICHOST, 0, 0).get(), std::placeholders::_1))); CPPUNIT_ASSERT(test_valid_ai_ref_err(std::bind(torrent::ai_get_addrinfo, "localhost", nullptr, torrent::ai_make_hint(AI_NUMERICHOST, 0, 0).get(), std::placeholders::_1), EAI_NONAME)); } void test_address_info::test_helpers() { torrent::sin_unique_ptr sin_zero = torrent::sin_from_sa(wrap_ai_get_first_sa("0.0.0.0")); CPPUNIT_ASSERT(sin_zero != nullptr); CPPUNIT_ASSERT(sin_zero->sin_family == AF_INET); CPPUNIT_ASSERT(sin_zero->sin_port == 0); CPPUNIT_ASSERT(sin_zero->sin_addr.s_addr == in_addr().s_addr); torrent::sin_unique_ptr sin_1 = torrent::sin_from_sa(wrap_ai_get_first_sa("1.2.3.4")); CPPUNIT_ASSERT(sin_1 != nullptr); CPPUNIT_ASSERT(sin_1->sin_family == AF_INET); CPPUNIT_ASSERT(sin_1->sin_port == 0); CPPUNIT_ASSERT(sin_1->sin_addr.s_addr == htonl(0x01020304)); torrent::sin6_unique_ptr sin6_zero = torrent::sin6_from_sa(wrap_ai_get_first_sa("::")); CPPUNIT_ASSERT(sin6_zero != nullptr); CPPUNIT_ASSERT(sin6_zero->sin6_family == AF_INET6); CPPUNIT_ASSERT(sin6_zero->sin6_port == 0); CPPUNIT_ASSERT(compare_sin6_addr(sin6_zero->sin6_addr, in6_addr{})); torrent::sin6_unique_ptr sin6_1 = torrent::sin6_from_sa(wrap_ai_get_first_sa("ff01::1")); CPPUNIT_ASSERT(sin6_1 != nullptr); CPPUNIT_ASSERT(sin6_1->sin6_family == AF_INET6); CPPUNIT_ASSERT(sin6_1->sin6_port == 0); CPPUNIT_ASSERT(compare_sin6_addr(sin6_1->sin6_addr, in6_addr{0xff, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})); CPPUNIT_ASSERT(!compare_sin6_addr(sin6_1->sin6_addr, in6_addr{0xff, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})); } libtorrent-0.16.17/test/torrent/net/test_address_info.h000066400000000000000000000005341522271512000231630ustar00rootroot00000000000000#include class test_address_info : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(test_address_info); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_numericserv); CPPUNIT_TEST(test_helpers); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_numericserv(); void test_helpers(); }; libtorrent-0.16.17/test/torrent/net/test_fd.cc000066400000000000000000000035121522271512000212510ustar00rootroot00000000000000#include "config.h" #include "test_fd.h" #include CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_fd, "torrent/net"); void test_fd::test_valid_flags() { CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_stream)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_datagram)); CPPUNIT_ASSERT(!torrent::fd_valid_flags(torrent::fd_flag_stream | torrent::fd_flag_datagram)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_stream | torrent::fd_flag_nonblock)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_stream | torrent::fd_flag_reuse_address)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_stream | torrent::fd_flag_v4only)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_stream | torrent::fd_flag_v6only)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_datagram | torrent::fd_flag_nonblock)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_datagram | torrent::fd_flag_reuse_address)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_datagram | torrent::fd_flag_v4only)); CPPUNIT_ASSERT(torrent::fd_valid_flags(torrent::fd_flag_datagram | torrent::fd_flag_v6only)); CPPUNIT_ASSERT(!torrent::fd_valid_flags(torrent::fd_flag_v4only | torrent::fd_flag_v6only)); CPPUNIT_ASSERT(!torrent::fd_valid_flags(torrent::fd_flag_stream | torrent::fd_flag_v4only | torrent::fd_flag_v6only)); CPPUNIT_ASSERT(!torrent::fd_valid_flags(torrent::fd_flag_datagram | torrent::fd_flag_v4only | torrent::fd_flag_v6only)); CPPUNIT_ASSERT(!torrent::fd_valid_flags(torrent::fd_flags())); CPPUNIT_ASSERT(!torrent::fd_valid_flags(torrent::fd_flags(~torrent::fd_flag_all))); CPPUNIT_ASSERT(!torrent::fd_valid_flags(torrent::fd_flags(torrent::fd_flag_stream | ~torrent::fd_flag_all))); CPPUNIT_ASSERT(!torrent::fd_valid_flags(torrent::fd_flags(0x3245132))); } libtorrent-0.16.17/test/torrent/net/test_fd.h000066400000000000000000000003331522271512000211110ustar00rootroot00000000000000#include "helpers/test_fixture.h" class test_fd : public test_fixture { CPPUNIT_TEST_SUITE(test_fd); CPPUNIT_TEST(test_valid_flags); CPPUNIT_TEST_SUITE_END(); public: void test_valid_flags(); }; libtorrent-0.16.17/test/torrent/net/test_socket_address.cc000066400000000000000000000457551522271512000236740ustar00rootroot00000000000000#include "config.h" #include "test_socket_address.h" #include "helpers/network.h" #include "torrent/exceptions.h" #include "torrent/net/socket_address.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_socket_address, "torrent/net"); void test_socket_address::test_sa_is_any() { TEST_DEFAULT_SA; CPPUNIT_ASSERT(torrent::sap_is_any(sin_any)); CPPUNIT_ASSERT(torrent::sap_is_any(sin_any_5000)); CPPUNIT_ASSERT(torrent::sap_is_any(sin6_v4_any)); CPPUNIT_ASSERT(torrent::sap_is_any(sin6_v4_any_5000)); CPPUNIT_ASSERT(!torrent::sap_is_any(sin_bc)); CPPUNIT_ASSERT(!torrent::sap_is_any(sin_1)); CPPUNIT_ASSERT(!torrent::sap_is_any(sin6_1)); CPPUNIT_ASSERT(!torrent::sap_is_any(sin_bc_5000)); CPPUNIT_ASSERT(!torrent::sap_is_any(sin_1_5000)); CPPUNIT_ASSERT(!torrent::sap_is_any(sin6_1_5000)); CPPUNIT_ASSERT(!torrent::sap_is_any(c_sin_bc)); CPPUNIT_ASSERT(!torrent::sap_is_any(c_sin_1)); CPPUNIT_ASSERT(!torrent::sap_is_any(c_sin6_1)); CPPUNIT_ASSERT(!torrent::sap_is_any(c_sin_bc_5000)); CPPUNIT_ASSERT(!torrent::sap_is_any(c_sin_1_5000)); CPPUNIT_ASSERT(!torrent::sap_is_any(c_sin6_1_5000)); } void test_socket_address::test_sa_is_broadcast() { TEST_DEFAULT_SA; CPPUNIT_ASSERT(torrent::sap_is_broadcast(sin_bc)); CPPUNIT_ASSERT(torrent::sap_is_broadcast(sin_bc_5000)); CPPUNIT_ASSERT(torrent::sap_is_broadcast(sin6_v4_bc)); CPPUNIT_ASSERT(torrent::sap_is_broadcast(sin6_v4_bc_5000)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(sin_any)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(sin_1)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(sin6_any)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(sin6_1)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(sin_any_5000)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(sin_1_5000)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(sin6_any_5000)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(sin6_1_5000)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(c_sin_any)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(c_sin_1)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(c_sin6_any)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(c_sin6_1)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(c_sin_any_5000)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(c_sin_1_5000)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(c_sin6_any_5000)); CPPUNIT_ASSERT(!torrent::sap_is_broadcast(c_sin6_1_5000)); } void test_socket_address::test_make() { torrent::sa_unique_ptr sa_unspec = torrent::sa_make_unspec(); CPPUNIT_ASSERT(sa_unspec != nullptr); CPPUNIT_ASSERT(sa_unspec->sa_family == AF_UNSPEC); torrent::sa_unique_ptr sa_inet = torrent::sa_make_inet(); CPPUNIT_ASSERT(sa_inet != nullptr); CPPUNIT_ASSERT(sa_inet->sa_family == AF_INET); sockaddr_in* sin_inet = reinterpret_cast(sa_inet.get()); CPPUNIT_ASSERT(sin_inet->sin_family == AF_INET); CPPUNIT_ASSERT(sin_inet->sin_port == 0); CPPUNIT_ASSERT(sin_inet->sin_addr.s_addr == in_addr().s_addr); torrent::sa_unique_ptr sa_inet6 = torrent::sa_make_inet6(); CPPUNIT_ASSERT(sa_inet6 != nullptr); CPPUNIT_ASSERT(sa_inet6->sa_family == AF_INET6); sockaddr_in6* sin6_inet6 = reinterpret_cast(sa_inet6.get()); CPPUNIT_ASSERT(sin6_inet6->sin6_family == AF_INET6); CPPUNIT_ASSERT(sin6_inet6->sin6_port == 0); CPPUNIT_ASSERT(sin6_inet6->sin6_flowinfo == 0); CPPUNIT_ASSERT(compare_sin6_addr(sin6_inet6->sin6_addr, (in6_addr{{}}))); CPPUNIT_ASSERT(sin6_inet6->sin6_scope_id == 0); torrent::sa_unique_ptr sa_unix = torrent::sa_make_unix(""); CPPUNIT_ASSERT(sa_unix != nullptr); CPPUNIT_ASSERT(sa_unix->sa_family == AF_UNIX); } void test_socket_address::test_sin_from_sa() { torrent::sa_unique_ptr sa_zero = wrap_ai_get_first_sa("0.0.0.0"); torrent::sin_unique_ptr sin_zero; CPPUNIT_ASSERT(sa_zero != nullptr); CPPUNIT_ASSERT_NO_THROW({ sin_zero = torrent::sin_from_sa(std::move(sa_zero)); }); CPPUNIT_ASSERT(sa_zero == nullptr); CPPUNIT_ASSERT(sin_zero != nullptr); CPPUNIT_ASSERT(sin_zero->sin_addr.s_addr == htonl(0x0)); torrent::sa_unique_ptr sa_inet = wrap_ai_get_first_sa("1.2.3.4"); torrent::sin_unique_ptr sin_inet; CPPUNIT_ASSERT(sa_inet != nullptr); CPPUNIT_ASSERT_NO_THROW({ sin_inet = torrent::sin_from_sa(std::move(sa_inet)); }); CPPUNIT_ASSERT(sa_inet == nullptr); CPPUNIT_ASSERT(sin_inet != nullptr); CPPUNIT_ASSERT(sin_inet->sin_addr.s_addr == htonl(0x01020304)); CPPUNIT_ASSERT_THROW(torrent::sin_from_sa(torrent::sa_unique_ptr()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sin_from_sa(torrent::sa_make_unspec()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sin_from_sa(torrent::sa_make_inet6()), torrent::internal_error); } void test_socket_address::test_sin6_from_sa() { torrent::sa_unique_ptr sa_zero = wrap_ai_get_first_sa("::"); torrent::sin6_unique_ptr sin6_zero; CPPUNIT_ASSERT(sa_zero != nullptr); CPPUNIT_ASSERT_NO_THROW({ sin6_zero = torrent::sin6_from_sa(std::move(sa_zero)); }); CPPUNIT_ASSERT(sa_zero == nullptr); CPPUNIT_ASSERT(sin6_zero != nullptr); CPPUNIT_ASSERT(sin6_zero->sin6_addr.s6_addr[0] == 0x0); CPPUNIT_ASSERT(sin6_zero->sin6_addr.s6_addr[1] == 0x0); CPPUNIT_ASSERT(sin6_zero->sin6_addr.s6_addr[15] == 0x0); torrent::sa_unique_ptr sa_inet6 = wrap_ai_get_first_sa("ff01::1"); torrent::sin6_unique_ptr sin6_inet6; CPPUNIT_ASSERT(sa_inet6 != nullptr); CPPUNIT_ASSERT_NO_THROW({ sin6_inet6 = torrent::sin6_from_sa(std::move(sa_inet6)); }); CPPUNIT_ASSERT(sa_inet6 == nullptr); CPPUNIT_ASSERT(sin6_inet6 != nullptr); CPPUNIT_ASSERT(sin6_inet6->sin6_addr.s6_addr[0] == 0xff); CPPUNIT_ASSERT(sin6_inet6->sin6_addr.s6_addr[1] == 0x01); CPPUNIT_ASSERT(sin6_inet6->sin6_addr.s6_addr[15] == 0x01); CPPUNIT_ASSERT_THROW(torrent::sin6_from_sa(torrent::sa_unique_ptr()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sin6_from_sa(torrent::sa_make_unspec()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sin6_from_sa(torrent::sa_make_inet()), torrent::internal_error); } void test_socket_address::test_sa_equal() { TEST_DEFAULT_SA; CPPUNIT_ASSERT(torrent::sap_equal(torrent::sa_make_unspec(), torrent::sa_make_unspec())); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sa_make_inet(), torrent::sa_make_inet())); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sa_make_inet6(), torrent::sa_make_inet6())); CPPUNIT_ASSERT(!torrent::sap_equal(torrent::sa_make_unspec(), torrent::sa_make_inet())); CPPUNIT_ASSERT(!torrent::sap_equal(torrent::sa_make_unspec(), torrent::sa_make_inet6())); CPPUNIT_ASSERT(!torrent::sap_equal(torrent::sa_make_inet(), torrent::sa_make_inet6())); CPPUNIT_ASSERT(!torrent::sap_equal(torrent::sa_make_inet6(), torrent::sa_make_inet())); CPPUNIT_ASSERT(torrent::sap_equal(sin_1, sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(sin_1, c_sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(c_sin_1, sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(c_sin_1, c_sin_1)); CPPUNIT_ASSERT(!torrent::sap_equal(sin_1, sin_2)); CPPUNIT_ASSERT(!torrent::sap_equal(sin_1, c_sin_2)); CPPUNIT_ASSERT(!torrent::sap_equal(c_sin_1, sin_2)); CPPUNIT_ASSERT(!torrent::sap_equal(c_sin_1, c_sin_2)); CPPUNIT_ASSERT(torrent::sap_equal(sin6_1, sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(sin6_1, c_sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(c_sin6_1, sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(c_sin6_1, c_sin6_1)); CPPUNIT_ASSERT(!torrent::sap_equal(sin6_1, sin6_2)); CPPUNIT_ASSERT(!torrent::sap_equal(sin6_1, c_sin6_2)); CPPUNIT_ASSERT(!torrent::sap_equal(c_sin6_1, sin6_2)); CPPUNIT_ASSERT(!torrent::sap_equal(c_sin6_1, c_sin6_2)); CPPUNIT_ASSERT(torrent::sap_equal(sin_1_5000, sin_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(sin6_1_5000, sin6_1_5000)); CPPUNIT_ASSERT(!torrent::sap_equal(sin_1_5000, sin_1_5100)); CPPUNIT_ASSERT(!torrent::sap_equal(sin6_1_5000, sin6_1_5100)); CPPUNIT_ASSERT(!torrent::sap_equal(sin_1_5000, sin_2_5000)); CPPUNIT_ASSERT(!torrent::sap_equal(sin6_1_5000, sin6_2_5000)); CPPUNIT_ASSERT(!torrent::sap_equal(sin_1_5000, sin_2_5100)); CPPUNIT_ASSERT(!torrent::sap_equal(sin6_1_5000, sin6_2_5100)); CPPUNIT_ASSERT_THROW(torrent::sap_equal(torrent::sa_make_unix(""), torrent::sa_make_unix("")), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_equal(torrent::sa_make_unix(""), sin6_1), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_equal(sin6_1, torrent::sa_make_unix("")), torrent::internal_error); } void test_socket_address::test_sa_equal_addr() { TEST_DEFAULT_SA; CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sa_make_unspec(), torrent::sa_make_unspec())); CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sa_make_inet(), torrent::sa_make_inet())); CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sa_make_inet6(), torrent::sa_make_inet6())); CPPUNIT_ASSERT(!torrent::sap_equal_addr(torrent::sa_make_unspec(), torrent::sa_make_inet())); CPPUNIT_ASSERT(!torrent::sap_equal_addr(torrent::sa_make_unspec(), torrent::sa_make_inet6())); CPPUNIT_ASSERT(!torrent::sap_equal_addr(torrent::sa_make_inet(), torrent::sa_make_inet6())); CPPUNIT_ASSERT(!torrent::sap_equal_addr(torrent::sa_make_inet6(), torrent::sa_make_inet())); CPPUNIT_ASSERT(torrent::sap_equal_addr(sin_1, sin_1)); CPPUNIT_ASSERT(torrent::sap_equal_addr(sin_1, c_sin_1)); CPPUNIT_ASSERT(torrent::sap_equal_addr(c_sin_1, sin_1)); CPPUNIT_ASSERT(torrent::sap_equal_addr(c_sin_1, c_sin_1)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(sin_1, sin_2)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(sin_1, c_sin_2)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(c_sin_1, sin_2)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(c_sin_1, c_sin_2)); CPPUNIT_ASSERT(torrent::sap_equal_addr(sin6_1, sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal_addr(sin6_1, c_sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal_addr(c_sin6_1, sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal_addr(c_sin6_1, c_sin6_1)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(sin6_1, sin6_2)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(sin6_1, c_sin6_2)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(c_sin6_1, sin6_2)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(c_sin6_1, c_sin6_2)); CPPUNIT_ASSERT(torrent::sap_equal_addr(sin_1_5000, sin_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal_addr(sin6_1_5000, sin6_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal_addr(sin_1_5000, sin_1_5100)); CPPUNIT_ASSERT(torrent::sap_equal_addr(sin6_1_5000, sin6_1_5100)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(sin_1_5000, sin_2_5000)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(sin6_1_5000, sin6_2_5000)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(sin_1_5000, sin_2_5100)); CPPUNIT_ASSERT(!torrent::sap_equal_addr(sin6_1_5000, sin6_2_5100)); CPPUNIT_ASSERT_THROW(torrent::sap_equal_addr(torrent::sa_make_unix(""), torrent::sa_make_unix("")), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_equal_addr(torrent::sa_make_unix(""), sin6_1), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_equal_addr(sin6_1, torrent::sa_make_unix("")), torrent::internal_error); } void test_socket_address::test_sa_copy() { TEST_DEFAULT_SA; CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(torrent::sa_make_unspec()), torrent::sa_make_unspec())); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(torrent::sa_make_inet()), sin_any)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(torrent::sa_make_inet6()), sin6_any)); CPPUNIT_ASSERT(torrent::sap_copy(sin_1).get() != sin_1.get()); CPPUNIT_ASSERT(torrent::sap_copy(c_sin_1).get() != c_sin_1.get()); CPPUNIT_ASSERT(torrent::sap_copy(sin6_1).get() != sin6_1.get()); CPPUNIT_ASSERT(torrent::sap_copy(c_sin6_1).get() != c_sin6_1.get()); CPPUNIT_ASSERT(torrent::sap_copy(sin_1_5000).get() != sin_1_5000.get()); CPPUNIT_ASSERT(torrent::sap_copy(sin6_1_5000).get() != sin6_1_5000.get()); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(sin_1), sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(sin_1), c_sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(c_sin_1), sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(c_sin_1), c_sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(sin6_1), sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(sin6_1), c_sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(c_sin6_1), sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(c_sin6_1), c_sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(sin_1_5000), sin_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(sin6_1_5000), sin6_1_5000)); auto sin6_flags = torrent::sap_copy(sin6_1_5000); reinterpret_cast(sin6_flags.get())->sin6_flowinfo = 0x12345678; reinterpret_cast(sin6_flags.get())->sin6_scope_id = 0x12345678; // TODO: Need 'strict' equal test. CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy(sin6_flags), sin6_flags)); CPPUNIT_ASSERT_THROW(torrent::sap_copy(torrent::sa_unique_ptr()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_copy(torrent::c_sa_unique_ptr()), torrent::internal_error); } void test_socket_address::test_sa_copy_addr() { TEST_DEFAULT_SA; CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(torrent::sa_make_unspec()), torrent::sa_make_unspec())); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(torrent::sa_make_inet()), sin_any)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(torrent::sa_make_inet6()), sin6_any)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(torrent::sa_make_unspec(), 5000), torrent::sa_make_unspec())); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(torrent::sa_make_inet(), 5000), sin_any_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(torrent::sa_make_inet6(), 5000), sin6_any_5000)); CPPUNIT_ASSERT(torrent::sap_copy_addr(sin_1).get() != sin_1.get()); CPPUNIT_ASSERT(torrent::sap_copy_addr(c_sin_1).get() != c_sin_1.get()); CPPUNIT_ASSERT(torrent::sap_copy_addr(sin6_1).get() != sin6_1.get()); CPPUNIT_ASSERT(torrent::sap_copy_addr(c_sin6_1).get() != c_sin6_1.get()); CPPUNIT_ASSERT(torrent::sap_copy_addr(sin_1_5000).get() != sin_1_5000.get()); CPPUNIT_ASSERT(torrent::sap_copy_addr(sin6_1_5000).get() != sin6_1_5000.get()); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin_1), sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin_1), c_sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(c_sin_1), sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(c_sin_1), c_sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin_1, 5000), sin_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin_1, 5000), c_sin_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(c_sin_1, 5000), sin_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(c_sin_1, 5000), c_sin_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin6_1), sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin6_1), c_sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(c_sin6_1), sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(c_sin6_1), c_sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin6_1, 5000), sin6_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin6_1, 5000), c_sin6_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(c_sin6_1, 5000), sin6_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(c_sin6_1, 5000), c_sin6_1_5000)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin_1_5000), sin_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin6_1_5000), sin6_1)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin_1_5000, 5100), sin_1_5100)); CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin6_1_5000, 5100), sin6_1_5100)); auto sin6_flags = wrap_ai_get_first_sa("ff01::1", "5555"); reinterpret_cast(sin6_flags.get())->sin6_flowinfo = 0x12345678; reinterpret_cast(sin6_flags.get())->sin6_scope_id = 0x12345678; CPPUNIT_ASSERT(torrent::sap_equal(torrent::sap_copy_addr(sin6_flags), sin6_1)); CPPUNIT_ASSERT_THROW(torrent::sap_copy_addr(torrent::sa_unique_ptr()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_copy_addr(torrent::c_sa_unique_ptr()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_copy_addr(torrent::sa_unique_ptr(), 5000), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_copy_addr(torrent::c_sa_unique_ptr(), 5000), torrent::internal_error); } void test_socket_address::test_sa_from_v4mapped() { TEST_DEFAULT_SA; CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sap_from_v4mapped(sin6_v4_any), sin_any)); CPPUNIT_ASSERT(torrent::sap_is_port_any(torrent::sap_from_v4mapped(sin6_v4_any))); CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sap_from_v4mapped(sin6_v4_1), sin_1)); CPPUNIT_ASSERT(torrent::sap_is_port_any(torrent::sap_from_v4mapped(sin6_v4_1))); CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sap_from_v4mapped(sin6_v4_bc), sin_bc)); CPPUNIT_ASSERT(torrent::sap_is_port_any(torrent::sap_from_v4mapped(sin6_v4_bc))); CPPUNIT_ASSERT_THROW(torrent::sap_from_v4mapped(torrent::sa_make_unspec()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_from_v4mapped(torrent::sa_make_inet()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_from_v4mapped(torrent::sa_make_unix("")), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_from_v4mapped(sin_any), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_from_v4mapped(sin_bc), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_from_v4mapped(sin_1), torrent::internal_error); } void test_socket_address::test_sa_to_v4mapped() { TEST_DEFAULT_SA; CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sap_to_v4mapped(sin_any), sin6_v4_any)); CPPUNIT_ASSERT(torrent::sap_is_v4mapped(torrent::sap_to_v4mapped(sin_any))); CPPUNIT_ASSERT(torrent::sap_is_port_any(torrent::sap_to_v4mapped(sin_any))); CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sap_to_v4mapped(sin_bc), sin6_v4_bc)); CPPUNIT_ASSERT(torrent::sap_is_v4mapped(torrent::sap_to_v4mapped(sin_bc))); CPPUNIT_ASSERT(torrent::sap_is_port_any(torrent::sap_to_v4mapped(sin_bc))); CPPUNIT_ASSERT(torrent::sap_equal_addr(torrent::sap_to_v4mapped(sin_1), sin6_v4_1)); CPPUNIT_ASSERT(torrent::sap_is_v4mapped(torrent::sap_to_v4mapped(sin_1))); CPPUNIT_ASSERT(torrent::sap_is_port_any(torrent::sap_to_v4mapped(sin_1))); CPPUNIT_ASSERT_THROW(torrent::sap_to_v4mapped(torrent::sa_make_unspec()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_to_v4mapped(torrent::sa_make_inet6()), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_to_v4mapped(torrent::sa_make_unix("")), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_to_v4mapped(sin6_any), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_to_v4mapped(sin6_1), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_to_v4mapped(sin6_v4_any), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_to_v4mapped(sin6_v4_bc), torrent::internal_error); CPPUNIT_ASSERT_THROW(torrent::sap_to_v4mapped(sin6_v4_1), torrent::internal_error); } libtorrent-0.16.17/test/torrent/net/test_socket_address.h000066400000000000000000000015441522271512000235220ustar00rootroot00000000000000#include class test_socket_address : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(test_socket_address); CPPUNIT_TEST(test_sa_is_any); CPPUNIT_TEST(test_sa_is_broadcast); CPPUNIT_TEST(test_make); CPPUNIT_TEST(test_sin_from_sa); CPPUNIT_TEST(test_sin6_from_sa); CPPUNIT_TEST(test_sa_equal); CPPUNIT_TEST(test_sa_equal_addr); CPPUNIT_TEST(test_sa_copy); CPPUNIT_TEST(test_sa_copy_addr); CPPUNIT_TEST(test_sa_from_v4mapped); CPPUNIT_TEST(test_sa_to_v4mapped); CPPUNIT_TEST_SUITE_END(); public: void test_sa_is_any(); void test_sa_is_broadcast(); void test_make(); void test_sin_from_sa(); void test_sin6_from_sa(); void test_sa_equal(); void test_sa_equal_addr(); void test_sa_copy(); void test_sa_copy_addr(); void test_sa_from_v4mapped(); void test_sa_to_v4mapped(); }; libtorrent-0.16.17/test/torrent/net/test_socket_address_key.h000066400000000000000000000005001522271512000243610ustar00rootroot00000000000000#include #include "protocol/request_list.h" class test_socket_address_key : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(test_socket_address_key); CPPUNIT_TEST(test_basic); CPPUNIT_TEST_SUITE_END(); public: void setUp() {} void tearDown() {} void test_basic(); }; libtorrent-0.16.17/test/torrent/object_static_map_test.cc000066400000000000000000000330371522271512000235510ustar00rootroot00000000000000#include "config.h" #include #include #include "torrent/object_static_map.h" #include "protocol/extensions.h" #include "object_test_utils.h" #include "object_static_map_test.h" CPPUNIT_TEST_SUITE_REGISTRATION(ObjectStaticMapTest); // Possible bencode keys in a DHT message. enum keys { key_d_a, key_d_d_a, key_d_b, key_e_0, key_e_1, key_e_2, key_s_a, key_s_b, key_v_a, key_v_b, key_LAST, }; enum keys_2 { key_2_d_x_y, key_2_d_x_z, key_2_l_x_1, key_2_l_x_2, key_2_s_a, key_2_v_a, key_2_LAST }; typedef torrent::static_map_type test_map_type; typedef torrent::static_map_type test_map_2_type; // List of all possible keys we need/support in a DHT message. // Unsupported keys we receive are dropped (ignored) while decoding. // See torrent/object_static_map.h for how this works. template <> const test_map_type::key_list_type test_map_type::keys = { { key_d_a, "d_a::b" }, { key_d_d_a, "d_a::c::a" }, { key_d_b, "d_a::d" }, { key_e_0, "e[]" }, { key_e_1, "e[]" }, { key_e_2, "e[]" }, { key_s_a, "s_a" }, { key_s_b, "s_b" }, { key_v_a, "v_a" }, { key_v_b, "v_b" }, }; template <> const test_map_2_type::key_list_type test_map_2_type::keys = { { key_2_d_x_y, "d_x::f" }, { key_2_d_x_z, "d_x::g" }, { key_2_l_x_1, "l_x[]" }, { key_2_l_x_2, "l_x[]" }, { key_2_s_a, "s_a" }, { key_2_v_a, "v_a" }, }; void ObjectStaticMapTest::test_basics() { test_map_type test_map; test_map[key_v_a] = int64_t(1); test_map[key_v_b] = int64_t(2); test_map[key_s_a] = std::string("a"); test_map[key_s_b] = std::string("b"); CPPUNIT_ASSERT(test_map[key_v_a].as_value() == 1); CPPUNIT_ASSERT(test_map[key_v_b].as_value() == 2); CPPUNIT_ASSERT(test_map[key_s_a].as_string() == "a"); CPPUNIT_ASSERT(test_map[key_s_b].as_string() == "b"); } void ObjectStaticMapTest::test_write() { test_map_type test_map; test_map[key_v_a] = int64_t(1); test_map[key_v_b] = int64_t(2); test_map[key_s_a] = std::string("a"); // test_map[key_s_b] = std::string("b"); test_map[key_d_a] = std::string("xx"); test_map[key_d_d_a] = std::string("a"); test_map[key_d_b] = std::string("yy"); test_map[key_e_0] = std::string("f"); test_map[key_e_1] = torrent::object_create_raw_bencode_c_str("1:g"); test_map[key_e_2] = std::string("h"); char buffer[1024]; torrent::object_buffer_t result = torrent::static_map_write_bencode_c(torrent::object_write_to_buffer, NULL, std::make_pair(buffer, buffer + sizeof(buffer)), test_map); // std::cout << "static map write: '" << std::string(buffer, std::distance(buffer, result.first)) << "'" << std::endl; CPPUNIT_ASSERT(validate_bencode(buffer, result.first)); } static const char* test_read_bencode = "d3:d_xd1:fi3e1:gli4ei5eee3:l_xli1ei2ei3ee3:s_a1:a3:v_ai2ee"; static const char* test_read_skip_bencode = "d3:d_xd1:fi3e1:gli4ei5eee1:fi1e3:l_xli1ei2ei3ee3:s_a1:a3:v_ai2ee"; template bool static_map_read_bencode(map_type& map, const char* str) { try { return torrent::static_map_read_bencode(str, str + strlen(str), map) == str + strlen(str); } catch (const torrent::bencode_error&) { return false; } } template bool static_map_read_bencode_exception(map_type& map, const char* str) { try { torrent::static_map_read_bencode(str, str + strlen(str), map); return false; } catch (const torrent::bencode_error&) { return true; } } void ObjectStaticMapTest::test_read() { test_map_2_type test_map; CPPUNIT_ASSERT(static_map_read_bencode(test_map, test_read_bencode)); CPPUNIT_ASSERT(test_map[key_2_d_x_y].as_value() == 3); CPPUNIT_ASSERT(test_map[key_2_d_x_z].as_list().size() == 2); CPPUNIT_ASSERT(test_map[key_2_l_x_1].as_value() == 1); CPPUNIT_ASSERT(test_map[key_2_l_x_2].as_value() == 2); CPPUNIT_ASSERT(test_map[key_2_s_a].as_string() == "a"); CPPUNIT_ASSERT(test_map[key_2_v_a].as_value() == 2); test_map_2_type test_skip_map; CPPUNIT_ASSERT(static_map_read_bencode(test_skip_map, test_read_skip_bencode)); } enum ext_test_keys { key_e, // key_m_utMetadata, key_m_utPex, // key_metadataSize, key_p, key_reqq, key_v, key_test_LAST }; typedef torrent::static_map_type ext_test_message; template <> const ext_test_message::key_list_type ext_test_message::keys = { { key_e, "e" }, { key_m_utPex, "m::ut_pex" }, { key_p, "p" }, { key_reqq, "reqq" }, { key_v, "v" }, }; void ObjectStaticMapTest::test_read_extensions() { ext_test_message test_ext; CPPUNIT_ASSERT(static_map_read_bencode(test_ext, "d1:ai1ee")); CPPUNIT_ASSERT(static_map_read_bencode(test_ext, "d1:mi1ee")); CPPUNIT_ASSERT(static_map_read_bencode(test_ext, "d1:mdee")); CPPUNIT_ASSERT(static_map_read_bencode(test_ext, "d6:ut_pexi0ee")); CPPUNIT_ASSERT(static_map_read_bencode(test_ext, "d1:md6:ut_pexi0eee")); } template bool static_map_write_bencode(map_type map, const char* original) { try { char buffer[1024]; char* last = torrent::static_map_write_bencode_c(&torrent::object_write_to_buffer, NULL, torrent::object_buffer_t(buffer, buffer + 1024), map).first; return std::strncmp(buffer, original, std::distance(buffer, last)) == 0; } catch (const torrent::bencode_error&) { return false; } } // // Proper unit tests: // enum keys_empty { key_empty_LAST }; enum keys_single { key_single_a, key_single_LAST }; enum keys_raw { key_raw_a, key_raw_LAST }; enum keys_raw_types { key_raw_types_empty, key_raw_types_list, key_raw_types_map, key_raw_types_str, key_raw_types_LAST}; enum keys_multiple { key_multiple_a, key_multiple_b, key_multiple_c, key_multiple_LAST }; enum keys_dict { key_dict_a_b, key_dict_LAST }; typedef torrent::static_map_type test_empty_type; typedef torrent::static_map_type test_single_type; typedef torrent::static_map_type test_raw_type; typedef torrent::static_map_type test_raw_types_type; typedef torrent::static_map_type test_multiple_type; typedef torrent::static_map_type test_dict_type; template <> const test_empty_type::key_list_type test_empty_type::keys = { }; template <> const test_single_type::key_list_type test_single_type::keys = { { key_single_a, "b" } }; template <> const test_raw_type::key_list_type test_raw_type::keys = { { key_raw_a, "b*" } }; template <> const test_raw_types_type::key_list_type test_raw_types_type::keys = { { key_raw_types_empty, "e*"}, { key_raw_types_list, "l*L"}, { key_raw_types_map, "m*M"}, { key_raw_types_str, "s*S"} }; template <> const test_multiple_type::key_list_type test_multiple_type::keys = { { key_multiple_a, "a" }, { key_multiple_b, "b*" }, { key_multiple_c, "c" } }; template <> const test_dict_type::key_list_type test_dict_type::keys = { { key_dict_a_b, "a::b" } }; void ObjectStaticMapTest::test_read_empty() { test_empty_type map_normal; test_empty_type map_skip; test_empty_type map_incomplete; CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "de")); CPPUNIT_ASSERT(static_map_read_bencode(map_skip, "d1:ai1ee")); CPPUNIT_ASSERT(static_map_read_bencode_exception(map_incomplete, "")); CPPUNIT_ASSERT(static_map_read_bencode_exception(map_incomplete, "d")); } void ObjectStaticMapTest::test_read_single() { test_single_type map_normal; test_single_type map_skip; test_single_type map_incomplete; CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "d1:bi1ee")); CPPUNIT_ASSERT(map_normal[key_single_a].as_value() == 1); CPPUNIT_ASSERT(static_map_read_bencode(map_skip, "d1:ai0e1:bi1e1:ci2ee")); CPPUNIT_ASSERT(map_skip[key_single_a].as_value() == 1); CPPUNIT_ASSERT(static_map_read_bencode_exception(map_incomplete, "d")); CPPUNIT_ASSERT(static_map_read_bencode_exception(map_incomplete, "d1:b")); CPPUNIT_ASSERT(static_map_read_bencode_exception(map_incomplete, "d1:bi1")); CPPUNIT_ASSERT(static_map_read_bencode_exception(map_incomplete, "d1:bi1e")); } void ObjectStaticMapTest::test_read_single_raw() { test_raw_type map_raw; CPPUNIT_ASSERT(static_map_read_bencode(map_raw, "d1:bi1ee")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_a].as_raw_bencode(), "i1e")); CPPUNIT_ASSERT(static_map_read_bencode(map_raw, "d1:b2:abe")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_a].as_raw_bencode(), "2:ab")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_a].as_raw_bencode().as_raw_string(), "ab")); CPPUNIT_ASSERT(static_map_read_bencode(map_raw, "d1:bli1ei2eee")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_a].as_raw_bencode(), "li1ei2ee")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_a].as_raw_bencode().as_raw_list(), "i1ei2e")); CPPUNIT_ASSERT(static_map_read_bencode(map_raw, "d1:bd1:ai1e1:bi2eee")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_a].as_raw_bencode(), "d1:ai1e1:bi2ee")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_a].as_raw_bencode().as_raw_map(), "1:ai1e1:bi2e")); } void ObjectStaticMapTest::test_read_raw_types() { test_raw_types_type map_raw; CPPUNIT_ASSERT(static_map_read_bencode(map_raw, "d" "1:ei1e" "1:lli1ei2ee" "1:md1:ai1e1:bi2ee" "1:s2:ab" "e")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_types_empty].as_raw_bencode(), "i1e")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_types_str].as_raw_string(), "ab")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_types_list].as_raw_list(), "i1ei2e")); CPPUNIT_ASSERT(torrent::raw_bencode_equal_c_str(map_raw[key_raw_types_map].as_raw_map(), "1:ai1e1:bi2e")); } void ObjectStaticMapTest::test_read_multiple() { test_multiple_type map_normal; CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "d1:ai1ee")); CPPUNIT_ASSERT(map_normal[key_multiple_a].as_value() == 1); CPPUNIT_ASSERT(map_normal[key_multiple_b].is_empty()); CPPUNIT_ASSERT(map_normal[key_multiple_c].is_empty()); map_normal = test_multiple_type(); CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "d1:ci3ee")); CPPUNIT_ASSERT(map_normal[key_multiple_a].is_empty()); CPPUNIT_ASSERT(map_normal[key_multiple_b].is_empty()); CPPUNIT_ASSERT(map_normal[key_multiple_c].as_value() == 3); map_normal = test_multiple_type(); CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "d1:ai1e1:ci3ee")); CPPUNIT_ASSERT(map_normal[key_multiple_a].as_value() == 1); CPPUNIT_ASSERT(map_normal[key_multiple_b].is_empty()); CPPUNIT_ASSERT(map_normal[key_multiple_c].as_value() == 3); map_normal = test_multiple_type(); CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "d1:ai1e1:bi2e1:ci3ee")); CPPUNIT_ASSERT(map_normal[key_multiple_a].as_value() == 1); // CPPUNIT_ASSERT(map_normal[key_multiple_b].as_raw_bencode().as_raw_value().size() == 1); // CPPUNIT_ASSERT(map_normal[key_multiple_b].as_raw_bencode().as_raw_value().data()[0] == '2'); CPPUNIT_ASSERT(map_normal[key_multiple_c].as_value() == 3); } void ObjectStaticMapTest::test_read_dict() { test_dict_type map_normal; CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "d1:ai1ee")); CPPUNIT_ASSERT(map_normal[key_dict_a_b].is_empty()); CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "d1:adee")); CPPUNIT_ASSERT(map_normal[key_dict_a_b].is_empty()); CPPUNIT_ASSERT(static_map_read_bencode(map_normal, "d1:ad1:bi1eee")); CPPUNIT_ASSERT(map_normal[key_dict_a_b].as_value() == 1); } void ObjectStaticMapTest::test_write_empty() { test_empty_type map_normal; CPPUNIT_ASSERT(static_map_write_bencode(map_normal, "de")); } void ObjectStaticMapTest::test_write_single() { test_single_type map_value; CPPUNIT_ASSERT(static_map_write_bencode(map_value, "de")); map_value[key_single_a] = (int64_t)1; CPPUNIT_ASSERT(static_map_write_bencode(map_value, "d1:bi1ee")); map_value[key_single_a] = "test"; CPPUNIT_ASSERT(static_map_write_bencode(map_value, "d1:b4:teste")); map_value[key_single_a] = torrent::Object::create_list(); map_value[key_single_a].as_list().emplace_back("test"); CPPUNIT_ASSERT(static_map_write_bencode(map_value, "d1:bl4:testee")); map_value[key_single_a] = torrent::raw_bencode("i1e", 3); CPPUNIT_ASSERT(static_map_write_bencode(map_value, "d1:bi1ee")); // map_value[key_single_a] = torrent::raw_value("1", 1); // CPPUNIT_ASSERT(static_map_write_bencode(map_value, "d1:bi1ee")); map_value[key_single_a] = torrent::raw_string("test", 4); CPPUNIT_ASSERT(static_map_write_bencode(map_value, "d1:b4:teste")); map_value[key_single_a] = torrent::raw_list("1:a2:bb", strlen("1:a2:bb")); CPPUNIT_ASSERT(static_map_write_bencode(map_value, "d1:bl1:a2:bbee")); map_value[key_single_a] = torrent::raw_map("1:a2:bb", strlen("1:a2:bb")); CPPUNIT_ASSERT(static_map_write_bencode(map_value, "d1:bd1:a2:bbee")); } void ObjectStaticMapTest::test_write_multiple() { // test_multiple_type map_value; // CPPUNIT_ASSERT(static_map_write_bencode(map_value, "de")); } libtorrent-0.16.17/test/torrent/object_static_map_test.h000066400000000000000000000020101522271512000233760ustar00rootroot00000000000000#include #include "torrent/object.h" class ObjectStaticMapTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(ObjectStaticMapTest); CPPUNIT_TEST(test_basics); CPPUNIT_TEST(test_write); CPPUNIT_TEST(test_read); CPPUNIT_TEST(test_read_extensions); CPPUNIT_TEST(test_read_empty); CPPUNIT_TEST(test_read_single); CPPUNIT_TEST(test_read_single_raw); CPPUNIT_TEST(test_read_raw_types); CPPUNIT_TEST(test_read_multiple); CPPUNIT_TEST(test_read_dict); CPPUNIT_TEST(test_write_empty); CPPUNIT_TEST(test_write_single); CPPUNIT_TEST(test_write_multiple); CPPUNIT_TEST_SUITE_END(); public: void test_basics(); void test_write(); void test_read(); void test_read_extensions(); // Proper unit tests: void test_read_empty(); void test_read_single(); void test_read_single_raw(); void test_read_raw_types(); void test_read_multiple(); void test_read_dict(); void test_write_empty(); void test_write_single(); void test_write_multiple(); }; libtorrent-0.16.17/test/torrent/object_stream_test.cc000066400000000000000000000160301522271512000227120ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include "object_stream_test.h" #include "object_test_utils.h" CPPUNIT_TEST_SUITE_REGISTRATION(ObjectStreamTest); namespace { const auto ordered_bencode = "d1:ei0e4:ipv44:XXXX4:ipv616:XXXXXXXXXXXXXXXX1:md11:upload_onlyi3e12:ut_holepunchi4e11:ut_metadatai2e6:ut_pexi1ee13:metadata_sizei15408e1:pi16033e4:reqqi255e1:v15:uuTorrent 1.8.46:yourip4:XXXXe"; const auto unordered_bencode = "d1:ei0e1:md11:upload_onlyi3e12:ut_holepunchi4e11:ut_metadatai2e6:ut_pexi1ee4:ipv44:XXXX4:ipv616:XXXXXXXXXXXXXXXX13:metadata_sizei15408e1:pi16033e4:reqqi255e1:v15:uuTorrent 1.8.46:yourip4:XXXXe"; const auto string_bencode = "32:aaaaaaaabbbbbbbbccccccccdddddddd"; // Dummy function that invalidates the buffer once called. torrent::object_buffer_t object_write_to_invalidate(void* data, torrent::object_buffer_t buffer) { return torrent::object_buffer_t(buffer.second, buffer.second); } bool object_write_bencode(const torrent::Object& obj, const char* original) { try { char buffer[1025]; char* last = torrent::object_write_bencode(buffer, buffer + 1024, &obj).first; return std::strncmp(buffer, original, std::distance(buffer, last)) == 0; } catch (const torrent::bencode_error&) { return false; } } bool object_stream_read_skip(const char* input) { try { torrent::Object tmp; return torrent::object_read_bencode_c(input, input + strlen(input), &tmp) == input + strlen(input) && torrent::object_read_bencode_skip_c(input, input + strlen(input)) == input + strlen(input); } catch (const torrent::bencode_error&) { return false; } } bool object_stream_read_skip_catch(const char* input) { try { torrent::Object tmp; torrent::object_read_bencode_c(input, input + strlen(input), &tmp); std::cout << "(N)"; return false; } catch (const torrent::bencode_error&) { } try { torrent::object_read_bencode_skip_c(input, input + strlen(input)); std::cout << "(S)"; return false; } catch (const torrent::bencode_error&) { return true; } } } // namespace void ObjectStreamTest::testInputOrdered() { torrent::Object orderedObj = create_bencode(ordered_bencode); torrent::Object unorderedObj = create_bencode(unordered_bencode); CPPUNIT_ASSERT(!(orderedObj.flags() & torrent::Object::flag_unordered)); CPPUNIT_ASSERT(unorderedObj.flags() & torrent::Object::flag_unordered); } void ObjectStreamTest::testInputNullKey() { torrent::Object obj = create_bencode("d0:i1e5:filesi2ee"); CPPUNIT_ASSERT(!(obj.flags() & torrent::Object::flag_unordered)); } void ObjectStreamTest::testOutputMask() { torrent::Object normalObj = create_bencode("d1:ai1e1:bi2e1:ci3ee"); CPPUNIT_ASSERT(compare_bencode(normalObj, "d1:ai1e1:bi2e1:ci3ee")); normalObj.get_key("b").set_flags(torrent::Object::flag_session_data); normalObj.get_key("c").set_flags(torrent::Object::flag_static_data); CPPUNIT_ASSERT(compare_bencode(normalObj, "d1:ai1e1:ci3ee", torrent::Object::flag_session_data)); } // // Testing for bugs in bencode write. // void ObjectStreamTest::testBuffer() { char raw_buffer[16]; torrent::object_buffer_t buffer(raw_buffer, raw_buffer + 16); torrent::Object obj = create_bencode(string_bencode); object_write_bencode_c(&object_write_to_invalidate, NULL, buffer, &obj); } static const char* single_level_bencode = "d1:ai1e1:bi2e1:cl1:ai1e1:bi2eee"; void ObjectStreamTest::testReadBencodeC() { torrent::Object orderedObj = create_bencode_c(ordered_bencode); torrent::Object unorderedObj = create_bencode_c(unordered_bencode); CPPUNIT_ASSERT(!(orderedObj.flags() & torrent::Object::flag_unordered)); CPPUNIT_ASSERT(unorderedObj.flags() & torrent::Object::flag_unordered); CPPUNIT_ASSERT(compare_bencode(orderedObj, ordered_bencode)); // torrent::Object single_level = create_bencode_c(single_level_bencode); torrent::Object single_level = create_bencode_c(single_level_bencode); CPPUNIT_ASSERT(compare_bencode(single_level, single_level_bencode)); } void ObjectStreamTest::test_read_skip() { CPPUNIT_ASSERT(object_stream_read_skip("i0e")); CPPUNIT_ASSERT(object_stream_read_skip("i9999e")); CPPUNIT_ASSERT(object_stream_read_skip("i-1e")); CPPUNIT_ASSERT(object_stream_read_skip("i-9999e")); CPPUNIT_ASSERT(object_stream_read_skip("0:")); CPPUNIT_ASSERT(object_stream_read_skip("4:test")); CPPUNIT_ASSERT(object_stream_read_skip("le")); CPPUNIT_ASSERT(object_stream_read_skip("li1ee")); CPPUNIT_ASSERT(object_stream_read_skip("llee")); CPPUNIT_ASSERT(object_stream_read_skip("ll1:a1:bel1:c1:dee")); CPPUNIT_ASSERT(object_stream_read_skip("de")); CPPUNIT_ASSERT(object_stream_read_skip("d1:ai1e1:b1:xe")); CPPUNIT_ASSERT(object_stream_read_skip("d1:ali1eee")); CPPUNIT_ASSERT(object_stream_read_skip("d1:ad1:bi1eee")); CPPUNIT_ASSERT(object_stream_read_skip("d1:md6:ut_pexi0eee")); } void ObjectStreamTest::test_read_skip_invalid() { CPPUNIT_ASSERT(object_stream_read_skip_catch("")); CPPUNIT_ASSERT(object_stream_read_skip_catch("i")); CPPUNIT_ASSERT(object_stream_read_skip_catch("1")); CPPUNIT_ASSERT(object_stream_read_skip_catch("d")); CPPUNIT_ASSERT(object_stream_read_skip_catch("i-0e")); CPPUNIT_ASSERT(object_stream_read_skip_catch("i--1e")); CPPUNIT_ASSERT(object_stream_read_skip_catch("-1")); CPPUNIT_ASSERT(object_stream_read_skip_catch("-1a")); CPPUNIT_ASSERT(object_stream_read_skip_catch("llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll" "llllllll")); } void ObjectStreamTest::test_write() { torrent::Object obj; CPPUNIT_ASSERT(object_write_bencode(torrent::Object(), "")); CPPUNIT_ASSERT(object_write_bencode(torrent::Object((int64_t)0), "i0e")); CPPUNIT_ASSERT(object_write_bencode(torrent::Object((int64_t)1), "i1e")); CPPUNIT_ASSERT(object_write_bencode(torrent::Object((int64_t)-1), "i-1e")); CPPUNIT_ASSERT(object_write_bencode(torrent::Object(INT64_C(123456789012345)), "i123456789012345e")); CPPUNIT_ASSERT(object_write_bencode(torrent::Object(INT64_C(-123456789012345)), "i-123456789012345e")); CPPUNIT_ASSERT(object_write_bencode(torrent::Object("test"), "4:test")); CPPUNIT_ASSERT(object_write_bencode(torrent::Object::create_list(), "le")); CPPUNIT_ASSERT(object_write_bencode(torrent::Object::create_map(), "de")); obj = torrent::Object::create_map(); obj.as_map()["a"] = (int64_t)1; CPPUNIT_ASSERT(object_write_bencode(obj, "d1:ai1ee")); obj.as_map()["b"] = "test"; CPPUNIT_ASSERT(object_write_bencode(obj, "d1:ai1e1:b4:teste")); obj.as_map()["c"] = torrent::Object::create_list(); obj.as_map()["c"].as_list().emplace_back("foo"); CPPUNIT_ASSERT(object_write_bencode(obj, "d1:ai1e1:b4:test1:cl3:fooee")); obj.as_map()["c"].as_list().emplace_back(); obj.as_map()["d"] = torrent::Object(); CPPUNIT_ASSERT(object_write_bencode(obj, "d1:ai1e1:b4:test1:cl3:fooee")); } libtorrent-0.16.17/test/torrent/object_stream_test.h000066400000000000000000000012621522271512000225550ustar00rootroot00000000000000#include #include "torrent/object_stream.h" class ObjectStreamTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(ObjectStreamTest); CPPUNIT_TEST(testInputOrdered); CPPUNIT_TEST(testInputNullKey); CPPUNIT_TEST(testOutputMask); CPPUNIT_TEST(testBuffer); CPPUNIT_TEST(testReadBencodeC); CPPUNIT_TEST(test_read_skip); CPPUNIT_TEST(test_read_skip_invalid); CPPUNIT_TEST(test_write); CPPUNIT_TEST_SUITE_END(); public: void testInputOrdered(); void testInputNullKey(); void testOutputMask(); void testBuffer(); void testReadBencodeC(); void test_read_skip(); void test_read_skip_invalid(); void test_write(); }; libtorrent-0.16.17/test/torrent/object_test.cc000066400000000000000000000116771522271512000213530ustar00rootroot00000000000000#include "config.h" #include #include #include "object_test.h" #include "object_test_utils.h" CPPUNIT_TEST_SUITE_REGISTRATION(ObjectTest); namespace { [[maybe_unused]] void object_test_return_void() { } } // namespace // template // struct object_void_wrapper { // typedef // object_void_wrapper(Slot s) : m_slot(s) {} // torrent::Object operator () () { m_slot( // Slot m_slot; // } void ObjectTest::test_basic() { // std::cout << "sizeof(torrent::Object) = " << sizeof(torrent::Object) << std::endl; // std::cout << "sizeof(torrent::Object::list_type) = " << sizeof(torrent::Object::list_type) << std::endl; // std::cout << "sizeof(torrent::Object::map_type) = " << sizeof(torrent::Object::map_type) << std::endl; // torrent::Object obj_void(object_test_return_void()); } void ObjectTest::test_flags() { torrent::Object objectFlagsValue = torrent::Object(int64_t()); torrent::Object objectNoFlagsEmpty = torrent::Object(); torrent::Object objectNoFlagsValue = torrent::Object(int64_t()); objectFlagsValue.set_flags(torrent::Object::flag_static_data | torrent::Object::flag_session_data); CPPUNIT_ASSERT(objectNoFlagsEmpty.flags() == 0); CPPUNIT_ASSERT(objectNoFlagsValue.flags() == 0); CPPUNIT_ASSERT(objectFlagsValue.flags() & torrent::Object::flag_session_data && objectFlagsValue.flags() & torrent::Object::flag_static_data); objectFlagsValue.unset_flags(torrent::Object::flag_session_data); CPPUNIT_ASSERT(!(objectFlagsValue.flags() & torrent::Object::flag_session_data) && objectFlagsValue.flags() & torrent::Object::flag_static_data); } void ObjectTest::test_merge() { } #define TEST_VALUE_A "i10e" #define TEST_VALUE_B "i20e" #define TEST_STRING_A "1:g" #define TEST_STRING_B "1:h" #define TEST_MAP_A "d1:ai1e1:bi2ee" #define TEST_MAP_B "d1:ci4e1:di5ee" #define TEST_LIST_A "l1:e1:fe" #define TEST_LIST_B "li1ei2ee" static bool swap_compare(const char* left, const char* right) { torrent::Object obj_left = create_bencode(left); torrent::Object obj_right = create_bencode(right); obj_left.swap(obj_right); if (!compare_bencode(obj_left, right) || !compare_bencode(obj_right, left)) return false; obj_left.swap(obj_right); if (!compare_bencode(obj_left, left) || !compare_bencode(obj_right, right)) return false; return true; } static bool swap_compare_dict_key(const char* left_key, const char* left_obj, const char* right_key, const char* right_obj) { torrent::Object obj_left = torrent::Object::create_dict_key(); torrent::Object obj_right = torrent::Object::create_dict_key(); obj_left.as_dict_key() = left_key; obj_left.as_dict_obj() = create_bencode(left_obj); obj_right.as_dict_key() = right_key; obj_right.as_dict_obj() = create_bencode(right_obj); obj_left.swap(obj_right); if (obj_left.as_dict_key() != right_key || !compare_bencode(obj_left.as_dict_obj(), right_obj) || obj_right.as_dict_key() != left_key || !compare_bencode(obj_right.as_dict_obj(), left_obj)) return false; obj_left.swap(obj_right); if (obj_left.as_dict_key() != left_key || !compare_bencode(obj_left.as_dict_obj(), left_obj) || obj_right.as_dict_key() != right_key || !compare_bencode(obj_right.as_dict_obj(), right_obj)) return false; return true; } void ObjectTest::test_swap_and_move() { CPPUNIT_ASSERT(swap_compare(TEST_VALUE_A, TEST_VALUE_B)); CPPUNIT_ASSERT(swap_compare(TEST_STRING_A, TEST_STRING_B)); CPPUNIT_ASSERT(swap_compare(TEST_MAP_A, TEST_MAP_B)); CPPUNIT_ASSERT(swap_compare(TEST_LIST_A, TEST_LIST_B)); CPPUNIT_ASSERT(swap_compare(TEST_VALUE_A, TEST_STRING_B)); CPPUNIT_ASSERT(swap_compare(TEST_STRING_A, TEST_MAP_B)); CPPUNIT_ASSERT(swap_compare(TEST_MAP_A, TEST_LIST_B)); CPPUNIT_ASSERT(swap_compare(TEST_LIST_A, TEST_VALUE_B)); CPPUNIT_ASSERT(swap_compare("i1e", TEST_VALUE_A)); CPPUNIT_ASSERT(swap_compare("i1e", TEST_MAP_A)); CPPUNIT_ASSERT(swap_compare("i1e", TEST_LIST_A)); CPPUNIT_ASSERT(swap_compare_dict_key("a", TEST_VALUE_A, "b", TEST_STRING_B)); CPPUNIT_ASSERT(swap_compare_dict_key("a", TEST_STRING_A, "b", TEST_STRING_B)); } void ObjectTest::test_create_normal() { torrent::Object obj; CPPUNIT_ASSERT(torrent::object_create_normal(create_bencode_raw_bencode_c("i45e")).as_value() == 45); CPPUNIT_ASSERT(torrent::object_create_normal(create_bencode_raw_bencode_c("4:test")).as_string() == "test"); CPPUNIT_ASSERT(torrent::object_create_normal(create_bencode_raw_bencode_c("li5ee")).as_list().front().as_value() == 5); CPPUNIT_ASSERT(torrent::object_create_normal(create_bencode_raw_bencode_c("d1:ai6ee")).as_map()["a"].as_value() == 6); CPPUNIT_ASSERT(torrent::object_create_normal(create_bencode_raw_string_c("test")).as_string() == "test"); CPPUNIT_ASSERT(torrent::object_create_normal(create_bencode_raw_list_c("i5ei6e")).as_list().back().as_value() == 6); CPPUNIT_ASSERT(torrent::object_create_normal(create_bencode_raw_map_c("1:ai2e1:bi3e")).as_map()["b"].as_value() == 3); } libtorrent-0.16.17/test/torrent/object_test.h000066400000000000000000000007031522271512000212010ustar00rootroot00000000000000#include #include "torrent/object.h" class ObjectTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(ObjectTest); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_flags); CPPUNIT_TEST(test_swap_and_move); CPPUNIT_TEST(test_create_normal); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_flags(); void test_merge(); void test_swap_and_move(); void test_create_normal(); }; libtorrent-0.16.17/test/torrent/object_test_utils.cc000066400000000000000000000027061522271512000225640ustar00rootroot00000000000000#include "config.h" #include #include #include "torrent/object_stream.h" #include "object_test_utils.h" torrent::Object create_bencode(const char* str) { torrent::Object obj; std::stringstream stream(str); stream >> obj; CPPUNIT_ASSERT(!stream.fail()); return obj; } torrent::Object create_bencode_c(const char* str) { torrent::Object obj; const char* last = str + strlen(str); CPPUNIT_ASSERT(object_read_bencode_c(str, last, &obj) == last); return obj; } // torrent::Object // create_bencode_raw_bencode_c(const char* str) { // torrent::Object obj; // const char* last = str + strlen(str); // CPPUNIT_ASSERT(object_read_bencode_skip_c(str, last) == last); // return torrent::raw_bencode(str, std::distance(str, last)); // } // torrent::Object // create_bencode_raw_list_c(const char* str) { // torrent::Object obj; // const char* last = str + strlen(str); // CPPUNIT_ASSERT(object_read_bencode_skip_c(str, last) == last); // return torrent::raw_bencode(str, std::distance(str, last)); // } bool validate_bencode(const char* first, const char* last) { torrent::Object obj; return object_read_bencode_c(first, last, &obj) == last; } bool compare_bencode(const torrent::Object& obj, const char* str, uint32_t skip_mask) { char buffer[256]; std::memset(buffer, 0, 256); torrent::object_write_bencode(buffer, buffer + 256, &obj, skip_mask); return strcmp(buffer, str) == 0; } libtorrent-0.16.17/test/torrent/object_test_utils.h000066400000000000000000000014221522271512000224200ustar00rootroot00000000000000#include #include "torrent/object.h" torrent::Object create_bencode(const char* str); torrent::Object create_bencode_c(const char* str); inline torrent::Object create_bencode_raw_bencode_c(const char* str) { return torrent::raw_bencode(str, std::strlen(str)); } inline torrent::Object create_bencode_raw_string_c(const char* str) { return torrent::raw_string(str, std::strlen(str)); } inline torrent::Object create_bencode_raw_list_c(const char* str) { return torrent::raw_list(str, std::strlen(str)); } inline torrent::Object create_bencode_raw_map_c(const char* str) { return torrent::raw_map(str, std::strlen(str)); } bool validate_bencode(const char* first, const char* last); bool compare_bencode(const torrent::Object& obj, const char* str, uint32_t skip_mask = 0); libtorrent-0.16.17/test/torrent/test_tracker_controller.cc000066400000000000000000000616431522271512000240010ustar00rootroot00000000000000#include "config.h" #include #include "test/torrent/test_tracker_controller.h" #include "test/torrent/test_tracker_list.h" #include "test/helpers/test_main_thread.h" #include "torrent/utils/log.h" CPPUNIT_TEST_SUITE_REGISTRATION(TestTrackerController); void process_main_and_tracker(TestFixtureWithMainAndTrackerThread* fixture) { std::this_thread::sleep_for(100ms); fixture->m_main_thread->test_process_events_without_cached_time(); std::this_thread::sleep_for(100ms); } bool test_tracker_value_in_range(uint32_t value, std::chrono::seconds min, std::chrono::seconds max) { return test_tracker_value_in_range(value, min.count(), max.count()); } bool test_tracker_value_in_range(uint32_t value, int64_t min, int64_t max) { int64_t range_min = min < 0 ? 0 : min; if (value < range_min || value > max) { lt_log_print(torrent::LOG_TRACKER_EVENTS, "test_tracker_value_in_range: %u not in range [%" PRIu64 ", %" PRId64 "]", value, range_min, max); return false; } return true; } void test_tracker_step_time(TestFixtureWithMainAndTrackerThread* fixture, int32_t seconds) { fixture->m_main_thread->test_add_cached_time(std::chrono::seconds(seconds)); std::this_thread::sleep_for(50ms); fixture->m_main_thread->test_process_events_without_cached_time(); std::this_thread::sleep_for(50ms); } bool test_goto_next_timeout(TestFixtureWithMainAndTrackerThread* fixture, torrent::TrackerController* tracker_controller, std::chrono::seconds assumed_timeout, bool is_scrape) { return test_goto_next_timeout(fixture, tracker_controller, (uint32_t)assumed_timeout.count(), is_scrape); } bool test_goto_next_timeout(TestFixtureWithMainAndTrackerThread* fixture, torrent::TrackerController* tracker_controller, uint32_t assumed_timeout, bool is_scrape) { uint32_t next_timeout = tracker_controller->is_timeout_queued() ? tracker_controller->seconds_to_next_timeout() : ~uint32_t(); uint32_t next_scrape = tracker_controller->is_scrape_queued() ? tracker_controller->seconds_to_next_scrape() : ~uint32_t(); bool result = true; std::string message; if (next_timeout == next_scrape && next_timeout == ~uint32_t()) { message = "(no timeout or scrape queued) "; result = false; } if (next_timeout < next_scrape) { if (is_scrape) { message += "ntest_process_events_without_cached_time(); CPPUNIT_ASSERT(timeout_counter == 1); TEST_SINGLE_END(0, 0); } void TestTrackerController::test_single_success() { TEST_SINGLE_BEGIN(); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(success_counter == 1 && failure_counter == 0); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_COMPLETED); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); TEST_SINGLE_END(1, 1); } #define TEST_SINGLE_FAILURE_TIMEOUT(test_interval) \ lt_log_print(torrent::LOG_TRACKER_EVENTS, "test_single_failure: main_thread->cached_time:%" PRIu64, torrent::this_thread::cached_time().count()); \ \ std::this_thread::sleep_for(100ms); \ m_main_thread->test_process_events_without_cached_time(); \ \ CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); \ CPPUNIT_ASSERT(tracker_controller.seconds_to_next_timeout() == test_interval); \ \ m_main_thread->test_add_cached_time(std::chrono::seconds(test_interval + 1)); void TestTrackerController::test_single_failure() { m_main_thread->test_set_cached_time(0s); TEST_SINGLE_BEGIN(); // TOOD: Need separate test handler for ThreadTracker? // TODO: Add a 'wait-for-one-event-loop' function to threads? // TODO: Add test-specific ThreadTracker that allows us to trigger process events and cached_time. auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); TEST_SINGLE_FAILURE_TIMEOUT(5); TEST_SINGLE_FAILURE_TIMEOUT(10); TEST_SINGLE_FAILURE_TIMEOUT(20); TEST_SINGLE_FAILURE_TIMEOUT(40); TEST_SINGLE_FAILURE_TIMEOUT(80); TEST_SINGLE_FAILURE_TIMEOUT(160); TEST_SINGLE_FAILURE_TIMEOUT(300); TEST_SINGLE_FAILURE_TIMEOUT(300); // TODO: Test with cached_time not rounded to second. TEST_SINGLE_END(0, 4); } void TestTrackerController::test_single_disable() { TEST_SINGLE_BEGIN(); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); TEST_SINGLE_END(0, 0); } void TestTrackerController::test_send_start() { TEST_SINGLE_BEGIN(); TEST_SEND_SINGLE_BEGIN(start); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); std::this_thread::sleep_for(100ms); m_main_thread->test_process_events_without_cached_time(); CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); CPPUNIT_ASSERT(tracker_0_0_worker->requesting_state() == torrent::tracker::TrackerState::EVENT_STARTED); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(!(tracker_controller.flags() & torrent::TrackerController::mask_send)); CPPUNIT_ASSERT(tracker_controller.seconds_to_next_timeout() != 0); tracker_controller.send_start_event(); tracker_controller.disable(); // This test doesn't work as the tracker remains busy. // CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); TEST_SEND_SINGLE_END(1, 0); } void TestTrackerController::test_send_stop_normal() { TEST_SINGLE_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); tracker_controller.send_stop_event(); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == torrent::TrackerController::flag_send_stop); CPPUNIT_ASSERT(tracker_controller.seconds_to_next_timeout() == 0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == 0); tracker_controller.send_stop_event(); tracker_controller.disable(); std::this_thread::sleep_for(100ms); // This checks that trackers finish requests even after controller is disabled. CPPUNIT_ASSERT(tracker_0_0.is_requesting()); tracker_0_0_worker->trigger_success(); TEST_SEND_SINGLE_END(3, 0); } // send_stop during request and right after start, send stop failed. void TestTrackerController::test_send_completed_normal() { TEST_SINGLE_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); tracker_controller.send_completed_event(); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == torrent::TrackerController::flag_send_completed); CPPUNIT_ASSERT(tracker_controller.seconds_to_next_timeout() == 0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == 0); tracker_controller.send_completed_event(); tracker_controller.disable(); std::this_thread::sleep_for(100ms); // This checks that trackers finish requests even after controller is disabled. CPPUNIT_ASSERT(tracker_0_0.is_requesting()); tracker_0_0_worker->trigger_success(); TEST_SEND_SINGLE_END(3, 0); } void TestTrackerController::test_send_update_normal() { TEST_SINGLE_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == torrent::TrackerController::flag_send_update); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_0_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); TEST_SEND_SINGLE_END(1, 0); } void TestTrackerController::test_send_update_failure() { m_main_thread->test_set_cached_time(0s); TEST_SINGLE_BEGIN(); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); tracker_controller.send_update_event(); TEST_SINGLE_FAILURE_TIMEOUT(5); TEST_SINGLE_FAILURE_TIMEOUT(10); TEST_SINGLE_END(0, 2); } void TestTrackerController::test_send_task_timeout() { TEST_SINGLE_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); std::this_thread::sleep_for(100ms); m_main_thread->test_process_events_without_cached_time(); TEST_SEND_SINGLE_END(0, 0); } void TestTrackerController::test_send_close_on_enable() { // TRACKER_CONTROLLER_SETUP(); // TRACKER_INSERT(0, tracker_0); // TRACKER_INSERT(0, tracker_1); // TRACKER_INSERT(0, tracker_2); // TRACKER_INSERT(0, tracker_3); // tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); // tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_STARTED); // tracker_list.send_event(tracker_list.at(2), torrent::tracker::TrackerState::EVENT_STOPPED); // tracker_list.send_event(tracker_list.at(3), torrent::tracker::TrackerState::EVENT_COMPLETED); // // TODO: This closes all trackers, except tracker_3. // tracker_controller.enable(); // // TODO: Replace sleep with process_callbacks for tracker thread. // std::this_thread::sleep_for(100ms); // m_main_thread->test_process_events_without_cached_time(); // std::this_thread::sleep_for(100ms); // CPPUNIT_ASSERT(!tracker_0.is_requesting()); // CPPUNIT_ASSERT(!tracker_1.is_requesting()); // CPPUNIT_ASSERT(!tracker_2.is_requesting()); // CPPUNIT_ASSERT(tracker_3.is_requesting()); } void TestTrackerController::test_multiple_success() { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_0_0.state().normal_interval())); TEST_MULTI3_IS_BUSY("10000", "10000"); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_0_0.state().normal_interval())); TEST_MULTI3_IS_BUSY("10000", "10000"); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(!tracker_list.has_active()); CPPUNIT_ASSERT(tracker_0_0.state().success_counter() == 3); TEST_MULTIPLE_END(3, 0); } void TestTrackerController::test_multiple_failure() { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); auto tracker_3_0_worker = TrackerTest::test_worker(tracker_3_0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); process_main_and_tracker(this); // CPPUNIT_ASSERT(!tracker_controller.is_failure_mode()); CPPUNIT_ASSERT(tracker_controller.is_failure_mode()); TEST_MULTI3_IS_BUSY("01000", "01000"); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_failure()); process_main_and_tracker(this); TEST_MULTI3_IS_BUSY("00100", "00100"); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_success()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_1_0.state().normal_interval())); process_main_and_tracker(this); // TEST_MULTI3_IS_BUSY("10000", "10000"); // CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); // TODO: This keeps requesting all the tracker groups after failure, which might be what we want // but not sure. TEST_MULTI3_IS_BUSY("00010", "00010"); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_failure()); process_main_and_tracker(this); // TEST_MULTI3_IS_BUSY("01000", "01000"); // CPPUNIT_ASSERT(tracker_0_1_worker->trigger_failure()); TEST_MULTI3_IS_BUSY("00001", "00001"); CPPUNIT_ASSERT(tracker_3_0_worker->trigger_failure()); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_controller.is_failure_mode()); // TEST_MULTI3_IS_BUSY("00100", "00100"); // CPPUNIT_ASSERT(tracker_1_0_worker->trigger_failure()); // TEST_MULTI3_IS_BUSY("00000", "00000"); TEST_MULTI3_IS_BUSY("10000", "10000"); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_controller.is_failure_mode()); process_main_and_tracker(this); // TEST_MULTI3_IS_BUSY("00010", "00010"); TEST_MULTI3_IS_BUSY("01000", "01000"); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_failure()); process_main_and_tracker(this); // TEST_MULTI3_IS_BUSY("00001", "00001"); TEST_MULTI3_IS_BUSY("00100", "00100"); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_failure()); // Properly tests that we're going into timeouts... Disable remaining trackers. // for (int i = 0; i < 5; i++) // std::cout << std::endl << i << ": " // << tracker_list[i]->activity_time_last() << ' ' // << tracker_list[i]->success_time_last() << ' ' // << tracker_list[i]->failed_time_last() << std::endl; // Try inserting some delays in order to test the timers. process_main_and_tracker(this); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 5)); TEST_MULTI3_IS_BUSY("00100", "00100"); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_success()); CPPUNIT_ASSERT(!tracker_controller.is_failure_mode()); // CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_list[0].state().normal_interval())); // TEST_MULTI3_IS_BUSY("01000", "10000"); // CPPUNIT_ASSERT(tracker_0_1_worker->trigger_success()); process_main_and_tracker(this); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 0); TEST_MULTIPLE_END(2, 7); } void TestTrackerController::test_multiple_cycle() { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_success()); CPPUNIT_ASSERT(tracker_list.front() == tracker_0_1); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_0_1.state().normal_interval())); TEST_MULTI3_IS_BUSY("01000", "10000"); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_success()); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 0); TEST_MULTIPLE_END(2, 1); } void TestTrackerController::test_multiple_cycle_second_group() { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_failure()); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_list.at(0) == tracker_0_0); CPPUNIT_ASSERT(tracker_list.at(1) == tracker_0_1); CPPUNIT_ASSERT(tracker_list.at(2) == tracker_1_0); CPPUNIT_ASSERT(tracker_list.at(3) == tracker_2_0); CPPUNIT_ASSERT(tracker_list.at(4) == tracker_3_0); TEST_MULTIPLE_END(1, 2); } void TestTrackerController::test_multiple_send_stop() { TEST_MULTI3_BEGIN(); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); auto tracker_3_0_worker = TrackerTest::test_worker(tracker_3_0); tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_NONE); tracker_list.send_event(tracker_list.at(3), torrent::tracker::TrackerState::EVENT_NONE); tracker_list.send_event(tracker_list.at(4), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_success()); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_3_0_worker->trigger_success()); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 0); tracker_controller.send_stop_event(); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 3); TEST_MULTI3_IS_BUSY("01011", "10011"); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_success()); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) != torrent::TrackerController::flag_send_stop); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_3_0_worker->trigger_success()); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 0); tracker_controller.send_stop_event(); tracker_controller.disable(); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == torrent::TrackerController::flag_send_stop); process_main_and_tracker(this); TEST_MULTI3_IS_BUSY("01011", "10011"); tracker_controller.enable(torrent::TrackerController::enable_dont_reset_stats); // process_main_and_tracker(this); // TEST_MULTI3_IS_BUSY("00000", "00000"); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == 0); tracker_controller.send_stop_event(); process_main_and_tracker(this); TEST_MULTI3_IS_BUSY("01011", "10011"); // Clear out any pending tracker requests. tracker_0_1_worker->trigger_success(); tracker_2_0_worker->trigger_success(); tracker_3_0_worker->trigger_success(); process_main_and_tracker(this); TEST_MULTI3_IS_BUSY("00000", "00000"); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == 0); tracker_controller.disable(); // tracker_controller.enable(); tracker_controller.send_stop_event(); process_main_and_tracker(this); TEST_MULTI3_IS_BUSY("00000", "00000"); // Test also that after closing the tracker controller, and // reopening it a 'send stop' event causes no tracker to be busy. // TEST_MULTIPLE_END(6, 0); TEST_MULTIPLE_END(9, 0); } void TestTrackerController::test_multiple_send_update() { TEST_MULTI3_BEGIN(); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); tracker_controller.send_update_event(); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_MULTI3_IS_BUSY("10000", "10000"); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); tracker_0_0_worker->set_failed(torrent::this_thread::cached_seconds().count()); tracker_controller.send_update_event(); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_MULTI3_IS_BUSY("01000", "01000"); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_failure()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_MULTI3_IS_BUSY("00100", "00100"); TEST_MULTIPLE_END(2, 0); } // Test timeout called, no usable trackers at all which leads to // disabling the timeout. // // The enable/disable tracker functions will poke the tracker // controller, ensuring the tast timeout gets re-started. void TestTrackerController::test_timeout_lacking_usable() { TEST_MULTI3_BEGIN(); std::for_each(tracker_list.begin(), tracker_list.end(), std::mem_fn(&torrent::tracker::Tracker::disable)); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_MULTI3_IS_BUSY("00000", "00000"); CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); tracker_1_0.enable(); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_controller.seconds_to_next_timeout() == 0); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_MULTI3_IS_BUSY("00100", "00100"); CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); tracker_3_0.enable(); CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(enabled_counter == 5 + 2 && disabled_counter == 5); TEST_MULTIPLE_END(0, 0); } void TestTrackerController::test_disable_tracker() { TEST_SINGLE_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); tracker_0_0.disable(); tracker_0_0_worker->trigger_success(); process_main_and_tracker(this); CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); TEST_SINGLE_END(1, 0); } void TestTrackerController::test_new_peers() { TRACKER_CONTROLLER_SETUP(); TRACKER_INSERT(0, tracker_0_0); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success(10)); CPPUNIT_ASSERT(tracker_0_0.state().latest_new_peers() == 10); tracker_controller.enable(); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success(20)); CPPUNIT_ASSERT(tracker_0_0.state().latest_new_peers() == 20); } // Add new function for finding the first tracker that will time out, // e.g. both with failure mode and normal rerequesting. // Make sure that adding a new tracker to a tracker list with no // usable tracker restarts the tracker requests. // Test timeout called, usable trackers exist but could not be called // at this moment, thus leading to no active trackers. (Rather, clean // up the 'is_usable()' functions, and make it a new function that is // independent of enabled, or is itself manipulating/dependent on // enabled) // Add checks to ensure we don't prematurely do timeout on a tracker // after disabling the lead one. // Make sure that we always remain with timeout, even if we send a // request that somehow doesn't actually activate any // trackers. e.g. check all send_tracker_itr uses. // Add test for promiscious mode while with a single tracker? // Test send_* with controller not enabled. // Test cleanup after disable. // Test cleanup of timers at disable (and empty timers at enable). // Test trying to send_start twice, etc. // Test send_start promiscious... // - Make sure we check that no timer is inserted while still having active trackers. // - Calculate the next timeout according to a list of in-use trackers, with the first timeout as the interval. // Test clearing of recv/failed counter on trackers. libtorrent-0.16.17/test/torrent/test_tracker_controller.h000066400000000000000000000210601522271512000236300ustar00rootroot00000000000000#include "test/helpers/test_main_thread.h" #include "torrent/download_info.h" #include "tracker/tracker_controller.h" class TestTrackerController : public TestFixtureWithMainAndTrackerThread { CPPUNIT_TEST_SUITE(TestTrackerController); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_enable); CPPUNIT_TEST(test_requesting); CPPUNIT_TEST(test_timeout); CPPUNIT_TEST(test_single_success); CPPUNIT_TEST(test_single_failure); CPPUNIT_TEST(test_single_disable); CPPUNIT_TEST(test_send_start); CPPUNIT_TEST(test_send_stop_normal); CPPUNIT_TEST(test_send_completed_normal); CPPUNIT_TEST(test_send_update_normal); CPPUNIT_TEST(test_send_update_failure); CPPUNIT_TEST(test_send_task_timeout); CPPUNIT_TEST(test_send_close_on_enable); CPPUNIT_TEST(test_multiple_success); CPPUNIT_TEST(test_multiple_failure); CPPUNIT_TEST(test_multiple_cycle); CPPUNIT_TEST(test_multiple_cycle_second_group); CPPUNIT_TEST(test_multiple_send_stop); CPPUNIT_TEST(test_timeout_lacking_usable); CPPUNIT_TEST(test_disable_tracker); CPPUNIT_TEST(test_new_peers); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_enable(); void test_disable(); void test_requesting(); void test_timeout(); void test_single_success(); void test_single_failure(); void test_single_disable(); void test_send_start(); void test_send_stop_normal(); void test_send_completed_normal(); void test_send_update_normal(); void test_send_update_failure(); void test_send_task_timeout(); void test_send_close_on_enable(); void test_multiple_success(); void test_multiple_failure(); void test_multiple_cycle(); void test_multiple_cycle_second_group(); void test_multiple_send_stop(); void test_multiple_send_update(); void test_timeout_lacking_usable(); void test_disable_tracker(); void test_new_peers(); }; #define TRACKER_CONTROLLER_SETUP() \ torrent::DownloadInfo download_info; \ download_info.slot_left() = []() { return 0; }; \ download_info.slot_completed() = []() { return 0; }; \ \ torrent::TrackerList tracker_list; \ TestTrackerListWrapper(&tracker_list).set_info(&download_info); \ \ torrent::TrackerController tracker_controller(&tracker_list); \ \ int success_counter = 0; \ int failure_counter = 0; \ int timeout_counter = 0; \ int enabled_counter = 0; \ int disabled_counter = 0; \ \ tracker_controller.slot_success() = std::bind(&increment_value_uint, &success_counter); \ tracker_controller.slot_failure() = std::bind(&increment_value_void, &failure_counter); \ tracker_controller.slot_timeout() = std::bind(&increment_value_void, &timeout_counter); \ tracker_controller.slot_tracker_enabled() = std::bind(&increment_value_void, &enabled_counter); \ tracker_controller.slot_tracker_disabled() = std::bind(&increment_value_void, &disabled_counter); \ \ tracker_list.slot_success() = std::bind(&torrent::TrackerController::receive_success, &tracker_controller, std::placeholders::_1, std::placeholders::_2); \ tracker_list.slot_failure() = std::bind(&torrent::TrackerController::receive_failure, &tracker_controller, std::placeholders::_1, std::placeholders::_2); \ tracker_list.slot_tracker_enabled() = std::bind(&torrent::TrackerController::receive_tracker_enabled, &tracker_controller, std::placeholders::_1); \ tracker_list.slot_tracker_disabled() = std::bind(&torrent::TrackerController::receive_tracker_disabled, &tracker_controller, std::placeholders::_1); #define TEST_SINGLE_BEGIN() \ TRACKER_CONTROLLER_SETUP(); \ TRACKER_INSERT(0, tracker_0_0); \ \ tracker_controller.enable(); \ CPPUNIT_ASSERT(!(tracker_controller.flags() & torrent::TrackerController::mask_send)); \ #define TEST_SINGLE_END(succeeded, failed) \ tracker_controller.disable(); \ /*CPPUNIT_ASSERT(!tracker_list.has_active());*/ \ CPPUNIT_ASSERT(success_counter == succeeded && \ failure_counter == failure_counter); #define TEST_SEND_SINGLE_BEGIN(event_name) \ tracker_controller.send_##event_name##_event(); \ CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::mask_send) == \ torrent::TrackerController::flag_send_##event_name); \ \ CPPUNIT_ASSERT(tracker_controller.is_active()); \ CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 1); #define TEST_SEND_SINGLE_END(succeeded, failed) \ TEST_SINGLE_END(succeeded, failed) \ CPPUNIT_ASSERT(tracker_controller.seconds_to_next_timeout() == 0); \ //CPPUNIT_ASSERT(tracker_controller.seconds_to_promicious_mode() != 0); #define TEST_MULTI3_BEGIN() \ TRACKER_CONTROLLER_SETUP(); \ TRACKER_INSERT(0, tracker_0_0); \ TRACKER_INSERT(0, tracker_0_1); \ TRACKER_INSERT(1, tracker_1_0); \ TRACKER_INSERT(2, tracker_2_0); \ TRACKER_INSERT(3, tracker_3_0); \ \ tracker_controller.enable(); \ CPPUNIT_ASSERT(!(tracker_controller.flags() & torrent::TrackerController::mask_send)); \ #define TEST_GROUP_BEGIN() \ TRACKER_CONTROLLER_SETUP(); \ TRACKER_INSERT(0, tracker_0_0); \ TRACKER_INSERT(0, tracker_0_1); \ TRACKER_INSERT(0, tracker_0_2); \ TRACKER_INSERT(1, tracker_1_0); \ TRACKER_INSERT(1, tracker_1_1); \ TRACKER_INSERT(2, tracker_2_0); \ \ tracker_controller.enable(); \ CPPUNIT_ASSERT(!(tracker_controller.flags() & torrent::TrackerController::mask_send)); #define TEST_MULTIPLE_END(succeeded, failed) \ tracker_controller.disable(); \ /*CPPUNIT_ASSERT(!tracker_list.has_active());*/ \ CPPUNIT_ASSERT(success_counter == succeeded && \ failure_counter == failed); #define TEST_GOTO_NEXT_SCRAPE(assumed_scrape) \ CPPUNIT_ASSERT(tracker_controller.is_scrape_queued()); \ CPPUNIT_ASSERT(assumed_scrape == tracker_controller.seconds_to_next_scrape()); \ CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, assumed_scrape, true)); void process_main_and_tracker(TestFixtureWithMainAndTrackerThread*); bool test_tracker_value_in_range(uint32_t value, int64_t min, int64_t max); bool test_tracker_value_in_range(uint32_t value, std::chrono::seconds min, std::chrono::seconds max); void test_tracker_step_time(TestFixtureWithMainAndTrackerThread* fixture, int32_t seconds); bool test_goto_next_timeout(TestFixtureWithMainAndTrackerThread*, torrent::TrackerController*, uint32_t assumed_timeout, bool is_scrape = false); bool test_goto_next_timeout(TestFixtureWithMainAndTrackerThread*, torrent::TrackerController*, std::chrono::seconds assumed_timeout, bool is_scrape = false); libtorrent-0.16.17/test/torrent/test_tracker_controller_features.cc000066400000000000000000000335611522271512000256750ustar00rootroot00000000000000#include "config.h" #include "test/torrent/test_tracker_controller_features.h" #include #include #include "test/helpers/test_thread.h" #include "test/torrent/test_tracker_controller.h" #include "test/torrent/test_tracker_list.h" #include "src/tracker/thread_tracker.h" CPPUNIT_TEST_SUITE_REGISTRATION(test_tracker_controller_features); void test_tracker_controller_features::test_requesting_basic() { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); auto tracker_3_0_worker = TrackerTest::test_worker(tracker_3_0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success(8, 9)); tracker_controller.start_requesting(); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_MULTI3_IS_BUSY("00111", "00111"); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_3_0_worker->trigger_success()); // TODO: Change this so that requesting state results in tracker // requests from many peers. Also, add a limit so we don't keep // requesting from spent trackers. // Next timeout should be soon... CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 30)); TEST_MULTI3_IS_BUSY("00000", "00000"); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_0_0.state().min_interval() - 30s)); TEST_MULTI3_IS_BUSY("10111", "10111"); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_3_0_worker->trigger_success()); tracker_controller.stop_requesting(); TEST_MULTIPLE_END(8, 0); } void test_tracker_controller_features::test_requesting_timeout() { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(update); tracker_controller.start_requesting(); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_MULTI3_IS_BUSY("10111", "10111"); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); auto tracker_3_0_worker = TrackerTest::test_worker(tracker_3_0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); auto next_timeout_1 = tracker_controller.seconds_to_next_timeout(); CPPUNIT_ASSERT(next_timeout_1 == 5 || next_timeout_1 == 6); // CPPUNIT_ASSERT(tracker_0_1_worker->trigger_failure()); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_failure()); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_failure()); CPPUNIT_ASSERT(tracker_3_0_worker->trigger_failure()); // CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); process_main_and_tracker(this); TEST_MULTI3_IS_BUSY("01000", "01000"); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 5)); TEST_MULTI3_IS_BUSY("01111", "01111"); CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_success()); auto next_timeout_2 = tracker_controller.seconds_to_next_timeout(); CPPUNIT_ASSERT(next_timeout_2 == 30 || next_timeout_2 == 31); TEST_MULTIPLE_END(1, 4); } void test_tracker_controller_features::test_promiscious_timeout() { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(start); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 3)); TEST_MULTI3_IS_BUSY("10111", "10111"); CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); auto tracker_3_0_worker = TrackerTest::test_worker(tracker_3_0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(!(tracker_controller.flags() & torrent::TrackerController::flag_promiscuous_mode)); // CPPUNIT_ASSERT(tracker_0_1_worker->trigger_success()); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_success()); CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_3_0_worker->trigger_success()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_0_0.state().normal_interval())); TEST_MULTIPLE_END(4, 0); } // Fix failure event handler so that it properly handles multi-request // situations. This includes fixing old tests. void test_tracker_controller_features::test_promiscious_failed() { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(start); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); [[maybe_unused]] auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); [[maybe_unused]] auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); auto tracker_3_0_worker = TrackerTest::test_worker(tracker_3_0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::flag_promiscuous_mode)); process_main_and_tracker(this); TEST_MULTI3_IS_BUSY("01111", "01111"); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(tracker_3_0_worker->trigger_failure()); test_tracker_step_time(this, 2); TEST_MULTI3_IS_BUSY("01110", "01110"); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_failure()); test_tracker_step_time(this, 0); TEST_MULTI3_IS_BUSY("01100", "01100"); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 3 - 1)); TEST_MULTIPLE_END(0, 3); // TODO: This is now failing after cached_time deprecation, and we already need to rewrite more // tracker code so skip for now. // TEST_MULTI3_IS_BUSY("01101", "01101"); // CPPUNIT_ASSERT(tracker_0_1_worker->trigger_failure()); // CPPUNIT_ASSERT(tracker_1_0_worker->trigger_failure()); // CPPUNIT_ASSERT(tracker_3_0_worker->trigger_failure()); // CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); // CPPUNIT_ASSERT(!tracker_list.has_active()); // CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); // TEST_MULTIPLE_END(0, 7); } void test_tracker_controller_features::test_scrape_basic() { // TEST_GROUP_BEGIN(); // tracker_controller.disable(); // auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); // auto tracker_0_2_worker = TrackerTest::test_worker(tracker_0_2); // auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); // CPPUNIT_ASSERT(!tracker_controller.is_scrape_queued()); // tracker_0_1_worker->set_scrapable(); // tracker_0_2_worker->set_scrapable(); // tracker_2_0_worker->set_scrapable(); // tracker_controller.scrape_request(0); // TEST_GROUP_IS_BUSY("000000", "000000"); // CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); // CPPUNIT_ASSERT(tracker_controller.is_scrape_queued()); // CPPUNIT_ASSERT(tracker_0_1.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); // CPPUNIT_ASSERT(tracker_0_2.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); // CPPUNIT_ASSERT(tracker_2_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); // TEST_GOTO_NEXT_SCRAPE(0); // TODO: This fails: // TEST_GROUP_IS_BUSY("010001", "010001"); // CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); // CPPUNIT_ASSERT(!tracker_controller.is_scrape_queued()); // CPPUNIT_ASSERT(tracker_0_1.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); // CPPUNIT_ASSERT(tracker_0_2.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); // CPPUNIT_ASSERT(tracker_2_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); // CPPUNIT_ASSERT(tracker_0_1_worker->trigger_scrape()); // CPPUNIT_ASSERT(tracker_2_0_worker->trigger_scrape()); // TEST_GROUP_IS_BUSY("000000", "000000"); // CPPUNIT_ASSERT(!tracker_controller.is_timeout_queued()); // CPPUNIT_ASSERT(!tracker_controller.is_scrape_queued()); // CPPUNIT_ASSERT(tracker_0_1.state().scrape_time_last() != 0); // CPPUNIT_ASSERT(tracker_0_2.state().scrape_time_last() == 0); // CPPUNIT_ASSERT(tracker_2_0.state().scrape_time_last() != 0); // TEST_MULTIPLE_END(0, 0); } void test_tracker_controller_features::test_scrape_priority() { TEST_SINGLE_BEGIN(); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); tracker_0_0_worker->trigger_success(); tracker_0_0_worker->set_scrapable(); tracker_controller.scrape_request(0); TEST_GOTO_NEXT_SCRAPE(0); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); CPPUNIT_ASSERT(tracker_0_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); // Check the other event types too? tracker_controller.send_update_event(); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); CPPUNIT_ASSERT(tracker_0_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_controller.is_timeout_queued()); CPPUNIT_ASSERT(!tracker_controller.is_scrape_queued()); tracker_0_0_worker->trigger_success(); CPPUNIT_ASSERT(tracker_controller.seconds_to_next_timeout() > 1); test_tracker_step_time(this, tracker_controller.seconds_to_next_timeout() - 1); tracker_controller.scrape_request(0); TEST_GOTO_NEXT_SCRAPE(0); TEST_SINGLE_END(2, 0); // test_tracker_step_time(this, 0); // CPPUNIT_ASSERT(tracker_0_0.is_requesting()); // CPPUNIT_ASSERT(tracker_0_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); // CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 1)); // CPPUNIT_ASSERT(tracker_0_0.is_requesting()); // CPPUNIT_ASSERT(tracker_0_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); // TEST_SINGLE_END(2, 0); } void test_tracker_controller_features::test_groups_requesting() { TEST_GROUP_BEGIN(); TEST_SEND_SINGLE_BEGIN(start); // CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success(10, 20)); tracker_controller.start_requesting(); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_GROUP_IS_BUSY("100101", "100101"); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_success()); // TODO: Change this so that requesting state results in tracker // requests from many peers. Also, add a limit so we don't keep // requesting from spent trackers. // Next timeout should be soon... CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 30)); TEST_GROUP_IS_BUSY("000000", "000000"); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_0_0.state().min_interval() - 30s)); TEST_GROUP_IS_BUSY("100101", "100101"); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_success()); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_success()); // Once we've requested twice, it should stop requesting from that tier. CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 30)); TEST_GROUP_IS_BUSY("000000", "000000"); tracker_controller.stop_requesting(); TEST_MULTIPLE_END(6, 0); } void test_tracker_controller_features::test_groups_scrape() { TEST_GROUP_BEGIN(); tracker_controller.disable(); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); auto tracker_0_2_worker = TrackerTest::test_worker(tracker_0_2); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); auto tracker_1_1_worker = TrackerTest::test_worker(tracker_1_1); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); tracker_0_0_worker->set_scrapable(); tracker_0_1_worker->set_scrapable(); tracker_0_2_worker->set_scrapable(); tracker_1_0_worker->set_scrapable(); tracker_1_1_worker->set_scrapable(); tracker_2_0_worker->set_scrapable(); CPPUNIT_ASSERT(!tracker_controller.is_scrape_queued()); tracker_controller.scrape_request(0); TEST_GROUP_IS_BUSY("000000", "000000"); TEST_GOTO_NEXT_SCRAPE(0); CPPUNIT_ASSERT(tracker_0_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(tracker_0_1.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_2.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_1_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(tracker_1_1.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_2_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); TEST_GROUP_IS_BUSY("100101", "100101"); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_scrape()); CPPUNIT_ASSERT(tracker_1_0_worker->trigger_scrape()); CPPUNIT_ASSERT(tracker_2_0_worker->trigger_scrape()); // Test with a non-can_scrape !busy tracker? // TEST_GROUP_IS_BUSY("100101", "100101"); // CPPUNIT_ASSERT(tracker_0_0_worker->trigger_scrape()); // CPPUNIT_ASSERT(tracker_0_1_worker->trigger_scrape()); // CPPUNIT_ASSERT(tracker_0_2_worker->trigger_scrape()); // CPPUNIT_ASSERT(tracker_1_0_worker->trigger_scrape()); // CPPUNIT_ASSERT(tracker_1_1_worker->trigger_scrape()); // CPPUNIT_ASSERT(tracker_2_0_worker->trigger_scrape()); TEST_GROUP_IS_BUSY("000000", "000000"); TEST_MULTIPLE_END(0, 0); } libtorrent-0.16.17/test/torrent/test_tracker_controller_features.h000066400000000000000000000014401522271512000255260ustar00rootroot00000000000000#include "test/helpers/test_main_thread.h" class test_tracker_controller_features : public TestFixtureWithMainAndTrackerThread { CPPUNIT_TEST_SUITE(test_tracker_controller_features); CPPUNIT_TEST(test_requesting_basic); CPPUNIT_TEST(test_requesting_timeout); CPPUNIT_TEST(test_promiscious_timeout); CPPUNIT_TEST(test_promiscious_failed); CPPUNIT_TEST(test_scrape_basic); CPPUNIT_TEST(test_scrape_priority); CPPUNIT_TEST(test_groups_requesting); CPPUNIT_TEST(test_groups_scrape); CPPUNIT_TEST_SUITE_END(); public: void test_requesting_basic(); void test_requesting_timeout(); void test_promiscious_timeout(); void test_promiscious_failed(); void test_scrape_basic(); void test_scrape_priority(); void test_groups_requesting(); void test_groups_scrape(); }; libtorrent-0.16.17/test/torrent/test_tracker_controller_requesting.cc000066400000000000000000000155611522271512000262450ustar00rootroot00000000000000#include "config.h" #include #include #include "test/helpers/test_thread.h" #include "test/torrent/test_tracker_controller_requesting.h" #include "test/torrent/test_tracker_list.h" #include "src/tracker/thread_tracker.h" CPPUNIT_TEST_SUITE_REGISTRATION(TestTrackerControllerRequesting); void TestTrackerControllerRequesting::do_test_hammering_basic(bool success1, bool success2, bool success3, uint32_t min_interval) { TEST_SINGLE_BEGIN(); TEST_SEND_SINGLE_BEGIN(start); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); if (min_interval != 0) tracker_0_0_worker->set_new_min_interval(min_interval); else tracker_0_0_worker->set_new_min_interval(600); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); CPPUNIT_ASSERT(success1 ? tracker_0_0_worker->trigger_success() : tracker_0_0_worker->trigger_failure()); CPPUNIT_ASSERT(test_tracker_value_in_range(tracker_controller.seconds_to_next_timeout(), tracker_0_0.state().normal_interval() - 2s, tracker_0_0.state().normal_interval() + 2s)); CPPUNIT_ASSERT(!(tracker_controller.flags() & torrent::TrackerController::flag_promiscuous_mode)); tracker_controller.start_requesting(); CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); CPPUNIT_ASSERT((tracker_controller.flags() & torrent::TrackerController::flag_requesting)); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_0_0.state().min_interval())); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); if (success2) { CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 30)); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, tracker_0_0.state().min_interval() - 30s)); } else { CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 5)); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); } tracker_controller.stop_requesting(); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); CPPUNIT_ASSERT(success3 ? tracker_0_0_worker->trigger_success() : tracker_0_0_worker->trigger_failure()); TEST_SINGLE_END(success1 + success2 + success3, !success1 + !success2 + !success3); } void TestTrackerControllerRequesting::test_hammering_basic_success() { do_test_hammering_basic(true, true, true); } void TestTrackerControllerRequesting::test_hammering_basic_success_long_timeout() { do_test_hammering_basic(true, true, true, 1000); } void TestTrackerControllerRequesting::test_hammering_basic_success_short_timeout() { do_test_hammering_basic(true, true, true, 300); } void TestTrackerControllerRequesting::test_hammering_basic_failure() { do_test_hammering_basic(true, false, false); } void TestTrackerControllerRequesting::test_hammering_basic_failure_long_timeout() { do_test_hammering_basic(true, false, false, 1000); } void TestTrackerControllerRequesting::test_hammering_basic_failure_short_timeout() { do_test_hammering_basic(true, false, false, 300); } // Differentiate between failure connection / http error and tracker returned error. void TestTrackerControllerRequesting::do_test_hammering_multi3(bool success1, bool success2, bool success3, uint32_t min_interval) { TEST_MULTI3_BEGIN(); TEST_SEND_SINGLE_BEGIN(start); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); if (min_interval != 0) tracker_0_0_worker->set_new_min_interval(min_interval); else tracker_0_0_worker->set_new_min_interval(600); TEST_MULTI3_IS_BUSY("10000", "10000"); CPPUNIT_ASSERT(success1 ? tracker_0_0_worker->trigger_success() : tracker_0_0_worker->trigger_failure()); CPPUNIT_ASSERT(test_tracker_value_in_range(tracker_controller.seconds_to_next_timeout(), tracker_0_0.state().normal_interval() - 2s, tracker_0_0.state().normal_interval() + 2s)); CPPUNIT_ASSERT(!(tracker_controller.flags() & torrent::TrackerController::flag_promiscuous_mode)); tracker_controller.start_requesting(); TEST_MULTI3_IS_BUSY("00000", "00000"); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 0)); TEST_MULTI3_IS_BUSY("00111", "00111"); if (success2) CPPUNIT_ASSERT(tracker_2_0_worker->trigger_success()); else CPPUNIT_ASSERT(tracker_2_0_worker->trigger_failure()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 30)); TEST_MULTI3_IS_BUSY("00101", "00101"); TrackerTest* next_tracker = tracker_0_0_worker; auto next_timeout = next_tracker->test_state().min_interval(); const char* next_is_busy = "10111"; if (tracker_0_0.state().min_interval() < tracker_2_0.state().min_interval()) { CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, next_tracker->test_state().min_interval() - 30s)); TEST_MULTI3_IS_BUSY("10101", "10101"); } else if (tracker_0_0.state().min_interval() > tracker_2_0.state().min_interval()) { next_tracker = tracker_2_0_worker; next_timeout = tracker_0_0.state().min_interval() - tracker_2_0.state().min_interval(); next_is_busy = "10101"; CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, next_tracker->test_state().min_interval() - 30s)); TEST_MULTI3_IS_BUSY("00111", "00111"); } else { CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, next_tracker->test_state().min_interval() - 30s)); TEST_MULTI3_IS_BUSY("10111", "10111"); } if (success2) { CPPUNIT_ASSERT(next_tracker->trigger_success()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 30)); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, next_timeout - 30s)); TEST_MULTI3_IS_BUSY(next_is_busy, next_is_busy); } else { CPPUNIT_ASSERT(next_tracker->trigger_failure()); CPPUNIT_ASSERT(test_goto_next_timeout(this, &tracker_controller, 5)); TEST_MULTI3_IS_BUSY("10000", "10000"); } tracker_controller.stop_requesting(); TEST_MULTI3_IS_BUSY(next_is_busy, next_is_busy); CPPUNIT_ASSERT(success3 ? tracker_0_0_worker->trigger_success() : tracker_0_0_worker->trigger_failure()); TEST_MULTIPLE_END(success1 + 2*success2 + success3, !success1 + 2*!success2 + !success3); } void TestTrackerControllerRequesting::test_hammering_multi_success() { do_test_hammering_multi3(true, true, true); } void TestTrackerControllerRequesting::test_hammering_multi_success_long_timeout() { do_test_hammering_multi3(true, true, true, 1000); } void TestTrackerControllerRequesting::test_hammering_multi_success_short_timeout() { do_test_hammering_multi3(true, true, true, 300); } void TestTrackerControllerRequesting::test_hammering_multi_failure() { } libtorrent-0.16.17/test/torrent/test_tracker_controller_requesting.h000066400000000000000000000033031522271512000260760ustar00rootroot00000000000000#include "test/helpers/test_main_thread.h" #include "test/torrent/test_tracker_controller.h" class TestTrackerControllerRequesting : public TestFixtureWithMainAndTrackerThread { CPPUNIT_TEST_SUITE(TestTrackerControllerRequesting); CPPUNIT_TEST(test_hammering_basic_success); CPPUNIT_TEST(test_hammering_basic_success_long_timeout); CPPUNIT_TEST(test_hammering_basic_success_short_timeout); CPPUNIT_TEST(test_hammering_basic_failure); CPPUNIT_TEST(test_hammering_basic_failure_long_timeout); CPPUNIT_TEST(test_hammering_basic_failure_short_timeout); CPPUNIT_TEST(test_hammering_multi_success); CPPUNIT_TEST(test_hammering_multi_success_long_timeout); CPPUNIT_TEST(test_hammering_multi_success_short_timeout); CPPUNIT_TEST(test_hammering_multi_failure); // CPPUNIT_TEST(test_hammering_multi_failure_long_timeout); // CPPUNIT_TEST(test_hammering_multi_failure_short_timeout); CPPUNIT_TEST_SUITE_END(); public: void test_hammering_basic_success(); void test_hammering_basic_success_long_timeout(); void test_hammering_basic_success_short_timeout(); void test_hammering_basic_failure(); void test_hammering_basic_failure_long_timeout(); void test_hammering_basic_failure_short_timeout(); void test_hammering_multi_success(); void test_hammering_multi_success_long_timeout(); void test_hammering_multi_success_short_timeout(); void test_hammering_multi_failure(); void test_hammering_multi_failure_long_timeout(); void test_hammering_multi_failure_short_timeout(); void do_test_hammering_basic(bool success1, bool success2, bool success3, uint32_t min_interval = 0); void do_test_hammering_multi3(bool success1, bool success2, bool success3, uint32_t min_interval = 0); }; libtorrent-0.16.17/test/torrent/test_tracker_list.cc000066400000000000000000000462451522271512000225720ustar00rootroot00000000000000#include "config.h" #include "test/torrent/test_tracker_list.h" #include "net/address_list.h" #include "torrent/utils/uri_parser.h" CPPUNIT_TEST_SUITE_REGISTRATION(TestTrackerList); void process_main_and_tracker(TestFixtureWithMainNetTrackerThread* fixture) { std::this_thread::sleep_for(100ms); fixture->m_main_thread->test_process_events_without_cached_time(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_basic() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); CPPUNIT_ASSERT(tracker_0 == tracker_list.at(0)); CPPUNIT_ASSERT(std::distance(tracker_list.begin_group(0), tracker_list.end_group(0)) == 1); auto usable_itr = std::find_if(tracker_list.begin(), tracker_list.end(), std::mem_fn(&torrent::tracker::Tracker::is_usable)); CPPUNIT_ASSERT(usable_itr != tracker_list.end()); } void TestTrackerList::test_enable() { TRACKER_LIST_SETUP(); int enabled_counter = 0; int disabled_counter = 0; tracker_list.slot_tracker_enabled() = std::bind(&increment_value_void, &enabled_counter); tracker_list.slot_tracker_disabled() = std::bind(&increment_value_void, &disabled_counter); TRACKER_INSERT(0, tracker_0); TRACKER_INSERT(1, tracker_1); CPPUNIT_ASSERT(enabled_counter == 2 && disabled_counter == 0); tracker_0.enable(); tracker_1.enable(); CPPUNIT_ASSERT(enabled_counter == 2 && disabled_counter == 0); tracker_0.disable(); tracker_1.enable(); CPPUNIT_ASSERT(enabled_counter == 2 && disabled_counter == 1); tracker_1.disable(); tracker_0.disable(); CPPUNIT_ASSERT(enabled_counter == 2 && disabled_counter == 2); tracker_0.enable(); tracker_1.enable(); tracker_0.enable(); tracker_1.enable(); CPPUNIT_ASSERT(enabled_counter == 4 && disabled_counter == 2); } void TestTrackerList::test_close() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); TRACKER_INSERT(0, tracker_1); TRACKER_INSERT(0, tracker_2); TRACKER_INSERT(0, tracker_3); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_STARTED); tracker_list.send_event(tracker_list.at(2), torrent::tracker::TrackerState::EVENT_STOPPED); tracker_list.send_event(tracker_list.at(3), torrent::tracker::TrackerState::EVENT_COMPLETED); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_0.is_requesting()); CPPUNIT_ASSERT(tracker_1.is_requesting()); CPPUNIT_ASSERT(tracker_2.is_requesting()); CPPUNIT_ASSERT(tracker_3.is_requesting()); // tracker_list.close_all_excluding((1 << torrent::tracker::TrackerState::EVENT_STARTED) | (1 << torrent::tracker::TrackerState::EVENT_STOPPED)); // CPPUNIT_ASSERT(!tracker_0.is_requesting()); // CPPUNIT_ASSERT(tracker_1.is_requesting()); // CPPUNIT_ASSERT(tracker_2.is_requesting()); // CPPUNIT_ASSERT(!tracker_3.is_requesting()); // tracker_list.close_all(); // CPPUNIT_ASSERT(!tracker_0.is_requesting()); // CPPUNIT_ASSERT(!tracker_1.is_requesting()); // CPPUNIT_ASSERT(!tracker_2.is_requesting()); // CPPUNIT_ASSERT(!tracker_3.is_requesting()); // tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); // tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_STARTED); // tracker_list.send_event(tracker_list.at(2), torrent::tracker::TrackerState::EVENT_STOPPED); // tracker_list.send_event(tracker_list.at(3), torrent::tracker::TrackerState::EVENT_COMPLETED); // tracker_list.close_all_excluding((1 << torrent::tracker::TrackerState::EVENT_NONE) | (1 << torrent::tracker::TrackerState::EVENT_COMPLETED)); // CPPUNIT_ASSERT(tracker_0.is_requesting()); // CPPUNIT_ASSERT(!tracker_1.is_requesting()); // CPPUNIT_ASSERT(!tracker_2.is_requesting()); // CPPUNIT_ASSERT(tracker_3.is_requesting()); } // Test clear. void TestTrackerList::test_tracker_flags() { TRACKER_LIST_SETUP(); tracker_list.insert(TrackerTest::new_tracker(&tracker_list, 0, "")); tracker_list.insert(TrackerTest::new_tracker(&tracker_list, 0, "", 0)); tracker_list.insert(TrackerTest::new_tracker(&tracker_list, 0, "", torrent::tracker::TrackerState::flag_enabled)); tracker_list.insert(TrackerTest::new_tracker(&tracker_list, 0, "", torrent::tracker::TrackerState::flag_extra_tracker)); tracker_list.insert(TrackerTest::new_tracker(&tracker_list, 0, "", torrent::tracker::TrackerState::flag_enabled | torrent::tracker::TrackerState::flag_extra_tracker)); CPPUNIT_ASSERT((TrackerTest::test_flags(tracker_list.at(0)) & 0xff) == torrent::tracker::TrackerState::flag_enabled); CPPUNIT_ASSERT((TrackerTest::test_flags(tracker_list.at(1)) & 0xff) == 0); CPPUNIT_ASSERT((TrackerTest::test_flags(tracker_list.at(2)) & 0xff) == torrent::tracker::TrackerState::flag_enabled); CPPUNIT_ASSERT((TrackerTest::test_flags(tracker_list.at(3)) & 0xff) == torrent::tracker::TrackerState::flag_extra_tracker); CPPUNIT_ASSERT((TrackerTest::test_flags(tracker_list.at(4)) & 0xff) == (torrent::tracker::TrackerState::flag_enabled | torrent::tracker::TrackerState::flag_extra_tracker)); tracker_list.clear(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_find_url() { TRACKER_LIST_SETUP(); tracker_list.insert(TrackerTest::new_tracker(&tracker_list, 0, "http://1")); tracker_list.insert(TrackerTest::new_tracker(&tracker_list, 0, "http://2")); tracker_list.insert(TrackerTest::new_tracker(&tracker_list, 1, "http://3")); CPPUNIT_ASSERT(tracker_list.find_url("http://") == tracker_list.end()); CPPUNIT_ASSERT(tracker_list.find_url("http://1") != tracker_list.end()); CPPUNIT_ASSERT(*tracker_list.find_url("http://1") == tracker_list.at(0)); CPPUNIT_ASSERT(tracker_list.find_url("http://2") != tracker_list.end()); CPPUNIT_ASSERT(*tracker_list.find_url("http://2") == tracker_list.at(1)); CPPUNIT_ASSERT(tracker_list.find_url("http://3") != tracker_list.end()); CPPUNIT_ASSERT(*tracker_list.find_url("http://3") == tracker_list.at(2)); tracker_list.clear(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_can_scrape() { TRACKER_LIST_SETUP(); tracker_list.insert_url(0, "http://example.com/announce"); CPPUNIT_ASSERT(tracker_list.back().is_scrapable()); CPPUNIT_ASSERT(torrent::utils::uri_generate_scrape_url(tracker_list.back().url()) == "http://example.com/scrape"); tracker_list.insert_url(0, "http://example.com/x/announce"); CPPUNIT_ASSERT(tracker_list.back().is_scrapable()); CPPUNIT_ASSERT(torrent::utils::uri_generate_scrape_url(tracker_list.back().url()) == "http://example.com/x/scrape"); tracker_list.insert_url(0, "http://example.com/announce.php"); CPPUNIT_ASSERT(tracker_list.back().is_scrapable()); CPPUNIT_ASSERT(torrent::utils::uri_generate_scrape_url(tracker_list.back().url()) == "http://example.com/scrape.php"); tracker_list.insert_url(0, "http://example.com/a"); CPPUNIT_ASSERT(!tracker_list.back().is_scrapable()); tracker_list.insert_url(0, "http://example.com/announce?x2%0644"); CPPUNIT_ASSERT(tracker_list.back().is_scrapable()); CPPUNIT_ASSERT(torrent::utils::uri_generate_scrape_url(tracker_list.back().url()) == "http://example.com/scrape?x2%0644"); tracker_list.insert_url(0, "http://example.com/announce?x=2/4"); CPPUNIT_ASSERT(!tracker_list.back().is_scrapable()); tracker_list.insert_url(0, "http://example.com/x%064announce"); CPPUNIT_ASSERT(!tracker_list.back().is_scrapable()); tracker_list.clear(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_single_success() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); auto tracker_0_worker = TrackerTest::test_worker(tracker_0); CPPUNIT_ASSERT(!tracker_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0.is_requesting_not_scrape()); CPPUNIT_ASSERT(!tracker_0_worker->is_open()); CPPUNIT_ASSERT(tracker_0_worker->requesting_state() == -1); CPPUNIT_ASSERT(tracker_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_NONE); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_STARTED); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_0.is_requesting()); CPPUNIT_ASSERT(tracker_0.is_requesting_not_scrape()); CPPUNIT_ASSERT(tracker_0_worker->is_open()); CPPUNIT_ASSERT(tracker_0_worker->requesting_state() == torrent::tracker::TrackerState::EVENT_STARTED); CPPUNIT_ASSERT(tracker_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_STARTED); CPPUNIT_ASSERT(tracker_0_worker->trigger_success()); CPPUNIT_ASSERT(!tracker_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0.is_requesting_not_scrape()); CPPUNIT_ASSERT(!tracker_0_worker->is_open()); CPPUNIT_ASSERT(tracker_0_worker->requesting_state() == -1); CPPUNIT_ASSERT(tracker_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_STARTED); CPPUNIT_ASSERT(success_counter == 1 && failure_counter == 0); CPPUNIT_ASSERT(tracker_0.state().success_counter() == 1); CPPUNIT_ASSERT(tracker_0.state().failed_counter() == 0); tracker_list.clear(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_single_failure() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); auto tracker_0_worker = TrackerTest::test_worker(tracker_0); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_worker->trigger_failure()); CPPUNIT_ASSERT(!tracker_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0_worker->is_open()); CPPUNIT_ASSERT(tracker_0_worker->requesting_state() == -1); CPPUNIT_ASSERT(success_counter == 0 && failure_counter == 1); CPPUNIT_ASSERT(tracker_0.state().success_counter() == 0); CPPUNIT_ASSERT(tracker_0.state().failed_counter() == 1); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_worker->trigger_success()); CPPUNIT_ASSERT(success_counter == 1 && failure_counter == 1); CPPUNIT_ASSERT(tracker_0.state().success_counter() == 1); CPPUNIT_ASSERT(tracker_0.state().failed_counter() == 0); tracker_list.clear(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_single_closing() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); auto tracker_0_worker = TrackerTest::test_worker(tracker_0); CPPUNIT_ASSERT(!tracker_0_worker->is_open()); tracker_0_worker->set_close_on_done(false); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_0_worker->is_open()); CPPUNIT_ASSERT(tracker_0_worker->trigger_success()); CPPUNIT_ASSERT(!tracker_0.is_requesting()); CPPUNIT_ASSERT(tracker_0_worker->is_open()); // tracker_list.close_all(); // tracker_list.clear_stats(); // CPPUNIT_ASSERT(!tracker_0_worker->is_open()); // CPPUNIT_ASSERT(tracker_0.state().success_counter() == 0); // CPPUNIT_ASSERT(tracker_0.state().failed_counter() == 0); tracker_list.clear(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_multiple_success() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0_0); TRACKER_INSERT(0, tracker_0_1); TRACKER_INSERT(1, tracker_1_0); TRACKER_INSERT(1, tracker_1_1); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); auto tracker_1_1_worker = TrackerTest::test_worker(tracker_1_1); CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0_1.is_requesting()); CPPUNIT_ASSERT(!tracker_1_0.is_requesting()); CPPUNIT_ASSERT(!tracker_1_1.is_requesting()); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_0_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0_1.is_requesting()); CPPUNIT_ASSERT(!tracker_1_0.is_requesting()); CPPUNIT_ASSERT(!tracker_1_1.is_requesting()); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success()); CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0_1.is_requesting()); CPPUNIT_ASSERT(!tracker_1_0.is_requesting()); CPPUNIT_ASSERT(!tracker_1_1.is_requesting()); tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_NONE); tracker_list.send_event(tracker_list.at(3), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); CPPUNIT_ASSERT(tracker_0_1.is_requesting()); CPPUNIT_ASSERT(!tracker_1_0.is_requesting()); CPPUNIT_ASSERT(tracker_1_1.is_requesting()); CPPUNIT_ASSERT(tracker_1_1_worker->trigger_success()); CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); CPPUNIT_ASSERT(tracker_0_1.is_requesting()); CPPUNIT_ASSERT(!tracker_1_0.is_requesting()); CPPUNIT_ASSERT(!tracker_1_1.is_requesting()); CPPUNIT_ASSERT(tracker_0_1_worker->trigger_success()); CPPUNIT_ASSERT(!tracker_0_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0_1.is_requesting()); CPPUNIT_ASSERT(!tracker_1_0.is_requesting()); CPPUNIT_ASSERT(!tracker_1_1.is_requesting()); CPPUNIT_ASSERT(success_counter == 3 && failure_counter == 0); tracker_list.clear(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_scrape_success() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); auto tracker_0_worker = TrackerTest::test_worker(tracker_0); tracker_0_worker->set_scrapable(); tracker_list.send_scrape(tracker_0); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0.is_requesting_not_scrape()); CPPUNIT_ASSERT(tracker_0_worker->is_open()); CPPUNIT_ASSERT(tracker_0_worker->requesting_state() == torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(tracker_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(tracker_0_worker->trigger_scrape()); CPPUNIT_ASSERT(!tracker_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0.is_requesting_not_scrape()); CPPUNIT_ASSERT(!tracker_0_worker->is_open()); CPPUNIT_ASSERT(tracker_0_worker->requesting_state() == -1); CPPUNIT_ASSERT(tracker_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(success_counter == 0 && failure_counter == 0); CPPUNIT_ASSERT(scrape_success_counter == 1 && scrape_failure_counter == 0); CPPUNIT_ASSERT(tracker_0.state().success_counter() == 0); CPPUNIT_ASSERT(tracker_0.state().failed_counter() == 0); CPPUNIT_ASSERT(tracker_0.state().scrape_counter() == 1); tracker_list.clear(); std::this_thread::sleep_for(100ms); } void TestTrackerList::test_scrape_failure() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); auto tracker_0_worker = TrackerTest::test_worker(tracker_0); tracker_0_worker->set_scrapable(); tracker_list.send_scrape(tracker_0); CPPUNIT_ASSERT(tracker_0_worker->trigger_failure()); CPPUNIT_ASSERT(!tracker_0.is_requesting()); CPPUNIT_ASSERT(!tracker_0_worker->is_open()); CPPUNIT_ASSERT(tracker_0_worker->requesting_state() == -1); CPPUNIT_ASSERT(tracker_0.state().latest_event() == torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(success_counter == 0 && failure_counter == 0); CPPUNIT_ASSERT(scrape_success_counter == 0 && scrape_failure_counter == 1); CPPUNIT_ASSERT(tracker_0.state().success_counter() == 0); CPPUNIT_ASSERT(tracker_0.state().failed_counter() == 0); CPPUNIT_ASSERT(tracker_0.state().scrape_counter() == 0); tracker_list.clear(); std::this_thread::sleep_for(100ms); } bool check_has_active_in_group(const torrent::TrackerList* tracker_list, const char* states, bool scrape) { int group = 0; while (*states != '\0') { bool result = scrape ? tracker_list->has_active_in_group(group++) : tracker_list->has_active_not_scrape_in_group(group++); if ((*states == '1' && !result) || (*states == '0' && result)) return false; states++; } return true; } void TestTrackerList::test_has_active() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); TRACKER_INSERT(0, tracker_1); TRACKER_INSERT(1, tracker_2); TRACKER_INSERT(3, tracker_3); TRACKER_INSERT(4, tracker_4); // TODO: Test scrape... auto tracker_0_worker = TrackerTest::test_worker(tracker_0); auto tracker_1_worker = TrackerTest::test_worker(tracker_1); auto tracker_2_worker = TrackerTest::test_worker(tracker_2); auto tracker_3_worker = TrackerTest::test_worker(tracker_3); TEST_TRACKERS_IS_BUSY_5("00000", "00000"); CPPUNIT_ASSERT(!tracker_list.has_active()); CPPUNIT_ASSERT(!tracker_list.has_active_not_scrape()); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "000000", false)); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "000000", true)); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); TEST_TRACKERS_IS_BUSY_5("10000", "10000"); CPPUNIT_ASSERT( tracker_list.has_active()); CPPUNIT_ASSERT( tracker_list.has_active_not_scrape()); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "100000", false)); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "100000", true)); CPPUNIT_ASSERT(tracker_0_worker->trigger_success()); TEST_TRACKERS_IS_BUSY_5("00000", "00000"); CPPUNIT_ASSERT(!tracker_list.has_active()); CPPUNIT_ASSERT(!tracker_list.has_active_not_scrape()); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "000000", false)); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "000000", true)); tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_NONE); tracker_list.send_event(tracker_list.at(3), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); TEST_TRACKERS_IS_BUSY_5("01010", "01010"); CPPUNIT_ASSERT( tracker_list.has_active()); CPPUNIT_ASSERT( tracker_list.has_active_not_scrape()); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "100100", false)); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "100100", true)); tracker_2_worker->set_scrapable(); tracker_list.send_scrape(tracker_2); tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_NONE); tracker_list.send_event(tracker_list.at(3), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); TEST_TRACKERS_IS_BUSY_5("01110", "01110"); CPPUNIT_ASSERT( tracker_list.has_active()); CPPUNIT_ASSERT( tracker_list.has_active_not_scrape()); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "100100", false)); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "110100", true)); CPPUNIT_ASSERT(tracker_1_worker->trigger_success()); TEST_TRACKERS_IS_BUSY_5("00110", "00110"); CPPUNIT_ASSERT( tracker_list.has_active()); CPPUNIT_ASSERT( tracker_list.has_active_not_scrape()); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "000100", false)); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "010100", true)); CPPUNIT_ASSERT(tracker_2_worker->trigger_scrape()); CPPUNIT_ASSERT(tracker_3_worker->trigger_success()); TEST_TRACKERS_IS_BUSY_5("00000", "00000"); CPPUNIT_ASSERT(!tracker_list.has_active()); CPPUNIT_ASSERT(!tracker_list.has_active_not_scrape()); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "000000", false)); CPPUNIT_ASSERT(check_has_active_in_group(&tracker_list, "000000", true)); tracker_list.clear(); std::this_thread::sleep_for(100ms); } libtorrent-0.16.17/test/torrent/test_tracker_list.h000066400000000000000000000125331522271512000224250ustar00rootroot00000000000000#include "test/helpers/test_main_thread.h" #include "test/helpers/tracker_test.h" #include "torrent/download_info.h" class TestTrackerList : public TestFixtureWithMainNetTrackerThread { CPPUNIT_TEST_SUITE(TestTrackerList); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_enable); CPPUNIT_TEST(test_close); CPPUNIT_TEST(test_tracker_flags); CPPUNIT_TEST(test_find_url); CPPUNIT_TEST(test_can_scrape); CPPUNIT_TEST(test_single_success); CPPUNIT_TEST(test_single_failure); CPPUNIT_TEST(test_single_closing); CPPUNIT_TEST(test_multiple_success); CPPUNIT_TEST(test_scrape_success); CPPUNIT_TEST(test_scrape_failure); CPPUNIT_TEST(test_has_active); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_enable(); void test_close(); void test_tracker_flags(); void test_find_url(); void test_can_scrape(); void test_single_success(); void test_single_failure(); void test_single_closing(); void test_multiple_success(); void test_scrape_success(); void test_scrape_failure(); void test_has_active(); }; void process_main_and_tracker(TestFixtureWithMainAndTrackerThread* fixture); void process_main_and_tracker(TestFixtureWithMainNetTrackerThread* fixture); bool check_has_active_in_group(const torrent::TrackerList* tracker_list, const char* states, bool scrape); struct TestTrackerListWrapper { TestTrackerListWrapper(torrent::TrackerList* tracker_list) : m_tracker_list(tracker_list) {} void set_info(torrent::DownloadInfo* info) { m_tracker_list->set_info(info); } torrent::TrackerList* m_tracker_list; }; #define TRACKER_LIST_SETUP() \ torrent::DownloadInfo download_info; \ download_info.slot_left() = []() { return 0; }; \ download_info.slot_completed() = []() { return 0; }; \ \ torrent::TrackerList tracker_list; \ TestTrackerListWrapper(&tracker_list).set_info(&download_info); \ \ int success_counter = 0; \ int failure_counter = 0; \ int scrape_success_counter = 0; \ int scrape_failure_counter = 0; \ \ tracker_list.slot_success() = std::bind(&increment_value_uint, &success_counter); \ tracker_list.slot_failure() = std::bind(&increment_value_void, &failure_counter); \ tracker_list.slot_scrape_success() = std::bind(&increment_value_void, &scrape_success_counter); \ tracker_list.slot_scrape_failure() = std::bind(&increment_value_void, &scrape_failure_counter); #define TRACKER_INSERT(group, name) \ auto name = TrackerTest::new_tracker(&tracker_list, group, ""); \ TrackerTest::insert_tracker(&tracker_list, group, name); #define TEST_TRACKER_IS_BUSY(tracker, state) \ CPPUNIT_ASSERT(state == '0' || tracker.is_requesting()); \ CPPUNIT_ASSERT(state == '1' || !tracker.is_requesting()); #define TEST_MULTI3_IS_BUSY(original, rearranged) \ TEST_TRACKER_IS_BUSY(tracker_0_0, original[0]); \ TEST_TRACKER_IS_BUSY(tracker_0_1, original[1]); \ TEST_TRACKER_IS_BUSY(tracker_1_0, original[2]); \ TEST_TRACKER_IS_BUSY(tracker_2_0, original[3]); \ TEST_TRACKER_IS_BUSY(tracker_3_0, original[4]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(0), rearranged[0]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(1), rearranged[1]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(2), rearranged[2]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(3), rearranged[3]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(4), rearranged[4]); #define TEST_GROUP_IS_BUSY(original, rearranged) \ TEST_TRACKER_IS_BUSY(tracker_0_0, original[0]); \ TEST_TRACKER_IS_BUSY(tracker_0_1, original[1]); \ TEST_TRACKER_IS_BUSY(tracker_0_2, original[2]); \ TEST_TRACKER_IS_BUSY(tracker_1_0, original[3]); \ TEST_TRACKER_IS_BUSY(tracker_1_1, original[4]); \ TEST_TRACKER_IS_BUSY(tracker_2_0, original[5]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(0), rearranged[0]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(1), rearranged[1]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(2), rearranged[2]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(3), rearranged[3]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(4), rearranged[4]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(5), rearranged[5]); #define TEST_TRACKERS_IS_BUSY_5(original, rearranged) \ TEST_TRACKER_IS_BUSY(tracker_0, original[0]); \ TEST_TRACKER_IS_BUSY(tracker_1, original[1]); \ TEST_TRACKER_IS_BUSY(tracker_2, original[2]); \ TEST_TRACKER_IS_BUSY(tracker_3, original[3]); \ TEST_TRACKER_IS_BUSY(tracker_4, original[4]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(0), rearranged[0]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(1), rearranged[1]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(2), rearranged[2]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(3), rearranged[3]); \ TEST_TRACKER_IS_BUSY(tracker_list.at(4), rearranged[4]); libtorrent-0.16.17/test/torrent/test_tracker_list_features.cc000066400000000000000000000264421522271512000244650ustar00rootroot00000000000000#include "config.h" #include #include "net/address_list.h" #include "test/torrent/test_tracker_list.h" #include "test/torrent/test_tracker_list_features.h" CPPUNIT_TEST_SUITE_REGISTRATION(TestTrackerListFeatures); namespace { [[maybe_unused]] bool verify_did_internal_error(std::function func, bool should_throw) { bool did_throw = false; try { func(); } catch (const torrent::internal_error&) { did_throw = true; } return should_throw == did_throw; } } // namespace void TestTrackerListFeatures::test_new_peers() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0_0); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); CPPUNIT_ASSERT(tracker_0_0.state().latest_new_peers() == 0); CPPUNIT_ASSERT(tracker_0_0.state().latest_sum_peers() == 0); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_success(10, 20)); CPPUNIT_ASSERT(tracker_0_0.state().latest_new_peers() == 10); CPPUNIT_ASSERT(tracker_0_0.state().latest_sum_peers() == 20); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(tracker_0_0_worker->trigger_failure()); CPPUNIT_ASSERT(tracker_0_0.state().latest_new_peers() == 10); CPPUNIT_ASSERT(tracker_0_0.state().latest_sum_peers() == 20); tracker_list.clear_stats(); CPPUNIT_ASSERT(tracker_0_0.state().latest_new_peers() == 0); CPPUNIT_ASSERT(tracker_0_0.state().latest_sum_peers() == 0); } // test last_connect timer. // test has_active, and then clean up TrackerManager. void TestTrackerListFeatures::test_has_active() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0_0); TRACKER_INSERT(0, tracker_0_1); TRACKER_INSERT(1, tracker_1_0); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); CPPUNIT_ASSERT(!tracker_list.has_active()); CPPUNIT_ASSERT(!tracker_list.has_active_not_scrape()); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_list.has_active()); CPPUNIT_ASSERT(tracker_list.has_active_not_scrape()); tracker_0_0_worker->trigger_success(); CPPUNIT_ASSERT(!tracker_list.has_active()); CPPUNIT_ASSERT(!tracker_list.has_active_not_scrape()); tracker_list.send_event(tracker_list.at(2), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_list.has_active()); tracker_1_0_worker->trigger_success(); CPPUNIT_ASSERT(!tracker_list.has_active()); // Test multiple active trackers. tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_list.has_active()); tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_NONE); process_main_and_tracker(this); tracker_0_0_worker->trigger_success(); CPPUNIT_ASSERT(tracker_list.has_active()); tracker_0_1_worker->trigger_success(); CPPUNIT_ASSERT(!tracker_list.has_active()); tracker_1_0_worker->set_scrapable(); tracker_list.send_scrape(tracker_1_0); process_main_and_tracker(this); CPPUNIT_ASSERT(tracker_list.has_active()); CPPUNIT_ASSERT(!tracker_list.has_active_not_scrape()); } void TestTrackerListFeatures::test_find_next_to_request() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); TRACKER_INSERT(0, tracker_1); TRACKER_INSERT(0, tracker_2); TRACKER_INSERT(0, tracker_3); auto tracker_0_worker = TrackerTest::test_worker(tracker_0); auto tracker_1_worker = TrackerTest::test_worker(tracker_1); auto tracker_2_worker = TrackerTest::test_worker(tracker_2); auto tracker_3_worker = TrackerTest::test_worker(tracker_3); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin()); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin() + 1) == tracker_list.begin() + 1); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.end()) == tracker_list.end()); tracker_0.disable(); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 1); tracker_0.enable(); tracker_0_worker->set_failed(torrent::this_thread::cached_seconds() - 0s); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 1); tracker_1_worker->set_failed(torrent::this_thread::cached_seconds() - 0s); tracker_2_worker->set_failed(torrent::this_thread::cached_seconds() - 0s); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 3); tracker_3_worker->set_failed(torrent::this_thread::cached_seconds() - 0s); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 0); tracker_0_worker->set_failed(torrent::this_thread::cached_seconds() - 3s); tracker_1_worker->set_failed(torrent::this_thread::cached_seconds() - 2s); tracker_2_worker->set_failed(torrent::this_thread::cached_seconds() - 4s); tracker_3_worker->set_failed(torrent::this_thread::cached_seconds() - 2s); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 2); tracker_1_worker->set_success(torrent::this_thread::cached_seconds() - 1s); CPPUNIT_ASSERT_EQUAL(&(*(tracker_list.begin() + 2)), &(*tracker_list.find_next_to_request(tracker_list.begin()))); tracker_1_worker->set_success(torrent::this_thread::cached_seconds() - (tracker_1.state().normal_interval() - 1s)); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 1); } void TestTrackerListFeatures::test_find_next_to_request_groups() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0); TRACKER_INSERT(0, tracker_1); TRACKER_INSERT(1, tracker_2); TRACKER_INSERT(1, tracker_3); auto tracker_0_worker = TrackerTest::test_worker(tracker_0); auto tracker_1_worker = TrackerTest::test_worker(tracker_1); auto tracker_2_worker = TrackerTest::test_worker(tracker_2); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin()); tracker_0_worker->set_failed(torrent::this_thread::cached_seconds() - 4s); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 1); tracker_1_worker->set_failed(torrent::this_thread::cached_seconds() - 3s); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 2); tracker_2_worker->set_failed(torrent::this_thread::cached_seconds() - 2s); CPPUNIT_ASSERT(tracker_list.find_next_to_request(tracker_list.begin()) == tracker_list.begin() + 3); tracker_1_worker->set_failed(torrent::this_thread::cached_seconds() - 1s); CPPUNIT_ASSERT_EQUAL(&(*(tracker_list.begin() + 3)), &(*tracker_list.find_next_to_request(tracker_list.begin()))); } void TestTrackerListFeatures::test_count_active() { TRACKER_LIST_SETUP(); TRACKER_INSERT(0, tracker_0_0); TRACKER_INSERT(0, tracker_0_1); TRACKER_INSERT(1, tracker_1_0); TRACKER_INSERT(2, tracker_2_0); auto tracker_0_0_worker = TrackerTest::test_worker(tracker_0_0); auto tracker_0_1_worker = TrackerTest::test_worker(tracker_0_1); auto tracker_1_0_worker = TrackerTest::test_worker(tracker_1_0); auto tracker_2_0_worker = TrackerTest::test_worker(tracker_2_0); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 0); tracker_list.send_event(tracker_list.at(0), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 1); tracker_list.send_event(tracker_list.at(3), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 2); tracker_list.send_event(tracker_list.at(1), torrent::tracker::TrackerState::EVENT_NONE); tracker_list.send_event(tracker_list.at(2), torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 4); tracker_0_0_worker->trigger_success(); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 3); tracker_0_1_worker->trigger_success(); tracker_2_0_worker->trigger_success(); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 1); tracker_1_0_worker->trigger_success(); CPPUNIT_ASSERT(TrackerTest::count_active(&tracker_list) == 0); } // Add separate functions for sending state to multiple trackers... void TestTrackerListFeatures::test_request_safeguard() { // TODO: Reimplement tracker hammering properly. // TRACKER_LIST_SETUP(); // TRACKER_INSERT(0, tracker_1); // TRACKER_INSERT(0, tracker_2); // TRACKER_INSERT(0, tracker_3); // TRACKER_INSERT(0, tracker_foo); // auto tracker_1_worker = TrackerTest::test_worker(tracker_1); // auto tracker_2_worker = TrackerTest::test_worker(tracker_2); // auto tracker_3_worker = TrackerTest::test_worker(tracker_3); // auto tracker_foo_worker = TrackerTest::test_worker(tracker_foo); // for (unsigned int i = 0; i < 9; i++) { // CPPUNIT_ASSERT(verify_did_internal_error(std::bind(&torrent::TrackerList::send_event, &tracker_list, tracker_1, torrent::tracker::TrackerState::EVENT_NONE), false)); // CPPUNIT_ASSERT(tracker_1_worker->trigger_success()); // CPPUNIT_ASSERT(tracker_1.state().success_counter() == (i + 1)); // } // CPPUNIT_ASSERT(verify_did_internal_error(std::bind(&torrent::TrackerList::send_event, &tracker_list, tracker_1, torrent::tracker::TrackerState::EVENT_NONE), true)); // CPPUNIT_ASSERT(tracker_1_worker->trigger_success()); // cached_time += rak::timer::from_seconds(1000); // for (unsigned int i = 0; i < 9; i++) { // CPPUNIT_ASSERT(verify_did_internal_error(std::bind(&torrent::TrackerList::send_event, &tracker_list, tracker_foo, torrent::tracker::TrackerState::EVENT_NONE), false)); // CPPUNIT_ASSERT(tracker_foo_worker->trigger_success()); // CPPUNIT_ASSERT(tracker_foo.state().success_counter() == (i + 1)); // CPPUNIT_ASSERT(tracker_foo->is_usable()); // } // CPPUNIT_ASSERT(verify_did_internal_error(std::bind(&torrent::TrackerList::send_event, &tracker_list, tracker_foo, torrent::tracker::TrackerState::EVENT_NONE), true)); // CPPUNIT_ASSERT(tracker_foo_worker->trigger_success()); // for (unsigned int i = 0; i < 40; i++) { // CPPUNIT_ASSERT(verify_did_internal_error(std::bind(&torrent::TrackerList::send_event, &tracker_list, tracker_2, torrent::tracker::TrackerState::EVENT_NONE), false)); // CPPUNIT_ASSERT(tracker_2_worker->trigger_success()); // CPPUNIT_ASSERT(tracker_2.state().success_counter() == (i + 1)); // cached_time += rak::timer::from_seconds(1); // } // for (unsigned int i = 0; i < 17; i++) { // CPPUNIT_ASSERT(verify_did_internal_error(std::bind(&torrent::TrackerList::send_event, &tracker_list, tracker_3, torrent::tracker::TrackerState::EVENT_NONE), false)); // CPPUNIT_ASSERT(tracker_3_worker->trigger_success()); // CPPUNIT_ASSERT(tracker_3.state().success_counter() == (i + 1)); // if (i % 2) // cached_time += rak::timer::from_seconds(1); // } // CPPUNIT_ASSERT(verify_did_internal_error(std::bind(&torrent::TrackerList::send_event, &tracker_list, tracker_3, torrent::tracker::TrackerState::EVENT_NONE), true)); // CPPUNIT_ASSERT(tracker_3_worker->trigger_success()); // TRACKER_LIST_CLEANUP(); } libtorrent-0.16.17/test/torrent/test_tracker_list_features.h000066400000000000000000000011741522271512000243220ustar00rootroot00000000000000#include "test/helpers/test_main_thread.h" class TestTrackerListFeatures : public TestFixtureWithMainAndTrackerThread { CPPUNIT_TEST_SUITE(TestTrackerListFeatures); CPPUNIT_TEST(test_new_peers); CPPUNIT_TEST(test_has_active); CPPUNIT_TEST(test_find_next_to_request); CPPUNIT_TEST(test_find_next_to_request_groups); CPPUNIT_TEST(test_count_active); CPPUNIT_TEST(test_request_safeguard); CPPUNIT_TEST_SUITE_END(); public: void test_new_peers(); void test_has_active(); void test_find_next_to_request(); void test_find_next_to_request_groups(); void test_count_active(); void test_request_safeguard(); }; libtorrent-0.16.17/test/torrent/test_tracker_timeout.cc000066400000000000000000000212361522271512000232760ustar00rootroot00000000000000#include "config.h" #include "test/torrent/test_tracker_timeout.h" #include "test/torrent/test_tracker_list.h" #include "tracker/tracker_controller.h" CPPUNIT_TEST_SUITE_REGISTRATION(TestTrackerTimeout); void TestTrackerTimeout::test_set_timeout() { auto tracker = TrackerTest::new_tracker(nullptr, 0, ""); auto tracker_worker = TrackerTest::test_worker(tracker); try { CPPUNIT_ASSERT(tracker.state().normal_interval() == torrent::tracker::TrackerState::min_normal_interval); CPPUNIT_ASSERT(tracker.state().min_interval() == torrent::tracker::TrackerState::min_min_interval); tracker_worker->set_new_normal_interval(100); CPPUNIT_ASSERT(tracker.state().normal_interval() == 600s); tracker_worker->set_new_normal_interval(8 * 4000); CPPUNIT_ASSERT(tracker.state().normal_interval() == 8 * 3600s); tracker_worker->set_new_min_interval(100); CPPUNIT_ASSERT_EQUAL(300s, tracker.state().min_interval()); tracker_worker->set_new_min_interval(4 * 4000); CPPUNIT_ASSERT(tracker.state().min_interval() == 4 * 3600s); tracker_worker->cleanup(); } catch (const std::exception& e) { fprintf(stderr, "Exception in %s: %s\n", __func__, e.what()); throw; } } void TestTrackerTimeout::test_timeout_tracker() { auto tracker = TrackerTest::new_tracker(nullptr, 0, "http://tracker.com/announce"); auto tracker_worker = TrackerTest::test_worker(tracker); int flags = 0; try { CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 0); m_main_thread->test_add_cached_time(std::chrono::seconds(3)); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 0); flags = torrent::TrackerController::flag_active; CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 0); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == ~uint32_t()); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 0); tracker_worker->close(); tracker_worker->set_success(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT_EQUAL((uint32_t)1800, torrent::tracker_next_timeout(tracker, flags)); // CPPUNIT_ASSERT(1800 <= torrent::tracker_next_timeout(tracker, flags) && torrent::tracker_next_timeout(tracker, flags) <= 1800 + 3); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == ~uint32_t()); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT_EQUAL((uint32_t)1800, torrent::tracker_next_timeout(tracker, flags)); // CPPUNIT_ASSERT(1800 <= torrent::tracker_next_timeout(tracker, flags) && torrent::tracker_next_timeout(tracker, flags) <= 1800 + 3); tracker_worker->close(); tracker_worker->set_success(torrent::this_thread::cached_seconds().count() - 3); CPPUNIT_ASSERT_EQUAL((uint32_t)(1800 - 3), torrent::tracker_next_timeout(tracker, flags)); tracker_worker->set_success(torrent::this_thread::cached_seconds().count() + 3); CPPUNIT_ASSERT_EQUAL((uint32_t)(1800 + 3), torrent::tracker_next_timeout(tracker, flags)); tracker_worker->close(); flags = torrent::TrackerController::flag_active | torrent::TrackerController::flag_promiscuous_mode; CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 603); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == ~uint32_t()); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 603); tracker_worker->cleanup(); } catch (const std::exception& e) { fprintf(stderr, "Exception in %s: %s\n", __func__, e.what()); throw; } } void TestTrackerTimeout::test_timeout_update() { auto tracker = TrackerTest::new_tracker(nullptr, 0, "http://tracker.com/announce"); auto tracker_worker = TrackerTest::test_worker(tracker); int flags = 0; flags = torrent::TrackerController::flag_active | torrent::TrackerController::flag_send_update; try { CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 0); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 0); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == ~uint32_t()); tracker_worker->close(); tracker_worker->set_failed(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 5); tracker_worker->set_success(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 600); tracker_worker->cleanup(); } catch (const std::exception& e) { fprintf(stderr, "Exception in %s: %s\n", __func__, e.what()); throw; } } void TestTrackerTimeout::test_timeout_requesting() { auto tracker = TrackerTest::new_tracker(nullptr, 0, "http://tracker.com/announce"); auto tracker_worker = TrackerTest::test_worker(tracker); int flags = 0; flags = torrent::TrackerController::flag_active | torrent::TrackerController::flag_requesting; try { CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 0); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_SCRAPE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 0); tracker_worker->send_event(torrent::tracker::TrackerParams{}, torrent::tracker::TrackerState::EVENT_NONE); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == ~uint32_t()); // tracker_worker->set_latest_new_peers(10 - 1); tracker_worker->close(); tracker_worker->set_failed(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 5); tracker_worker->set_failed(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 10); tracker_worker->set_failed(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 20); tracker_worker->set_failed(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 40); tracker_worker->set_failed(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 80); tracker_worker->set_failed(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 160); tracker_worker->set_failed(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 300); //std::cout << "timeout:" << torrent::tracker_next_timeout(tracker, flags) << std::endl; tracker_worker->set_success(torrent::this_thread::cached_seconds().count()); // CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 10); // tracker_worker->set_success(1, torrent::this_thread::cached_seconds().count()); // CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 20); // tracker_worker->set_success(2, torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 600); tracker_worker->set_success(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 600); tracker_worker->set_success(torrent::this_thread::cached_seconds().count()); // tracker_worker->set_latest_sum_peers(9); // CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 20); tracker_worker->set_latest_sum_peers(10); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 600); tracker_worker->set_latest_sum_peers(10); tracker_worker->set_latest_new_peers(10); tracker_worker->set_success(torrent::this_thread::cached_seconds().count()); CPPUNIT_ASSERT(torrent::tracker_next_timeout(tracker, flags) == 600); tracker_worker->cleanup(); } catch (const std::exception& e) { fprintf(stderr, "Exception in %s: %s\n", __func__, e.what()); throw; } } libtorrent-0.16.17/test/torrent/test_tracker_timeout.h000066400000000000000000000007321522271512000231360ustar00rootroot00000000000000#include "test/helpers/test_main_thread.h" class TestTrackerTimeout : public TestFixtureWithMainAndTrackerThread { CPPUNIT_TEST_SUITE(TestTrackerTimeout); CPPUNIT_TEST(test_set_timeout); CPPUNIT_TEST(test_timeout_tracker); CPPUNIT_TEST(test_timeout_update); CPPUNIT_TEST(test_timeout_requesting); CPPUNIT_TEST_SUITE_END(); public: void test_set_timeout(); void test_timeout_tracker(); void test_timeout_update(); void test_timeout_requesting(); }; libtorrent-0.16.17/test/torrent/utils/000077500000000000000000000000001522271512000176635ustar00rootroot00000000000000libtorrent-0.16.17/test/torrent/utils/directory_events_test.cc000066400000000000000000000105601522271512000246230ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include #include #include #include "directory_events_test.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(utils_directory_events_test, "torrent/utils"); namespace { [[maybe_unused]] constexpr int added_flags = torrent::directory_events::flag_on_added | torrent::directory_events::flag_on_updated; void assert_mkdir(const std::string& path) { CPPUNIT_ASSERT_MESSAGE(("Could not create test directory: " + path).c_str(), ::mkdir(path.c_str(), 0700) == 0); } [[maybe_unused]] void assert_write_file(const std::string& path) { int fd = ::open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0600); CPPUNIT_ASSERT_MESSAGE(("Could not create test file: " + path).c_str(), fd != -1); CPPUNIT_ASSERT(::write(fd, "x", 1) == 1); CPPUNIT_ASSERT(::close(fd) == 0); } [[maybe_unused]] void assert_conflicting_watch(torrent::directory_events* events, const std::string& path, int flags) { try { events->notify_on(path, flags, [](auto) {}); CPPUNIT_ASSERT_MESSAGE(("Expected conflicting watch for: " + path).c_str(), false); } catch (const torrent::input_error&) { } } } // namespace void utils_directory_events_test::setUp() { TestFixtureWithMainThread::setUp(); char path_template[] = "/tmp/libtorrent-directory-events.XXXXXX"; char* path = ::mkdtemp(path_template); CPPUNIT_ASSERT(path != nullptr); m_root = path; m_link = m_root + "-link"; assert_mkdir(m_root + "/child"); assert_mkdir(m_root + "/other"); CPPUNIT_ASSERT_MESSAGE(("Could not create test symlink: " + m_link).c_str(), ::symlink(m_root.c_str(), m_link.c_str()) == 0); } void utils_directory_events_test::tearDown() { if (!m_root.empty()) { ::unlink((m_root + "/symlink-callback.torrent").c_str()); ::unlink(m_link.c_str()); ::rmdir((m_root + "/child").c_str()); ::rmdir((m_root + "/other").c_str()); ::rmdir(m_root.c_str()); m_link.clear(); m_root.clear(); } TestFixtureWithMainThread::tearDown(); } void utils_directory_events_test::test_ready_conflicts_with_added_same_directory() { #ifdef USE_INOTIFY torrent::directory_events events; CPPUNIT_ASSERT(events.open()); events.notify_on(m_root, torrent::directory_events::flag_on_ready, [](auto){}); assert_conflicting_watch(&events, m_root + "/.", added_flags); events.close(); #endif } void utils_directory_events_test::test_symlink_watch_uses_configured_callback_path() { #ifdef USE_INOTIFY torrent::directory_events events; std::vector paths; CPPUNIT_ASSERT(events.open()); events.notify_on(m_link, added_flags, [&paths](const std::string& path) { paths.push_back(path); }); assert_write_file(m_link + "/symlink-callback.torrent"); for (int i = 0; i < 100 && paths.empty(); i++) { events.event_read(); ::usleep(1000); } CPPUNIT_ASSERT(!paths.empty()); CPPUNIT_ASSERT_EQUAL(m_link + "/symlink-callback.torrent", paths.front()); events.close(); #endif } void utils_directory_events_test::test_added_conflicts_with_ready_same_directory_normalized() { #ifdef USE_INOTIFY torrent::directory_events events; CPPUNIT_ASSERT(events.open()); events.notify_on(m_root + "/child/..", added_flags, [](auto){}); assert_conflicting_watch(&events, m_root, torrent::directory_events::flag_on_ready); events.close(); #endif } void utils_directory_events_test::test_ready_allows_added_child() { #ifdef USE_INOTIFY torrent::directory_events events; CPPUNIT_ASSERT(events.open()); events.notify_on(m_root, torrent::directory_events::flag_on_ready, [](auto){}); events.notify_on(m_root + "/child", added_flags, [](auto){}); events.close(); #endif } void utils_directory_events_test::test_added_allows_ready_parent() { #ifdef USE_INOTIFY torrent::directory_events events; CPPUNIT_ASSERT(events.open()); events.notify_on(m_root + "/child", added_flags, [](auto){}); events.notify_on(m_root, torrent::directory_events::flag_on_ready, [](auto){}); events.close(); #endif } void utils_directory_events_test::test_ready_allows_added_sibling() { #ifdef USE_INOTIFY torrent::directory_events events; CPPUNIT_ASSERT(events.open()); events.notify_on(m_root + "/child", torrent::directory_events::flag_on_ready, [](auto){}); events.notify_on(m_root + "/other", added_flags, [](auto){}); events.close(); #endif } libtorrent-0.16.17/test/torrent/utils/directory_events_test.h000066400000000000000000000021401522271512000244600ustar00rootroot00000000000000#ifndef LIBTORRENT_TEST_DIRECTORY_EVENTS_TEST_H #define LIBTORRENT_TEST_DIRECTORY_EVENTS_TEST_H #include #include "test/helpers/test_main_thread.h" #include "torrent/utils/directory_events.h" class utils_directory_events_test : public TestFixtureWithMainThread { CPPUNIT_TEST_SUITE(utils_directory_events_test); CPPUNIT_TEST(test_ready_conflicts_with_added_same_directory); CPPUNIT_TEST(test_added_conflicts_with_ready_same_directory_normalized); CPPUNIT_TEST(test_ready_allows_added_child); CPPUNIT_TEST(test_added_allows_ready_parent); CPPUNIT_TEST(test_ready_allows_added_sibling); CPPUNIT_TEST(test_symlink_watch_uses_configured_callback_path); CPPUNIT_TEST_SUITE_END(); public: void setUp(); void tearDown(); void test_ready_conflicts_with_added_same_directory(); void test_added_conflicts_with_ready_same_directory_normalized(); void test_ready_allows_added_child(); void test_added_allows_ready_parent(); void test_ready_allows_added_sibling(); void test_symlink_watch_uses_configured_callback_path(); private: std::string m_link; std::string m_root; }; #endif libtorrent-0.16.17/test/torrent/utils/test_extents.cc000066400000000000000000000030421522271512000227220ustar00rootroot00000000000000#include "config.h" #include "test_extents.h" #include #include CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_extents, "torrent/utils"); #define TEST_EXTENT_BEGIN(name) \ lt_log_print(torrent::LOG_MOCK_CALLS, "extent: %s", name); typedef torrent::extents extent_type_1; template bool verify_extent_data(Extent& extent, const uint32_t* idx, const int* val) { while (*idx != *(idx + 1)) { for (auto i = *idx; i != *(idx + 1); i++) { lt_log_print(torrent::LOG_MOCK_CALLS, "extent: at %u", i); if (extent.at(i) != *val) return false; } idx++; val++; } return true; } static const uint32_t idx_empty[] = {0, 256, 256}; static const int val_empty[] = {0}; static const uint32_t idx_basic_1[] = {0, 1, 255, 256, 256}; static const int val_basic_1[] = {1, 0, 1}; void test_extents::test_basic() { extent_type_1 extent_1; extent_1.insert(0, 255, int()); { TEST_EXTENT_BEGIN("empty"); CPPUNIT_ASSERT(verify_extent_data(extent_1, idx_empty, val_empty)); CPPUNIT_ASSERT(extent_1.at(0) == int()); CPPUNIT_ASSERT(extent_1.at(255) == int()); }; { TEST_EXTENT_BEGIN("borders"); extent_1.insert(0, 0, 1); extent_1.insert(255, 255, 1); // This step shouldn't be needed. extent_1.insert(1, 254, int()); CPPUNIT_ASSERT(extent_1.at(0) == 1); CPPUNIT_ASSERT(extent_1.at(255) == 1); CPPUNIT_ASSERT(verify_extent_data(extent_1, idx_basic_1, val_basic_1)); }; } libtorrent-0.16.17/test/torrent/utils/test_extents.h000066400000000000000000000003431522271512000225650ustar00rootroot00000000000000#include "test/helpers/test_main_thread.h" class test_extents : public TestFixtureWithMainThread { CPPUNIT_TEST_SUITE(test_extents); CPPUNIT_TEST(test_basic); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); }; libtorrent-0.16.17/test/torrent/utils/test_log.cc000066400000000000000000000134221522271512000220140ustar00rootroot00000000000000#include "config.h" #include "test_log.h" #include #include #include #include #include #include #include "torrent/exceptions.h" #include "torrent/utils/log.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_log, "torrent/utils"); namespace { const char* expected_output = nullptr; unsigned int output_mask; void test_output(const char* output, unsigned int length, unsigned int mask) { CPPUNIT_ASSERT_MESSAGE("'" + std::string(output) + "' != '" + std::string(expected_output) + "'", std::strcmp(output, expected_output) == 0); CPPUNIT_ASSERT_MESSAGE("'" + std::string(output) + "'", std::strlen(output) == length); output_mask |= mask; } } // namespace namespace torrent { typedef std::vector > log_output_list; extern log_output_list log_outputs; } #define LTUNIT_ASSERT_OUTPUT(group, mask, expected, ...) \ output_mask = 0; expected_output = expected; \ lt_log_print(group, __VA_ARGS__); \ CPPUNIT_ASSERT(output_mask == (mask)); void test_log::setUp() { TestFixtureWithMainThread::setUp(); // Don't initialize since this creates the group->child connections. torrent::log_cleanup(); } void test_log::test_basic() { CPPUNIT_ASSERT(!torrent::log_groups.empty()); CPPUNIT_ASSERT(torrent::log_groups.size() == torrent::LOG_GROUP_MAX_SIZE); CPPUNIT_ASSERT(std::find_if(torrent::log_groups.begin(), torrent::log_groups.end(), std::bind(&torrent::log_group::valid, std::placeholders::_1)) == torrent::log_groups.end()); } inline void open_output(const char* name, int mask = 0) { torrent::log_open_output(name, std::bind(&::test_output, std::placeholders::_1, std::placeholders::_2, mask)); } void test_log::test_output_open() { CPPUNIT_ASSERT(torrent::log_groups[0].size_outputs() == 0); // Add test for unknown output names. open_output("test_output_1", 0x0); torrent::log_add_group_output(0, "test_output_1"); CPPUNIT_ASSERT(torrent::log_outputs.size() == 1); CPPUNIT_ASSERT(torrent::log_outputs[0].first == "test_output_1"); CPPUNIT_ASSERT(torrent::log_groups[0].outputs() == 0x1); CPPUNIT_ASSERT(torrent::log_groups[0].size_outputs() == 1); // Test inserting duplicate names, should catch. // CPPUNIT_ASSERT_THROW(torrent::log_open_output("test_output_1", torrent::log_slot());, torrent::input_error); // try { // torrent::log_open_output("test_output_1", torrent::log_slot()); // } catch (const torrent::input_error&) { // return; // } // CPPUNIT_ASSERT(false); // Test more than 64 entries. } // Test to make sure we don't call functions when using lt_log_print // on unused log items. void test_log::test_print() { open_output("test_print_1", 0x1); open_output("test_print_2", 0x2); torrent::log_add_group_output(0, "test_print_1"); LTUNIT_ASSERT_OUTPUT(0, 0x1, "foo_bar", "foo_bar"); LTUNIT_ASSERT_OUTPUT(0, 0x1, "foo 123 bar", "foo %i %s", 123, "bar"); torrent::log_add_group_output(0, "test_print_2"); LTUNIT_ASSERT_OUTPUT(0, 0x1|0x2, "test_multiple", "test_multiple"); } enum { GROUP_PARENT_1, GROUP_PARENT_2, GROUP_CHILD_1, GROUP_CHILD_1_1 }; void test_log::test_children() { open_output("test_children_1", 0x1); open_output("test_children_2", 0x2); torrent::log_add_group_output(GROUP_PARENT_1, "test_children_1"); torrent::log_add_group_output(GROUP_PARENT_2, "test_children_2"); torrent::log_add_child(GROUP_PARENT_1, GROUP_CHILD_1); torrent::log_add_child(GROUP_CHILD_1, GROUP_CHILD_1_1); // std::cout << "cached_output(" << torrent::log_groups[GROUP_PARENT_1].cached_outputs() << ')'; LTUNIT_ASSERT_OUTPUT(GROUP_PARENT_1, 0x1, "parent_1", "parent_1"); LTUNIT_ASSERT_OUTPUT(GROUP_CHILD_1, 0x1, "child_1", "child_1"); LTUNIT_ASSERT_OUTPUT(GROUP_CHILD_1_1, 0x1, "child_1", "child_1"); torrent::log_add_child(GROUP_PARENT_2, GROUP_CHILD_1); LTUNIT_ASSERT_OUTPUT(GROUP_PARENT_2, 0x2, "parent_2", "parent_2"); LTUNIT_ASSERT_OUTPUT(GROUP_CHILD_1, 0x3, "child_1", "child_1"); LTUNIT_ASSERT_OUTPUT(GROUP_CHILD_1_1, 0x3, "child_1", "child_1"); } void test_log::test_file_output() { std::string filename = "test_log.XXXXXX"; mktemp(&*filename.begin()); torrent::log_open_file_output("test_file", filename.c_str()); torrent::log_add_group_output(GROUP_PARENT_1, "test_file"); lt_log_print(GROUP_PARENT_1, "test_file"); torrent::log_cleanup(); // To ensure we flush the buffers. std::ifstream temp_file(filename.c_str()); CPPUNIT_ASSERT(temp_file.good()); char buffer[256]; temp_file.getline(buffer, 256); CPPUNIT_ASSERT_MESSAGE(buffer, std::string(buffer).find("test_file") != std::string::npos); std::remove(filename.c_str()); } void test_log::test_file_output_append() { std::string filename = "test_log.XXXXXX"; mktemp(&*filename.begin()); torrent::log_open_file_output("test_file", filename.c_str(), false); torrent::log_add_group_output(GROUP_PARENT_1, "test_file"); lt_log_print(GROUP_PARENT_1, "test_line_1"); torrent::log_cleanup(); // To ensure we flush the buffers. // re-open and write 2nd line torrent::log_open_file_output("test_file", filename.c_str(), true); torrent::log_add_group_output(GROUP_PARENT_1, "test_file"); lt_log_print(GROUP_PARENT_1, "test_line_2"); torrent::log_cleanup(); // To ensure we flush the buffers. std::ifstream temp_file(filename.c_str()); CPPUNIT_ASSERT(temp_file.good()); char buffer_line1[256]; temp_file.getline(buffer_line1, 256); char buffer_line2[256]; temp_file.getline(buffer_line2, 256); CPPUNIT_ASSERT_MESSAGE(buffer_line1, std::string(buffer_line1).find("test_line_1") != std::string::npos); CPPUNIT_ASSERT_MESSAGE(buffer_line2, std::string(buffer_line2).find("test_line_2") != std::string::npos); std::remove(filename.c_str()); } libtorrent-0.16.17/test/torrent/utils/test_log.h000066400000000000000000000010371522271512000216550ustar00rootroot00000000000000#include "helpers/test_main_thread.h" class test_log : public TestFixtureWithMainThread { CPPUNIT_TEST_SUITE(test_log); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_output_open); CPPUNIT_TEST(test_print); CPPUNIT_TEST(test_children); CPPUNIT_TEST(test_file_output); CPPUNIT_TEST(test_file_output_append); CPPUNIT_TEST_SUITE_END(); public: void setUp() override; void test_basic(); void test_output_open(); void test_print(); void test_children(); void test_file_output(); void test_file_output_append(); }; libtorrent-0.16.17/test/torrent/utils/test_log_buffer.cc000066400000000000000000000035621522271512000233510ustar00rootroot00000000000000#include "config.h" #include "test_log_buffer.h" #include "torrent/utils/log_buffer.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_log_buffer, "torrent/utils"); void test_log_buffer::test_basic() { torrent::log_buffer log; m_main_thread->test_set_cached_time(1000s); log.lock(); CPPUNIT_ASSERT(log.empty()); CPPUNIT_ASSERT(log.find_older(0) == log.end()); log.unlock(); log.lock_and_push_log("foobar", 6, -1); CPPUNIT_ASSERT(log.empty()); auto timestamp = std::chrono::seconds(365 * 24h + 1000s).count(); log.lock_and_push_log("foobar", 6, 0); CPPUNIT_ASSERT(log.size() == 1); CPPUNIT_ASSERT(log.back().timestamp == timestamp); CPPUNIT_ASSERT(log.back().group == 0); CPPUNIT_ASSERT(log.back().message == "foobar"); m_main_thread->test_add_cached_time(1s); log.lock_and_push_log("barbaz", 6, 0); CPPUNIT_ASSERT(log.size() == 2); CPPUNIT_ASSERT(log.back().timestamp == timestamp + 1); CPPUNIT_ASSERT(log.back().group == 0); CPPUNIT_ASSERT(log.back().message == "barbaz"); } void test_log_buffer::test_timestamps() { torrent::log_buffer log; m_main_thread->test_set_cached_time(1000s); auto timestamp = std::chrono::seconds(365 * 24h + 1000s).count(); log.lock_and_push_log("foobar", 6, 0); CPPUNIT_ASSERT(log.back().timestamp == timestamp); CPPUNIT_ASSERT(log.find_older(timestamp - 1) == log.begin()); CPPUNIT_ASSERT(log.find_older(timestamp) == log.end()); CPPUNIT_ASSERT(log.find_older(timestamp + 1) == log.end()); m_main_thread->test_add_cached_time(10s); log.lock_and_push_log("foobar", 6, 0); CPPUNIT_ASSERT(log.back().timestamp == timestamp + 10); CPPUNIT_ASSERT(log.find_older(timestamp) == log.begin()); CPPUNIT_ASSERT(log.find_older(timestamp + 10 - 1) == log.begin() + 1); CPPUNIT_ASSERT(log.find_older(timestamp + 10) == log.end()); CPPUNIT_ASSERT(log.find_older(timestamp + 10 + 1) == log.end()); } libtorrent-0.16.17/test/torrent/utils/test_log_buffer.h000066400000000000000000000004371522271512000232110ustar00rootroot00000000000000#include "helpers/test_main_thread.h" class test_log_buffer : public TestFixtureWithMainThread { CPPUNIT_TEST_SUITE(test_log_buffer); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_timestamps); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_timestamps(); }; libtorrent-0.16.17/test/torrent/utils/test_option_strings.cc000066400000000000000000000023471522271512000243200ustar00rootroot00000000000000#include "config.h" #include "test_option_strings.h" #include #include #include CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_option_strings, "torrent/utils"); #define TEST_ENTRY(group, name, value) \ { lt_log_print(torrent::LOG_MOCK_CALLS, "option_string: %s", name); \ std::string result(torrent::option_to_c_str_or_throw(torrent::group, value)); \ CPPUNIT_ASSERT_MESSAGE("Not found '" + result + "'", result == name); \ CPPUNIT_ASSERT(torrent::option_find_string(torrent::group, name) == value); \ } void test_option_strings::test_entries() { TEST_ENTRY(OPTION_CONNECTION_TYPE, "leech", torrent::Download::CONNECTION_LEECH); TEST_ENTRY(OPTION_CONNECTION_TYPE, "seed", torrent::Download::CONNECTION_SEED); TEST_ENTRY(OPTION_CONNECTION_TYPE, "initial_seed", torrent::Download::CONNECTION_INITIAL_SEED); TEST_ENTRY(OPTION_CONNECTION_TYPE, "metadata", torrent::Download::CONNECTION_METADATA); TEST_ENTRY(OPTION_LOG_GROUP, "critical", torrent::LOG_CRITICAL); TEST_ENTRY(OPTION_LOG_GROUP, "storage_notice", torrent::LOG_STORAGE_NOTICE); TEST_ENTRY(OPTION_LOG_GROUP, "torrent_debug", torrent::LOG_TORRENT_DEBUG); } libtorrent-0.16.17/test/torrent/utils/test_option_strings.h000066400000000000000000000003561522271512000241600ustar00rootroot00000000000000#include "helpers/test_main_thread.h" class test_option_strings : public TestFixtureWithMainThread { CPPUNIT_TEST_SUITE(test_option_strings); CPPUNIT_TEST(test_entries); CPPUNIT_TEST_SUITE_END(); public: void test_entries(); }; libtorrent-0.16.17/test/torrent/utils/test_queue_buckets.cc000066400000000000000000000162001522271512000240740ustar00rootroot00000000000000#include "config.h" #include "test_queue_buckets.h" #include "utils/instrumentation.h" #include "utils/queue_buckets.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_queue_buckets, "torrent/utils"); struct test_constants { static const int bucket_count = 2; static const torrent::instrumentation_enum instrumentation_added[bucket_count]; static const torrent::instrumentation_enum instrumentation_moved[bucket_count]; static const torrent::instrumentation_enum instrumentation_removed[bucket_count]; static const torrent::instrumentation_enum instrumentation_total[bucket_count]; template static void destroy(Type& obj); }; const int test_constants::bucket_count; const torrent::instrumentation_enum test_constants::instrumentation_added[bucket_count] = { torrent::INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_ADDED, torrent::INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_ADDED }; const torrent::instrumentation_enum test_constants::instrumentation_moved[bucket_count] = { torrent::INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_MOVED, torrent::INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_MOVED }; const torrent::instrumentation_enum test_constants::instrumentation_removed[bucket_count] = { torrent::INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_REMOVED, torrent::INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_REMOVED }; const torrent::instrumentation_enum test_constants::instrumentation_total[bucket_count] = { torrent::INSTRUMENTATION_TRANSFER_REQUESTS_QUEUED_TOTAL, torrent::INSTRUMENTATION_TRANSFER_REQUESTS_UNORDERED_TOTAL }; typedef torrent::queue_buckets buckets_type; static int items_destroyed = 0; template <> void test_constants::destroy([[maybe_unused]] int& obj) { items_destroyed++; } struct test_queue_bucket_compare { test_queue_bucket_compare(int v) : m_v(v) {} bool operator () (int obj) { return m_v == obj; } int m_v; }; #define FILL_BUCKETS(s_0, s_1) \ for (int i = 0; i < s_0; i++) \ buckets.push_back(0, i); \ for (int i = 0; i < s_1; i++) \ buckets.push_back(1, s_0 + i); #define VERIFY_QUEUE_SIZES(s_0, s_1) \ CPPUNIT_ASSERT(buckets.queue_size(0) == s_0); \ CPPUNIT_ASSERT(buckets.queue_size(1) == s_1); #ifdef LT_INSTRUMENTATION #define VERIFY_INSTRUMENTATION(a_0, m_0, r_0, t_0, a_1, m_1, r_1, t_1) \ CPPUNIT_ASSERT(torrent::instrumentation_values[test_constants::instrumentation_added[0]] == a_0); \ CPPUNIT_ASSERT(torrent::instrumentation_values[test_constants::instrumentation_moved[0]] == m_0); \ CPPUNIT_ASSERT(torrent::instrumentation_values[test_constants::instrumentation_removed[0]] == r_0); \ CPPUNIT_ASSERT(torrent::instrumentation_values[test_constants::instrumentation_total[0]] == t_0); \ CPPUNIT_ASSERT(torrent::instrumentation_values[test_constants::instrumentation_added[1]] == a_1); \ CPPUNIT_ASSERT(torrent::instrumentation_values[test_constants::instrumentation_moved[1]] == m_1); \ CPPUNIT_ASSERT(torrent::instrumentation_values[test_constants::instrumentation_removed[1]] == r_1); \ CPPUNIT_ASSERT(torrent::instrumentation_values[test_constants::instrumentation_total[1]] == t_1); #else #define VERIFY_INSTRUMENTATION(a_0, m_0, r_0, t_0, a_1, m_1, r_1, t_1) #endif #define VERIFY_ITEMS_DESTROYED(count) \ CPPUNIT_ASSERT(items_destroyed == count); \ items_destroyed = 0; // // Basic tests: // void test_queue_buckets::test_basic() { torrent::instrumentation_initialize(); buckets_type buckets; VERIFY_QUEUE_SIZES(0, 0); CPPUNIT_ASSERT(buckets.empty()); buckets.push_front(0, int()); VERIFY_QUEUE_SIZES(1, 0); buckets.push_back(0, int()); VERIFY_QUEUE_SIZES(2, 0); VERIFY_INSTRUMENTATION(2, 0, 0, 2, 0, 0, 0, 0); CPPUNIT_ASSERT(!buckets.empty()); buckets.push_front(1, int()); VERIFY_QUEUE_SIZES(2, 1); buckets.push_back(1, int()); VERIFY_QUEUE_SIZES(2, 2); VERIFY_INSTRUMENTATION(2, 0, 0, 2, 2, 0, 0, 2); CPPUNIT_ASSERT(!buckets.empty()); buckets.pop_front(0); VERIFY_QUEUE_SIZES(1, 2); buckets.pop_back(0); VERIFY_QUEUE_SIZES(0, 2); VERIFY_INSTRUMENTATION(2, 0, 2, 0, 2, 0, 0, 2); CPPUNIT_ASSERT(!buckets.empty()); buckets.pop_front(1); VERIFY_QUEUE_SIZES(0, 1); buckets.pop_back(1); VERIFY_QUEUE_SIZES(0, 0); VERIFY_INSTRUMENTATION(2, 0, 2, 0, 2, 0, 2, 0); CPPUNIT_ASSERT(buckets.empty()); } void test_queue_buckets::test_erase() { items_destroyed = 0; torrent::instrumentation_initialize(); buckets_type buckets; FILL_BUCKETS(10, 5); VERIFY_QUEUE_SIZES(10, 5); VERIFY_INSTRUMENTATION(10, 0, 0, 10, 5, 0, 0, 5); buckets.destroy(0, buckets.begin(0) + 3, buckets.begin(0) + 7); VERIFY_ITEMS_DESTROYED(4); VERIFY_QUEUE_SIZES(6, 5); VERIFY_INSTRUMENTATION(10, 0, 4, 6, 5, 0, 0, 5); buckets.destroy(1, buckets.begin(1) + 0, buckets.begin(1) + 5); VERIFY_ITEMS_DESTROYED(5); VERIFY_QUEUE_SIZES(6, 0); VERIFY_INSTRUMENTATION(10, 0, 4, 6, 5, 0, 5, 0); } static buckets_type::const_iterator bucket_queue_find_in_queue(const buckets_type& buckets, int idx, int value) { return torrent::queue_bucket_find_if_in_queue(buckets, idx, test_queue_bucket_compare(value)); } static std::pair bucket_queue_find_in_any(const buckets_type& buckets, int value) { return torrent::queue_bucket_find_if_in_any(buckets, test_queue_bucket_compare(value)); } void test_queue_buckets::test_find() { items_destroyed = 0; torrent::instrumentation_initialize(); buckets_type buckets; FILL_BUCKETS(10, 5); CPPUNIT_ASSERT(bucket_queue_find_in_queue(buckets, 0, 0) == buckets.begin(0)); CPPUNIT_ASSERT(bucket_queue_find_in_queue(buckets, 0, 10) == buckets.end(0)); CPPUNIT_ASSERT(bucket_queue_find_in_queue(buckets, 1, 10) == buckets.begin(1)); CPPUNIT_ASSERT(bucket_queue_find_in_any(buckets, 0).first == 0); CPPUNIT_ASSERT(bucket_queue_find_in_any(buckets, 0).second == buckets.begin(0)); CPPUNIT_ASSERT(bucket_queue_find_in_any(buckets, 10).first == 1); CPPUNIT_ASSERT(bucket_queue_find_in_any(buckets, 10).second == buckets.begin(1)); CPPUNIT_ASSERT(bucket_queue_find_in_any(buckets, 20).first == 2); CPPUNIT_ASSERT(bucket_queue_find_in_any(buckets, 20).second == buckets.end(1)); } void test_queue_buckets::test_destroy_range() { items_destroyed = 0; torrent::instrumentation_initialize(); buckets_type buckets; FILL_BUCKETS(10, 5); VERIFY_QUEUE_SIZES(10, 5); VERIFY_INSTRUMENTATION(10, 0, 0, 10, 5, 0, 0, 5); buckets.destroy(0, buckets.begin(0) + 3, buckets.begin(0) + 7); VERIFY_ITEMS_DESTROYED(4); VERIFY_QUEUE_SIZES(6, 5); VERIFY_INSTRUMENTATION(10, 0, 4, 6, 5, 0, 0, 5); buckets.destroy(1, buckets.begin(1) + 0, buckets.begin(1) + 5); VERIFY_ITEMS_DESTROYED(5); VERIFY_QUEUE_SIZES(6, 0); VERIFY_INSTRUMENTATION(10, 0, 4, 6, 5, 0, 5, 0); } void test_queue_buckets::test_move_range() { items_destroyed = 0; torrent::instrumentation_initialize(); buckets_type buckets; FILL_BUCKETS(10, 5); torrent::instrumentation_reset(); buckets.move_to(0, buckets.begin(0) + 3, buckets.begin(0) + 7, 1); VERIFY_ITEMS_DESTROYED(0); VERIFY_QUEUE_SIZES(6, 9); VERIFY_INSTRUMENTATION(0, 4, 0, 6, 4, 0, 0, 9); } libtorrent-0.16.17/test/torrent/utils/test_queue_buckets.h000066400000000000000000000006671522271512000237500ustar00rootroot00000000000000#include "helpers/test_fixture.h" class test_queue_buckets : public test_fixture { CPPUNIT_TEST_SUITE(test_queue_buckets); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_erase); CPPUNIT_TEST(test_find); CPPUNIT_TEST(test_destroy_range); CPPUNIT_TEST(test_move_range); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_erase(); void test_find(); void test_destroy_range(); void test_move_range(); }; libtorrent-0.16.17/test/torrent/utils/test_thread_base.cc000066400000000000000000000062571522271512000235040ustar00rootroot00000000000000#include "config.h" #include "test_thread_base.h" #include #include #include #include #include "runtime.h" #include "helpers/test_thread.h" #include "helpers/test_utils.h" #include "torrent/exceptions.h" #include "torrent/utils/log.h" #include "torrent/system/thread.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_thread_base, "torrent/utils"); void test_thread_base::test_basic() { auto thread = test_thread::create(); CPPUNIT_ASSERT(thread->flags() == 0); CPPUNIT_ASSERT(!thread->is_active()); CPPUNIT_ASSERT(thread->internal_poll() != nullptr); // Check active... } void test_thread_base::test_lifecycle() { auto thread = test_thread::create(); torrent::Runtime::initialize(); CPPUNIT_ASSERT(thread->state() == torrent::system::Thread::STATE_UNKNOWN); CPPUNIT_ASSERT(thread->test_state() == test_thread::TEST_NONE); thread->init_thread(); CPPUNIT_ASSERT(thread->state() == torrent::system::Thread::STATE_INITIALIZED); CPPUNIT_ASSERT(thread->is_initialized()); CPPUNIT_ASSERT(thread->test_state() == test_thread::TEST_PRE_START); thread->set_pre_stop(); CPPUNIT_ASSERT(wait_for_not_true(std::bind(&test_thread::is_test_state, thread.get(), test_thread::TEST_PRE_STOP))); thread->start_thread(); CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_state, thread.get(), test_thread::STATE_ACTIVE))); CPPUNIT_ASSERT(thread->is_active()); CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_test_state, thread.get(), test_thread::TEST_PRE_STOP))); thread->stop_thread_wait(); CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_state, thread.get(), test_thread::STATE_INACTIVE))); CPPUNIT_ASSERT(thread->is_inactive()); torrent::Runtime::cleanup(); } void test_thread_base::test_interrupt() { auto thread = test_thread::create(); torrent::Runtime::initialize(); thread->set_test_flag(test_thread::test_flag_long_timeout); thread->init_thread(); thread->start_thread(); // Vary the various timeouts. for (int i = 0; i < 20; i++) { std::this_thread::sleep_for(100ms); thread->interrupt(); std::this_thread::sleep_for(100ms); // usleep(0); thread->set_test_flag(test_thread::test_flag_do_work); thread->interrupt(); // Wait for flag to clear. CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_not_test_flags, thread.get(), test_thread::test_flag_do_work))); } thread->stop_thread_wait(); CPPUNIT_ASSERT(wait_for_true(std::bind(&test_thread::is_state, thread.get(), test_thread::STATE_INACTIVE))); torrent::Runtime::cleanup(); } void test_thread_base::test_stop() { std::unique_ptr thread; try { for (int i = 0; i < 20; i++) { thread = test_thread::create(); torrent::Runtime::initialize(); thread->set_test_flag(test_thread::test_flag_do_work); thread->init_thread(); thread->start_thread(); thread->stop_thread_wait(); CPPUNIT_ASSERT(thread->is_inactive()); torrent::Runtime::cleanup(); } } catch (const torrent::internal_error& e) { if (thread && thread->is_active()) thread->stop_thread_wait(); CPPUNIT_FAIL(std::string("Caught internal error: ") + e.what()); } } libtorrent-0.16.17/test/torrent/utils/test_thread_base.h000066400000000000000000000006301522271512000233330ustar00rootroot00000000000000#include "helpers/test_fixture.h" class test_thread_base : public test_fixture { CPPUNIT_TEST_SUITE(test_thread_base); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_lifecycle); CPPUNIT_TEST(test_interrupt); CPPUNIT_TEST(test_stop); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_lifecycle(); void test_interrupt(); void test_interrupt_legacy(); void test_stop(); }; libtorrent-0.16.17/test/torrent/utils/test_uri_parser.cc000066400000000000000000000062141522271512000234070ustar00rootroot00000000000000#include "config.h" #include "test_uri_parser.h" #include #include CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_uri_parser, "torrent/utils"); namespace { void test_print_uri_state(torrent::utils::uri_state state) { lt_log_print(torrent::LOG_MOCK_CALLS, "state.uri: %s", state.uri.c_str()); lt_log_print(torrent::LOG_MOCK_CALLS, "state.scheme: %s", state.scheme.c_str()); lt_log_print(torrent::LOG_MOCK_CALLS, "state.resource: %s", state.resource.c_str()); lt_log_print(torrent::LOG_MOCK_CALLS, "state.query: %s", state.query.c_str()); lt_log_print(torrent::LOG_MOCK_CALLS, "state.fragment: %s", state.fragment.c_str()); } } // namespace void test_uri_parser::test_basic() { torrent::utils::uri_state state; CPPUNIT_ASSERT(state.state == torrent::utils::uri_state::state_empty); CPPUNIT_ASSERT(state.state != torrent::utils::uri_state::state_valid); CPPUNIT_ASSERT(state.state != torrent::utils::uri_state::state_invalid); } #define MAGNET_BASIC "magnet:?xt=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C" void test_uri_parser::test_basic_magnet() { torrent::utils::uri_state state; uri_parse_str(MAGNET_BASIC, state); test_print_uri_state(state); CPPUNIT_ASSERT(state.state == torrent::utils::uri_state::state_valid); CPPUNIT_ASSERT(state.uri == MAGNET_BASIC); CPPUNIT_ASSERT(state.scheme == "magnet"); CPPUNIT_ASSERT(state.resource == ""); CPPUNIT_ASSERT(state.query == "xt=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C"); CPPUNIT_ASSERT(state.fragment == ""); } #define QUERY_MAGNET_QUERY \ "xt=urn:ed2k:31D6CFE0D16AE931B73C59D7E0C089C0" \ "&xl=0&dn=zero_len.fil" \ "&xt=urn:bitprint:3I42H3S6NNFQ2MSVX7XZKYAYSCX5QBYJ" \ ".LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ" \ "&xt=urn:md5:D41D8CD98F00B204E9800998ECF8427E" #define QUERY_MAGNET "magnet:?" QUERY_MAGNET_QUERY void test_uri_parser::test_query_magnet() { torrent::utils::uri_state state; torrent::utils::uri_query_state query_state; uri_parse_str(QUERY_MAGNET, state); test_print_uri_state(state); CPPUNIT_ASSERT(state.state == torrent::utils::uri_state::state_valid); CPPUNIT_ASSERT(state.uri == QUERY_MAGNET); CPPUNIT_ASSERT(state.scheme == "magnet"); CPPUNIT_ASSERT(state.resource == ""); CPPUNIT_ASSERT(state.query == QUERY_MAGNET_QUERY); CPPUNIT_ASSERT(state.fragment == ""); uri_parse_query_str(state.query, query_state); for (auto element : query_state.elements) lt_log_print(torrent::LOG_MOCK_CALLS, "query_element: %s", element.c_str()); CPPUNIT_ASSERT(query_state.state == torrent::utils::uri_query_state::state_valid); CPPUNIT_ASSERT(query_state.elements.size() == 5); CPPUNIT_ASSERT(query_state.elements.at(0) == "xt=urn:ed2k:31D6CFE0D16AE931B73C59D7E0C089C0"); CPPUNIT_ASSERT(query_state.elements.at(1) == "xl=0"); CPPUNIT_ASSERT(query_state.elements.at(2) == "dn=zero_len.fil"); CPPUNIT_ASSERT(query_state.elements.at(3) == "xt=urn:bitprint:3I42H3S6NNFQ2MSVX7XZKYAYSCX5QBYJ.LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ"); CPPUNIT_ASSERT(query_state.elements.at(4) == "xt=urn:md5:D41D8CD98F00B204E9800998ECF8427E"); } libtorrent-0.16.17/test/torrent/utils/test_uri_parser.h000066400000000000000000000005421522271512000232470ustar00rootroot00000000000000#include "helpers/test_main_thread.h" class test_uri_parser : public TestFixtureWithMainThread { CPPUNIT_TEST_SUITE(test_uri_parser); CPPUNIT_TEST(test_basic); CPPUNIT_TEST(test_basic_magnet); CPPUNIT_TEST(test_query_magnet); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); void test_basic_magnet(); void test_query_magnet(); }; libtorrent-0.16.17/test/tracker/000077500000000000000000000000001522271512000164615ustar00rootroot00000000000000libtorrent-0.16.17/test/tracker/test_tracker_http.cc000066400000000000000000000003071522271512000225210ustar00rootroot00000000000000#include "config.h" #include "test_tracker_http.h" #include "tracker/tracker_http.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(test_tracker_http, "tracker"); void test_tracker_http::test_basic() { } libtorrent-0.16.17/test/tracker/test_tracker_http.h000066400000000000000000000003711522271512000223640ustar00rootroot00000000000000#include "helpers/test_fixture.h" #include "torrent/system/thread.h" class test_tracker_http : public test_fixture { CPPUNIT_TEST_SUITE(test_tracker_http); CPPUNIT_TEST(test_basic); CPPUNIT_TEST_SUITE_END(); public: void test_basic(); };