PHPでグローバルIPアドレスを取得してieserver.netのDynamicDNSを更新する

# やりたいこと

PHPから家サーバ・プロジェクト(ieserver.net)様提供のDynamicDNSにIPアドレスを登録したい。

Perl版更新スクリプトのサンプルが提供されているので、
それをPHPでリライトする感じでいきたい。

# 注意事項

グローバルIPアドレスの取得のために外部サービスを利用するので、頻繁に実行しないこと。
サービス提供者、他の利用者の迷惑になります。

# 動作確認環境、使用ライブラリ等

- Fedora 24 (Server Edition) 64bit
- PHP 5.6.27

- 外部IPアドレス表示サービス
 - ieserver.net IPアドレス確認画面
 - WTF is my IP?!?!??
 - My External IP address

# Perl版コードの解析

1. IPキャッシュの内容を取得
2. IPアドレス確認URLへアクセスし、自身のグローバルIPを取得
3. もし、グローバルIPアドレスがIPキャッシュの内容と同一か、オールゼロ(取得失敗)であれば、プログラムを終了
4. 登録用スクリプトにアクセスして登録を試みる
5. レスポンスの内容にグローバルIPアドレスが含まれていれば、登録成功したと見なす。

# PHPに書き換え

```php:ddns_update.php
<?php

# 戻り値の定義
define("DDNS_NOCHANGE",    0);
define("DDNS_SUCCESS",     1);
define("DDNS_FAILED",      2);
define("DDNS_IPCHK_FAIL",  3);
define("DDNS_UPDATE_FAIL", 4);

function ddns_update($account, $domain, $password, &$outside_ip){

        $ip_cache = '/tmp/php.ip.cache';

        # 一時ファイルがなければ作る。前回のグローバルIPアドレスが記録される
        if(!file_exists($ip_cache)){ touch($ip_cache); }

        # 現在のグローバルIPアドレスを取得する
        $ip_urls = array(
                "http://ieserver.net/ipcheck.shtml",
                "https://wtfismyip.com/text",
                "https://myexternalip.com/raw",
        );
        $outside_ip = "";
        foreach($ip_urls as $ip_url)
        {
                $outside_ip = trim(file_get_contents($ip_url));
                if($outside_ip != "" AND $outside_ip != "0.0.0.0")  { break; }
        }
        if($outside_ip == "" OR $outside_ip == "0.0.0.0")  { return DDNS_IPCHK_FAIL; }

        # 前回のグローバルIPアドレスを取得する
        $recent_ip = trim(file_get_contents($ip_cache));

        # 現在のIPアドレスが前回のものと同じ場合は、変更なしとして関数を抜ける
        if($outside_ip == $recent_ip) { return DDNS_NOCHANGE; }

        # 登録用スクリプトにアクセスして更新を試みる
        $result = file_get_contents(sprintf(
                'https://ieserver.net/cgi-bin/dip.cgi?username=%1$s&domain=%2$s&password=%3$s&updatehost=1',
                $account, $domain, $password
        ));

        # 戻り値の取得に失敗した場合は、エラーとして関数を抜ける
        if($result == false) { return DDNS_UPDATE_FAIL; }

        # 戻り値に現在のグローバルIPアドレスが含まれていなければ、エラーとして関数を抜ける
        if(strpos($result, $outside_ip) === false) { return DDNS_UPDATE_FAIL; }

        # 一時ファイルに、現在のグローバルIPアドレスを書き込む
        file_put_contents(DDNS_TEMP_FILE, trim($outside_ip), LOCK_EX);

        # 成功として関数を抜ける
        return DDNS_SUCCESS;
}
```


使い方

```php:driver.php
<?php
require 'ddns_update.php';

$result = ddns_update("example", "orz.hm", "password", $ip);

switch($result) {
        case DDNS_NOCHANGE:
                echo "IPアドレスは前回と同じです。\n"; break;

        case DDNS_SUCCESS:
                echo "IPアドレスを更新しました。IPアドレスは ${ip} です。\n"; break;

        case DDNS_IPCHK_FAIL:
        case DDNS_UPDATE_FAIL:
                echo "IPアドレスの更新に失敗しました。\n"; break;
}

```

# 簡単な解説

サンプルのPerlスクリプトをPHPに起こして、ログ書き出しを省いてステータスを取得できるよう手を加えた。
新しいIPアドレスの取得も可能となり、他プログラムとの連携もしやすくなっていると思う。
ログが必要な場合は、関数側か使用する側のどちらかに手を加えればよい。

**20161029追記**
ieserver.netオフィシャルのIPアドレス表示が調子悪い時があるみたいなので
ieserver.netでの取得に失敗した場合は、順番に他サービスを参照することで、IPアドレス取得の信頼性の向上を図ることにした。

具体的な手法としては、配列に格納された各サービスのURLを順番に参照し、
最初に取得できたデータをIPアドレスとして用いる。




# 謝辞

DynamicDNSサービスを提供して頂いている家サーバ・プロジェクト様に深謝の意を表します。
また、グローバルIPアドレス取得サービスを提供して頂いている「wtfismyip.com」および「My External IP address」運営の皆様に感謝の意を表します。