Powershell を使って、専用線の向こうにあるマスタ DNSサーバ からレコードを読み込んで、社内LANローカル DNSサーバ の レコード を定期的にチェック・ 更新 する仕組みを組んでみました。
CCASS専用線とターミナル
香港の證券商には、中央結算系統(CCASS)から専用線が延びており、担当者はそこへ繋がるクローズドなターミナル(CCASSから割当てられた静的IPアドレスの設定された、単なるWindows PC)を使って、日々の決算処理を行っています。
この担当者は、決算処理以外の業務にもPCが必要で、時にそれら2台のPC間でデータのやり取りが必要な際は、USBメモリを使わなければならないといった弊害もありました。
ルーティングによる論理的制御
規模が大きくなるにつれ、物理的にネットワークを分けることの煩わしさから、数年前より専用線も一度社内ルータへ取り込み、ルーティングによりトラフィックを制御するようにしています。
そして担当者側のPCも統合し、1台のPCに社内LANとCCASS用の2つのIPアドレスを持たせて、どちらのネットワークへもアクセス可能な状態にしています。
条件付きフォワーダー
CCASSターミナルはブラウザからインターネットには公開されていない、 www.ccass.com へアクセスして證券の決算処理を進めるわけですが、その名前解決はCCASSネットワーク内にあるDNSサーバが担っています。
そこで、社内LANのローカルDNSサーバに条件付きフォワーダーを設定し、このドメインの名前解決リクエストはCCASS側のDNSサーバへ転送するよう、仕向けていました。
数年間この仕様で問題無く機能していたのが、あるときより名前解決が出来なくなってしまいました。
CCASS側のDNSを相手に、nslookupで直接名前解決するには問題はありませんが、社内LANのローカルDNSサーバ経由のフォワーディングでは REJECTED のレスポンスと共に失敗することから、CCASS側DNSサーバかファイヤウォールのセキュリティ仕様が厳しくなったようです。
1 2 |
[CCASS DNS] <----------------------- [PC] NSLOOKUP SUCCESS! [CCASS DNS] --x-- [LOCAL DNS] <----- [PC] NSLOOKUP REJECTED! |
ローカルDNSサーバにゾーン作成
フォワーディングを受け付けてもらえなくなったので、ひとまず社内LANのローカルDNSサーバが自前で名前解決できるように、正引きゾーンを作成しました(OSはWindows Server 2012)。
ローカルセカンダリDNSサーバへ転送設定
社内LANにはもう1機DNSサーバが稼働しているので、ゾーン設定の済んだプライマリサーバからセカンダリへ転送するような、主従関係を両者の間に構築します。
プライマリ側はプロパティの転送タブで、セカンダリへの転送を許可します。
さらにレコードが更新された場合のセカンダリへの通知を設定。
そしてセカンダリサーバ上で、プライマリと同じ名前のセカンダリゾーンを作成すれば完成です。
次に、このレコードのオリジナルを有するCCASSのDNSサーバからレコードを読み取り、ローカルDNSサーバに作った同じレコードを適宜更新する仕組みを、Poweshellを使って組んでみます。
Powershell : DNSレコードの取得
nslookup のように、PowershellでDNSサーバからレコードを取得するには、Resolve-DnsNameコマンドレットを使います。
1 2 3 4 |
PS C:\> Resolve-DnsName www.ccass.com -Server 10.243.1.1 -Type A -QuickTImeout Name Type TTL Section IPAddress ---- ---- --- ------- --------- www.ccass.com A 0 Answer 10.129.3.3 |
オブジェクト指向なこのコマンドレットの良いところは、特定の要素を取り出せること。例えばIP Addressだけを取り出したいなら、以下のようにコマンドを発行するだけです。
1 2 |
PS C:\> (Resolve-DnsName www.ccass.com -Server 10.243.1.1 -Type A -QuickTImeout).IPAddress 10.129.3.3 |
Powershell : DNSレコードの内容を書き込み
対してPowershellでDNSサーバのレコード内容を編集するにはまず、Get-DnsServerResourceRecordコマンドレットで現在のレコードをオブジェクトとして取得、それを編集の後、Set-DnsServerResourceRecordコマンドレットへ渡す手順になります。
この時、 Set-DnsServerResourceRecord は更新前・更新後双方のレコードオブジェクトを必要とするので、 Get-DnsServerResourceRecord を実行する際、ワンライナーで一気に2個取得すると、更新時にエラーになります。
1 2 3 4 5 6 7 8 9 10 |
PS C:\> $new = $old = Get-DnsServerResourceRecord -ComputerName 192.168.2.8 -ZoneName "ccass.com" -Name "www" -RRType "A" PS C:\> $new.RecordData.IPv4Address = "10.129.1.1" PS C:\> Set-DnsServerResourceRecord -NewInputObject $new -OldInputObject $old -ComputerName 192.168.2.8 -ZoneName "ccass.com" -Passthru Set-DnsServerResourceRecord : Resource record in OldInputObject not found in ccass.com zone on 192.168.2.8 server. At line:1 char:1 + Set-DnsServerResourceRecord -NewInputObject $new -OldInputObject $old ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (192.168.2.8:root/Microsoft/...rResourceRecord) [Set-DnsServerResourceRe cord], CimException + FullyQualifiedErrorId : WIN32 9714,Set-DnsServerResourceRecord |
これを回避するには、原始的ですが Get-DnsServerResourceRecord を2回に分けてそれぞれ実行するのが確実でした。
(一度取得したオブジェクトをクローンする手もあるようですが、Poweshellのバージョンに依っては機能しないとの指摘もネット上に散見されます。)
1 2 3 4 5 6 7 |
PS C:\> $new = Get-DnsServerResourceRecord -ComputerName 192.168.2.8 -ZoneName "ccass.com" -Name "www" -RRType "A" PS C:\> $old = Get-DnsServerResourceRecord -ComputerName 192.168.2.8 -ZoneName "ccass.com" -Name "www" -RRType "A" PS C:\> $new.RecordData.IPv4Address = "10.129.1.1" PS C:\> Set-DnsServerResourceRecord -NewInputObject $new -OldInputObject $old -ComputerName 192.168.2.8 -ZoneName "ccass.com" -Passthru HostName RecordType Timestamp TimeToLive RecordData -------- ---------- --------- ---------- ---------- www A 0 01:00:00 10.129.1.1 |
DNSレコード読込・書込スクリプト
以上で必要な個々のアクションは確認できたので、これらを元に以下のステップで動作する自動化スクリプトを組みました。
- CCASSのDNSサーバにあるレコードのIPアドレスを読み取り。
- ローカルDNSサーバの同レコードのIPアドレスを読み取り。
- 取得した2つのIPアドレスを比較。
- 差異のある場合、ローカルDNSサーバのレコードを更新。
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 |
# ----------------- # CCASS DNS UPDATER # ----------------- ## Vars $DateFormat = "[yyyy/MM/dd HH:mm:ss]" $strZoneName = "ccass.com" $strRecName = "www" $strFQDN = $strRecName + "." + $strZoneName $ccassDNS = "10.243.1.1" $localDNS = "192.168.2.8" $MasterIP = "" $LocalIP = "" ## Get Master Record try { $ErrorActionPreference = "silentlycontinue" $MasterIP = (Resolve-DnsName $strFQDN -Server $ccassDNS -Type A -QuickTImeout).IPAddress $ErrorActionPreference = "continue" } catch { # IRREGULAR CASE $ErrorMessage = $_.Exception_Message Write-Host (Get-Date -Format $DateFormat) "Unexpected Response from CCASS DNS Server, Aborted." exit } # IP Address Casual Validation if (-! [Bool]($MasterIP -as [IPAddress])) { Write-Host (Get-Date -Format $DateFormat) "Invalid Response from CCASS DNS Server, Aborted." exit } ## Get Local Current Record try { $ErrorActionPreference = "silentlycontinue" $LocalIP = (Resolve-DnsName $strFQDN -Server $localDNS -Type A).IPAddress $ErrorActionPreference = "continue" } catch { # IRREGULAR CASE $ErrorMessage = $_.Exception_Message Write-Host (Get-Date -Format $DateFormat) "Unexpected Response from Local DNS Server, Aborted." exit } # IP Address Casual Validation if (-! [Bool]($LocalIP -as [IPAddress])) { Write-Host (Get-Date -Format $DateFormat) "Invalid Response from CCASS DNS Server, Aborted." exit } ## Compare & Update Local Record #$MasterIP = "10.129.1.1" # :DUMMY FOR TEST if ($MasterIP -eq $LocalIP) { Write-Host (Get-Date -Format $DateFormat) "Record Unchanged : $MasterIP" } else { Write-Host (Get-Date -Format $DateFormat) "New IP Address Found on Master DNS Server" Write-Host (Get-Date -Format $DateFormat) "Updating Local Record from $LocalIP to $MasterIP" #$new = $old = Get-DnsServerResourceRecord -ComputerName $localDNS -ZoneName $strZoneName -Name $strRecName -RRType "A" $new = Get-DnsServerResourceRecord -ComputerName $localDNS -ZoneName $strZoneName -Name $strRecName -RRType "A" $old = Get-DnsServerResourceRecord -ComputerName $localDNS -ZoneName $strZoneName -Name $strRecName -RRType "A" $new.RecordData.IPv4Address = $MasterIP try { Set-DnsServerResourceRecord -NewInputObject $new -OldInputObject $old -ComputerName $localDNS -ZoneName $strZoneName -Passthru } catch { # UPDATE FAILURE $ErrorMessage = $_.Exception_Message Write-Host (Get-Date -Format $DateFormat) "Local Record Update Failed, Aborted." exit } # Confirm Updated $LocalIP = (Resolve-DnsName $strFQDN -Server $localDNS -Type A).IPAddress Write-Host (Get-Date -Format $DateFormat) "Local DNS Record Updated to $LocalIP" } |
予めローカルDNSのレコードをダミーデータに書き換えた上で作成したスクリプトを実行し、期待通りに動作することを確認しました。
1 2 3 4 5 6 7 |
PS C:\> .\ccassDNS_updater.ps1 [2022/08/31 15:27:00] New IP Address Found on Master DNS Server [2022/08/31 15:27:00] Updating Local Record from 10.129.1.1 to 10.129.3.3 HostName RecordType Timestamp TimeToLive RecordData -------- ---------- --------- ---------- ---------- www A 0 01:00:00 10.129.3.3 [2022/08/31 15:27:00] Local DNS Record Updated to 10.129.3.3 |
CCASS側DNSサーバのレコードに変化が無い場合は、何もせず終了します。
1 2 |
PS C:\> .\ccassDNS_updater.ps1 [2022/08/31 15:22:13] Record Unchanged : 10.129.3.3 |
タスクスケジューラへ登録
出来上がったスクリプトをローカルDNSサーバへ配置し、以下の要領でタスクスケジューラへ登録します。
1 2 |
Command : Powershell.exe Arguments : -command C:\ccassDNS_updater.ps1 *>> C:\ccassDNS_updater.log |
Arguments の後方で実行結果をログファイルに出力するように仕向けていますが、単に >> としただけでは何も取り出せないので調べてみると、 *>> とする必要があるとのことでした。
タスクの実行頻度は平日の日中半日の間、毎時1回としておきました。
しばらくしてログファイルを確認してみましたが、そう頻繁に変化する要素でもないので、基本的に変化なしで終わっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[2022/08/30 17:30:00] Record Unchanged : 10.129.3.3 [2022/08/30 18:30:01] Record Unchanged : 10.129.3.3 [2022/08/30 19:30:01] Record Unchanged : 10.129.3.3 [2022/08/31 07:30:04] Record Unchanged : 10.129.3.3 [2022/08/31 08:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 09:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 10:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 11:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 12:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 13:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 14:30:01] Record Unchanged : 10.129.3.3 [2022/08/31 15:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 16:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 17:30:01] Record Unchanged : 10.129.3.3 [2022/08/31 18:30:00] Record Unchanged : 10.129.3.3 [2022/08/31 19:30:01] Record Unchanged : 10.129.3.3 [2022/09/01 07:30:04] Record Unchanged : 10.129.3.3 [2022/09/01 08:30:00] Record Unchanged : 10.129.3.3 [2022/09/01 09:30:00] Record Unchanged : 10.129.3.3 [2022/09/01 10:30:00] Record Unchanged : 10.129.3.3 [2022/09/01 11:30:00] Record Unchanged : 10.129.3.3 [2022/09/01 12:30:00] Record Unchanged : 10.129.3.3 [2022/09/01 13:30:00] Record Unchanged : 10.129.3.3 |
今回のきっかけとなったDNS転送拒否問題は、実は系列の他の證券商では発生していないとの報告を受けており、「どうしてうちだけ?!」とますます不可解なのですが、もし今後発生した際にはこの解決方法が役に立ちそうです。