Amazon AWS Route 53 动态 IP 更新器 Windows 服务
自动更新您的动态 IP 地址,支持多种 DNS 提供商和 IP 检查器。本项目最初是为了更新 Amazon AWS Route 53 而启动的。
引言
我长期以来一直是 DynDns 的付费客户,但 Amazon Route 53 的价格更优惠,并且月度账单更灵活,更不用说我正在将虚拟机迁移到 AWS VPC/EC2,这促使我做出改变。 Route 53 很棒,但问题是我找不到任何 Route 53 Windows 服务客户端来自动更新 IP,我仍然希望以可靠的方式访问我家的 Windows 服务器,而不是依赖那些免费的 IP/DNS 服务。这就是我启动这个项目的原因。
背景
最初,该项目是为 Amazon Route 53 编写的,但其架构和设计足够灵活,任何人都可以实现其他 DNS 更新提供商。需要 Visual Studio 2013 和 .NET 4.5.0。
主要功能包括:
- 支持多种 DNS 提供商、多个域名更新,不限于 Amazon Route 53,您可以轻松实现其他提供商,例如 (DynDns, No-IP 等)
- 多种 IP 检查器,您可以在您的 ISP 或 Amazon VPC/EC2 上运行自己的 IP 检查器。当一个失败时,它会自动切换到下一个
- 自定义 XML 配置,易于配置
- 详细的日志记录所有活动 (不限于文件日志),您可以轻松配置写入事件日志,无需更改代码 (需要 Microsoft Enterprise Library 知识)
- 通过电子邮件通知 (已测试 Gmail SMTP),告知任何更新
- 使用 Des3 加密密码。这可能被视为一个弱点,因为对称加密不安全
- 监控 DNS 提供商状态更改。DNS 提供商如 Route 53 的更新通常需要几分钟才能传播到其所有 DNS 服务器。AWS 提供了一个 API,可以通过给定的 ID 来检查更改状态。为了确保更改成功,PENDING 将变为 INSYNC 以完成。
- 强制在 x 天内更新
- 使用开源 TopShelf 项目作为 Windows 服务运行
- 没有用户界面 :)
必备组件
使用了 Visual Studio 2013 和 .NET 4.5.0。以下包需要从 NuGet restore
- Microsoft Enterprise Library 6.0.1304
- Microsoft Unity 3.5.1404
- AWS SDK 2.3.19
- Topshelf 3.1.4
架构和设计
首先,我们来谈谈配置。为了支持多个域、多个提供商和多个 IPChecker,默认的 App.config 键值对配置不够灵活,因此我们有一个自定义的 XmlConfig。
XML 配置
<xmlconfig>
home.yourdomain1.com
AMAZON_ROUTE_53
ZQ455PJ32KNK8GI2A
AJUGYGYSNHYQHSJFOB
jFjF34lazdJFjdjm24FHsdjfg
5
1.1.1.1
2015-02-21T19:29:12.8381639Z
home.yourdomain2.com
AMAZON_ROUTE_53
ZJFD34IIFJ52SIF
AJUGYGYSNHYQHSJFOB
jFjF34lazdJFjdjm24FHsdjfg
5
1.1.1.1
2015-02-21T19:29:12.8381639Z
AMAZON_ROUTE_53
https://route53.amazonaws.com
CUSTOM
WEB_HTTP
http://ec2.yourdomain1.com/
JSON_IP
WEB_HTTP
http://www.jsonip.com/
DYN_DNS
WEB_HTTP
http://checkip.dyndns.com/
</xmlconfig>
要理解设计,您需要对软件工程中的依赖注入 (DI) 和控制反转 (IoC) 有基本了解。
使用 Unity IoC 容器进行依赖注入
由于配置非常灵活,我们在运行时完成读取 XmlConfig 之前无法创建对象实例,因此 DI 是绝对必需的。Microsoft Unity IoC 容器用于从不同的接口解析实例。
对于接口,我们有 **"IDnsProvider", "IIpAddressChecker" 和 "IClient"**。
**IDnsProviders** 可以是 AmazonRoute53DnsProvider, DynDnsProvider, NoIpDnsProvider 等。
**IIPAddressChecker** 可以是 CustomIpAddressChecker, DynDnsIpAddressChecker, JsonIpAddressChecker 等。
**IClient** 可以是 WebHttpClient, WebSslClient, WebSoapClient 等。(供 IpAddress Checker 重用)
public interface IDnsProvider
{
string UpdateDns(string accessID, string secretKey, string providerUrl, string domainName, string hostZoneId, string newIPaddress);
Meta.Enum.ChangeStatusType CheckUpdateStatus(string accessID, string secretKey, string providerUrl, string id);
}
public interface IIpAddressChecker
{
string GetCurrentIpAddress(string IpProviderURL, IClient client);
}
public interface IClient
{
string GetContent(string IpProviderUrl, DelegateParser parser);
}
序列化和对象映射
要使用配置,我们执行将 XML 反序列化为对象,但还需要灵活性以将其用作包含所有子实例的内存对象。目标是获得与反序列化对象**解耦**的可工作对象。在这种情况下,我们将 XmlConfig 简单地映射到 2 个新模型:**"DomainModel"** 和 **"IpCheckerModel"**。
在多层应用程序中,我们通常使用开源 AutoMapper 进行数据传输对象 (DTO),但对于这个项目来说,这有点过度,所以我们进行了手动映射。
public class DomainModel
{
public string DomainName { get; set; }
public Meta.Enum.DnsProviderType DnsProviderType { get; set; }
public IDnsProvider DnsProvider { get; set; }
public string ProviderUrl { get; set; } // Flatten as one of the properties
public string HostedZoneId { get; set; }
public string AccessID { get; set; }
public string SecretKey { get; set; }
public string MinimalUpdateIntervalInMinutes { get; set; }
public string LastIpAddress { get; set; }
public DateTime LastUpdatedDateTime { get; set; }
public string ChangeStatusID { get; set; }
}
public class IpCheckerModel
{
public Meta.Enum.IpCheckerType IpCheckerType { get; set; }
public Meta.Enum.ClientType ClientType { get; set; }
public string IpCheckerUrl { get; set; }
public IClient Client { get; set; }
public IIpAddressChecker IpAddressChecker { get; set; }
}
运行时从 XmlConfig 解析 DnsProvider 实例并映射到 DomainModel。类似地,根据配置的类型对 IpChecker 进行处理。我假设您已经明白了。
//.......
//.......
// Load Domains from Config, Resolve the DnsProvider instance based on configuration
_domainModelList = new List<DomainModel>();
foreach (Domain domain in xmlConfig.Domains)
{
DomainModel domainModel = new DomainModel();
domainModel.DomainName = domain.DomainName;
domainModel.DnsProviderType = domain.ProviderType;
// Resolve DnsProvider
domainModel.DnsProvider = _container.Resolve<IDnsProvider>(domain.ProviderType.ToString());
// Find the matching Provider (XmlConfig object) and get the URL, flatten it for this Model
domainModel.ProviderUrl = ((Provider)xmlConfig.Providers.FirstOrDefault(x => x.ProviderType == domain.ProviderType)).ProviderUrl;
domainModel.HostedZoneId = domain.HostedZoneId;
domainModel.AccessID = domain.AccessID;
// Decrypt the string if it the data is encrypted
if (ConfigHelper.EnablePasswordEncryption)
domainModel.SecretKey = Des3.Decrypt(domain.SecretKey, ConfigHelper.EncryptionKey);
else
domainModel.SecretKey = domain.SecretKey;
domainModel.MinimalUpdateIntervalInMinutes = domain.MinimalUpdateIntervalInMinutes;
domainModel.LastIpAddress = domain.LastIpAddress;
domainModel.LastUpdatedDateTime = domain.LastUpdatedDateTime;
if (domainModel.DnsProvider == null)
{
Logger.Write("Cannot resolve Provider, misconfiguration in XML config file in Domain section.", Meta.Enum.LogCategoryType.CONFIGURATION.ToString());
throw new ArgumentNullException();
}
if (String.IsNullOrWhiteSpace(domain.ChangeStatusID))
domainModel.ChangeStatusID = null;
else
domainModel.ChangeStatusID = domain.ChangeStatusID;
_domainModelList.Add(domainModel);
}
// Load IpCheckers from Config, Resolve the instance
_ipCheckerModelList = new List<IpCheckerModel>();
foreach (IpChecker ipChecker in xmlConfig.IpCheckers)
{
// Mapping from Config to Model
IpCheckerModel model = new IpCheckerModel();
model.IpCheckerType = ipChecker.IpCheckerType;
model.IpCheckerUrl = ipChecker.IpCheckerUrl;
model.ClientType = ipChecker.ClientType;
// Resolve Client
model.Client = _container.Resolve<IClient>(ipChecker.ClientType.ToString());
// Resolve IpAddressChecker
model.IpAddressChecker = _container.Resolve<IIpAddressChecker>(ipChecker.IpCheckerType.ToString());
if ((model.Client == null) || (model.IpAddressChecker == null))
{
Logger.Write("Cannot resolve Checker or Client, misconfiguration in XML config file in IpChecker section.", Meta.Enum.LogCategoryType.CONFIGURATION.ToString());
throw new ArgumentNullException();
}
_ipCheckerModelList.Add(model);
}
//.......
//.......
XPath 与重新序列化 (有争议)
更新操作完成后,我们需要更新 XML 文件,信息包括 LastUpdatedDateTime, LastIpAddress 和 ChangeStatusID 使用 XPath 进行更新。但这有争议,因为您可以重新序列化 XML 并保存整个文档。
/// <summary>
/// Update LastIpAddress + LastUpdatedDateTime in XmlConfig using XPath (Called by Update Dns)
/// </summary>
/// <param name="domainName"></param>
/// <param name="dateTimeinUTC"></param>
/// <returns></returns>
public static bool UpdateLastUpdatedInformation(string domainName, string ipAddress, DateTime dateTimeinUTC)
{
// Use Xpath to update (debatable - not deserialize the xml?)
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(AppDomain.CurrentDomain.BaseDirectory + XmlConfigFileName);
XmlNode root = xmlDocument.DocumentElement;
// Find the matching domain name using Xpath and update the datetime
XmlNode node = root.SelectSingleNode("//Domains/Domain[DomainName=\"" + domainName + "\"]");
if (node != null)
{
node["LastIpAddress"].InnerText = ipAddress;
node["LastUpdatedDateTime"].InnerText = dateTimeinUTC.ToString("o"); // UTC timestamp in ISO 8601 format
// Need to use this to fix carriage return problem if InnerText is an empty string
XmlWriterSettings settings = new XmlWriterSettings { Indent = true };
using (XmlWriter writer = XmlWriter.Create(AppDomain.CurrentDomain.BaseDirectory + XmlConfigFileName, settings))
{
xmlDocument.Save(writer);
}
return true;
}
else
return false;
}
DnsProviders 和 IpCheckers 的实现
现在进入核心 DNS 更新和 IP 检查功能的实际实现。未来,例如 Microsoft Azure 推出新的 DNS 服务,您可以轻松实现 **MicrosoftAzureProvider : IDnsProvider**。
现在我们专注于 Route 53,AWS SDK 有一些很好的在线示例用于更新 DNS,唯一的问题是它们使用无限的 WHILE 循环和 SLEEP 来检查状态,这在实际生产中我**不太赞同**使用,尽管我们知道文档仅用于教育目的。
参考:http://docs.aws.amazon.com/AWSSdkDocsNET/latest/DeveloperGuide/route53-recordset.html
因此,我们有另一个用于 **CheckUpdateStatus** 的方法,它由一个单独的 Timer 调用。AmazonRoute53Provider 类旨在执行 DNS 更新和状态检查。这是实现:
public class AmazonRoute53DnsProvider : IDnsProvider
{
// ........
// ........
// ... Constructor/variables not shown here (download source code)
// ........
// ........
public string UpdateDns(string accessID, string secretKey, string providerUrl, string domainName, string hostZoneId, string newIPaddress)
{
string changeRequestId = null;
// Assign parameters
_accessID = accessID;
_secretKey = secretKey;
_providerUrl = providerUrl;
// Create a resource record set change batch
ResourceRecordSet recordSet = new ResourceRecordSet()
{
Name = domainName,
TTL = 60,
Type = RRType.A,
ResourceRecords = new List<ResourceRecord> { new ResourceRecord { Value = newIPaddress } }
};
Change change1 = new Change()
{
ResourceRecordSet = recordSet,
Action = ChangeAction.UPSERT // Note: UPSERT is used
};
ChangeBatch changeBatch = new ChangeBatch()
{
Changes = new List<Change> { change1 }
};
// Update the zone's resource record sets
ChangeResourceRecordSetsRequest recordsetRequest = new ChangeResourceRecordSetsRequest()
{
HostedZoneId = hostZoneId,
ChangeBatch = changeBatch
};
ChangeResourceRecordSetsResponse recordsetResponse = AmazonRoute53Client.ChangeResourceRecordSets(recordsetRequest);
changeRequestId = recordsetResponse.ChangeInfo.Id;
return changeRequestId;
} // method
/// <summary>
/// AmazonRoute53 takes several minutes to propagate through all the DNS servers, Status is Pending after submit
/// </summary>
/// <param name="id"></param>
public Meta.Enum.ChangeStatusType CheckUpdateStatus(string accessID, string secretKey, string providerUrl, string id)
{
if (String.IsNullOrEmpty(id))
return Meta.Enum.ChangeStatusType.INSYNC;
// Assign parameters
_accessID = accessID;
_secretKey = secretKey;
_providerUrl = providerUrl;
// Monitor the change status
GetChangeRequest changeRequest = new GetChangeRequest(id);
if (AmazonRoute53Client.GetChange(changeRequest).ChangeInfo.Status == ChangeStatus.PENDING)
return Meta.Enum.ChangeStatusType.PENDING;
else
return Meta.Enum.ChangeStatusType.INSYNC;
} // method
}
至于 IpChecker,每个 **IpAddressChecker** 都可以返回不同的格式,所以我们需要一个解析器来解析 checkip.dyndns.com 的 HTML 或一个 JSON 解析器来解析 JsonIp.com 的结果。为了重用 IClient,我们将 Parser 作为 Delegate 传递。
/// <summary>
/// Web Http Client with Delegate Parser
/// </summary>
public class WebHttpClient : IClient
{
/// <summary>
/// Get the content of the Html as string
/// </summary>
/// <param name="IpProviderUrl"></param>
/// <returns></returns>
public string GetContent(string IpProviderUrl, DelegateParser parser)
{
string content = null;
int timeoutInMilliSeconds = Convert.ToInt32(ConfigHelper.ClientTimeoutInMinutes) * 60 *1000;
// Use IDisposable webclient to get the page of content of existing IP
using (TimeoutWebClient client = new TimeoutWebClient((timeoutInMilliSeconds)))
{
content = client.DownloadString(IpProviderUrl);
}
if (content != null)
return parser(content);
else
return null;
}
/// <summary>
/// To support timeout value, alternatively you can use HttpWebRequest and Stream
/// </summary>
public class TimeoutWebClient : WebClient
{
public int Timeout { get; set; }
public TimeoutWebClient(int timeout)
{
Timeout = timeout;
}
protected override WebRequest GetWebRequest(Uri address)
{
WebRequest request = base.GetWebRequest(address);
request.Timeout = Timeout;
return request;
}
}
}
**JsonIp.com** 是一个很棒的免费服务,速度很快,它以 JSON 格式返回您的当前 IP,所以我们需要解析它。这是实现:
public class JsonIpAddressChecker : IIpAddressChecker
{
public string GetCurrentIpAddress(string ipProviderURL, IClient client)
{
// Pass the parser as function to the client
DelegateParser handler = Parse;
return client.GetContent(ipProviderURL, handler);
}
private string Parse(string jsonString)
{
string ipString = null;
// format: {"ip":"x.x.x.x","about":"/about","Pro!":"http://getjsonip.com"}
var jsonSerializer = new JavaScriptSerializer();
Dictionary<string, string> data = jsonSerializer.Deserialize<Dictionary<string, string>>(jsonString);
ipString = data["ip"];
// Validate if this is a valid IPV4 address
if (IpHelper.IpAddressV4Validator(ipString))
return ipString;
else
return null;
}
}
public class DynDnsIpAddressChecker : IIpAddressChecker
{
// Implementation (download source code)
}
public class CustomIpAddressChecker : IIpAddressChecker
{
// Implementation (download source code)
}
如果您有带有 IIS 的 Amazon VPC/EC2,或者您有运行传统 ASPX 页面的首选 ISP,您可以使用 CustomIpAddressChecker 并将此 ASPX 代码放在 Default.aspx 中。
<%@ Page Language="C#" %>
<%
// In case your server is behind proxy
string ip = Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
// If not, then use the traditional remote_addr
if (ip == null)
ip = Request.ServerVariables["REMOTE_ADDR"];
Response.Write(ip);
%>
基于时间的更新和监控工作流程
Amazon Route 53 与其他 DNS 更新提供商不同,在调用 Route 53 API 进行 IP 更新后,该域/子域的状态将变为 **PENDING**,并返回一个更改状态 **ID**。这需要几分钟才能传播到其所有 DNS 服务器,但对于更新家庭 IP 地址来说是可以接受的。Route 53 提供了另一个 API,您可以调用它来检查更新状态。当操作完成时,它返回 **INSYNC**。
为了执行基于时间的更新,可以使用 Thread 或 Timer。由于一个更新可能在另一个之后发生,因此没有必要实现并发线程。使用了两个独立的 Timer:
- 第一个 Timer (用于更新) 每 5 分钟运行一次 (默认),以检查更新 (调用 IPCheckers 并从结果中解析 IP),如果小于 MinimalUpdateIntervalInMinutes (每个域的设置,以防止滥用) 或 IP 没有改变,则继续。否则,调用 Dns Provider 以针对当前 IP 执行更新,然后更新内存对象以及 XmlConfig.xml 中的 LastIpAddress, LastUpdatedDateTime 和 ChangeStatusID。对配置中的所有域重复此操作。
- 第二个 Timer (用于监控) 每 1 分钟运行一次 (默认),以检查是否有 ChangeStatusID 需要监控所有域。当 DNS Provider API 返回 **INSYNC** (使用 Amazon 的术语) 时,ChangeStatusID 将被设置为空,并发送电子邮件通知。
我们定义了核心 "Worker" 类,其中包含 2 个 Timer,并在 Unity 容器中注册所有类型映射。然后在读取配置后解析它们 (如上所述)。
public class Worker
{
// Timer for interval update and monitor status
private Timer _updateTimer;
private Timer _montiorTimer;
// IoC for flexbile configuration
private IUnityContainer _container = new UnityContainer();
// XmlConfig (XML serialization objects) maps to Model Collection
private List<IpCheckerModel> _ipCheckerModelList;
private List<DomainModel> _domainModelList;
/// <summary>
/// Constructor
/// </summary>
public Worker()
{
try
{
// Init the enterprise log
Logger.SetLogWriter(new LogWriterFactory().Create());
Logger.Write("Updater worker Initialized.", Meta.Enum.LogCategoryType.WIN_SERVICE.ToString());
// Mappings for Unity container
_container.RegisterType<IDnsProvider, AmazonRoute53DnsProvider>(Meta.Enum.DnsProviderType.AMAZON_ROUTE_53.ToString());
_container.RegisterType<IClient, WebHttpClient>(Meta.Enum.ClientType.WEB_HTTP.ToString());
_container.RegisterType<IIpAddressChecker, CustomIpAddressChecker>(Meta.Enum.IpCheckerType.CUSTOM.ToString());
_container.RegisterType<IIpAddressChecker, DynDnsIpAddressChecker>(Meta.Enum.IpCheckerType.DYN_DNS.ToString());
_container.RegisterType<IIpAddressChecker, JsonIpAddressChecker>(Meta.Enum.IpCheckerType.JSON_IP.ToString());
_container.RegisterType<INotification, EmailNotification>(Meta.Enum.NotificationType.EMAIL.ToString());
// Read the XML config file for all the Domains/Providers/IpCheckers and Map them to Model
MappingToModel(ConfigHelper.LoadConfig());
// Configure the Timers and handlers
_updateTimer = new Timer(Convert.ToDouble(ConfigHelper.UpdateIntervalInMinutes) * 1000 * 60);
_updateTimer.Elapsed += new ElapsedEventHandler(UpdateTimerElapsed);
_montiorTimer = new Timer(Convert.ToDouble(ConfigHelper.MonitorStatusInMinutes) * 1000 * 60);
_montiorTimer.Elapsed += new ElapsedEventHandler(MonitorTimerElapsed);
}
catch (Exception ex)
{
Logger.Write(String.Format("FATAL ERROR, Exception={0}", ex.ToString()), Meta.Enum.LogCategoryType.WIN_SERVICE.ToString());
}
}
/// <summary>
/// Start the timer
/// </summary>
public void Start()
{
_updateTimer.Start();
_montiorTimer.Start();
Logger.Write("Service has started.", Meta.Enum.LogCategoryType.WIN_SERVICE.ToString());
// First time running it no waiting
this.RunUpdate();
}
/// <summary>
/// Stop the timer
/// </summary>
public void Stop()
{
_updateTimer.Stop();
_montiorTimer.Stop();
this.CleanUp();
Logger.Write("Service has stopped.", Meta.Enum.LogCategoryType.WIN_SERVICE.ToString());
}
/// <summary>
/// Event hook for the update job
/// </summary>
private void UpdateTimerElapsed(object sender, ElapsedEventArgs e)
{
this.RunUpdate();
}
/// <summary>
/// Monitor the update status after submit
/// </summary>
private void MonitorTimerElapsed(object sender, ElapsedEventArgs e)
{
this.RunMonitor();
}
/// <summary>
/// Loop through the domains and update the IP
/// </summary>
public void RunUpdate()
{
// ...............
// ............... Details Implementation (download source code)
// ...............
}
/// <summary>
/// Monitor the DNS update status after submitted to the provider
/// </summary>
public void RunMonitor()
{
// ...............
// ............... Details Implementation (download source code)
// ...............
}
}
Windows 服务和 TopShelf
开源 TopShelf 被使用,因为它是一个非常好的 Windows 服务框架,它使代码可调试,并使应用程序更易于配置和安装。它创建 Worker() 的新实例,并在服务启动时调用 Start(),在服务停止时调用 Stop()。
public class Program
{
public static void Main()
{
// Using Open Source project "Topshelf" to handle the run as windows service
// Ref: http://topshelf-project.com/
HostFactory.Run(x =>
{
x.Service<Worker>(s =>
{
s.ConstructUsing(name => new Worker());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.SetDescription("Update current IP address supports multiple DNS providers");
x.SetDisplayName("Dynamic DNS Updater Service");
x.SetServiceName("DynamicDnsUpdater");
});
} // main
} // program
实际运行的日志文件示例
在此示例中,我们首先将 XmlConfig 中的 LastUpdatedDateTime 设置为超过 1 个月,启动应用程序,由于已过 30 天,IP 已更新,ForceUpdate 已触发。在大约 10 分钟标记时,我们将 VPC/EC2 上的 IPChecker 手动更改为 192.168.0.1 以模拟 IP 更改。如果您仔细查看,第一次更新只发送了 1 条通知。但第二次更新发送了 2 条通知。原因是 1 分钟的监控间隔,Route 53 完成了第一个域的更改,但第二个域仍处于 PENDING 状态。
2015-03-04 12:04:49 WIN_SERVICE -1 Updater worker Initialized. 2015-03-04 12:04:49 WIN_SERVICE -1 Service has started. 2015-03-04 12:04:50 IP_CHECKER -1 IpChecker=http://checkip.yourdomain.com/, IP=74.125.226.111 2015-03-04 12:04:50 DNS_UPDATE -1 Domain=home.yourdomain1.com - FORCE UPDATED provider successfully from IP=74.125.226.111 to IP=74.125.226.111 with ID=/change/C1XBHGFNFIHU8A, passed 30 days 2015-03-04 12:04:50 DNS_UPDATE -1 Domain=home.yourdomain1.com - UPDATED XML configuration LastIpAddress=74.125.226.111, LastUpdatedDateTime=3/4/2015 5:04:50 PM 2015-03-04 12:04:50 DNS_UPDATE -1 Domain=home.yourdomain2.com - FORCE UPDATED provider successfully from IP=74.125.226.111 to IP=74.125.226.111 with ID=/change/C4L2T7HJFSOI4B, passed 30 days 2015-03-04 12:04:50 DNS_UPDATE -1 Domain=home.yourdomain2.com - UPDATED XML configuration LastIpAddress=74.125.226.111, LastUpdatedDateTime=3/4/2015 5:04:50 PM 2015-03-04 12:05:49 STATUS_MONITOR -1 Domain=home.yourdomain1.com - ChangeStatus=PENDING 2015-03-04 12:05:50 STATUS_MONITOR -1 Domain=home.yourdomain2.com - ChangeStatus=PENDING 2015-03-04 12:06:50 STATUS_MONITOR -1 Domain=home.yourdomain1.com - XML configuration ChangeStatusType Updated to INSYNC 2015-03-04 12:06:50 STATUS_MONITOR -1 Domain=home.yourdomain2.com - XML configuration ChangeStatusType Updated to INSYNC 2015-03-04 12:06:53 NOTIFICATION -1 Notification has been sent successfully 2015-03-04 12:11:02 IP_CHECKER -1 IpChecker=http://checkip.yourdomain.com/, IP=74.125.226.111 2015-03-04 12:11:17 DNS_UPDATE -1 Domain=home.yourdomain1.com - NOT UPDATED because IP=74.125.226.111 has not been changed 2015-03-04 12:11:20 DNS_UPDATE -1 Domain=home.yourdomain2.com - NOT UPDATED because IP=74.125.226.111 has not been changed 2015-03-04 12:16:59 IP_CHECKER -1 IpChecker=http://checkip.yourdomain.com/, IP=192.168.0.1 2015-03-04 12:17:00 DNS_UPDATE -1 Domain=home.yourdomain1.com - UPDATED provider successfully from IP=74.125.226.111 to IP=192.168.0.1 with ID=/change/C47J0BHCS33NAD 2015-03-04 12:17:00 DNS_UPDATE -1 Domain=home.yourdomain1.com - UPDATED XML configuration LastIpAddress=192.168.0.1, LastUpdatedDateTime=3/4/2015 5:17:00 PM 2015-03-04 12:17:10 DNS_UPDATE -1 Domain=home.yourdomain2.com - UPDATED provider successfully from IP=74.125.226.111 to IP=192.168.0.1 with ID=/change/C5YDKABPC145AM 2015-03-04 12:17:10 DNS_UPDATE -1 Domain=home.yourdomain2.com - UPDATED XML configuration LastIpAddress=192.168.0.1, LastUpdatedDateTime=3/4/2015 5:17:10 PM 2015-03-04 12:17:55 STATUS_MONITOR -1 Domain=home.yourdomain1.com - ChangeStatus=PENDING 2015-03-04 12:17:55 STATUS_MONITOR -1 Domain=home.yourdomain2.com - ChangeStatus=PENDING 2015-03-04 12:18:55 STATUS_MONITOR -1 Domain=home.yourdomain1.com - XML configuration ChangeStatusType Updated to INSYNC 2015-03-04 12:18:55 STATUS_MONITOR -1 Domain=home.yourdomain2.com - ChangeStatus=PENDING 2015-03-04 12:18:57 NOTIFICATION -1 Notification has been sent successfully 2015-03-04 12:19:55 STATUS_MONITOR -1 Domain=home.yourdomain2.com - XML configuration ChangeStatusType Updated to INSYNC 2015-03-04 12:19:57 NOTIFICATION -1 Notification has been sent successfully 2015-03-04 12:22:52 IP_CHECKER -1 IpChecker=http://checkip.yourdomain.com/, IP=192.168.0.1 2015-03-04 12:22:52 DNS_UPDATE -1 Domain=home.yourdomain1.com - NOT UPDATED because IP=192.168.0.1 has not been changed 2015-03-04 12:22:53 DNS_UPDATE -1 Domain=home.yourdomain2.com - NOT UPDATED because IP=192.168.0.1 has not been changed
重要说明
**生产注意事项:** 如果您手动编辑 XmlConfig.xml 文件,您必须 **重新启动 Windows 服务** 才能生效。否则,它将继续使用内存中的数据。
**调试注意事项:** 正如我提到的,开源 TopShelf 非常好,您甚至可以在 Visual Studio 中启动代码并调试 Windows 服务!请注意,在调试模式下运行时,它会在 **/bin/debug** 文件夹中创建 **DynamicDnsUpdater.Service.config** 和 **XmlConfig.xml**。该应用程序**绝不会**更改您源文件夹中的 App.config 和 Xml.Config.xml。
编译和安装
- Visual Studio 2013 - 启用 NuGet 管理器以还原所有库包,然后编译。
- 在 /BIN 文件夹中,删除所有不必要的文件,例如 *.pdb, *.manifest, *.application (可选)。
- 将文件复制到服务器,例如 C:\Program Files (x86)\DnsUpdaterService\
- 在服务器上,请确保您安装了 .NET Framework 4.5 的完整版本 (而不是客户端配置文件版本)。
- 在服务器命令提示符下,键入 > **DynamicDnsUpdater.Service.exe** **install**
- 编辑 **DynamicDnsUpdater.Service.config** 文件 (这是 App.config) 并更改:
- - 通知电子邮件/用户名/密码/SMTP
- - 日志路径:**c:\Logs\DnsUpdaterService.log** 和 ** c:\Logs\DnsUpdaterError.log**
- 编辑 **XmlConfig.xml** (域/提供商配置) 并更新您的域/提供商。
- 转到 Windows 服务,找到 "Dynamic DNS Update Service",然后启动。
加密密码/密钥
默认情况下,App.config 中的 smtp 密码和 XmlConfig.xml 中的 SecretKey 未加密,如果您愿意,可以使用默认的 DES3 加密方法加密两者。DES3 是对称的,因此不是很安全,但总比没有好。要做到这一点:
- 您可以在 App.config 中将标志 "EnablePasswordEncryption" 设置为 true。
- 在 ConfigHelper.EncryptionKey 中更新您自己的加密密钥。
- 使用 Des3.Encrypt() 加密您的明文密码/密钥。
收紧 AWS IAM 安全性
您可能希望收紧 IAM (身份和访问管理) 中创建的用户的安全性,因为默认情况下创建用户时,它可能会分配可以访问您 AWS 账户 (VPC/EC2 等) 中所有内容的 Power User 组。为了限制用户只能访问 Route 53 的特定区域,请将您的 IAM 用户编辑为以下策略。在此示例中,ZQ455PJ32KNK8GI2A 是我第一个域的托管区域 ID,ZJFD34IIFJ52SIF 是我第二个域的托管区域 ID。这只是一个简单的 JSON 文档,不言自明。
{
"Version": "2012-10-17",
"Statement":[
{
"Action":[
"route53:ChangeResourceRecordSets",
"route53:GetHostedZone",
"route53:ListResourceRecordSets",
"route53:GetChange"
],
"Effect":"Allow",
"Resource":[
"arn:aws:route53:::hostedzone/ZQ455PJ32KNK8GI2A",
"arn:aws:route53:::hostedzone/ZJFD34IIFJ52SIF",
"arn:aws:route53:::change/*"
]
},
{
"Action":[
"route53:ListHostedZones"
],
"Effect":"Allow",
"Resource":[
"*"
]
}
]
}
在撰写本文时,Amazon AWS **不支持**域/子域名称级别的安全性,它只支持区域级别的安全性。换句话说,IAM 用户可以更改区域内的所有内容。
希望您觉得这篇文章有用!
GitHub 开源项目
在 GitHub 上查看、下载或贡献:https://github.com/riniboo/DynamicDnsUpdater.Service
历史
2015-03-04 - 首次发布
2015-03-05 - 修复了拼写错误并增加了更详细的描述
2015-03-07 - 添加了更多代码来解释设计
2015-12-19 (V1.0.0.2)
- 修复了由于对象未正确释放导致的通知超时的小 bug
- 在日志和电子邮件通知中添加了新的 "Forced" 或 "Changed" 状态
- 更新了 Amazon AWS API、Topshelf 和 Unity