├── .editorconfig ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── release.yml └── workflows │ ├── main.yml │ ├── pull_request.yml │ └── pull_request_label.yml ├── .gitignore ├── .gitmodules ├── .licenseignore ├── .spi.yml ├── .swift-format ├── .yamllint.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.txt ├── LICENSE.txt ├── NOTICE.txt ├── Package.swift ├── README.md ├── Sources ├── AsyncDNSResolver │ ├── AsyncDNSResolver.swift │ ├── Docs.docc │ │ └── index.md │ ├── Errors.swift │ ├── c-ares │ │ ├── AresChannel.swift │ │ ├── AresOptions.swift │ │ ├── DNSResolver_c-ares.swift │ │ └── Errors_c-ares.swift │ └── dnssd │ │ ├── DNSResolver_dnssd.swift │ │ └── Errors_dnssd.swift └── CAsyncDNSResolver │ └── include │ ├── CAsyncDNSResolver.h │ ├── ares_build.h │ └── ares_config.h └── Tests └── AsyncDNSResolverTests ├── c-ares ├── AresChannelTests.swift ├── AresErrorTests.swift ├── AresOptionsTests.swift └── CAresDNSResolverTests.swift └── dnssd ├── DNSDArraySliceTests.swift ├── DNSSDDNSResolverTests.swift └── DNSSDErrorTests.swift /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Motivation 2 | 3 | _[Explain here the context, and why you're making that change. What is the problem you're trying to solve.]_ 4 | 5 | ### Modifications 6 | 7 | _[Describe the modifications you've made.]_ 8 | 9 | ### Result 10 | 11 | _[After your change, what will change.]_ 12 | 13 | ### Test Plan 14 | 15 | _[Describe the steps you took, or will take, to qualify the change - such as adjusting tests and manual testing.]_ 16 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: SemVer Major 4 | labels: 5 | - ⚠️ semver/major 6 | - title: SemVer Minor 7 | labels: 8 | - 🆕 semver/minor 9 | - title: SemVer Patch 10 | labels: 11 | - 🔨 semver/patch 12 | - title: Other Changes 13 | labels: 14 | - semver/none 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | schedule: 7 | - cron: "0 8,20 * * *" 8 | 9 | jobs: 10 | unit-tests: 11 | name: Unit tests 12 | uses: apple/swift-nio/.github/workflows/unit_tests.yml@main 13 | with: 14 | linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 15 | linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 16 | linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 17 | linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error" 18 | linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error" 19 | 20 | cxx-interop: 21 | name: Cxx interop 22 | uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main 23 | 24 | static-sdk: 25 | name: Static SDK 26 | # Workaround https://github.com/nektos/act/issues/1875 27 | uses: apple/swift-nio/.github/workflows/static_sdk.yml@main 28 | 29 | macos-tests: 30 | name: macOS tests 31 | uses: apple/swift-nio/.github/workflows/macos_tests.yml@main 32 | with: 33 | runner_pool: nightly 34 | build_scheme: swift-async-dns-resolver 35 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | soundness: 9 | name: Soundness 10 | uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main 11 | with: 12 | license_header_check_project_name: "SwiftAsyncDNSResolver" 13 | api_breakage_check_enabled: false 14 | 15 | unit-tests: 16 | name: Unit tests 17 | uses: apple/swift-nio/.github/workflows/unit_tests.yml@main 18 | with: 19 | linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 20 | linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 21 | linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 22 | linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error" 23 | linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error" 24 | 25 | cxx-interop: 26 | name: Cxx interop 27 | uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main 28 | 29 | static-sdk: 30 | name: Static SDK 31 | # Workaround https://github.com/nektos/act/issues/1875 32 | uses: apple/swift-nio/.github/workflows/static_sdk.yml@main 33 | 34 | macos-tests: 35 | name: macOS tests 36 | uses: apple/swift-nio/.github/workflows/macos_tests.yml@main 37 | with: 38 | runner_pool: general 39 | build_scheme: swift-async-dns-resolver 40 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_label.yml: -------------------------------------------------------------------------------- 1 | name: PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, reopened, synchronize] 6 | 7 | jobs: 8 | semver-label-check: 9 | name: Semantic version label check 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 1 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | with: 16 | persist-credentials: false 17 | - name: Check for Semantic Version label 18 | uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .build/ 4 | /.swiftpm 5 | /Packages 6 | Package.resolved 7 | 8 | /*.xcodeproj 9 | xcuserdata/ 10 | .xcode 11 | 12 | .idea 13 | 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "c-ares"] 2 | path = Sources/CAsyncDNSResolver/c-ares 3 | url = https://github.com/c-ares/c-ares.git 4 | -------------------------------------------------------------------------------- /.licenseignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .licenseignore 3 | .swiftformatignore 4 | .spi.yml 5 | .swift-format 6 | .github/ 7 | **.md 8 | **.txt 9 | **Package.swift 10 | docker/* 11 | .gitmodules 12 | .editorconfig 13 | Sources/CAsyncDNSResolver/c-ares 14 | .yamllint.yml 15 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [AsyncDNSResolver] 5 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "version" : 1, 3 | "indentation" : { 4 | "spaces" : 4 5 | }, 6 | "tabWidth" : 4, 7 | "fileScopedDeclarationPrivacy" : { 8 | "accessLevel" : "private" 9 | }, 10 | "spacesAroundRangeFormationOperators" : false, 11 | "indentConditionalCompilationBlocks" : false, 12 | "indentSwitchCaseLabels" : false, 13 | "lineBreakAroundMultilineExpressionChainComponents" : false, 14 | "lineBreakBeforeControlFlowKeywords" : false, 15 | "lineBreakBeforeEachArgument" : true, 16 | "lineBreakBeforeEachGenericRequirement" : true, 17 | "lineLength" : 120, 18 | "maximumBlankLines" : 1, 19 | "respectsExistingLineBreaks" : true, 20 | "prioritizeKeepingFunctionOutputTogether" : true, 21 | "rules" : { 22 | "AllPublicDeclarationsHaveDocumentation" : false, 23 | "AlwaysUseLiteralForEmptyCollectionInit" : false, 24 | "AlwaysUseLowerCamelCase" : false, 25 | "AmbiguousTrailingClosureOverload" : true, 26 | "BeginDocumentationCommentWithOneLineSummary" : false, 27 | "DoNotUseSemicolons" : true, 28 | "DontRepeatTypeInStaticProperties" : true, 29 | "FileScopedDeclarationPrivacy" : true, 30 | "FullyIndirectEnum" : true, 31 | "GroupNumericLiterals" : true, 32 | "IdentifiersMustBeASCII" : true, 33 | "NeverForceUnwrap" : false, 34 | "NeverUseForceTry" : false, 35 | "NeverUseImplicitlyUnwrappedOptionals" : false, 36 | "NoAccessLevelOnExtensionDeclaration" : true, 37 | "NoAssignmentInExpressions" : true, 38 | "NoBlockComments" : true, 39 | "NoCasesWithOnlyFallthrough" : true, 40 | "NoEmptyTrailingClosureParentheses" : true, 41 | "NoLabelsInCasePatterns" : true, 42 | "NoLeadingUnderscores" : false, 43 | "NoParensAroundConditions" : true, 44 | "NoVoidReturnOnFunctionSignature" : true, 45 | "OmitExplicitReturns" : true, 46 | "OneCasePerLine" : true, 47 | "OneVariableDeclarationPerLine" : true, 48 | "OnlyOneTrailingClosureArgument" : true, 49 | "OrderedImports" : true, 50 | "ReplaceForEachWithForLoop" : true, 51 | "ReturnVoidInsteadOfEmptyTuple" : true, 52 | "UseEarlyExits" : false, 53 | "UseExplicitNilCheckInConditions" : false, 54 | "UseLetInEveryBoundCaseVariable" : false, 55 | "UseShorthandTypeNames" : true, 56 | "UseSingleLinePropertyGetter" : false, 57 | "UseSynthesizedInitializer" : false, 58 | "UseTripleSlashForDocumentationComments" : true, 59 | "UseWhereClausesInForLoops" : false, 60 | "ValidateDocumentationComments" : false 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | ignore: | 2 | Sources/CAsyncDNSResolver/c-ares/ 3 | .cirrus.yml 4 | 5 | extends: default 6 | 7 | rules: 8 | line-length: false 9 | document-start: false 10 | truthy: 11 | check-keys: false # Otherwise we get a false positive on GitHub action's `on` key 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | The code of conduct for this project can be found at https://swift.org/code-of-conduct. 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Legal 2 | 3 | By submitting a pull request, you represent that you have the right to license 4 | your contribution to Apple and the community, and agree by submitting the patch 5 | that your contributions are licensed under the Apache 2.0 license (see 6 | `LICENSE.txt`). 7 | 8 | ## How to submit a bug report 9 | 10 | Please ensure to specify the following: 11 | 12 | * Commit hash 13 | * Contextual information (e.g. what you were trying to achieve with swift-async-dns-resolver) 14 | * Simplest possible steps to reproduce 15 | * More complex the steps are, lower the priority will be. 16 | * A pull request with failing test case is preferred, but it's just fine to paste the test case into the issue description. 17 | * Anything that might be relevant in your opinion, such as: 18 | * Swift version or the output of `swift --version` 19 | * OS version and the output of `uname -a` 20 | * Network configuration 21 | 22 | ### Example 23 | 24 | ``` 25 | Commit hash: b17a8a9f0f814c01a56977680cb68d8a779c951f 26 | 27 | Context: 28 | While testing my application that uses with swift-async-dns-resolver, I noticed that ... 29 | 30 | Steps to reproduce: 31 | 1. ... 32 | 2. ... 33 | 3. ... 34 | 4. ... 35 | 36 | $ swift --version 37 | Swift version 4.0.2 (swift-4.0.2-RELEASE) 38 | Target: x86_64-unknown-linux-gnu 39 | 40 | Operating system: Ubuntu Linux 16.04 64-bit 41 | 42 | $ uname -a 43 | Linux beefy.machine 4.4.0-101-generic #124-Ubuntu SMP Fri Nov 10 18:29:59 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 44 | 45 | My system has IPv6 disabled. 46 | ``` 47 | 48 | ## Writing a Patch 49 | 50 | A good patch is: 51 | 52 | 1. Concise, and contains as few changes as needed to achieve the end result. 53 | 2. Tested, ensuring that any tests provided failed before the patch and pass after it. 54 | 3. Documented, adding API documentation as needed to cover new functions and properties. 55 | 4. Accompanied by a great commit message, using our commit message template. 56 | 57 | ### Run CI checks locally 58 | 59 | You can run the Github Actions workflows locally using 60 | [act](https://github.com/nektos/act). For detailed steps on how to do this please see [https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally](https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally). 61 | 62 | ## How to contribute your work 63 | 64 | Please open a pull request at https://github.com/apple/swift-async-dns-resolver. Make sure the CI passes, and then wait for code review. 65 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | For the purpose of tracking copyright, this is the list of individuals and 2 | organizations who have contributed source code to the Swift Asynchronous DNS Resolver. 3 | 4 | For employees of an organization/company where the copyright of work done 5 | by employees of that company is held by the company itself, only the company 6 | needs to be listed here. 7 | 8 | ## COPYRIGHT HOLDERS 9 | 10 | - Apple Inc. (all contributors with '@apple.com') 11 | 12 | ### Contributors 13 | 14 | - Yim Lee 15 | 16 | **Updating this list** 17 | 18 | Please do not edit this file manually. It is generated using `./scripts/generate_contributors_list.sh`. If a name is misspelled or appearing multiple times: add an entry in `./.mailmap` 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | The SwiftAsyncDNSResolver Project 3 | ================================= 4 | 5 | Please visit the SwiftAsyncDNSResolver web site for more information: 6 | 7 | * https://github.com/apple/swift-async-dns-resolver 8 | 9 | Copyright 2020-2023 The SwiftAsyncDNSResolver Project 10 | 11 | The SwiftAsyncDNSResolver Project licenses this file to you under the Apache License, 12 | version 2.0 (the "License"); you may not use this file except in compliance 13 | with the License. You may obtain a copy of the License at: 14 | 15 | https://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 19 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 20 | License for the specific language governing permissions and limitations 21 | under the License. 22 | 23 | Also, please refer to each LICENSE.txt file, which is located in 24 | the 'license' directory of the distribution file, for the license terms of the 25 | components that this product depends on. 26 | 27 | ------------------------------------------------------------------------------- 28 | 29 | This product uses and is influenced by c-ares. 30 | 31 | * LICENSE (MIT License): 32 | * https://github.com/c-ares/c-ares/blob/main/LICENSE.md 33 | * HOMEPAGE: 34 | * https://c-ares.org 35 | 36 | --- 37 | 38 | This product contains derivations of various scripts and templates from SwiftNIO. 39 | 40 | * LICENSE (Apache License 2.0): 41 | * https://www.apache.org/licenses/LICENSE-2.0 42 | * HOMEPAGE: 43 | * https://github.com/apple/swift-nio 44 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | 3 | import PackageDescription 4 | 5 | import class Foundation.FileManager 6 | 7 | var caresExclude = [ 8 | "./c-ares/src/lib/cares.rc", 9 | "./c-ares/src/lib/CMakeLists.txt", 10 | "./c-ares/src/lib/ares_config.h.cmake", 11 | "./c-ares/src/lib/Makefile.am", 12 | "./c-ares/src/lib/Makefile.inc", 13 | ] 14 | 15 | do { 16 | if try !(FileManager.default.contentsOfDirectory(atPath: "./Sources/CAsyncDNSResolver/c-ares/CMakeFiles").isEmpty) { 17 | caresExclude.append("./c-ares/CMakeFiles/") 18 | } 19 | } catch { 20 | // Assume CMakeFiles does not exist so no need to exclude it 21 | } 22 | 23 | let package = Package( 24 | name: "swift-async-dns-resolver", 25 | products: [ 26 | .library(name: "AsyncDNSResolver", targets: ["AsyncDNSResolver"]) 27 | ], 28 | dependencies: [], 29 | targets: [ 30 | .target( 31 | name: "CAsyncDNSResolver", 32 | dependencies: [], 33 | exclude: caresExclude, 34 | sources: ["./c-ares/src/lib"], 35 | cSettings: [ 36 | .headerSearchPath("./c-ares/include"), 37 | .headerSearchPath("./c-ares/src/lib"), 38 | .define("HAVE_CONFIG_H", to: "1"), 39 | ] 40 | ), 41 | 42 | .target( 43 | name: "AsyncDNSResolver", 44 | dependencies: [ 45 | "CAsyncDNSResolver" 46 | ] 47 | ), 48 | 49 | .testTarget(name: "AsyncDNSResolverTests", dependencies: ["AsyncDNSResolver"]), 50 | ], 51 | cLanguageStandard: .gnu11 52 | ) 53 | 54 | for target in package.targets { 55 | var settings = target.swiftSettings ?? [] 56 | settings.append(.enableExperimentalFeature("StrictConcurrency=complete")) 57 | target.swiftSettings = settings 58 | } 59 | 60 | // --- STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- // 61 | for target in package.targets { 62 | switch target.type { 63 | case .regular, .test, .executable: 64 | var settings = target.swiftSettings ?? [] 65 | // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md 66 | settings.append(.enableUpcomingFeature("MemberImportVisibility")) 67 | target.swiftSettings = settings 68 | case .macro, .plugin, .system, .binary: 69 | () // not applicable 70 | @unknown default: 71 | () // we don't know what to do here, do nothing 72 | } 73 | } 74 | // --- END: STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- // 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Asynchronous DNS Resolver 2 | 3 | A Swift library for asynchronous DNS queries. 4 | 5 | ## Overview 6 | 7 | This library wraps around the [dnssd](https://developer.apple.com/documentation/dnssd) framework and 8 | the [c-ares](https://github.com/c-ares/c-ares) C library with Swift-friendly APIs and data structures. 9 | 10 | ## Usage 11 | 12 | Add the package dependency in your `Package.swift`: 13 | 14 | ```swift 15 | .package( 16 | url: "https://github.com/apple/swift-async-dns-resolver", 17 | .upToNextMajor(from: "0.1.0") 18 | ), 19 | ``` 20 | 21 | Next, in your target, add `AsyncDNSResolver` to your dependencies: 22 | 23 | ```swift 24 | .target(name: "MyTarget", dependencies: [ 25 | .product(name: "AsyncDNSResolver", package: "swift-async-dns-resolver"), 26 | ], 27 | ``` 28 | 29 | ### Using the resolver 30 | 31 | ```swift 32 | // import the package 33 | import AsyncDNSResolver 34 | 35 | // Initialize a resolver 36 | let resolver = try AsyncDNSResolver() 37 | 38 | // Run a query 39 | let aRecords = try await resolver.queryA(name: "apple.com") 40 | 41 | // Process the `ARecord`s 42 | ... 43 | ``` 44 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/AsyncDNSResolver.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | // MARK: - Async DNS resolver API 16 | 17 | /// `AsyncDNSResolver` provides API for running asynchronous DNS queries. 18 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 19 | public struct AsyncDNSResolver { 20 | let underlying: DNSResolver 21 | 22 | /// Initialize an `AsyncDNSResolver`. 23 | /// 24 | /// By default, this makes use of the `dnssd` framework on Darwin platforms, 25 | /// and the `c-ares` C library on others. 26 | public init() throws { 27 | #if canImport(Darwin) 28 | self.init(DNSSDDNSResolver()) 29 | #else 30 | try self.init(CAresDNSResolver()) 31 | #endif 32 | } 33 | 34 | /// Initialize an `AsyncDNSResolver` using the given ``DNSResolver``. 35 | /// 36 | /// - Parameters: 37 | /// - dnsResolver: The ``DNSResolver`` to use. 38 | public init(_ dnsResolver: DNSResolver) { 39 | self.underlying = dnsResolver 40 | } 41 | 42 | /// Initialize an `AsyncDNSResolver` backed by ``CAresDNSResolver`` 43 | /// created using the given options. 44 | /// 45 | /// - Parameters: 46 | /// - options: Options to create ``CAresDNSResolver`` with. 47 | public init(options: CAresDNSResolver.Options) throws { 48 | try self.init(CAresDNSResolver(options: options)) 49 | } 50 | 51 | /// See ``DNSResolver/queryA(name:)``. 52 | public func queryA(name: String) async throws -> [ARecord] { 53 | try await self.underlying.queryA(name: name) 54 | } 55 | 56 | /// See ``DNSResolver/queryAAAA(name:)``. 57 | public func queryAAAA(name: String) async throws -> [AAAARecord] { 58 | try await self.underlying.queryAAAA(name: name) 59 | } 60 | 61 | /// See ``DNSResolver/queryNS(name:)``. 62 | public func queryNS(name: String) async throws -> NSRecord { 63 | try await self.underlying.queryNS(name: name) 64 | } 65 | 66 | /// See ``DNSResolver/queryCNAME(name:)``. 67 | public func queryCNAME(name: String) async throws -> String? { 68 | try await self.underlying.queryCNAME(name: name) 69 | } 70 | 71 | /// See ``DNSResolver/querySOA(name:)``. 72 | public func querySOA(name: String) async throws -> SOARecord? { 73 | try await self.underlying.querySOA(name: name) 74 | } 75 | 76 | /// See ``DNSResolver/queryPTR(name:)``. 77 | public func queryPTR(name: String) async throws -> PTRRecord { 78 | try await self.underlying.queryPTR(name: name) 79 | } 80 | 81 | /// See ``DNSResolver/queryMX(name:)``. 82 | public func queryMX(name: String) async throws -> [MXRecord] { 83 | try await self.underlying.queryMX(name: name) 84 | } 85 | 86 | /// See ``DNSResolver/queryTXT(name:)``. 87 | public func queryTXT(name: String) async throws -> [TXTRecord] { 88 | try await self.underlying.queryTXT(name: name) 89 | } 90 | 91 | /// See ``DNSResolver/querySRV(name:)``. 92 | public func querySRV(name: String) async throws -> [SRVRecord] { 93 | try await self.underlying.querySRV(name: name) 94 | } 95 | } 96 | 97 | /// API for running DNS queries. 98 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 99 | public protocol DNSResolver { 100 | /// Lookup A records associated with `name`. 101 | /// 102 | /// - Parameters: 103 | /// - name: The name to resolve. 104 | /// 105 | /// - Returns: ``ARecord``s for the given name, empty if no records were found. 106 | func queryA(name: String) async throws -> [ARecord] 107 | 108 | /// Lookup AAAA records associated with `name`. 109 | /// 110 | /// - Parameters: 111 | /// - name: The name to resolve. 112 | /// 113 | /// - Returns: ``AAAARecord``s for the given name, empty if no records were found. 114 | func queryAAAA(name: String) async throws -> [AAAARecord] 115 | 116 | /// Lookup NS record associated with `name`. 117 | /// 118 | /// - Parameters: 119 | /// - name: The name to resolve. 120 | /// 121 | /// - Returns: ``NSRecord`` for the given name. 122 | func queryNS(name: String) async throws -> NSRecord 123 | 124 | /// Lookup CNAME record associated with `name`. 125 | /// 126 | /// - Parameters: 127 | /// - name: The name to resolve. 128 | /// 129 | /// - Returns: CNAME for the given name, `nil` if no record was found. 130 | func queryCNAME(name: String) async throws -> String? 131 | 132 | /// Lookup SOA record associated with `name`. 133 | /// 134 | /// - Parameters: 135 | /// - name: The name to resolve. 136 | /// 137 | /// - Returns: ``SOARecord`` for the given name, `nil` if no record was found. 138 | func querySOA(name: String) async throws -> SOARecord? 139 | 140 | /// Lookup PTR record associated with `name`. 141 | /// 142 | /// - Parameters: 143 | /// - name: The name to resolve. 144 | /// 145 | /// - Returns: ``PTRRecord`` for the given name. 146 | func queryPTR(name: String) async throws -> PTRRecord 147 | 148 | /// Lookup MX records associated with `name`. 149 | /// 150 | /// - Parameters: 151 | /// - name: The name to resolve. 152 | /// 153 | /// - Returns: ``MXRecord``s for the given name, empty if no records were found. 154 | func queryMX(name: String) async throws -> [MXRecord] 155 | 156 | /// Lookup TXT records associated with `name`. 157 | /// 158 | /// - Parameters: 159 | /// - name: The name to resolve. 160 | /// 161 | /// - Returns: ``TXTRecord``s for the given name, empty if no records were found. 162 | func queryTXT(name: String) async throws -> [TXTRecord] 163 | 164 | /// Lookup SRV records associated with `name`. 165 | /// 166 | /// - Parameters: 167 | /// - name: The name to resolve. 168 | /// 169 | /// - Returns: ``SRVRecord``s for the given name, empty if no records were found. 170 | func querySRV(name: String) async throws -> [SRVRecord] 171 | } 172 | 173 | enum QueryType { 174 | case A 175 | case NS 176 | case CNAME 177 | case SOA 178 | case PTR 179 | case MX 180 | case TXT 181 | case AAAA 182 | case SRV 183 | case NAPTR 184 | } 185 | 186 | // MARK: - Query reply types 187 | 188 | public enum IPAddress: Sendable, Hashable, CustomStringConvertible { 189 | case ipv4(IPv4) 190 | case ipv6(IPv6) 191 | 192 | public var description: String { 193 | switch self { 194 | case .ipv4(let address): 195 | return String(describing: address) 196 | case .ipv6(let address): 197 | return String(describing: address) 198 | } 199 | } 200 | 201 | public struct IPv4: Sendable, Hashable, CustomStringConvertible { 202 | public var address: String 203 | public var description: String { self.address } 204 | 205 | public init(address: String) { 206 | self.address = address 207 | } 208 | } 209 | 210 | public struct IPv6: Sendable, Hashable, CustomStringConvertible { 211 | public var address: String 212 | public var description: String { self.address } 213 | 214 | public init(address: String) { 215 | self.address = address 216 | } 217 | } 218 | } 219 | 220 | public struct ARecord: Sendable, Hashable, CustomStringConvertible { 221 | public let address: IPAddress.IPv4 222 | public let ttl: Int32? 223 | 224 | public var description: String { 225 | "\(Self.self)(address=\(self.address), ttl=\(self.ttl.map { "\($0)" } ?? ""))" 226 | } 227 | 228 | public init(address: IPAddress.IPv4, ttl: Int32?) { 229 | self.address = address 230 | self.ttl = ttl 231 | } 232 | } 233 | 234 | public struct AAAARecord: Sendable, Hashable, CustomStringConvertible { 235 | public let address: IPAddress.IPv6 236 | public let ttl: Int32? 237 | 238 | public var description: String { 239 | "\(Self.self)(address=\(self.address), ttl=\(self.ttl.map { "\($0)" } ?? ""))" 240 | } 241 | 242 | public init(address: IPAddress.IPv6, ttl: Int32?) { 243 | self.address = address 244 | self.ttl = ttl 245 | } 246 | } 247 | 248 | public struct NSRecord: Sendable, Hashable, CustomStringConvertible { 249 | public let nameservers: [String] 250 | 251 | public var description: String { 252 | "\(Self.self)(nameservers=\(self.nameservers))" 253 | } 254 | 255 | public init(nameservers: [String]) { 256 | self.nameservers = nameservers 257 | } 258 | } 259 | 260 | public struct SOARecord: Sendable, Hashable, CustomStringConvertible { 261 | public let mname: String? 262 | public let rname: String? 263 | public let serial: UInt32 264 | public let refresh: UInt32 265 | public let retry: UInt32 266 | public let expire: UInt32 267 | public let ttl: UInt32 268 | 269 | public var description: String { 270 | "\(Self.self)(mname=\(self.mname ?? ""), rname=\(self.rname ?? ""), serial=\(self.serial), refresh=\(self.refresh), retry=\(self.retry), expire=\(self.expire), ttl=\(self.ttl))" 271 | } 272 | } 273 | 274 | public struct PTRRecord: Sendable, Hashable, CustomStringConvertible { 275 | public let names: [String] 276 | 277 | public var description: String { 278 | "\(Self.self)(names=\(self.names))" 279 | } 280 | 281 | public init(names: [String]) { 282 | self.names = names 283 | } 284 | } 285 | 286 | public struct MXRecord: Sendable, Hashable, CustomStringConvertible { 287 | public let host: String 288 | public let priority: UInt16 289 | 290 | public var description: String { 291 | "\(Self.self)(host=\(self.host), priority=\(self.priority))" 292 | } 293 | 294 | public init(host: String, priority: UInt16) { 295 | self.host = host 296 | self.priority = priority 297 | } 298 | } 299 | 300 | public struct TXTRecord: Sendable, Hashable { 301 | public let txt: String 302 | 303 | public var description: String { 304 | "\(Self.self)(\(self.txt))" 305 | } 306 | 307 | public init(txt: String) { 308 | self.txt = txt 309 | } 310 | } 311 | 312 | public struct SRVRecord: Sendable, Hashable, CustomStringConvertible { 313 | public let host: String 314 | public let port: UInt16 315 | public let weight: UInt16 316 | public let priority: UInt16 317 | 318 | public var description: String { 319 | "\(Self.self)(host=\(self.host), port=\(self.port), weight=\(self.weight), priority=\(self.priority))" 320 | } 321 | 322 | public init(host: String, port: UInt16, weight: UInt16, priority: UInt16) { 323 | self.host = host 324 | self.port = port 325 | self.weight = weight 326 | self.priority = priority 327 | } 328 | } 329 | 330 | public struct NAPTRRecord: Sendable, Hashable, CustomStringConvertible { 331 | public let flags: String? 332 | public let service: String? 333 | public let regExp: String? 334 | public let replacement: String 335 | public let order: UInt16 336 | public let preference: UInt16 337 | 338 | public var description: String { 339 | "\(Self.self)(flags=\(self.flags ?? ""), service=\(self.service ?? ""), regExp=\(self.regExp ?? ""), replacement=\(self.replacement), order=\(self.order), preference=\(self.preference))" 340 | } 341 | 342 | public init( 343 | flags: String?, 344 | service: String?, 345 | regExp: String?, 346 | replacement: String, 347 | order: UInt16, 348 | preference: UInt16 349 | ) { 350 | self.flags = flags 351 | self.service = service 352 | self.regExp = regExp 353 | self.replacement = replacement 354 | self.order = order 355 | self.preference = preference 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/Docs.docc/index.md: -------------------------------------------------------------------------------- 1 | # ``AsyncDNSResolver`` 2 | 3 | A Swift library for asynchronous DNS queries. 4 | 5 | ## Overview 6 | 7 | This library wraps around the [dnssd](https://developer.apple.com/documentation/dnssd) framework and 8 | the [c-ares](https://github.com/c-ares/c-ares) C library with Swift-friendly APIs and data structures. 9 | 10 | ## Usage 11 | 12 | Add the package dependency in your `Package.swift`: 13 | 14 | ```swift 15 | .package( 16 | url: "https://github.com/apple/swift-async-dns-resolver", 17 | .upToNextMajor(from: "0.1.0") 18 | ), 19 | ``` 20 | 21 | Next, in your target, add `AsyncDNSResolver` to your dependencies: 22 | 23 | ```swift 24 | .target(name: "MyTarget", dependencies: [ 25 | .product(name: "AsyncDNSResolver", package: "swift-async-dns-resolver"), 26 | ], 27 | ``` 28 | 29 | ### Using the resolver 30 | 31 | ```swift 32 | // import the package 33 | import AsyncDNSResolver 34 | 35 | // Initialize a resolver 36 | let resolver = AsyncDNSResolver() 37 | 38 | // Run a query 39 | let aRecords = try await resolver.queryA(name: "apple.com") 40 | 41 | // Process the `ARecord`s 42 | ... 43 | ``` 44 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/Errors.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 16 | extension AsyncDNSResolver { 17 | /// Possible ``AsyncDNSResolver/AsyncDNSResolver`` errors. 18 | public struct Error: Swift.Error, CustomStringConvertible { 19 | public struct Code: Hashable, Sendable { 20 | fileprivate enum Value: Hashable, Sendable { 21 | case badQuery 22 | case badResponse 23 | case connectionRefused 24 | case timeout 25 | case internalError 26 | } 27 | 28 | fileprivate var value: Value 29 | private init(_ value: Value) { 30 | self.value = value 31 | } 32 | 33 | /// The query was badly formed. 34 | public static var badQuery: Self { Self(.badQuery) } 35 | 36 | /// The response couldn't be parsed. 37 | public static var badResponse: Self { Self(.badResponse) } 38 | 39 | /// The server refused to accept a connection. 40 | public static var connectionRefused: Self { Self(.connectionRefused) } 41 | 42 | /// The query timed out. 43 | public static var timeout: Self { Self(.timeout) } 44 | 45 | /// An internal error. 46 | public static var internalError: Self { Self(.internalError) } 47 | } 48 | 49 | public var code: Code 50 | public var message: String 51 | public var source: Swift.Error? 52 | 53 | public init(code: Code, message: String = "", source: Swift.Error? = nil) { 54 | self.code = code 55 | self.message = message 56 | self.source = source 57 | } 58 | 59 | public var description: String { 60 | let name: String 61 | switch self.code.value { 62 | case .badQuery: 63 | name = "bad query" 64 | case .badResponse: 65 | name = "bad response" 66 | case .connectionRefused: 67 | name = "connection refused" 68 | case .timeout: 69 | name = "timeout" 70 | case .internalError: 71 | name = "internal" 72 | } 73 | 74 | let suffix = self.source.map { " (\($0))" } ?? "" 75 | return "\(name): \(self.message)\(suffix)" 76 | } 77 | } 78 | } 79 | 80 | /// An error thrown from c-ares. 81 | public struct CAresError: Error, Hashable, Sendable { 82 | /// The error code. 83 | public var code: Int 84 | 85 | public init(code: Int) { 86 | self.code = code 87 | } 88 | } 89 | 90 | /// An error thrown from DNSSD. 91 | public struct DNSSDError: Error, Hashable, Sendable { 92 | /// The error code. 93 | public var code: Int 94 | 95 | public init(code: Int) { 96 | self.code = code 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/c-ares/AresChannel.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import CAsyncDNSResolver 16 | import Foundation 17 | 18 | // MARK: - ares_channel 19 | 20 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 21 | final class AresChannel: @unchecked Sendable { 22 | private let locked_pointer: UnsafeMutablePointer 23 | private let lock = NSLock() 24 | 25 | // For testing only. 26 | var underlying: ares_channel? { 27 | self.locked_pointer.pointee 28 | } 29 | 30 | deinit { 31 | // Safe to perform without the lock, as in deinit we know that no more 32 | // strong references to self exist, so nobody can be holding the lock. 33 | ares_destroy(locked_pointer.pointee) 34 | locked_pointer.deallocate() 35 | ares_library_cleanup() 36 | } 37 | 38 | init(options: AresOptions) throws { 39 | // Initialize c-ares 40 | try checkAresResult { ares_library_init(ARES_LIB_INIT_ALL) } 41 | 42 | // Initialize channel with options 43 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 44 | try checkAresResult { ares_init_options(pointer, options.pointer, options.optionMasks) } 45 | 46 | // Additional options that require channel 47 | if let serversCSV = options.servers?.joined(separator: ",") { 48 | try checkAresResult { ares_set_servers_ports_csv(pointer.pointee, serversCSV) } 49 | } 50 | 51 | if let sortlist = options.sortlist?.joined(separator: " ") { 52 | try checkAresResult { ares_set_sortlist(pointer.pointee, sortlist) } 53 | } 54 | 55 | self.locked_pointer = pointer 56 | } 57 | 58 | func withChannel(_ body: (ares_channel) -> Void) { 59 | self.lock.lock() 60 | defer { self.lock.unlock() } 61 | 62 | guard let underlying = self.underlying else { 63 | fatalError("ares_channel not initialized") 64 | } 65 | body(underlying) 66 | } 67 | } 68 | 69 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 70 | private func checkAresResult(body: () -> Int32) throws { 71 | let result = body() 72 | guard result == ARES_SUCCESS else { 73 | throw AsyncDNSResolver.Error(cAresCode: result, "failed to initialize channel") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/c-ares/AresOptions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import CAsyncDNSResolver 16 | 17 | // MARK: - Options for `CAresDNSResolver` 18 | 19 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 20 | extension CAresDNSResolver { 21 | /// Options for ``CAresDNSResolver``. 22 | public struct Options: Sendable { 23 | public static var `default`: Options { 24 | .init() 25 | } 26 | 27 | /// Flags controlling the behavior of the resolver. 28 | /// 29 | /// - SeeAlso: ``CAresDNSResolver/Options/Flags`` 30 | public var flags: Flags = .init() 31 | 32 | /// The number of milliseconds each name server is given to respond to a query on the first try. (After the first try, the 33 | /// timeout algorithm becomes more complicated, but scales linearly with the value of timeout). 34 | public var timeoutMillis: Int32 = 3000 35 | 36 | /// The number of attempts the resolver will try contacting each name server before giving up. 37 | public var attempts: Int32 = 3 38 | 39 | /// The number of dots which must be present in a domain name for it to be queried for "as is" prior to querying for it 40 | /// with the default domain extensions appended. The value here is the default unless set otherwise by `resolv.conf` 41 | /// or the `RES_OPTIONS` environment variable. 42 | public var numberOfDots: Int32 = 1 43 | 44 | /// The UDP port to use for queries. The default value is 53, the standard name service port. 45 | public var udpPort: UInt16 = 53 46 | 47 | /// The TCP port to use for queries. The default value is 53, the standard name service port. 48 | public var tcpPort: UInt16 = 53 49 | 50 | /// The socket send buffer size. 51 | public var socketSendBufferSize: Int32? 52 | 53 | /// The socket receive buffer size. 54 | public var socketReceiveBufferSize: Int32? 55 | 56 | /// The EDNS packet size. 57 | public var ednsPacketSize: Int32? 58 | 59 | /// Configures round robin selection of nameservers. 60 | public var rotate: Bool? 61 | 62 | /// The path to use for reading the resolv.conf file. The `resolvconf_path` should be set to a path string, and 63 | /// will be honored on \*nix like systems. The default is `/etc/resolv.conf`. 64 | public var resolvConfPath: String? 65 | 66 | /// The path to use for reading the hosts file. The `hosts_path` should be set to a path string, and 67 | /// will be honored on \*nix like systems. The default is `/etc/hosts`. 68 | public var hostsFilePath: String? 69 | 70 | /// The lookups to perform for host queries. `lookups` should be set to a string of the characters "b" or "f", 71 | /// where "b" indicates a DNS lookup and "f" indicates a lookup in the hosts file. 72 | public var lookups: String? 73 | 74 | /// The domains to search, instead of the domains specified in `resolv.conf` or the domain derived 75 | /// from the kernel hostname variable. 76 | public var domains: [String]? 77 | 78 | /// The list of servers to contact, instead of the servers specified in `resolv.conf` or the local named. 79 | /// 80 | /// String format is `host[:port]`. IPv6 addresses with ports require square brackets. e.g. `[2001:4860:4860::8888]:53`. 81 | public var servers: [String]? 82 | 83 | /// The address sortlist configuration, so that addresses returned by `ares_gethostbyname` are sorted 84 | /// according to it. 85 | /// 86 | /// String format IP-address-netmask pairs. The netmask is optional but follows the address after a slash if present. 87 | /// e.g., `130.155.160.0/255.255.240.0 130.155.0.0`. 88 | public var sortlist: [String]? 89 | } 90 | } 91 | 92 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 93 | extension CAresDNSResolver.Options { 94 | public struct Flags: OptionSet, Sendable { 95 | public let rawValue: Int32 96 | 97 | public init(rawValue: Int32) { 98 | self.rawValue = rawValue 99 | } 100 | 101 | /// Always use TCP queries (the "virtual circuit") instead of UDP queries. Normally, TCP is only used if a UDP query yields a truncated result. 102 | public static let USEVC = Flags(rawValue: ARES_FLAG_USEVC) 103 | /// Only query the first server in the list of servers to query. 104 | public static let PRIMARY = Flags(rawValue: ARES_FLAG_PRIMARY) 105 | /// If a truncated response to a UDP query is received, do not fall back to TCP; simply continue on with the truncated response. 106 | public static let IGNTC = Flags(rawValue: ARES_FLAG_IGNTC) 107 | /// Do not set the "recursion desired" bit on outgoing queries, so that the name server being contacted will not try to fetch the answer 108 | /// from other servers if it doesn't know the answer locally. Be aware that this library will not do the recursion for you. Recursion must be 109 | /// handled by the client calling this library. 110 | public static let NORECURSE = Flags(rawValue: ARES_FLAG_NORECURSE) 111 | /// Do not close communications sockets when the number of active queries drops to zero. 112 | public static let STAYOPEN = Flags(rawValue: ARES_FLAG_STAYOPEN) 113 | /// Do not use the default search domains; only query hostnames as-is or as aliases. 114 | public static let NOSEARCH = Flags(rawValue: ARES_FLAG_NOSEARCH) 115 | /// Do not honor the HOSTALIASES environment variable, which normally specifies a file of hostname translations. 116 | public static let NOALIASES = Flags(rawValue: ARES_FLAG_NOALIASES) 117 | /// Do not discard responses with the SERVFAIL, NOTIMP, or REFUSED response code or responses whose questions don't match the 118 | /// questions in the request. Primarily useful for writing clients which might be used to test or debug name servers. 119 | public static let NOCHECKRESP = Flags(rawValue: ARES_FLAG_NOCHECKRESP) 120 | /// Include an EDNS pseudo-resource record (RFC 2671) in generated requests. 121 | public static let EDNS = Flags(rawValue: ARES_FLAG_EDNS) 122 | } 123 | } 124 | 125 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 126 | extension CAresDNSResolver.Options { 127 | var aresOptions: AresOptions { 128 | let aresOptions = AresOptions() 129 | aresOptions.setFlags(self.flags.rawValue) 130 | aresOptions.setTimeoutMillis(self.timeoutMillis) 131 | aresOptions.setTries(self.attempts) 132 | aresOptions.setNDots(self.numberOfDots) 133 | aresOptions.setUDPPort(self.udpPort) 134 | aresOptions.setTCPPort(self.tcpPort) 135 | 136 | if let socketSendBufferSize = self.socketSendBufferSize { 137 | aresOptions.setSocketSendBufferSize(socketSendBufferSize) 138 | } 139 | 140 | if let socketReceiveBufferSize = self.socketReceiveBufferSize { 141 | aresOptions.setSocketReceiveBufferSize(socketReceiveBufferSize) 142 | } 143 | 144 | if let ednsPacketSize = self.ednsPacketSize { 145 | aresOptions.setEDNSPacketSize(ednsPacketSize) 146 | } 147 | 148 | if let rotate = self.rotate { 149 | if rotate { 150 | aresOptions.setRotate() 151 | } else { 152 | aresOptions.setNoRotate() 153 | } 154 | } 155 | 156 | if let resolvConfPath = self.resolvConfPath { 157 | aresOptions.setResolvConfPath(resolvConfPath) 158 | } 159 | 160 | if let hostsFilePath = self.hostsFilePath { 161 | aresOptions.setHostsFilePath(hostsFilePath) 162 | } 163 | 164 | if let lookups = self.lookups { 165 | aresOptions.setLookups(lookups) 166 | } 167 | 168 | if let domains = self.domains { 169 | aresOptions.setDomains(domains) 170 | } 171 | 172 | if let servers = self.servers { 173 | aresOptions.setServers(servers) 174 | } 175 | 176 | if let sortlist = self.sortlist { 177 | aresOptions.setSortlist(sortlist) 178 | } 179 | 180 | return aresOptions 181 | } 182 | } 183 | 184 | // MARK: - ares_options 185 | 186 | /// Wrapper for `ares_options`. 187 | /// 188 | /// `servers` /`nservers` and `sortlist`/`nsort` are configured by calling `ares_set_*`, which 189 | /// require `ares_channel` argument. Even though `ares_options` has dedicated fields for them, they 190 | /// are not set here but in `AresChannel` instead. 191 | class AresOptions { 192 | let pointer: UnsafeMutablePointer 193 | 194 | private var resolvConfPathPointer: UnsafeMutablePointer? 195 | private var hostsFilePathPointer: UnsafeMutablePointer? 196 | private var lookupsPointer: UnsafeMutablePointer? 197 | private var domainPointers: [UnsafeMutablePointer?]? 198 | 199 | private(set) var servers: [String]? 200 | private(set) var sortlist: [String]? 201 | 202 | private(set) var _optionMasks: AresOptionMasks = .init() 203 | var optionMasks: AresOptionMasks.RawValue { 204 | self._optionMasks.rawValue 205 | } 206 | 207 | var underlying: ares_options { 208 | self.pointer.pointee 209 | } 210 | 211 | deinit { 212 | self.pointer.deallocate() 213 | self.resolvConfPathPointer?.deallocate() 214 | self.hostsFilePathPointer?.deallocate() 215 | self.lookupsPointer?.deallocate() 216 | self.domainPointers?.deallocate() 217 | } 218 | 219 | init() { 220 | self.pointer = UnsafeMutablePointer.allocate(capacity: 1) 221 | self.pointer.pointee = ares_options() 222 | } 223 | 224 | func setFlags(_ flags: CInt) { 225 | self.set(option: .FLAGS, keyPath: \.flags, value: flags) 226 | } 227 | 228 | func setTimeoutMillis(_ timeoutMillis: CInt) { 229 | self.set(option: .TIMEOUTMS, keyPath: \.timeout, value: timeoutMillis) 230 | } 231 | 232 | func setTries(_ tries: CInt) { 233 | self.set(option: .TRIES, keyPath: \.tries, value: tries) 234 | } 235 | 236 | func setNDots(_ ndots: CInt) { 237 | self.set(option: .NDOTS, keyPath: \.ndots, value: ndots) 238 | } 239 | 240 | func setUDPPort(_ udpPort: CUnsignedShort) { 241 | self.set(option: .UDP_PORT, keyPath: \.udp_port, value: udpPort) 242 | } 243 | 244 | func setTCPPort(_ tcpPort: CUnsignedShort) { 245 | self.set(option: .TCP_PORT, keyPath: \.tcp_port, value: tcpPort) 246 | } 247 | 248 | func setSocketSendBufferSize(_ socketSendBufferSize: CInt) { 249 | self.set(option: .SOCK_SNDBUF, keyPath: \.socket_send_buffer_size, value: socketSendBufferSize) 250 | } 251 | 252 | func setSocketReceiveBufferSize(_ socketReceiveBufferSize: CInt) { 253 | self.set(option: .SOCK_RCVBUF, keyPath: \.socket_receive_buffer_size, value: socketReceiveBufferSize) 254 | } 255 | 256 | func setEDNSPacketSize(_ ednsPacketSize: CInt) { 257 | self.set(option: .EDNSPSZ, keyPath: \.ednspsz, value: ednsPacketSize) 258 | } 259 | 260 | func setRotate() { 261 | self._optionMasks.insert(.ROTATE) 262 | self._optionMasks.remove(.NOROTATE) 263 | } 264 | 265 | func setNoRotate() { 266 | self._optionMasks.insert(.NOROTATE) 267 | self._optionMasks.remove(.ROTATE) 268 | } 269 | 270 | func setResolvConfPath(_ resolvConfPath: String) { 271 | // The pointer is being replaced so deallocate it first 272 | self.resolvConfPathPointer?.deallocate() 273 | self.resolvConfPathPointer = resolvConfPath.ccharArrayPointer 274 | self.set(option: .RESOLVCONF, keyPath: \.resolvconf_path, value: self.resolvConfPathPointer) 275 | } 276 | 277 | func setHostsFilePath(_ hostsFilePath: String) { 278 | // The pointer is being replaced so deallocate it first 279 | self.hostsFilePathPointer?.deallocate() 280 | self.hostsFilePathPointer = hostsFilePath.ccharArrayPointer 281 | self.set(option: .HOSTS_FILE, keyPath: \.hosts_path, value: self.hostsFilePathPointer) 282 | } 283 | 284 | func setLookups(_ lookups: String) { 285 | // The pointer is being replaced so deallocate it first 286 | self.lookupsPointer?.deallocate() 287 | self.lookupsPointer = lookups.ccharArrayPointer 288 | self.set(option: .LOOKUPS, keyPath: \.lookups, value: self.lookupsPointer) 289 | } 290 | 291 | func setDomains(_ domains: [String]) { 292 | // The pointers are being replaced so deallocate them first 293 | self.domainPointers?.deallocate() 294 | 295 | let domainPointers = domains.map(\.ccharArrayPointer) 296 | self.domainPointers = domainPointers 297 | 298 | domainPointers.withUnsafeBufferPointer { bufferPointer in 299 | let domainsPointer = UnsafeMutablePointer?>(mutating: bufferPointer.baseAddress) 300 | self.set(option: .DOMAINS, keyPath: \.domains, value: domainsPointer) 301 | self.set(keyPath: \.ndomains, value: Int32(domains.count)) 302 | } 303 | } 304 | 305 | func setServers(_ servers: [String]) { 306 | self.servers = servers 307 | } 308 | 309 | func setSortlist(_ sortlist: [String]) { 310 | self.sortlist = sortlist 311 | } 312 | 313 | /// Sets the callback function to be invoked when a socket changes state. 314 | /// 315 | /// `callback(data, socket, readable, writable)` will be called when a socket changes state: 316 | /// - `data` is the optional `data` used to invoke `setSocketStateCallback` 317 | /// - `readable` is set to true if the socket should listen for read events 318 | /// - `writable` is set to true if the socket should listen for write events 319 | func setSocketStateCallback(with data: UnsafeMutableRawPointer? = nil, _ callback: @escaping SocketStateCallback) { 320 | self.set(option: .SOCK_STATE_CB, keyPath: \.sock_state_cb, value: callback) 321 | self.set(keyPath: \.sock_state_cb_data, value: data) 322 | } 323 | 324 | private func set(option: AresOptionMasks, keyPath: WritableKeyPath, value: T) { 325 | self.set(keyPath: keyPath, value: value) 326 | self._optionMasks.insert(option) 327 | } 328 | 329 | private func set(keyPath: WritableKeyPath, value: T) { 330 | var underlying = self.underlying 331 | underlying[keyPath: keyPath] = value 332 | self.pointer.pointee = underlying 333 | } 334 | 335 | private func set(option: AresOptionMasks, keyPath: WritableKeyPath, value: T?) { 336 | var underlying = self.underlying 337 | underlying[keyPath: keyPath] = value 338 | self.pointer.pointee = underlying 339 | self._optionMasks.insert(option) 340 | } 341 | } 342 | 343 | typealias Socket = ares_socket_t 344 | typealias SocketStateCallback = @convention(c) (UnsafeMutableRawPointer?, Socket, CInt, CInt) -> Void 345 | 346 | extension String { 347 | fileprivate var ccharArrayPointer: UnsafeMutablePointer? { 348 | let count = self.utf8CString.count 349 | let destPointer = UnsafeMutablePointer.allocate(capacity: count) 350 | self.withCString { srcPointer in 351 | destPointer.initialize(from: srcPointer, count: count) 352 | } 353 | return destPointer 354 | } 355 | } 356 | 357 | extension Sequence { 358 | fileprivate func deallocate() where Element == UnsafeMutablePointer? { 359 | for entry in self { 360 | entry?.deallocate() 361 | } 362 | } 363 | } 364 | 365 | /// Represents `ARES_OPT_*` values. 366 | struct AresOptionMasks: OptionSet { 367 | let rawValue: CInt 368 | 369 | static let FLAGS = AresOptionMasks(rawValue: ARES_OPT_FLAGS) 370 | static let TIMEOUT = AresOptionMasks(rawValue: ARES_OPT_TIMEOUT) // Deprecated by TIMEOUTMS 371 | static let TRIES = AresOptionMasks(rawValue: ARES_OPT_TRIES) 372 | static let NDOTS = AresOptionMasks(rawValue: ARES_OPT_NDOTS) 373 | static let UDP_PORT = AresOptionMasks(rawValue: ARES_OPT_UDP_PORT) 374 | static let TCP_PORT = AresOptionMasks(rawValue: ARES_OPT_TCP_PORT) 375 | static let SERVERS = AresOptionMasks(rawValue: ARES_OPT_SERVERS) 376 | static let DOMAINS = AresOptionMasks(rawValue: ARES_OPT_DOMAINS) 377 | static let LOOKUPS = AresOptionMasks(rawValue: ARES_OPT_LOOKUPS) 378 | static let SOCK_STATE_CB = AresOptionMasks(rawValue: ARES_OPT_SOCK_STATE_CB) 379 | static let SORTLIST = AresOptionMasks(rawValue: ARES_OPT_SORTLIST) 380 | static let SOCK_SNDBUF = AresOptionMasks(rawValue: ARES_OPT_SOCK_SNDBUF) 381 | static let SOCK_RCVBUF = AresOptionMasks(rawValue: ARES_OPT_SOCK_RCVBUF) 382 | static let TIMEOUTMS = AresOptionMasks(rawValue: ARES_OPT_TIMEOUTMS) 383 | static let ROTATE = AresOptionMasks(rawValue: ARES_OPT_ROTATE) 384 | static let EDNSPSZ = AresOptionMasks(rawValue: ARES_OPT_EDNSPSZ) 385 | static let NOROTATE = AresOptionMasks(rawValue: ARES_OPT_NOROTATE) 386 | static let RESOLVCONF = AresOptionMasks(rawValue: ARES_OPT_RESOLVCONF) 387 | static let HOSTS_FILE = AresOptionMasks(rawValue: ARES_OPT_HOSTS_FILE) 388 | } 389 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/c-ares/DNSResolver_c-ares.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import CAsyncDNSResolver 16 | import Foundation 17 | 18 | /// ``DNSResolver`` implementation backed by c-ares C library. 19 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 20 | public final class CAresDNSResolver: DNSResolver, Sendable { 21 | let options: Options 22 | let ares: Ares 23 | 24 | /// Initialize a `CAresDNSResolver` with the given options. 25 | /// 26 | /// - Parameters: 27 | /// - options: ``CAresDNSResolver/Options`` to create resolver with. 28 | public init(options: Options) throws { 29 | self.options = options 30 | self.ares = try Ares(options: options.aresOptions) 31 | } 32 | 33 | /// Initialize a `CAresDNSResolver` using default options. 34 | public convenience init() throws { 35 | try self.init(options: .default) 36 | } 37 | 38 | /// See ``DNSResolver/queryA(name:)``. 39 | public func queryA(name: String) async throws -> [ARecord] { 40 | try await self.ares.query(type: .A, name: name, replyParser: Ares.AQueryReplyParser.instance) 41 | } 42 | 43 | /// See ``DNSResolver/queryAAAA(name:)``. 44 | public func queryAAAA(name: String) async throws -> [AAAARecord] { 45 | try await self.ares.query(type: .AAAA, name: name, replyParser: Ares.AAAAQueryReplyParser.instance) 46 | } 47 | 48 | /// See ``DNSResolver/queryNS(name:)``. 49 | public func queryNS(name: String) async throws -> NSRecord { 50 | try await self.ares.query(type: .NS, name: name, replyParser: Ares.NSQueryReplyParser.instance) 51 | } 52 | 53 | /// See ``DNSResolver/queryCNAME(name:)``. 54 | public func queryCNAME(name: String) async throws -> String? { 55 | try await self.ares.query(type: .CNAME, name: name, replyParser: Ares.CNAMEQueryReplyParser.instance) 56 | } 57 | 58 | /// See ``DNSResolver/querySOA(name:)``. 59 | public func querySOA(name: String) async throws -> SOARecord? { 60 | try await self.ares.query(type: .SOA, name: name, replyParser: Ares.SOAQueryReplyParser.instance) 61 | } 62 | 63 | /// See ``DNSResolver/queryPTR(name:)``. 64 | public func queryPTR(name: String) async throws -> PTRRecord { 65 | try await self.ares.query(type: .PTR, name: name, replyParser: Ares.PTRQueryReplyParser.instance) 66 | } 67 | 68 | /// See ``DNSResolver/queryMX(name:)``. 69 | public func queryMX(name: String) async throws -> [MXRecord] { 70 | try await self.ares.query(type: .MX, name: name, replyParser: Ares.MXQueryReplyParser.instance) 71 | } 72 | 73 | /// See ``DNSResolver/queryTXT(name:)``. 74 | public func queryTXT(name: String) async throws -> [TXTRecord] { 75 | try await self.ares.query(type: .TXT, name: name, replyParser: Ares.TXTQueryReplyParser.instance) 76 | } 77 | 78 | /// See ``DNSResolver/querySRV(name:)``. 79 | public func querySRV(name: String) async throws -> [SRVRecord] { 80 | try await self.ares.query(type: .SRV, name: name, replyParser: Ares.SRVQueryReplyParser.instance) 81 | } 82 | 83 | /// Lookup NAPTR records associated with `name`. 84 | /// 85 | /// - Parameters: 86 | /// - name: The name to resolve. 87 | /// 88 | /// - Returns: ``NAPTRRecord``s for the given name. 89 | public func queryNAPTR(name: String) async throws -> [NAPTRRecord] { 90 | try await self.ares.query(type: .NAPTR, name: name, replyParser: Ares.NAPTRQueryReplyParser.instance) 91 | } 92 | } 93 | 94 | extension QueryType { 95 | fileprivate var intValue: CInt { 96 | /// See `arpa/nameser.h`. 97 | switch self { 98 | case .A: 99 | return 1 100 | case .NS: 101 | return 2 102 | case .CNAME: 103 | return 5 104 | case .SOA: 105 | return 6 106 | case .PTR: 107 | return 12 108 | case .MX: 109 | return 15 110 | case .TXT: 111 | return 16 112 | case .AAAA: 113 | return 28 114 | case .SRV: 115 | return 33 116 | case .NAPTR: 117 | return 35 118 | } 119 | } 120 | } 121 | 122 | // MARK: - c-ares query wrapper 123 | 124 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 125 | final class Ares: Sendable { 126 | typealias QueryCallback = @convention(c) ( 127 | UnsafeMutableRawPointer?, CInt, CInt, UnsafeMutablePointer?, CInt 128 | ) -> Void 129 | 130 | private let channel: AresChannel 131 | private let queryProcessor: QueryProcessor 132 | 133 | init(options: AresOptions) throws { 134 | self.channel = try AresChannel(options: options) 135 | 136 | // Need to call `ares_process` or `ares_process_fd` for query callbacks to happen 137 | self.queryProcessor = QueryProcessor(channel: self.channel) 138 | self.queryProcessor.start() 139 | } 140 | 141 | func query( 142 | type: QueryType, 143 | name: String, 144 | replyParser: ReplyParser 145 | ) async throws -> ReplyParser.Reply { 146 | let channel = self.channel 147 | return try await withTaskCancellationHandler( 148 | operation: { 149 | try await withCheckedThrowingContinuation { continuation in 150 | let handler = QueryReplyHandler(parser: replyParser, continuation) 151 | 152 | // Wrap `handler` into a pointer so we can pass it to callback. The pointer will be deallocated in there later. 153 | let handlerPointer = UnsafeMutableRawPointer.allocate( 154 | byteCount: MemoryLayout.stride, 155 | alignment: MemoryLayout.alignment 156 | ) 157 | handlerPointer.initializeMemory(as: QueryReplyHandler.self, repeating: handler, count: 1) 158 | 159 | let queryCallback: QueryCallback = { arg, status, _, buf, len in 160 | guard let handlerPointer = arg else { 161 | preconditionFailure("'arg' is nil. This is a bug.") 162 | } 163 | 164 | let pointer = handlerPointer.assumingMemoryBound(to: QueryReplyHandler.self) 165 | let handler = pointer.pointee 166 | defer { 167 | pointer.deinitialize(count: 1) 168 | pointer.deallocate() 169 | } 170 | 171 | handler.handle(status: status, buffer: buf, length: len) 172 | } 173 | 174 | self.channel.withChannel { channel in 175 | ares_query(channel, name, DNSClass.IN.rawValue, type.intValue, queryCallback, handlerPointer) 176 | } 177 | } 178 | }, 179 | onCancel: { 180 | channel.withChannel { channel in 181 | ares_cancel(channel) 182 | } 183 | } 184 | ) 185 | } 186 | 187 | /// See `arpa/nameser.h`. 188 | private enum DNSClass: CInt { 189 | case IN = 1 190 | } 191 | } 192 | 193 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 194 | extension Ares { 195 | // TODO: implement this more nicely using NIO EventLoop? 196 | // See: 197 | // https://github.com/dimbleby/c-ares-resolver/blob/master/src/unix/eventloop.rs // ignore-unacceptable-language 198 | // https://github.com/dimbleby/rust-c-ares/blob/master/src/channel.rs // ignore-unacceptable-language 199 | // https://github.com/dimbleby/rust-c-ares/blob/master/examples/event-loop.rs // ignore-unacceptable-language 200 | final class QueryProcessor: @unchecked Sendable { 201 | static let defaultPollInterval: UInt64 = 10 * 1_000_000 // 10ms 202 | 203 | private let channel: AresChannel 204 | private let pollIntervalNanos: UInt64 205 | 206 | private let lock = NSLock() 207 | private var locked_pollingTask: Task? 208 | 209 | deinit { 210 | // No need to lock here as there can exist no more strong references to self. 211 | self.locked_pollingTask?.cancel() 212 | } 213 | 214 | init(channel: AresChannel, pollIntervalNanos: UInt64 = QueryProcessor.defaultPollInterval) { 215 | self.channel = channel 216 | self.pollIntervalNanos = pollIntervalNanos 217 | } 218 | 219 | /// Asks c-ares for the set of socket descriptors we are waiting on for the `ares_channel`'s pending queries 220 | /// then call `ares_process_fd` if any is ready for read and/or write. 221 | /// c-ares returns up to `ARES_GETSOCK_MAXNUM` socket descriptors only. If more are in use (unlikely) they are not reported back. 222 | func poll() { 223 | var socks = [ares_socket_t](repeating: ares_socket_t(), count: Int(ARES_GETSOCK_MAXNUM)) 224 | 225 | self.channel.withChannel { channel in 226 | // Indicates what actions (i.e., read/write) to wait for on the different sockets 227 | let bitmask = UInt32(ares_getsock(channel, &socks, ARES_GETSOCK_MAXNUM)) 228 | 229 | for (index, socket) in socks.enumerated() { 230 | let readableBit: UInt32 = 1 << UInt32(index) 231 | let readable = (bitmask & readableBit) != 0 232 | let writableBit = readableBit << UInt32(ARES_GETSOCK_MAXNUM) 233 | let writable = (bitmask & writableBit) != 0 234 | 235 | if readable || writable { 236 | // `ARES_SOCKET_BAD` instructs c-ares not to perform the action 237 | let readFD = readable ? socket : ARES_SOCKET_BAD 238 | let writeFD = writable ? socket : ARES_SOCKET_BAD 239 | ares_process_fd(channel, readFD, writeFD) 240 | } 241 | } 242 | } 243 | 244 | // Schedule next poll 245 | self.schedule() 246 | } 247 | 248 | func start() { 249 | self.schedule() 250 | } 251 | 252 | private func schedule() { 253 | self.lock.lock() 254 | defer { self.lock.unlock() } 255 | self.locked_pollingTask = Task { [weak self] in 256 | guard let s = self else { 257 | return 258 | } 259 | try await Task.sleep(nanoseconds: s.pollIntervalNanos) 260 | s.poll() 261 | } 262 | } 263 | } 264 | } 265 | 266 | // MARK: - c-ares query reply handler 267 | 268 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 269 | extension Ares { 270 | class QueryReplyHandler { 271 | private let _handler: (CInt, UnsafeMutablePointer?, CInt) -> Void 272 | 273 | init(parser: Parser, _ continuation: CheckedContinuation) { 274 | self._handler = { status, buffer, length in 275 | guard status == ARES_SUCCESS || status == ARES_ENODATA else { 276 | return continuation.resume(throwing: AsyncDNSResolver.Error(cAresCode: status)) 277 | } 278 | 279 | do { 280 | let reply = try parser.parse(buffer: buffer, length: length) 281 | continuation.resume(returning: reply) 282 | } catch { 283 | continuation.resume(throwing: error) 284 | } 285 | } 286 | } 287 | 288 | func handle(status: CInt, buffer: UnsafeMutablePointer?, length: CInt) { 289 | self._handler(status, buffer, length) 290 | } 291 | } 292 | } 293 | 294 | // MARK: - c-ares query reply parsers 295 | 296 | protocol AresQueryReplyParser { 297 | associatedtype Reply: Sendable 298 | 299 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> Reply 300 | } 301 | 302 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 303 | extension Ares { 304 | static let maxAddresses: Int = 32 305 | 306 | struct AQueryReplyParser: AresQueryReplyParser { 307 | static let instance = AQueryReplyParser() 308 | 309 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> [ARecord] { 310 | let addrttlsPointer = UnsafeMutablePointer.allocate(capacity: Ares.maxAddresses) 311 | defer { addrttlsPointer.deallocate() } 312 | let naddrttlsPointer = UnsafeMutablePointer.allocate(capacity: 1) 313 | defer { naddrttlsPointer.deallocate() } 314 | 315 | // Set a limit or else addrttl array won't be populated 316 | naddrttlsPointer.pointee = CInt(Ares.maxAddresses) 317 | 318 | let parseStatus = ares_parse_a_reply(buffer, length, nil, addrttlsPointer, naddrttlsPointer) 319 | 320 | switch parseStatus { 321 | case ARES_SUCCESS: 322 | let records = Array(UnsafeBufferPointer(start: addrttlsPointer, count: Int(naddrttlsPointer.pointee))) 323 | .map { ARecord($0) } 324 | return records 325 | 326 | case ARES_ENODATA: 327 | return [] 328 | 329 | default: 330 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse A query reply") 331 | } 332 | } 333 | } 334 | 335 | struct AAAAQueryReplyParser: AresQueryReplyParser { 336 | static let instance = AAAAQueryReplyParser() 337 | 338 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> [AAAARecord] { 339 | let addrttlsPointer = UnsafeMutablePointer.allocate(capacity: Ares.maxAddresses) 340 | defer { addrttlsPointer.deallocate() } 341 | let naddrttlsPointer = UnsafeMutablePointer.allocate(capacity: 1) 342 | defer { naddrttlsPointer.deallocate() } 343 | 344 | // Set a limit or else addrttl array won't be populated 345 | naddrttlsPointer.pointee = CInt(Ares.maxAddresses) 346 | 347 | let parseStatus = ares_parse_aaaa_reply(buffer, length, nil, addrttlsPointer, naddrttlsPointer) 348 | 349 | switch parseStatus { 350 | case ARES_SUCCESS: 351 | let records = Array(UnsafeBufferPointer(start: addrttlsPointer, count: Int(naddrttlsPointer.pointee))) 352 | .map { AAAARecord($0) } 353 | return records 354 | 355 | case ARES_ENODATA: 356 | return [] 357 | 358 | default: 359 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse AAAA query reply") 360 | } 361 | } 362 | } 363 | 364 | struct NSQueryReplyParser: AresQueryReplyParser { 365 | static let instance = NSQueryReplyParser() 366 | 367 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> NSRecord { 368 | let hostentPtrPtr = UnsafeMutablePointer?>.allocate(capacity: 1) 369 | defer { hostentPtrPtr.deallocate() } 370 | 371 | let parseStatus = ares_parse_ns_reply(buffer, length, hostentPtrPtr) 372 | 373 | switch parseStatus { 374 | case ARES_SUCCESS: 375 | guard let hostent = hostentPtrPtr.pointee?.pointee else { 376 | return NSRecord(nameservers: []) 377 | } 378 | 379 | let nameServers = toStringArray(hostent.h_aliases) 380 | return NSRecord(nameservers: nameServers ?? []) 381 | 382 | case ARES_ENODATA: 383 | return NSRecord(nameservers: []) 384 | 385 | default: 386 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse NS query reply") 387 | } 388 | } 389 | } 390 | 391 | struct CNAMEQueryReplyParser: AresQueryReplyParser { 392 | static let instance = CNAMEQueryReplyParser() 393 | 394 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> String? { 395 | let hostentPtrPtr = UnsafeMutablePointer?>.allocate(capacity: 1) 396 | defer { hostentPtrPtr.deallocate() } 397 | 398 | let parseStatus = ares_parse_a_reply(buffer, length, hostentPtrPtr, nil, nil) 399 | 400 | switch parseStatus { 401 | case ARES_SUCCESS: 402 | guard let hostent = hostentPtrPtr.pointee?.pointee else { 403 | return nil 404 | } 405 | return String(cString: hostent.h_name) 406 | 407 | case ARES_ENODATA: 408 | return nil 409 | default: 410 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse CNAME query reply") 411 | } 412 | } 413 | } 414 | 415 | struct SOAQueryReplyParser: AresQueryReplyParser { 416 | static let instance = SOAQueryReplyParser() 417 | 418 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> SOARecord? { 419 | let soaReplyPtrPtr = UnsafeMutablePointer?>.allocate(capacity: 1) 420 | defer { soaReplyPtrPtr.deallocate() } 421 | 422 | let parseStatus = ares_parse_soa_reply(buffer, length, soaReplyPtrPtr) 423 | switch parseStatus { 424 | case ARES_SUCCESS: 425 | guard let soaReply = soaReplyPtrPtr.pointee?.pointee else { 426 | return nil 427 | } 428 | 429 | return SOARecord( 430 | mname: soaReply.nsname.map { String(cString: $0) }, 431 | rname: soaReply.hostmaster.map { String(cString: $0) }, 432 | serial: soaReply.serial, 433 | refresh: soaReply.refresh, 434 | retry: soaReply.retry, 435 | expire: soaReply.expire, 436 | ttl: soaReply.minttl 437 | ) 438 | 439 | case ARES_ENODATA: 440 | return nil 441 | 442 | default: 443 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse SOA query reply") 444 | } 445 | } 446 | } 447 | 448 | struct PTRQueryReplyParser: AresQueryReplyParser { 449 | static let instance = PTRQueryReplyParser() 450 | 451 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> PTRRecord { 452 | let dummyAddrPointer = UnsafeMutablePointer.allocate(capacity: 1) 453 | defer { dummyAddrPointer.deallocate() } 454 | let hostentPtrPtr = UnsafeMutablePointer?>.allocate(capacity: 1) 455 | defer { hostentPtrPtr.deallocate() } 456 | 457 | let parseStatus = ares_parse_ptr_reply( 458 | buffer, 459 | length, 460 | dummyAddrPointer, 461 | INET_ADDRSTRLEN, 462 | AF_INET, 463 | hostentPtrPtr 464 | ) 465 | 466 | switch parseStatus { 467 | case ARES_SUCCESS: 468 | guard let hostent = hostentPtrPtr.pointee?.pointee else { 469 | return PTRRecord(names: []) 470 | } 471 | 472 | let hostnames = toStringArray(hostent.h_aliases) 473 | return PTRRecord(names: hostnames ?? []) 474 | 475 | case ARES_ENODATA: 476 | return PTRRecord(names: []) 477 | 478 | default: 479 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse PTR query record") 480 | } 481 | } 482 | } 483 | 484 | struct MXQueryReplyParser: AresQueryReplyParser { 485 | static let instance = MXQueryReplyParser() 486 | 487 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> [MXRecord] { 488 | let mxsPointer = UnsafeMutablePointer?>.allocate(capacity: 1) 489 | defer { mxsPointer.deallocate() } 490 | 491 | let parseStatus = ares_parse_mx_reply(buffer, length, mxsPointer) 492 | switch parseStatus { 493 | case ARES_SUCCESS: 494 | var mxRecords = [MXRecord]() 495 | var mxRecordOptional = mxsPointer.pointee?.pointee 496 | while let mxRecord = mxRecordOptional { 497 | mxRecords.append( 498 | MXRecord( 499 | host: String(cString: mxRecord.host), 500 | priority: mxRecord.priority 501 | ) 502 | ) 503 | mxRecordOptional = mxRecord.next?.pointee 504 | } 505 | return mxRecords 506 | 507 | case ARES_ENODATA: 508 | return [] 509 | 510 | default: 511 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse MX query record") 512 | } 513 | } 514 | } 515 | 516 | struct TXTQueryReplyParser: AresQueryReplyParser { 517 | static let instance = TXTQueryReplyParser() 518 | 519 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> [TXTRecord] { 520 | let txtsPointer = UnsafeMutablePointer?>.allocate(capacity: 1) 521 | defer { txtsPointer.deallocate() } 522 | 523 | let parseStatus = ares_parse_txt_reply(buffer, length, txtsPointer) 524 | 525 | switch parseStatus { 526 | case ARES_SUCCESS: 527 | var txtRecords = [TXTRecord]() 528 | var txtRecordOptional = txtsPointer.pointee?.pointee 529 | while let txtRecord = txtRecordOptional { 530 | txtRecords.append( 531 | TXTRecord( 532 | txt: String(cString: txtRecord.txt) 533 | ) 534 | ) 535 | txtRecordOptional = txtRecord.next?.pointee 536 | } 537 | return txtRecords 538 | 539 | case ARES_ENODATA: 540 | return [] 541 | 542 | default: 543 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse TXT query reply") 544 | } 545 | } 546 | } 547 | 548 | struct SRVQueryReplyParser: AresQueryReplyParser { 549 | static let instance = SRVQueryReplyParser() 550 | 551 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> [SRVRecord] { 552 | let replyPointer = UnsafeMutablePointer?>.allocate(capacity: 1) 553 | defer { replyPointer.deallocate() } 554 | 555 | let parseStatus = ares_parse_srv_reply(buffer, length, replyPointer) 556 | 557 | switch parseStatus { 558 | case ARES_SUCCESS: 559 | var srvRecords = [SRVRecord]() 560 | var srvRecordOptional = replyPointer.pointee?.pointee 561 | while let srvRecord = srvRecordOptional { 562 | srvRecords.append( 563 | SRVRecord( 564 | host: String(cString: srvRecord.host), 565 | port: srvRecord.port, 566 | weight: srvRecord.weight, 567 | priority: srvRecord.priority 568 | ) 569 | ) 570 | srvRecordOptional = srvRecord.next?.pointee 571 | } 572 | return srvRecords 573 | 574 | case ARES_ENODATA: 575 | return [] 576 | 577 | default: 578 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse SRV query reply") 579 | } 580 | } 581 | } 582 | 583 | struct NAPTRQueryReplyParser: AresQueryReplyParser { 584 | static let instance = NAPTRQueryReplyParser() 585 | 586 | func parse(buffer: UnsafeMutablePointer?, length: CInt) throws -> [NAPTRRecord] { 587 | let naptrsPointer = UnsafeMutablePointer?>.allocate(capacity: 1) 588 | defer { naptrsPointer.deallocate() } 589 | 590 | let parseStatus = ares_parse_naptr_reply(buffer, length, naptrsPointer) 591 | 592 | switch parseStatus { 593 | case ARES_SUCCESS: 594 | var naptrRecords = [NAPTRRecord]() 595 | var naptrRecordOptional = naptrsPointer.pointee?.pointee 596 | while let naptrRecord = naptrRecordOptional { 597 | naptrRecords.append( 598 | NAPTRRecord( 599 | flags: String(cString: naptrRecord.flags), 600 | service: String(cString: naptrRecord.service), 601 | regExp: String(cString: naptrRecord.regexp), 602 | replacement: String(cString: naptrRecord.replacement), 603 | order: naptrRecord.order, 604 | preference: naptrRecord.preference 605 | ) 606 | ) 607 | naptrRecordOptional = naptrRecord.next?.pointee 608 | } 609 | return naptrRecords 610 | 611 | case ARES_ENODATA: 612 | return [] 613 | 614 | default: 615 | throw AsyncDNSResolver.Error(cAresCode: parseStatus, "failed to parse NAPTR query reply") 616 | } 617 | } 618 | } 619 | } 620 | 621 | // MARK: - helpers 622 | 623 | private func toStringArray(_ arrayPointer: UnsafeMutablePointer?>?) -> [String]? { 624 | guard let arrayPointer = arrayPointer else { 625 | return nil 626 | } 627 | 628 | var result = [String]() 629 | var stringPointer = arrayPointer 630 | while let ptr = stringPointer.pointee { 631 | result.append(String(cString: ptr)) 632 | stringPointer = stringPointer.advanced(by: 1) 633 | } 634 | return result 635 | } 636 | 637 | extension IPAddress.IPv4 { 638 | init(_ address: in_addr) { 639 | var address = address 640 | var addressBytes = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) 641 | inet_ntop(AF_INET, &address, &addressBytes, socklen_t(INET_ADDRSTRLEN)) 642 | self = .init(address: String(cString: addressBytes)) 643 | } 644 | } 645 | 646 | extension IPAddress.IPv6 { 647 | init(_ address: ares_in6_addr) { 648 | var address = address 649 | var addressBytes = [CChar](repeating: 0, count: Int(INET6_ADDRSTRLEN)) 650 | inet_ntop(AF_INET6, &address, &addressBytes, socklen_t(INET6_ADDRSTRLEN)) 651 | self = .init(address: String(cString: addressBytes)) 652 | } 653 | } 654 | 655 | extension ARecord { 656 | init(_ addrttl: ares_addrttl) { 657 | self.address = IPAddress.IPv4(addrttl.ipaddr) 658 | self.ttl = addrttl.ttl 659 | } 660 | } 661 | 662 | extension AAAARecord { 663 | init(_ addrttl: ares_addr6ttl) { 664 | self.address = IPAddress.IPv6(addrttl.ip6addr) 665 | self.ttl = addrttl.ttl 666 | } 667 | } 668 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/c-ares/Errors_c-ares.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import CAsyncDNSResolver 16 | 17 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 18 | extension AsyncDNSResolver.Error { 19 | /// Create an ``AsyncDNSResolver/AsyncDNSResolver/Error`` from c-ares error code. 20 | init(cAresCode: Int32, _ description: String = "") { 21 | self.message = description 22 | self.source = CAresError(code: Int(cAresCode)) 23 | 24 | switch cAresCode { 25 | case ARES_EFORMERR, ARES_EBADQUERY, ARES_EBADNAME, ARES_EBADFAMILY, ARES_EBADFLAGS: 26 | self.code = .badQuery 27 | case ARES_EBADRESP: 28 | self.code = .badResponse 29 | case ARES_ECONNREFUSED: 30 | self.code = .connectionRefused 31 | case ARES_ETIMEOUT: 32 | self.code = .timeout 33 | default: 34 | self.code = .internalError 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/dnssd/DNSResolver_dnssd.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2023-2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #if canImport(Darwin) 16 | import dnssd 17 | 18 | /// ``DNSResolver`` implementation backed by dnssd framework. 19 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 20 | public struct DNSSDDNSResolver: DNSResolver, Sendable { 21 | let dnssd: DNSSD 22 | 23 | init() { 24 | self.dnssd = DNSSD() 25 | } 26 | 27 | /// See ``DNSResolver/queryA(name:)``. 28 | public func queryA(name: String) async throws -> [ARecord] { 29 | try await self.dnssd.query(type: .A, name: name, replyHandler: DNSSD.AQueryReplyHandler.instance) 30 | } 31 | 32 | /// See ``DNSResolver/queryAAAA(name:)``. 33 | public func queryAAAA(name: String) async throws -> [AAAARecord] { 34 | try await self.dnssd.query(type: .AAAA, name: name, replyHandler: DNSSD.AAAAQueryReplyHandler.instance) 35 | } 36 | 37 | /// See ``DNSResolver/queryNS(name:)``. 38 | public func queryNS(name: String) async throws -> NSRecord { 39 | try await self.dnssd.query(type: .NS, name: name, replyHandler: DNSSD.NSQueryReplyHandler.instance) 40 | } 41 | 42 | /// See ``DNSResolver/queryCNAME(name:)``. 43 | public func queryCNAME(name: String) async throws -> String? { 44 | try await self.dnssd.query(type: .CNAME, name: name, replyHandler: DNSSD.CNAMEQueryReplyHandler.instance) 45 | } 46 | 47 | /// See ``DNSResolver/querySOA(name:)``. 48 | public func querySOA(name: String) async throws -> SOARecord? { 49 | try await self.dnssd.query(type: .SOA, name: name, replyHandler: DNSSD.SOAQueryReplyHandler.instance) 50 | } 51 | 52 | /// See ``DNSResolver/queryPTR(name:)``. 53 | public func queryPTR(name: String) async throws -> PTRRecord { 54 | try await self.dnssd.query(type: .PTR, name: name, replyHandler: DNSSD.PTRQueryReplyHandler.instance) 55 | } 56 | 57 | /// See ``DNSResolver/queryMX(name:)``. 58 | public func queryMX(name: String) async throws -> [MXRecord] { 59 | try await self.dnssd.query(type: .MX, name: name, replyHandler: DNSSD.MXQueryReplyHandler.instance) 60 | } 61 | 62 | /// See ``DNSResolver/queryTXT(name:)``. 63 | public func queryTXT(name: String) async throws -> [TXTRecord] { 64 | try await self.dnssd.query(type: .TXT, name: name, replyHandler: DNSSD.TXTQueryReplyHandler.instance) 65 | } 66 | 67 | /// See ``DNSResolver/querySRV(name:)``. 68 | public func querySRV(name: String) async throws -> [SRVRecord] { 69 | try await self.dnssd.query(type: .SRV, name: name, replyHandler: DNSSD.SRVQueryReplyHandler.instance) 70 | } 71 | } 72 | 73 | extension QueryType { 74 | fileprivate var kDNSServiceType: Int { 75 | switch self { 76 | case .A: 77 | return kDNSServiceType_A 78 | case .NS: 79 | return kDNSServiceType_NS 80 | case .CNAME: 81 | return kDNSServiceType_CNAME 82 | case .SOA: 83 | return kDNSServiceType_SOA 84 | case .PTR: 85 | return kDNSServiceType_PTR 86 | case .MX: 87 | return kDNSServiceType_MX 88 | case .TXT: 89 | return kDNSServiceType_TXT 90 | case .AAAA: 91 | return kDNSServiceType_AAAA 92 | case .SRV: 93 | return kDNSServiceType_SRV 94 | case .NAPTR: 95 | return kDNSServiceType_NAPTR 96 | } 97 | } 98 | } 99 | 100 | // MARK: - dnssd query wrapper 101 | 102 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 103 | struct DNSSD: Sendable { 104 | // Reference: https://gist.github.com/fikeminkel/a9c4bc4d0348527e8df3690e242038d3 105 | func query( 106 | type: QueryType, 107 | name: String, 108 | replyHandler: ReplyHandler 109 | ) async throws -> ReplyHandler.Reply { 110 | let recordStream = AsyncThrowingStream { continuation in 111 | let handler = QueryReplyHandler(handler: replyHandler, continuation) 112 | 113 | // Wrap `handler` into a pointer so we can pass it to DNSServiceQueryRecord 114 | let handlerPointer = UnsafeMutableRawPointer.allocate( 115 | byteCount: MemoryLayout.stride, 116 | alignment: MemoryLayout.alignment 117 | ) 118 | 119 | handlerPointer.initializeMemory(as: QueryReplyHandler.self, repeating: handler, count: 1) 120 | 121 | // The handler might be called multiple times so don't deallocate inside `callback` 122 | defer { 123 | let pointer = handlerPointer.assumingMemoryBound(to: QueryReplyHandler.self) 124 | pointer.deinitialize(count: 1) 125 | pointer.deallocate() 126 | } 127 | 128 | // This is called once per record received 129 | let callback: DNSServiceQueryRecordReply = { _, _, _, errorCode, _, _, _, rdlen, rdata, _, context in 130 | guard let handlerPointer = context else { 131 | preconditionFailure("'context' is nil. This is a bug.") 132 | } 133 | 134 | let pointer = handlerPointer.assumingMemoryBound(to: QueryReplyHandler.self) 135 | let handler = pointer.pointee 136 | 137 | // This parses a record then adds it to the stream 138 | handler.handleRecord(errorCode: errorCode, data: rdata, length: rdlen) 139 | } 140 | 141 | let serviceRefPtr = UnsafeMutablePointer.allocate(capacity: 1) 142 | defer { serviceRefPtr.deallocate() } 143 | 144 | // Run the query 145 | let _code = DNSServiceQueryRecord( 146 | serviceRefPtr, 147 | kDNSServiceFlagsTimeout, 148 | 0, 149 | name, 150 | UInt16(type.kDNSServiceType), 151 | UInt16(kDNSServiceClass_IN), 152 | callback, 153 | handlerPointer 154 | ) 155 | 156 | // Check if query completed successfully 157 | guard _code == kDNSServiceErr_NoError else { 158 | return continuation.finish(throwing: AsyncDNSResolver.Error(dnssdCode: _code)) 159 | } 160 | 161 | // Read reply from the socket (blocking) then call reply handler 162 | DNSServiceProcessResult(serviceRefPtr.pointee) 163 | DNSServiceRefDeallocate(serviceRefPtr.pointee) 164 | 165 | // Streaming done 166 | continuation.finish() 167 | } 168 | 169 | // Build reply using records received 170 | let records = try await recordStream.reduce(into: []) { partial, record in 171 | partial.append(record) 172 | } 173 | 174 | return try replyHandler.generateReply(records: records) 175 | } 176 | } 177 | 178 | // MARK: - dnssd query reply handler 179 | 180 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 181 | extension DNSSD { 182 | class QueryReplyHandler { 183 | private let _handleRecord: (DNSServiceErrorType, UnsafeRawPointer?, UInt16) -> Void 184 | 185 | init( 186 | handler: Handler, 187 | _ continuation: AsyncThrowingStream.Continuation 188 | ) { 189 | self._handleRecord = { errorCode, _data, _length in 190 | let data: UnsafeRawPointer? 191 | let length: UInt16 192 | 193 | switch Int(errorCode) { 194 | case kDNSServiceErr_NoError: 195 | data = _data 196 | length = _length 197 | case kDNSServiceErr_Timeout: 198 | // DNSSD doesn't give up until it has answer or it times out. If it times out assume 199 | // no answer is available, in which case `data` will be `nil` and parsers will deal 200 | // with empty responses appropriately. 201 | data = nil 202 | length = 0 203 | default: 204 | return continuation.finish(throwing: AsyncDNSResolver.Error(dnssdCode: errorCode)) 205 | } 206 | 207 | do { 208 | if let record = try handler.parseRecord(data: data, length: length) { 209 | continuation.yield(record) 210 | } else { 211 | continuation.finish() 212 | } 213 | } catch { 214 | continuation.finish(throwing: error) 215 | } 216 | } 217 | } 218 | 219 | func handleRecord(errorCode: DNSServiceErrorType, data: UnsafeRawPointer?, length: UInt16) { 220 | self._handleRecord(errorCode, data, length) 221 | } 222 | } 223 | } 224 | 225 | // MARK: - dnssd query reply handlers 226 | 227 | protocol DNSSDQueryReplyHandler { 228 | associatedtype Record: Sendable 229 | associatedtype Reply 230 | 231 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> Record? 232 | 233 | func generateReply(records: [Record]) throws -> Reply 234 | } 235 | 236 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 237 | extension DNSSD { 238 | // Reference: https://github.com/orlandos-nl/DNSClient/blob/master/Sources/DNSClient/Messages/Message.swift // // ignore-unacceptable-language 239 | 240 | struct AQueryReplyHandler: DNSSDQueryReplyHandler { 241 | static let instance = AQueryReplyHandler() 242 | 243 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> ARecord? { 244 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 245 | return nil 246 | } 247 | 248 | guard length >= MemoryLayout.size else { 249 | throw AsyncDNSResolver.Error(code: .badResponse) 250 | } 251 | 252 | var parsedAddressBytes = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) 253 | inet_ntop(AF_INET, ptr, &parsedAddressBytes, socklen_t(INET_ADDRSTRLEN)) 254 | let parsedAddress = String(cString: parsedAddressBytes) 255 | return ARecord(address: .init(address: parsedAddress), ttl: nil) 256 | } 257 | 258 | func generateReply(records: [ARecord]) throws -> [ARecord] { 259 | records 260 | } 261 | } 262 | 263 | struct AAAAQueryReplyHandler: DNSSDQueryReplyHandler { 264 | static let instance = AAAAQueryReplyHandler() 265 | 266 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> AAAARecord? { 267 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 268 | return nil 269 | } 270 | 271 | guard length >= MemoryLayout.size else { 272 | throw AsyncDNSResolver.Error(code: .badResponse) 273 | } 274 | 275 | var parsedAddressBytes = [CChar](repeating: 0, count: Int(INET6_ADDRSTRLEN)) 276 | inet_ntop(AF_INET6, ptr, &parsedAddressBytes, socklen_t(INET6_ADDRSTRLEN)) 277 | let parsedAddress = String(cString: parsedAddressBytes) 278 | return AAAARecord(address: .init(address: parsedAddress), ttl: nil) 279 | } 280 | 281 | func generateReply(records: [AAAARecord]) throws -> [AAAARecord] { 282 | records 283 | } 284 | } 285 | 286 | struct NSQueryReplyHandler: DNSSDQueryReplyHandler { 287 | static let instance = NSQueryReplyHandler() 288 | 289 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> String? { 290 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 291 | return nil 292 | } 293 | 294 | let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) 295 | var buffer = Array(bufferPtr)[...] 296 | 297 | guard let nameserver = self.readName(&buffer) else { 298 | throw AsyncDNSResolver.Error(code: .badResponse, message: "failed to read name") 299 | } 300 | 301 | return nameserver 302 | } 303 | 304 | func generateReply(records: [String]) throws -> NSRecord { 305 | NSRecord(nameservers: records) 306 | } 307 | } 308 | 309 | struct CNAMEQueryReplyHandler: DNSSDQueryReplyHandler { 310 | static let instance = CNAMEQueryReplyHandler() 311 | 312 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> String? { 313 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 314 | return nil 315 | } 316 | 317 | let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) 318 | var buffer = Array(bufferPtr)[...] 319 | 320 | guard let cname = self.readName(&buffer) else { 321 | throw AsyncDNSResolver.Error(code: .badResponse, message: "failed to read name") 322 | } 323 | 324 | return cname 325 | } 326 | 327 | func generateReply(records: [String]) throws -> String? { 328 | try self.ensureAtMostOne(records: records) 329 | } 330 | } 331 | 332 | struct SOAQueryReplyHandler: DNSSDQueryReplyHandler { 333 | static let instance = SOAQueryReplyHandler() 334 | 335 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> SOARecord? { 336 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 337 | return nil 338 | } 339 | 340 | let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) 341 | var buffer = Array(bufferPtr)[...] 342 | 343 | guard let mname = self.readName(&buffer), 344 | let rname = self.readName(&buffer), 345 | let serial = buffer.readInteger(as: UInt32.self), 346 | let refresh = buffer.readInteger(as: UInt32.self), 347 | let retry = buffer.readInteger(as: UInt32.self), 348 | let expire = buffer.readInteger(as: UInt32.self), 349 | let ttl = buffer.readInteger(as: UInt32.self) 350 | else { 351 | throw AsyncDNSResolver.Error(code: .badResponse) 352 | } 353 | 354 | return SOARecord( 355 | mname: mname, 356 | rname: rname, 357 | serial: serial, 358 | refresh: refresh, 359 | retry: retry, 360 | expire: expire, 361 | ttl: ttl 362 | ) 363 | } 364 | 365 | func generateReply(records: [SOARecord]) throws -> SOARecord? { 366 | try self.ensureAtMostOne(records: records) 367 | } 368 | } 369 | 370 | struct PTRQueryReplyHandler: DNSSDQueryReplyHandler { 371 | static let instance = PTRQueryReplyHandler() 372 | 373 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> String? { 374 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 375 | return nil 376 | } 377 | 378 | let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) 379 | var buffer = Array(bufferPtr)[...] 380 | 381 | guard let name = self.readName(&buffer) else { 382 | throw AsyncDNSResolver.Error(code: .badResponse, message: "failed to read name") 383 | } 384 | 385 | return name 386 | } 387 | 388 | func generateReply(records: [String]) throws -> PTRRecord { 389 | PTRRecord(names: records) 390 | } 391 | } 392 | 393 | struct MXQueryReplyHandler: DNSSDQueryReplyHandler { 394 | static let instance = MXQueryReplyHandler() 395 | 396 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> MXRecord? { 397 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 398 | return nil 399 | } 400 | 401 | let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) 402 | var buffer = Array(bufferPtr)[...] 403 | 404 | guard let priority = buffer.readInteger(as: UInt16.self), 405 | let host = self.readName(&buffer) 406 | else { 407 | throw AsyncDNSResolver.Error(code: .badResponse) 408 | } 409 | 410 | return MXRecord( 411 | host: host, 412 | priority: priority 413 | ) 414 | } 415 | 416 | func generateReply(records: [MXRecord]) throws -> [MXRecord] { 417 | records 418 | } 419 | } 420 | 421 | struct TXTQueryReplyHandler: DNSSDQueryReplyHandler { 422 | static let instance = TXTQueryReplyHandler() 423 | 424 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> TXTRecord? { 425 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 426 | return nil 427 | } 428 | let txt = String(cString: ptr.advanced(by: 1)) 429 | return TXTRecord(txt: txt) 430 | } 431 | 432 | func generateReply(records: [TXTRecord]) throws -> [TXTRecord] { 433 | records 434 | } 435 | } 436 | 437 | struct SRVQueryReplyHandler: DNSSDQueryReplyHandler { 438 | static let instance = SRVQueryReplyHandler() 439 | 440 | func parseRecord(data: UnsafeRawPointer?, length: UInt16) throws -> SRVRecord? { 441 | guard let ptr = data?.assumingMemoryBound(to: UInt8.self) else { 442 | return nil 443 | } 444 | 445 | let bufferPtr = UnsafeBufferPointer(start: ptr, count: Int(length)) 446 | var buffer = Array(bufferPtr)[...] 447 | 448 | guard let priority = buffer.readInteger(as: UInt16.self), 449 | let weight = buffer.readInteger(as: UInt16.self), 450 | let port = buffer.readInteger(as: UInt16.self), 451 | let host = self.readName(&buffer) 452 | else { 453 | throw AsyncDNSResolver.Error(code: .badResponse) 454 | } 455 | 456 | return SRVRecord( 457 | host: host, 458 | port: port, 459 | weight: weight, 460 | priority: priority 461 | ) 462 | } 463 | 464 | func generateReply(records: [SRVRecord]) throws -> [SRVRecord] { 465 | records 466 | } 467 | } 468 | } 469 | 470 | extension DNSSDQueryReplyHandler { 471 | func readName(_ buffer: inout ArraySlice) -> String? { 472 | var parts: [String] = [] 473 | while let length = buffer.readInteger(as: UInt8.self), 474 | length > 0, 475 | let part = buffer.readString(length: Int(length)) 476 | { 477 | parts.append(part) 478 | } 479 | 480 | return parts.isEmpty ? nil : parts.joined(separator: ".") 481 | } 482 | 483 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 484 | func ensureAtMostOne(records: [R]) throws -> R? { 485 | guard records.count <= 1 else { 486 | throw AsyncDNSResolver.Error(code: .badResponse, message: "expected 1 record but got \(records.count)") 487 | } 488 | 489 | return records.first 490 | } 491 | } 492 | 493 | extension ArraySlice { 494 | mutating func readInteger(as: T.Type = T.self) -> T? { 495 | let size = MemoryLayout.size 496 | guard self.count >= size else { return nil } 497 | 498 | let value = self.withUnsafeBytes { pointer in 499 | var value = T.zero 500 | Swift.withUnsafeMutableBytes(of: &value) { valuePointer in 501 | valuePointer.copyMemory(from: UnsafeRawBufferPointer(rebasing: pointer[.. String? { 511 | guard self.count >= length else { return nil } 512 | 513 | let prefix = self.prefix(length) 514 | self = self.dropFirst(length) 515 | return String(decoding: prefix, as: UTF8.self) 516 | } 517 | } 518 | #endif 519 | -------------------------------------------------------------------------------- /Sources/AsyncDNSResolver/dnssd/Errors_dnssd.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #if canImport(Darwin) 16 | import dnssd 17 | 18 | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) 19 | extension AsyncDNSResolver.Error { 20 | /// Create an ``AsyncDNSResolver/AsyncDNSResolver/Error`` from a DNSSD error code. 21 | init(dnssdCode code: Int32, _ description: String = "") { 22 | self.message = description 23 | self.source = DNSSDError(code: Int(code)) 24 | 25 | switch Int(code) { 26 | case kDNSServiceErr_BadFlags, kDNSServiceErr_BadParam, kDNSServiceErr_Invalid: 27 | self.code = .badQuery 28 | case kDNSServiceErr_Refused: 29 | self.code = .connectionRefused 30 | case kDNSServiceErr_Timeout: 31 | self.code = .timeout 32 | default: 33 | self.code = .internalError 34 | } 35 | } 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /Sources/CAsyncDNSResolver/include/CAsyncDNSResolver.h: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2023 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #ifndef C_ASYNC_RESOLVER_H 16 | #define C_ASYNC_RESOLVER_H 17 | 18 | #include // inet_ntop 19 | #include // hostent 20 | 21 | #include "ares_build.h" 22 | #include "ares_config.h" 23 | #include "../c-ares/include/ares.h" 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /Sources/CAsyncDNSResolver/include/ares_build.h: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #ifndef __CARES_BUILD_H 16 | #define __CARES_BUILD_H 17 | 18 | #define CARES_TYPEOF_ARES_SOCKLEN_T socklen_t 19 | #define CARES_TYPEOF_ARES_SSIZE_T ssize_t 20 | 21 | /* Prefix names with CARES_ to make sure they don't conflict with other config.h 22 | * files. We need to include some dependent headers that may be system specific 23 | * for C-Ares */ 24 | #define CARES_HAVE_SYS_TYPES_H 25 | #define CARES_HAVE_SYS_SOCKET_H 26 | /* #undef CARES_HAVE_WINDOWS_H */ 27 | /* #undef CARES_HAVE_WS2TCPIP_H */ 28 | /* #undef CARES_HAVE_WINSOCK2_H */ 29 | /* #undef CARES_HAVE_WINDOWS_H */ 30 | 31 | #ifdef CARES_HAVE_SYS_TYPES_H 32 | # include 33 | #endif 34 | 35 | #ifdef CARES_HAVE_SYS_SOCKET_H 36 | # include 37 | #endif 38 | 39 | #ifdef CARES_HAVE_WINSOCK2_H 40 | # include 41 | #endif 42 | 43 | #ifdef CARES_HAVE_WS2TCPIP_H 44 | # include 45 | #endif 46 | 47 | #ifdef CARES_HAVE_WINDOWS_H 48 | # include 49 | #endif 50 | 51 | 52 | typedef CARES_TYPEOF_ARES_SOCKLEN_T ares_socklen_t; 53 | typedef CARES_TYPEOF_ARES_SSIZE_T ares_ssize_t; 54 | 55 | #endif /* __CARES_BUILD_H */ 56 | -------------------------------------------------------------------------------- /Sources/CAsyncDNSResolver/include/ares_config.h: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | /* Generated from ares_config.h.cmake*/ 16 | 17 | /* Define if building universal (internal helper macro) */ 18 | #undef AC_APPLE_UNIVERSAL_BUILD 19 | 20 | /* define this if ares is built for a big endian system */ 21 | #undef ARES_BIG_ENDIAN 22 | 23 | /* when building as static part of libcurl */ 24 | #undef BUILDING_LIBCURL 25 | 26 | /* Defined for build that exposes internal static functions for testing. */ 27 | #undef CARES_EXPOSE_STATICS 28 | 29 | /* Defined for build with symbol hiding. */ 30 | #undef CARES_SYMBOL_HIDING 31 | 32 | /* Definition to make a library symbol externally visible. */ 33 | #undef CARES_SYMBOL_SCOPE_EXTERN 34 | 35 | /* Use resolver library to configure cares */ 36 | #undef CARES_USE_LIBRESOLV 37 | 38 | /* if a /etc/inet dir is being used */ 39 | #undef ETC_INET 40 | 41 | /* Define to the type of arg 2 for gethostname. */ 42 | #define GETHOSTNAME_TYPE_ARG2 size_t 43 | 44 | /* Define to the type qualifier of arg 1 for getnameinfo. */ 45 | #define GETNAMEINFO_QUAL_ARG1 46 | 47 | /* Define to the type of arg 1 for getnameinfo. */ 48 | #define GETNAMEINFO_TYPE_ARG1 struct sockaddr * 49 | 50 | /* Define to the type of arg 2 for getnameinfo. */ 51 | #define GETNAMEINFO_TYPE_ARG2 socklen_t 52 | 53 | /* Define to the type of args 4 and 6 for getnameinfo. */ 54 | #define GETNAMEINFO_TYPE_ARG46 socklen_t 55 | 56 | /* Define to the type of arg 7 for getnameinfo. */ 57 | #define GETNAMEINFO_TYPE_ARG7 int 58 | 59 | /* Specifies the number of arguments to getservbyport_r */ 60 | #define GETSERVBYPORT_R_ARGS 61 | 62 | /* Specifies the number of arguments to getservbyname_r */ 63 | #define GETSERVBYNAME_R_ARGS 64 | 65 | /* Define to 1 if you have AF_INET6. */ 66 | #define HAVE_AF_INET6 67 | 68 | /* Define to 1 if you have the header file. */ 69 | #define HAVE_ARPA_INET_H 70 | 71 | /* Define to 1 if you have the header file. */ 72 | #define HAVE_ARPA_NAMESER_COMPAT_H 73 | 74 | /* Define to 1 if you have the header file. */ 75 | #define HAVE_ARPA_NAMESER_H 76 | 77 | /* Define to 1 if you have the header file. */ 78 | #define HAVE_ASSERT_H 79 | 80 | /* Define to 1 if you have the `bitncmp' function. */ 81 | /* #undef HAVE_BITNCMP */ 82 | 83 | /* Define to 1 if bool is an available type. */ 84 | #define HAVE_BOOL_T 85 | 86 | /* Define to 1 if you have the clock_gettime function and monotonic timer. */ 87 | #define HAVE_CLOCK_GETTIME_MONOTONIC 88 | 89 | /* Define to 1 if you have the closesocket function. */ 90 | /* #undef HAVE_CLOSESOCKET */ 91 | 92 | /* Define to 1 if you have the CloseSocket camel case function. */ 93 | /* #undef HAVE_CLOSESOCKET_CAMEL */ 94 | 95 | /* Define to 1 if you have the connect function. */ 96 | #define HAVE_CONNECT 97 | 98 | /* define if the compiler supports basic C++11 syntax */ 99 | /* #undef HAVE_CXX11 */ 100 | 101 | /* Define to 1 if you have the header file. */ 102 | #define HAVE_DLFCN_H 103 | 104 | /* Define to 1 if you have the header file. */ 105 | #define HAVE_ERRNO_H 106 | 107 | /* Define to 1 if you have the fcntl function. */ 108 | #define HAVE_FCNTL 109 | 110 | /* Define to 1 if you have the header file. */ 111 | #define HAVE_FCNTL_H 112 | 113 | /* Define to 1 if you have a working fcntl O_NONBLOCK function. */ 114 | #define HAVE_FCNTL_O_NONBLOCK 115 | 116 | /* Define to 1 if you have the freeaddrinfo function. */ 117 | #define HAVE_FREEADDRINFO 118 | 119 | /* Define to 1 if you have a working getaddrinfo function. */ 120 | #define HAVE_GETADDRINFO 121 | 122 | /* Define to 1 if the getaddrinfo function is threadsafe. */ 123 | #define HAVE_GETADDRINFO_THREADSAFE 124 | 125 | /* Define to 1 if you have the getenv function. */ 126 | #define HAVE_GETENV 127 | 128 | /* Define to 1 if you have the gethostbyaddr function. */ 129 | #define HAVE_GETHOSTBYADDR 130 | 131 | /* Define to 1 if you have the gethostbyname function. */ 132 | #define HAVE_GETHOSTBYNAME 133 | 134 | /* Define to 1 if you have the gethostname function. */ 135 | #define HAVE_GETHOSTNAME 136 | 137 | /* Define to 1 if you have the getnameinfo function. */ 138 | #define HAVE_GETNAMEINFO 139 | 140 | /* Define to 1 if you have the getservbyport_r function. */ 141 | /* #undef HAVE_GETSERVBYPORT_R */ 142 | 143 | /* Define to 1 if you have the getservbyname_r function. */ 144 | /* #undef HAVE_GETSERVBYNAME_R */ 145 | 146 | /* Define to 1 if you have the `gettimeofday' function. */ 147 | #define HAVE_GETTIMEOFDAY 148 | 149 | /* Define to 1 if you have the `if_indextoname' function. */ 150 | #define HAVE_IF_INDEXTONAME 151 | 152 | /* Define to 1 if you have a IPv6 capable working inet_net_pton function. */ 153 | /* #undef HAVE_INET_NET_PTON */ 154 | 155 | /* Define to 1 if you have a IPv6 capable working inet_ntop function. */ 156 | #define HAVE_INET_NTOP 157 | 158 | /* Define to 1 if you have a IPv6 capable working inet_pton function. */ 159 | #define HAVE_INET_PTON 160 | 161 | /* Define to 1 if you have the header file. */ 162 | #define HAVE_INTTYPES_H 163 | 164 | /* Define to 1 if you have the ioctl function. */ 165 | #define HAVE_IOCTL 166 | 167 | /* Define to 1 if you have the ioctlsocket function. */ 168 | /* #undef HAVE_IOCTLSOCKET */ 169 | 170 | /* Define to 1 if you have the IoctlSocket camel case function. */ 171 | /* #undef HAVE_IOCTLSOCKET_CAMEL */ 172 | 173 | /* Define to 1 if you have a working IoctlSocket camel case FIONBIO function. 174 | */ 175 | /* #undef HAVE_IOCTLSOCKET_CAMEL_FIONBIO */ 176 | 177 | /* Define to 1 if you have a working ioctlsocket FIONBIO function. */ 178 | /* #undef HAVE_IOCTLSOCKET_FIONBIO */ 179 | 180 | /* Define to 1 if you have a working ioctl FIONBIO function. */ 181 | #define HAVE_IOCTL_FIONBIO 182 | 183 | /* Define to 1 if you have a working ioctl SIOCGIFADDR function. */ 184 | #define HAVE_IOCTL_SIOCGIFADDR 185 | 186 | /* Define to 1 if you have the `resolve' library (-lresolve). */ 187 | #undef HAVE_LIBRESOLV 188 | 189 | /* Define to 1 if you have the header file. */ 190 | #define HAVE_LIMITS_H 191 | 192 | /* if your compiler supports LL */ 193 | #define HAVE_LL 194 | 195 | /* Define to 1 if the compiler supports the 'long long' data type. */ 196 | #define HAVE_LONGLONG 197 | 198 | /* Define to 1 if you have the malloc.h header file. */ 199 | /* #undef HAVE_MALLOC_H */ 200 | 201 | /* Define to 1 if you have the memory.h header file. */ 202 | #define HAVE_MEMORY_H 203 | 204 | /* Define to 1 if you have the MSG_NOSIGNAL flag. */ 205 | /* #undef HAVE_MSG_NOSIGNAL */ 206 | 207 | /* Define to 1 if you have the header file. */ 208 | #define HAVE_NETDB_H 209 | 210 | /* Define to 1 if you have the header file. */ 211 | #define HAVE_NETINET_IN_H 212 | 213 | /* Define to 1 if you have the header file. */ 214 | #define HAVE_NETINET_TCP_H 215 | 216 | /* Define to 1 if you have the header file. */ 217 | #define HAVE_NET_IF_H 218 | 219 | /* Define to 1 if you have PF_INET6. */ 220 | #define HAVE_PF_INET6 221 | 222 | /* Define to 1 if you have the recv function. */ 223 | #define HAVE_RECV 224 | 225 | /* Define to 1 if you have the recvfrom function. */ 226 | #define HAVE_RECVFROM 227 | 228 | /* Define to 1 if you have the send function. */ 229 | #define HAVE_SEND 230 | 231 | /* Define to 1 if you have the setsockopt function. */ 232 | #define HAVE_SETSOCKOPT 233 | 234 | /* Define to 1 if you have a working setsockopt SO_NONBLOCK function. */ 235 | /* #undef HAVE_SETSOCKOPT_SO_NONBLOCK */ 236 | 237 | /* Define to 1 if you have the header file. */ 238 | #define HAVE_SIGNAL_H 239 | 240 | /* Define to 1 if sig_atomic_t is an available typedef. */ 241 | #define HAVE_SIG_ATOMIC_T 242 | 243 | /* Define to 1 if sig_atomic_t is already defined as volatile. */ 244 | /* #undef HAVE_SIG_ATOMIC_T_VOLATILE */ 245 | 246 | /* Define to 1 if your struct sockaddr_in6 has sin6_scope_id. */ 247 | #define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 248 | 249 | /* Define to 1 if you have the socket function. */ 250 | #define HAVE_SOCKET 251 | 252 | /* Define to 1 if you have the header file. */ 253 | /* #undef HAVE_SOCKET_H */ 254 | 255 | /* Define to 1 if you have the header file. */ 256 | #define HAVE_STDBOOL_H 257 | 258 | /* Define to 1 if you have the header file. */ 259 | #define HAVE_STDINT_H 260 | 261 | /* Define to 1 if you have the header file. */ 262 | #define HAVE_STDLIB_H 263 | 264 | /* Define to 1 if you have the strcasecmp function. */ 265 | #define HAVE_STRCASECMP 266 | 267 | /* Define to 1 if you have the strcmpi function. */ 268 | /* #undef HAVE_STRCMPI */ 269 | 270 | /* Define to 1 if you have the strdup function. */ 271 | #define HAVE_STRDUP 272 | 273 | /* Define to 1 if you have the stricmp function. */ 274 | /* #undef HAVE_STRICMP */ 275 | 276 | /* Define to 1 if you have the header file. */ 277 | #define HAVE_STRINGS_H 278 | 279 | /* Define to 1 if you have the header file. */ 280 | #define HAVE_STRING_H 281 | 282 | /* Define to 1 if you have the strncasecmp function. */ 283 | #define HAVE_STRNCASECMP 284 | 285 | /* Define to 1 if you have the strncmpi function. */ 286 | /* #undef HAVE_STRNCMPI */ 287 | 288 | /* Define to 1 if you have the strnicmp function. */ 289 | /* #undef HAVE_STRNICMP */ 290 | 291 | /* Define to 1 if you have the header file. */ 292 | /* #undef HAVE_STROPTS_H */ 293 | 294 | /* Define to 1 if you have struct addrinfo. */ 295 | #define HAVE_STRUCT_ADDRINFO 296 | 297 | /* Define to 1 if you have struct in6_addr. */ 298 | #define HAVE_STRUCT_IN6_ADDR 299 | 300 | /* Define to 1 if you have struct sockaddr_in6. */ 301 | #define HAVE_STRUCT_SOCKADDR_IN6 302 | 303 | /* if struct sockaddr_storage is defined */ 304 | #define HAVE_STRUCT_SOCKADDR_STORAGE 305 | 306 | /* Define to 1 if you have the timeval struct. */ 307 | #define HAVE_STRUCT_TIMEVAL 308 | 309 | /* Define to 1 if you have the header file. */ 310 | #define HAVE_SYS_IOCTL_H 311 | 312 | /* Define to 1 if you have the header file. */ 313 | #define HAVE_SYS_PARAM_H 314 | 315 | /* Define to 1 if you have the header file. */ 316 | #define HAVE_SYS_SELECT_H 317 | 318 | /* Define to 1 if you have the header file. */ 319 | #define HAVE_SYS_SOCKET_H 320 | 321 | /* Define to 1 if you have the header file. */ 322 | #define HAVE_SYS_STAT_H 323 | 324 | /* Define to 1 if you have the header file. */ 325 | #define HAVE_SYS_TIME_H 326 | 327 | /* Define to 1 if you have the header file. */ 328 | #define HAVE_SYS_TYPES_H 329 | 330 | /* Define to 1 if you have the header file. */ 331 | #define HAVE_SYS_UIO_H 332 | 333 | /* Define to 1 if you have the header file. */ 334 | #define HAVE_TIME_H 335 | 336 | /* Define to 1 if you have the header file. */ 337 | #define HAVE_UNISTD_H 338 | 339 | /* Define to 1 if you have the windows.h header file. */ 340 | /* #undef HAVE_WINDOWS_H */ 341 | 342 | /* Define to 1 if you have the winsock2.h header file. */ 343 | /* #undef HAVE_WINSOCK2_H */ 344 | 345 | /* Define to 1 if you have the winsock.h header file. */ 346 | /* #undef HAVE_WINSOCK_H */ 347 | 348 | /* Define to 1 if you have the writev function. */ 349 | #define HAVE_WRITEV 350 | 351 | /* Define to 1 if you have the ws2tcpip.h header file. */ 352 | /* #undef HAVE_WS2TCPIP_H */ 353 | 354 | /* Define to 1 if you have the __system_property_get function */ 355 | /* #undef HAVE___SYSTEM_PROPERTY_GET */ 356 | 357 | /* Define to 1 if you need the malloc.h header file even with stdlib.h */ 358 | /* #undef NEED_MALLOC_H */ 359 | 360 | /* Define to 1 if you need the memory.h header file even with stdlib.h */ 361 | /* #undef NEED_MEMORY_H */ 362 | 363 | /* a suitable file/device to read random data from */ 364 | /* #undef RANDOM_FILE */ 365 | 366 | /* Define to the type qualifier pointed by arg 5 for recvfrom. */ 367 | #define RECVFROM_QUAL_ARG5 368 | 369 | /* Define to the type of arg 1 for recvfrom. */ 370 | #define RECVFROM_TYPE_ARG1 int 371 | 372 | /* Define to the type pointed by arg 2 for recvfrom. */ 373 | #define RECVFROM_TYPE_ARG2 void * 374 | 375 | /* Define to 1 if the type pointed by arg 2 for recvfrom is void. */ 376 | #define RECVFROM_TYPE_ARG2_IS_VOID 0 377 | 378 | /* Define to the type of arg 3 for recvfrom. */ 379 | #define RECVFROM_TYPE_ARG3 size_t 380 | 381 | /* Define to the type of arg 4 for recvfrom. */ 382 | #define RECVFROM_TYPE_ARG4 int 383 | 384 | /* Define to the type pointed by arg 5 for recvfrom. */ 385 | #define RECVFROM_TYPE_ARG5 struct sockaddr * 386 | 387 | /* Define to 1 if the type pointed by arg 5 for recvfrom is void. */ 388 | #define RECVFROM_TYPE_ARG5_IS_VOID 0 389 | 390 | /* Define to the type pointed by arg 6 for recvfrom. */ 391 | #define RECVFROM_TYPE_ARG6 socklen_t * 392 | 393 | /* Define to 1 if the type pointed by arg 6 for recvfrom is void. */ 394 | #define RECVFROM_TYPE_ARG6_IS_VOID 0 395 | 396 | /* Define to the function return type for recvfrom. */ 397 | #define RECVFROM_TYPE_RETV ssize_t 398 | 399 | /* Define to the type of arg 1 for recv. */ 400 | #define RECV_TYPE_ARG1 int 401 | 402 | /* Define to the type of arg 2 for recv. */ 403 | #define RECV_TYPE_ARG2 void * 404 | 405 | /* Define to the type of arg 3 for recv. */ 406 | #define RECV_TYPE_ARG3 size_t 407 | 408 | /* Define to the type of arg 4 for recv. */ 409 | #define RECV_TYPE_ARG4 int 410 | 411 | /* Define to the function return type for recv. */ 412 | #define RECV_TYPE_RETV ssize_t 413 | 414 | /* Define as the return type of signal handlers (`int' or `void'). */ 415 | #define RETSIGTYPE 416 | 417 | /* Define to the type qualifier of arg 2 for send. */ 418 | #define SEND_QUAL_ARG2 419 | 420 | /* Define to the type of arg 1 for send. */ 421 | #define SEND_TYPE_ARG1 int 422 | 423 | /* Define to the type of arg 2 for send. */ 424 | #define SEND_TYPE_ARG2 void * 425 | 426 | /* Define to the type of arg 3 for send. */ 427 | #define SEND_TYPE_ARG3 size_t 428 | 429 | /* Define to the type of arg 4 for send. */ 430 | #define SEND_TYPE_ARG4 int 431 | 432 | /* Define to the function return type for send. */ 433 | #define SEND_TYPE_RETV ssize_t 434 | 435 | /* Define to 1 if you can safely include both and . */ 436 | #define TIME_WITH_SYS_TIME 437 | 438 | /* Define to disable non-blocking sockets. */ 439 | #undef USE_BLOCKING_SOCKETS 440 | 441 | /* Define to avoid automatic inclusion of winsock.h */ 442 | #undef WIN32_LEAN_AND_MEAN 443 | 444 | /* Type to use in place of in_addr_t when system does not provide it. */ 445 | #undef in_addr_t 446 | 447 | -------------------------------------------------------------------------------- /Tests/AsyncDNSResolverTests/c-ares/AresChannelTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2023 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import XCTest 16 | 17 | @testable import AsyncDNSResolver 18 | 19 | final class AresChannelTests: XCTestCase { 20 | func test_init() async throws { 21 | let options = AresOptions() 22 | 23 | let servers = ["[2001:4860:4860::8888]:53", "130.155.0.1:53"] 24 | options.setServers(servers) 25 | 26 | let sortlist = ["130.155.160.0/255.255.240.0", "130.155.0.0"] 27 | options.setSortlist(sortlist) 28 | 29 | guard let channel = try? AresChannel(options: options) else { 30 | return XCTFail("Channel not initialized") 31 | } 32 | guard let _ = channel.underlying else { 33 | return XCTFail("Underlying ares_channel is nil") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/AsyncDNSResolverTests/c-ares/AresErrorTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import CAsyncDNSResolver 16 | import XCTest 17 | 18 | @testable import AsyncDNSResolver 19 | 20 | final class AresErrorTests: XCTestCase { 21 | func test_initFromCode() { 22 | let inputs: [Int32: AsyncDNSResolver.Error.Code] = [ 23 | ARES_EFORMERR: .badQuery, 24 | ARES_EBADQUERY: .badQuery, 25 | ARES_EBADNAME: .badQuery, 26 | ARES_EBADFAMILY: .badQuery, 27 | ARES_EBADFLAGS: .badQuery, 28 | ARES_EBADRESP: .badResponse, 29 | ARES_ECONNREFUSED: .connectionRefused, 30 | ARES_ETIMEOUT: .timeout, 31 | ] 32 | 33 | for (code, expected) in inputs { 34 | let error = AsyncDNSResolver.Error(cAresCode: code, "some error") 35 | XCTAssertEqual(error.code, expected) 36 | XCTAssertEqual( 37 | error.message, 38 | "some error", 39 | "Expected description to be \"some error\", got \(error.message)" 40 | ) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/AsyncDNSResolverTests/c-ares/AresOptionsTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2023 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import CAsyncDNSResolver 16 | import XCTest 17 | 18 | @testable import AsyncDNSResolver 19 | 20 | final class AresOptionsTests: XCTestCase { 21 | private typealias Options = CAresDNSResolver.Options 22 | private typealias Flags = CAresDNSResolver.Options.Flags 23 | 24 | func test_flags() { 25 | let flags: Flags = [.NOALIASES, .IGNTC, .NOSEARCH] 26 | let expected = Flags.NOALIASES.rawValue | Flags.IGNTC.rawValue | Flags.NOSEARCH.rawValue 27 | XCTAssertEqual(flags.rawValue, expected, "Expected value to be \(expected), got \(flags.rawValue)") 28 | XCTAssertTrue(flags.contains(.IGNTC)) 29 | XCTAssertFalse(flags.contains(.NORECURSE)) 30 | } 31 | 32 | func test_OptionsToAresOptions() { 33 | var options = Options() 34 | options.flags = .STAYOPEN 35 | options.timeoutMillis = 200 36 | options.attempts = 3 37 | options.numberOfDots = 7 38 | options.udpPort = 59 39 | options.tcpPort = 95 40 | options.socketSendBufferSize = 1024 41 | options.socketReceiveBufferSize = 2048 42 | options.ednsPacketSize = 512 43 | options.resolvConfPath = "/foo/bar" 44 | options.hostsFilePath = "/foo/baz" 45 | options.lookups = "bf" 46 | options.domains = ["foo", "bar", "baz"] 47 | options.servers = ["dns-one.local", "dns-two.local"] 48 | options.sortlist = ["130.155.160.0/255.255.240.0", "130.155.0.0"] 49 | 50 | // Verify `ares_options` 51 | let aresOptions = options.aresOptions 52 | 53 | self.assertKeyPathValue(options: aresOptions, keyPath: \.flags, expected: options.flags.rawValue) 54 | self.ensureOptionMaskSet(aresOptions, .FLAGS) 55 | 56 | self.assertKeyPathValue(options: aresOptions, keyPath: \.timeout, expected: options.timeoutMillis) 57 | self.ensureOptionMaskSet(aresOptions, .TIMEOUTMS) 58 | 59 | self.assertKeyPathValue(options: aresOptions, keyPath: \.tries, expected: options.attempts) 60 | self.ensureOptionMaskSet(aresOptions, .TRIES) 61 | 62 | self.assertKeyPathValue(options: aresOptions, keyPath: \.ndots, expected: options.numberOfDots) 63 | self.ensureOptionMaskSet(aresOptions, .NDOTS) 64 | 65 | self.assertKeyPathValue(options: aresOptions, keyPath: \.udp_port, expected: options.udpPort) 66 | self.ensureOptionMaskSet(aresOptions, .UDP_PORT) 67 | 68 | self.assertKeyPathValue(options: aresOptions, keyPath: \.tcp_port, expected: options.tcpPort) 69 | self.ensureOptionMaskSet(aresOptions, .TCP_PORT) 70 | 71 | self.assertKeyPathValue( 72 | options: aresOptions, 73 | keyPath: \.socket_send_buffer_size, 74 | expected: options.socketSendBufferSize! 75 | ) 76 | self.ensureOptionMaskSet(aresOptions, .SOCK_SNDBUF) 77 | 78 | self.assertKeyPathValue( 79 | options: aresOptions, 80 | keyPath: \.socket_receive_buffer_size, 81 | expected: options.socketReceiveBufferSize! 82 | ) 83 | self.ensureOptionMaskSet(aresOptions, .SOCK_RCVBUF) 84 | 85 | self.assertKeyPathValue(options: aresOptions, keyPath: \.ednspsz, expected: options.ednsPacketSize!) 86 | self.ensureOptionMaskSet(aresOptions, .EDNSPSZ) 87 | 88 | self.assertKeyPathValue(options: aresOptions, keyPath: \.resolvconf_path, expected: options.resolvConfPath!) 89 | self.ensureOptionMaskSet(aresOptions, .RESOLVCONF) 90 | 91 | self.assertKeyPathValue(options: aresOptions, keyPath: \.hosts_path, expected: options.hostsFilePath!) 92 | self.ensureOptionMaskSet(aresOptions, .HOSTS_FILE) 93 | 94 | self.assertKeyPathValue(options: aresOptions, keyPath: \.lookups, expected: options.lookups!) 95 | self.ensureOptionMaskSet(aresOptions, .LOOKUPS) 96 | 97 | self.assertKeyPathValue(options: aresOptions, keyPath: \.domains, expected: options.domains!) 98 | self.assertKeyPathValue(options: aresOptions, keyPath: \.ndomains, expected: CInt(options.domains!.count)) 99 | self.ensureOptionMaskSet(aresOptions, .DOMAINS) 100 | 101 | XCTAssertNotNil(aresOptions.servers) 102 | XCTAssertEqual( 103 | aresOptions.servers, 104 | options.servers, 105 | "Expected servers to be \(options.servers!), got \(aresOptions.servers!)" 106 | ) 107 | 108 | XCTAssertNotNil(aresOptions.sortlist) 109 | XCTAssertEqual( 110 | aresOptions.sortlist, 111 | options.sortlist, 112 | "Expected sortlist to be \(options.sortlist!), got \(aresOptions.sortlist!)" 113 | ) 114 | } 115 | 116 | func test_rotate() { 117 | var options = Options() 118 | 119 | do { 120 | options.rotate = nil 121 | 122 | let aresOptions = options.aresOptions 123 | XCTAssertFalse(aresOptions._optionMasks.contains(.ROTATE)) 124 | XCTAssertFalse(aresOptions._optionMasks.contains(.NOROTATE)) 125 | } 126 | 127 | do { 128 | options.rotate = true 129 | 130 | let aresOptions = options.aresOptions 131 | XCTAssertTrue(aresOptions._optionMasks.contains(.ROTATE)) 132 | XCTAssertFalse(aresOptions._optionMasks.contains(.NOROTATE)) 133 | } 134 | 135 | do { 136 | options.rotate = false 137 | 138 | let aresOptions = options.aresOptions 139 | XCTAssertFalse(aresOptions._optionMasks.contains(.ROTATE)) 140 | XCTAssertTrue(aresOptions._optionMasks.contains(.NOROTATE)) 141 | } 142 | } 143 | 144 | func test_AresOptions_socketStateCallback() { 145 | let options = AresOptions() 146 | 147 | let dataPointer = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 1) 148 | defer { dataPointer.deallocate() } 149 | 150 | options.setSocketStateCallback(with: dataPointer) { _, _, _, _ in } 151 | XCTAssertNotNil(options.underlying.sock_state_cb, "Expected sock_state_cb to be non nil") 152 | XCTAssertNotNil(options.underlying.sock_state_cb_data, "Expected sock_state_cb_data to be non nil") 153 | self.ensureOptionMaskSet(options, .SOCK_STATE_CB) 154 | } 155 | 156 | private func assertKeyPathValue( 157 | options: AresOptions, 158 | keyPath: KeyPath, 159 | expected: T 160 | ) where T: Equatable { 161 | let actual = options.underlying[keyPath: keyPath] 162 | XCTAssertEqual(actual, expected, "Expected \(keyPath) to be \(expected), got \(actual)") 163 | } 164 | 165 | private func assertKeyPathValue( 166 | options: AresOptions, 167 | keyPath: KeyPath?>, 168 | expected: String 169 | ) { 170 | let actualPointer = options.underlying[keyPath: keyPath] 171 | XCTAssertNotNil(actualPointer, "Expected \(keyPath) to be non nil") 172 | let actual = String(cString: actualPointer!) // !-safe since we check for nil 173 | XCTAssertEqual(actual, expected, "Expected \(keyPath) to be \(expected), got \(actual)") 174 | } 175 | 176 | private func assertKeyPathValue( 177 | options: AresOptions, 178 | keyPath: KeyPath?>?>, 179 | expected: [String] 180 | ) { 181 | let actualPointer = options.underlying[keyPath: keyPath] 182 | XCTAssertNotNil(actualPointer, "Expected \(keyPath) to be non nil") 183 | let actual = Array(UnsafeBufferPointer(start: actualPointer!, count: expected.count)) 184 | .map { $0.map { String(cString: $0) } } 185 | XCTAssertEqual(actual, expected, "Expected \(keyPath) to be \(expected), got \(actual)") 186 | } 187 | 188 | private func ensureOptionMaskSet(_ options: AresOptions, _ mask: AresOptionMasks) { 189 | XCTAssertTrue(options._optionMasks.contains(mask), "Expected \(mask) to be set") 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Tests/AsyncDNSResolverTests/c-ares/CAresDNSResolverTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2020-2023 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import XCTest 16 | 17 | @testable import AsyncDNSResolver 18 | 19 | final class CAresDNSResolverTests: XCTestCase { 20 | var resolver: CAresDNSResolver! 21 | var verbose: Bool = false 22 | 23 | override func setUp() { 24 | super.setUp() 25 | 26 | let servers = ProcessInfo.processInfo.environment["NAME_SERVERS"]?.split(separator: ",").map { String($0) } 27 | 28 | var options = CAresDNSResolver.Options() 29 | options.servers = servers 30 | 31 | self.resolver = try! CAresDNSResolver(options: options) 32 | self.verbose = ProcessInfo.processInfo.environment["VERBOSE_TESTS"] == "true" 33 | } 34 | 35 | override func tearDown() { 36 | super.tearDown() 37 | self.resolver = nil // FIXME: for tsan 38 | } 39 | 40 | func test_queryA() async throws { 41 | let reply = try await self.resolver.queryA(name: "apple.com") 42 | if self.verbose { 43 | print("test_queryA: \(reply)") 44 | } 45 | XCTAssertFalse(reply.isEmpty, "should have A record(s)") 46 | } 47 | 48 | func test_queryAAAA() async throws { 49 | let reply = try await self.resolver.queryAAAA(name: "apple.com") 50 | if self.verbose { 51 | print("test_queryAAAA: \(reply)") 52 | } 53 | XCTAssertFalse(reply.isEmpty, "should have AAAA record(s)") 54 | } 55 | 56 | func test_queryNS() async throws { 57 | let reply = try await self.resolver.queryNS(name: "apple.com") 58 | if self.verbose { 59 | print("test_queryNS: \(reply)") 60 | } 61 | XCTAssertFalse(reply.nameservers.isEmpty, "should have nameserver(s)") 62 | } 63 | 64 | func test_queryCNAME() async throws { 65 | let reply = try await self.resolver.queryCNAME(name: "www.apple.com") 66 | if self.verbose { 67 | print("test_queryCNAME: \(String(describing: reply))") 68 | } 69 | XCTAssertNotNil(reply?.isEmpty ?? true, "should have CNAME") 70 | } 71 | 72 | func test_querySOA() async throws { 73 | let reply = try await self.resolver.querySOA(name: "apple.com") 74 | if self.verbose { 75 | print("test_querySOA: \(String(describing: reply))") 76 | } 77 | XCTAssertFalse(reply?.mname?.isEmpty ?? true, "should have nameserver") 78 | } 79 | 80 | func test_queryPTR() async throws { 81 | let reply = try await self.resolver.queryPTR(name: "47.224.172.17.in-addr.arpa") 82 | if self.verbose { 83 | print("test_queryPTR: \(reply)") 84 | } 85 | XCTAssertFalse(reply.names.isEmpty, "should have names") 86 | } 87 | 88 | func test_queryMX() async throws { 89 | let reply = try await self.resolver.queryMX(name: "apple.com") 90 | if self.verbose { 91 | print("test_queryMX: \(reply)") 92 | } 93 | XCTAssertFalse(reply.isEmpty, "should have MX record(s)") 94 | } 95 | 96 | func test_queryTXT() async throws { 97 | let reply = try await self.resolver.queryTXT(name: "apple.com") 98 | if self.verbose { 99 | print("test_queryTXT: \(reply)") 100 | } 101 | XCTAssertFalse(reply.isEmpty, "should have TXT record(s)") 102 | } 103 | 104 | func test_querySRV() async throws { 105 | let reply = try await self.resolver.querySRV(name: "_caldavs._tcp.google.com") 106 | if self.verbose { 107 | print("test_querySRV: \(reply)") 108 | } 109 | XCTAssertFalse(reply.isEmpty, "should have SRV record(s)") 110 | } 111 | 112 | func test_queryNAPTR() async throws { 113 | do { 114 | // expected: "no data" error 115 | let reply = try await self.resolver.queryNAPTR(name: "apple.com") 116 | if self.verbose { 117 | print("test_queryNAPTR: \(reply)") 118 | } 119 | } catch { 120 | print("test_queryNAPTR error: \(error)") 121 | } 122 | } 123 | 124 | func test_concurrency() async throws { 125 | let verbose = self.verbose 126 | 127 | func run( 128 | _ name: String, 129 | times: Int = 100, 130 | _ query: @Sendable @escaping (_ index: Int) async throws -> Void 131 | ) async throws { 132 | let start = Date.now 133 | defer { 134 | if verbose { 135 | print("Test of \(name) took \(Int64(start.timeIntervalSinceNow * -1000)) ms.") 136 | } 137 | } 138 | do { 139 | try await withThrowingTaskGroup(of: Void.self) { group in 140 | for i in 1...times { 141 | group.addTask { 142 | try await query(i) 143 | } 144 | } 145 | for try await _ in group {} 146 | } 147 | } catch { 148 | if verbose { 149 | print("Test of \(name) is throwing an error.") 150 | } 151 | throw error 152 | } 153 | } 154 | 155 | let resolver = self.resolver! 156 | try await run("queryA") { i in 157 | let reply = try await resolver.queryA(name: "apple.com") 158 | if verbose { 159 | print("[A] run #\(i) result: \(reply)") 160 | } 161 | } 162 | 163 | try await run("queryAAAA") { i in 164 | let reply = try await resolver.queryAAAA(name: "apple.com") 165 | if verbose { 166 | print("[AAAA] run #\(i) result: \(reply)") 167 | } 168 | } 169 | 170 | try await run("queryNS") { i in 171 | let reply = try await resolver.queryNS(name: "apple.com") 172 | if verbose { 173 | print("[NS] run #\(i) result: \(reply)") 174 | } 175 | } 176 | 177 | try await run("queryCNAME") { i in 178 | let reply = try await resolver.queryCNAME(name: "www.apple.com") 179 | if verbose { 180 | print("[CNAME] run #\(i) result: \(String(describing: reply))") 181 | } 182 | } 183 | 184 | try await run("querySOA") { i in 185 | let reply = try await resolver.querySOA(name: "apple.com") 186 | if verbose { 187 | print("[SOA] run #\(i) result: \(String(describing: reply))") 188 | } 189 | } 190 | 191 | try await run("queryPTR") { i in 192 | let reply = try await resolver.queryPTR(name: "47.224.172.17.in-addr.arpa") 193 | if verbose { 194 | print("[PTR] run #\(i) result: \(reply)") 195 | } 196 | } 197 | 198 | try await run("queryMX") { i in 199 | let reply = try await resolver.queryMX(name: "apple.com") 200 | if verbose { 201 | print("[MX] run #\(i) result: \(reply)") 202 | } 203 | } 204 | 205 | // TXT lookups are very slow in CI and lead to timeouts. 206 | try await run("queryTXT", times: 5) { i in 207 | let reply = try await resolver.queryTXT(name: "apple.com") 208 | if verbose { 209 | print("[TXT] run #\(i) result: \(reply)") 210 | } 211 | } 212 | 213 | try await run("querySRV") { i in 214 | let reply = try await resolver.querySRV(name: "_caldavs._tcp.google.com") 215 | if verbose { 216 | print("[SRV] run #\(i) result: \(reply)") 217 | } 218 | } 219 | 220 | // expected: "no data" error 221 | // try await run { i in 222 | // let reply = try await self.resolver.queryNAPTR(name: "apple.com") 223 | // if self.verbose { 224 | // print("[NAPTR] run #\(i) result: \(reply)") 225 | // } 226 | // } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Tests/AsyncDNSResolverTests/dnssd/DNSDArraySliceTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import XCTest 16 | 17 | @testable import AsyncDNSResolver 18 | 19 | #if canImport(Darwin) 20 | final class DNSDArraySliceTests: XCTestCase { 21 | func testReadUnsignedInteger() { 22 | // [UInt8(0), UInt16(.max), UInt32(0), UInt64(.max)] 23 | let bytes: [UInt8] = [0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255] 24 | var slice = bytes[...] 25 | 26 | XCTAssertEqual(slice.readInteger(as: UInt8.self), 0) 27 | XCTAssertEqual(slice.readInteger(as: UInt16.self), .max) 28 | XCTAssertEqual(slice.readInteger(as: UInt32.self), 0) 29 | XCTAssertEqual(slice.readInteger(as: UInt64.self), .max) 30 | 31 | XCTAssertNil(slice.readInteger(as: UInt8.self)) 32 | } 33 | 34 | func testReadSignedInteger() { 35 | // [Int8(0), Int16(-1), Int32(0), Int64(-1)] 36 | let bytes: [UInt8] = [0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255] 37 | var slice = bytes[...] 38 | 39 | XCTAssertEqual(slice.readInteger(as: Int8.self), 0) 40 | XCTAssertEqual(slice.readInteger(as: Int16.self), -1) 41 | XCTAssertEqual(slice.readInteger(as: Int32.self), 0) 42 | XCTAssertEqual(slice.readInteger(as: Int64.self), -1) 43 | 44 | XCTAssertNil(slice.readInteger(as: Int8.self)) 45 | } 46 | 47 | func testReadString() { 48 | let bytes = Array("hello, world!".utf8) 49 | var slice = bytes[...] 50 | 51 | XCTAssertEqual(slice.readString(length: 13), "hello, world!") 52 | XCTAssertEqual(slice.readString(length: 0), "") 53 | XCTAssertNil(slice.readString(length: 1)) 54 | } 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /Tests/AsyncDNSResolverTests/dnssd/DNSSDDNSResolverTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2023 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import XCTest 16 | 17 | @testable import AsyncDNSResolver 18 | 19 | #if canImport(Darwin) 20 | final class DNSSDDNSResolverTests: XCTestCase { 21 | var resolver: DNSSDDNSResolver! 22 | var verbose: Bool = false 23 | 24 | override func setUp() { 25 | super.setUp() 26 | 27 | self.resolver = DNSSDDNSResolver() 28 | self.verbose = ProcessInfo.processInfo.environment["VERBOSE_TESTS"] == "true" 29 | } 30 | 31 | override func tearDown() { 32 | super.tearDown() 33 | self.resolver = nil // FIXME: for tsan 34 | } 35 | 36 | func test_queryA() async throws { 37 | let reply = try await self.resolver.queryA(name: "apple.com") 38 | if self.verbose { 39 | print("test_queryA: \(reply)") 40 | } 41 | XCTAssertFalse(reply.isEmpty, "should have A record(s)") 42 | } 43 | 44 | func test_queryAAAA() async throws { 45 | let reply = try await self.resolver.queryAAAA(name: "apple.com") 46 | if self.verbose { 47 | print("test_queryAAAA: \(reply)") 48 | } 49 | XCTAssertFalse(reply.isEmpty, "should have AAAA record(s)") 50 | } 51 | 52 | func test_queryNS() async throws { 53 | let reply = try await self.resolver.queryNS(name: "apple.com") 54 | if self.verbose { 55 | print("test_queryNS: \(reply)") 56 | } 57 | XCTAssertFalse(reply.nameservers.isEmpty, "should have nameserver(s)") 58 | } 59 | 60 | func test_queryCNAME() async throws { 61 | let reply = try await self.resolver.queryCNAME(name: "www.apple.com") 62 | if self.verbose { 63 | print("test_queryCNAME: \(String(describing: reply))") 64 | } 65 | XCTAssertFalse(reply?.isEmpty ?? true, "should have CNAME") 66 | } 67 | 68 | func test_querySOA() async throws { 69 | let reply = try await self.resolver.querySOA(name: "apple.com") 70 | if self.verbose { 71 | print("test_querySOA: \(String(describing: reply))") 72 | } 73 | XCTAssertFalse(reply?.mname?.isEmpty ?? true, "should have nameserver") 74 | } 75 | 76 | func test_queryPTR() async throws { 77 | let reply = try await self.resolver.queryPTR(name: "47.224.172.17.in-addr.arpa") 78 | if self.verbose { 79 | print("test_queryPTR: \(reply)") 80 | } 81 | XCTAssertFalse(reply.names.isEmpty, "should have names") 82 | } 83 | 84 | func test_queryMX() async throws { 85 | let reply = try await self.resolver.queryMX(name: "apple.com") 86 | if self.verbose { 87 | print("test_queryMX: \(reply)") 88 | } 89 | XCTAssertFalse(reply.isEmpty, "should have MX record(s)") 90 | } 91 | 92 | func test_queryTXT() async throws { 93 | let reply = try await self.resolver.queryTXT(name: "apple.com") 94 | if self.verbose { 95 | print("test_queryTXT: \(reply)") 96 | } 97 | XCTAssertFalse(reply.isEmpty, "should have TXT record(s)") 98 | } 99 | 100 | func test_querySRV() async throws { 101 | let reply = try await self.resolver.querySRV(name: "_caldavs._tcp.google.com") 102 | if self.verbose { 103 | print("test_querySRV: \(reply)") 104 | } 105 | XCTAssertFalse(reply.isEmpty, "should have SRV record(s)") 106 | } 107 | 108 | func test_parseA() throws { 109 | let addrBytes: [UInt8] = [38, 32, 1, 73] 110 | try addrBytes.withUnsafeBufferPointer { 111 | let record = try DNSSD.AQueryReplyHandler.instance.parseRecord( 112 | data: $0.baseAddress, 113 | length: UInt16($0.count) 114 | ) 115 | XCTAssertEqual(record, ARecord(address: .init(address: "38.32.1.73"), ttl: nil)) 116 | } 117 | } 118 | 119 | func test_parseATooShort() throws { 120 | let addrBytes: [UInt8] = [38, 32, 1] 121 | try addrBytes.withUnsafeBufferPointer { 122 | XCTAssertThrowsError( 123 | try DNSSD.AQueryReplyHandler.instance.parseRecord( 124 | data: $0.baseAddress, 125 | length: UInt16($0.count) 126 | ) 127 | ) 128 | } 129 | } 130 | 131 | func test_parseAAAATooShort() throws { 132 | let addrBytes: [UInt8] = [38, 32, 1, 73, 17, 11, 71, 14, 0, 0, 0, 0, 0, 0, 14] 133 | try addrBytes.withUnsafeBufferPointer { 134 | XCTAssertThrowsError( 135 | try DNSSD.AAAAQueryReplyHandler.instance.parseRecord( 136 | data: $0.baseAddress, 137 | length: UInt16($0.count) 138 | ) 139 | ) 140 | } 141 | } 142 | 143 | func test_concurrency() async throws { 144 | func run( 145 | times: Int = 100, 146 | _ query: @Sendable @escaping (_ index: Int) async throws -> Void 147 | ) async throws { 148 | try await withThrowingTaskGroup(of: Void.self) { group in 149 | for i in 1...times { 150 | group.addTask { 151 | try await query(i) 152 | } 153 | } 154 | for try await _ in group {} 155 | } 156 | } 157 | 158 | let resolver = self.resolver! 159 | let verbose = self.verbose 160 | try await run { i in 161 | let reply = try await resolver.queryA(name: "apple.com") 162 | if verbose { 163 | print("[A] run #\(i) result: \(reply)") 164 | } 165 | } 166 | 167 | try await run { i in 168 | let reply = try await resolver.queryAAAA(name: "apple.com") 169 | if verbose { 170 | print("[AAAA] run #\(i) result: \(reply)") 171 | } 172 | } 173 | 174 | try await run { i in 175 | let reply = try await resolver.queryNS(name: "apple.com") 176 | if verbose { 177 | print("[NS] run #\(i) result: \(reply)") 178 | } 179 | } 180 | 181 | try await run { i in 182 | let reply = try await resolver.queryCNAME(name: "www.apple.com") 183 | if verbose { 184 | print("[CNAME] run #\(i) result: \(String(describing: reply))") 185 | } 186 | } 187 | 188 | try await run { i in 189 | let reply = try await resolver.querySOA(name: "apple.com") 190 | if verbose { 191 | print("[SOA] run #\(i) result: \(String(describing: reply))") 192 | } 193 | } 194 | 195 | try await run { i in 196 | let reply = try await resolver.queryPTR(name: "47.224.172.17.in-addr.arpa") 197 | if verbose { 198 | print("[PTR] run #\(i) result: \(reply)") 199 | } 200 | } 201 | 202 | try await run { i in 203 | let reply = try await resolver.queryMX(name: "apple.com") 204 | if verbose { 205 | print("[MX] run #\(i) result: \(reply)") 206 | } 207 | } 208 | 209 | try await run { i in 210 | let reply = try await resolver.queryTXT(name: "apple.com") 211 | if verbose { 212 | print("[TXT] run #\(i) result: \(reply)") 213 | } 214 | } 215 | 216 | try await run { i in 217 | let reply = try await resolver.querySRV(name: "_caldavs._tcp.google.com") 218 | if verbose { 219 | print("[SRV] run #\(i) result: \(reply)") 220 | } 221 | } 222 | } 223 | } 224 | #endif 225 | -------------------------------------------------------------------------------- /Tests/AsyncDNSResolverTests/dnssd/DNSSDErrorTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftAsyncDNSResolver open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the SwiftAsyncDNSResolver project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftAsyncDNSResolver project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | #if canImport(Darwin) 16 | @testable import AsyncDNSResolver 17 | import dnssd 18 | import XCTest 19 | 20 | final class DNSSDErrorTests: XCTestCase { 21 | func test_initFromCode() { 22 | let inputs: [Int: AsyncDNSResolver.Error.Code] = [ 23 | kDNSServiceErr_BadFlags: .badQuery, 24 | kDNSServiceErr_BadParam: .badQuery, 25 | kDNSServiceErr_Invalid: .badQuery, 26 | kDNSServiceErr_Refused: .connectionRefused, 27 | kDNSServiceErr_Timeout: .timeout, 28 | ] 29 | 30 | for (code, expected) in inputs { 31 | let error = AsyncDNSResolver.Error(dnssdCode: Int32(code), "some error") 32 | XCTAssertEqual(error.code, expected) 33 | XCTAssertEqual( 34 | error.message, 35 | "some error", 36 | "Expected description to be \"some error\", got \(error.message)" 37 | ) 38 | } 39 | } 40 | } 41 | #endif 42 | --------------------------------------------------------------------------------