├── .clang-format ├── .codecov.yml ├── .github └── workflows │ └── version-check.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Contributing.md ├── HappyDNS-dynamic ├── HappyDNS.h └── Info.plist ├── HappyDNS.podspec ├── HappyDNS.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── HappyDNS_Mac.xcscheme │ ├── HappyDNS_iOS.xcscheme │ └── xcschememanagement.plist ├── HappyDNS.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── HappyDNS ├── Common │ ├── QNDnsError.h │ ├── QNDnsError.m │ ├── QNDnsManager.h │ ├── QNDnsManager.m │ ├── QNDomain.h │ ├── QNDomain.m │ ├── QNLruCache.h │ ├── QNLruCache.m │ ├── QNNetworkInfo.h │ ├── QNNetworkInfo.m │ ├── QNRecord.h │ ├── QNRecord.m │ └── QNResolverDelegate.h ├── Dns │ ├── QNDnsDefine.h │ ├── QNDnsMessage.h │ ├── QNDnsMessage.m │ ├── QNDnsRequest.h │ ├── QNDnsRequest.m │ ├── QNDnsResolver.h │ ├── QNDnsResolver.m │ ├── QNDnsResponse.h │ ├── QNDnsResponse.m │ ├── QNDnsUdpResolver.h │ ├── QNDnsUdpResolver.m │ ├── QNDohResolver.h │ └── QNDohResolver.m ├── HappyDNS.h ├── Http │ ├── QNDnspodEnterprise.h │ └── QNDnspodEnterprise.m ├── Local │ ├── QNHijackingDetectWrapper.h │ ├── QNHijackingDetectWrapper.m │ ├── QNHosts.h │ ├── QNHosts.m │ ├── QNResolvUtil.h │ ├── QNResolvUtil.m │ ├── QNResolver.h │ ├── QNResolver.m │ ├── QNTxtResolver.h │ └── QNTxtResolver.m ├── PrivacyInfo.xcprivacy ├── Util │ ├── NSData+QNRW.h │ ├── NSData+QNRW.m │ ├── QNAsyncUdpSocket.h │ ├── QNAsyncUdpSocket.m │ ├── QNDes.h │ ├── QNDes.m │ ├── QNGetAddrInfo.h │ ├── QNGetAddrInfo.m │ ├── QNHex.h │ ├── QNHex.m │ ├── QNIP.h │ ├── QNIP.m │ ├── QNMD5.h │ └── QNMD5.m └── include │ └── HappyDNS │ ├── HappyDNS.h │ ├── NSData+QNRW.h │ ├── QNAsyncUdpSocket.h │ ├── QNDes.h │ ├── QNDnsDefine.h │ ├── QNDnsError.h │ ├── QNDnsManager.h │ ├── QNDnsMessage.h │ ├── QNDnsRequest.h │ ├── QNDnsResolver.h │ ├── QNDnsResponse.h │ ├── QNDnsUdpResolver.h │ ├── QNDnspodEnterprise.h │ ├── QNDohResolver.h │ ├── QNDomain.h │ ├── QNGetAddrInfo.h │ ├── QNHex.h │ ├── QNHijackingDetectWrapper.h │ ├── QNHosts.h │ ├── QNIP.h │ ├── QNLruCache.h │ ├── QNMD5.h │ ├── QNNetworkInfo.h │ ├── QNRecord.h │ ├── QNResolvUtil.h │ ├── QNResolver.h │ ├── QNResolverDelegate.h │ └── QNTxtResolver.h ├── HappyDNSTests ├── DesTest.m ├── DnsServerResolverTest.m ├── DnsTest.m ├── DnspodEnterpriseTest.m ├── DohResolverTest.m ├── DohTest.h ├── GetAddrInfoTest.m ├── HexTest.m ├── HostsTest.m ├── IPTest.m ├── Info.plist ├── LruCacheTest.m ├── NetworkTest.m ├── ResolverTest.m └── TxtResolverTest.m ├── LICENSE ├── Package.swift ├── Podfile ├── README.md ├── clang-format ├── codecov.yml ├── format.sh └── uncrustify.cfg /.clang-format: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015-present, Parse, LLC. 2 | # All rights reserved. 3 | # 4 | # This source code is licensed under the BSD-style license found in the 5 | # LICENSE file in the root directory of this source tree. An additional grant 6 | # of patent rights can be found in the PATENTS file in the same directory. 7 | 8 | --- 9 | Language: Cpp 10 | BasedOnStyle: LLVM 11 | AccessModifierOffset: -2 12 | AlignAfterOpenBracket: true 13 | AlignEscapedNewlinesLeft: true 14 | AlignOperands: true 15 | AlignTrailingComments: false 16 | AllowAllParametersOfDeclarationOnNextLine: false 17 | AllowShortBlocksOnASingleLine: false 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortIfStatementsOnASingleLine: true 20 | AllowShortLoopsOnASingleLine: false 21 | AllowShortFunctionsOnASingleLine: false 22 | AlwaysBreakAfterDefinitionReturnType: false 23 | AlwaysBreakTemplateDeclarations: false 24 | AlwaysBreakBeforeMultilineStrings: false 25 | BreakBeforeBinaryOperators: None 26 | BreakBeforeTernaryOperators: true 27 | BreakConstructorInitializersBeforeComma: true 28 | BinPackParameters: true 29 | BinPackArguments: true 30 | ColumnLimit: 0 31 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 32 | ConstructorInitializerIndentWidth: 4 33 | DerivePointerAlignment: true 34 | ExperimentalAutoDetectBinPacking: true 35 | IndentCaseLabels: true 36 | IndentWrappedFunctionNames: true 37 | IndentFunctionDeclarationAfterType: true 38 | MaxEmptyLinesToKeep: 1 39 | KeepEmptyLinesAtTheStartOfBlocks: true 40 | NamespaceIndentation: None 41 | ObjCBlockIndentWidth: 4 42 | ObjCSpaceAfterProperty: true 43 | ObjCSpaceBeforeProtocolList: true 44 | PenaltyBreakBeforeFirstCallParameter: 19 45 | PenaltyBreakComment: 300 46 | PenaltyBreakString: 1000 47 | PenaltyBreakFirstLessLess: 140 48 | PenaltyExcessCharacter: 1000000 49 | PenaltyReturnTypeOnItsOwnLine: 120 50 | PointerAlignment: Right 51 | SpacesBeforeTrailingComments: 1 52 | Cpp11BracedListStyle: true 53 | Standard: Cpp11 54 | IndentWidth: 4 55 | TabWidth: 4 56 | UseTab: Never 57 | BreakBeforeBraces: Attach 58 | SpacesInParentheses: false 59 | SpacesInSquareBrackets: false 60 | SpacesInAngles: false 61 | SpaceInEmptyParentheses: false 62 | SpacesInCStyleCastParentheses: false 63 | SpaceAfterCStyleCast: false 64 | SpacesInContainerLiterals: true 65 | SpaceBeforeAssignmentOperators: true 66 | ContinuationIndentWidth: 4 67 | CommentPragmas: '^ IWYU pragma:' 68 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 69 | SpaceBeforeParens: ControlStatements 70 | DisableFormat: false 71 | ... 72 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Pods" 3 | -------------------------------------------------------------------------------- /.github/workflows/version-check.yml: -------------------------------------------------------------------------------- 1 | name: HappyDNS Objective-C SDK Version Check 2 | on: 3 | push: 4 | tags: 5 | - 'v[0-9]+.[0-9]+.[0-9]+' 6 | jobs: 7 | linux: 8 | name: Version Check 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Set env 14 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV 15 | - name: Check 16 | run: | 17 | set -e 18 | grep -E 's\.version\s+=' HappyDNS.podspec | grep -qF "'${RELEASE_VERSION}'" 19 | grep -qF "## ${RELEASE_VERSION}" CHANGELOG.md 20 | grep -qF "\"${RELEASE_VERSION}\"" README.md 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # System 2 | .DS_Store 3 | 4 | # Xcode 5 | # 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.xcuserstate 22 | 23 | 24 | # Subversion 25 | .svn 26 | 27 | # AppCode 28 | .idea 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 35 | # 36 | Pods/ 37 | Podfile.lock 38 | .swiftpm 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.2 3 | 4 | before_install: 5 | - pod install 6 | 7 | 8 | 9 | script: 10 | - xcodebuild -workspace HappyDNS.xcworkspace -scheme HappyDNS_iOS -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 5s,OS=11.2' test -enableCodeCoverage YES 11 | - xcodebuild -workspace HappyDNS.xcworkspace -scheme HappyDNS_Mac -sdk macosx -configuration Debug test 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## 1.0.4 (2024-04-11) 3 | ### 新增 4 | * 增加 PrivacyInfo 文件 5 | 6 | ## 1.0.3 (2023-06-16) 7 | ### 调整 8 | * 内部使用 114.114.114.114 替换 8.8.8.8 9 | 10 | ## 1.0.2 (2022-06-02) 11 | * 优化 udp dns reslover 的线程队列使用 12 | 13 | ## 1.0.1 (2021-11-25) 14 | * 支持 Swift Package Manager 15 | * Dnspod 默认 server ip 切至 119.29.29.98 16 | 17 | ## 1.0.0 (2021-09-02) 18 | * 新增 dns udp 解析 api 19 | * 新增 doh 解析 api 20 | * 调整 QNRecord api 21 | * 调整 DnsManager Api,支持返回 IPv6 22 | * 删除 QNDnspodFree init api 23 | 24 | ## 0.3.17 (2020-07-03) 25 | 26 | ### 修改 27 | * qiniu record增加source, dns manager增加query error handler 28 | 29 | ## 0.3.16 (2020-05-07) 30 | 31 | ### 修改 32 | * qiniu http dns 增加额外query接口 33 | 34 | ## 0.3.15 (2019-02-12) 35 | 36 | ### 修改 37 | * qiniu http dns 去掉 38 | 39 | ## 0.3.14 (2018-05-04) 40 | 41 | ### 增加 42 | * qiniu http dns 更新 43 | 44 | ## 0.3.13 (2018-04-03) 45 | 46 | ### 增加 47 | * qiniu http dns 支持 48 | 49 | ## 0.3.12 (2017-08-31) 50 | 51 | ### 修正 52 | * 去掉一处无用的copy 53 | 54 | ## 0.3.11 (2017-08-31) 55 | 56 | ### 修正 57 | * 偶发崩溃问题 58 | * 一处注释错误 59 | 60 | ## 0.3.10 (2016-07-30) 61 | 62 | ### 增加 63 | * ip status 上报 64 | * 根据时区判断是否该启用httpdns 65 | 66 | ### 0.3.9 (2016-07-19) 67 | * upversion for cocoapods 68 | 69 | ## 0.3.8 (2016-07-19) 70 | 71 | ### 增加 72 | * getaddrinfo 支持 73 | 74 | ## 0.3.7 (2016-07-11) 75 | 76 | ### 增加 77 | * 超时设置 78 | 79 | ## 0.3.6 (2016-07-05) 80 | 81 | ### 修复 82 | * ip 轮换不采用随机策略,每次轮换位置确定 83 | * 实现LruCache, 避免切到后台时丢失解析 84 | * typo QNResover initAddres -> initAddress 85 | 86 | ## 0.3.5 (2016-05-31) 87 | 88 | ### 修复 89 | * pod spec lint warning 90 | 91 | ## 0.3.4 (2016-05-31) 92 | 93 | ### 修复 94 | * ipv6 判断网络变化的本地ip字符串缓存长度小于最长ipv6长度 95 | 96 | ## 0.3.2 (2016-05-26) 97 | 98 | ### 修复 99 | * ipv6 ios8 doesnt work. 100 | 101 | ## 0.3.1 (2016-05-26) 102 | 103 | ### 修复 104 | * ipv6 reslover bug 105 | 106 | ## 0.3.0 (2016-05-26) 107 | 108 | ### 增加 109 | * ipv6 全面支持 110 | 111 | ## 0.2.4 (2016-05-10) 112 | 113 | ### 增加 114 | * 使用txt resolve 防劫持 115 | * ipv6 支持 116 | * 代码格式化 117 | * url query 118 | 119 | ## 0.2.3 (2015-10-30) 120 | 121 | ### 修正 122 | * http dns 返回 typeA 123 | 124 | ## 0.2.2 (2015-10-12) 125 | 126 | ### 修正 127 | * res_state 没有释放造成的内存泄漏 128 | 129 | ## 0.2.1 (2015-08-03) 130 | 131 | ### 修正 132 | * 外部排序接口为空时,没有返回值 133 | 134 | ## 0.2.0 (2015-08-01) 135 | 136 | ### 增加 137 | * Dnspod 企业版支持 138 | * 外部排序接口 139 | 140 | ## 0.1.1 (2015-07-16) 141 | 142 | ### 增加 143 | * localdns 劫持检测 144 | 145 | ## 0.1.0 (2015-07-15) 146 | 147 | ### 增加 148 | * 利用本地IP检查网络变化 149 | 150 | ## 0.0.1 (2015-06-21) 151 | 152 | ### 增加 153 | * localdns 154 | * httpdns 155 | 156 | 157 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # 贡献代码指南 2 | 3 | 我们非常欢迎大家来贡献代码,我们会向贡献者致以最诚挚的敬意。 4 | 5 | 一般可以通过在Github上提交[Pull Request](https://github.com/qiniu/happy-dns-objc)来贡献代码。 6 | 7 | ## Pull Request要求 8 | 9 | - **代码规范** 参考 https://github.com/NYTimes/objective-c-style-guide。 10 | 11 | - **代码格式** 提交前请使用Xcode格式化插件 BBUncrustifyPlugin 默认风格进行格式化。 12 | 13 | - **必须添加测试!** - 如果没有测试(单元测试、集成测试都可以),那么提交的补丁是不会通过的。 14 | 15 | - **记得更新文档** - 保证`README.md`以及其他相关文档及时更新,和代码的变更保持一致性。 16 | 17 | - **考虑我们的发布周期** - 我们的版本号会服从[SemVer v2.0.0](http://semver.org/),我们绝对不会随意变更对外的API。 18 | 19 | - **创建feature分支** - 最好不要从你的master分支提交 pull request。 20 | 21 | - **一个feature提交一个pull请求** - 如果你的代码变更了多个操作,那就提交多个pull请求吧。 22 | 23 | - **清晰的commit历史** - 保证你的pull请求的每次commit操作都是有意义的。如果你开发中需要执行多次的即时commit操作,那么请把它们放到一起再提交pull请求。 24 | 25 | ## 运行测试 26 | 27 | ``` bash 28 | $ xctool -workspace HappyDNS.xcworkspace -scheme "HappyDNS iOS" -sdk iphonesimulator -configuration Release test -test-sdk iphonesimulator7.0 -freshInstall -freshSimulator 29 | $ xctool -workspace HappyDNS.xcworkspace -scheme "HappyDNS Mac" -sdk macosx -configuration Release test -test-sdk macosx 30 | 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /HappyDNS-dynamic/HappyDNS.h: -------------------------------------------------------------------------------- 1 | // 2 | // HappyDNS.h 3 | // HappyDNS 4 | // 5 | // Created by hxiongan on 2018/1/5. 6 | // Copyright © 2018年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | -------------------------------------------------------------------------------- /HappyDNS-dynamic/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /HappyDNS.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'HappyDNS' 3 | s.version = '1.0.4' 4 | s.summary = 'DNS library for iOS and Mac' 5 | s.homepage = 'https://github.com/qiniu/happy-dns-objc' 6 | s.social_media_url = 'http://weibo.com/qiniutek' 7 | s.author = 'Qiniu => sdk@qiniu.com' 8 | s.source = {:git => 'https://github.com/qiniu/happy-dns-objc.git', :tag => "v#{s.version}"} 9 | 10 | s.ios.deployment_target = '9.0' 11 | s.osx.deployment_target = '10.11' 12 | s.libraries = 'resolv' 13 | s.source_files = 'HappyDNS/Common/*.{h,m}','HappyDNS/Dns/*.{h,m}','HappyDNS/Http/*.{h,m}','HappyDNS/Local/*.{h,m}','HappyDNS/Util/*.{h,m}','HappyDNS/HappyDNS.h' 14 | s.resource_bundle = {"HappyDNS.privacy"=>"HappyDNS/PrivacyInfo.xcprivacy"} 15 | s.requires_arc = true 16 | s.license = { :type => 'MIT', :text => <<-LICENSE 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2011-2024 qiniu.com 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in 29 | all copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 37 | THE SOFTWARE. 38 | LICENSE 39 | } 40 | 41 | end 42 | -------------------------------------------------------------------------------- /HappyDNS.xcodeproj/xcshareddata/xcschemes/HappyDNS_Mac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /HappyDNS.xcodeproj/xcshareddata/xcschemes/HappyDNS_iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /HappyDNS.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | HappyDNS_Mac.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | HappyDNS_iOS.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | DF22C0BE1B37B9B90010FEBF 21 | 22 | primary 23 | 24 | 25 | DF22C0C91B37B9B90010FEBF 26 | 27 | primary 28 | 29 | 30 | DF801F941B3A4F4D00866FDE 31 | 32 | primary 33 | 34 | 35 | DF801F9E1B3A4F4D00866FDE 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /HappyDNS.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /HappyDNS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNDnsError.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsError.h 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | extern const int kQNDomainHijackingCode; 13 | extern const int kQNDomainNotOwnCode; 14 | extern const int kQNDomainSeverError; 15 | 16 | extern const int kQNDnsMethodErrorCode; 17 | extern const int kQNDnsInvalidParamCode; 18 | extern const int kQNDnsResponseBadTypeCode; 19 | extern const int kQNDnsResponseBadClassCode; 20 | extern const int kQNDnsResponseFormatCode; 21 | 22 | #define kQNDnsErrorDomain @"qiniu.dns" 23 | 24 | @interface QNDnsError : NSObject 25 | 26 | + (NSError *)error:(int)code desc:(NSString *)desc; 27 | 28 | @end 29 | 30 | #define kQNDnsMethodError(description) [QNDnsError error:kQNDnsMethodErrorCode desc:description] 31 | #define kQNDnsInvalidParamError(description) [QNDnsError error:kQNDnsInvalidParamCode desc:description] 32 | #define kQNDnsResponseBadTypeError(description) [QNDnsError error:kQNDnsResponseBadTypeCode desc:description] 33 | #define kQNDnsResponseBadClassError(description) [QNDnsError error:kQNDnsResponseBadClassCode desc:description] 34 | #define kQNDnsResponseFormatError(description) [QNDnsError error:kQNDnsResponseFormatCode desc:description] 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNDnsError.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsError.m 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/20. 6 | // 7 | 8 | #import "QNDnsError.h" 9 | 10 | const int kQNDomainHijackingCode = -7001; 11 | const int kQNDomainNotOwnCode = -7002; 12 | const int kQNDomainSeverError = -7003; 13 | 14 | const int kQNDnsMethodErrorCode = -7010; 15 | 16 | const int kQNDnsInvalidParamCode = -7021; 17 | const int kQNDnsResponseBadTypeCode = -7022; 18 | const int kQNDnsResponseBadClassCode = -7023; 19 | const int kQNDnsResponseFormatCode = -7024; 20 | 21 | @implementation QNDnsError 22 | 23 | + (NSError *)error:(int)code desc:(NSString *)desc { 24 | return [NSError errorWithDomain:kQNDnsErrorDomain code:code userInfo:@{@"user_info" : desc ?: @"nil"}]; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNDnsManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsManager.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNRecord.h" 10 | 11 | @class QNNetworkInfo; 12 | @class QNDomain; 13 | 14 | /** 15 | * getaddrinfo 回调上层的函数 16 | * 17 | * @param host 请求的域名 18 | * @return ip 列表 19 | */ 20 | typedef NSArray * (^QNGetAddrInfoCallback)(NSString *host); 21 | 22 | /** 23 | * ip status 回调上层的函数 24 | * 25 | * @param ip 请求的IP 26 | * @param code 错误码 27 | * @param ms 消耗时间 28 | */ 29 | typedef void (^QNIpStatusCallback)(NSString *ip, int code, int ms); 30 | 31 | /** 32 | * 外部 Record 排序接口 33 | */ 34 | @protocol QNRecordSorter 35 | 36 | /** 37 | * 排序方法 38 | * 39 | * @param ips 传入的IP列表 40 | * 41 | * @return 返回排序好的IP 列表 42 | */ 43 | - (NSArray *)sort:(NSArray *)ips; 44 | @end 45 | 46 | /** 47 | * DNS请求客户端,集成了cache管理 48 | */ 49 | @interface QNDnsManager : NSObject 50 | 51 | /// 查询失败时抛出错误信息回调 52 | @property(nonatomic, copy)void(^queryErrorHandler)(NSError *error, NSString *host); 53 | 54 | /** 55 | * 解析域名 56 | * 57 | * @param domain 域名 58 | * 59 | * @return QNRecord列表 QNRecord.value即为host 60 | */ 61 | - (NSArray *)queryRecords:(NSString *)domain; 62 | 63 | /** 64 | * 解析域名,使用Domain对象进行详细约定 65 | * 66 | * @param domain 配置了一些domain 参数的 domain 对象 67 | * 68 | * @return IP 列表 69 | */ 70 | - (NSArray *)queryRecordsWithDomain:(QNDomain *)domain; 71 | 72 | /** 73 | * 通知网络发生变化 74 | * 75 | * @param netInfo 网络信息 76 | */ 77 | - (void)onNetworkChange:(QNNetworkInfo *)netInfo; 78 | 79 | /** 80 | * Dns client 初始化 81 | * 82 | * @param resolvers 解析服务器列表 83 | * @param netInfo 当前网络信息 84 | * 85 | * @return DnsManager 86 | */ 87 | - (instancetype)init:(NSArray *)resolvers networkInfo:(QNNetworkInfo *)netInfo; 88 | 89 | /** 90 | * Dns client 初始化 91 | * 92 | * @param resolvers 解析服务器列表 93 | * @param netInfo 当前网络信息 94 | * @param sorter 外部排序函数 95 | * 96 | * @return DnsManager 97 | */ 98 | - (instancetype)init:(NSArray *)resolvers networkInfo:(QNNetworkInfo *)netInfo sorter:(id)sorter; 99 | 100 | /** 101 | * 内置 Hosts 解析 102 | * 103 | * @param domain 域名 104 | * @param ipv4 对应IPv4 ip 105 | * 106 | * @return 当前Dnsmanager, 为了链式调用 107 | */ 108 | - (instancetype)putHosts:(NSString *)domain ipv4:(NSString *)ipv4; 109 | 110 | /** 111 | * 内置 Hosts 解析 112 | * 113 | * @param domain 域名 114 | * @param ip 对应IP 115 | * @param type ip 类别,kQNTypeA / kQNTypeAAAA 116 | * @param provider 网络运营商 117 | * 118 | * @return 当前Dnsmanager, 为了链式调用 119 | */ 120 | - (instancetype)putHosts:(NSString *)domain ip:(NSString *)ip type:(int)type provider:(int)provider; 121 | 122 | /** 123 | * 内置 Hosts 解析 124 | * 125 | * @param domain 域名 126 | * @param record 对应 record 记录 127 | * @param provider 网络运营商 128 | * 129 | * @return 当前Dnsmanager, 为了链式调用 130 | */ 131 | - (instancetype)putHosts:(NSString *)domain record:(QNRecord *)record provider:(int)provider; 132 | 133 | /** 134 | * 设置底层 getaddrinfo 使用的回调 135 | * 136 | * @param block 回调的代码块 137 | */ 138 | + (void)setGetAddrInfoBlock:(QNGetAddrInfoCallback)block; 139 | 140 | /** 141 | * 设置底层 getaddrinfo 回调使用的dnsmanager 142 | * 143 | * @param dns 回调用的dnsmanager 144 | */ 145 | + (void)setDnsManagerForGetAddrInfo:(QNDnsManager *)dns; 146 | 147 | /** 148 | * 设置底层 业务统计 如connect 回调使用的Callback 149 | * 150 | * @param block 回调返回该IP状态 151 | */ 152 | + (void)setIpStatusCallback:(QNIpStatusCallback)block; 153 | 154 | /** 155 | * 根据时区判断是否要设置httpDns 156 | */ 157 | + (BOOL)needHttpDns; 158 | 159 | @end 160 | 161 | /** 162 | * DnsManager 的 URL 辅助类 163 | */ 164 | @interface QNDnsManager (NSURL) 165 | 166 | /** 167 | * 使用URL 进行请求 168 | * 169 | * @param url 请求的Url 170 | * 171 | * @return 返回IP 替换过的url 172 | */ 173 | - (NSURL *)queryAndReplaceWithIP:(NSURL *)url; 174 | @end 175 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNDnsManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsManager.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDnsManager.h" 10 | #import "QNDomain.h" 11 | #import "QNHosts.h" 12 | #import "QNIP.h" 13 | #import "QNLruCache.h" 14 | #import "QNNetworkInfo.h" 15 | #import "QNRecord.h" 16 | #import "QNResolverDelegate.h" 17 | 18 | #include "QNGetAddrInfo.h" 19 | 20 | @interface QNDnsManager () 21 | 22 | @property (nonatomic, strong) QNLruCache *cache; 23 | @property (atomic) QNNetworkInfo *curNetwork; 24 | @property (nonatomic) NSArray *resolvers; 25 | @property (atomic) UInt32 resolverStatus; 26 | @property (nonatomic, strong) QNHosts *hosts; 27 | @property (nonatomic, strong) id sorter; 28 | @end 29 | 30 | //static inline BOOL bits_isSet(UInt32 v, int index) { 31 | // return (v & (1 << index)) != 0; 32 | //} 33 | 34 | static inline UInt32 bits_set(UInt32 v, int bitIndex) { 35 | return v |= (1 << bitIndex); 36 | } 37 | 38 | static inline UInt32 bits_leadingZeros(UInt32 x) { 39 | UInt32 y; 40 | int n = 32; 41 | y = x >> 16; 42 | if (y != 0) { 43 | n = n - 16; 44 | x = y; 45 | } 46 | y = x >> 8; 47 | if (y != 0) { 48 | n = n - 8; 49 | x = y; 50 | } 51 | y = x >> 4; 52 | if (y != 0) { 53 | n = n - 4; 54 | x = y; 55 | } 56 | y = x >> 2; 57 | if (y != 0) { 58 | n = n - 2; 59 | x = y; 60 | } 61 | y = x >> 1; 62 | if (y != 0) { 63 | return n - 2; 64 | } 65 | return n - x; 66 | } 67 | 68 | static NSMutableArray *trimCname(NSArray *records) { 69 | NSMutableArray *array = [[NSMutableArray alloc] init]; 70 | for (QNRecord *r in records) { 71 | if (r.type == kQNTypeA || r.type == kQNTypeAAAA) { 72 | [array addObject:r]; 73 | } 74 | } 75 | return array; 76 | } 77 | 78 | static NSArray *records2Ips(NSArray *records) { 79 | NSMutableArray *array = [[NSMutableArray alloc] init]; 80 | for (QNRecord *r in records) { 81 | if (r.value && r.value.length > 0) { 82 | [array addObject:r.value]; 83 | } 84 | } 85 | return [array copy]; 86 | } 87 | 88 | static NSArray * filterInvalidRecords(NSArray *records) { 89 | NSMutableArray *array = [[NSMutableArray alloc] init]; 90 | long long timestamp = [[NSDate date] timeIntervalSince1970]; 91 | for (QNRecord *r in records) { 92 | if (r.value && r.value.length > 0 && ![r expired:timestamp]) { 93 | [array addObject:r]; 94 | } 95 | } 96 | return [array copy]; 97 | } 98 | 99 | @interface DummySorter : NSObject 100 | 101 | @end 102 | 103 | @implementation DummySorter 104 | 105 | //sorted already 106 | - (NSArray *)sort:(NSArray *)ips { 107 | return ips; 108 | } 109 | 110 | @end 111 | 112 | @implementation QNDnsManager 113 | 114 | - (NSArray *)queryRecords:(NSString *)domain { 115 | return [self queryRecordsWithDomain:[[QNDomain alloc] init:domain]]; 116 | } 117 | 118 | - (NSArray *)queryRecordsWithDomain:(QNDomain *)domain{ 119 | if (domain == nil) { 120 | return nil; 121 | } 122 | if ([QNIP mayBeIpV4:domain.domain]) { 123 | QNRecord *record = [[QNRecord alloc] init:domain.domain ttl:kQNRecordForeverTTL type:kQNTypeA source:QNRecordSourceUnknown]; 124 | return [NSArray arrayWithObject:record]; 125 | } 126 | NSArray *records = [self queryInternalWithDomain:domain]; 127 | return [_sorter sort:records]; 128 | } 129 | 130 | - (NSArray *)queryInternalWithDomain:(QNDomain *)domain { 131 | if (domain.hostsFirst) { 132 | NSArray *result = [_hosts query:domain networkInfo:_curNetwork]; 133 | result = filterInvalidRecords(result); 134 | if (result.count > 0) { 135 | return [result copy]; 136 | } 137 | } 138 | 139 | if ([_curNetwork isEqualToInfo:[QNNetworkInfo normal]] && [QNNetworkInfo isNetworkChanged]) { 140 | @synchronized(_cache) { 141 | [_cache removeAllObjects]; 142 | } 143 | _resolverStatus = 0; 144 | } else { 145 | @synchronized(_cache) { 146 | NSArray *result = [_cache objectForKey:domain.domain]; 147 | result = filterInvalidRecords(result); 148 | if (result.count > 0) { 149 | return [result copy]; 150 | } 151 | } 152 | } 153 | 154 | NSArray *records = nil; 155 | NSError *error = nil; 156 | int firstOk = 32 - bits_leadingZeros(_resolverStatus); 157 | for (int i = 0; i < _resolvers.count; i++) { 158 | int pos = (firstOk + i) % _resolvers.count; 159 | id resolver = [_resolvers objectAtIndex:pos]; 160 | QNNetworkInfo *previousNetwork = _curNetwork; 161 | NSString *previousIp = [QNNetworkInfo getIp]; 162 | records = [resolver query:domain networkInfo:previousNetwork error:&error]; 163 | if (error != nil) { 164 | NSError *tmp = error; 165 | error = nil; 166 | if (tmp.code == kQNDomainNotOwnCode) { 167 | continue; 168 | } 169 | 170 | if (self.queryErrorHandler) { 171 | self.queryErrorHandler(error, domain.domain); 172 | } 173 | } 174 | 175 | if (records == nil || records.count == 0) { 176 | if (_curNetwork == previousNetwork && [previousIp isEqualToString:[QNNetworkInfo getIp]]) { 177 | _resolverStatus = bits_set(_resolverStatus, pos); 178 | } 179 | } else { 180 | NSMutableArray *result = trimCname(records); 181 | if (_curNetwork == previousNetwork && [previousIp isEqualToString:[QNNetworkInfo getIp]]) { 182 | @synchronized(_cache) { 183 | [_cache setObject:[result copy] forKey:domain.domain]; 184 | } 185 | } 186 | return [result copy]; 187 | } 188 | } 189 | 190 | if (!domain.hostsFirst) { 191 | return [_hosts query:domain networkInfo:_curNetwork]; 192 | } 193 | 194 | return nil; 195 | } 196 | 197 | - (instancetype)init:(NSArray *)resolvers networkInfo:(QNNetworkInfo *)netInfo { 198 | return [self init:resolvers networkInfo:netInfo sorter:nil]; 199 | } 200 | 201 | - (instancetype)init:(NSArray *)resolvers networkInfo:(QNNetworkInfo *)netInfo sorter:(id)sorter { 202 | if (self = [super init]) { 203 | _cache = [[QNLruCache alloc] init:1024]; 204 | _curNetwork = netInfo; 205 | _resolvers = [[NSArray alloc] initWithArray:resolvers]; 206 | _hosts = [[QNHosts alloc] init]; 207 | if (sorter == nil) { 208 | _sorter = [[DummySorter alloc] init]; 209 | } else { 210 | _sorter = sorter; 211 | } 212 | } 213 | return self; 214 | } 215 | 216 | - (void)onNetworkChange:(QNNetworkInfo *)netInfo { 217 | @synchronized(_cache) { 218 | [_cache removeAllObjects]; 219 | } 220 | _curNetwork = netInfo; 221 | } 222 | 223 | - (instancetype)putHosts:(NSString *)domain ipv4:(NSString *)ipv4 { 224 | return [self putHosts:domain ip:ipv4 type:kQNTypeA provider:kQNISP_GENERAL]; 225 | } 226 | 227 | - (instancetype)putHosts:(NSString *)domain ip:(NSString *)ip type:(int)type provider:(int)provider { 228 | return [self putHosts:domain record:[[QNRecord alloc] init:ip ttl:kQNRecordForeverTTL type:type source:QNRecordSourceCustom] provider:provider]; 229 | } 230 | 231 | - (instancetype)putHosts:(NSString *)domain record:(QNRecord *)record provider:(int)provider { 232 | QNRecord *recordNew = [[QNRecord alloc] init:record.value ttl:record.ttl type:record.type timeStamp:record.timeStamp server:record.server source:QNRecordSourceCustom]; 233 | [_hosts put:domain record:recordNew provider:provider]; 234 | return self; 235 | } 236 | 237 | - (NSURL *)queryAndReplaceWithIP:(NSURL *)url { 238 | NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:YES]; 239 | if (!urlComponents) { 240 | return nil; 241 | } 242 | 243 | NSString *host = urlComponents.host; 244 | NSArray *records = [self queryRecords:host]; 245 | 246 | NSURL *URL = nil; 247 | if (records && records.firstObject) { 248 | urlComponents.host = [QNIP ipHost:records.firstObject.value]; 249 | } 250 | 251 | URL = urlComponents.URL; 252 | return URL; 253 | } 254 | 255 | static QNGetAddrInfoCallback getAddrInfoCallback = nil; 256 | static qn_ips_ret *dns_callback_internal(const char *host) { 257 | if (getAddrInfoCallback == nil) { 258 | return NULL; 259 | } 260 | NSString *s = [[NSString alloc] initWithUTF8String:host]; 261 | if (s == nil) { 262 | return NULL; 263 | } 264 | NSArray *ips = getAddrInfoCallback(s); 265 | if (ips == nil) { 266 | return NULL; 267 | } 268 | qn_ips_ret *ret = calloc(sizeof(char *), ips.count + 1); 269 | for (int i = 0; i < ips.count; i++) { 270 | NSString *ip = ips[i]; 271 | char *ip2 = strdup([ip cStringUsingEncoding:NSUTF8StringEncoding]); 272 | ret->ips[i] = ip2; 273 | } 274 | return ret; 275 | } 276 | static qn_ips_ret *dns_callback(const char *host) { 277 | qn_ips_ret *ret = dns_callback_internal(host); 278 | if (ret == NULL) { 279 | //only for compatible 280 | qn_ips_ret *ret = calloc(sizeof(char *), 2); 281 | ret->ips[0] = strdup(host); 282 | } 283 | return ret; 284 | } 285 | 286 | static QNIpStatusCallback ipStatusCallback = nil; 287 | static void ip_status_callback(const char *ip, int code, int time_ms) { 288 | if (ipStatusCallback == nil) { 289 | return; 290 | } 291 | NSString *s = [[NSString alloc] initWithUTF8String:ip]; 292 | if (s == nil) { 293 | return; 294 | } 295 | ipStatusCallback(s, code, time_ms); 296 | } 297 | 298 | + (void)setGetAddrInfoBlock:(QNGetAddrInfoCallback)block { 299 | if ([QNIP isIpV6FullySupported] || ![QNIP isV6]) { 300 | getAddrInfoCallback = block; 301 | qn_set_dns_callback(dns_callback); 302 | } 303 | } 304 | 305 | + (void)setDnsManagerForGetAddrInfo:(QNDnsManager *)dns { 306 | [QNDnsManager setGetAddrInfoBlock:^NSArray *(NSString *host) { 307 | NSArray *records = [dns queryRecords:host]; 308 | return records2Ips(records); 309 | }]; 310 | } 311 | 312 | + (void)setIpStatusCallback:(QNIpStatusCallback)block { 313 | ipStatusCallback = block; 314 | qn_set_ip_report_callback(ip_status_callback); 315 | } 316 | 317 | + (BOOL)needHttpDns { 318 | NSTimeZone *timeZone = [NSTimeZone localTimeZone]; 319 | NSString *tzName = [timeZone name]; 320 | return [tzName isEqual:@"Asia/Shanghai"] || [tzName isEqual:@"Asia/Chongqing"] || [tzName isEqual:@"Asia/Harbin"] || [tzName isEqual:@"Asia/Urumqi"]; 321 | } 322 | @end 323 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNDomain.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDomain.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QNDomain : NSObject 12 | @property (nonatomic, strong, readonly) NSString *domain; 13 | 14 | // 用来判断劫持 15 | @property (nonatomic, readonly) BOOL hasCname; 16 | // 用来判断劫持 17 | @property (nonatomic, readonly) int maxTtl; 18 | 19 | @property (nonatomic, readonly) BOOL hostsFirst; 20 | 21 | - (instancetype)init:(NSString *)domain; 22 | 23 | - (instancetype)init:(NSString *)domain hostsFirst:(BOOL)hostsFirst hasCname:(BOOL)hasCname; 24 | 25 | - (instancetype)init:(NSString *)domain hostsFirst:(BOOL)hostsFirst hasCname:(BOOL)hasCname maxTtl:(int)maxTtl; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNDomain.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNDomain.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDomain.h" 10 | 11 | @implementation QNDomain 12 | - (instancetype)init:(NSString *)domain { 13 | return [self init:domain hostsFirst:NO hasCname:NO maxTtl:0]; 14 | } 15 | 16 | - (instancetype)init:(NSString *)domain hostsFirst:(BOOL)hostsFirst hasCname:(BOOL)hasCname { 17 | return [self init:domain hostsFirst:hostsFirst hasCname:hasCname maxTtl:0]; 18 | } 19 | 20 | - (instancetype)init:(NSString *)domain hostsFirst:(BOOL)hostsFirst hasCname:(BOOL)hasCname maxTtl:(int)maxTtl { 21 | if (self = [super init]) { 22 | _domain = domain; 23 | _hasCname = hasCname; 24 | _maxTtl = maxTtl; 25 | _hostsFirst = hostsFirst; 26 | } 27 | return self; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNLruCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNLruCache.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/7/5. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QNLruCache : NSObject 12 | 13 | - (instancetype)init:(NSUInteger)limit; 14 | 15 | - (void)removeAllObjects; 16 | 17 | - (void)removeObjectForKey:(NSString *)key; 18 | 19 | - (id)objectForKey:(NSString *)key; 20 | 21 | - (void)setObject:(id)obj forKey:(NSString *)key; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNLruCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNLruCache.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/7/5. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNLruCache.h" 10 | 11 | @interface QNLruCache () 12 | 13 | @property (nonatomic, readonly) NSUInteger limit; 14 | 15 | @property (nonatomic, readonly) NSMutableDictionary* cache; 16 | 17 | @property (nonatomic, readonly) NSMutableArray* list; 18 | 19 | @end 20 | 21 | @interface _QNElement : NSObject 22 | @property (nonatomic, readonly, strong) NSString* key; 23 | @property (nonatomic, strong) id obj; 24 | - (instancetype)initObject:(id)obj forKey:(NSString*)key; 25 | @end 26 | 27 | @implementation _QNElement 28 | 29 | - (instancetype)initObject:(id)obj forKey:(NSString*)key { 30 | if (self = [super init]) { 31 | _key = key; 32 | _obj = obj; 33 | } 34 | return self; 35 | } 36 | 37 | @end 38 | 39 | @implementation QNLruCache 40 | 41 | - (instancetype)init:(NSUInteger)limit { 42 | if (self = [super init]) { 43 | _limit = limit; 44 | _cache = [NSMutableDictionary new]; 45 | _list = [NSMutableArray new]; 46 | } 47 | return self; 48 | } 49 | 50 | - (void)removeAllObjects { 51 | [_cache removeAllObjects]; 52 | [_list removeAllObjects]; 53 | } 54 | 55 | - (void)removeObjectForKey:(NSString*)key { 56 | _QNElement* obj = [_cache objectForKey:key]; 57 | if (obj == nil) { 58 | return; 59 | } 60 | [_cache removeObjectForKey:key]; 61 | [_list removeObjectIdenticalTo:obj]; 62 | } 63 | 64 | - (id)objectForKey:(NSString*)key { 65 | _QNElement* obj = [_cache objectForKey:key]; 66 | if (obj != nil) { 67 | [_list removeObjectIdenticalTo:obj]; 68 | [_list insertObject:obj atIndex:0]; 69 | } 70 | return obj.obj; 71 | } 72 | 73 | - (void)setObject:(id)obj forKey:(NSString*)key { 74 | _QNElement* old = [_cache objectForKey:key]; 75 | if (old) { 76 | old.obj = obj; 77 | [_list removeObjectIdenticalTo:old]; 78 | [_list insertObject:old atIndex:0]; 79 | return; 80 | } else if (_list.count == _limit) { 81 | old = [_list lastObject]; 82 | [_list removeLastObject]; 83 | [_cache removeObjectForKey:old.key]; 84 | } 85 | _QNElement* newElement = [[_QNElement alloc] initObject:obj forKey:key]; 86 | [_cache setObject:newElement forKey:key]; 87 | [_list insertObject:newElement atIndex:0]; 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNNetworkInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNNetworkInfo.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/25. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern const int kQNNO_NETWORK; 12 | extern const int kQNWIFI; 13 | extern const int kQNMOBILE; 14 | 15 | extern const int kQNISP_GENERAL; 16 | extern const int kQNISP_CTC; 17 | extern const int kQNISP_DIANXIN; 18 | extern const int kQNISP_CNC; 19 | extern const int kQNISP_LIANTONG; 20 | extern const int kQNISP_CMCC; 21 | extern const int kQNISP_YIDONG; 22 | extern const int kQNISP_OTHER; 23 | 24 | @interface QNNetworkInfo : NSObject 25 | 26 | @property (nonatomic, readonly) int networkConnection; 27 | @property (nonatomic, readonly) int provider; 28 | 29 | - (instancetype)init:(int)connecton provider:(int)provider; 30 | 31 | - (BOOL)isEqual:(id)other; 32 | - (BOOL)isEqualToInfo:(QNNetworkInfo *)info; 33 | 34 | + (instancetype)noNet; 35 | 36 | + (instancetype)normal; 37 | 38 | + (BOOL)isNetworkChanged; 39 | 40 | + (NSString *)getIp; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNNetworkInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNNetworkInfo.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/25. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #import "QNIP.h" 16 | #import "QNNetworkInfo.h" 17 | 18 | const int kQNNO_NETWORK = -1; 19 | const int kQNWIFI = 1; 20 | const int kQNMOBILE = 2; 21 | 22 | const int kQNISP_GENERAL = 0; 23 | const int kQNISP_CTC = 1; 24 | const int kQNISP_DIANXIN = kQNISP_CTC; 25 | const int kQNISP_CNC = 2; 26 | const int kQNISP_LIANTONG = kQNISP_CNC; 27 | const int kQNISP_CMCC = 3; 28 | const int kQNISP_YIDONG = kQNISP_CMCC; 29 | const int kQNISP_OTHER = 999; 30 | 31 | #define IPLength 64 32 | 33 | static char previousIp[IPLength] = {0}; 34 | static NSString *lock = @""; 35 | 36 | @implementation QNNetworkInfo 37 | 38 | - (instancetype)init:(int)connecton provider:(int)provider { 39 | if (self = [super init]) { 40 | _networkConnection = connecton; 41 | _provider = provider; 42 | } 43 | return self; 44 | } 45 | 46 | + (instancetype)noNet { 47 | return [[QNNetworkInfo alloc] init:kQNNO_NETWORK provider:kQNISP_GENERAL]; 48 | } 49 | 50 | + (instancetype)normal { 51 | return [[QNNetworkInfo alloc] init:kQNISP_GENERAL provider:kQNISP_GENERAL]; 52 | } 53 | 54 | - (BOOL)isEqualToInfo:(QNNetworkInfo *)info { 55 | if (self == info) 56 | return YES; 57 | return self.provider == info.provider && self.networkConnection == info.networkConnection; 58 | } 59 | 60 | - (BOOL)isEqual:(id)other { 61 | if (other == self) 62 | return YES; 63 | if (!other || ![other isKindOfClass:[self class]]) 64 | return NO; 65 | return [self isEqualToInfo:other]; 66 | } 67 | 68 | + (BOOL)isNetworkChanged { 69 | @synchronized(lock) { 70 | char local[IPLength] = {0}; 71 | int err = qn_localIp(local, sizeof(local)); 72 | if (err != 0) { 73 | return YES; 74 | } 75 | if (memcmp(previousIp, local, sizeof(local)) != 0) { 76 | memcpy(previousIp, local, sizeof(local)); 77 | return YES; 78 | } 79 | return NO; 80 | } 81 | } 82 | 83 | + (NSString *)getIp { 84 | return [QNIP local]; 85 | } 86 | @end 87 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNRecord.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNRecord.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * A 记录 13 | */ 14 | extern const int kQNTypeA; 15 | 16 | /** 17 | * AAAA 记录 18 | */ 19 | extern const int kQNTypeAAAA; 20 | 21 | /** 22 | * Cname 记录 23 | */ 24 | extern const int kQNTypeCname; 25 | 26 | /** 27 | * Txt 记录 28 | */ 29 | extern const int kQNTypeTXT; 30 | 31 | /** 32 | * 永久有效的 ttl 33 | */ 34 | extern const int kQNRecordForeverTTL; 35 | 36 | typedef NS_ENUM(NSUInteger, QNRecordSource) { 37 | QNRecordSourceUnknown, 38 | QNRecordSourceCustom, 39 | QNRecordSourceDnspodEnterprise, 40 | QNRecordSourceSystem, 41 | QNRecordSourceUdp, 42 | QNRecordSourceDoh, 43 | }; 44 | 45 | 46 | @interface QNRecord : NSObject 47 | 48 | @property (nonatomic, copy, readonly) NSString *value; 49 | @property (nonatomic, copy, readonly) NSString *server; 50 | @property (nonatomic, readonly) int ttl; 51 | @property (nonatomic, readonly) int type; 52 | @property (nonatomic, readonly) long long timeStamp; 53 | @property (nonatomic, readonly) QNRecordSource source; 54 | 55 | - (instancetype)init:(NSString *)value 56 | ttl:(int)ttl 57 | type:(int)type 58 | source:(QNRecordSource)source; 59 | 60 | - (instancetype)init:(NSString *)value 61 | ttl:(int)ttl 62 | type:(int)type 63 | timeStamp:(long long)timeStamp 64 | server:(NSString *)server 65 | source:(QNRecordSource)source; 66 | 67 | - (BOOL)expired:(long long)time; 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNRecord.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNRecord.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNRecord.h" 10 | 11 | const int kQNTypeA = 1; 12 | const int kQNTypeAAAA = 28; 13 | const int kQNTypeCname = 5; 14 | const int kQNTypeTXT = 16; 15 | const int kQNRecordForeverTTL = -1; 16 | 17 | @implementation QNRecord 18 | - (instancetype)init:(NSString *)value 19 | ttl:(int)ttl 20 | type:(int)type 21 | source:(QNRecordSource)source { 22 | if (self = [super init]) { 23 | _value = value; 24 | _type = type; 25 | _ttl = ttl; 26 | _source = source; 27 | _timeStamp = [[NSDate date] timeIntervalSince1970]; 28 | } 29 | return self; 30 | } 31 | 32 | - (instancetype)init:(NSString *)value 33 | ttl:(int)ttl 34 | type:(int)type 35 | timeStamp:(long long)timeStamp 36 | server:(NSString *)server 37 | source:(QNRecordSource)source { 38 | if (self = [super init]) { 39 | _value = value; 40 | _type = type; 41 | _ttl = ttl; 42 | _server = server; 43 | _source = source; 44 | _timeStamp = timeStamp; 45 | } 46 | return self; 47 | } 48 | 49 | - (BOOL)expired:(long long)time { 50 | if (_ttl == kQNRecordForeverTTL) { 51 | return false; 52 | } 53 | return time > _timeStamp + _ttl; 54 | } 55 | 56 | - (NSString *)description { 57 | return [NSString stringWithFormat:@"value:%@, ttl:%d, timestamp:%lld, type:%d server:%@ source:%lu", _value, _ttl, _timeStamp, _type, _server, (unsigned long)_source]; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /HappyDNS/Common/QNResolverDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNResolverDelegate.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDnsError.h" 10 | 11 | #define QN_DNS_DEFAULT_TIMEOUT 20 //seconds 12 | 13 | @class QNDomain; 14 | @class QNNetworkInfo; 15 | @protocol QNResolverDelegate 16 | 17 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError **)error; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsDefine.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsDefine.h 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/20. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_ENUM(NSInteger, QNDnsOpCode) { 11 | QNDnsOpCodeQuery = 0, // 标准查询 12 | QNDnsOpCodeIQuery = 1, // 反向查询 13 | QNDnsOpCodeStatus = 2, // DNS状态请求 14 | QNDnsOpCodeUpdate = 5, // DNS域更新请求 15 | }; 16 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsMessage.h 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface QNDnsMessage : NSObject 13 | 14 | /** 15 | * 16位的消息ID标示一次正常的交互,该ID由消息请求者设置,消息响应者回复请求时带上该ID。最大:0xFFFF,即:65536 16 | */ 17 | @property(nonatomic, assign, readonly)int messageId; 18 | 19 | /** 20 | * 请求类型,目前有三类值: 21 | * 0 QUERY, 标准查询 22 | * 1 IQUERY, 反向查询 23 | * 2 STATUS, DNS状态请求 24 | * 5 UPDATE, DNS域更新请求 25 | */ 26 | @property(nonatomic, assign, readonly)int opCode; 27 | 28 | /** 29 | * 是否递归查询。如果该位被设置为1,则收到请求的域名服务器会递归查询域名, 30 | * 注: 该位为1,域名服务器不一定会做递归查询,这取决于域名服务器是否支持递归查询。 31 | */ 32 | @property(nonatomic, assign, readonly)int rd; 33 | 34 | /** 35 | * 在响应消息中清除并设置。表示该DNS域名服务器是否支持递归查询。 36 | */ 37 | @property(nonatomic, assign, readonly)int ra; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsMessage.m 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/20. 6 | // 7 | 8 | #import "QNDnsMessage.h" 9 | 10 | @implementation QNDnsMessage 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // DnsQuestion.h 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/16. 6 | // 7 | 8 | #import "QNDnsDefine.h" 9 | #import "QNDnsMessage.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface QNDnsRequest : QNDnsMessage 14 | 15 | @property(nonatomic, assign, readonly)int recordType; 16 | @property(nonatomic, copy, readonly)NSString *host; 17 | 18 | /// 构造函数 19 | /// @param messageId 请求 id 20 | /// @param recordType 记录类型 21 | /// @param host 需要进行 Dns 解析的 host 22 | + (instancetype)request:(int)messageId 23 | recordType:(int)recordType 24 | host:(NSString *)host; 25 | 26 | /// 构造函数 27 | /// @param messageId 请求 id 28 | /// @param opCode 请求类型 29 | /// @param rd 是否递归查询。如果该位被设置为1,则收到请求的域名服务器会递归查询域名 30 | /// 注: 该位为1,域名服务器不一定会做递归查询,这取决于域名服务器是否支持递归查询。 31 | /// @param recordType 记录类型 32 | /// @param host 需要进行 Dns 解析的 host 33 | + (instancetype)request:(int)messageId 34 | opCode:(QNDnsOpCode)opCode 35 | rd:(int)rd 36 | recordType:(int)recordType 37 | host:(NSString *)host; 38 | 39 | - (NSData *)toDnsQuestionData:(NSError **)error; 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // DnsQuestion.m 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/16. 6 | // 7 | 8 | #import "QNRecord.h" 9 | #import "NSData+QNRW.h" 10 | #import "QNDnsError.h" 11 | #import "QNDnsRequest.h" 12 | 13 | @interface QNDnsRequest() 14 | 15 | @property(nonatomic, assign)int messageId; 16 | @property(nonatomic, assign)QNDnsOpCode opCode; 17 | @property(nonatomic, assign)int rd; 18 | @property(nonatomic, assign)int recordType; 19 | @property(nonatomic, copy)NSString *host; 20 | 21 | @end 22 | @implementation QNDnsRequest 23 | @synthesize messageId; 24 | @synthesize opCode; 25 | @synthesize rd; 26 | 27 | + (instancetype)request:(int)messageId 28 | recordType:(int)recordType 29 | host:(NSString *)host { 30 | return [self request:messageId opCode:QNDnsOpCodeQuery rd:1 recordType:recordType host:host]; 31 | } 32 | 33 | + (instancetype)request:(int)messageId 34 | opCode:(QNDnsOpCode)opCode 35 | rd:(int)rd 36 | recordType:(int)recordType 37 | host:(NSString *)host { 38 | QNDnsRequest *request = [[QNDnsRequest alloc] init]; 39 | request.messageId = messageId; 40 | request.opCode = opCode; 41 | request.rd = rd; 42 | request.recordType = recordType; 43 | request.host = host; 44 | return request; 45 | } 46 | 47 | - (NSData *)toDnsQuestionData:(NSError *__autoreleasing _Nullable *)error { 48 | if (self.host == nil || self.host.length == 0) { 49 | [self copyError:kQNDnsInvalidParamError(@"host can not empty") toErrorPoint:error]; 50 | return nil; 51 | } 52 | 53 | if (self.opCode != QNDnsOpCodeQuery && 54 | self.opCode != QNDnsOpCodeIQuery && 55 | self.opCode != QNDnsOpCodeStatus && 56 | self.opCode != QNDnsOpCodeUpdate) { 57 | [self copyError:kQNDnsInvalidParamError(@"opCode is not valid") toErrorPoint:error]; 58 | return nil; 59 | } 60 | 61 | if (self.rd != 0 && self.rd != 1) { 62 | [self copyError:kQNDnsInvalidParamError(@"rd is not valid") toErrorPoint:error]; 63 | return nil; 64 | } 65 | 66 | if (self.recordType != kQNTypeA && 67 | self.recordType != kQNTypeCname && 68 | self.recordType != kQNTypeTXT && 69 | self.recordType != kQNTypeAAAA) { 70 | [self copyError:kQNDnsInvalidParamError(@"recordType is not valid") toErrorPoint:error]; 71 | return nil; 72 | } 73 | 74 | NSMutableData *data = [NSMutableData data]; 75 | [data qn_appendBigEndianInt16:self.messageId]; // 16 bit id 76 | // |00|01|02|03|04|05|06|07| 77 | // |QR| OPCODE |AA|TC|RD| 78 | [data qn_appendInt8:(self.opCode<<3) + self.rd]; 79 | // |00|01|02|03|04|05|06|07| 80 | // |RA|r1|r2|r3| RCODE | 81 | [data qn_appendInt8:0x00]; 82 | [data qn_appendInt8:0x00]; 83 | [data qn_appendInt8:0x01]; // QDCOUNT (number of entries in the question section) 84 | [data qn_appendInt8:0x00]; 85 | [data qn_appendInt8:0x00]; // ANCOUNT 86 | [data qn_appendInt8:0x00]; 87 | [data qn_appendInt8:0x00]; // NSCOUNT 88 | [data qn_appendInt8:0x00]; 89 | [data qn_appendInt8:0x00]; // ARCOUNT 90 | 91 | NSArray *hostParts = [self.host componentsSeparatedByString:@"."]; 92 | for (NSString *part in hostParts) { 93 | if (part.length > 63) { 94 | return nil; 95 | } 96 | [data qn_appendInt8:part.length]; 97 | [data qn_appendString:part usingEncoding:NSUTF8StringEncoding]; 98 | } 99 | [data qn_appendInt8:0x00]; /* terminating zero */ 100 | [data qn_appendInt8:0x00]; 101 | [data qn_appendInt8:self.recordType]; 102 | [data qn_appendInt8:0x00]; 103 | [data qn_appendInt8:0x01]; /* IN - "the Internet" */ 104 | 105 | return data; 106 | } 107 | 108 | - (void)copyError:(NSError *)error toErrorPoint:(NSError **)errorPoint { 109 | if (errorPoint != nil) { 110 | *errorPoint = error; 111 | } 112 | } 113 | 114 | - (NSString *)description { 115 | return [NSString stringWithFormat:@"messageId:%d opcode:%ld rd:%d ra:%d type:%ld", self.messageId, (long)self.opCode, self.rd, self.ra, (long)self.recordType]; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsResolver.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsResolver.h 3 | // HappyDNS 4 | // 5 | // Created by yangsen on 2021/7/28. 6 | // Copyright © 2021 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDnsDefine.h" 10 | #import "QNRecord.h" 11 | #import "QNResolverDelegate.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @class QNDnsResponse; 16 | // 抽象对象,不能直接使用,使用其子类 17 | @interface QNDnsResolver : NSObject 18 | 19 | @property(nonatomic, assign, readonly)int recordType; 20 | @property(nonatomic, assign, readonly)int timeout; 21 | @property(nonatomic, copy, readonly)NSArray *servers; 22 | 23 | 24 | 25 | // 抽象方法,子类实现 26 | - (void)request:(NSString *)server 27 | host:(NSString *)host 28 | recordType:(int)recordType 29 | complete:(void(^)(QNDnsResponse *response, NSError *error))complete; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsResolver.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsResolver.m 3 | // HappyDNS 4 | // 5 | // Created by yangsen on 2021/7/28. 6 | // Copyright © 2021 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNRecord.h" 10 | #import "QNDomain.h" 11 | #import "QNDnsError.h" 12 | #import "QNDnsResponse.h" 13 | #import "QNDnsResolver.h" 14 | 15 | @interface QNDnsResolver() 16 | 17 | @property(nonatomic, strong)dispatch_queue_t timerQueue; 18 | 19 | @end 20 | @implementation QNDnsResolver 21 | + (dispatch_queue_t)timeoutQueue { 22 | static dispatch_once_t onceToken; 23 | static dispatch_queue_t timerQueue; 24 | dispatch_once(&onceToken, ^{ 25 | timerQueue = dispatch_queue_create("com.happyDns.timeoutQueue", DISPATCH_QUEUE_CONCURRENT); 26 | }); 27 | return timerQueue; 28 | } 29 | 30 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error { 31 | NSError *err = nil; 32 | QNDnsResponse *response = [self lookupHost:domain.domain error:&err]; 33 | if (err != nil) { 34 | *error = err; 35 | return @[]; 36 | } 37 | 38 | NSMutableArray *records = [NSMutableArray array]; 39 | for (QNRecord *record in response.answerArray) { 40 | if (record.type == kQNTypeA || record.type == kQNTypeAAAA || record.type == kQNTypeCname) { 41 | [records addObject:record]; 42 | } 43 | } 44 | return [records copy]; 45 | } 46 | 47 | - (QNDnsResponse *)lookupHost:(NSString *)host error:(NSError *__autoreleasing _Nullable *)error { 48 | 49 | // 异步转同步 50 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 51 | 52 | __block NSError *errorP = nil; 53 | __block QNDnsResponse *dnsResponse = nil; 54 | [self request:host recordType:self.recordType complete:^(QNDnsResponse *response, NSError *err) { 55 | errorP = err; 56 | dnsResponse = response; 57 | dispatch_semaphore_signal(semaphore); 58 | }]; 59 | dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, self.timeout * NSEC_PER_SEC)); 60 | 61 | if (error != NULL) { 62 | *error = errorP; 63 | } 64 | 65 | return dnsResponse; 66 | } 67 | 68 | - (void)request:(NSString *)host 69 | recordType:(int)recordType 70 | complete:(void(^)(QNDnsResponse *response, NSError *error))complete { 71 | if (complete == nil) { 72 | return; 73 | } 74 | 75 | if (self.servers == nil || self.servers.count == 0) { 76 | complete(nil, kQNDnsInvalidParamError(@"server can not empty")); 77 | return; 78 | } 79 | 80 | NSLock *locker = [[NSLock alloc] init]; 81 | __block BOOL hasCallBack = false; 82 | __block BOOL completeCount = 0; 83 | 84 | // 超时处理 85 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.timeout * NSEC_PER_SEC)), [QNDnsResolver timeoutQueue], ^{ 86 | BOOL shouldCallBack = false; 87 | [locker lock]; 88 | if (!hasCallBack) { 89 | shouldCallBack = true; 90 | hasCallBack = true; 91 | } 92 | [locker unlock]; 93 | 94 | if (shouldCallBack) { 95 | NSString *error = [NSString stringWithFormat:@"resolver timeout for server:%@ host:%@",[self.servers description], host]; 96 | complete(nil, kQNDnsInvalidParamError(error)); 97 | } 98 | }); 99 | 100 | for (NSString *server in self.servers) { 101 | [self request:server host:host recordType:recordType complete:^(QNDnsResponse *response, NSError *error) { 102 | BOOL shouldCallBack = false; 103 | 104 | [locker lock]; 105 | completeCount++; 106 | if (completeCount == self.servers.count || (response != nil && response.rCode == 0 && !hasCallBack)) { 107 | shouldCallBack = true; 108 | hasCallBack = true; 109 | } 110 | [locker unlock]; 111 | 112 | if (shouldCallBack) { 113 | complete(response, error); 114 | } 115 | }]; 116 | } 117 | 118 | } 119 | 120 | - (void)request:(NSString *)server 121 | host:(NSString *)host 122 | recordType:(int)recordType 123 | complete:(void(^)(QNDnsResponse *response, NSError *error))complete { 124 | if (complete != nil) { 125 | complete(nil, kQNDnsMethodError(@"use sub class of QNDnsResolver")); 126 | } 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // DnsRecord.h 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/16. 6 | // 7 | 8 | #import "QNRecord.h" 9 | #import "QNDnsRequest.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface QNDnsResponse : QNDnsMessage 14 | 15 | @property(nonatomic, assign, readonly)NSInteger timestamp; 16 | @property(nonatomic, assign, readonly)QNRecordSource source; 17 | @property(nonatomic, copy, readonly)NSString *server; 18 | @property(nonatomic, strong, readonly)QNDnsRequest *request; 19 | @property(nonatomic, strong, readonly)NSData *recordData; 20 | 21 | 22 | /** 23 | * 响应该消息的域名服务器是该域中的权威域名服务器。因为Answer Section中可能会有很多域名 24 | */ 25 | @property(nonatomic, assign, readonly)int aa; 26 | 27 | /** 28 | * 响应消息的类型: 29 | * 0 成功的响应 30 | * 1 格式错误--域名服务器无法解析请求,因为请求消息格式错误 31 | * 2 服务器错误--域名服务器因为内部错误无法解析该请求 32 | * 3 名字错误-- 只在权威域名服务器的响应消息中有效,标示请求中请求的域不存在 33 | * 4 Not Implemented--域名服务器不支持请求的类型 34 | * 5 Refused -- 域名服务器因为策略的原因拒绝执行请求的操作。例如域名服务器不会为特定的请求者返回查询结果,或者域名服务器不会为特定的请求返回特定的数据 35 | */ 36 | @property(nonatomic, assign, readonly)int rCode; 37 | 38 | @property(nonatomic, copy, readonly)NSArray *answerArray; 39 | @property(nonatomic, copy, readonly)NSArray *authorityArray; 40 | @property(nonatomic, copy, readonly)NSArray *additionalArray; 41 | 42 | + (instancetype)dnsResponse:(NSString *)server source:(QNRecordSource)source request:(QNDnsRequest *)request dnsRecordData:(NSData *)recordData error:(NSError **)error; 43 | 44 | @end 45 | 46 | NS_ASSUME_NONNULL_END 47 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // DnsRecord.m 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/16. 6 | // 7 | #import "QNRecord.h" 8 | #import "QNDnsError.h" 9 | #import "NSData+QNRW.h" 10 | #import "QNDnsResponse.h" 11 | 12 | 13 | /// DNS 记录中的名字可能是引用,需要到指定的 index 读取,所以需要跳过的长度不一定是 name 的长度 14 | @interface QNDnsRecordName : NSObject 15 | 16 | @property(nonatomic, assign)NSInteger skipLength; 17 | @property(nonatomic, copy)NSString *name; 18 | 19 | @end 20 | @implementation QNDnsRecordName 21 | @end 22 | 23 | 24 | @interface QNDnsRecordResource : NSObject 25 | 26 | @property(nonatomic, copy)NSString *name; 27 | @property(nonatomic, assign)int count; 28 | @property(nonatomic, assign)int from; 29 | @property(nonatomic, assign)int length; 30 | @property(nonatomic, strong)NSMutableArray *records; 31 | 32 | @end 33 | @implementation QNDnsRecordResource 34 | + (instancetype)resource:(NSString *)name count:(int)count from:(int)from { 35 | QNDnsRecordResource *resource = [[QNDnsRecordResource alloc] init]; 36 | resource.name = name; 37 | resource.count = count; 38 | resource.from = from; 39 | resource.length = 0; 40 | resource.records = [NSMutableArray array]; 41 | return resource; 42 | } 43 | @end 44 | 45 | 46 | @interface QNDnsResponse() 47 | 48 | @property(nonatomic, assign)NSInteger timestamp; 49 | @property(nonatomic, assign)QNRecordSource source; 50 | @property(nonatomic, copy)NSString *server; 51 | @property(nonatomic, strong)QNDnsRequest *request; 52 | @property(nonatomic, strong)NSData *recordData; 53 | 54 | @property(nonatomic, assign)int messageId; 55 | @property(nonatomic, assign)QNDnsOpCode opCode; 56 | @property(nonatomic, assign)int aa; 57 | @property(nonatomic, assign)int ra; 58 | @property(nonatomic, assign)int rd; 59 | @property(nonatomic, assign)int rCode; 60 | 61 | @property(nonatomic, copy)NSArray *answerArray; 62 | @property(nonatomic, copy)NSArray *authorityArray; 63 | @property(nonatomic, copy)NSArray *additionalArray; 64 | 65 | @end 66 | @implementation QNDnsResponse 67 | @synthesize messageId; 68 | @synthesize opCode; 69 | @synthesize aa; 70 | @synthesize ra; 71 | @synthesize rd; 72 | @synthesize rCode; 73 | 74 | + (instancetype)dnsResponse:(NSString *)server source:(QNRecordSource)source request:(QNDnsRequest *)request dnsRecordData:(NSData *)recordData error:(NSError *__autoreleasing _Nullable *)error { 75 | QNDnsResponse *record = [[QNDnsResponse alloc] init]; 76 | record.server = server; 77 | record.source = source; 78 | record.request = request; 79 | record.recordData = recordData; 80 | record.timestamp = [[NSDate date] timeIntervalSince1970]; 81 | 82 | NSError *err = nil; 83 | [record parse:&err]; 84 | if (error != nil) { 85 | *error = err; 86 | } 87 | return record; 88 | } 89 | 90 | - (void)parse:(NSError **)error { 91 | 92 | if (self.recordData.length < 12) { 93 | [self copyError:kQNDnsResponseFormatError(@"response data too small") toErrorPoint:error]; 94 | return; 95 | } 96 | 97 | // Header 98 | [self parseHeader:error]; 99 | if (error != nil && *error != nil) { 100 | return; 101 | } 102 | 103 | // Question 104 | int index = [self parseQuestion:error]; 105 | if (error != nil && *error != nil) { 106 | return; 107 | } 108 | 109 | // Answer 110 | QNDnsRecordResource *answer = [QNDnsRecordResource resource:@"answer" 111 | count:[self.recordData qn_readBigEndianInt16:6] 112 | from:index]; 113 | [self parseResourceRecord:answer error:error]; 114 | if (error != nil && *error != nil) { 115 | return; 116 | } 117 | index += answer.length; 118 | self.answerArray = [answer.records copy]; 119 | 120 | // Authority 121 | QNDnsRecordResource *authority = [QNDnsRecordResource resource:@"authority" 122 | count:[self.recordData qn_readBigEndianInt16:8] 123 | from:index]; 124 | [self parseResourceRecord:authority error:error]; 125 | if (error != nil && *error != nil) { 126 | return; 127 | } 128 | index += authority.length; 129 | self.authorityArray = [authority.records copy]; 130 | 131 | // Additional 132 | QNDnsRecordResource *additional = [QNDnsRecordResource resource:@"additional" 133 | count:[self.recordData qn_readBigEndianInt16:10] 134 | from:index]; 135 | [self parseResourceRecord:additional error:error]; 136 | if (error != nil && *error != nil) { 137 | return; 138 | } 139 | self.additionalArray = [additional.records copy]; 140 | } 141 | 142 | - (void)parseHeader:(NSError **)error { 143 | self.messageId = [self.recordData qn_readBigEndianInt16:0]; 144 | // question id 不匹配 145 | if (self.messageId != self.request.messageId) { 146 | [self copyError:kQNDnsResponseFormatError(@"question id error") toErrorPoint:error]; 147 | return; 148 | } 149 | 150 | // |00|01|02|03|04|05|06|07| 151 | // |QR| OPCODE |AA|TC|RD| 152 | int field0 = [self.recordData qn_readInt8:2]; 153 | int qr = [self.recordData qn_readInt8:2] & 0x80; 154 | // 非 dns 响应数据 155 | if (qr == 0) { 156 | [self copyError:kQNDnsResponseFormatError(@"not a response data") toErrorPoint:error]; 157 | return; 158 | } 159 | 160 | self.opCode = (field0 >> 3) & 0x07; 161 | self.aa = (field0 >> 2) & 0x01; 162 | self.rd = field0 & 0x01; 163 | 164 | // |00|01|02|03|04|05|06|07| 165 | // |RA|r1|r2|r3| RCODE | 166 | int field1 = [self.recordData qn_readInt8:3]; 167 | self.ra = (field1 >> 7) & 0x1; 168 | self.rCode = field1 & 0x0F; 169 | } 170 | 171 | - (int)parseQuestion:(NSError **)error { 172 | int index = 12; 173 | int qdCount = [self.recordData qn_readBigEndianInt16:4]; 174 | while (qdCount) { 175 | QNDnsRecordName *recordName = [self getNameFrom:index]; 176 | if (recordName == nil) { 177 | [self copyError:kQNDnsResponseFormatError(@"read Question error") toErrorPoint:error]; 178 | return -1; 179 | } 180 | 181 | if (self.recordData.length < (index + recordName.skipLength + 4)) { 182 | [self copyError:kQNDnsResponseFormatError(@"read Question error: out of range") toErrorPoint:error]; 183 | return -1; 184 | } 185 | 186 | index += recordName.skipLength + 4; 187 | qdCount --; 188 | } 189 | return index; 190 | } 191 | 192 | - (void)parseResourceRecord:(QNDnsRecordResource *)resource error:(NSError **)error { 193 | int index = resource.from; 194 | int count = resource.count; 195 | while (count) { 196 | QNDnsRecordName *recordName = [self getNameFrom:index]; 197 | if (recordName == nil) { 198 | NSString *errorDesc = [NSString stringWithFormat:@"read %@ error", resource.name]; 199 | [self copyError:kQNDnsResponseFormatError(errorDesc) toErrorPoint:error]; 200 | return; 201 | } 202 | 203 | index += recordName.skipLength; 204 | if (self.recordData.length < (index + 2)) { 205 | NSString *errorDesc = [NSString stringWithFormat:@"read %@ error: out of range", resource.name]; 206 | [self copyError:kQNDnsResponseFormatError(errorDesc) toErrorPoint:error]; 207 | return; 208 | } 209 | 210 | int type = [self.recordData qn_readBigEndianInt16:index]; 211 | index += 2; 212 | 213 | if (self.recordData.length < (index + 2)) { 214 | NSString *errorDesc = [NSString stringWithFormat:@"%@ read Answer error: out of range", resource.name]; 215 | [self copyError:kQNDnsResponseFormatError(errorDesc) toErrorPoint:error]; 216 | return; 217 | } 218 | 219 | int class = [self.recordData qn_readBigEndianInt16:index]; 220 | index += 2; 221 | 222 | if (self.recordData.length < (index + 4)) { 223 | NSString *errorDesc = [NSString stringWithFormat:@"%@ read Answer error: out of range", resource.name]; 224 | [self copyError:kQNDnsResponseFormatError(errorDesc) toErrorPoint:error]; 225 | return; 226 | } 227 | 228 | int ttl = [self.recordData qn_readBigEndianInt32:index]; 229 | index += 4; 230 | 231 | if (self.recordData.length < (index + 2)) { 232 | NSString *errorDesc = [NSString stringWithFormat:@"%@ read Answer error: out of range", resource.name]; 233 | [self copyError:kQNDnsResponseFormatError(errorDesc) toErrorPoint:error]; 234 | return; 235 | } 236 | 237 | int rdLength = [self.recordData qn_readBigEndianInt16:index]; 238 | index += 2; 239 | if (self.recordData.length < (index + rdLength)) { 240 | NSString *errorDesc = [NSString stringWithFormat:@"%@ read Answer error: out of range", resource.name]; 241 | [self copyError:kQNDnsResponseFormatError(errorDesc) toErrorPoint:error]; 242 | return; 243 | } 244 | 245 | NSString *value = [self readData:type range:NSMakeRange(index, rdLength)]; 246 | 247 | if (class == 0x01 && (type == kQNTypeCname || type == self.request.recordType)) { 248 | QNRecord *record = [[QNRecord alloc] init:[value copy] ttl:ttl type:type timeStamp:self.timestamp server:self.server source:self.source]; 249 | [resource.records addObject:record]; 250 | } 251 | 252 | index += rdLength; 253 | count --; 254 | } 255 | resource.length = index - resource.from; 256 | } 257 | 258 | - (QNDnsRecordName *)getNameFrom:(NSInteger)fromIndex { 259 | 260 | NSInteger partLength = 0; 261 | NSInteger index = fromIndex; 262 | NSMutableString *name = [NSMutableString string]; 263 | QNDnsRecordName *recordName = [[QNDnsRecordName alloc] init]; 264 | 265 | int maxLoop = 128; 266 | do { 267 | if (index >= self.recordData.length) { 268 | return nil; 269 | } 270 | 271 | partLength = [self.recordData qn_readInt8:index]; 272 | if ((partLength & 0xc0) == 0xc0) { 273 | // name pointer 274 | if((index + 1) >= self.recordData.length) { 275 | return nil; 276 | } 277 | if (recordName.skipLength < 1) { 278 | recordName.skipLength = index + 2 - fromIndex; 279 | } 280 | index = (partLength & 0x3f) << 8 | [self.recordData qn_readInt8:index + 1]; 281 | continue; 282 | } else if((partLength & 0xc0) > 0) { 283 | return nil; 284 | } else { 285 | index++; 286 | } 287 | 288 | if (partLength > 0) { 289 | if (name.length > 0) { 290 | [name appendString:@"."]; 291 | } 292 | 293 | if (index + partLength > self.recordData.length) { 294 | return nil; 295 | } 296 | 297 | NSData *nameData = [self.recordData subdataWithRange:NSMakeRange(index, partLength)]; 298 | [name appendString:[[NSString alloc] initWithData:nameData encoding:NSUTF8StringEncoding]]; 299 | index += partLength; 300 | } 301 | 302 | } while (partLength && --maxLoop); 303 | 304 | recordName.name = name; 305 | if (recordName.skipLength < 1) { 306 | recordName.skipLength = index - fromIndex; 307 | } 308 | return recordName; 309 | } 310 | 311 | - (NSString *)readData:(int)recordType range:(NSRange)range { 312 | 313 | NSString *dataString = nil; 314 | NSData *dataValue = [self.recordData subdataWithRange:range]; 315 | if (recordType == kQNTypeA) { 316 | if (dataValue.length == 4) { 317 | dataString = [NSString stringWithFormat:@"%d.%d.%d.%d", [dataValue qn_readInt8:0], [dataValue qn_readInt8:1], [dataValue qn_readInt8:2], [dataValue qn_readInt8:3]]; 318 | } 319 | } else if (recordType == kQNTypeAAAA) { 320 | if (dataValue.length == 16) { 321 | NSMutableString *ipv6 = [NSMutableString string]; 322 | for (int i=0; i<16; i+=2) { 323 | [ipv6 appendFormat:@"%@%02x%02x",(i?@":":@""), [dataValue qn_readInt8:i], [dataValue qn_readInt8:i+1]]; 324 | } 325 | dataString = [ipv6 copy]; 326 | } 327 | } else if (recordType == kQNTypeCname) { 328 | if (dataValue.length > 1) { 329 | QNDnsRecordName *name = [self getNameFrom:range.location]; 330 | dataString = [name.name copy]; 331 | } 332 | } else if (recordType == kQNTypeTXT) { 333 | if (dataValue.length > 1) { 334 | dataString = [[NSString alloc] initWithData:[dataValue subdataWithRange:NSMakeRange(1, dataValue.length - 1)] encoding:NSUTF8StringEncoding]; 335 | } 336 | } 337 | return dataString; 338 | } 339 | 340 | - (void)copyError:(NSError *)error toErrorPoint:(NSError **)errorPoint { 341 | if (errorPoint != nil) { 342 | *errorPoint = error; 343 | } 344 | } 345 | 346 | - (NSString *)description { 347 | return [NSString stringWithFormat:@"{messageId:%d, rd:%d, ra:%d, aa:%d, rCode:%d, server:%@, request:%@, answerArray:%@, authorityArray:%@, additionalArray:%@}", self.messageId, self.rd, self.ra, self.aa, self.rCode, self.server, self.request, self.answerArray, self.authorityArray, self.additionalArray]; 348 | } 349 | 350 | @end 351 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsUdpResolver.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsServer.h 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/20. 6 | // 7 | 8 | #import "QNDnsResolver.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface QNDnsUdpResolver : QNDnsResolver 13 | 14 | 15 | /// 构造函数 16 | /// @param serverIP 指定 dns local server1. eg:114.114.114.114 17 | + (instancetype)resolverWithServerIP:(NSString *)serverIP; 18 | 19 | /// 构造函数 20 | /// @param serverIP 指定 dns local server1. eg:114.114.114.114 21 | /// @param recordType 记录类型 eg:kQNTypeA 22 | /// @param timeout 超时时间 23 | + (instancetype)resolverWithServerIP:(NSString *)serverIP 24 | recordType:(int)recordType 25 | timeout:(int)timeout; 26 | 27 | /// 构造函数 28 | /// @param serverIPs 指定多个 dns local server,同时进行 dns 解析,当第一个有效数据返回时结束,或均为解析到数据时结束. eg:@[@"8.8.8.8"] 29 | /// @param recordType 记录类型 eg:kQNTypeA 30 | /// @param timeout 超时时间 31 | + (instancetype)resolverWithServerIPs:(NSArray *)serverIPs 32 | recordType:(int)recordType 33 | timeout:(int)timeout; 34 | 35 | /// 构造函数 36 | /// @param serverIPs 指定多个 dns local server,同时进行 dns 解析,当第一个有效数据返回时结束,或均为解析到数据时结束. eg:@[@"8.8.8.8"] 37 | /// @param recordType 记录类型 eg:kQNTypeA 38 | /// @param queue 多个 udp 包所在的 queue 39 | /// @param timeout 超时时间 40 | + (instancetype)resolverWithServerIPs:(NSArray *)serverIPs 41 | recordType:(int)recordType 42 | queue:(dispatch_queue_t _Nullable)queue 43 | timeout:(int)timeout; 44 | 45 | @end 46 | 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDnsUdpResolver.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnsServer.m 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/20. 6 | // 7 | 8 | #import "QNRecord.h" 9 | #import "QNDomain.h" 10 | #import "QNDnsError.h" 11 | #import "QNDnsResponse.h" 12 | #import "QNDnsUdpResolver.h" 13 | #import "QNAsyncUdpSocket.h" 14 | 15 | @interface QNDnsFlow : NSObject 16 | 17 | @property(nonatomic, assign)long flowId; 18 | @property(nonatomic, copy)NSString *server; 19 | @property(nonatomic, strong)QNDnsRequest *dnsRequest; 20 | @property(nonatomic, strong)QNAsyncUdpSocket *socket; 21 | @property(nonatomic, copy)void(^complete)(QNDnsResponse *response, NSError *error); 22 | 23 | @end 24 | @implementation QNDnsFlow 25 | @end 26 | 27 | #define kDnsPort 53 28 | @interface QNDnsUdpResolver() 29 | 30 | @property(nonatomic, assign)int recordType; 31 | @property(nonatomic, assign)int timeout; 32 | @property(nonatomic, copy)NSArray *servers; 33 | @property(nonatomic, strong)dispatch_queue_t queue; 34 | @property(nonatomic, strong)NSMutableDictionary *flows; 35 | 36 | @end 37 | 38 | @implementation QNDnsUdpResolver 39 | @synthesize recordType; 40 | @synthesize timeout; 41 | @synthesize servers; 42 | 43 | + (instancetype)resolverWithServerIP:(NSString *)serverIP { 44 | return [self resolverWithServerIP:serverIP recordType:kQNTypeA timeout:QN_DNS_DEFAULT_TIMEOUT]; 45 | } 46 | 47 | + (instancetype)resolverWithServerIP:(NSString *)serverIP 48 | recordType:(int)recordType 49 | timeout:(int)timeout { 50 | return [self resolverWithServerIPs:serverIP ? @[serverIP] : @[] recordType:recordType timeout:timeout]; 51 | } 52 | 53 | + (instancetype)resolverWithServerIPs:(NSArray *)serverIPs 54 | recordType:(int)recordType 55 | timeout:(int)timeout { 56 | return [self resolverWithServerIPs:serverIPs recordType:recordType queue:nil timeout:timeout]; 57 | } 58 | 59 | + (instancetype)resolverWithServerIPs:(NSArray *)servers 60 | recordType:(int)recordType 61 | queue:(dispatch_queue_t _Nullable)queue 62 | timeout:(int)timeout { 63 | 64 | QNDnsUdpResolver *resolver = [[self alloc] init]; 65 | resolver.recordType = recordType; 66 | resolver.servers = [servers copy] ?: @[]; 67 | resolver.timeout = timeout; 68 | resolver.queue = queue; 69 | return resolver; 70 | } 71 | 72 | + (dispatch_queue_t)defaultQueue { 73 | static dispatch_once_t onceToken; 74 | static dispatch_queue_t timerQueue; 75 | dispatch_once(&onceToken, ^{ 76 | timerQueue = dispatch_queue_create("com.qiniu.dns.udp.queue", DISPATCH_QUEUE_CONCURRENT); 77 | }); 78 | return timerQueue; 79 | } 80 | 81 | - (dispatch_queue_t)queue { 82 | if (_queue == nil) { 83 | _queue = [QNDnsUdpResolver defaultQueue]; 84 | } 85 | return _queue; 86 | } 87 | 88 | - (NSMutableDictionary *)flows { 89 | if (_flows == nil) { 90 | _flows = [NSMutableDictionary dictionary]; 91 | } 92 | return _flows; 93 | } 94 | 95 | - (void)request:(NSString *)server 96 | host:(NSString *)host 97 | recordType:(int)recordType 98 | complete:(void(^)(QNDnsResponse *response, NSError *error))complete { 99 | if (complete == nil) { 100 | return; 101 | } 102 | 103 | int messageId = arc4random()%(0xFFFF); 104 | QNDnsRequest *dnsRequest = [QNDnsRequest request:messageId recordType:recordType host:host]; 105 | 106 | NSError *error = nil; 107 | NSData *requestData = [dnsRequest toDnsQuestionData:&error]; 108 | if (error) { 109 | complete(nil, error); 110 | return; 111 | } 112 | 113 | QNAsyncUdpSocket *socket = [[QNAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:self.queue]; 114 | // 由系统决定端口号 115 | [socket bindToPort:0 error: &error]; 116 | if (error) { 117 | complete(nil, error); 118 | return; 119 | } 120 | 121 | [socket beginReceiving:&error]; 122 | if (error) { 123 | complete(nil, error); 124 | return; 125 | } 126 | 127 | QNDnsFlow *flow = [[QNDnsFlow alloc] init]; 128 | flow.flowId = [socket hash]; 129 | flow.server = server; 130 | flow.dnsRequest = dnsRequest; 131 | flow.socket = socket; 132 | flow.complete = complete; 133 | [self setFlow:flow withId:flow.flowId]; 134 | 135 | [socket sendData:requestData toHost:server port:kDnsPort withTimeout:self.timeout tag:flow.flowId]; 136 | } 137 | 138 | - (void)udpSocketComplete:(QNAsyncUdpSocket *)sock data:(NSData *)data error:(NSError * _Nullable)error { 139 | [sock close]; 140 | 141 | QNDnsFlow *flow = [self getFlowWithId:[sock hash]]; 142 | if (!flow) { 143 | return; 144 | } 145 | [self removeFlowWithId:flow.flowId]; 146 | 147 | if (error != nil) { 148 | flow.complete(nil, error); 149 | } else if (data != nil) { 150 | NSError *err = nil; 151 | QNDnsResponse *response = [QNDnsResponse dnsResponse:flow.server source:QNRecordSourceUdp request:flow.dnsRequest dnsRecordData:data error:&err]; 152 | flow.complete(response, err); 153 | } else { 154 | flow.complete(nil, nil); 155 | } 156 | } 157 | 158 | //MARK: -- QNAsyncUdpSocketDelegate 159 | - (void)udpSocket:(QNAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address { 160 | } 161 | 162 | - (void)udpSocket:(QNAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error { 163 | [self udpSocketComplete:sock data:nil error:error]; 164 | } 165 | 166 | - (void)udpSocket:(QNAsyncUdpSocket *)sock didSendDataWithTag:(long)tag { 167 | } 168 | 169 | - (void)udpSocket:(QNAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error { 170 | [self udpSocketComplete:sock data:nil error:error]; 171 | } 172 | 173 | - (void)udpSocket:(QNAsyncUdpSocket *)sock 174 | didReceiveData:(NSData *)data 175 | fromAddress:(NSData *)address 176 | withFilterContext:(nullable id)filterContext { 177 | [self udpSocketComplete:sock data:data error:nil]; 178 | } 179 | 180 | - (void)udpSocketDidClose:(QNAsyncUdpSocket *)sock withError:(NSError * _Nullable)error { 181 | [self udpSocketComplete:sock data:nil error:error]; 182 | } 183 | 184 | 185 | //MARK: flows 186 | - (QNDnsFlow *)getFlowWithId:(long)flowId { 187 | NSString *key = [NSString stringWithFormat:@"%ld", flowId]; 188 | QNDnsFlow *flow = nil; 189 | @synchronized (self) { 190 | flow = self.flows[key]; 191 | } 192 | return flow; 193 | } 194 | 195 | - (BOOL)setFlow:(QNDnsFlow *)flow withId:(long)flowId { 196 | if (flow == nil) { 197 | return false; 198 | } 199 | 200 | NSString *key = [NSString stringWithFormat:@"%ld", flowId]; 201 | @synchronized (self) { 202 | self.flows[key] = flow; 203 | } 204 | return true; 205 | } 206 | 207 | - (void)removeFlowWithId:(long)flowId { 208 | NSString *key = [NSString stringWithFormat:@"%ld", flowId]; 209 | @synchronized (self) { 210 | [self.flows removeObjectForKey:key]; 211 | } 212 | } 213 | 214 | @end 215 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDohResolver.h: -------------------------------------------------------------------------------- 1 | // 2 | // Doh.h 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/15. 6 | // 7 | 8 | #import "QNDnsResolver.h" 9 | #import "QNDnsDefine.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface QNDohResolver : QNDnsResolver 14 | 15 | /// 构造函数 16 | /// @param server 指定 dns server url。 eg:https://dns.google/dns-query 17 | + (instancetype)resolverWithServer:(NSString *)server; 18 | 19 | /// 构造函数 20 | /// @param server 指定 dns server url。 eg:https://dns.google/dns-query 21 | /// @param recordType 记录类型 eg:kQNTypeA 22 | /// @param timeout 超时时间 23 | + (instancetype)resolverWithServer:(NSString *)server 24 | recordType:(int)recordType 25 | timeout:(int)timeout; 26 | 27 | /// 构造函数 28 | /// @param servers 指定多个 dns server url,同时进行 dns 解析,当第一个有效数据返回时结束,或均为解析到数据时结束 29 | /// eg:https://dns.google/dns-query 30 | /// @param recordType 记录类型 eg:kQNTypeA 31 | /// @param timeout 超时时间 32 | + (instancetype)resolverWithServers:(NSArray *)servers 33 | recordType:(int)recordType 34 | timeout:(int)timeout; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /HappyDNS/Dns/QNDohResolver.m: -------------------------------------------------------------------------------- 1 | // 2 | // Doh.m 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/15. 6 | // 7 | #import "QNDnsResponse.h" 8 | #import "QNDohResolver.h" 9 | 10 | @interface QNDohResolver() 11 | 12 | @property(nonatomic, assign)int recordType; 13 | @property(nonatomic, assign)int timeout; 14 | @property(nonatomic, copy)NSArray *servers; 15 | 16 | @end 17 | @implementation QNDohResolver 18 | @synthesize recordType; 19 | @synthesize timeout; 20 | @synthesize servers; 21 | 22 | + (instancetype)resolverWithServer:(NSString *)server { 23 | return [self resolverWithServer:server recordType:kQNTypeA timeout:QN_DNS_DEFAULT_TIMEOUT]; 24 | } 25 | 26 | + (instancetype)resolverWithServer:(NSString *)server 27 | recordType:(int)recordType 28 | timeout:(int)timeout { 29 | return [self resolverWithServers:server ? @[server] : @[] recordType:recordType timeout:timeout]; 30 | } 31 | 32 | + (instancetype)resolverWithServers:(NSArray *)servers 33 | recordType:(int)recordType 34 | timeout:(int)timeout { 35 | QNDohResolver *resolver = [[self alloc] init]; 36 | resolver.recordType = recordType; 37 | resolver.servers = [servers copy] ?: @[]; 38 | resolver.timeout = timeout; 39 | return resolver; 40 | } 41 | 42 | - (void)request:(NSString *)server 43 | host:(NSString *)host 44 | recordType:(int)recordType 45 | complete:(void(^)(QNDnsResponse *response, NSError *error))complete { 46 | if (complete == nil) { 47 | return; 48 | } 49 | 50 | if (host == nil || host.length == 0) { 51 | complete(nil, kQNDnsInvalidParamError(@"host can not empty")); 52 | return; 53 | } 54 | 55 | int messageId = arc4random()%(0xFFFF); 56 | QNDnsRequest *dnsRequest = [QNDnsRequest request:messageId recordType:recordType host:host]; 57 | NSError *error = nil; 58 | NSData *requestData = [dnsRequest toDnsQuestionData:&error]; 59 | if (error) { 60 | complete(nil, error); 61 | return; 62 | } 63 | 64 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:server]]; 65 | request.HTTPMethod = @"POST"; 66 | request.HTTPBody = requestData; 67 | request.timeoutInterval = self.timeout; 68 | [request addValue:@"application/dns-message" forHTTPHeaderField:@"Content-Type"]; 69 | [request addValue:@"application/dns-message" forHTTPHeaderField:@"Accept"]; 70 | 71 | NSURLSession *session = [NSURLSession sharedSession]; 72 | NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 73 | if (error) { 74 | complete(nil, error); 75 | } else if (data) { 76 | QNDnsResponse *dnsResponse = [QNDnsResponse dnsResponse:server source:QNRecordSourceDoh request:dnsRequest dnsRecordData:data error:nil]; 77 | complete(dnsResponse, nil); 78 | } else { 79 | complete(nil, nil); 80 | } 81 | }]; 82 | [task resume]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /HappyDNS/HappyDNS.h: -------------------------------------------------------------------------------- 1 | // 2 | // HappyDNS.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/24. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QNDnsError.h" 12 | #import "QNDnsManager.h" 13 | #import "QNDnspodEnterprise.h" 14 | #import "QNDomain.h" 15 | #import "QNDnsResolver.h" 16 | #import "QNDnsUdpResolver.h" 17 | #import "QNDohResolver.h" 18 | #import "QNDnsDefine.h" 19 | #import "QNHijackingDetectWrapper.h" 20 | #import "QNIP.h" 21 | #import "QNNetworkInfo.h" 22 | #import "QNRecord.h" 23 | #import "QNResolver.h" 24 | #import "QNResolverDelegate.h" 25 | #import "QNGetAddrInfo.h" 26 | -------------------------------------------------------------------------------- /HappyDNS/Http/QNDnspodEnterprise.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnspodEnterprise.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/7/31. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNResolverDelegate.h" 10 | #import 11 | 12 | extern const int kQN_ENCRYPT_FAILED; 13 | extern const int kQN_DECRYPT_FAILED; 14 | 15 | @interface QNDnspodEnterprise : NSObject 16 | 17 | - (instancetype)initWithId:(NSString *)userId 18 | key:(NSString *)key; 19 | 20 | - (instancetype)initWithId:(NSString *)userId 21 | key:(NSString *)key 22 | server:(NSString *)server; 23 | 24 | - (instancetype)initWithId:(NSString *)userId 25 | key:(NSString *)key 26 | server:(NSString *)server 27 | timeout:(NSUInteger)time; 28 | 29 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error; 30 | @end 31 | -------------------------------------------------------------------------------- /HappyDNS/Http/QNDnspodEnterprise.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNDnspodEnterprise.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDnspodEnterprise.h" 10 | #import 11 | 12 | #import "QNDes.h" 13 | #import "QNDomain.h" 14 | #import "QNHex.h" 15 | #import "QNIP.h" 16 | #import "QNRecord.h" 17 | 18 | const int kQN_ENCRYPT_FAILED = -10001; 19 | const int kQN_DECRYPT_FAILED = -10002; 20 | 21 | @interface QNDnspodEnterprise () 22 | @property (readonly, strong) NSString *server; 23 | @property (nonatomic, strong) NSString *userId; 24 | @property (nonatomic, strong) QNDes *des; 25 | @property (nonatomic) NSUInteger timeout; 26 | 27 | @end 28 | 29 | @implementation QNDnspodEnterprise 30 | 31 | - (instancetype)initWithId:(NSString *)userId 32 | key:(NSString *)key { 33 | return [self initWithId:userId key:key server:@"119.29.29.98"]; 34 | } 35 | 36 | - (instancetype)initWithId:(NSString *)userId 37 | key:(NSString *)key 38 | server:(NSString *)server { 39 | return [self initWithId:userId key:key server:@"119.29.29.98" timeout:QN_DNS_DEFAULT_TIMEOUT]; 40 | } 41 | 42 | - (instancetype)initWithId:(NSString *)userId 43 | key:(NSString *)key 44 | server:(NSString *)server 45 | timeout:(NSUInteger)time { 46 | if (self = [super init]) { 47 | _server = server; 48 | _userId = userId; 49 | _des = [[QNDes alloc] init:[key dataUsingEncoding:NSUTF8StringEncoding]]; 50 | _timeout = time; 51 | } 52 | return self; 53 | } 54 | 55 | - (NSString *)encrypt:(NSString *)domain { 56 | NSData *data = [_des encrypt:[domain dataUsingEncoding:NSUTF8StringEncoding]]; 57 | if (data == nil) { 58 | return nil; 59 | } 60 | NSString *str = [QNHex encodeHexData:data]; 61 | return str; 62 | } 63 | 64 | - (NSString *)decrypt:(NSData *)raw { 65 | NSData *enc = [QNHex decodeHexString:[[NSString alloc] initWithData:raw 66 | encoding:NSUTF8StringEncoding]]; 67 | if (enc == nil) { 68 | return nil; 69 | } 70 | NSData *data = [_des decrpyt:enc]; 71 | if (data == nil) { 72 | return nil; 73 | } 74 | return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 75 | } 76 | 77 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error { 78 | NSString *encrypt = [self encrypt:domain.domain]; 79 | if (encrypt == nil) { 80 | if (error != nil) { 81 | *error = [[NSError alloc] initWithDomain:domain.domain code:kQN_ENCRYPT_FAILED userInfo:nil]; 82 | } 83 | return nil; 84 | } 85 | 86 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 87 | 88 | __block NSData *data = nil; 89 | __block NSError *httpError = nil; 90 | __block NSHTTPURLResponse *response = nil; 91 | NSString *url = [NSString stringWithFormat:@"http://%@/d?ttl=1&dn=%@&id=%@", [QNIP ipHost:_server], encrypt, _userId]; 92 | NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:_timeout]; 93 | NSURLSession *session = [NSURLSession sharedSession]; 94 | NSURLSessionTask *task = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable resp, NSError * _Nullable error) { 95 | 96 | data = data; 97 | httpError = error; 98 | response = (NSHTTPURLResponse *)resp; 99 | dispatch_semaphore_signal(semaphore); 100 | }]; 101 | [task resume]; 102 | 103 | dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 104 | 105 | if (httpError != nil) { 106 | if (error != nil) { 107 | *error = httpError; 108 | } 109 | return nil; 110 | } 111 | if (response.statusCode != 200) { 112 | return nil; 113 | } 114 | 115 | NSString *raw = [self decrypt:data]; 116 | if (raw == nil) { 117 | if (error != nil) { 118 | *error = [[NSError alloc] initWithDomain:domain.domain code:kQN_DECRYPT_FAILED userInfo:nil]; 119 | } 120 | return nil; 121 | } 122 | NSArray *ip1 = [raw componentsSeparatedByString:@","]; 123 | if (ip1.count != 2) { 124 | return nil; 125 | } 126 | NSString *ttlStr = [ip1 objectAtIndex:1]; 127 | int ttl = [ttlStr intValue]; 128 | if (ttl <= 0) { 129 | return nil; 130 | } 131 | NSString *ips = [ip1 objectAtIndex:0]; 132 | NSArray *ipArray = [ips componentsSeparatedByString:@";"]; 133 | NSMutableArray *ret = [[NSMutableArray alloc] initWithCapacity:ipArray.count]; 134 | for (int i = 0; i < ipArray.count; i++) { 135 | QNRecord *record = [[QNRecord alloc] init:[ipArray objectAtIndex:i] ttl:ttl type:kQNTypeA source:QNRecordSourceDnspodEnterprise]; 136 | [ret addObject:record]; 137 | } 138 | return ret; 139 | } 140 | 141 | @end 142 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNHijackingDetectWrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNHijackingDetectWrapper.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/7/16. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QNResolverDelegate.h" 12 | 13 | @class QNResolver; 14 | @interface QNHijackingDetectWrapper : NSObject 15 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error; 16 | - (instancetype)initWithResolver:(QNResolver *)resolver; 17 | @end 18 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNHijackingDetectWrapper.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNHijackingDetectWrapper.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/7/16. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNHijackingDetectWrapper.h" 10 | #import "QNDomain.h" 11 | #import "QNRecord.h" 12 | #import "QNResolver.h" 13 | 14 | @interface QNHijackingDetectWrapper () 15 | @property (nonatomic, readonly) QNResolver *resolver; 16 | @end 17 | 18 | @implementation QNHijackingDetectWrapper 19 | 20 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error { 21 | NSArray *result = [_resolver query:domain networkInfo:netInfo error:error]; 22 | if (((!domain.hasCname) && domain.maxTtl == 0) || result == nil || result.count == 0) { 23 | return result; 24 | } 25 | BOOL hasCname = NO; 26 | BOOL outOfTtl = NO; 27 | for (int i = 0; i < result.count; i++) { 28 | QNRecord *record = [result objectAtIndex:i]; 29 | if (record.type == kQNTypeCname) { 30 | hasCname = YES; 31 | } 32 | if (domain.maxTtl > 0 && record.type == kQNTypeA && record.ttl > domain.maxTtl) { 33 | outOfTtl = YES; 34 | } 35 | } 36 | if ((domain.hasCname && !hasCname) || outOfTtl) { 37 | if (error != nil) { 38 | *error = [[NSError alloc] initWithDomain:domain.domain code:kQNDomainHijackingCode userInfo:nil]; 39 | } 40 | return nil; 41 | } 42 | return result; 43 | } 44 | - (instancetype)initWithResolver:(QNResolver *)resolver { 45 | if (self = [super init]) { 46 | _resolver = resolver; 47 | } 48 | return self; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNHosts.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNHosts.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNRecord.h" 10 | #import "QNResolverDelegate.h" 11 | #import 12 | 13 | @interface QNHosts : NSObject 14 | 15 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo; 16 | 17 | - (void)put:(NSString *)domain record:(QNRecord *)record; 18 | - (void)put:(NSString *)domain record:(QNRecord *)record provider:(int)provider; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNHosts.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNHosts.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNHosts.h" 10 | #import "QNDomain.h" 11 | #import "QNNetworkInfo.h" 12 | 13 | @interface QNHostsValue : NSObject 14 | @property (nonatomic, copy, readonly) QNRecord *record; 15 | @property (readonly) int provider; 16 | @end 17 | 18 | @implementation QNHostsValue 19 | 20 | - (instancetype)init:(QNRecord *)record provider:(int)provider { 21 | if (self = [super init]) { 22 | _record = record; 23 | _provider = provider; 24 | } 25 | return self; 26 | } 27 | 28 | @end 29 | 30 | static NSArray *filter(NSArray *input, int provider) { 31 | NSMutableArray *normal = [[NSMutableArray alloc] initWithCapacity:input.count]; 32 | NSMutableArray *special = [[NSMutableArray alloc] init]; 33 | for (QNHostsValue *v in input) { 34 | if (v.provider == kQNISP_GENERAL) { 35 | [normal addObject:v]; 36 | } else if (provider == v.provider) { 37 | [special addObject:v]; 38 | } 39 | } 40 | if (special.count != 0) { 41 | return special; 42 | } 43 | return [normal copy]; 44 | } 45 | 46 | @interface QNHosts () 47 | @property (nonatomic) NSMutableDictionary *dict; 48 | @end 49 | 50 | @implementation QNHosts 51 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo { 52 | NSMutableArray *x; 53 | @synchronized(_dict) { 54 | x = [_dict objectForKey:domain.domain]; 55 | } 56 | 57 | if (x == nil || x.count == 0) { 58 | return nil; 59 | } 60 | 61 | @synchronized (_dict) { 62 | if (x.count >= 2) { 63 | QNHostsValue *first = [x firstObject]; 64 | [x removeObjectAtIndex:0]; 65 | [x addObject:first]; 66 | } 67 | } 68 | 69 | NSArray *values = filter([x copy], netInfo.provider); 70 | return [self toRecords:values]; 71 | } 72 | 73 | - (NSArray *)toRecords:(NSArray *)values { 74 | if (values == nil) { 75 | return nil; 76 | } 77 | 78 | NSMutableArray *records = [NSMutableArray array]; 79 | for (QNHostsValue *value in values) { 80 | if (value.record != nil && value.record.value != nil) { 81 | [records addObject:value.record]; 82 | } 83 | } 84 | return [records copy]; 85 | } 86 | 87 | 88 | - (void)put:(NSString *)domain record:(QNRecord *)record { 89 | [self put:domain record:record provider:kQNISP_GENERAL]; 90 | } 91 | 92 | - (void)put:(NSString *)domain record:(QNRecord *)record provider:(int)provider { 93 | QNHostsValue *v = [[QNHostsValue alloc] init:record provider:provider]; 94 | @synchronized(_dict) { 95 | NSMutableArray *x = [_dict objectForKey:domain]; 96 | if (x == nil) { 97 | x = [[NSMutableArray alloc] init]; 98 | } 99 | [x addObject:v]; 100 | [_dict setObject:x forKey:domain]; 101 | } 102 | } 103 | 104 | - (instancetype)init { 105 | if (self = [super init]) { 106 | _dict = [[NSMutableDictionary alloc] init]; 107 | } 108 | return self; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNResolvUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNResolv.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/5/28. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #ifndef QNResolv_h 10 | #define QNResolv_h 11 | 12 | extern BOOL isV6(NSString *address); 13 | 14 | extern int setup_dns_server(void *res, NSString *dns_server, NSUInteger timeout); 15 | 16 | #endif /* QNResolv_h */ 17 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNResolvUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNResolvUtil.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/5/28. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #import "QNIP.h" 22 | 23 | #if __IPHONE_OS_VERSION_MIN_REQUIRED 24 | #import 25 | #import 26 | #endif 27 | 28 | BOOL isV6(NSString *address) { 29 | return strchr(address.UTF8String, ':') != NULL; 30 | } 31 | 32 | int setup_dns_server(void *_res_state, NSString *dns_server, NSUInteger timeout) { 33 | res_state res = (res_state)_res_state; 34 | int r = res_ninit(res); 35 | if (r != 0) { 36 | return r; 37 | } 38 | res->retrans = (int)timeout; 39 | res->retry = 1; 40 | if (dns_server == nil) { 41 | return 0; 42 | } 43 | res->options |= RES_IGNTC; 44 | 45 | union res_sockaddr_union server = {0}; 46 | 47 | struct addrinfo hints = {0}, *ai = NULL; 48 | hints.ai_family = AF_UNSPEC; 49 | hints.ai_socktype = SOCK_STREAM; 50 | int ret = getaddrinfo(dns_server.UTF8String, "53", &hints, &ai); 51 | if (ret != 0) { 52 | return ret; 53 | } 54 | int family = ai->ai_family; 55 | 56 | if (family == AF_INET6) { 57 | ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port = htons(53); 58 | server.sin6 = *((struct sockaddr_in6 *)ai->ai_addr); 59 | } else { 60 | server.sin = *((struct sockaddr_in *)ai->ai_addr); 61 | } 62 | 63 | if (![QNIP isIpV6FullySupported] && family == AF_INET) { 64 | if ([QNIP isV6]) { 65 | freeaddrinfo(ai); 66 | ai = NULL; 67 | bzero(&hints, (size_t)0); 68 | hints.ai_family = AF_UNSPEC; 69 | hints.ai_socktype = SOCK_STREAM; 70 | char buf[64] = {0}; 71 | qn_nat64(buf, sizeof(buf), (uint32_t)server.sin.sin_addr.s_addr); 72 | int ret = getaddrinfo(buf, "53", &hints, &ai); 73 | if (ret != 0) { 74 | return -1; 75 | } 76 | ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port = htons(53); 77 | server.sin6 = *((struct sockaddr_in6 *)ai->ai_addr); 78 | } 79 | } 80 | 81 | freeaddrinfo(ai); 82 | res_setservers(res, &server, 1); 83 | return 0; 84 | } -------------------------------------------------------------------------------- /HappyDNS/Local/QNResolver.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNResolver.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNResolverDelegate.h" 10 | #import 11 | 12 | @interface QNResolver : NSObject 13 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error; 14 | 15 | // @deprecated typo 16 | - (instancetype)initWithAddres:(NSString *)address DEPRECATED_ATTRIBUTE; 17 | 18 | - (instancetype)initWithAddress:(NSString *)address; 19 | 20 | - (instancetype)initWithAddress:(NSString *)address 21 | timeout:(NSUInteger)time; 22 | 23 | + (instancetype)systemResolver; 24 | + (NSString *)systemDnsServer; 25 | @end 26 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNResolver.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNResolver.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/23. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #import "QNDomain.h" 20 | #import "QNIP.h" 21 | #import "QNRecord.h" 22 | #import "QNResolver.h" 23 | 24 | #import "QNResolvUtil.h" 25 | 26 | #if __IPHONE_OS_VERSION_MIN_REQUIRED 27 | #import 28 | #import 29 | #endif 30 | 31 | @interface QNResolver () 32 | @property (nonatomic, readonly, strong) NSString *address; 33 | @property (nonatomic, readonly) NSUInteger timeout; 34 | @end 35 | 36 | static NSArray *query_ip_v4(res_state res, const char *host) { 37 | u_char answer[2000]; 38 | int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer)); 39 | 40 | ns_msg handle; 41 | ns_initparse(answer, len, &handle); 42 | 43 | int count = ns_msg_count(handle, ns_s_an); 44 | if (count <= 0) { 45 | res_ndestroy(res); 46 | return nil; 47 | } 48 | NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:count]; 49 | char buf[32]; 50 | char cnameBuf[NS_MAXDNAME]; 51 | memset(cnameBuf, 0, sizeof(cnameBuf)); 52 | for (int i = 0; i < count; i++) { 53 | ns_rr rr; 54 | if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) { 55 | res_ndestroy(res); 56 | return nil; 57 | } 58 | int t = ns_rr_type(rr); 59 | int ttl = ns_rr_ttl(rr); 60 | NSString *val; 61 | if (t == ns_t_a) { 62 | const char *p = inet_ntop(AF_INET, ns_rr_rdata(rr), buf, sizeof(buf)); 63 | val = [NSString stringWithUTF8String:p]; 64 | } else if (t == ns_t_cname) { 65 | int x = ns_name_uncompress(answer, &(answer[len]), ns_rr_rdata(rr), cnameBuf, sizeof(cnameBuf)); 66 | if (x <= 0) { 67 | continue; 68 | } 69 | val = [NSString stringWithUTF8String:cnameBuf]; 70 | memset(cnameBuf, 0, sizeof(cnameBuf)); 71 | } else { 72 | continue; 73 | } 74 | QNRecord *record = [[QNRecord alloc] init:val ttl:ttl type:t source:QNRecordSourceSystem]; 75 | [array addObject:record]; 76 | } 77 | res_ndestroy(res); 78 | return array; 79 | } 80 | 81 | @implementation QNResolver 82 | - (instancetype)initWithAddres:(NSString *)address { 83 | return [self initWithAddress:address]; 84 | } 85 | 86 | - (instancetype)initWithAddress:(NSString *)address { 87 | return [self initWithAddress:address timeout:QN_DNS_DEFAULT_TIMEOUT]; 88 | } 89 | 90 | - (instancetype)initWithAddress:(NSString *)address 91 | timeout:(NSUInteger)time { 92 | if (self = [super init]) { 93 | _address = address; 94 | _timeout = time; 95 | } 96 | return self; 97 | } 98 | 99 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error { 100 | struct __res_state res; 101 | 102 | int r = setup_dns_server(&res, _address, _timeout); 103 | if (r != 0) { 104 | if (error != nil) { 105 | *error = [[NSError alloc] initWithDomain:@"qiniu.dns" code:kQNDomainSeverError userInfo:nil]; 106 | } 107 | return nil; 108 | } 109 | 110 | NSArray *ret = query_ip_v4(&res, [domain.domain cStringUsingEncoding:NSUTF8StringEncoding]); 111 | if (ret != nil && ret.count != 0) { 112 | return ret; 113 | } 114 | if (error != nil) { 115 | *error = [[NSError alloc] initWithDomain:@"qiniu.dns" code:NSURLErrorDNSLookupFailed userInfo:nil]; 116 | } 117 | return nil; 118 | } 119 | 120 | + (instancetype)systemResolver { 121 | return [[QNResolver alloc] initWithAddress:nil]; 122 | } 123 | 124 | + (NSString *)systemDnsServer { 125 | struct __res_state res; 126 | int r = res_ninit(&res); 127 | if (r != 0) { 128 | return nil; 129 | } 130 | 131 | union res_sockaddr_union server[MAXNS] = {0}; 132 | r = res_getservers(&res, server, MAXNS); 133 | res_ndestroy(&res); 134 | if (r <= 0) { 135 | return nil; 136 | } 137 | 138 | int family = server[0].sin.sin_family; 139 | char buf[64] = {0}; 140 | const void *addr; 141 | if (family == AF_INET6) { 142 | addr = &server[0].sin6.sin6_addr; 143 | } else { 144 | addr = &server[0].sin.sin_addr; 145 | } 146 | const char *p = inet_ntop(family, addr, buf, sizeof(buf)); 147 | if (p == NULL) { 148 | return nil; 149 | } 150 | return [NSString stringWithUTF8String:p]; 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNTxtResolver.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNTxtResolver.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/1/5. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNResolverDelegate.h" 10 | #import 11 | 12 | @interface QNTxtResolver : NSObject 13 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error; 14 | 15 | /** 16 | * 根据服务器地址进行初始化 17 | * 18 | * @param address DNS 服务器地址,nil 表示系统的 19 | */ 20 | - (instancetype)initWithAddress:(NSString *)address; 21 | 22 | - (instancetype)initWithAddress:(NSString *)address timeout:(NSUInteger)time; 23 | @end 24 | -------------------------------------------------------------------------------- /HappyDNS/Local/QNTxtResolver.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNTxtResolver.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/1/5. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNTxtResolver.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #import "QNDomain.h" 22 | #import "QNRecord.h" 23 | #import "QNResolvUtil.h" 24 | #import "QNResolver.h" 25 | 26 | @interface QNTxtResolver () 27 | @property (nonatomic, readonly, strong) NSString *address; 28 | @property (nonatomic, readonly) NSUInteger timeout; 29 | @end 30 | 31 | static NSArray *query_ip(res_state res, const char *host) { 32 | u_char answer[1500]; 33 | int len = res_nquery(res, host, ns_c_in, ns_t_txt, answer, sizeof(answer)); 34 | 35 | ns_msg handle; 36 | ns_initparse(answer, len, &handle); 37 | 38 | int count = ns_msg_count(handle, ns_s_an); 39 | if (count != 1) { 40 | res_ndestroy(res); 41 | return nil; 42 | } 43 | 44 | char txtbuf[256]; 45 | memset(txtbuf, 0, sizeof(txtbuf)); 46 | 47 | ns_rr rr; 48 | if (ns_parserr(&handle, ns_s_an, 0, &rr) != 0) { 49 | res_ndestroy(res); 50 | return nil; 51 | } 52 | int t = ns_rr_type(rr); 53 | int ttl = ns_rr_ttl(rr); 54 | int rdlen = ns_rr_rdlen(rr); 55 | if (rdlen <= 1 + 7) { 56 | res_ndestroy(res); 57 | return nil; 58 | } 59 | NSString *val; 60 | if (t == ns_t_txt) { 61 | memcpy(txtbuf, ns_rr_rdata(rr) + 1, rdlen - 1); 62 | val = [NSString stringWithUTF8String:txtbuf]; 63 | } else { 64 | res_ndestroy(res); 65 | return nil; 66 | } 67 | NSArray *ipArray = [val componentsSeparatedByString:@","]; 68 | NSMutableArray *ret = [[NSMutableArray alloc] initWithCapacity:ipArray.count]; 69 | for (int i = 0; i < ipArray.count; i++) { 70 | QNRecord *record = [[QNRecord alloc] init:[ipArray objectAtIndex:i] ttl:ttl type:kQNTypeA source:QNRecordSourceSystem]; 71 | [ret addObject:record]; 72 | } 73 | 74 | res_ndestroy(res); 75 | return ret; 76 | } 77 | 78 | @implementation QNTxtResolver 79 | - (instancetype)initWithAddress:(NSString *)address { 80 | return [self initWithAddress:address timeout:QN_DNS_DEFAULT_TIMEOUT]; 81 | } 82 | 83 | - (instancetype)initWithAddress:(NSString *)address timeout:(NSUInteger)time { 84 | if (self = [super init]) { 85 | _address = address; 86 | _timeout = time; 87 | } 88 | return self; 89 | } 90 | 91 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error { 92 | struct __res_state res; 93 | 94 | int r = setup_dns_server(&res, _address, _timeout); 95 | if (r != 0) { 96 | if (error != nil) { 97 | *error = [[NSError alloc] initWithDomain:@"qiniu.dns" code:kQNDomainSeverError userInfo:nil]; 98 | } 99 | return nil; 100 | } 101 | 102 | NSArray *ret = query_ip(&res, [domain.domain cStringUsingEncoding:NSUTF8StringEncoding]); 103 | if (ret == nil && error != nil) { 104 | *error = [[NSError alloc] initWithDomain:@"qiniu.dns" code:NSURLErrorDNSLookupFailed userInfo:nil]; 105 | } 106 | return ret; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /HappyDNS/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTracking 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | NSPrivacyTrackingDomains 10 | 11 | NSPrivacyCollectedDataTypes 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HappyDNS/Util/NSData+QNRW.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableData+QNWriter.h 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/16. 6 | // 7 | #import 8 | 9 | @interface NSData (QNReader) 10 | 11 | //MARK: 读数据 12 | - (uint8_t)qn_readInt8:(NSInteger)from; 13 | 14 | - (uint16_t)qn_readLittleEndianInt16:(NSInteger)from; 15 | - (uint32_t)qn_readLittleEndianInt32:(NSInteger)from; 16 | - (uint64_t)qn_readLittleEndianInt64:(NSInteger)from; 17 | 18 | - (uint16_t)qn_readBigEndianInt16:(NSInteger)from; 19 | - (uint32_t)qn_readBigEndianInt32:(NSInteger)from; 20 | - (uint64_t)qn_readBigEndianInt64:(NSInteger)from; 21 | 22 | - (int8_t)qn_readSInt8:(NSInteger)from; 23 | - (int16_t)qn_readLittleEndianSInt16:(NSInteger)from; 24 | - (int32_t)qn_readLittleEndianSInt32:(NSInteger)from; 25 | - (int64_t)qn_readLittleEndianSInt64:(NSInteger)from; 26 | 27 | - (int16_t)qn_readBigEndianSInt16:(NSInteger)from; 28 | - (int32_t)qn_readBigEndianSInt32:(NSInteger)from; 29 | - (int64_t)qn_readBigEndianSInt64:(NSInteger)from; 30 | 31 | - (float)qn_readFloat:(NSInteger)from; 32 | - (double)qn_readDouble:(NSInteger)from; 33 | 34 | - (float)qn_readSwappedFloat:(NSInteger)from; 35 | - (double)qn_readSwappedDouble:(NSInteger)from; 36 | 37 | - (NSString *)qn_readString:(NSRange)range usingEncoding:(NSStringEncoding)encoding; 38 | 39 | @end 40 | 41 | 42 | @interface NSMutableData (QNWriter) 43 | 44 | //MARK: 写数据 45 | - (void)qn_appendInt8:(uint8_t)value; 46 | 47 | - (void)qn_appendLittleEndianInt16:(uint16_t)value; 48 | - (void)qn_appendLittleEndianInt32:(uint32_t)value; 49 | - (void)qn_appendLittleEndianInt64:(uint64_t)value; 50 | 51 | - (void)qn_appendBigEndianInt16:(uint16_t)value; 52 | - (void)qn_appendBigEndianInt32:(uint32_t)value; 53 | - (void)qn_appendBigEndianInt64:(uint64_t)value; 54 | 55 | - (void)qn_appendSInt8:(int8_t)value; 56 | 57 | - (void)qn_appendLittleEndianSInt16:(int16_t)value; 58 | - (void)qn_appendLittleEndianSInt32:(int32_t)value; 59 | - (void)qn_appendLittleEndianSInt64:(int64_t)value; 60 | 61 | - (void)qn_appendBigEndianSInt16:(int16_t)value; 62 | - (void)qn_appendBigEndianSInt32:(int32_t)value; 63 | - (void)qn_appendBigEndianSInt64:(int64_t)value; 64 | 65 | // These methods append floating point values depending on the architecture of your processor 66 | // they're usually not appropriate for network transmission 67 | - (void)qn_appendFloat:(float)value; 68 | - (void)qn_appendDouble:(double)value; 69 | 70 | - (void)qn_appendSwappedFloat:(float)value; 71 | - (void)qn_appendSwappedDouble:(double)value; 72 | 73 | - (void)qn_appendString:(NSString *)value usingEncoding:(NSStringEncoding)encoding; 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /HappyDNS/Util/NSData+QNRW.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableData+QNWriter.m 3 | // Doh 4 | // 5 | // Created by yangsen on 2021/7/16. 6 | // 7 | #import "NSData+QNRW.h" 8 | 9 | @implementation NSData (QNReader) 10 | 11 | //MARK: 读数据 12 | - (uint8_t)qn_readInt8:(NSInteger)from { 13 | uint8_t value = 0; 14 | [[self subdataWithRange:NSMakeRange(from, sizeof(value))] getBytes:&value length:sizeof(value)]; 15 | return value; 16 | } 17 | 18 | #define READ_METHOD(endian, size) \ 19 | - (uint ## size ## _t)qn_read ## endian ## EndianInt ## size:(NSInteger)from { \ 20 | uint ## size ## _t value = 0; \ 21 | [[self subdataWithRange:NSMakeRange(from, sizeof(value))] getBytes:&value length:sizeof(value)]; \ 22 | value = CFSwapInt ## size ## HostTo ## endian(value); \ 23 | return value; \ 24 | } 25 | 26 | READ_METHOD(Little, 16) 27 | READ_METHOD(Little, 32) 28 | READ_METHOD(Little, 64) 29 | READ_METHOD(Big, 16) 30 | READ_METHOD(Big, 32) 31 | READ_METHOD(Big, 64) 32 | 33 | #undef READ_METHOD 34 | 35 | - (int8_t)qn_readSInt8:(NSInteger)from { 36 | int8_t value = 0; 37 | [[self subdataWithRange:NSMakeRange(from, 8)] getBytes:&value length:sizeof(value)]; 38 | return value; 39 | } 40 | 41 | #define READ_METHOD(endian, size) \ 42 | - (int ## size ## _t)qn_read ## endian ## EndianSInt ## size:(NSInteger)from { \ 43 | int ## size ## _t value = 0; \ 44 | [[self subdataWithRange:NSMakeRange(from, sizeof(value))] getBytes:&value length:sizeof(value)]; \ 45 | value = CFSwapInt ## size ## HostTo ## endian(value); \ 46 | return value; \ 47 | } 48 | 49 | READ_METHOD(Little, 16) 50 | READ_METHOD(Little, 32) 51 | READ_METHOD(Little, 64) 52 | READ_METHOD(Big, 16) 53 | READ_METHOD(Big, 32) 54 | READ_METHOD(Big, 64) 55 | 56 | #undef READ_METHOD 57 | 58 | 59 | - (float)qn_readFloat:(NSInteger)from { 60 | float value = 0; 61 | [[self subdataWithRange:NSMakeRange(from, sizeof(value))] getBytes:&value length:sizeof(value)]; 62 | return value; 63 | } 64 | 65 | - (double)qn_readDouble:(NSInteger)from { 66 | double value = 0; 67 | [[self subdataWithRange:NSMakeRange(from, sizeof(value))] getBytes:&value length:sizeof(value)]; 68 | return value; 69 | } 70 | 71 | - (float)qn_readSwappedFloat:(NSInteger)from { 72 | CFSwappedFloat32 value; 73 | [[self subdataWithRange:NSMakeRange(from, sizeof(value))] getBytes:&value length:sizeof(value)]; 74 | return CFConvertFloatSwappedToHost(value); 75 | } 76 | 77 | - (double)qn_readSwappedDouble:(NSInteger)from { 78 | CFSwappedFloat64 value; 79 | [[self subdataWithRange:NSMakeRange(from, sizeof(value))] getBytes:&value length:sizeof(value)]; 80 | return CFConvertDoubleSwappedToHost(value); 81 | } 82 | 83 | - (NSString *)qn_readString:(NSRange)range usingEncoding:(NSStringEncoding)encoding { 84 | NSData *value = [self subdataWithRange:range]; 85 | return [[NSString alloc] initWithData:value encoding:encoding]; 86 | } 87 | 88 | @end 89 | 90 | @implementation NSMutableData (QNWriter) 91 | 92 | - (void)qn_appendInt8:(uint8_t)value { 93 | [self appendBytes:&value length:sizeof(value)]; 94 | } 95 | 96 | #define APPEND_METHOD(endian, size) \ 97 | - (void)qn_append ## endian ## EndianInt ## size:(uint ## size ## _t)value { \ 98 | value = CFSwapInt ## size ## HostTo ## endian(value); \ 99 | [self appendBytes:&value length:sizeof(value)]; \ 100 | } 101 | 102 | APPEND_METHOD(Little, 16) 103 | APPEND_METHOD(Little, 32) 104 | APPEND_METHOD(Little, 64) 105 | APPEND_METHOD(Big, 16) 106 | APPEND_METHOD(Big, 32) 107 | APPEND_METHOD(Big, 64) 108 | 109 | #undef APPEND_METHOD 110 | 111 | - (void)qn_appendSInt8:(int8_t)value { 112 | [self qn_appendInt8:*(int8_t *)&value]; 113 | } 114 | 115 | #define APPEND_METHOD(endian, size) \ 116 | - (void)qn_append ## endian ## EndianSInt ## size:(int ## size ## _t)value { \ 117 | [self qn_append ## endian ## EndianInt ## size:*(uint ## size ## _t *)&value]; \ 118 | } 119 | 120 | APPEND_METHOD(Little, 16) 121 | APPEND_METHOD(Little, 32) 122 | APPEND_METHOD(Little, 64) 123 | APPEND_METHOD(Big, 16) 124 | APPEND_METHOD(Big, 32) 125 | APPEND_METHOD(Big, 64) 126 | 127 | #undef APPEND_METHOD 128 | 129 | - (void)qn_appendFloat:(float)value { 130 | [self appendBytes:&value length:sizeof(value)]; 131 | } 132 | 133 | - (void)qn_appendDouble:(double)value { 134 | [self appendBytes:&value length:sizeof(value)]; 135 | } 136 | 137 | - (void)qn_appendSwappedFloat:(float)value { 138 | CFSwappedFloat32 v = CFConvertFloatHostToSwapped(value); 139 | [self appendBytes:&v length:sizeof(value)]; 140 | } 141 | 142 | - (void)qn_appendSwappedDouble:(double)value { 143 | CFSwappedFloat64 v = CFConvertDoubleHostToSwapped(value); 144 | [self appendBytes:&v length:sizeof(value)]; 145 | } 146 | 147 | - (void)qn_appendString:(NSString *)value usingEncoding:(NSStringEncoding)encoding { 148 | [self appendData:[value dataUsingEncoding:encoding]]; 149 | } 150 | 151 | @end 152 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNDes.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNDes.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/8/1. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern const int kQN_ENCRYPT_FAILED; 12 | extern const int kQN_DECRYPT_FAILED; 13 | 14 | @interface QNDes : NSObject 15 | 16 | - (NSData *)encrypt:(NSData *)input; 17 | 18 | - (NSData *)decrpyt:(NSData *)input; 19 | 20 | - (instancetype)init:(NSData *)key; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNDes.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNDes.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/8/1. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QNDes.h" 12 | 13 | @interface QNDes () 14 | @property (nonatomic, strong) NSData *key; 15 | @end 16 | 17 | @implementation QNDes 18 | 19 | - (NSData *)encrypt:(NSData *)data { 20 | const void *input = data.bytes; 21 | size_t inputSize = data.length; 22 | 23 | size_t bufferSize = (inputSize + kCCBlockSize3DES) & ~(kCCBlockSize3DES - 1); 24 | uint8_t *buffer = malloc(bufferSize * sizeof(uint8_t)); 25 | memset((void *)buffer, 0x0, bufferSize); 26 | size_t movedBytes = 0; 27 | 28 | const void *vkey = _key.bytes; 29 | 30 | CCCryptorStatus ccStatus = CCCrypt(kCCEncrypt, 31 | kCCAlgorithmDES, 32 | kCCOptionECBMode | kCCOptionPKCS7Padding, 33 | vkey, 34 | kCCKeySizeDES, 35 | NULL, 36 | input, 37 | inputSize, 38 | (void *)buffer, 39 | bufferSize, 40 | &movedBytes); 41 | if (ccStatus != kCCSuccess) { 42 | NSLog(@"error code %d", ccStatus); 43 | free(buffer); 44 | return nil; 45 | } 46 | NSData *encrypted = [NSData dataWithBytes:(const void *)buffer length:(NSUInteger)movedBytes]; 47 | free(buffer); 48 | return encrypted; 49 | } 50 | 51 | - (NSData *)decrpyt:(NSData *)raw { 52 | const void *input = raw.bytes; 53 | size_t inputSize = raw.length; 54 | 55 | size_t bufferSize = 1024; 56 | uint8_t *buffer = malloc(bufferSize * sizeof(uint8_t)); 57 | memset((void *)buffer, 0x0, bufferSize); 58 | size_t movedBytes = 0; 59 | 60 | const void *vkey = _key.bytes; 61 | 62 | CCCryptorStatus ccStatus = CCCrypt(kCCDecrypt, 63 | kCCAlgorithmDES, 64 | kCCOptionECBMode | kCCOptionPKCS7Padding, 65 | vkey, 66 | kCCKeySizeDES, 67 | NULL, 68 | input, 69 | inputSize, 70 | (void *)buffer, 71 | bufferSize, 72 | &movedBytes); 73 | 74 | if (ccStatus != kCCSuccess) { 75 | NSLog(@"error code %d", ccStatus); 76 | free(buffer); 77 | return nil; 78 | } 79 | 80 | NSData *decrypted = [NSData dataWithBytes:(const void *)buffer length:(NSUInteger)movedBytes]; 81 | free(buffer); 82 | return decrypted; 83 | } 84 | 85 | - (instancetype)init:(NSData *)key { 86 | if (self = [super init]) { 87 | _key = key; 88 | } 89 | return self; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNGetAddrInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNGetAddrInfo.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/7/19. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #ifndef QNGetAddrInfo_h 10 | #define QNGetAddrInfo_h 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | typedef struct qn_ips_ret { 17 | char *ips[1]; 18 | } qn_ips_ret; 19 | 20 | typedef qn_ips_ret *(*qn_dns_callback)(const char *host); 21 | 22 | typedef void (*qn_ip_report_callback)(const char *ip, int code, int time_ms); 23 | 24 | extern void qn_free_ips_ret(qn_ips_ret *ip_list); 25 | 26 | extern int qn_getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res); 27 | 28 | extern void qn_freeaddrinfo(struct addrinfo *ai); 29 | 30 | extern void qn_set_dns_callback(qn_dns_callback cb); 31 | 32 | extern void qn_set_ip_report_callback(qn_ip_report_callback cb); 33 | 34 | extern void qn_ip_report(const struct addrinfo *info, int code, int time_ms); 35 | 36 | #ifdef __cplusplus 37 | }; 38 | #endif 39 | 40 | #endif /* QNGetAddrInfo_h */ 41 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNGetAddrInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNGetAddrInfo.c 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/7/19. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "QNGetAddrInfo.h" 15 | 16 | //fast judge domain or ip, not verify ip right. 17 | static int isIp(const char* domain) { 18 | size_t l = strlen(domain); 19 | if (l > 15 || l < 7) { 20 | return 0; 21 | } 22 | 23 | for (const char* p = domain; p < domain + l; p++) { 24 | if ((*p < '0' || *p > '9') && *p != '.') { 25 | return 0; 26 | } 27 | } 28 | return 1; 29 | } 30 | 31 | static struct addrinfo* addrinfo_clone(struct addrinfo* addr) { 32 | struct addrinfo* ai; 33 | ai = (struct addrinfo*)calloc(sizeof(struct addrinfo) + addr->ai_addrlen, 1); 34 | if (ai) { 35 | memcpy(ai, addr, sizeof(struct addrinfo)); 36 | ai->ai_addr = (struct sockaddr*)(ai + 1); 37 | memcpy(ai->ai_addr, addr->ai_addr, addr->ai_addrlen); 38 | if (addr->ai_canonname) { 39 | ai->ai_canonname = strdup(addr->ai_canonname); 40 | } 41 | ai->ai_next = NULL; 42 | } 43 | return ai; 44 | } 45 | 46 | static void append_addrinfo(struct addrinfo** head, struct addrinfo* addr) { 47 | if (*head == NULL) { 48 | *head = addr; 49 | return; 50 | } 51 | struct addrinfo* ai = *head; 52 | while (ai->ai_next != NULL) { 53 | ai = ai->ai_next; 54 | } 55 | ai->ai_next = addr; 56 | } 57 | 58 | void qn_free_ips_ret(qn_ips_ret* ip_list) { 59 | if (ip_list == NULL) { 60 | return; 61 | } 62 | char** p = ip_list->ips; 63 | while (*p != NULL) { 64 | free(*p); 65 | p++; 66 | } 67 | free(ip_list); 68 | } 69 | 70 | static qn_dns_callback dns_callback = NULL; 71 | int qn_getaddrinfo(const char* hostname, const char* servname, const struct addrinfo* hints, struct addrinfo** res) { 72 | if (dns_callback == NULL || hostname == NULL || isIp(hostname)) { 73 | return getaddrinfo(hostname, servname, hints, res); 74 | } 75 | 76 | qn_ips_ret* ret = dns_callback(hostname); 77 | if (ret == NULL) { 78 | return EAI_NODATA; 79 | } 80 | if (ret->ips[0] == NULL) { 81 | qn_free_ips_ret(ret); 82 | return EAI_NODATA; 83 | } 84 | int i; 85 | struct addrinfo* ai = NULL; 86 | struct addrinfo* store = NULL; 87 | int r = 0; 88 | for (i = 0; ret->ips[i] != NULL; i++) { 89 | r = getaddrinfo(ret->ips[i], servname, hints, &ai); 90 | if (r != 0) { 91 | break; 92 | } 93 | struct addrinfo* temp = ai; 94 | ai = addrinfo_clone(ai); 95 | append_addrinfo(&store, ai); 96 | freeaddrinfo(temp); 97 | ai = NULL; 98 | } 99 | qn_free_ips_ret(ret); 100 | if (r != 0) { 101 | qn_freeaddrinfo(store); 102 | return r; 103 | } 104 | *res = store; 105 | return 0; 106 | } 107 | 108 | void qn_freeaddrinfo(struct addrinfo* ai) { 109 | if (ai == NULL) { 110 | return; 111 | } 112 | struct addrinfo* next; 113 | do { 114 | next = ai->ai_next; 115 | if (ai->ai_canonname) 116 | free(ai->ai_canonname); 117 | /* no need to free(ai->ai_addr) */ 118 | free(ai); 119 | ai = next; 120 | } while (ai); 121 | } 122 | 123 | void qn_set_dns_callback(qn_dns_callback cb) { 124 | dns_callback = cb; 125 | } 126 | 127 | static qn_ip_report_callback ip_report_cb = NULL; 128 | void qn_set_ip_report_callback(qn_ip_report_callback cb) { 129 | ip_report_cb = cb; 130 | } 131 | 132 | void qn_ip_report(const struct addrinfo* info, int code, int time_ms) { 133 | if (ip_report_cb == NULL || info == NULL) { 134 | return; 135 | } 136 | char ip_str_buf[32] = {0}; 137 | const char* c_ip; 138 | if (info->ai_family == AF_INET6) { 139 | c_ip = inet_ntop(info->ai_family, &((struct sockaddr_in6*)(info->ai_addr))->sin6_addr, ip_str_buf, sizeof(ip_str_buf)); 140 | } else { 141 | c_ip = inet_ntop(info->ai_family, &((struct sockaddr_in*)(info->ai_addr))->sin_addr, ip_str_buf, sizeof(ip_str_buf)); 142 | } 143 | if (c_ip == NULL) { 144 | c_ip = ""; 145 | } 146 | ip_report_cb(c_ip, code, time_ms); 147 | } 148 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNHex.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNHex.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/7/31. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | char *qn_encodeHexData(char *output_buf, const char *data, int data_size, BOOL up); 12 | 13 | @interface QNHex : NSObject 14 | 15 | + (NSString *)encodeHexData:(NSData *)data; 16 | + (NSString *)encodeHexString:(NSString *)str; 17 | 18 | + (NSData *)decodeHexString:(NSString *)hex; 19 | + (NSString *)decodeHexToString:(NSString *)hex; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNHex.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNHex.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/7/31. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNHex.h" 10 | 11 | static char DIGITS_LOWER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 12 | 13 | static char DIGITS_UPPER[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 14 | 15 | static int hexDigit(char c) { 16 | int result = -1; 17 | if ('0' <= c && c <= '9') { 18 | result = c - '0'; 19 | } else if ('a' <= c && c <= 'f') { 20 | result = 10 + (c - 'a'); 21 | } else if ('A' <= c && c <= 'F') { 22 | result = 10 + (c - 'A'); 23 | } 24 | return result; 25 | } 26 | 27 | static char *decodeHex(const char *data, int size) { 28 | if ((size & 0x01) != 0) { 29 | return NULL; 30 | } 31 | char *output = malloc(size / 2); 32 | int outLimit = 0; 33 | for (int i = 0, j = 0; j < size; i++) { 34 | int f = hexDigit(data[j]); 35 | if (f < 0) { 36 | outLimit = 1; 37 | break; 38 | } 39 | j++; 40 | int f2 = hexDigit(data[j]); 41 | if (f2 < 0) { 42 | outLimit = 1; 43 | break; 44 | } 45 | f = (f << 4) | f2; 46 | j++; 47 | output[i] = (char)(f & 0xff); 48 | } 49 | if (outLimit) { 50 | free(output); 51 | return NULL; 52 | } 53 | return output; 54 | } 55 | 56 | static char *encodeHexInternal(char *output_buf, const char *data, int size, char hexTable[]) { 57 | for (int i = 0, j = 0; i < size; i++) { 58 | output_buf[j++] = hexTable[((0XF0 & data[i]) >> 4) & 0X0F]; 59 | output_buf[j++] = hexTable[((0X0F & data[i])) & 0X0F]; 60 | } 61 | return output_buf; 62 | } 63 | 64 | static char *encodeHex(const char *data, int size, char hexTable[]) { 65 | char *output = malloc(size * 2); 66 | return encodeHexInternal(output, data, size, hexTable); 67 | } 68 | 69 | char *qn_encodeHexData(char *buff, const char *data, int data_size, BOOL up) { 70 | char *hexTable = DIGITS_UPPER; 71 | if (!up) { 72 | hexTable = DIGITS_LOWER; 73 | } 74 | return encodeHexInternal(buff, data, data_size, hexTable); 75 | } 76 | 77 | @implementation QNHex 78 | 79 | + (NSString *)encodeHexData:(NSData *)data { 80 | char *e = encodeHex(data.bytes, (int)data.length, DIGITS_UPPER); 81 | NSString *str = [[NSString alloc] initWithBytes:e length:data.length * 2 encoding:NSASCIIStringEncoding]; 82 | free(e); 83 | return str; 84 | } 85 | 86 | + (NSString *)encodeHexString:(NSString *)str { 87 | return [QNHex encodeHexData:[str dataUsingEncoding:NSUTF8StringEncoding]]; 88 | } 89 | 90 | + (NSData *)decodeHexString:(NSString *)hex { 91 | char *d = decodeHex(hex.UTF8String, (int)hex.length); 92 | if (d == NULL) { 93 | return nil; 94 | } 95 | NSData *data = [NSData dataWithBytes:d length:hex.length / 2]; 96 | free(d); 97 | return data; 98 | } 99 | 100 | + (NSString *)decodeHexToString:(NSString *)hex { 101 | NSData *data = [QNHex decodeHexString:hex]; 102 | if (data == nil) { 103 | return nil; 104 | } 105 | return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 106 | } 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNIP.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNIPV6.h 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/5/25. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern int qn_localIp(char *buf, int buf_size); 12 | extern void qn_nat64(char *buf, int buf_size, uint32_t ipv4_addr); 13 | 14 | @interface QNIP : NSObject 15 | 16 | + (BOOL)isV6; 17 | 18 | + (NSString *)adaptiveIp:(NSString *)ipv4; 19 | 20 | + (NSString *)local; 21 | 22 | // ipv6 in url like http://[::xxx]/ 23 | + (NSString *)ipHost:(NSString *)ip; 24 | 25 | + (NSString *)nat64:(NSString *)ip; 26 | 27 | + (BOOL)isIpV6FullySupported; 28 | 29 | + (BOOL)mayBeIpV4:(NSString *)domain; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNIP.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNIPV6.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/5/25. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | 14 | #include 15 | #include 16 | 17 | #import "QNHex.h" 18 | #import "QNIP.h" 19 | 20 | #if __IPHONE_OS_VERSION_MIN_REQUIRED 21 | #import 22 | #import 23 | #endif 24 | 25 | #ifndef kQNDnsDefaultIPv4IP 26 | #define kQNDnsDefaultIPv4IP "114.114.114.114" 27 | #endif 28 | 29 | #ifndef kQNDnsDefaultIPv6IP 30 | #define kQNDnsDefaultIPv6IP "2400:3200::1" 31 | #endif 32 | 33 | void qn_nat64(char *buf, int buf_size, uint32_t ipv4addr) { 34 | bzero(buf, buf_size); 35 | //nat 4 to ipv6 36 | const char *p = (const char *)&ipv4addr; 37 | const char prefix[] = "64:ff9b::"; 38 | memcpy(buf, prefix, sizeof(prefix)); 39 | char *phex = buf + sizeof(prefix) - 1; 40 | qn_encodeHexData(phex, p, 2, false); 41 | if (*phex == '0') { 42 | memmove(phex, phex + 1, 3); 43 | phex += 3; 44 | } else { 45 | phex += 4; 46 | } 47 | *phex = ':'; 48 | phex++; 49 | qn_encodeHexData(phex, p + 2, 2, false); 50 | if (*phex == '0') { 51 | memmove(phex, phex + 1, 3); 52 | phex[3] = 0; 53 | } 54 | } 55 | 56 | int qn_local_ip_internal(char *buf, int buf_size, const char *t_ip) { 57 | struct addrinfo hints = {0}, *ai; 58 | int err = 0; 59 | hints.ai_family = AF_UNSPEC; 60 | hints.ai_socktype = SOCK_DGRAM; 61 | int ret = getaddrinfo(t_ip, "53", &hints, &ai); 62 | if (ret != 0) { 63 | err = errno; 64 | return err; 65 | } 66 | 67 | int family = ai->ai_family; 68 | int sock = socket(family, ai->ai_socktype, 0); 69 | if (sock < 0) { 70 | err = errno; 71 | freeaddrinfo(ai); 72 | return err; 73 | } 74 | 75 | //fix getaddrinfo bug in ipv4 to ipv6 76 | if (ai->ai_family == AF_INET6) { 77 | ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port = htons(53); 78 | } 79 | 80 | err = connect(sock, ai->ai_addr, ai->ai_addrlen); 81 | freeaddrinfo(ai); 82 | if (err < 0) { 83 | close(sock); 84 | err = errno; 85 | return err; 86 | } 87 | 88 | uint32_t localAddress[16] = {0}; 89 | 90 | socklen_t addressLength = sizeof(localAddress); 91 | err = getsockname(sock, (struct sockaddr *)&localAddress, &addressLength); 92 | close(sock); 93 | if (err != 0) { 94 | return err; 95 | } 96 | void *addr; 97 | if (family == AF_INET6) { 98 | addr = &((struct sockaddr_in6 *)&localAddress)->sin6_addr; 99 | } else { 100 | addr = &((struct sockaddr_in *)&localAddress)->sin_addr; 101 | } 102 | const char *ip = inet_ntop(family, addr, buf, buf_size); 103 | if (ip == nil) { 104 | return -1; 105 | } 106 | return 0; 107 | } 108 | 109 | int qn_localIp(char *buf, int buf_size) { 110 | int ret = qn_local_ip_internal(buf, buf_size, kQNDnsDefaultIPv4IP); 111 | if (ret != 0) { 112 | #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED 113 | if (![QNIP isIpV6FullySupported]) { 114 | ret = qn_local_ip_internal(buf, buf_size, kQNDnsDefaultIPv6IP); 115 | } 116 | #endif 117 | } 118 | return ret; 119 | } 120 | 121 | @implementation QNIP 122 | 123 | + (BOOL)isV6 { 124 | struct addrinfo hints = {0}, *ai; 125 | hints.ai_family = AF_UNSPEC; 126 | hints.ai_socktype = SOCK_STREAM; 127 | hints.ai_flags = AI_NUMERICHOST; 128 | int ret = getaddrinfo(kQNDnsDefaultIPv4IP, "http", &hints, &ai); 129 | if (ret != 0) { 130 | return NO; 131 | } 132 | int family = ai->ai_family; 133 | freeaddrinfo(ai); 134 | BOOL result = family == AF_INET6; 135 | #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED 136 | if (![QNIP isIpV6FullySupported] && !ret) { 137 | char buf[64] = {0}; 138 | ret = qn_local_ip_internal(buf, sizeof(buf), kQNDnsDefaultIPv6IP); 139 | if (strchr(buf, ':') != NULL) { 140 | result = YES; 141 | } 142 | } 143 | #endif 144 | return result; 145 | } 146 | 147 | + (NSString *)adaptiveIp:(NSString *)ipv4 { 148 | struct addrinfo hints = {0}, *ai; 149 | hints.ai_family = AF_UNSPEC; 150 | hints.ai_socktype = SOCK_STREAM; 151 | int ret = getaddrinfo(ipv4.UTF8String, "http", &hints, &ai); 152 | if (ret != 0) { 153 | return nil; 154 | } 155 | int family = ai->ai_family; 156 | 157 | void *addr; 158 | if (family == AF_INET6) { 159 | addr = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; 160 | } else { 161 | addr = &((struct sockaddr_in *)ai->ai_addr)->sin_addr; 162 | } 163 | char buf[64] = {0}; 164 | const char *ip = inet_ntop(family, addr, buf, sizeof(buf)); 165 | #ifdef __IPHONE_OS_VERSION_MAX_ALLOWED 166 | if (![QNIP isIpV6FullySupported] && family == AF_INET) { 167 | char buf2[64] = {0}; 168 | ret = qn_local_ip_internal(buf2, sizeof(buf2), kQNDnsDefaultIPv6IP); 169 | if (strchr(buf2, ':') != NULL) { 170 | bzero(buf, sizeof(buf)); 171 | qn_nat64(buf, sizeof(buf), *((uint32_t *)addr)); 172 | } 173 | } 174 | #endif 175 | 176 | freeaddrinfo(ai); 177 | return [NSString stringWithUTF8String:ip]; 178 | } 179 | 180 | + (NSString *)local { 181 | char buf[64] = {0}; 182 | int err = qn_localIp(buf, sizeof(buf)); 183 | if (err != 0) { 184 | return nil; 185 | } 186 | return [NSString stringWithUTF8String:buf]; 187 | } 188 | 189 | + (NSString *)ipHost:(NSString *)ip { 190 | NSRange range = [ip rangeOfString:@":"]; 191 | if (range.location != NSNotFound) { 192 | return [NSString stringWithFormat:@"[%@]", ip]; 193 | } 194 | return ip; 195 | } 196 | 197 | + (NSString *)nat64:(NSString *)ip { 198 | struct in_addr s = {0}; 199 | inet_pton(AF_INET, ip.UTF8String, (void *)&s); 200 | char buf[64] = {0}; 201 | qn_nat64(buf, sizeof(buf), (uint32_t)s.s_addr); 202 | return [NSString stringWithUTF8String:buf]; 203 | } 204 | 205 | + (BOOL)isIpV6FullySupported { 206 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) 207 | float sysVersion = [[[UIDevice currentDevice] systemVersion] floatValue]; 208 | if (sysVersion < 9.0) { 209 | return NO; 210 | } 211 | #else 212 | NSOperatingSystemVersion sysVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; 213 | if (sysVersion.majorVersion < 10) { 214 | return NO; 215 | } else if (sysVersion.majorVersion == 10) { 216 | return sysVersion.minorVersion >= 11; 217 | } 218 | #endif 219 | return YES; 220 | } 221 | 222 | + (BOOL)mayBeIpV4:(NSString *)domain { 223 | NSUInteger l = domain.length; 224 | if (l > 15 || l < 7) { 225 | return NO; 226 | } 227 | const char *str = domain.UTF8String; 228 | if (str == nil) { 229 | return NO; 230 | } 231 | 232 | for (const char *p = str; p < str + l; p++) { 233 | if ((*p < '0' || *p > '9') && *p != '.') { 234 | return NO; 235 | } 236 | } 237 | return YES; 238 | } 239 | 240 | @end 241 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNMD5.h: -------------------------------------------------------------------------------- 1 | // 2 | // QNMD5.h 3 | // HappyDNS_Mac 4 | // 5 | // Created by 何昊宇 on 2018/4/25. 6 | // Copyright © 2018年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QNMD5 : NSObject 12 | 13 | +(NSString *)MD5:(NSString *)string; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /HappyDNS/Util/QNMD5.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNMD5.m 3 | // HappyDNS_Mac 4 | // 5 | // Created by 何昊宇 on 2018/4/25. 6 | // Copyright © 2018年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNMD5.h" 10 | #import 11 | 12 | @implementation QNMD5 13 | 14 | + (NSString *)MD5:(NSString *)string{ 15 | 16 | const char* input = [string UTF8String]; 17 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 18 | CC_MD5(input, (CC_LONG)strlen(input), result); 19 | 20 | NSMutableString *digest = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2]; 21 | for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { 22 | [digest appendFormat:@"%02x", result[i]]; 23 | } 24 | 25 | return digest; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/HappyDNS.h: -------------------------------------------------------------------------------- 1 | ../../HappyDNS.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/NSData+QNRW.h: -------------------------------------------------------------------------------- 1 | ../../Util/NSData+QNRW.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNAsyncUdpSocket.h: -------------------------------------------------------------------------------- 1 | ../../Util/QNAsyncUdpSocket.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDes.h: -------------------------------------------------------------------------------- 1 | ../../Util/QNDes.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnsDefine.h: -------------------------------------------------------------------------------- 1 | ../../Dns/QNDnsDefine.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnsError.h: -------------------------------------------------------------------------------- 1 | ../../Common/QNDnsError.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnsManager.h: -------------------------------------------------------------------------------- 1 | ../../Common/QNDnsManager.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnsMessage.h: -------------------------------------------------------------------------------- 1 | ../../Dns/QNDnsMessage.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnsRequest.h: -------------------------------------------------------------------------------- 1 | ../../Dns/QNDnsRequest.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnsResolver.h: -------------------------------------------------------------------------------- 1 | ../../Dns/QNDnsResolver.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnsResponse.h: -------------------------------------------------------------------------------- 1 | ../../Dns/QNDnsResponse.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnsUdpResolver.h: -------------------------------------------------------------------------------- 1 | ../../Dns/QNDnsUdpResolver.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDnspodEnterprise.h: -------------------------------------------------------------------------------- 1 | ../../Http/QNDnspodEnterprise.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDohResolver.h: -------------------------------------------------------------------------------- 1 | ../../Dns/QNDohResolver.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNDomain.h: -------------------------------------------------------------------------------- 1 | ../../Common/QNDomain.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNGetAddrInfo.h: -------------------------------------------------------------------------------- 1 | ../../Util/QNGetAddrInfo.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNHex.h: -------------------------------------------------------------------------------- 1 | ../../Util/QNHex.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNHijackingDetectWrapper.h: -------------------------------------------------------------------------------- 1 | ../../Local/QNHijackingDetectWrapper.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNHosts.h: -------------------------------------------------------------------------------- 1 | ../../Local/QNHosts.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNIP.h: -------------------------------------------------------------------------------- 1 | ../../Util/QNIP.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNLruCache.h: -------------------------------------------------------------------------------- 1 | ../../Common/QNLruCache.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNMD5.h: -------------------------------------------------------------------------------- 1 | ../../Util/QNMD5.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNNetworkInfo.h: -------------------------------------------------------------------------------- 1 | ../../Common/QNNetworkInfo.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNRecord.h: -------------------------------------------------------------------------------- 1 | ../../Common/QNRecord.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNResolvUtil.h: -------------------------------------------------------------------------------- 1 | ../../Local/QNResolvUtil.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNResolver.h: -------------------------------------------------------------------------------- 1 | ../../Local/QNResolver.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNResolverDelegate.h: -------------------------------------------------------------------------------- 1 | ../../Common/QNResolverDelegate.h -------------------------------------------------------------------------------- /HappyDNS/include/HappyDNS/QNTxtResolver.h: -------------------------------------------------------------------------------- 1 | ../../Local/QNTxtResolver.h -------------------------------------------------------------------------------- /HappyDNSTests/DesTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // DesTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/8/1. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDes.h" 10 | #import 11 | 12 | @interface DesTest : XCTestCase 13 | 14 | @end 15 | 16 | @implementation DesTest 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testEncrypt { 29 | NSData *key = [@"12345678" dataUsingEncoding:NSUTF8StringEncoding]; 30 | NSString *origin = @"abcdef"; 31 | QNDes *des = [[QNDes alloc] init:key]; 32 | NSData *enc = [des encrypt:[origin dataUsingEncoding:NSUTF8StringEncoding]]; 33 | NSData *dec = [des decrpyt:enc]; 34 | NSString *n = [[NSString alloc] initWithData:dec encoding:NSUTF8StringEncoding]; 35 | // This is an example of a functional test case. 36 | XCTAssertEqualObjects(origin, n, @"PASS"); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /HappyDNSTests/DnsServerResolverTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // DnsServerTest.m 3 | // HappyDNS 4 | // 5 | // Created by yangsen on 2021/7/28. 6 | // Copyright © 2021 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QNDnsManager.h" 11 | #import "QNDomain.h" 12 | #import "QNDnsUdpResolver.h" 13 | 14 | @interface DnsServerResolverTest : XCTestCase 15 | 16 | @end 17 | 18 | @implementation DnsServerResolverTest 19 | 20 | - (void)testSimpleDns { 21 | NSString *host = @"en.wikipedia.org"; 22 | NSError *err = nil; 23 | 24 | NSArray *typeArray = @[@(kQNTypeA), @(kQNTypeAAAA)]; 25 | for (NSNumber *type in typeArray) { 26 | QNDnsUdpResolver *server = [QNDnsUdpResolver resolverWithServerIP:@"8.8.8.8" recordType:type.intValue timeout:5]; 27 | NSArray *records = [server query:[[QNDomain alloc] init:host] networkInfo:nil error:&err]; 28 | NSLog(@"== records:%@", records); 29 | XCTAssertNil(err, "query error:%@", err); 30 | XCTAssertNotNil(records, "type:%@ query result nil", type); 31 | XCTAssertTrue(records.count > 0, "type:%@ query result empty", type); 32 | } 33 | 34 | for (NSNumber *type in typeArray) { 35 | QNDnsUdpResolver *server = [QNDnsUdpResolver resolverWithServerIP:@"8.8.8.8" recordType:type.intValue timeout:5]; 36 | QNDnsManager *manager = [[QNDnsManager alloc] init:@[server] networkInfo:nil]; 37 | NSArray *records = [manager queryRecords:host]; 38 | NSLog(@"== records:%@", records); 39 | XCTAssertNotNil(records, "type:%@ query result nil", type); 40 | XCTAssertTrue(records.count > 0, "type:%@ query result empty", type); 41 | } 42 | } 43 | 44 | - (void)testMultiDnsServer { 45 | NSString *host = @"en.wikipedia.org"; 46 | NSError *err = nil; 47 | 48 | NSArray *typeArray = @[@(kQNTypeA), @(kQNTypeAAAA)]; 49 | for (NSNumber *type in typeArray) { 50 | QNDnsUdpResolver *server = [QNDnsUdpResolver resolverWithServerIPs:@[@"8.8.8.8", @"114.114.114.114"] recordType:type.intValue queue:nil timeout:5]; 51 | NSArray *records = [server query:[[QNDomain alloc] init:host] networkInfo:nil error:&err]; 52 | NSLog(@"== records:%@", records); 53 | XCTAssertNil(err, "query error:%@", err); 54 | XCTAssertNotNil(records, "type:%@ query result nil", type); 55 | XCTAssertTrue(records.count > 0, "type:%@ query result empty", type); 56 | } 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /HappyDNSTests/DnsTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // DnsTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/30. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDnsManager.h" 10 | #import "QNDomain.h" 11 | #import "QNHijackingDetectWrapper.h" 12 | #import "QNNetworkInfo.h" 13 | #import "QNResolver.h" 14 | #import 15 | 16 | @interface DnsTest : XCTestCase 17 | 18 | @end 19 | 20 | @interface NotRunResolver : NSObject 21 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error; 22 | 23 | @end 24 | 25 | @implementation NotRunResolver 26 | 27 | - (NSArray *)query:(QNDomain *)domain networkInfo:(QNNetworkInfo *)netInfo error:(NSError *__autoreleasing *)error { 28 | @throw [NSException exceptionWithName:NSInvalidArgumentException 29 | reason:@"does not run here" 30 | userInfo:nil]; 31 | return nil; 32 | } 33 | 34 | @end 35 | 36 | @implementation DnsTest 37 | 38 | - (void)setUp { 39 | [super setUp]; 40 | // Put setup code here. This method is called before the invocation of each test method in the class. 41 | } 42 | 43 | - (void)tearDown { 44 | // Put teardown code here. This method is called after the invocation of each test method in the class. 45 | [super tearDown]; 46 | } 47 | 48 | - (void)testDns { 49 | NSMutableArray *array = [[NSMutableArray alloc] init]; 50 | [array addObject:[QNResolver systemResolver]]; 51 | [array addObject:[[QNResolver alloc] initWithAddress:@"119.29.29.29"]]; 52 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]]; 53 | NSArray *records = [dns queryRecords:@"www.baidu.com"]; 54 | XCTAssertNotNil(records, @"PASS"); 55 | XCTAssertTrue(records.count > 0, @"PASS"); 56 | } 57 | 58 | - (void)testCnc { 59 | NSMutableArray *array = [[NSMutableArray alloc] init]; 60 | [array addObject:[QNResolver systemResolver]]; 61 | [array addObject:[[QNResolver alloc] initWithAddress:@"119.29.29.29"]]; 62 | QNNetworkInfo *info = [[QNNetworkInfo alloc] init:kQNMOBILE provider:kQNISP_CNC]; 63 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:info]; 64 | [dns putHosts:@"hello.qiniu.com" ipv4:@"1.1.1.1"]; 65 | [dns putHosts:@"hello.qiniu.com" ipv4:@"2.2.2.2"]; 66 | [dns putHosts:@"qiniu.com" ipv4:@"3.3.3.3"]; 67 | [dns putHosts:@"qiniu.com" ip:@"4.4.4.4" type:kQNTypeA provider:kQNISP_CNC]; 68 | QNDomain *domain = [[QNDomain alloc] init:@"qiniu.com" hostsFirst:YES hasCname:NO maxTtl:0]; 69 | NSArray *r = [dns queryRecordsWithDomain:domain]; 70 | XCTAssertEqual(r.count, 1, @"PASS"); 71 | XCTAssertEqualObjects(@"4.4.4.4", [r.firstObject value], @"PASS"); 72 | } 73 | 74 | - (void)testTtl { 75 | NSMutableArray *array = [[NSMutableArray alloc] init]; 76 | [array addObject:[[QNHijackingDetectWrapper alloc] initWithResolver:[QNResolver systemResolver]]]; 77 | [array addObject:[[QNHijackingDetectWrapper alloc] initWithResolver:[[QNResolver alloc] initWithAddress:@"114.114.115.115"]]]; 78 | QNNetworkInfo *info = [[QNNetworkInfo alloc] init:kQNMOBILE provider:kQNISP_CNC]; 79 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:info]; 80 | [dns putHosts:@"hello.qiniu.com" ipv4:@"1.1.1.1"]; 81 | [dns putHosts:@"hello.qiniu.com" ipv4:@"2.2.2.2"]; 82 | [dns putHosts:@"qiniu.com" ipv4:@"3.3.3.3"]; 83 | [dns putHosts:@"qiniu.com" ip:@"4.4.4.4" type:kQNTypeA provider:kQNISP_CNC]; 84 | 85 | QNDomain *domain = [[QNDomain alloc] init:@"qiniu.com" hostsFirst:NO hasCname:NO maxTtl:10]; 86 | NSArray *r = [dns queryRecordsWithDomain:domain]; 87 | XCTAssertEqual(r.count, 1, @"PASS"); 88 | XCTAssertEqualObjects(@"4.4.4.4", [r.firstObject value], @"PASS"); 89 | 90 | domain = [[QNDomain alloc] init:@"qiniu.com" hostsFirst:NO hasCname:NO maxTtl:1000]; 91 | r = [dns queryRecordsWithDomain:domain]; 92 | XCTAssertEqual(r.count, 1, @"PASS"); 93 | XCTAssertFalse([@"4.4.4.4" isEqualToString:[r.firstObject value]], @"PASS"); 94 | } 95 | 96 | - (void)testCname { 97 | NSMutableArray *array = [[NSMutableArray alloc] init]; 98 | [array addObject:[[QNHijackingDetectWrapper alloc] initWithResolver:[QNResolver systemResolver]]]; 99 | [array addObject:[[QNHijackingDetectWrapper alloc] initWithResolver:[[QNResolver alloc] initWithAddress:@"114.114.115.115"]]]; 100 | QNNetworkInfo *info = [QNNetworkInfo normal]; 101 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:info]; 102 | [dns putHosts:@"hello.qiniu.com" ipv4:@"1.1.1.1"]; 103 | [dns putHosts:@"hello.qiniu.com" ipv4:@"2.2.2.2"]; 104 | [dns putHosts:@"qiniu.com" ipv4:@"3.3.3.3"]; 105 | [dns putHosts:@"qiniu.com" ip:@"4.4.4.4" type:kQNTypeA provider:kQNISP_CNC]; 106 | 107 | QNDomain *domain = [[QNDomain alloc] init:@"qiniu.com" hostsFirst:NO hasCname:YES maxTtl:0]; 108 | NSArray *r = [dns queryRecordsWithDomain:domain]; 109 | XCTAssertEqual(r.count, 1, @"PASS"); 110 | XCTAssertEqualObjects(@"3.3.3.3", [r.firstObject value], @"PASS"); 111 | 112 | domain = [[QNDomain alloc] init:@"qiniu.com" hostsFirst:NO hasCname:NO maxTtl:0]; 113 | r = [dns queryRecordsWithDomain:domain]; 114 | XCTAssertEqual(r.count, 1, @"PASS"); 115 | XCTAssertFalse([@"3.3.3.3" isEqualToString:r[0]], @"PASS"); 116 | } 117 | 118 | - (void)testUrlQuery { 119 | NSMutableArray *array = [[NSMutableArray alloc] init]; 120 | [array addObject:[QNResolver systemResolver]]; 121 | [array addObject:[[QNResolver alloc] initWithAddress:@"119.29.29.29"]]; 122 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]]; 123 | NSURL *u = [[NSURL alloc] initWithString:@"rtmp://www.qiniu.com/abc?q=1"]; 124 | NSURL *u2 = [dns queryAndReplaceWithIP:u]; 125 | 126 | XCTAssertNotNil(u2, @"PASS"); 127 | NSLog(@"%@ %@", u.path, u2.path); 128 | XCTAssertEqualObjects(u.path, u2.path, @"PASS"); 129 | XCTAssertEqualObjects(u.scheme, u2.scheme, @"PASS"); 130 | XCTAssertNotEqualObjects(u.host, u2.host, @"PASS"); 131 | } 132 | 133 | - (void)testUrlQueryV6 { 134 | NSMutableArray *array = [[NSMutableArray alloc] init]; 135 | [array addObject:[QNResolver systemResolver]]; 136 | [array addObject:[[QNResolver alloc] initWithAddress:@"119.29.29.29"]]; 137 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]]; 138 | NSURL *u = [[NSURL alloc] initWithString:@"rtmp://ipv6test.qiniu.com/abc?q=1"]; 139 | NSURL *u2 = [dns queryAndReplaceWithIP:u]; 140 | 141 | XCTAssertNotNil(u2, @"PASS"); 142 | NSLog(@"path %@ %@", u.path, u2.path); 143 | XCTAssertEqualObjects(u.path, u2.path, @"PASS"); 144 | XCTAssertEqualObjects(u.scheme, u2.scheme, @"PASS"); 145 | NSLog(@"host %@ %@", u.host, u2.host); 146 | XCTAssertNotEqualObjects(u.host, u2.host, @"PASS"); 147 | } 148 | 149 | - (void)testIpQuery { 150 | NSMutableArray *array = [[NSMutableArray alloc] init]; 151 | [array addObject:[NotRunResolver new]]; 152 | [array addObject:[[QNResolver alloc] initWithAddress:@"119.29.29.29"]]; 153 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]]; 154 | NSURL *u = [[NSURL alloc] initWithString:@"rtmp://119.29.29.29/abc?q=1"]; 155 | NSURL *u2 = [dns queryAndReplaceWithIP:u]; 156 | 157 | XCTAssertNotNil(u2, @"PASS"); 158 | NSLog(@"%@ %@", u.path, u2.path); 159 | XCTAssertEqualObjects(u.path, u2.path, @"PASS"); 160 | XCTAssertEqualObjects(u.scheme, u2.scheme, @"PASS"); 161 | XCTAssertEqualObjects(u.host, u2.host, @"PASS"); 162 | } 163 | 164 | - (void)testNeedHttpDns { 165 | NSTimeZone *timeZone = [NSTimeZone localTimeZone]; 166 | NSString *tzName = [timeZone name]; 167 | if ([tzName isEqual:@"Asia/Shanghai"]) { 168 | XCTAssertTrue([QNDnsManager needHttpDns]); 169 | } else { 170 | XCTAssertFalse([QNDnsManager needHttpDns]); 171 | } 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /HappyDNSTests/DnspodEnterpriseTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // DnspodEnterpriseTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/8/1. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDnspodEnterprise.h" 10 | #import "QNDomain.h" 11 | #import "QNRecord.h" 12 | #import "QNResolverDelegate.h" 13 | #import 14 | 15 | @interface DnspodEnterpriseTest : XCTestCase 16 | 17 | @end 18 | 19 | @implementation DnspodEnterpriseTest 20 | 21 | - (void)setUp { 22 | [super setUp]; 23 | // Put setup code here. This method is called before the invocation of each test method in the class. 24 | } 25 | 26 | - (void)tearDown { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | [super tearDown]; 29 | } 30 | 31 | //- (void)testDnspodEnterprise { 32 | // id resolver = [[QNDnspodEnterprise alloc] initWithId:@"007" key:@"abcdef"]; 33 | // NSArray *records = [resolver query:[[QNDomain alloc]init:@"baidu.com"] networkInfo:nil error:nil]; 34 | // XCTAssert(records != nil, @"Pass"); 35 | // XCTAssert(records.count > 0, @"Pass"); 36 | // XCTAssert(records.count >= 1, @"Pass"); 37 | // QNRecord *record = [records objectAtIndex:0]; 38 | // XCTAssert(record.ttl >= 0, @"Pass"); 39 | // 40 | // records = [resolver query:[[QNDomain alloc]init:@"www.qiniu.com"] networkInfo:nil error:nil]; 41 | // XCTAssert(records != nil, @"Pass"); 42 | // XCTAssert(records.count >= 1, @"Pass"); 43 | // record = [records objectAtIndex:0]; 44 | // XCTAssert(record.ttl >= 0, @"Pass"); 45 | //} 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /HappyDNSTests/DohResolverTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // DohTest.m 3 | // HappyDNS 4 | // 5 | // Created by yangsen on 2021/7/28. 6 | // Copyright © 2021 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | #import 9 | #import "QNDnsManager.h" 10 | #import "QNDohResolver.h" 11 | #import "QNDomain.h" 12 | 13 | @interface DohResolverTest : XCTestCase 14 | 15 | @end 16 | 17 | @implementation DohResolverTest 18 | 19 | - (void)testSimpleDns { 20 | NSString *host = @"en.wikipedia.org"; 21 | NSString *serverUrl = @"https://223.6.6.6/dns-query"; 22 | NSError *err = nil; 23 | 24 | NSArray *typeArray = @[@(kQNTypeA), @(kQNTypeAAAA)]; 25 | for (NSNumber *type in typeArray) { 26 | QNDohResolver *server = [QNDohResolver resolverWithServer:serverUrl recordType:type.intValue timeout:2]; 27 | NSArray *records = [server query:[[QNDomain alloc] init:host] networkInfo:nil error:&err]; 28 | NSLog(@"== records:%@", records); 29 | XCTAssertNil(err, "query error:%@", err); 30 | XCTAssertNotNil(records, "type:%@ query result nil", type); 31 | XCTAssertTrue(records.count > 0, "type:%@ query result empty", type); 32 | } 33 | 34 | for (NSNumber *type in typeArray) { 35 | QNDohResolver *server = [QNDohResolver resolverWithServers:@[serverUrl] recordType:type.intValue timeout:5]; 36 | QNDnsManager *manager = [[QNDnsManager alloc] init:@[server] networkInfo:nil]; 37 | NSArray *records = [manager queryRecords:host]; 38 | NSLog(@"== records:%@", records); 39 | XCTAssertNotNil(records, "type:%@ query result nil", type); 40 | XCTAssertTrue(records.count > 0, "type:%@ query result empty", type); 41 | } 42 | } 43 | 44 | - (void)testMultiDnsServer { 45 | NSString *host = @"en.wikipedia.org"; 46 | NSError *err = nil; 47 | 48 | NSArray *typeArray = @[@(kQNTypeA), @(kQNTypeAAAA)]; 49 | for (NSNumber *type in typeArray) { 50 | // https://dns.alidns.com/dns-query 51 | // https://dns.google/dns-query 52 | QNDohResolver *server = [QNDohResolver resolverWithServers:@[@"https://dns.alidns.com/dns-query", @"https://dns.google/dns-query"] recordType:type.intValue timeout:5]; 53 | NSArray *records = [server query:[[QNDomain alloc] init:host] networkInfo:nil error:&err]; 54 | NSLog(@"== records:%@", records); 55 | XCTAssertNil(err, "query error:%@", err); 56 | XCTAssertNotNil(records, "type:%@ query result nil", type); 57 | XCTAssertTrue(records.count > 0, "type:%@ query result empty", type); 58 | } 59 | 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /HappyDNSTests/DohTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // DohTest.h 3 | // HappyDNS 4 | // 5 | // Created by yangsen on 2021/7/28. 6 | // Copyright © 2021 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface DohTest : XCTestCase 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /HappyDNSTests/GetAddrInfoTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // GetAddrInfoTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/7/19. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "QNDnsManager.h" 13 | #import "QNDomain.h" 14 | #import "QNNetworkInfo.h" 15 | #import "QNResolver.h" 16 | 17 | #import "QNGetAddrInfo.h" 18 | @interface GetAddrInfoTest : XCTestCase 19 | 20 | @end 21 | 22 | @implementation GetAddrInfoTest 23 | 24 | - (void)setUp { 25 | [super setUp]; 26 | // Put setup code here. This method is called before the invocation of each test method in the class. 27 | } 28 | 29 | static int count(struct addrinfo *ai) { 30 | int count = 0; 31 | while (ai != NULL) { 32 | count++; 33 | ai = ai->ai_next; 34 | } 35 | return count; 36 | } 37 | 38 | static struct addrinfo resetHints() { 39 | struct addrinfo hints = {0}; 40 | hints.ai_family = PF_UNSPEC; 41 | hints.ai_socktype = SOCK_STREAM; 42 | hints.ai_flags = AI_DEFAULT; 43 | return hints; 44 | } 45 | 46 | - (void)testNotSet { 47 | qn_set_dns_callback(NULL); 48 | struct addrinfo hints = resetHints(); 49 | struct addrinfo *ai = NULL; 50 | int x = qn_getaddrinfo("baidu.com", "80", &hints, &ai); 51 | XCTAssert(x == 0); 52 | XCTAssert(ai != NULL); 53 | 54 | #if __IPHONE_OS_VERSION_MIN_REQUIRED 55 | struct addrinfo hints2 = resetHints(); 56 | struct addrinfo *ai2 = NULL; 57 | int x2 = getaddrinfo("baidu.com", "80", &hints2, &ai2); 58 | NSLog(@"return qn %d, ori %d", x, x2); 59 | NSLog(@"count qn %d, ori %d", count(ai), count(ai2)); 60 | XCTAssert(x2 == x); 61 | freeaddrinfo(ai2); 62 | #endif 63 | qn_freeaddrinfo(ai); 64 | } 65 | 66 | static QNDnsManager *dns = nil; 67 | 68 | - (void) template:(const char *)host { 69 | struct addrinfo hints = resetHints(); 70 | struct addrinfo *ai = NULL; 71 | int x = qn_getaddrinfo(host, "80", &hints, &ai); 72 | XCTAssert(x == 0); 73 | XCTAssert(ai != NULL); 74 | 75 | #if __IPHONE_OS_VERSION_MIN_REQUIRED 76 | struct addrinfo hints2 = resetHints(); 77 | struct addrinfo *ai2 = NULL; 78 | int x2 = getaddrinfo(host, "80", &hints2, &ai2); 79 | XCTAssert(x2 == 0); 80 | XCTAssert(count(ai) <= count(ai2)); 81 | NSLog(@"return qn %d, ori %d", x, x2); 82 | NSLog(@"count qn %d ori %d", count(ai), count(ai2)); 83 | freeaddrinfo(ai2); 84 | #endif 85 | 86 | qn_freeaddrinfo(ai); 87 | } 88 | 89 | - (void)testCustomDns { 90 | dns = [[QNDnsManager alloc] init:@[ [QNResolver systemResolver] ] networkInfo:nil]; 91 | [QNDnsManager setDnsManagerForGetAddrInfo:dns]; 92 | [self template:"baidu.com"]; 93 | [self template:"www.qiniu.com"]; 94 | [self template:"qq.com"]; 95 | [self template:"taobao.com"]; 96 | } 97 | 98 | - (void)testNoIpStatusCallback { 99 | qn_set_dns_callback(NULL); 100 | struct addrinfo hints2 = resetHints(); 101 | struct addrinfo *ai2 = NULL; 102 | qn_ip_report(ai2, 3, 4); 103 | getaddrinfo("8.8.8.8", "80", &hints2, &ai2); 104 | qn_ip_report(ai2, 1, 2); 105 | freeaddrinfo(ai2); 106 | } 107 | 108 | - (void)testIpStatusCallback { 109 | __block NSString *ip1; 110 | __block int c = 0; 111 | __block int m = 0; 112 | [QNDnsManager setIpStatusCallback:^(NSString *ip, int code, int ms) { 113 | ip1 = ip; 114 | c = code; 115 | m = ms; 116 | }]; 117 | struct addrinfo hints2 = resetHints(); 118 | struct addrinfo *ai2 = NULL; 119 | getaddrinfo("8.8.8.8", "80", &hints2, &ai2); 120 | 121 | qn_ip_report(ai2, 1, 2); 122 | freeaddrinfo(ai2); 123 | XCTAssertEqual(c, 1); 124 | XCTAssertEqual(m, 2); 125 | XCTAssertEqualObjects(ip1, @"8.8.8.8"); 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /HappyDNSTests/HexTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // HexTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/8/1. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QNHex.h" 12 | 13 | @interface HexTest : XCTestCase 14 | 15 | @end 16 | 17 | @implementation HexTest 18 | 19 | - (void)setUp { 20 | [super setUp]; 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | } 23 | 24 | - (void)tearDown { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testHex { 30 | NSString *origin = @"12345678"; 31 | NSString *hex = [QNHex encodeHexString:origin]; 32 | XCTAssertEqual(origin.length * 2, hex.length, @"PASS"); 33 | NSString *n = [QNHex decodeHexToString:hex]; 34 | XCTAssertEqualObjects(origin, n, @"PASS"); 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /HappyDNSTests/HostsTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // HostsTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/30. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDomain.h" 10 | #import "QNHosts.h" 11 | #import "QNNetworkInfo.h" 12 | #import 13 | 14 | @interface HostsTest : XCTestCase 15 | 16 | @end 17 | 18 | @implementation HostsTest 19 | 20 | - (void)setUp { 21 | [super setUp]; 22 | // Put setup code here. This method is called before the invocation of each test method in the class. 23 | } 24 | 25 | - (void)tearDown { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testQuery { 31 | QNHosts *hosts = [[QNHosts alloc] init]; 32 | [hosts put:@"hello.qiniu.com" record:[[QNRecord alloc] init:@"1.1.1.1" ttl:120 type:kQNTypeA source:QNRecordSourceUnknown]]; 33 | [hosts put:@"hello.qiniu.com" record:[[QNRecord alloc] init:@"2.2.2.2" ttl:120 type:kQNTypeA source:QNRecordSourceUnknown]]; 34 | [hosts put:@"qiniu.com" record:[[QNRecord alloc] init:@"3.3.3.3" ttl:120 type:kQNTypeA source:QNRecordSourceUnknown]]; 35 | QNNetworkInfo *info = [QNNetworkInfo normal]; 36 | NSArray *array = [hosts query:[[QNDomain alloc] init:@"hello.qiniu.com"] networkInfo:info]; 37 | XCTAssert(array.count == 2, @"Pass"); 38 | XCTAssert([@"2.2.2.2" isEqual:array.firstObject.value], @"PASS"); 39 | XCTAssert([@"1.1.1.1" isEqual:array[1].value], @"PASS"); 40 | 41 | NSArray *array2 = [hosts query:[[QNDomain alloc] init:@"hello.qiniu.com"] networkInfo:info]; 42 | XCTAssert(array2.count == 2, @"Pass"); 43 | XCTAssert([@"1.1.1.1" isEqual:array2.firstObject.value], @"PASS"); 44 | XCTAssert([@"2.2.2.2" isEqual:array2[1].value], @"PASS"); 45 | } 46 | 47 | - (void)testCnc { 48 | QNHosts *hosts = [[QNHosts alloc] init]; 49 | [hosts put:@"hello.qiniu.com" record:[[QNRecord alloc] init:@"1.1.1.1" ttl:120 type:kQNTypeA source:QNRecordSourceUnknown]]; 50 | [hosts put:@"hello.qiniu.com" record:[[QNRecord alloc] init:@"2.2.2.2" ttl:120 type:kQNTypeA source:QNRecordSourceUnknown]]; 51 | [hosts put:@"qiniu.com" record:[[QNRecord alloc] init:@"3.3.3.3" ttl:120 type:kQNTypeA source:QNRecordSourceUnknown]]; 52 | [hosts put:@"qiniu.com" record:[[QNRecord alloc] init:@"4.4.4.4" ttl:120 type:kQNTypeA source:QNRecordSourceUnknown] provider:kQNISP_CNC]; 53 | QNNetworkInfo *info = [[QNNetworkInfo alloc] init:kQNMOBILE provider:kQNISP_CNC]; 54 | NSArray *r = [hosts query:[[QNDomain alloc] init:@"qiniu.com"] networkInfo:info]; 55 | XCTAssertTrue(r.count == 1, @"PASS"); 56 | XCTAssertEqualObjects(@"4.4.4.4", r.firstObject.value); 57 | r = [hosts query:[[QNDomain alloc] init:@"qiniu.com"] networkInfo:[QNNetworkInfo normal]]; 58 | XCTAssertTrue(r.count == 1, @"PASS"); 59 | XCTAssertEqualObjects(@"3.3.3.3", r.firstObject.value); 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /HappyDNSTests/IPTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // IPTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/5/26. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QNIP.h" 12 | 13 | @interface IPTest : XCTestCase 14 | 15 | @end 16 | 17 | @implementation IPTest 18 | 19 | - (void)setUp { 20 | [super setUp]; 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | } 23 | 24 | - (void)tearDown { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testAdaptiveIP { 30 | NSString* ip = @"1.2.3.4"; 31 | NSString* ip2 = [QNIP adaptiveIp:ip]; 32 | XCTAssertNotNil(ip2, @"pass"); 33 | if (![QNIP isV6]) { 34 | XCTAssertEqualObjects(ip2, ip, @"pass"); 35 | } else { 36 | XCTAssertEqualObjects(ip2, @"64:ff9b::102:304", @"pass"); 37 | } 38 | } 39 | 40 | - (void)testAdaptiveIP2 { 41 | NSString* ip = @"8.8.8.8"; 42 | NSString* ip2 = [QNIP adaptiveIp:ip]; 43 | NSLog(@"ip %@", ip2); 44 | XCTAssertNotNil(ip2, @"pass"); 45 | if (![QNIP isV6]) { 46 | XCTAssertEqualObjects(ip2, ip, @"pass"); 47 | } else { 48 | XCTAssertEqualObjects(ip2, @"64:ff9b::808:808", @"pass"); 49 | } 50 | } 51 | 52 | - (void)testAdaptiveIP3 { 53 | NSString* ip = @"119.29.29.29"; 54 | NSString* ip2 = [QNIP adaptiveIp:ip]; 55 | NSLog(@"ip %@", ip2); 56 | XCTAssertNotNil(ip2, @"pass"); 57 | if (![QNIP isV6]) { 58 | XCTAssertEqualObjects(ip2, ip, @"pass"); 59 | } else { 60 | XCTAssertEqualObjects(ip2, @"64:ff9b::771d:1d1d", @"pass"); 61 | } 62 | } 63 | 64 | - (void)testNat64 { 65 | NSString* ip = @"119.29.29.29"; 66 | NSString* ip2 = [QNIP nat64:ip]; 67 | NSLog(@"ip %@", ip2); 68 | XCTAssertNotNil(ip2, @"pass"); 69 | 70 | XCTAssertEqualObjects(ip2, @"64:ff9b::771d:1d1d", @"pass"); 71 | } 72 | 73 | - (void)testLocalIP { 74 | NSString* ip = [QNIP local]; 75 | NSLog(@"ip %@", ip); 76 | XCTAssertNotNil(ip, @"pass"); 77 | } 78 | 79 | - (void)testHost { 80 | NSString* ipv4 = @"1.2.3.4"; 81 | XCTAssertEqualObjects(ipv4, [QNIP ipHost:ipv4], @"pass"); 82 | 83 | NSString* domain = @"a.b.c"; 84 | XCTAssertEqualObjects(domain, [QNIP ipHost:domain], @"pass"); 85 | 86 | NSString* ipv6 = @"::AB:CD"; 87 | NSString* a = [NSString stringWithFormat:@"[%@]", ipv6]; 88 | XCTAssertEqualObjects(a, [QNIP ipHost:ipv6], @"pass"); 89 | } 90 | 91 | - (void)testMayBeIpV4 { 92 | NSString* ip = @"0.0.0.0"; 93 | XCTAssert([QNIP mayBeIpV4:ip]); 94 | 95 | ip = @"a.0.0.0"; 96 | XCTAssert(![QNIP mayBeIpV4:ip]); 97 | 98 | ip = @"0.0.0"; 99 | XCTAssert(![QNIP mayBeIpV4:ip]); 100 | 101 | ip = @"a.b.com"; 102 | XCTAssert(![QNIP mayBeIpV4:ip]); 103 | 104 | ip = @"255.255.255.255"; 105 | XCTAssert([QNIP mayBeIpV4:ip]); 106 | 107 | ip = @"99.99.99.99"; 108 | XCTAssert([QNIP mayBeIpV4:ip]); 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /HappyDNSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | BNDL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | 1 28 | 29 | 30 | -------------------------------------------------------------------------------- /HappyDNSTests/LruCacheTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // LruCacheTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/7/5. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNLruCache.h" 10 | #import 11 | 12 | @interface LruCacheTest : XCTestCase 13 | 14 | @end 15 | 16 | @implementation LruCacheTest 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testPut { 29 | QNLruCache *x = [[QNLruCache alloc] init:2]; 30 | [x setObject:@"1" forKey:@"1"]; 31 | [x setObject:@"2" forKey:@"2"]; 32 | [x setObject:@"3" forKey:@"3"]; 33 | XCTAssertNil([x objectForKey:@"1"]); 34 | XCTAssertEqualObjects(@"2", [x objectForKey:@"2"]); 35 | XCTAssertEqualObjects(@"3", [x objectForKey:@"3"]); 36 | [x removeObjectForKey:@"2"]; 37 | [x setObject:@"1" forKey:@"1"]; 38 | XCTAssertEqualObjects(@"1", [x objectForKey:@"1"]); 39 | XCTAssertNil([x objectForKey:@"2"]); 40 | } 41 | 42 | - (void)testOut { 43 | QNLruCache *x = [[QNLruCache alloc] init:2]; 44 | [x setObject:@"1" forKey:@"1"]; 45 | [x setObject:@"2" forKey:@"2"]; 46 | [x objectForKey:@"1"]; 47 | [x setObject:@"3" forKey:@"3"]; 48 | XCTAssertNil([x objectForKey:@"2"]); 49 | XCTAssertEqualObjects(@"1", [x objectForKey:@"1"]); 50 | XCTAssertEqualObjects(@"3", [x objectForKey:@"3"]); 51 | [x removeObjectForKey:@"2"]; 52 | [x setObject:@"1" forKey:@"1"]; 53 | XCTAssertEqualObjects(@"1", [x objectForKey:@"1"]); 54 | XCTAssertNil([x objectForKey:@"2"]); 55 | } 56 | 57 | - (void)testClear { 58 | QNLruCache *x = [[QNLruCache alloc] init:2]; 59 | [x setObject:@"1" forKey:@"1"]; 60 | [x setObject:@"2" forKey:@"2"]; 61 | [x setObject:@"3" forKey:@"3"]; 62 | [x removeAllObjects]; 63 | XCTAssertNil([x objectForKey:@"3"]); 64 | [x setObject:@"1" forKey:@"1"]; 65 | [x setObject:@"2" forKey:@"2"]; 66 | [x setObject:@"3" forKey:@"3"]; 67 | XCTAssertNil([x objectForKey:@"1"]); 68 | XCTAssertEqualObjects(@"2", [x objectForKey:@"2"]); 69 | XCTAssertEqualObjects(@"3", [x objectForKey:@"3"]); 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /HappyDNSTests/NetworkTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // QNNetworkTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/7/15. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNNetworkInfo.h" 10 | #import 11 | 12 | @interface QNNetworkTest : XCTestCase 13 | 14 | @end 15 | 16 | @implementation QNNetworkTest 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | // conflict with dnsmanager test 28 | //- (void)testNetworkChange { 29 | // BOOL changed = [QNNetworkInfo isNetworkChanged]; 30 | // XCTAssertTrue(changed, @"PASS"); 31 | // changed =[QNNetworkInfo isNetworkChanged]; 32 | // XCTAssertTrue(!changed, @"PASS"); 33 | //} 34 | 35 | - (void)testLocalIp { 36 | NSString *ip = [QNNetworkInfo getIp]; 37 | XCTAssertNotNil(ip, @"PASS"); 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /HappyDNSTests/ResolverTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // ResolverTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 15/6/24. 6 | // Copyright (c) 2015年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import "QNDomain.h" 10 | #import "QNRecord.h" 11 | #import "QNResolver.h" 12 | #import "QNResolverDelegate.h" 13 | #import 14 | 15 | #import "QNIP.h" 16 | 17 | @interface ResolverTest : XCTestCase 18 | 19 | @end 20 | 21 | @implementation ResolverTest 22 | 23 | - (void)setUp { 24 | [super setUp]; 25 | } 26 | 27 | - (void)tearDown { 28 | [super tearDown]; 29 | } 30 | 31 | - (void) template:(NSString *)server { 32 | id resolver = [[QNResolver alloc] initWithAddress:server]; 33 | NSArray *records = [resolver query:[[QNDomain alloc] init:@"baidu.com"] networkInfo:nil error:nil]; 34 | XCTAssert(records != nil, @"Pass"); 35 | XCTAssert(records.count >= 1, @"Pass"); 36 | QNRecord *record = [records objectAtIndex:0]; 37 | XCTAssert(record.ttl >= 0, @"Pass"); 38 | 39 | records = [resolver query:[[QNDomain alloc] init:@"www.qiniu.com"] networkInfo:nil error:nil]; 40 | XCTAssert(records != nil, @"Pass"); 41 | XCTAssert(records.count >= 3, @"Pass"); 42 | record = [records objectAtIndex:0]; 43 | XCTAssert(record.ttl >= 0, @"Pass"); 44 | 45 | records = [resolver query:[[QNDomain alloc] init:@"fasdfasfasf.qiniu.com"] networkInfo:nil error:nil]; 46 | XCTAssert(records == nil, @"Pass"); 47 | } 48 | 49 | - (void)templateV6:(NSString *)server { 50 | if (![QNIP isV6]) { 51 | return; 52 | } 53 | id resolver = [[QNResolver alloc] initWithAddress:server]; 54 | NSArray *records = [resolver query:[[QNDomain alloc] init:@"ipv6test.qiniu.com"] networkInfo:nil error:nil]; 55 | XCTAssert(records != nil, @"Pass"); 56 | XCTAssert(records.count >= 1, @"Pass"); 57 | QNRecord *record = [records objectAtIndex:0]; 58 | XCTAssert(record.ttl >= 0, @"Pass"); 59 | XCTAssert(record.type == kQNTypeAAAA, @"Pass"); 60 | XCTAssert([record.value isEqual:@"2404:6800:4005:802::2004"], @"Pass"); 61 | } 62 | 63 | - (void)testLocal { 64 | [self template:nil]; 65 | } 66 | 67 | // http://www.alidns.com/ 68 | //- (void)testAli { 69 | // [self template:@"223.5.5.5"]; 70 | //} 71 | 72 | // https://www.114dns.com/ 73 | - (void)test114 { 74 | [self template:@"114.114.115.115"]; 75 | } 76 | 77 | // http://dudns.baidu.com/ 78 | //- (void)testDu { 79 | // [self template:@"180.76.76.76"]; 80 | //} 81 | 82 | // http://www.sdns.cn/ 83 | //- (void)testCnnic { 84 | // [self template:@"1.2.4.8"]; 85 | //} 86 | 87 | - (void)testGoogle { 88 | [self template:@"8.8.8.8"]; 89 | } 90 | 91 | // http://www.dnspai.com/ 92 | //- (void)testPai { 93 | // [self template:@"101.226.4.6"]; 94 | //} 95 | 96 | //- (void)testDnspod { 97 | // [self template:@"119.29.29.29"]; 98 | //} 99 | 100 | - (void)testGetDnsServer { 101 | NSString *dns = [QNResolver systemDnsServer]; 102 | NSLog(@"dns %@", dns); 103 | XCTAssertNotNil(dns, @"pass"); 104 | } 105 | 106 | //- (void)testTimeout { 107 | // id resolver = [[QNResolver alloc] initWithAddress:@"8.1.1.1" timeout:5]; 108 | // NSError *err; 109 | // NSDate *t1 = [NSDate date]; 110 | // NSArray *records = [resolver query:[[QNDomain alloc] init:@"baidu.com"] networkInfo:nil error:&err]; 111 | // NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:t1]; 112 | // XCTAssert(duration > 4 && duration < 6, @"Pass"); 113 | // NSLog(@"duration is %f", duration); 114 | // 115 | // XCTAssert(records == nil, @"Pass"); 116 | // XCTAssert(err != nil, @"Pass"); 117 | //} 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /HappyDNSTests/TxtResolverTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // TxtResolverTest.m 3 | // HappyDNS 4 | // 5 | // Created by bailong on 16/1/5. 6 | // Copyright © 2016年 Qiniu Cloud Storage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QNDomain.h" 12 | #import "QNRecord.h" 13 | #import "QNResolverDelegate.h" 14 | #import "QNTxtResolver.h" 15 | 16 | @interface TxtResolverTest : XCTestCase 17 | 18 | @end 19 | 20 | @implementation TxtResolverTest 21 | 22 | - (void)setUp { 23 | [super setUp]; 24 | } 25 | 26 | - (void)tearDown { 27 | [super tearDown]; 28 | } 29 | 30 | - (void) template:(NSString *)server { 31 | id resolver = [[QNTxtResolver alloc] initWithAddress:server]; 32 | // txttest.qiniu.com. 600 IN TXT "183.136.139.10,183.136.139.16,115.231.182.136" 33 | NSArray *records = [resolver query:[[QNDomain alloc] init:@"txttest.qiniu.com"] networkInfo:nil error:nil]; 34 | XCTAssert(records != nil, @"Pass"); 35 | XCTAssert(records.count >= 2, @"Pass"); 36 | QNRecord *record = [records objectAtIndex:0]; 37 | XCTAssert(record.ttl >= 0, @"Pass"); 38 | } 39 | 40 | - (void)testLocal { 41 | [self template:nil]; 42 | } 43 | 44 | - (void)testDnspod { 45 | [self template:@"119.29.29.29"]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Qiniu, Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "HappyDNS", 8 | platforms: [ 9 | .macOS(.v10_11), 10 | .iOS(.v9) 11 | ], 12 | products: [ 13 | .library( 14 | name: "HappyDNS", 15 | targets: ["HappyDNS"]), 16 | ], 17 | dependencies: [ 18 | ], 19 | targets: [ 20 | .target( 21 | name: "HappyDNS", 22 | path: "HappyDNS", 23 | sources: ["Common", "Dns", "Http", "Local", "Util"], 24 | cSettings: [ 25 | .headerSearchPath("Common"), 26 | .headerSearchPath("Dns"), 27 | .headerSearchPath("Http"), 28 | .headerSearchPath("Local"), 29 | .headerSearchPath("Util"), 30 | ], 31 | linkerSettings:[ 32 | .linkedLibrary("resolv", nil) 33 | ]), 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | target "HappyDNS_iOSTests" do 4 | platform :ios, "6.0" 5 | pod 'AGAsyncTestHelper/Shorthand' 6 | end 7 | 8 | target "HappyDNS_MacTests" do 9 | platform :osx, "10.8" 10 | pod 'AGAsyncTestHelper/Shorthand' 11 | end 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Happy DNS for Objective-C 2 | 3 | [![@qiniu on weibo](http://img.shields.io/badge/weibo-%40qiniutek-blue.svg)](http://weibo.com/qiniutek) 4 | [![LICENSE](https://img.shields.io/github/license/qiniu/happy-dns-objc.svg)](https://github.com/qiniu/happy-dns-objc/blob/master/LICENSE) 5 | [![Build Status](https://travis-ci.org/qiniu/happy-dns-objc.svg?branch=master)](https://travis-ci.org/qiniu/happy-dns-objc) 6 | [![GitHub release](https://img.shields.io/github/v/tag/qiniu/happy-dns-objc.svg?label=release)](https://github.com/qiniu/happy-dns-objc/releases) 7 | [![codecov](https://codecov.io/gh/qiniu/happy-dns-objc/branch/master/graph/badge.svg)](https://codecov.io/gh/qiniu/happy-dns-objc) 8 | ![Platform](http://img.shields.io/cocoapods/p/HappyDNS.svg) 9 | 10 | ## 用途 11 | 12 | 调用系统底层Dns解析库,可以使用 114 等第三方 dns 解析,可以使用 Doh 协议的 Dns 解析方案,也可以集成 dnspod 等 httpdns。另外也有丰富的 hosts 域名配置。 13 | 14 | ## 安装 15 | 16 | 通过 CocoaPods 17 | ```ruby 18 | pod "HappyDNS" 19 | ``` 20 | 21 | 通过 Swift Package Manager (Xcode 11+) 22 | ``` 23 | App 对接: 24 | File -> Swift Packages -> Add Package Dependency,输入 HappyDNS 库链接,选择相应版本即可 25 | 库链接: https://github.com/qiniu/happy-dns-objc 26 | 27 | 库对接: 28 | let package = Package( 29 | dependencies: [ 30 | .package(url: "https://github.com/qiniu/happy-dns-objc", from: "1.0.4") 31 | ], 32 | // ... 33 | ) 34 | 35 | ``` 36 | 37 | ## 运行环境 38 | 39 | 40 | ## 使用方法 41 | * 返回 IP 列表 42 | ``` 43 | NSMutableArray *array = [[NSMutableArray alloc] init]; 44 | [array addObject:[QNResolver systemResolver]]; 45 | [array addObject:[[QNResolver alloc] initWithAddress:@"119.29.29.29"]]; 46 | [array addObject:[QNDohResolver resolverWithServer:@"https://dns.alidns.com/dns-query"]]; 47 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]]; 48 | NSArray *records = [dns queryRecords:@"www.qiniu.com"]; 49 | ``` 50 | * url 请求,返回一个IP 替换URL 里的domain 51 | ``` 52 | NSMutableArray *array = [[NSMutableArray alloc] init]; 53 | [array addObject:[QNResolver systemResolver]]; 54 | [array addObject:[[QNResolver alloc] initWithAddress:@"119.29.29.29"]]; 55 | QNDnsManager *dns = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]]; 56 | NSURL *u = [[NSURL alloc] initWithString:@"rtmp://www.qiniu.com/abc?q=1"]; 57 | NSURL *u2 = [dns queryAndReplaceWithIP:u]; 58 | ``` 59 | * 兼容 getaddrinfo, 方便底层 C 代码接入 60 | ``` 61 | static QNDnsManager *dns = nil; 62 | dns = [[QNDnsManager alloc] init:@[ [QNResolver systemResolver] ] networkInfo:nil]; 63 | [QNDnsManager setGetAddrInfoBlock:^NSArray *(NSString *host) { 64 | return [dns query:host]; 65 | }]; 66 | struct addrinfo hints = {0}; 67 | struct addrinfo *ai = NULL; 68 | int x = qn_getaddrinfo(host, "http", &hints, &ai); 69 | qn_freeaddrinfo(ai); // 也可以用系统的freeaddrinfo, 代码一致,不过最好用这个 70 | ``` 71 | ### 运行测试 72 | 73 | ``` bash 74 | $ xctool -workspace HappyDNS.xcworkspace -scheme "HappyDNS_Mac" -sdk macosx -configuration Release test -test-sdk macosx 75 | ``` 76 | 77 | ### 指定测试 78 | 79 | 可以在单元测试上修改,熟悉使用 80 | 81 | ``` bash 82 | ``` 83 | 84 | ## 常见问题 85 | 86 | - 如果碰到其他编译错误,请参考 CocoaPods 的 [troubleshooting](http://guides.cocoapods.org/using/troubleshooting.html) 87 | - httpdns 在**ios8** 时不支持 nat64 模式下 IP 直接访问url,原因是 NSUrlConnection 不支持。无论是用http://119.29.29.29/d 还是http://[64:ff9b::771d:1d1d]/d 都不行,此时可以使用localdns方式。 88 | - 如果软件有国外的使用情况时,建议初始化程序采取这样的方式 89 | ```Objective-C 90 | QNDnsManager *dns; 91 | if([QNDnsManager needHttpDns]){ 92 | NSMutableArray *array = [[NSMutableArray alloc] init]; 93 | [array addObject:[[QNResolver alloc] initWithAddress:@"119.29.29.29"]]; 94 | [array addObject:[QNResolver systemResolver]]; 95 | dns = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]]; 96 | }else{ 97 | NSMutableArray *array = [[NSMutableArray alloc] init]; 98 | [array addObject:[QNResolver systemResolver]]; 99 | [array addObject:[[QNResolver alloc] initWithAddress:@"114.114.114.114"]]; 100 | dns = [[QNDnsManager alloc] init:array networkInfo:[QNNetworkInfo normal]]; 101 | } 102 | ``` 103 | 104 | ## 代码贡献 105 | 106 | 详情参考[代码提交指南](https://github.com/qiniu/happy-dns-objc/blob/master/CONTRIBUTING.md)。 107 | 108 | ## 贡献记录 109 | 110 | - [所有贡献者](https://github.com/qiniu/happy-dns-objc/contributors) 111 | 112 | ## 联系我们 113 | 114 | - 如果有什么问题,可以到问答社区提问,[问答社区](http://qiniu.segmentfault.com/) 115 | - 如果发现了bug, 欢迎提交 [issue](https://github.com/qiniu/happy-dns-objc/issues) 116 | - 如果有功能需求,欢迎提交 [issue](https://github.com/qiniu/happy-dns-objc/issues) 117 | - 如果要提交代码,欢迎提交 pull request 118 | - 欢迎关注我们的[微信](http://www.qiniu.com/#weixin) [微博](http://weibo.com/qiniutek),及时获取动态信息。 119 | 120 | ## 代码许可 121 | 122 | The MIT License (MIT).详情见 [License文件](https://github.com/qiniu/happy-dns-objc/blob/master/LICENSE). 123 | -------------------------------------------------------------------------------- /clang-format: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiniu/happy-dns-objc/b1d31d568eb26ce6e7d88f1e492a1da4b2eddb79/clang-format -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | ci: 3 | - prow.qiniu.io # prow 里面运行需添加,其他 CI 不要 4 | require_ci_to_pass: no # 改为 no,否则 codecov 会等待其他 GitHub 上所有 CI 通过才会留言。 5 | 6 | github_checks: #关闭github checks 7 | annotations: false 8 | 9 | comment: 10 | layout: "reach, diff, flags, files" 11 | behavior: new # 默认是更新旧留言,改为 new,删除旧的,增加新的。 12 | require_changes: false # if true: only post the comment if coverage changes 13 | require_base: no # [yes :: must have a base report to post] 14 | require_head: yes # [yes :: must have a head report to post] 15 | branches: # branch names that can post comment 16 | - "master" 17 | 18 | coverage: 19 | status: # 评判 pr 通过的标准 20 | patch: off 21 | project: # project 统计所有代码x 22 | default: 23 | # basic 24 | target: 54% # 总体通过标准 25 | threshold: 3% # 允许单次下降的幅度 26 | base: auto 27 | if_not_found: success 28 | if_ci_failed: error -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Change this if your clang-format executable is somewhere else 3 | #CLANG_FORMAT="$HOME/Library/Application Support/Alcatraz/Plug-ins/ClangFormat/bin/clang-format" 4 | CLANG_FORMAT=./clang-format 5 | find . \( -name '*.h' -or -name '*.m' -or -name '*.mm' \) -print0 | xargs -0 "$CLANG_FORMAT" -i 6 | --------------------------------------------------------------------------------