Jの衝動書き日記

さらりーまんSEの日記でございます。

Cのソケットプログラミングメモ

 お仕事でソケットを扱ったので、その時に学んだことをメモとして残しておく。

UDPポートの複数Bindについて

 ソケットでデータを受信する時ソケットを作成してポート番号をbindするが、そのポート番号が既にbind済である場合エラーとなり出来ない。
 だがUDPの場合、作成したソケットに対しsetsockoptでSO_REUSEADDRを1に設定するとこれが可能になる。ただし、そのソケットからデータを読み取れるのは一番最後にそのポートをbindしたソケットのみとなり、ほかのソケットは読み取り時に待機となる。

 例えば、sock1・sock2・sock3の順でソケットをbindした場合、読み取り可能なソケットはsock3のみとなる。sock3をクローズしない限りsock1・sock2からはデータは読み出せない。たとえsock3で読み取りしていない場合でも、sock3がクローズされない限りsock1・sock2はデータを読み出せない。

 また、読み取り可能なソケットをクローズした場合、次に読み取り可能となるのはクローズしたソケットの直前にbindしたソケットになる。

 例えば、sock1・sock2・sock3の順でソケットをbindした場合、sock3→sock2→sock1の順番でクローズするたびに読み取り可能となる。

 (2017.05.18追記)

 上記の話は前提が間違っていた。上記の話は同一プロセス内で行った場合である。複数プロセスにおける同一ポートのbindはそれぞれのプロセスでデータの読み取りができる。

 

複数IF持ち装置におけるソケットの送受信について

受信の場合

 ソケットをbindするとき通常はINADDR_ANYを指定する。この時、指定されたポート宛のパケットを待ち受けるように全てのIFに対して指定したことになる。

 これに対し、特定のIFに対してのみ待ち受けたい場合、bind時にそのIFのIPアドレスを指定することでそれが可能になる。

例)
    netstat -a で見ると待ち受けの指定がわかる
    *:23000 → INADDR_ANY指定
    192.168.1.10:23100 → IPアドレス指定

送信の場合

 通常は、送信先IPアドレスと同じネットワークに属するIFのIPアドレス、それがない場合はデフォルトゲートウェイに設定されたIFのIPアドレスが送信元IPアドレスとして設定される。

 これに対し、送信元IPアドレスを特定のIPアドレスにしたい場合、送信用で作成したソケットをそのIPアドレスでbindすると可能となる(ポート番号は指定しない)。

 

ソケット関連のデータ構造とデバッグ

 ソケットでは、IPアドレスとポート番号は、sockaddr_inで管理する。

struct sockaddr_in{
 short          sin_family;
 unsigned short sin_port;
 struct in_addr sin_addr;
 char           sin_zero[8];
};
ちなみにin_addrの定義は以下
typedef unsigned int uint32_t; (※stdint.h)
typedef uint32_t in_addr_t;
struct in_addr {
  in_addr_t s_addr;
};

 デバッガでポートを確認する場合、これらの値はネットワークバイトオーダ(ビッグエンディアン)に変換済のため、値をもとのホストバイトオーダ(リトルエンディアン)に戻す必要がある。確認のためにはntohs()をデバッガ上で使用する。

 ちなみにJavaのバイトオーダーは基本的にビッグエンディアンであるらしい。

struct sockaddr_in addr;
(gdp) p addr.sin_port
$1 = 24810 (01100000 11101010) 
→ ネットワークオーダ変換済のため、上位と下位の桁がひっくり返っている (gdb) p ntohs(addr.sin_port) $2 = 60000 (11101010 01100000)
→ ホストオーダに変換して表示 これが実際のポート番号 ちなみに、sin_portに60000(バイトオーダ変換なし)と入れると
実際の送信時には24810になる。


 デバッガでIPアドレスを確認したいが、デバッガ上ではinet_ntoa()が使えない(inet_ntoaが返す領域をデバッガが参照できないため? Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6.x86_64と出てたので、これがあれば見れるのかも(未検証))。代わりにinet_ntop()を使う。

(gdb) p addr.sin_addr.s_addr
$3 = 1268257290 (4B98120A:75.152.18.10)
→ ネットワークオーダ変換済のため、上位と下位の桁がひっくり返っている
(gdb) p (void*) malloc(16)
$4 = (void *) 0x602060
→IPアドレスの格納先を確保
(gdb) p (char*)inet_ntop(2,&addr.sin_addr.s_addr,0x602060, 16) 
→ 確保したアドレスを引数に指定
$5 = 0x601010 "10.18.152.75"

 

 ところで、bindなどのソケットを扱う関数はsockaddrを引数として取る。sockaddrは bind等の関数のキャスト用の定義であるため中身を見ても意味はない。

struct sockaddr {
 sa_family_t sa_family;  /* address family, AF_xxx */
 char        sa_data[14];/* 14 bytes of protocol address */
};

 

 デバッグしているソースがsockaddr_inの定義を取り込んでいない場合(netinet/in.hを未include)、sockaddrをsockaddr_inにキャストして見ようとしてもsockaddr_inの定義がないため中身を見ることが出来ない。no typeと出る。

 この場合、アドレスを指定しつつsockaddr_inの中身の型を直接指定することで見ることが出来る。

(gdb) p &addr
$2 = (struct sockaddr *) 0x7fffffffe400
(gdb) p *(short*)0x7fffffffe400 → 先頭はshort sin_family
$3 = 2
(gdb) p ntohs(*(unsigned short *) 0x7fffffffe402) 
→ unsigned short sin_port 先頭からsin_family分アドレスをずらす
$4 = 60000
(gdb) p *(unsigned int *) 0x7fffffffe404
→ struct in_addr(unsigned int) sin_addr sin_port分アドレスをずらす
$5 = 1268257290 (4B98120A:75.152.18.10)

 

 いちいち手で打つのは面倒なのでユーザ定義を使用してみる。

define show_sockaddr_in
  set $port_addr = $arg0+2
  set $ip_addr   = $arg0+4
  set $port = ntohs(*(unsigned short*)$port_addr)
  set $tmp = (void*)malloc(16)
  set $ip = (char*)inet_ntop(2,$ip_addr,$tmp,16)
  printf "sin_family:%d sin_port:%d sin_addr:%s\n", *(short*)$arg0,$port,$ip
  set $f = free($tmp)
end
(gdb) show_sockaddr_in 0x7fffffffe400
sin_family:2 sin_port:60000 sin_addr:10.18.152.75

 

 今はIPv6対応があるためsockaddr_storageを使うことも多いが、同様に上記の方法でsockaddr_storageの中のポート番号とIPアドレスを確認することが出来る。

 ちなみにアドレスと型を指定して見るやり方はソケットに限らず何でも出来る(そのアドレスにあるデータに対して、表現したいデータ形式を指定しているだけなので)。

参考元

http://stackoverflow.com/questions/1816628/printing-ip-addresses-using-gdb