Skip to content

Instantly share code, notes, and snippets.

@ttsuki
Last active December 29, 2015 20:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ttsuki/7723258 to your computer and use it in GitHub Desktop.
Save ttsuki/7723258 to your computer and use it in GitHub Desktop.
ぼくんちのNASで動いているValue Domain DDNS Updater
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using System.Xml.XPath;
namespace ValueDomainDDNSUpdater
{
class Program
{
readonly static string routerHost = "192.168.0.1"; // UPnPルータの場所
readonly static string routerConnection = "WANPPPConn1"; // ルータのインターネット接続名
readonly static string targetDomain = "tu3.jp"; // 管理してるドメイン
readonly static string targetHost = "@"; // 更新対象のホスト。他のサブドメインも対象にするときはcname
readonly static string updatePassword = "xxxxxxxxxxxx"; // 更新用パスワード
static void Main(string[] args)
{
int interval = 60; // デフォルト更新間隔
IPAddress registeredIPAddress = new IPAddress(0);
while (true)
{
Console.Error.Write(DateTime.Now.ToString("[yyyy/MM/dd HH:mm:ss] ") + "Getting my address...");
IPAddress addr = GetExternalAddress(routerHost, routerConnection); // ルータのIPと接続名
if (addr == null)
{
// 失敗
Console.Error.WriteLine("failed.");
}
else if (registeredIPAddress.Equals(addr))
{
// 取得したIPは変わってない。
Console.Error.WriteLine(addr.ToString() + " (not changed)");
}
else
{
// 取得したIPが変わってる。
Console.Error.WriteLine(addr.ToString() + " (changed)");
// DNSレコード更新リクエスト
Console.Error.Write(DateTime.Now.ToString("[yyyy/MM/dd HH:mm:ss] ") + "Updateing DNS Record...");
Exception ex = UpdateValueDomainDDNS(addr, targetDomain, targetHost, updatePassword);
if (ex == null)
{
// 成功
Console.Error.WriteLine("Updated.");
registeredIPAddress = addr;
interval = 60;
}
else
{
// 失敗
Console.Error.WriteLine("failed.");
interval = Math.Min(interval * 2, 3600);
}
}
Thread.Sleep(interval * 1000);
}
}
/// <summary>
/// 指定されたルータにUPnPして、IPアドレス取ってくる
/// </summary>
/// <param name="routerIPAddress">ルータIP</param>
/// <param name="targetConnServiceID">サービスID</param>
/// <returns>外部IPAddress。失敗するとnull。</returns>
static IPAddress GetExternalAddress(string routerIPAddress, string targetConnServiceID)
{
// ルータのデバイス情報のURLを得る。
string deviceUrl = null;
{
byte[] request = Encoding.ASCII.GetBytes(
@"M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: ""ssdp:discover""
MX: 3
ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1");
/*// 素直な方法は素晴らしいが自分のルータ側のIPが必要だったりするのでめどい
var _192_168_0_2 = new System.Net.IPAddress(0x0200A8C0);
var _239_255_255_250 = new System.Net.IPAddress(0xFAFFFFEF);
using (var client = new UdpClient(new IPEndPoint(_192_168_0_2, 0)))
{
client.JoinMulticastGroup(_239_255_255_250);
client.Send(request, request.Length, new IPEndPoint(_239_255_255_250, 1900)));
IPEndPoint from = null;
string ret = Encoding.ASCII.GetString(client.Receive(ref from));
Match m = Regex.Match(ret, @"^location: (.*)$", RegexOptions.IgnoreCase | RegexOptions.Multiline);
if (m.Success) { deviceUrl = m.Groups[1].Value; }
}
/*/
using (var client = new UdpClient())
{
int retry = 3;
do
{
// ルータにダイレクトアタック!!!
client.Send(request, request.Length, routerIPAddress, 1900);
for (int i = 0; i < 100; i++) //
{
try
{
// デバイス情報のURLが書かれた行を抜き出す
IPEndPoint from = null;
string ret = Encoding.ASCII.GetString(client.Receive(ref from));
Match m = Regex.Match(ret, @"^location: (.*)$", RegexOptions.IgnoreCase | RegexOptions.Multiline);
if (m.Success) { deviceUrl = m.Groups[1].Value; break; }
}
catch { }
Thread.Sleep(100);
}
} while (deviceUrl == null && retry-- > 0);
}
//*/
}
// ルータのデバイス情報のURLとれた
//Console.Error.WriteLine("devurl: " + deviceUrl);
if (deviceUrl == null) return null;
// 指定されたServiceIDのコントロールURLを取得する。
string controlUrl = null;
{
try
{
XPathNavigator doc = new XPathDocument(deviceUrl).CreateNavigator();
XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);
nsm.AddNamespace("D", "urn:schemas-upnp-org:device-1-0");
// 子に <serviceId>targetServiceID</serviceId> を持つ <controlURL> を探す
var targetServiceID = "urn:upnp-org:serviceId:" + targetConnServiceID;
var x = doc.SelectSingleNode("//D:serviceId[text()='" + targetServiceID + "']/../D:controlURL", nsm);
if (x != null)
{
// URLの絶対化
controlUrl = new Uri(new Uri(deviceUrl), x.InnerXml).ToString();
}
}
catch (Exception)
{
// リクエストに失敗, XML解釈に失敗, etc
}
}
// コントロールURLがとれた
//Console.Error.WriteLine("contUrl: " + controlUrl);
if (controlUrl == null) return null;
// コントロールURLに対して、GetExternalIPAddress
IPAddress myIp = null;
{
byte[] request = Encoding.ASCII.GetBytes(
@"<?xml version=""1.0""?>
<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
<s:Body>
<u:GetExternalIPAddress xmlns:u=""urn:schemas-upnp-org:service:WANPPPConnection:1"" />
</s:Body>
</s:Envelope>");
try
{
// SOAP。
HttpWebRequest rq = (HttpWebRequest)HttpWebRequest.Create(controlUrl);
rq.Method = "POST";
rq.Headers.Add("SOAPAction", "\"urn:schemas-upnp-org:service:WANPPPConnection:1#GetExternalIPAddress\"");
rq.ContentType = "text/xml; charset=utf-8";
rq.GetRequestStream().Write(request, 0, request.Length);
rq.GetRequestStream().Close();
using (HttpWebResponse rs = (HttpWebResponse)rq.GetResponse())
{
XPathDocument doc = new XPathDocument(rs.GetResponseStream());
XPathNavigator node = doc.CreateNavigator().SelectSingleNode("//NewExternalIPAddress");
// IPアドレスは……
if (node != null)
{
myIp = IPAddress.Parse(node.InnerXml);
}
}
}
catch (Exception)
{
}
}
// IPアドレスが取れた
//Console.Error.WriteLine("ipAddr: " + myIp);
return myIp;
}
/// <summary>
/// ValueDomainのDDNS更新URLを使って、DNSレコードを書き換える
/// </summary>
/// <param name="address">更新後IPアドレス</param>
/// <param name="targetDomain">ドメイン</param>
/// <param name="targetHost">ホスト名</param>
/// <param name="password">更新パスワード</param>
/// <returns>成功したらnull。失敗したら起きた例外</returns>
static Exception UpdateValueDomainDDNS(IPAddress address, string targetDomain, string targetHost, string password)
{
using (WebClient wc = new WebClient())
{
try
{
// ValueDomainへリクエスト
string requestUrl = string.Format("http://dyn.value-domain.com/cgi-bin/dyn.fcg?d={0}&h={1}&p={2}&i={3}",
targetDomain, targetHost, updatePassword, address.ToString());
string resultString = wc.DownloadString(requestUrl);
bool succeeded = resultString.IndexOf("status=0") >= 0; // リクエスト成功?
if (!succeeded)
{
throw new ApplicationException("Update error: " + resultString);
}
}
catch (Exception ex)
{
return ex;
}
}
return null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment