UPnP(NATトラバーサル)を使ったグローバルIP取得
Universal Plug and Playとは、機器を接続するだけで利用可能になるプラグ・アンド・プレイ(PnP)を
ネットワークに拡張して、機器を接続しただけでコンピュータネットワークへの参加を可能に概念のプロトコルです。
(なお、技術的に PnPと関係あるものではありません。)
一般にブロードバンドルータなどには、LAN内の特定のホストに外部からアクセスできるようにする方法として
「ポートフォワーディング」の機能を持つものがあります。
この機能は、外部のインターネットから、ルータの特定ポートにアクセスがあった場合に、
それを内部LANの特定ホストに転送(フォワーディング)するようなう機能です。
(ポートフォワーディングは、静的NATや静的IPマスカレードなどと呼ばれることもあります。)
これを利用すれば、家内LANで動作しているサーバーをインターネットに公開して、
家の外からアクセスするような使い方ができるようになります。
ポートフォワーディングの設定を、 UPnP NATトラバーサルを使えば自動的に設定することが可能となります。
UPnPの仕様では 各種機能を持ったデバイスと、そのデバイスに対して制御を行なうコントロールポイントを定義しています。
この定義に基づいて対応のデバイス側では、自身が持つ機能をXML形式で公開しています。
そして これをコントロールする側では、そのをXML形式の情報を参照して、必要な機能を設定をする形態です。
(このような、LANで提供されるサービスの自動検索と利用のための設定には、
Mac OS で使われているBonjourと呼ばれる技術もあります。)
以下で、UPnP NATトラバーサルを使ってLANのWebサーバーをインターネットから利用できるように、ブロードバンドルータを
ポートフォワーディングの設定を行うJavaのプログラム(SetMyHomeGlobalIP2)の例を紹介します。
プログラムの流れ
この例で示すプログラムは次のようになっています。
これらを実現するために次のようなクラスを作成した。(全てstaticで作られる)
SetMyHomeGlobalIP2 |
debugFlag | : boolean | 各メソッドの処理過程の表示(このページの各出力情報は、これを利用) |
serviceType | : String | post_UPnP_Message内のSoapActionを指定する時に利用 |
SCPDURL | : String | このURIのHTTP取得で、関連するアクセス方の詳細を得る。(例:WANPPPConnection.xml) |
controlURL | : String | SOAPのパスを意味します。post_UPnP_Messageで利用 |
serviceId,eventSubURL | : String | getParameteで記憶するが、利用していない |
getGlobaleDeviceLocation() | : String |
【1】の処理で、UPnp対応のルータを探し、そのURI返す。 |
getHttpGet(uri) | : String |
【2】,【3】の処理内で、uriをHTTPのGETで取得し,得た文字列を返す。 |
getParameter(xmlStr , serviceTypeName) | : boolean |
xmlStrのXML文字列からserviceTypeNameのサービス情報取得(【2】のサービスアクセス情報取得用で、上記クラス変数に記憶する。) |
post_UPnP_Message(String ip_Port,String acctionName,String postMsg) |
: String | 各種 SOAP のサービスを実行する時に、共通で使う処理をまとめたメソッド |
post_GetExternalIPAddress(String ip_Port) |
: String | 【4】ブロードバンドルータのWAN側のグローバルIPアドレスを得る。 |
post_AddPortMapping(String ip_Port, String newExternalPort,String newInternalClient, String newInternalPort) |
: boolean | 【5】のポートフォワーディングの追加処理 |
post_DeletePortMapping(String ip_Port, String newExternalPort) |
: boolean | 【5】のポートフォワーディングの削除処理 |
post_GetGenericPortMappingEntry(String ip_Port,String strIndex) |
: String | 【5】のポートフォワーディングの確認処理 |
getLocalHost(int number) |
String | このソフトを起動したPCのIPアドレスを取得する。上記の【5】の設定でnewInternalClientの指定で使われる |
- 【1】グローバルIPコネクションを持つUPnp対応のルータを探す。
そのために、getGlobaleDeviceLocation()メソッドを用意している。この中で、次の文字列を「"239.255.255.250:1900」"でマルチキャストで送信する。
"M-SEARCH * HTTP/1.1\n"+
"HOST: 239.255.255.250:1900\n"+
"MX: 3\n"+
"MAN: \"ssdp:discover\"\n"+
"ST: urn:schemas-upnp-org:service:WANIPConnection:1\n\n"
そうすると、対応ルータが存在すれば、そのルータから例えば次のような応答が返ってくる。
HTTP/1.1 200 OK
Server: Unknown/0.0 UPnP/1.0 Web Server
EXT:
LOCATION: http://192.168.0.1:80/InternetGatewayDevice.xml
CACHE-CONTROL: max-age = 1830
ST: urn:schemas-upnp-org:service:WANIPConnection:1
USN: uuid:aabbf7bcc294-53ed-b6b7-34ca-467ceb0b2acc::urn:schemas-upnp-org:service:WANIPConnection:1
このメソッドの戻り値は、このhttp://192.168.0.1:80/InternetGatewayDevice.xmlで、
これよりどんなサービスがあるの情報が得られます。
-
【2】上記で得られたhttp://192.168.0.1:80/InternetGatewayDevice.xmlを、HTTPで取得します。
この uri を HTTP取得するには、getHttpGet(uri)メソッドで行えます。これで得られる内容は、例えば次の内容である。
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<URLBase>http://192.168.0.1/</URLBase>
<device>
<deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>
<friendlyName>FA11-W5</friendlyName>
<manufacturer>Fujitsu ACCESS Ltd.</manufacturer>
<manufacturerURL>http://jp.fujitsu.com</manufacturerURL>
<modelDescription>UPnP IGD</modelDescription>
<modelName>FA11-W5</modelName>
<modelNumber>R6.71.B18</modelNumber>
<modelURL>http://jp.fujitsu.com</modelURL>
<serialNumber>00000001</serialNumber>
<UDN>uuid:29c87be4-983e-9d80-3938-541383267d5f</UDN>
<UPC>Universal Product Code</UPC>
<iconList>
<icon>
<mimetype>image/png</mimetype>
<width>16</width>
<height>16</height>
<depth>1</depth>
<url>16X16X1.png</url>
</icon>
<icon>
<mimetype>image/png</mimetype>
<width>16</width>
<height>16</height>
<depth>8</depth>
<url>16X16X8.png</url>
</icon>
<icon>
<mimetype>image/png</mimetype>
<width>32</width>
<height>32</height>
<depth>1</depth>
<url>32X32X1.png</url>
</icon>
<icon>
<mimetype>image/png</mimetype>
<width>32</width>
<height>32</height>
<depth>8</depth>
<url>32X32X8.png</url>
</icon>
<icon>
<mimetype>image/png</mimetype>
<width>48</width>
<height>48</height>
<depth>1</depth>
<url>48X48X1.png</url>
</icon>
<icon>
<mimetype>image/png</mimetype>
<width>48</width>
<height>48</height>
<depth>8</depth>
<url>48X48X8.png</url>
</icon>
</iconList>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType>
<serviceId>urn:upnp-org:serviceId:L3Forwarding1</serviceId>
<SCPDURL>Layer3Forwarding.xml</SCPDURL>
<controlURL>/EmWeb/UPnP/Control/1</controlURL>
<eventSubURL>/EmWeb/UPnP/Eventing/1</eventSubURL>
</service>
</serviceList>
<deviceList>
<device>
<deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
<friendlyName>FA11-W5</friendlyName>
<manufacturer>Fujitsu ACCESS Ltd.</manufacturer>
<manufacturerURL>http://jp.fujitsu.com</manufacturerURL>
<modelDescription>UPnP IGD</modelDescription>
<modelName>FA11-W5</modelName>
<modelNumber>R6.71.B18</modelNumber>
<modelURL>http://jp.fujitsu.com</modelURL>
<serialNumber>00000001</serialNumber>
<UDN>uuid:9645a725-31be-bc05-3f11-8c3cba720800</UDN>
<UPC>Universal Product Code</UPC>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
<SCPDURL>WANCommonInterfaceConfig.xml</SCPDURL>
<controlURL>/EmWeb/UPnP/Control/2</controlURL>
<eventSubURL>/EmWeb/UPnP/Eventing/2</eventSubURL>
</service>
</serviceList>
<deviceList>
<device>
<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
<friendlyName>FA11-W5</friendlyName>
<manufacturer>Fujitsu ACCESS Ltd.</manufacturer>
<manufacturerURL>http://jp.fujitsu.com</manufacturerURL>
<modelDescription>UPnP IGD</modelDescription>
<modelName>FA11-W5</modelName>
<modelNumber>R6.71.B18</modelNumber>
<modelURL>http://jp.fujitsu.com</modelURL>
<serialNumber>00000001</serialNumber>
<UDN>uuid:f7bcc294-53ed-b6b7-34ca-467ceb0b2acc</UDN>
<UPC>Universal Product Code</UPC>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:WANDSLLinkConfig:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANDSLLinkC1</serviceId>
<SCPDURL>WANDSLLinkConfig.xml</SCPDURL>
<controlURL>/EmWeb/UPnP/Control/3</controlURL>
<eventSubURL>/EmWeb/UPnP/Eventing/3</eventSubURL>
</service>
<service>
<serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANPPPConn1</serviceId>
<SCPDURL>WANPPPConnection.xml</SCPDURL>
<controlURL>/EmWeb/UPnP/Control/4</controlURL>
<eventSubURL>/EmWeb/UPnP/Eventing/4</eventSubURL>
</service>
<service>
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
<SCPDURL>WANIPConnection.xml</SCPDURL>
<controlURL>/EmWeb/UPnP/Control/5</controlURL>
<eventSubURL>/EmWeb/UPnP/Eventing/5</eventSubURL>
</service>
</serviceList>
</device>
</deviceList>
</device>
</deviceList>
<presentationURL>http://192.168.0.1/</presentationURL>
</device>
</root>
上記の情報で、serviceTypeにのserviceに WANPPPConnectionがある所を探して、
serviceType、serviceId、SCPDURL、controlURL、eventSubURLのアクセス情報を取得する。
これを
getParameter(String services_Str, String serviceTypeName)メソッドを用意して
呼び出すで取得す、各クラス変数に記憶する。ここで記憶する各情報の概要を以下に示す。
タグ名 | 上記の設定例 | 概要 |
serviceType | urn:schemas-upnp-org:service:WANPPPConnection:1 |
post_UPnP_Message内のSoapActionを指定する時に利用 |
serviceId | urn:upnp-org:serviceId:WANPPPConn1 |
(サービスの識別用IDであるが、、ここでは利用していない) |
SCPDURL | WANPPPConnection.xml |
ルータにこのURIのHTTPのGETで取得すると、関連するアクセス方の詳細情報が得られる。 |
controlURL | /EmWeb/UPnP/Control/4 |
SOAPのパスを意味します。この後に、serviceTypeを付けてPOSTする形態をpost_UPnP_Messageメソッドでできるように用意している。 |
eventSubURL | /EmWeb/UPnP/Eventing/4 |
(イベント通知に使うが、ここでは利用していない) |
UPnPは、SOAP(ソープ:Simple Object Access Protocol)は、Webサービスのための、XMLベースのRPCプロトコルです。
-
【3】上記で得たWANPPPConnection.xmlより、各サービスで指定する引数指定に使う詳細キーワードを得る。これは必ずしも必要ない)
HTTP GET http://192.168.0.1:80/WANPPPConnection.xml
上記のリクエストで下記のような情報が得られる。これで、GetGenericPortMappingEntry、
AddPortMapping、DeletePortMapping、GetExternalIPAddress、
などのSoapAction操作名と、 の各引数が分かる。(これら名前がはじめから知っていれば、このようにアクセスして調べる必要はない。)
<?xml version="1.0"?>
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<actionList>
<action>
<name>SetConnectionType</name>
<argumentList>
<argument>
<name>NewConnectionType</name>
<direction>in</direction>
<relatedStateVariable>ConnectionType</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetConnectionTypeInfo</name>
<argumentList>
<argument>
<name>NewConnectionType</name>
<direction>out</direction>
<relatedStateVariable>ConnectionType</relatedStateVariable>
</argument>
<argument>
<name>NewPossibleConnectionTypes</name>
<direction>out</direction>
<relatedStateVariable>PossibleConnectionTypes</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>RequestConnection</name>
</action>
<action>
<name>ForceTermination</name>
</action>
<action>
<name>GetStatusInfo</name>
<argumentList>
<argument>
<name>NewConnectionStatus</name>
<direction>out</direction>
<relatedStateVariable>ConnectionStatus</relatedStateVariable>
</argument>
<argument>
<name>NewLastConnectionError</name>
<direction>out</direction>
<relatedStateVariable>LastConnectionError</relatedStateVariable>
</argument>
<argument>
<name>NewUptime</name>
<direction>out</direction>
<relatedStateVariable>Uptime</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetLinkLayerMaxBitRates</name>
<argumentList>
<argument>
<name>NewUpstreamMaxBitRate</name>
<direction>out</direction>
<relatedStateVariable>UpstreamMaxBitRate</relatedStateVariable>
</argument>
<argument>
<name>NewDownstreamMaxBitRate</name>
<direction>out</direction>
<relatedStateVariable>DownstreamMaxBitRate</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetNATRSIPStatus</name>
<argumentList>
<argument>
<name>NewRSIPAvailable</name>
<direction>out</direction>
<relatedStateVariable>RSIPAvailable</relatedStateVariable>
</argument>
<argument>
<name>NewNATEnabled</name>
<direction>out</direction>
<relatedStateVariable>NATEnabled</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetGenericPortMappingEntry</name>
<argumentList>
<argument>
<name>NewPortMappingIndex</name> <!-- 取得したい情報のインデックス。インデックスは 0, 1, 2 〜 -->
<direction>in</direction>
<relatedStateVariable>PortMappingNumberOfEntries</relatedStateVariable>
</argument>
<argument>
<name>NewRemoteHost</name>
<direction>out</direction>
<relatedStateVariable>RemoteHost</relatedStateVariable>
</argument>
<argument>
<name>NewExternalPort</name>
<direction>out</direction>
<relatedStateVariable>ExternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewProtocol</name>
<direction>out</direction>
<relatedStateVariable>PortMappingProtocol</relatedStateVariable>
</argument>
<argument>
<name>NewInternalPort</name>
<direction>out</direction>
<relatedStateVariable>InternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewInternalClient</name>
<direction>out</direction>
<relatedStateVariable>InternalClient</relatedStateVariable>
</argument>
<argument>
<name>NewEnabled</name>
<direction>out</direction>
<relatedStateVariable>PortMappingEnabled</relatedStateVariable>
</argument>
<argument>
<name>NewPortMappingDescription</name>
<direction>out</direction>
<relatedStateVariable>PortMappingDescription</relatedStateVariable>
</argument>
<argument>
<name>NewLeaseDuration</name>
<direction>out</direction>
<relatedStateVariable>PortMappingLeaseDuration</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetSpecificPortMappingEntry</name>
<argumentList>
<argument>
<name>NewRemoteHost</name>
<direction>in</direction>
<relatedStateVariable>RemoteHost</relatedStateVariable>
</argument>
<argument>
<name>NewExternalPort</name>
<direction>in</direction>
<relatedStateVariable>ExternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewProtocol</name>
<direction>in</direction>
<relatedStateVariable>PortMappingProtocol</relatedStateVariable>
</argument>
<argument>
<name>NewInternalPort</name>
<direction>out</direction>
<relatedStateVariable>InternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewInternalClient</name>
<direction>out</direction>
<relatedStateVariable>InternalClient</relatedStateVariable>
</argument>
<argument>
<name>NewEnabled</name>
<direction>out</direction>
<relatedStateVariable>PortMappingEnabled</relatedStateVariable>
</argument>
<argument>
<name>NewPortMappingDescription</name>
<direction>out</direction>
<relatedStateVariable>PortMappingDescription</relatedStateVariable>
</argument>
<argument>
<name>NewLeaseDuration</name>
<direction>out</direction>
<relatedStateVariable>PortMappingLeaseDuration</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>AddPortMapping</name>
<argumentList>
<argument>
<name>NewRemoteHost</name> <!-- 指定した送信元からのメッセージ(パケット)のみ転送するときに設定する -->
<direction>in</direction>
<relatedStateVariable>RemoteHost</relatedStateVariable>
</argument>
<argument>
<name>NewExternalPort</name> <!-- WAN側のポート 通常、NewInternalPort と同じ値 -->
<direction>in</direction>
<relatedStateVariable>ExternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewProtocol</name> <!-- TCP または UDP -->
<direction>in</direction>
<relatedStateVariable>PortMappingProtocol</relatedStateVariable>
</argument>
<argument>
<name>NewInternalPort</name> <!-- 宛先ポート(転送先PCの受信処理プログラムのポート番号) -->
<direction>in</direction>
<relatedStateVariable>InternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewInternalClient</name> <!-- 宛先アドレス(転送先PCのローカルIPアドレス) -->
<direction>in</direction>
<relatedStateVariable>InternalClient</relatedStateVariable>
</argument>
<argument>
<name>NewEnabled</name> <!--TRUE or FALSE -->
<direction>in</direction>
<relatedStateVariable>PortMappingEnabled</relatedStateVariable>
</argument>
<argument>
<name>NewPortMappingDescription</name> <!--記述欄 -->
<direction>in</direction>
<relatedStateVariable>PortMappingDescription</relatedStateVariable>
</argument>
<argument>
<name>NewLeaseDuration</name> <!-- リース期間(秒)なお、0 のときは無期限 -->
<direction>in</direction>
<relatedStateVariable>PortMappingLeaseDuration</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>DeletePortMapping</name>
<argumentList>
<argument>
<name>NewRemoteHost</name>
<direction>in</direction>
<relatedStateVariable>RemoteHost</relatedStateVariable>
</argument>
<argument>
<name>NewExternalPort</name>
<direction>in</direction>
<relatedStateVariable>ExternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewProtocol</name>
<direction>in</direction>
<relatedStateVariable>PortMappingProtocol</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>GetExternalIPAddress</name>
<argumentList>
<argument>
<name>NewExternalIPAddress</name>
<direction>out</direction>
<relatedStateVariable>ExternalIPAddress</relatedStateVariable>
</argument>
</argumentList>
</action>
<action>
<name>X_getInterfaceInfo</name>
<argumentList>
<argument>
<name>portNo</name>
<direction>out</direction>
<relatedStateVariable>Uptime</relatedStateVariable>
</argument>
<argument>
<name>channelNo</name>
<direction>out</direction>
<relatedStateVariable>Uptime</relatedStateVariable>
</argument>
<argument>
<name>interfaceNo</name>
<direction>out</direction>
<relatedStateVariable>Uptime</relatedStateVariable>
</argument>
<argument>
<name>interfaceType</name>
<direction>out</direction>
<relatedStateVariable>ExternalIPAddress</relatedStateVariable>
</argument>
<argument>
<name>WANServiceInstance</name>
<direction>out</direction>
<relatedStateVariable>ExternalIPAddress</relatedStateVariable>
</argument>
<argument>
<name>LANServiceInstance</name>
<direction>out</direction>
<relatedStateVariable>ExternalIPAddress</relatedStateVariable>
</argument>
<argument>
<name>interfaceObject</name>
<direction>out</direction>
<relatedStateVariable>ExternalIPAddress</relatedStateVariable>
</argument>
<argument>
<name>FWInterfaceObject</name>
<direction>out</direction>
<relatedStateVariable>ExternalIPAddress</relatedStateVariable>
</argument>
<argument>
<name>refCount</name>
<direction>out</direction>
<relatedStateVariable>Uptime</relatedStateVariable>
</argument>
<argument>
<name>interfaceName</name>
<direction>out</direction>
<relatedStateVariable>ExternalIPAddress</relatedStateVariable>
</argument>
</argumentList>
</action>
</actionList>
<serviceStateTable>
<stateVariable sendEvents="no">
<name>ConnectionType</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="yes">
<name>PossibleConnectionTypes</name>
<dataType>string</dataType>
<allowedValueList>
<allowedValue>Unconfigured</allowedValue>
<allowedValue>IP_Routed</allowedValue>
<allowedValue>DHCP_Spoofed</allowedValue>
<allowedValue>PPPoE_Bridged</allowedValue>
<allowedValue>PPTP_Relay</allowedValue>
<allowedValue>L2TP_Relay</allowedValue>
<allowedValue>PPPoE_Relay</allowedValue>
</allowedValueList>
</stateVariable>
<stateVariable sendEvents="yes">
<name>ConnectionStatus</name>
<dataType>string</dataType>
<allowedValueList>
<allowedValue>Unconfigured</allowedValue>
<allowedValue>Connecting</allowedValue>
<allowedValue>Authenticating</allowedValue>
<allowedValue>Connected</allowedValue>
<allowedValue>PendingDisconnect</allowedValue>
<allowedValue>Disconnecting</allowedValue>
<allowedValue>Disconnected</allowedValue>
</allowedValueList>
</stateVariable>
<stateVariable sendEvents="no">
<name>Uptime</name>
<dataType>ui4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>UpstreamMaxBitRate</name>
<dataType>ui4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>DownstreamMaxBitRate</name>
<dataType>ui4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>LastConnectionError</name>
<dataType>string</dataType>
<allowedValueList>
<allowedValue>ERROR_NONE</allowedValue>
<allowedValue>ERROR_ISP_TIME_OUT</allowedValue>
<allowedValue>ERROR_COMMAND_ABORTED</allowedValue>
<allowedValue>ERROR_NOT_ENABLED_FOR_INTERNET</allowedValue>
<allowedValue>ERROR_BAD_PHONE_NUMBER</allowedValue>
<allowedValue>ERROR_USER_DISCONNECT</allowedValue>
<allowedValue>ERROR_ISP_DISCONNECT</allowedValue>
<allowedValue>ERROR_IDLE_DISCONNECT</allowedValue>
<allowedValue>ERROR_FORCED_DISCONNECT</allowedValue>
<allowedValue>ERROR_SERVER_OUT_OF_RESOURCES</allowedValue>
<allowedValue>ERROR_RESTRICTED_LOGON_HOURS</allowedValue>
<allowedValue>ERROR_ACCOUNT_DISABLED</allowedValue>
<allowedValue>ERROR_ACCOUNT_EXPIRED</allowedValue>
<allowedValue>ERROR_PASSWORD_EXPIRED</allowedValue>
<allowedValue>ERROR_AUTHENTICATION_FAILURE</allowedValue>
<allowedValue>ERROR_NO_DIALTONE</allowedValue>
<allowedValue>ERROR_NO_CARRIER</allowedValue>
<allowedValue>ERROR_NO_ANSWER</allowedValue>
<allowedValue>ERROR_LINE_BUSY</allowedValue>
<allowedValue>ERROR_UNSUPPORTED_BITSPERSECOND</allowedValue>
<allowedValue>ERROR_TOO_MANY_LINE_ERRORS</allowedValue>
<allowedValue>ERROR_IP_CONFIGURATION</allowedValue>
<allowedValue>ERROR_UNKNOWN</allowedValue>
</allowedValueList>
</stateVariable>
<stateVariable sendEvents="no">
<name>RSIPAvailable</name>
<dataType>boolean</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>NATEnabled</name>
<dataType>boolean</dataType>
</stateVariable>
<stateVariable sendEvents="yes">
<name>ExternalIPAddress</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="yes">
<name>PortMappingNumberOfEntries</name>
<dataType>ui2</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>PortMappingEnabled</name>
<dataType>boolean</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>PortMappingLeaseDuration</name>
<dataType>ui4</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>RemoteHost</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>ExternalPort</name>
<dataType>ui2</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>InternalPort</name>
<dataType>ui2</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>PortMappingProtocol</name>
<dataType>string</dataType>
<allowedValueList>
<allowedValue>TCP</allowedValue>
<allowedValue>UDP</allowedValue>
</allowedValueList>
</stateVariable>
<stateVariable sendEvents="no">
<name>InternalClient</name>
<dataType>string</dataType>
</stateVariable>
<stateVariable sendEvents="no">
<name>PortMappingDescription</name>
<dataType>string</dataType>
</stateVariable>
</serviceStateTable>
</scpd>
- 【4】上記で得たアクセス方法に従って、ブロードバンドルータのWAN側のグローバルIPアドレスを得る。
POST /EmWeb/UPnP/Control/4/WANPPPConnection HTTP/1.1
SoapAction: urn:schemas-upnp-org:service:WANPPPConnection:1#GetExternalIPAddress
Host: 192.168.0.1:80
Content-Type: text/xml
Content-Length: 338
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:GetExternalIPAddress xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1">
</m:GetExternalIPAddress>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
上記のリクエストを行うと次のようなメッセージが返される。この処理のメソッドがpost_GetExternalIPAddress(String ip_Port)である。
GetExternalIPAddress:HTTP/1.1 200 OK
Server: Unknown/0.0 UPnP/1.0 Web Server
Content-Type: text/xml; charset=utf-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Pragma: no-cache
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"><NewExternalIPAddress>219.98.53.73</NewExternalIPAddress></u:GetExternalIPAddressResponse>
</s:Body>
</s:Envelope>
<NewExternalIPAddress>219.98.53.73</NewExternalIPAddress>の部分で、ルータのWAN側グローバルIPアドレスを得ることができる。
- 【5】のポートマッピングの追加例
ルータのWAN側へ来た8080のポート番号のTCP接続を、192.168.0.71:8080 でマッピングする指定を追加するリクエスト例。
POST /EmWeb/UPnP/Control/4/WANPPPConnection HTTP/1.1
SoapAction: urn:schemas-upnp-org:service:WANPPPConnection:1#AddPortMapping
Host: 192.168.0.1:80
Content-Type: text/xml
Content-Length: 673
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>8080</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>8080</NewInternalPort>
<NewInternalClient>192.168.0.71</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>UPnP 8080 port Mapping</NewPortMappingDescription>
<NewLeaseDuration>0</NewLeaseDuration>
</m:AddPortMapping>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
成功した場合の応答レスポンスを以下に示します。上記の要求と応答受信処理は、post_AddPortMappingメソッドで行う。
HTTP/1.1 200 OK
Server: Unknown/0.0 UPnP/1.0 Web Server
Content-Type: text/xml; charset=utf-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Pragma: no-cache
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"></u:AddPortMappingResponse>
</s:Body>
</s:Envelope>
- 【5】のポートマッピングの削除例
ルータのWAN側に設定されている8080のポートマッピングを削除するリクエスト例。
POST /EmWeb/UPnP/Control/4/WANPPPConnection HTTP/1.1
SoapAction: urn:schemas-upnp-org:service:WANPPPConnection:1#DeletePortMapping
Host: 192.168.0.1:80
Content-Type: text/xml
Content-Length: 438
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:DeletePortMapping xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>8080</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
</m:DeletePortMapping>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
成功した場合の応答レスポンスを以下に示します。上記の要求と応答受信処理は、post_DeletePortMappingメソッドで行う。
HTTP/1.1 200 OK
Server: Unknown/0.0 UPnP/1.0 Web Server
Content-Type: text/xml; charset=utf-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Pragma: no-cache
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:DeletePortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"></u:DeletePortMappingResponse>
</s:Body>
</s:Envelope>
- 【5】のポートマッピングの確認例
ルータのWAN側に設定されているUPnPの設定によるポートマッピングの表で、0からのインデックスを指定して、その情報を得るリクエスト例。
POST /EmWeb/UPnP/Control/4/WANPPPConnection HTTP/1.1
SoapAction: urn:schemas-upnp-org:service:WANPPPConnection:1#GetGenericPortMappingEntry
Host: 192.168.0.1:80
Content-Type: text/xml
Content-Length: 396
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:GetGenericPortMappingEntry xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1">
<NewPortMappingIndex>0</NewPortMappingIndex>
</m:GetGenericPortMappingEntry>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
成功した場合の応答レスポンスを以下に示します。上記の要求と応答受信処理は、post_GetGenericPortMappingEntryメソッドで行う。
HTTP/1.1 200 OK
Server: Unknown/0.0 UPnP/1.0 Web Server
Content-Type: text/xml; charset=utf-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Pragma: no-cache
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetGenericPortMappingEntryResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>8080</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>8080</NewInternalPort>
<NewInternalClient>192.168.0.71</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>UPnP 8080 port Mapping</NewPortMappingDescription>
<NewLeaseDuration>0</NewLeaseDuration>
</u:GetGenericPortMappingEntryResponse>
</s:Body>
</s:Envelope>
各種メソッド
getGlobaleDeviceLocationメソッド
//グローバルIPコネクションを持つUPnp対応のルータを探す
public static String getGlobaleDeviceLocation() throws Exception{
String queryResponse=null;
//グローバルIPコネクションを持つUPnp対応のルータを探す文字列
String query = "M-SEARCH * HTTP/1.1\n"+
"HOST: 239.255.255.250:1900\n"+
"MX: 3\n"+
"MAN: \"ssdp:discover\"\n"+
"ST: urn:schemas-upnp-org:service:WANIPConnection:1\n\n";
//UnpnマルチキャストのIPアドレスとポート
InetAddress inet = InetAddress.getByName("239.255.255.250");
int portNumber = 1900;//ポート番号
MulticastSocket multSock = new MulticastSocket();//マルチキャスト送信用
multSock.setTimeToLive(10);//寿命設定
byte[] buf = query.getBytes("UTF-8");//バイト列に変換
DatagramPacket packet;//マルチキャストソケット生成
multSock.joinGroup(inet); //指定のマルチキャストアドレスのグループに参加★
packet = new DatagramPacket(buf, buf.length, inet, portNumber);//IPアドレス、ポート番号も指定
if(debugFlag) System.out.println(new String(buf , "UTF-8"));
multSock.send(packet);//送信
byte[] data = new byte[1024];
packet = new DatagramPacket(data, data.length);
multSock.setSoTimeout(20000);
multSock.receive(packet);//受信& wait
multSock.close();
int len = packet.getLength();//受信バイト数取得
queryResponse = new String(data, 0, len,"UTF-8");//受信バイト列のデコード
if(debugFlag) {
System.out.println("-----------------------------");
System.out.println(queryResponse);
}
//上記で得られたqueryResponse から、「LOCATION: 」以降の1行を取得します。
// 例えば『Location: http://192.168.0.1:80/DeviceDescription.xml』の行が取得されます。
int idx = queryResponse.toUpperCase().indexOf("LOCATION: ");
if(idx == -1){
if(debugFlag) System.out.println("「LOCATION: 」の取得に失敗しました。");
return null;
}
String loacation = queryResponse.substring(idx + "LOCATION: ".length());
idx = loacation.indexOf("\n");
loacation = loacation.substring(0, idx).trim();
return loacation;
}
getHttpGetメソッド
//uriをHTTPのGETで取得し,得た文字列を返す。
public static String getHttpGet(String uri) throws Exception {
if(debugFlag) System.out.println("============HTTP GET "+uri);
String str="";
URL url = new URL(uri);
HttpURLConnection huc = (HttpURLConnection) url.openConnection();
huc.setRequestMethod("GET");
huc.connect();
InputStreamReader isr = new InputStreamReader(huc.getInputStream(),"UTF-8");
int c;
while((c = isr.read()) != -1){
str+=(char)c;
}
huc.disconnect();
return str;
}
getParameter(String services_Str, String serviceTypeName)メソッド
//引数に<service></service>群がある文字列を指定する。その中に含まれるserviceTypeNameのサービスの各上記情報を取得して設定する。
public static boolean getParameter(String services_Str, String serviceTypeName){
if(debugFlag) System.out.println("++++++++++++++++++++ getParameter( ,"+ serviceTypeName + ")");
serviceTypeName = serviceTypeName.toUpperCase();
//serviceTypeNameがある<service></service>を探し、それを基準とする。
int idx = services_Str.toUpperCase().indexOf(serviceTypeName);
if(idx == -1)return false;
//この<service>内で、<controlURL>を取得する。
idx = services_Str.toUpperCase().lastIndexOf("<SERVICE>", idx);
services_Str = services_Str.substring(idx);//この<service>の文字列を先頭になる文字列を得る
//この<service>内で、<controlURL>を取得する。
idx = services_Str.toUpperCase().indexOf("<SERVICETYPE>");
serviceType = services_Str.substring("<serviceType>".length()+idx);
idx = serviceType.toUpperCase().indexOf("</SERVICETYPE>");
serviceType = serviceType.substring(0,idx);
if(debugFlag) System.out.println("<serviceType>:"+serviceType);
//この<service>内で、<serviceId>を取得する。
idx = services_Str.toUpperCase().indexOf("<SERVICEID>");
if(idx != -1){
serviceId = services_Str.substring( "<serviceId>".length()+idx);
idx = serviceId.toUpperCase().indexOf("</SERVICEID>");
serviceId = serviceId.substring(0,idx);
if(debugFlag) System.out.println("<serviceId>:"+serviceId);
}
//この<service>内で、<SCPDURL>を取得する。
idx = services_Str.toUpperCase().indexOf("<SCPDURL>");
if(idx != -1){
SCPDURL = services_Str.substring("<SCPDURL>".length()+idx);
idx = SCPDURL.toUpperCase().indexOf("</SCPDURL>");
SCPDURL = SCPDURL.substring(0,idx);
if(debugFlag) System.out.println("<SCPDURL>:"+SCPDURL);
}
//この<service>内で、<controlURL>を取得する。
idx = services_Str.toUpperCase().indexOf("<CONTROLURL>");
if(idx != -1){
controlURL = services_Str.substring("<controlURL>".length()+idx);
idx = controlURL.toUpperCase().indexOf("</CONTROLURL>");
controlURL = controlURL.substring(0,idx);
if(debugFlag) System.out.println("<controlURL>:"+controlURL);
}
//この<service>内で、<eventSubURL>を取得する。
idx = services_Str.toUpperCase().indexOf( "<EVENTSUBURL>");
if(idx != -1){
eventSubURL = services_Str.substring("<eventSubURL>".length()+idx);
idx = eventSubURL.toUpperCase().indexOf("</EVENTSUBURL>");
eventSubURL = eventSubURL.substring(0,idx);
if(debugFlag) System.out.println("<eventSubURL>:"+eventSubURL);
}
if(debugFlag) System.out.println("++++++++++++++++++++++++++++++++++++++++++++\n\n");
return true;
}
post_UPnP_Messageメソッド
public static String post_UPnP_Message(String ip_Port, String acctionName,String postMsg)throws Exception
{
byte []postMsgBuf = postMsg.getBytes("UTF-8");
String str = "";//戻り値
String post = "POST "+controlURL+"/WANPPPConnection HTTP/1.1\r\n"+
"SoapAction: " + serviceType + "#"+acctionName+"\r\n"+
"Host: "+ip_Port+"\r\n"+
"Content-Type: text/xml\r\n"+
"Content-Length: " + postMsgBuf.length + "\r\n\r\n";
String uriPath = "http://" + ip_Port + controlURL;
URL url = new URL(uriPath);
if(debugFlag) {
System.out.println("-----URL:"+uriPath);
System.out.println("-----post_UPnP_Message:\n"+post);
System.out.println(postMsg);
}
String ip = null;
int port=80;
int idx = ip_Port.indexOf(":");
if(idx != -1){
ip= ip_Port.substring(0,idx);
port = Integer.parseInt(ip_Port.substring(idx+1));
}
Socket soket = new Socket(ip, port);
OutputStream outStream = soket.getOutputStream();
InputStream is = soket.getInputStream();
byte []buf = post.getBytes();
outStream.write(buf);
outStream.write(postMsgBuf);
outStream.flush();
InputStreamReader isr = new InputStreamReader(is);
int c;
while((c = isr.read()) != -1){
str+=(char)c;
}
soket.close();
if(debugFlag)System.out.println("------------------------ that over.\n");
return str;
}
post_GetExternalIPAddressメソッド
public static String post_GetExternalIPAddress(String ip_Port) throws Exception{
String postMsg = "<?xml version=\"1.0\"?>\r\n"+
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"+
"<SOAP-ENV:Body>\r\n"+
"<m:GetExternalIPAddress xmlns:m=\"" + serviceType +"\">\r\n"+
"</m:GetExternalIPAddress>\r\n"+
"</SOAP-ENV:Body>\r\n"+
"</SOAP-ENV:Envelope>\r\n";
String resposeMsg = post_UPnP_Message(ip_Port, "GetExternalIPAddress", postMsg);
if(debugFlag) System.out.println("-----GetExternalIPAddress:"+resposeMsg);
int idx = resposeMsg.toUpperCase().indexOf("<NEWEXTERNALIPADDRESS>");
resposeMsg = resposeMsg.substring(idx + "<NEWEXTERNALIPADDRESS>".length());
idx = resposeMsg.indexOf("<");
resposeMsg = resposeMsg.substring(0, idx);
return resposeMsg;
}
post_AddPortMappingメソッド
public static boolean post_AddPortMapping(String ip_Port, String newExternalPort,
String newInternalClient, String newInternalPort) throws Exception{
String postMsg = "<?xml version=\"1.0\"?>\r\n"+
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"+
"<SOAP-ENV:Body>\r\n"+
"<m:AddPortMapping xmlns:m=\"" + serviceType +"\">\r\n"+
"<NewRemoteHost></NewRemoteHost>\r\n"+
"<NewExternalPort>"+newExternalPort+"</NewExternalPort>\r\n"+
"<NewProtocol>TCP</NewProtocol>\r\n"+
"<NewInternalPort>"+newInternalPort+"</NewInternalPort>\r\n"+
"<NewInternalClient>"+newInternalClient+"</NewInternalClient>\r\n"+
"<NewEnabled>1</NewEnabled>\r\n"+
"<NewPortMappingDescription>UPnP "+newExternalPort+" port Mapping</NewPortMappingDescription>\r\n"+
"<NewLeaseDuration>0</NewLeaseDuration>\r\n"+
"</m:AddPortMapping>\r\n"+
"</SOAP-ENV:Body>\r\n"+
"</SOAP-ENV:Envelope>\r\n";
String resposeMsg = post_UPnP_Message(ip_Port, "AddPortMapping", postMsg);
if(debugFlag) System.out.println("-----AddPortMapping:"+resposeMsg);
int idx = resposeMsg.toUpperCase().indexOf("\n");
if(idx != -1) resposeMsg = resposeMsg.substring(0,idx).toUpperCase();
idx = resposeMsg.toUpperCase().indexOf("200");//成功の番号がレスポンス先頭行にあるか
return idx != -1;
}
post_DeletePortMappingメソッド
public static boolean post_DeletePortMapping(String ip_Port, String newExternalPort) throws Exception{
String postMsg = "<?xml version=\"1.0\"?>\r\n"+
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"+
"<SOAP-ENV:Body>\r\n"+
"<m:DeletePortMapping xmlns:m=\"" + serviceType +"\">\r\n"+
"<NewRemoteHost></NewRemoteHost>\r\n"+
"<NewExternalPort>"+newExternalPort+"</NewExternalPort>\r\n"+
"<NewProtocol>TCP</NewProtocol>\r\n"+
"</m:DeletePortMapping>\r\n"+
"</SOAP-ENV:Body>\r\n"+
"</SOAP-ENV:Envelope>\r\n";
String resposeMsg = post_UPnP_Message(ip_Port, "DeletePortMapping", postMsg);
if(debugFlag) System.out.println("-----DeletePortMapping:"+resposeMsg);
int idx = resposeMsg.toUpperCase().indexOf("\n");
if(idx != -1) resposeMsg = resposeMsg.substring(0,idx).toUpperCase();
idx = resposeMsg.toUpperCase().indexOf("200");//成功の番号がレスポンス先頭行にあるか
return idx != -1;
}
post_GetGenericPortMappingEntryメソッド
public static String post_GetGenericPortMappingEntry(String ip_Port,String strIndex) throws Exception{
String postMsg = "<?xml version=\"1.0\"?>\r\n"+
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"+
"<SOAP-ENV:Body>\r\n"+
"<m:GetGenericPortMappingEntry xmlns:m=\"" + serviceType +"\">\r\n"+
"<NewPortMappingIndex>"+strIndex+"</NewPortMappingIndex>\r\n"+
"</m:GetGenericPortMappingEntry>\r\n"+
"</SOAP-ENV:Body>\r\n"+
"</SOAP-ENV:Envelope>\r\n";
String resposeMsg = post_UPnP_Message(ip_Port, "GetGenericPortMappingEntry", postMsg);
if(debugFlag) System.out.println("-----GetGenericPortMappingEntry:"+resposeMsg);
return resposeMsg;
}
getLocalHostメソッド
public static String getLocalHost(int number) throws Exception {
String host = null;
Enumeration <NetworkInterface> netSet = NetworkInterface.getNetworkInterfaces();
LOOP: while(netSet.hasMoreElements()){
NetworkInterface netinterface = (NetworkInterface) netSet.nextElement();
Enumeration <InetAddress> inetSet = netinterface.getInetAddresses();
InetAddress inet = InetAddress.getByName("localhost");
while(inetSet.hasMoreElements()){
inet = (InetAddress) inetSet.nextElement();
host = inet.getHostAddress();
if(debugFlag){
System.out.println("--------"+inet.getClass().getName());
System.out.println("IPアドレス:" + host);
System.out.println("IPアドレス:" + inet);
} else if(inet.getClass().getName().equals("java.net.Inet4Address")){
if(--number <= 0) break LOOP;
}
}
}
return host;
}
以上の作成したSetMyHomeGlobalIP2.javaのソースファイル内容は、ここから参照できます。