├── .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 | [](https://github.com/sanjusss/aliyun-ddns/actions/workflows/dotnet-core.yml)
3 | [](https://hub.docker.com/r/sanjusss/aliyun-ddns)
4 | [](https://github.com/sanjusss/aliyun-ddns/blob/master/LICENSE)
5 | [](https://github.com/sanjusss/aliyun-ddns/tags)
6 | [](https://github.com/sanjusss/aliyun-ddns/issues)
7 | [](https://github.com/sanjusss/aliyun-ddns/network)
8 | [](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 |
--------------------------------------------------------------------------------