├── .gitattributes ├── .github ├── Dockerfile └── workflows │ └── dotnet-core.yml ├── .gitignore ├── LICENSE ├── ReadMe.md ├── aliyun-ddns.sln └── aliyun-ddns ├── .dockerignore ├── Common ├── InstanceCreator.cs ├── Log.cs └── TaskExtension.cs ├── Dockerfile ├── DomainUpdater.cs ├── IPGetter ├── BaseIPGetter.cs ├── BaseLocalIPGetter.cs ├── IIPGetter.cs ├── IPv4Getter │ ├── BaseIPv4Getter.cs │ ├── CommonIPv4Getter.cs │ ├── IIPv4Getter.cs │ ├── IPv4GetterCreator.cs │ └── LocalIPv4Getter.cs ├── IPv6Getter │ ├── BaseIPv6Getter.cs │ ├── CommonIPv6Getter.cs │ ├── IIPv6Getter.cs │ ├── IPv6GetterCreator.cs │ └── LocalIPv6Getter.cs └── Ignore.cs ├── Options.cs ├── Program.cs ├── PublicIpGetter.cs ├── WebHook ├── WebHookAction.cs └── WebHookItem.cs └── aliyun-ddns.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS final 3 | LABEL maintainer="sanjusss@qq.com" 4 | ENV AKID="access key id" 5 | ENV AKSCT="access key secret" 6 | ENV DOMAIN="my.domain.com" 7 | ENV REDO=300 8 | ENV TTL=600 9 | ENV TIMEZONE=8 10 | ENV TYPE=A,AAAA 11 | ENV CNIPV4=false 12 | ENV WEBHOOK= 13 | ENV CHECKLOCAL=false 14 | ENV IPV4NETS= 15 | ENV IPV6NETS= 16 | WORKDIR /app 17 | COPY ./out . 18 | ENTRYPOINT ["dotnet", "aliyun-ddns.dll"] 19 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ master ] 7 | tags: '*' 8 | paths: 9 | - "**" 10 | - "!**.MD" 11 | - "!**.md" 12 | - "!LICENSE" 13 | - "!.gitignore" 14 | - "!.github/workflows/**" 15 | pull_request: 16 | branches: [ master ] 17 | paths: 18 | - "**" 19 | - "!**.MD" 20 | - "!**.md" 21 | - "!LICENSE" 22 | - "!.gitignore" 23 | 24 | jobs: 25 | build: 26 | 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - uses: actions/checkout@v2 31 | - name: Setup .NET Core 32 | uses: actions/setup-dotnet@v1 33 | with: 34 | dotnet-version: '6.0.x' 35 | - name: Install dependencies 36 | run: dotnet restore 37 | - name: Build 38 | run: dotnet build --configuration Release --no-restore 39 | - name: Publish 40 | run: dotnet publish --configuration Release --no-build --output ./.github/out 41 | - name: Delete extra files 42 | run: rm -f ./.github/out/aliyun-ddns ./.github/out/aliyun-ddns.pdb 43 | - name: Upload a Build Artifact 44 | uses: actions/upload-artifact@v2 45 | with: 46 | path: ./.github/out 47 | 48 | - name: Set up QEMU 49 | uses: docker/setup-qemu-action@v1 50 | - name: Set up Docker Buildx 51 | id: buildx 52 | uses: docker/setup-buildx-action@v1 53 | - name: Available platforms 54 | run: echo ${{ steps.buildx.outputs.platforms }} 55 | 56 | - name: Prepare 57 | id: prepare 58 | run: | 59 | echo ::set-output name=docker_platforms::linux/amd64,linux/arm/v7,linux/arm64 60 | echo ::set-output name=docker_image::${{ secrets.DOCKER_HUB_USER }}/aliyun-ddns 61 | 62 | - name: Docker Buildx (no push) 63 | run: | 64 | docker buildx build --platform linux/amd64,linux/arm/v7,linux/arm64 \ 65 | --output "type=image,push=false" \ 66 | --file ./.github/Dockerfile ./.github 67 | 68 | - name: Docker Hub Login 69 | if: success() && !startsWith(github.event_name, 'pull_request') 70 | run: | 71 | docker login --username "${{ secrets.DOCKER_HUB_USER }}" -p ${{ secrets.DOCKER_HUB_PASS }} 72 | 73 | - name: Docker Buildx (push) 74 | if: success() && !startsWith(github.event_name, 'pull_request') 75 | run: | 76 | docker buildx build --platform ${{ steps.prepare.outputs.docker_platforms }} \ 77 | --push \ 78 | --tag "${{ steps.prepare.outputs.docker_image }}:latest" \ 79 | --file ./.github/Dockerfile ./.github 80 | docker buildx build --platform linux/amd64 \ 81 | --push \ 82 | --tag "${{ steps.prepare.outputs.docker_image }}:linux-amd64" \ 83 | --file ./.github/Dockerfile ./.github 84 | docker buildx build --platform linux/arm64 \ 85 | --push \ 86 | --tag "${{ steps.prepare.outputs.docker_image }}:linux-arm64v8" \ 87 | --file ./.github/Dockerfile ./.github 88 | docker buildx build --platform linux/arm/v7 \ 89 | --push \ 90 | --tag "${{ steps.prepare.outputs.docker_image }}:linux-arm32v7" \ 91 | --file ./.github/Dockerfile ./.github 92 | 93 | - name: Docker Buildx (push on tags) 94 | if: success() && startsWith(github.ref, 'refs/tags') 95 | run: | 96 | docker buildx build --platform ${{ steps.prepare.outputs.docker_platforms }} \ 97 | --push \ 98 | --tag "${{ steps.prepare.outputs.docker_image }}:${GITHUB_REF#refs/tags/}" \ 99 | --file ./.github/Dockerfile ./.github 100 | docker buildx build --platform linux/amd64 \ 101 | --push \ 102 | --tag "${{ steps.prepare.outputs.docker_image }}:${GITHUB_REF#refs/tags/}-linux-amd64" \ 103 | --file ./.github/Dockerfile ./.github 104 | docker buildx build --platform linux/arm64 \ 105 | --push \ 106 | --tag "${{ steps.prepare.outputs.docker_image }}:${GITHUB_REF#refs/tags/}-linux-arm64v8" \ 107 | --file ./.github/Dockerfile ./.github 108 | docker buildx build --platform linux/arm/v7 \ 109 | --push \ 110 | --tag "${{ steps.prepare.outputs.docker_image }}:${GITHUB_REF#refs/tags/}-linux-arm32v7" \ 111 | --file ./.github/Dockerfile ./.github 112 | 113 | - name: Docker Check Manifest 114 | if: success() && !startsWith(github.event_name, 'pull_request') 115 | run: | 116 | docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:latest 117 | 118 | - name: Clear 119 | if: always() && !startsWith(github.event_name, 'pull_request') 120 | run: | 121 | rm -f ${HOME}/.docker/config.json 122 | 123 | - name: Prepare 124 | if: success() && startsWith(github.ref, 'refs/tags') 125 | id: zipname 126 | run: | 127 | echo ::set-output name=zip_name::aliyun-ddns-${GITHUB_REF#refs/tags/}.zip 128 | echo ::set-output name=version::${GITHUB_REF#refs/tags/} 129 | - name: Create zip 130 | if: success() && startsWith(github.ref, 'refs/tags') 131 | run: | 132 | zip --junk-paths -q -r ${{ steps.zipname.outputs.zip_name }} ./.github/out/* 133 | 134 | - name: Create Release 135 | if: success() && startsWith(github.ref, 'refs/tags') 136 | id: create_release 137 | uses: actions/create-release@v1 138 | env: 139 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 140 | with: 141 | tag_name: ${{ steps.zipname.outputs.version }} 142 | release_name: ${{ steps.zipname.outputs.version }} 143 | draft: false 144 | prerelease: false 145 | - name: Upload Release Asset 146 | if: success() && startsWith(github.ref, 'refs/tags') 147 | id: upload-release-asset 148 | uses: actions/upload-release-asset@v1 149 | env: 150 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 151 | with: 152 | upload_url: ${{ steps.create_release.outputs.upload_url }} 153 | asset_path: ./${{ steps.zipname.outputs.zip_name }} 154 | asset_name: ${{ steps.zipname.outputs.zip_name }} 155 | asset_content_type: application/zip 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | /aliyun-ddns/Properties/launchSettings.json 263 | /docker-compose.override.yml 264 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, sanjusss 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 |  2 | [![.NET Core](https://github.com/sanjusss/aliyun-ddns/actions/workflows/dotnet-core.yml/badge.svg?branch=master)](https://github.com/sanjusss/aliyun-ddns/actions/workflows/dotnet-core.yml) 3 | [![](https://img.shields.io/docker/stars/sanjusss/aliyun-ddns.svg?logo=docker)](https://hub.docker.com/r/sanjusss/aliyun-ddns) 4 | [![GitHub license](https://img.shields.io/github/license/sanjusss/aliyun-ddns.svg)](https://github.com/sanjusss/aliyun-ddns/blob/master/LICENSE) 5 | [![GitHub tag](https://img.shields.io/github/tag/sanjusss/aliyun-ddns.svg)](https://github.com/sanjusss/aliyun-ddns/tags) 6 | [![GitHub issues](https://img.shields.io/github/issues/sanjusss/aliyun-ddns.svg)](https://github.com/sanjusss/aliyun-ddns/issues) 7 | [![GitHub forks](https://img.shields.io/github/forks/sanjusss/aliyun-ddns.svg)](https://github.com/sanjusss/aliyun-ddns/network) 8 | [![GitHub stars](https://img.shields.io/github/stars/sanjusss/aliyun-ddns.svg)](https://github.com/sanjusss/aliyun-ddns/stargazers) 9 | 10 | # 功能 11 | - 通过在线API获取公网IPv4/v6地址,更新到域名A/AAAA记录。 12 | - 通过本地网卡获取公网或内网IPv4/v6地址,更新到域名A/AAAA记录。 13 | - 支持更新多个域名的记录。 14 | - 支持更新指定线路的记录。 15 | - 支持Docker容器,支持x64、ARMv7和ARMv8。 16 | - IP发生变化时,使用WebHook通知。 17 | 18 | # 使用方法 19 | 20 | ### Docker 21 | ``` 22 | docker run -d --restart=always --net=host \ 23 | -e "AKID=[ALIYUN's AccessKey-ID]" \ 24 | -e "AKSCT=[ALIYUN's AccessKey-Secret]" \ 25 | -e "DOMAIN=ddns.aliyun.win" \ 26 | -e "REDO=30" \ 27 | -e "TTL=60" \ 28 | -e "TIMEZONE=8.0" \ 29 | -e "TYPE=A,AAAA" \ 30 | sanjusss/aliyun-ddns 31 | ``` 32 | | 环境变量名称 | 注释 | 默认值 | 33 | | :---- | :----- | :--- | 34 | |AKID|阿里云的Access Key ID。[获取阿里云AccessToken](https://usercenter.console.aliyun.com/)|access key id| 35 | |AKSCT|阿里云的Access Key Secret。|access key secret| 36 | |DOMAIN|需要更新的域名,可以用“,”隔开。
可以指定线路,用“:”分隔线路和域名([线路名说明](https://help.aliyun.com/document_detail/29807.html?spm=a2c4g.11186623.2.14.42405eb4boCsnd))。
例如:“baidu.com,telecom:dianxin.baidu.com”。|my.domain.com| 37 | |ROOT_DOMAIN|以参数DOMAIN为 a.www.example.com 为示例:
1.如果参数ROOT_DOMAIN为空,则查询域名为example.com、主机名为”a.www“的解析记录;
2.如果参数ROOT_DOMAIN为 www.example.com,则查询域名为www.example.com、主机名为 "a"的解析记录;
3.如果参数ROOT_DOMAIN为 a.www.example.com,则查询域名为a.www.example.com、主机名为 "@"的解析记录。|无| 38 | |REDO|更新间隔,单位秒。建议大于等于TTL/2。|300| 39 | |TTL|服务器缓存解析记录的时长,单位秒,普通用户最小为600。|600| 40 | |TIMEZONE|输出日志时的时区,单位小时。|8| 41 | |TYPE|需要更改的记录类型,可以用“,”隔开,只能是“A”、“AAAA”或“A,AAAA”。|A,AAAA| 42 | |CNIPV4|检查IPv4地址时,仅使用中国服务器。|false| 43 | |WEBHOOK|WEBHOOK推送地址。|无| 44 | |CHECKLOCAL|是否检查本地网卡IP。此选项将禁用在线API的IP检查。
网络模式必须设置为host。
(Windows版docker无法读取本机IP)|false| 45 | |IPV4NETS|本地网卡的IPv4网段。格式示例:“192.168.1.0/24”。多个网段用“,”隔开。|无| 46 | |IPV6NETS|本地网卡的IPv6网段。格式示例:“240e::/16”。多个网段用“,”隔开。|无| 47 | 48 | 以上环境变量均存在默认值,添加需要修改的环境变量即可。 49 | 50 | ### 命令行 51 | ##### 查看帮助信息 52 | ``` 53 | dotnet aliyun-ddns.dll --help 54 | ``` 55 | ##### 查看版本信息 56 | ``` 57 | dotnet aliyun-ddns.dll --version 58 | ``` 59 | ##### 运行 60 | ``` 61 | dotnet aliyun-ddns.dll \ 62 | -u "ALIYUN's AccessKey-ID" \ 63 | -p "ALIYUN's AccessKey-Secret" \ 64 | -d "ddns.aliyun.win,ddns2.aliyun2.win" \ 65 | -i 300 \ 66 | -t 600 \ 67 | --timezone 8.0 \ 68 | --type A \ 69 | --cnipv4 70 | ``` 71 | 72 | | 参数名称 | 注释 | 默认值 | 73 | | :---- | :----- | :--- | 74 | |u|阿里云的Access Key ID。[获取阿里云AccessToken](https://usercenter.console.aliyun.com/)|access key id| 75 | |p|阿里云的Access Key Secret。|access key secret| 76 | |d|需要更新的域名,可以用“,”隔开。
可以指定线路,用“:”分隔线路和域名([线路名说明](https://help.aliyun.com/document_detail/29807.html?spm=a2c4g.11186623.2.14.42405eb4boCsnd))。
例如:“baidu.com,telecom:dianxin.baidu.com”。|my.domain.com| 77 | |root-domain|以参数DOMAIN为 a.www.example.com 为示例:
1.如果参数ROOT_DOMAIN为空,则查询域名为example.com、主机名为”a.www“的解析记录;
2.如果参数ROOT_DOMAIN为 www.example.com,则查询域名为www.example.com、主机名为 "a"的解析记录;
3.如果参数ROOT_DOMAIN为 a.www.example.com,则查询域名为a.www.example.com、主机名为 "@"的解析记录。|无| 78 | |i|更新间隔,单位秒。建议大于等于TTL/2。|300| 79 | |t|服务器缓存解析记录的时长,单位秒,普通用户最小为600。|600| 80 | |timezone|输出日志时的时区,单位小时。|8| 81 | |type|需要更改的记录类型,可以用“,”隔开,只能是“A”、“AAAA”或“A,AAAA”。|A,AAAA| 82 | |cnipv4|检查IPv4地址时,仅使用中国服务器。|false| 83 | |webhook|WEBHOOK推送地址。|无| 84 | |checklocal|是否检查本地网卡IP。此选项将禁用在线API的IP检查。|false| 85 | |ipv4nets|本地网卡的IPv4网段。格式示例:“192.168.1.0/24”。多个网段用“,”隔开。|无| 86 | |ipv6nets|本地网卡的IPv6网段。格式示例:“240e::/16”。多个网段用“,”隔开。|无| 87 | 88 | 以上参数均存在默认值,添加需要修改的参数即可。 89 | 90 | # 常见问题 91 | 92 | ### 无法获取DNS记录 93 | #### 日志提示 94 | 获取xxx.yyy.zzz的所有记录时出现异常:Aliyun.Acs.Core.Exceptions.ClientException: SDK.WebException : HttpWebRequest WebException occured, the request url is alidns.aliyuncs.com System.Net.WebException: Resource temporarily unavailable Resource temporarily unavailable 95 | #### 可能的原因 96 | - alidns.aliyuncs.com服务器宕机 97 | - 当地电信运营商网络故障 98 | - docker容器无法访问网络 99 | #### 可能的解决方法 100 | 我们自己可以解决的只有“docker容器无法访问网络”这个问题。 101 | 执行`curl https://alidns.aliyuncs.com`有返回内容(403之类的),说明是docker容器无法访问网络。 102 | 如果之前手动修改过防火墙设置和docker网桥,请先修改回去。 103 | 可以通过重启网络解决一部分问题。 104 | 以CentOS7为例: 105 | ```shell 106 | systemctl restart network 107 | systemctl restart docker 108 | ``` 109 | -------------------------------------------------------------------------------- /aliyun-ddns.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30204.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "aliyun-ddns", "aliyun-ddns\aliyun-ddns.csproj", "{E25AF90B-C12B-4A54-94FB-329F486FBD65}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4B571F9E-C2B5-4DC1-8580-AC782BEFAC43}" 9 | ProjectSection(SolutionItems) = preProject 10 | LICENSE = LICENSE 11 | ReadMe.md = ReadMe.md 12 | EndProjectSection 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {E25AF90B-C12B-4A54-94FB-329F486FBD65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {E25AF90B-C12B-4A54-94FB-329F486FBD65}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {E25AF90B-C12B-4A54-94FB-329F486FBD65}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {E25AF90B-C12B-4A54-94FB-329F486FBD65}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {785D6A71-DD0E-432A-A986-2C4C391DCD0D} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /aliyun-ddns/.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .env 3 | .git 4 | .gitignore 5 | .vs 6 | .vscode 7 | docker-compose.yml 8 | docker-compose.*.yml 9 | */bin 10 | */obj 11 | -------------------------------------------------------------------------------- /aliyun-ddns/Common/InstanceCreator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace aliyun_ddns.Common 8 | { 9 | /// 10 | /// 实例反射创建类。 11 | /// 12 | public static class InstanceCreator 13 | { 14 | public static IEnumerable Create(Func check = null) 15 | { 16 | var target = typeof(T); 17 | try 18 | { 19 | List instances = new List(); 20 | foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) 21 | { 22 | foreach (var t in a.GetTypes()) 23 | { 24 | try 25 | { 26 | if (target.IsAssignableFrom(t) && 27 | t.IsAbstract == false && 28 | t.IsInterface == false && 29 | (check == null || check(t))) 30 | { 31 | try 32 | { 33 | T i = (T)Activator.CreateInstance(t); 34 | instances.Add(i); 35 | } 36 | catch (Exception e) 37 | { 38 | Log.Print($"创建接口{ typeof(T) }的实例时,无法实例化{ t }:\n{ e }"); 39 | } 40 | } 41 | } 42 | catch 43 | { 44 | //do noting; 45 | } 46 | } 47 | } 48 | 49 | return instances; 50 | } 51 | catch (Exception e) 52 | { 53 | Log.Print($"创建接口{ typeof(T) }的实例时出现异常:\n{ e }"); 54 | return new List(); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /aliyun-ddns/Common/Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.Common 6 | { 7 | /// 8 | /// 日志输出类。 9 | /// 10 | public static class Log 11 | { 12 | /// 13 | /// 打印日志到屏幕。 14 | /// 15 | /// 日志信息 16 | public static void Print(string msg) 17 | { 18 | Console.WriteLine($"[{ DateTime.UtcNow.AddHours(Options.Instance.Timezone) }]{ msg }"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /aliyun-ddns/Common/TaskExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace aliyun_ddns.Common 8 | { 9 | public static class TaskExtension 10 | { 11 | public static async Task WhenAny(this IEnumerable> tasks, Func, bool> check) 12 | { 13 | var runningTasks = new HashSet>(tasks); 14 | while (runningTasks.Count > 0) 15 | { 16 | var current = await Task.WhenAny(runningTasks); 17 | if (current.IsCompletedSuccessfully && 18 | check(current)) 19 | { 20 | return current.Result; 21 | } 22 | else 23 | { 24 | runningTasks.Remove(current); 25 | } 26 | } 27 | 28 | return default; 29 | } 30 | 31 | public static async Task WhenAny(this IEnumerable> tasks, Func, bool> check, TimeSpan timeout) 32 | { 33 | var task = WhenAny(tasks, check); 34 | if (timeout == Timeout.InfiniteTimeSpan || 35 | await Task.WhenAny(task, Task.Delay(timeout)) == task) 36 | { 37 | return await task; 38 | } 39 | else 40 | { 41 | return default; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /aliyun-ddns/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build 2 | MAINTAINER sanjusss 3 | WORKDIR /src 4 | COPY . /src 5 | RUN dotnet publish -c Release -o /app /src/aliyun-ddns/ 6 | 7 | FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS final 8 | MAINTAINER sanjusss 9 | WORKDIR /app 10 | COPY --from=build /app . 11 | ENTRYPOINT ["dotnet", "aliyun-ddns.dll"] 12 | -------------------------------------------------------------------------------- /aliyun-ddns/DomainUpdater.cs: -------------------------------------------------------------------------------- 1 | using Aliyun.Acs.Alidns.Model.V20150109; 2 | using Aliyun.Acs.Core; 3 | using Aliyun.Acs.Core.Profile; 4 | using aliyun_ddns.Common; 5 | using aliyun_ddns.WebHook; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Net.NetworkInformation; 10 | using System.Net.Sockets; 11 | using System.Text.RegularExpressions; 12 | using System.Threading; 13 | using System.Threading.Tasks; 14 | using static Aliyun.Acs.Alidns.Model.V20150109.DescribeSubDomainRecordsResponse; 15 | 16 | namespace aliyun_ddns 17 | { 18 | using Record = DescribeSubDomainRecords_Record; 19 | /// 20 | /// 域名更新类。 21 | /// 22 | public class DomainUpdater 23 | { 24 | /// 25 | /// 每次请求的记录上限。 26 | /// 27 | private const long _pageSize = 100; 28 | 29 | /// 30 | /// IP和记录类型。 31 | /// 32 | private enum IpType 33 | { 34 | /// 35 | /// A记录,IPv4。 36 | /// 37 | A, 38 | /// 39 | /// AAAA记录,IPv6。 40 | /// 41 | AAAA, 42 | /// 43 | /// 任意记录类型。 44 | /// 45 | ANY 46 | } 47 | 48 | /// 49 | /// 运行自动更新。 50 | /// 51 | public void Run() 52 | { 53 | TimeSpan maxWait = new TimeSpan(0, 0, Options.Instance.Redo); 54 | string[] domains = Options.Instance.Domain.Split(',', StringSplitOptions.RemoveEmptyEntries); 55 | HashSet targetTypes = GetTargetTypes(); 56 | if (targetTypes.Count == 0) 57 | { 58 | Log.Print("没有设置需要修改的记录类型。"); 59 | return; 60 | } 61 | 62 | while (true) 63 | { 64 | DateTime start = DateTime.Now; 65 | 66 | try 67 | { 68 | Update(domains, targetTypes); 69 | } 70 | catch (Exception e) 71 | { 72 | Log.Print($"更新时出现未预料的异常:{ e }"); 73 | } 74 | 75 | var used = DateTime.Now - start; 76 | if (used < maxWait) 77 | { 78 | Thread.Sleep(maxWait - used); 79 | } 80 | } 81 | } 82 | 83 | private void Update(string[] domains, HashSet targetTypes) 84 | { 85 | HashSet types = new HashSet(GetSupportIpTypes()); 86 | types.IntersectWith(targetTypes); 87 | var tasks = types.ToDictionary(t => t, t => t == IpType.A ? PublicIPGetter.GetIpv4() : PublicIPGetter.GetIpv6()); 88 | Task.WaitAll(tasks.Values.ToArray()); 89 | var ips = tasks.ToDictionary(p => p.Key, p => p.Value.Result); 90 | 91 | List items = new List(); 92 | foreach (var i in domains) 93 | { 94 | var oldRecords = GetRecords(i);//获取域名的所有记录 95 | if (oldRecords == null) 96 | { 97 | Log.Print($"跳过设置域名 { i }"); 98 | continue; 99 | } 100 | 101 | ClearCName(ref oldRecords);//清理必然重复的CNAME记录。 102 | 103 | foreach (var j in ips) 104 | { 105 | string type = j.Key.ToString(); 106 | //获取当前类型的所有记录。 107 | List typedRecords = new List(); 108 | for (int k = oldRecords.Count - 1; k >= 0; --k) 109 | { 110 | if (oldRecords[k].Type == type) 111 | { 112 | typedRecords.Add(oldRecords[k]); 113 | oldRecords.RemoveAt(k); 114 | } 115 | } 116 | 117 | //根据已有记录数量决定操作。 118 | bool success = false; 119 | if (typedRecords.Count == 1) 120 | { 121 | success = UpdateRecord(typedRecords[0], j.Value); 122 | } 123 | else 124 | { 125 | if (typedRecords.Count > 1) 126 | { 127 | DeleteRecords(typedRecords); 128 | } 129 | 130 | success = AddRecord(j.Key, i, j.Value); 131 | } 132 | 133 | if (success) 134 | { 135 | items.Add(new WebHookItem 136 | { 137 | recordType = j.Key.ToString(), 138 | domain = i, 139 | ip = j.Value 140 | }); 141 | } 142 | } 143 | } 144 | 145 | if (items.Count > 0) 146 | { 147 | WebHookAction.Push(items); 148 | } 149 | } 150 | 151 | /// 152 | /// 获取目标记录类型。 153 | /// 154 | /// 目标记录类型的集合 155 | private HashSet GetTargetTypes() 156 | { 157 | HashSet inputTypes = new HashSet(Options.Instance.Type.Split(',', StringSplitOptions.RemoveEmptyEntries)); 158 | HashSet targetTypes = new HashSet(); 159 | if (inputTypes.Contains("A")) 160 | { 161 | targetTypes.Add(IpType.A); 162 | } 163 | 164 | if (inputTypes.Contains("AAAA")) 165 | { 166 | targetTypes.Add(IpType.AAAA); 167 | } 168 | 169 | return targetTypes; 170 | } 171 | 172 | /// 173 | /// 获取本机支持的记录类型。 174 | /// 175 | /// 记录类型的集合 176 | private IEnumerable GetSupportIpTypes() 177 | { 178 | try 179 | { 180 | HashSet res = new HashSet(); 181 | //获取说有网卡信息 182 | NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); 183 | foreach (NetworkInterface adapter in nics) 184 | { 185 | try 186 | { 187 | if (adapter.NetworkInterfaceType != NetworkInterfaceType.Ethernet && 188 | adapter.NetworkInterfaceType != NetworkInterfaceType.Wireless80211 && 189 | adapter.NetworkInterfaceType != NetworkInterfaceType.Ppp) 190 | { 191 | continue; 192 | } 193 | 194 | //获取网络接口信息 195 | IPInterfaceProperties properties = adapter.GetIPProperties(); 196 | //获取单播地址集 197 | UnicastIPAddressInformationCollection ips = properties.UnicastAddresses; 198 | foreach (UnicastIPAddressInformation i in ips) 199 | { 200 | switch (i.Address.AddressFamily) 201 | { 202 | case AddressFamily.InterNetwork: 203 | res.Add(IpType.A); 204 | break; 205 | 206 | case AddressFamily.InterNetworkV6: 207 | if (i.Address.IsIPv6LinkLocal == false && 208 | i.Address.IsIPv6Multicast == false && 209 | i.Address.IsIPv6SiteLocal == false) 210 | { 211 | res.Add(IpType.AAAA); 212 | } 213 | 214 | break; 215 | 216 | default: 217 | break; 218 | } 219 | } 220 | } 221 | catch 222 | { 223 | continue; 224 | } 225 | } 226 | 227 | return res; 228 | } 229 | catch (Exception e) 230 | { 231 | Log.Print($"获取网卡信息时出现异常:{ e }"); 232 | return new IpType[] { IpType.A, IpType.AAAA }; 233 | } 234 | } 235 | 236 | /// 237 | /// 获取新的阿里云客户端。 238 | /// 239 | /// 新的阿里云客户端 240 | private DefaultAcsClient GetNewClient() 241 | { 242 | //var clientProfile = DefaultProfile.GetProfile(Options.Instance.ENDPOINT, Options.Instance.AKID, Options.Instance.AKSCT); 243 | //return new DefaultAcsClient(clientProfile); 244 | return new DefaultAcsClient(DefaultProfile.GetProfile(), 245 | new Aliyun.Acs.Core.Auth.BasicCredentials(Options.Instance.Akid, Options.Instance.Aksct)); 246 | } 247 | 248 | /// 249 | /// 获取已经存在的记录。 250 | /// 251 | /// 域名或子域名 252 | /// 记录的集合,获取失败时返回null。 253 | private IList GetRecords(string domain) 254 | { 255 | List records = new List(); 256 | try 257 | { 258 | var client = GetNewClient(); 259 | long pageNumber = 1; 260 | ParseSubDomainAndLine(domain, out string subDomain, out string line); 261 | do 262 | { 263 | DescribeSubDomainRecordsRequest request = new DescribeSubDomainRecordsRequest 264 | { 265 | DomainName = Options.Instance.RootDomain, 266 | SubDomain = subDomain, 267 | PageSize = _pageSize, 268 | PageNumber = pageNumber, 269 | Line = line 270 | }; 271 | 272 | var response = client.GetAcsResponse(request); 273 | records.AddRange(response.DomainRecords); 274 | if (response.TotalCount <= records.Count) 275 | { 276 | break; 277 | } 278 | else 279 | { 280 | ++pageNumber; 281 | } 282 | } while (true); 283 | Log.Print($"成功获取{ domain }的所有记录,共{ records.Count }条。"); 284 | } 285 | catch (Exception e) 286 | { 287 | Log.Print($"获取{ domain }的所有记录时出现异常:{ e }"); 288 | return null; 289 | } 290 | 291 | return records; 292 | } 293 | 294 | ///// 295 | ///// 删除所有指定类型的记录。 296 | ///// 297 | ///// 域名 298 | ///// 主机记录 299 | ///// 解析记录类型 300 | ///// 是否删除成功。 301 | //private bool DeleteRecordsByType(string domain, string rr, string type) 302 | //{ 303 | // try 304 | // { 305 | // var client = GetNewClient(); 306 | // DeleteSubDomainRecordsRequest request = new DeleteSubDomainRecordsRequest 307 | // { 308 | // DomainName = domain, 309 | // RR = rr, 310 | // Type = type, 311 | // }; 312 | // var response = client.GetAcsResponse(request); 313 | // if (response.HttpResponse.isSuccess()) 314 | // { 315 | // Log.Print($"成功清理{ type }记录{ rr }.{ domain }。"); 316 | // return true; 317 | // } 318 | // else 319 | // { 320 | // Log.Print($"清理{ type }记录{ rr }.{ domain }失败。"); 321 | // return false; 322 | // } 323 | // } 324 | // catch (Exception e) 325 | // { 326 | // Log.Print($"删除{ type }记录{ rr }.{ domain }记录时出现异常:{ e }"); 327 | // return false; 328 | // } 329 | //} 330 | 331 | /// 332 | /// 删除所有记录。 333 | /// 334 | /// 记录 335 | /// 是否成功删除 336 | private bool DeleteRecords(IEnumerable records) 337 | { 338 | foreach (var rd in records) 339 | { 340 | try 341 | { 342 | var client = GetNewClient(); 343 | DeleteDomainRecordRequest request = new DeleteDomainRecordRequest 344 | { 345 | RecordId = rd.RecordId 346 | }; 347 | var response = client.GetAcsResponse(request); 348 | if (response.HttpResponse.isSuccess()) 349 | { 350 | Log.Print($"成功清理{ rd.Type }记录{ rd.RR }.{ rd.DomainName }({ rd.Line })记录{ rd.RecordId }。"); 351 | } 352 | else 353 | { 354 | Log.Print($"清理{ rd.Type }记录{ rd.RR }.{ rd.DomainName }({ rd.Line })记录{ rd.RecordId }失败。"); 355 | return false; 356 | } 357 | } 358 | catch (Exception e) 359 | { 360 | Log.Print($"删除{ rd.Type }记录{ rd.RR }.{ rd.DomainName }({ rd.Line })记录{ rd.RecordId }时出现异常:{ e }"); 361 | return false; 362 | } 363 | } 364 | 365 | return true; 366 | } 367 | 368 | /// 369 | /// 删除多余的CNAME记录。 370 | /// 371 | /// 记录集合 372 | /// 是否完全删除成功。 373 | private bool ClearCName(ref IList rds) 374 | { 375 | List cnameRecords = new List(); 376 | for (int i = rds.Count - 1; i >= 0; --i) 377 | { 378 | if (rds[i].Type == "CNAME") 379 | { 380 | cnameRecords.Add(rds[i]); 381 | rds.RemoveAt(i); 382 | } 383 | } 384 | 385 | return cnameRecords.Count == 0 || DeleteRecords(cnameRecords); 386 | } 387 | 388 | /// 389 | /// 添加解析记录。 390 | /// 391 | /// 记录类型 392 | /// 域名或子域名 393 | /// 公网ip 394 | /// 添加成功返回true,否则返回false。 395 | private bool AddRecord(IpType type, string domain, string ip) 396 | { 397 | try 398 | { 399 | string domainName; 400 | string rr; 401 | 402 | ParseSubDomainAndLine(domain, out string subDomain, out string line); 403 | if (Options.Instance.RootDomain == null) 404 | { 405 | string pattern = @"^(\S*)\.(\S+)\.(\S+)$"; 406 | Regex regex = new Regex(pattern); 407 | var match = regex.Match(subDomain); 408 | if (match.Success) 409 | { 410 | rr = match.Groups[1].Value; 411 | domainName = match.Groups[2].Value + "." + match.Groups[3].Value; 412 | } 413 | else 414 | { 415 | rr = "@"; 416 | domainName = subDomain; 417 | } 418 | } 419 | else 420 | { 421 | if (subDomain == Options.Instance.RootDomain) 422 | { 423 | rr = "@"; 424 | domainName = subDomain; 425 | } 426 | else if (subDomain.EndsWith($".{ Options.Instance.RootDomain }")) 427 | { 428 | rr = subDomain.Substring(0, subDomain.Length - $".{ Options.Instance.RootDomain }".Length); 429 | domainName = Options.Instance.RootDomain; 430 | } 431 | else 432 | { 433 | throw new ArgumentException($"DOMAIN(“{ subDomain }“)必须以ROOTDOMAIN(“.{ Options.Instance.RootDomain }“)结尾"); 434 | } 435 | } 436 | 437 | var client = GetNewClient(); 438 | AddDomainRecordRequest request = new AddDomainRecordRequest 439 | { 440 | DomainName = domainName, 441 | RR = rr, 442 | Type = type.ToString(), 443 | _Value = ip, 444 | TTL = Options.Instance.TTL, 445 | Line = line 446 | }; 447 | var response = client.GetAcsResponse(request); 448 | if (response.HttpResponse.isSuccess()) 449 | { 450 | Log.Print($"成功增加{ type.ToString() }记录{ domain }为{ ip }。"); 451 | return true; 452 | } 453 | else 454 | { 455 | Log.Print($"增加{ type.ToString() }记录{ domain }失败。"); 456 | return false; 457 | } 458 | } 459 | catch (Exception e) 460 | { 461 | Log.Print($"增加{ type.ToString() }记录{ domain }时发生异常: { e }"); 462 | return false; 463 | } 464 | } 465 | 466 | /// 467 | /// 更新解析记录。 468 | /// 469 | /// 原记录。 470 | /// 公网IP。 471 | /// 更新成功返回true,否则返回false。 472 | private bool UpdateRecord(Record rd, string ip) 473 | { 474 | if (string.IsNullOrEmpty(ip)) 475 | { 476 | return false; 477 | } 478 | else if (ip == rd._Value) 479 | { 480 | Log.Print($"{ rd.Type }记录{ rd.RR }.{ rd.DomainName }({ rd.Line })不需要更新。"); 481 | return false; 482 | } 483 | 484 | try 485 | { 486 | var client = GetNewClient(); 487 | UpdateDomainRecordRequest request = new UpdateDomainRecordRequest 488 | { 489 | RecordId = rd.RecordId, 490 | RR = rd.RR, 491 | Type = rd.Type, 492 | _Value = ip, 493 | TTL = Options.Instance.TTL, 494 | Line = rd.Line 495 | }; 496 | var response = client.GetAcsResponse(request); 497 | if (response.HttpResponse.isSuccess()) 498 | { 499 | Log.Print($"成功更新{ rd.Type }记录{ rd.RR }.{ rd.DomainName }({ rd.Line })为{ ip }。"); 500 | return true; 501 | } 502 | else 503 | { 504 | Log.Print($"更新{ rd.Type }记录{ rd.RR }.{ rd.DomainName }({ rd.Line })失败。"); 505 | return false; 506 | } 507 | } 508 | catch (Exception e) 509 | { 510 | Log.Print($"更新{ rd.Type }记录{ rd.RR }.{ rd.DomainName }({ rd.Line })时出现异常:{ e }。"); 511 | return false; 512 | } 513 | } 514 | 515 | private static void ParseSubDomainAndLine(string domain, out string subDomain, out string line) 516 | { 517 | var words = domain.Split(':', StringSplitOptions.RemoveEmptyEntries); 518 | if (words.Length > 2 || words.Length == 0) 519 | { 520 | line = "default"; 521 | subDomain = domain; 522 | return; 523 | } 524 | 525 | if (words.Length == 2) 526 | { 527 | line = words[0]; 528 | subDomain = words[1]; 529 | } 530 | else 531 | { 532 | line = "default"; 533 | subDomain = words[0]; 534 | } 535 | } 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/BaseIPGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | 8 | namespace aliyun_ddns.IPGetter 9 | { 10 | public abstract class BaseIPGetter : IIPGetter 11 | { 12 | /// 13 | /// IP获取器的描述字符串。 14 | /// 15 | public abstract string Description { get; } 16 | 17 | /// 18 | /// 优先级。 19 | /// 仅在顺序模式下有效,越小执行的时间越早。 20 | /// 21 | public virtual int Order => 50; 22 | 23 | /// 24 | /// 获取公网IP的API地址。 25 | /// 26 | protected abstract string Url { get; } 27 | 28 | /// 29 | /// 匹配IP的正则表达式。 30 | /// 31 | protected abstract string IPPattern { get; } 32 | 33 | /// 34 | /// 异步的方式获取IP。 35 | /// 使用Get方法访问Url,将返回值与IPPattern匹配。 36 | /// 37 | /// 返回IP,出错时返回null。 38 | public async Task GetIP() 39 | { 40 | try 41 | { 42 | using HttpClient http = new HttpClient 43 | { 44 | Timeout = TimeSpan.FromSeconds(30) 45 | }; 46 | string content = await http.GetStringAsync(Url); 47 | var match = Regex.Match(content, IPPattern); 48 | if (match.Success) 49 | { 50 | return match.Value; 51 | } 52 | else 53 | { 54 | return null; 55 | } 56 | } 57 | catch 58 | { 59 | return null; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/BaseLocalIPGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.NetworkInformation; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace aliyun_ddns.IPGetter 10 | { 11 | public abstract class BaseLocalIPGetter : IIPGetter 12 | { 13 | public abstract string Description { get; } 14 | public abstract int Order { get; } 15 | 16 | /// 17 | /// IP网段字符串。 18 | /// 19 | protected abstract string IPNetworks { get; } 20 | 21 | /// 22 | /// 检查IP信息是否符合要求。 23 | /// 24 | /// IP信息 25 | /// 是否符合要求 26 | protected abstract bool CheckIPAddressInformation(IPAddressInformation info); 27 | 28 | public async Task GetIP() 29 | { 30 | return await Task.Run(() => 31 | { 32 | try 33 | { 34 | var nets = GetIPNetworks(); 35 | string ip = null; 36 | //获取所有网卡信息,返回最后一个地址。 37 | NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces(); 38 | foreach (NetworkInterface adapter in nics) 39 | { 40 | try 41 | { 42 | //获取网络接口信息 43 | IPInterfaceProperties properties = adapter.GetIPProperties(); 44 | //获取单播地址集 45 | UnicastIPAddressInformationCollection ips = properties.UnicastAddresses; 46 | foreach (UnicastIPAddressInformation i in ips) 47 | { 48 | if (CheckIPAddressInformation(i) == false) 49 | { 50 | continue; 51 | } 52 | 53 | bool success = false; 54 | foreach (var net in nets) 55 | { 56 | if (net.Contains(i.Address)) 57 | { 58 | success = true; 59 | break; 60 | } 61 | } 62 | 63 | if (success) 64 | { 65 | ip = i.Address.ToString(); 66 | } 67 | } 68 | } 69 | catch 70 | { 71 | continue; 72 | } 73 | } 74 | 75 | return ip; 76 | } 77 | catch 78 | { 79 | return null; 80 | } 81 | }); 82 | } 83 | 84 | private IEnumerable GetIPNetworks() 85 | { 86 | List nets = new List(); 87 | string text = IPNetworks; 88 | if (string.IsNullOrEmpty(text) == false) 89 | { 90 | string[] subs = text.Split(',', StringSplitOptions.RemoveEmptyEntries); 91 | foreach (var i in subs) 92 | { 93 | if (IPNetwork.TryParse(i, out IPNetwork net)) 94 | { 95 | nets.Add(net); 96 | } 97 | } 98 | } 99 | 100 | return nets; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IIPGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace aliyun_ddns.IPGetter 7 | { 8 | public interface IIPGetter 9 | { 10 | /// 11 | /// IP获取器的描述字符串。 12 | /// 13 | public string Description { get; } 14 | /// 15 | /// 优先级。 16 | /// 仅在顺序模式下有效,越小执行的时间越早。 17 | /// 18 | public int Order { get; } 19 | /// 20 | /// 获取IP。 21 | /// 22 | /// IP字符串,获取失败时返回null。 23 | public Task GetIP(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv4Getter/BaseIPv4Getter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.IPGetter.IPv4Getter 6 | { 7 | public abstract class BaseIPv4Getter : BaseIPGetter, IIPv4Getter 8 | { 9 | protected override string IPPattern => @"(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv4Getter/CommonIPv4Getter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.IPGetter.IPv4Getter 6 | { 7 | [Ignore] 8 | public sealed class CommonIPv4Getter : BaseIPv4Getter 9 | { 10 | private readonly string _description; 11 | public override string Description => _description; 12 | 13 | private readonly string _url; 14 | protected override string Url => _url; 15 | 16 | private readonly int _order; 17 | public override int Order => _order; 18 | 19 | public CommonIPv4Getter(string description, string url, int order = 50) 20 | { 21 | _description = description; 22 | _url = url; 23 | _order = order; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv4Getter/IIPv4Getter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.IPGetter.IPv4Getter 6 | { 7 | public interface IIPv4Getter : IIPGetter 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv4Getter/IPv4GetterCreator.cs: -------------------------------------------------------------------------------- 1 | using aliyun_ddns.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace aliyun_ddns.IPGetter.IPv4Getter 7 | { 8 | public static class IPv4GetterCreator 9 | { 10 | private static readonly IEnumerable _definedGetters = new CommonIPv4Getter[] 11 | { 12 | new CommonIPv4Getter("test-ipv6.com接口", "https://ipv4.lookup.test-ipv6.com/ip/", 100), 13 | //new CommonIPv4Getter("ipip.net接口", "http://myip.ipip.net/"), 14 | new CommonIPv4Getter("ip-api.com接口", "http://ip-api.com/json/?fields=query", 100), 15 | new CommonIPv4Getter("3322接口", "http://ip.3322.net/"), 16 | }; 17 | 18 | public static IEnumerable Create() 19 | { 20 | List getters = new List(); 21 | if (Options.Instance.CheckLocalNetworkAdaptor) 22 | { 23 | getters.Add(new LocalIPv4Getter()); 24 | } 25 | else 26 | { 27 | getters.AddRange(InstanceCreator.Create(Ignore.CheckType)); 28 | getters.AddRange(_definedGetters); 29 | } 30 | 31 | return getters; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv4Getter/LocalIPv4Getter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.NetworkInformation; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | 7 | namespace aliyun_ddns.IPGetter.IPv4Getter 8 | { 9 | /// 10 | /// 获取所有网卡信息,返回最后一个IPv6地址。 11 | /// 12 | [Ignore] 13 | public sealed class LocalIPv4Getter : BaseLocalIPGetter, IIPv4Getter 14 | { 15 | public override string Description => "读取网卡IPv4设置"; 16 | 17 | public override int Order => 200; 18 | 19 | protected override string IPNetworks => Options.Instance.IPv4Networks; 20 | 21 | protected override bool CheckIPAddressInformation(IPAddressInformation info) 22 | { 23 | return info.Address.AddressFamily == AddressFamily.InterNetwork; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv6Getter/BaseIPv6Getter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.IPGetter.IPv6Getter 6 | { 7 | public abstract class BaseIPv6Getter : BaseIPGetter, IIPv6Getter 8 | { 9 | protected override string IPPattern => @"((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv6Getter/CommonIPv6Getter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.IPGetter.IPv6Getter 6 | { 7 | [Ignore] 8 | public class CommonIPv6Getter : BaseIPv6Getter 9 | { 10 | private readonly string _description; 11 | public override string Description => _description; 12 | 13 | private readonly string _url; 14 | protected override string Url => _url; 15 | 16 | private readonly int _order; 17 | public override int Order => _order; 18 | 19 | public CommonIPv6Getter(string description, string url, int order = 50) 20 | { 21 | _description = description; 22 | _url = url; 23 | _order = order; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv6Getter/IIPv6Getter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.IPGetter.IPv6Getter 6 | { 7 | public interface IIPv6Getter : IIPGetter 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv6Getter/IPv6GetterCreator.cs: -------------------------------------------------------------------------------- 1 | using aliyun_ddns.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace aliyun_ddns.IPGetter.IPv6Getter 7 | { 8 | public static class IPv6GetterCreator 9 | { 10 | private static readonly IEnumerable _definedGetters = new CommonIPv6Getter[] 11 | { 12 | new CommonIPv6Getter("dyndns.com接口", "http://checkipv6.dyndns.com/", 101), 13 | new CommonIPv6Getter("ip.sb接口", "https://api-ipv6.ip.sb/ip", 101), 14 | new CommonIPv6Getter("ident.me接口", "http://v6.ident.me/", 101), 15 | new CommonIPv6Getter("ipify.org接口", "https://api6.ipify.org/", 101), 16 | new CommonIPv6Getter("test-ipv6.com接口", "https://ipv6.lookup.test-ipv6.com/ip/", 100) 17 | }; 18 | 19 | public static IEnumerable Create() 20 | { 21 | List getters = new List(); 22 | if (Options.Instance.CheckLocalNetworkAdaptor) 23 | { 24 | getters.Add(new LocalIPv6Getter()); 25 | } 26 | else 27 | { 28 | getters.AddRange(InstanceCreator.Create(Ignore.CheckType)); 29 | getters.AddRange(_definedGetters); 30 | } 31 | 32 | return getters; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/IPv6Getter/LocalIPv6Getter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.NetworkInformation; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace aliyun_ddns.IPGetter.IPv6Getter 11 | { 12 | /// 13 | /// 获取所有网卡信息,返回最后一个IPv6地址。 14 | /// 15 | [Ignore] 16 | public sealed class LocalIPv6Getter : BaseLocalIPGetter, IIPv6Getter 17 | { 18 | public override string Description => "读取网卡IPv6设置"; 19 | 20 | public override int Order => 200; 21 | 22 | protected override string IPNetworks => Options.Instance.IPv6Networks; 23 | 24 | protected override bool CheckIPAddressInformation(IPAddressInformation info) 25 | { 26 | return info.Address.AddressFamily == AddressFamily.InterNetworkV6; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /aliyun-ddns/IPGetter/Ignore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.IPGetter 6 | { 7 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 8 | public class Ignore : Attribute 9 | { 10 | public static bool CheckType(Type t) 11 | { 12 | return t.GetCustomAttributes(typeof(Ignore), false).Length == 0; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aliyun-ddns/Options.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System; 3 | 4 | namespace aliyun_ddns 5 | { 6 | public class Options 7 | { 8 | [Option('u', "access-key-id", Required = false, Default = "access key id", HelpText = "access key id")] 9 | public string Akid { get; set; } = "access key id"; 10 | 11 | [Option('p', "access-key-secret", Required = false, Default = "access key secret", HelpText = "access key secret")] 12 | public string Aksct { get; set; } = "access key secret"; 13 | 14 | //[Option('e', "endpoint", Required = false, Default = "cn-hangzhou", HelpText = "Region Id,详见https://help.aliyun.com/document_detail/40654.html?spm=a2c4e.11153987.0.0.6d85366aUfTWbG")] 15 | //public string ENDPOINT { get; set; } = "cn-hangzhou"; 16 | 17 | [Option('d', "domain", Required = false, Default = "my.domain.com", HelpText = "需要更新的域名,可以用“,”隔开。可以指定线路,用“:”分隔线路和域名(线路名说明见https://help.aliyun.com/document_detail/29807.html?spm=a2c4g.11186623.2.14.42405eb4boCsnd)。例如:“baidu.com,telecom:dianxin.baidu.com”。")] 18 | public string Domain { get; set; } = "my.domain.com"; 19 | 20 | [Option("root-domain", Required = false, Default = null, HelpText = "需要更新的主域名")] 21 | public string RootDomain { get; set; } = null; 22 | 23 | [Option('i', "interval", Required = false, Default = 300, HelpText = "执行域名更新的间隔,单位秒。")] 24 | public int Redo { get; set; } = 300; 25 | 26 | [Option('t', "ttl", Required = false, Default = 600, HelpText = "服务器缓存解析记录的时长,单位秒。")] 27 | public long TTL { get; set; } = 600; 28 | 29 | [Option("timezone", Required = false, Default = 8.0, HelpText = "输出日志时的时区,单位小时。")] 30 | public double Timezone { get; set; } = 8; 31 | 32 | [Option("type", Required = false, Default = "A,AAAA", HelpText = "需要更改的记录类型,可以用“,”隔开,只能是“A”、“AAAA”或“A,AAAA”。")] 33 | public string Type { get; set; } = "A,AAAA"; 34 | 35 | [Option("cnipv4", Required = false, Default = false, HelpText = "仅使用中国服务器检测公网IPv4地址。")] 36 | public bool CNIPv4 { get; set; } = false; 37 | 38 | [Option('h',"webhook", Required = false, Default = null, HelpText = "WEB HOOK推送地址。")] 39 | public string WebHook { get; set; } = null; 40 | 41 | [Option('c', "checklocal", Required = false, Default = false, HelpText = "是否检查本地网卡IP。此选项将禁用在线API的IP检查。")] 42 | public bool CheckLocalNetworkAdaptor { get; set; } = false; 43 | 44 | [Option("ipv4nets", Required = false, Default = null, HelpText = "本地网卡的IPv4网段。格式示例:“192.168.1.0/24”。多个网段用“,”隔开。")] 45 | public string IPv4Networks { get; set; } = null; 46 | 47 | [Option("ipv46nets", Required = false, Default = null, HelpText = "本地网卡的IPv6网段。格式示例:“240e::/16”。多个网段用“,”隔开。")] 48 | public string IPv6Networks { get; set; } = null; 49 | 50 | private static Options _instance = null; 51 | private static object _instanceLocker = new object(); 52 | 53 | public static Options Instance 54 | { 55 | get 56 | { 57 | lock (_instanceLocker) 58 | { 59 | if (_instance == null) 60 | { 61 | _instance = new Options(); 62 | _instance.InitOptionsFromEnvironment(); 63 | } 64 | 65 | return _instance; 66 | } 67 | } 68 | set 69 | { 70 | lock (_instanceLocker) 71 | { 72 | value.InitOptionsFromEnvironment(); 73 | _instance = value; 74 | } 75 | } 76 | } 77 | 78 | private void InitOptionsFromEnvironment() 79 | { 80 | Akid = GetEnvironmentVariable("AKID") ?? Akid; 81 | Aksct = GetEnvironmentVariable("AKSCT") ?? Aksct; 82 | //ENDPOINT = GetEnvironmentVariable("ENDPOINT") ?? ENDPOINT; 83 | RootDomain = GetEnvironmentVariable("ROOT_DOMAIN") ?? RootDomain; 84 | Domain = GetEnvironmentVariable("DOMAIN") ?? Domain; 85 | Type = GetEnvironmentVariable("TYPE") ?? Type; 86 | WebHook = GetEnvironmentVariable("WEBHOOK") ?? WebHook; 87 | IPv4Networks = GetEnvironmentVariable("IPV4NETS") ?? IPv4Networks; 88 | IPv6Networks = GetEnvironmentVariable("IPV6NETS") ?? IPv6Networks; 89 | 90 | string redoText = GetEnvironmentVariable("REDO"); 91 | if (int.TryParse(redoText, out int redo)) 92 | { 93 | Redo = redo; 94 | } 95 | 96 | string ttlText = GetEnvironmentVariable("TTL"); 97 | if (long.TryParse(ttlText, out long ttl)) 98 | { 99 | TTL = ttl; 100 | } 101 | 102 | string tzText = GetEnvironmentVariable("TIMEZONE"); 103 | if (double.TryParse(tzText, out double tz)) 104 | { 105 | Timezone = tz; 106 | } 107 | 108 | string cnipv4Text = GetEnvironmentVariable("CNIPV4"); 109 | if (bool.TryParse(cnipv4Text, out bool cnipv4)) 110 | { 111 | CNIPv4 = cnipv4; 112 | } 113 | 114 | string checkLocalNetworkAdaptorText = GetEnvironmentVariable("CHECKLOCAL"); 115 | if (bool.TryParse(checkLocalNetworkAdaptorText, out bool checkLocalNetworkAdaptor)) 116 | { 117 | CheckLocalNetworkAdaptor = checkLocalNetworkAdaptor; 118 | } 119 | } 120 | 121 | private static string GetEnvironmentVariable(string variable) 122 | { 123 | string res = Environment.GetEnvironmentVariable(variable); 124 | if (string.IsNullOrEmpty(res)) 125 | { 126 | return null; 127 | } 128 | else 129 | { 130 | return res; 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /aliyun-ddns/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System; 3 | 4 | namespace aliyun_ddns 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | if (Console.WindowWidth <= 10) 11 | { 12 | Parser.Default.Settings.MaximumDisplayWidth = 80; 13 | } 14 | 15 | Parser.Default.ParseArguments(args) 16 | .WithParsed(op => 17 | { 18 | Options.Instance = op; 19 | DomainUpdater updater = new DomainUpdater(); 20 | updater.Run(); 21 | }); 22 | #if DEBUG 23 | Console.Read(); 24 | #endif 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /aliyun-ddns/PublicIpGetter.cs: -------------------------------------------------------------------------------- 1 | using aliyun_ddns.Common; 2 | using aliyun_ddns.IPGetter.IPv4Getter; 3 | using aliyun_ddns.IPGetter.IPv6Getter; 4 | using System; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace aliyun_ddns 9 | { 10 | /// 11 | /// 公网IP获取类。 12 | /// 13 | public static class PublicIPGetter 14 | { 15 | /// 16 | /// 获取公网IPv4。 17 | /// 18 | /// 返回公网IPv4,如果获取失败,返回null。 19 | public static async Task GetIpv4() 20 | { 21 | try 22 | { 23 | var dics = IPv4GetterCreator.Create() 24 | .Where(g => Options.Instance.CNIPv4 == false || g.Order < 100) 25 | .ToDictionary(g => g.GetIP(), g => g.Description); 26 | string result = await dics.Keys.WhenAny(task => 27 | { 28 | string ip = task.Result; 29 | if (ip == null) 30 | { 31 | Log.Print($"从 { dics[task] } 获取公网IPv4失败。"); 32 | } 33 | else 34 | { 35 | Log.Print($"当前公网IPv4为 { ip }({ dics[task] })。"); 36 | } 37 | 38 | return ip != null; 39 | }, 40 | TimeSpan.FromSeconds(30)); 41 | 42 | if (result == null) 43 | { 44 | Log.Print($"获取公网IPv4失败,所有API接口均无法返回IPv4地址。"); 45 | } 46 | 47 | return result; 48 | } 49 | catch (Exception e) 50 | { 51 | Log.Print($"检测公网IPv4时发生异常:{ e.Message }"); 52 | return null; 53 | } 54 | } 55 | 56 | /// 57 | /// 获取公网IPv6。 58 | /// 59 | /// 返回公网IPv6,如果获取失败,返回null。 60 | public static async Task GetIpv6() 61 | { 62 | try 63 | { 64 | var dics = IPv6GetterCreator.Create().ToDictionary(g => g.GetIP(), g => g.Description); 65 | string result = await dics.Keys.WhenAny(task => 66 | { 67 | string ip = task.Result; 68 | if (ip == null) 69 | { 70 | Log.Print($"从 { dics[task] } 获取公网IPv6失败。"); 71 | } 72 | else 73 | { 74 | Log.Print($"当前公网IPv6为 { ip }({ dics[task] })。"); 75 | } 76 | 77 | return ip != null; 78 | }, 79 | TimeSpan.FromSeconds(30)); 80 | 81 | if (result == null) 82 | { 83 | Log.Print($"获取公网IPv6失败,所有API接口均无法返回IPv6地址。"); 84 | } 85 | 86 | return result; 87 | } 88 | catch (Exception e) 89 | { 90 | Log.Print($"检测公网IPv6时发生异常:{ e.Message }"); 91 | return null; 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /aliyun-ddns/WebHook/WebHookAction.cs: -------------------------------------------------------------------------------- 1 | using aliyun_ddns.Common; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Net.Http; 8 | using System.Text; 9 | 10 | namespace aliyun_ddns.WebHook 11 | { 12 | public static class WebHookAction 13 | { 14 | public static void Push(IEnumerable items) 15 | { 16 | string url = Options.Instance.WebHook; 17 | if (string.IsNullOrWhiteSpace(url)) 18 | { 19 | return; 20 | } 21 | 22 | try 23 | { 24 | DateTime now = DateTime.Now; 25 | StringBuilder textBuilder = new StringBuilder($"以下域名在{ now }发生变化:"); 26 | foreach (var i in items) 27 | { 28 | textBuilder.Append($"\n{ i.domain }({ i.recordType }) => { i.ip }"); 29 | } 30 | 31 | dynamic o = new 32 | { 33 | timestamp = ToTimestamp(now), 34 | changes = items, 35 | msgtype = "text", 36 | text = new 37 | { 38 | content = textBuilder.ToString() 39 | } 40 | }; 41 | 42 | string content = JsonConvert.SerializeObject(o); 43 | using (var http = new HttpClient()) 44 | { 45 | StringContent sc = new StringContent(content, Encoding.UTF8, "application/json"); 46 | http.PostAsync(url, sc).Wait(); 47 | } 48 | 49 | Log.Print("成功推送WebHook。"); 50 | } 51 | catch (Exception e) 52 | { 53 | Log.Print($"推送WebHook时发生异常:\n{ e }"); 54 | } 55 | } 56 | 57 | private static long ToTimestamp(DateTime dt) 58 | { 59 | return (dt.ToUniversalTime().Ticks - 621355968000000000) / 10000000; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /aliyun-ddns/WebHook/WebHookItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace aliyun_ddns.WebHook 6 | { 7 | public struct WebHookItem 8 | { 9 | public string recordType; 10 | public string domain; 11 | public string ip; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /aliyun-ddns/aliyun-ddns.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Exe 84 | netcoreapp3.1 85 | aliyun_ddns 86 | 0.2.7 87 | sanjusss 88 | https://github.com/sanjusss/aliyun-ddns 89 | 90 | https://github.com/sanjusss/aliyun-ddns 91 | git 92 | false 93 | BSD 94 | 0.2.7.0 95 | 0.2.7.0 96 | Linux 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | --------------------------------------------------------------------------------