STUNは本来はNATの操作を知るためのプロトコルらしいですが、
難しいことは一旦無視して、とりあえずグローバルIPアドレスを知るために利用してみます。
RFC3489の11. Protocol Detailsを参考にしました。
C++要素はstd::stringとthrow/try/catchくらいしかないので、ちょっと頑張ればC言語にできると思います。
Winsockを使用しています。Linuxなどの環境では適宜移植してください。
STUNを利用してグローバルIPアドレスを知る方法
1. STUNサーバーにBinding Requestを投げる。Attributesはなし、Message LengthもTransaction IDも全て0x00で良い
2. STUNサーバーからBinding Responseが返ってくるはずなので、受信する。
3. メッセージの中からMAPPED-ADDRESS(必須なのであるはず)を探す。
4. MAPPED-ADDRESSからAddressのデータを取得する。
#include <cstdio>
#include <string>
#include <winsock2.h>
std::string get_global_ip(void) {
// サーバー情報
static const char* server_addr="stun.l.google.com";
static const int server_port=19302;
static const unsigned int timeout=2000; // ms
// ソケット関連の変数
SOCKET sock;
sockaddr_in addr;
unsigned int host_buf;
unsigned int* host_ptr_buf[2]={&host_buf,NULL};
unsigned int** host=host_ptr_buf;
/* STUN Message Type = 0x0001 (Binding Request)
* Message Length = 0x0000
* Transaction ID = (All 0x00)
*/
unsigned char send_buf[20]={0x00,0x01};
unsigned char recv_buf[70000];
// UDPのソケットを作成
sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock==INVALID_SOCKET)throw std::string("socket creation failed");
// タイムアウトを設定
if(setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(const char*)&timeout,sizeof(timeout))==SOCKET_ERROR) {
closesocket(sock);
throw std::string("setsockopt failed");
}
// 接続先の情報を設定
addr.sin_family=AF_INET;
addr.sin_port=htons(server_port);
host_buf=inet_addr(server_addr);
if(host_buf==0xffffffff) {
hostent* host_info;
host_info=gethostbyname(server_addr);
if(host_info==NULL) {
closesocket(sock);
throw std::string("gethostbyname failed");
}
host=(unsigned int**)host_info->h_addr_list;
}
while(*host!=NULL) {
int recv_len;
sockaddr src_addr;
int addrlen=sizeof(src_addr);
// アドレスを設定
addr.sin_addr.S_un.S_addr=**(host++);
// メッセージを送信
if(sendto(sock,(const char*)send_buf,sizeof(send_buf),0,(struct sockaddr*)&addr,sizeof(addr))==sizeof(send_buf)) {
// メッセージを受信
if((recv_len=recvfrom(sock,(char*)recv_buf,sizeof(recv_buf),0,&src_addr,&addrlen))>=32) {
// それっぽい長さのメッセージ
if(recv_buf[0]==0x01 && recv_buf[1]==0x01) {
// Binding Response
int length=(recv_buf[2]<<8)|recv_buf[3];
if(recv_len<20+length) {
closesocket(sock);
throw std::string("bad Message Length");
}
unsigned char* now_message=&recv_buf[20];
while(length>0) {
int now_id=(now_message[0]<<8)|now_message[1];
int now_length=(now_message[2]<<8)|now_message[3];
if(now_length+4>length) {
closesocket(sock);
throw std::string("bad Length");
}
if(now_id==0x0001) {
char buffer[1024];
// MAPPED-ADDRESS
if(now_message[5]!=0x01) {
closesocket(sock);
throw std::string("unsupported address family");
}
sprintf(buffer,"%d.%d.%d.%d",
now_message[8],now_message[9],now_message[10],now_message[11]);
closesocket(sock);
return std::string(buffer);
}
now_message+=now_length+4;
length-=now_length+4;
if(length>0 && length<4) {
closesocket(sock);
throw std::string("bad Message Length (Attribute too short)");
}
}
closesocket(sock);
throw std::string("MAPPED-ADDRESS not found");
} else if(recv_buf[0]==0x01 && recv_buf[1]==0x11) {
// Binding Error Response
closesocket(sock);
throw std::string("received Binding Error Response");
} else {
closesocket(sock);
throw std::string("received unknown response");
}
}
}
}
closesocket(sock);
throw std::string("no response fron server");
}
int main(void) {
WSADATA wsaData;
WSAStartup(MAKEWORD(2,0),&wsaData);
try {
std::string addr=get_global_ip();
printf("addr = %s\n",addr.c_str());
} catch(std::string e) {
printf("ERROR: %s\n",e.c_str());
}
WSACleanup();
}
参考文献(サイト)