65.9K
CodeProject 正在变化。 阅读更多。
Home

Amazon AWS Route 53 动态 IP 更新器 Windows 服务

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2015年3月4日

GPL3

9分钟阅读

viewsIcon

27515

downloadIcon

486

自动更新您的动态 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:

  1. 第一个 Timer (用于更新) 每 5 分钟运行一次 (默认),以检查更新 (调用 IPCheckers 并从结果中解析 IP),如果小于 MinimalUpdateIntervalInMinutes (每个域的设置,以防止滥用) 或 IP 没有改变,则继续。否则,调用 Dns Provider 以针对当前 IP 执行更新,然后更新内存对象以及 XmlConfig.xml 中的 LastIpAddress, LastUpdatedDateTime 和 ChangeStatusID。对配置中的所有域重复此操作。
     
  2. 第二个 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。

编译和安装

  1. Visual Studio 2013 - 启用 NuGet 管理器以还原所有库包,然后编译。
  2. 在 /BIN 文件夹中,删除所有不必要的文件,例如 *.pdb, *.manifest, *.application (可选)。
  3. 将文件复制到服务器,例如 C:\Program Files (x86)\DnsUpdaterService\
  4. 在服务器上,请确保您安装了 .NET Framework 4.5 的完整版本 (而不是客户端配置文件版本)。
  5. 在服务器命令提示符下,键入 > **DynamicDnsUpdater.Service.exe** **install**
  6. 编辑 **DynamicDnsUpdater.Service.config** 文件 (这是 App.config) 并更改:
  7.   - 通知电子邮件/用户名/密码/SMTP
  8.   - 日志路径:**c:\Logs\DnsUpdaterService.log** 和 ** c:\Logs\DnsUpdaterError.log**
  9. 编辑 **XmlConfig.xml** (域/提供商配置) 并更新您的域/提供商。
  10. 转到 Windows 服务,找到 "Dynamic DNS Update Service",然后启动。

加密密码/密钥

默认情况下,App.config 中的 smtp 密码和 XmlConfig.xml 中的 SecretKey 未加密,如果您愿意,可以使用默认的 DES3 加密方法加密两者。DES3 是对称的,因此不是很安全,但总比没有好。要做到这一点:

  1. 您可以在 App.config 中将标志 "EnablePasswordEncryption" 设置为 true。
  2. 在 ConfigHelper.EncryptionKey 中更新您自己的加密密钥。
  3. 使用 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

 

© . All rights reserved.