通信時のソースアドレスを取得する

f:id:dfukunaga:20170417005216p:plain

Linuxです。

なんて言えばよいのかわからなかったのですが、ip route get xxx.xxx.xxx.xxxと打ったときに出てくる ソース(送信元)アドレスをC/C++内で取得したかった。すぐに思いついた方法は、

  • popenを使ってip route getを実行し、標準出力を取得
  • 一時的にソケットを作って送信元IPアドレスを取得

です。以下のようなサンプルコードを書きました。

get route source address

どちらも同じ出力が得られますが、手元の環境で試したところ、 1000回実行するのにかかる時間がpopenを使った方法では約1秒、 ソケットを使った方法では約0.005秒だった。(コードの改善点はあるかもしれませんが。)

他にもっと良い方法があれば教えてください!

ソケットに結び付けられたアドレスを取得する

f:id:dfukunaga:20170412233423p:plain

Linuxです。

netstatしたときのLocal AddressとForeign AddressにあたるものをC/C++で取得したかった。 Local Addressはgetsockname()、Foreign Addressはgetpeername()を使えば取得できるらしい。 C++でサンプルを書くと以下のような感じ。

gist6c0628fe0ad9790fe7a1fbc5cba9b13c

vagrant@vagrant-ubuntu-trusty-64:~$ ./a.out 192.168.0.2 2551
Local Address:   192.168.0.1:46402
Foreign Address: 192.168.0.2:2551

ちゃんと取得できてますね!(↑のサンプルコードを使うときは対応するアドレスをlistenしてください)

ZeroMQのheartbeatを試してみた

ZeroMQのversion 4.2からheartbeat機能が追加されました。 元々コネクションの切断は検知してzmq_socket_monitor(3)で通知してくれたのですが、 TCPエラーを吐かずにコネクションが死んだり、Out of Memory等でプロセスが止まったりした場合は検知できないので heartbeat機能が追加されたようです。(参考:ZeroMQのRFC

heartbeatに関するソケットのオプションは以下の3つです。

  • ZMQ_HEARTBEAT_IVL
  • ZMQ_HEARTBEAT_TIMEOUT
  • ZMQ_HEARTBEAT_TTL

ZMQ_HEARTBEAT_IVLミリ秒の間隔でpingを送り、ZMQ_HEARTBEAT_TIMEOUTミリ秒経っても pongが返ってこなかったらコネクションが死んだとみなすという感じです。また、pongを返す側では ZMQ_HEARTBEAT_TTLミリ秒経っても次のpingが来なかったらコネクションが死んだとみなします。 ZMQ_HEARTBEAT_IVLを設定することで初めてheartbeatが動作します。

試しに以下のような設定をしてみます。基本的にどちらか片方(connect側もしくはbind側)のソケットにだけ設定しておけばOKだと思います。

int interval = 100, timeout = 1000, ttl = 1000;
zmq_setsockopt(sock, ZMQ_HEARTBEAT_IVL, &interval, sizeof(int));
zmq_setsockopt(sock, ZMQ_HEARTBEAT_TIMEOUT, &timeout, sizeof(int));
zmq_setsockopt(sock, ZMQ_HEARTBEAT_TTL, &ttl, sizeof(int));
zmq_setsockopt(sock, ZMQ_HANDSHAKE_IVL, &timeout, sizeof(int));

ZMQ_HEARTBEAT_TTLZMQ_HEARTBEAT_IVLよりも十分大きい値にしておく必要があります。 そうしないと接続が頻繁に切れてしまいます。ZMQ_HEARTBEAT_TIMEOUTも短すぎると 偽陽性が増えてしまうので注意です。

また、ZMQ_HANDSHAKE_IVLを設定しています。ZeroMQではソケットが接続した後に ソケットの設定情報を交換する(これをhandshakeと呼ぶ)そうなのですが、止まっているソケットに再接続した際に ZMQ_HEARTBEAT_TIMEOUTよりもZMQ_HANDSHAKE_IVLが優先されるため、両方共同じ値にしておきます。 ZMQ_HANDSHAKE_IVLのデフォルト値は30秒なので、これを設定しないと ZMQ_HEARTBEAT_TIMEOUTをどれだけ短くしても再接続時に30秒待ってしまいます。

実際にこんな感じのコードを書いて動かします。 bind/connectが完了した時点で、connect側のプロセスにSIGTSTPを送るとbind側のzmq_socket_monitor(3)から ZMQ_EVENT_DISCONNECTEDが通知されます。逆に、bind側のプロセスにSIGTSTPを送ると、connect側の zmq_socket_monitor(3)からZMQ_EVENT_DISCONNECTEDが通知された後に、再接続を試みます。 bind側のlistenが残っているからか、再接続は成功しますが、handshakeが失敗して再びZMQ_EVENT_DISCONNECTEDが 通知されるというような挙動になります。

また、heartbeatを有効にしても前回の調査同様、unbind/disconnect時には 相手側のソケットには何も通知されません。disconnectしても再接続しようとするし、unbind/disconnectとは何なのだろう…。 とりあえずソケットを切断するときは必ずcloseした方が良さそうです。

※ZeroMQ v4.2.1を使用

ZeroMQのzmq_socket_monitor(3)の挙動を調べた

ZeroMQのzmq_socket_monitor(3)の挙動を調べてみました。 bindするソケットとconnectするソケットを用意し、以下の順に処理して zmq_socket_monitor(3)でどのようなイベントが通知されるか確認しました。

  1. create (bind/connect socket)
  2. bind
  3. connect
  4. disconnect
  5. close (connect socket)
  6. create (connect socket)
  7. connect
  8. unbind
  9. close (bind socket)

上記の処理を1秒間隔で行い、イベントの出力を確認します。 使用したソースコードこちらに載せました。

bind側の出力

vagrant@vagrant-ubuntu-trusty-64:~$ ./a.out bind tcp://0.0.0.0:2551
[0.000] [main] Create...
[1.000] [main] Bind...
[1.010] [event] number: 8, value: 12, address: tcp://0.0.0.0:2551
[2.011] [event] number: 32, value: 13, address: tcp://0.0.0.0:2551
[4.014] [event] number: 512, value: 13, address: tcp://0.0.0.0:2551
[6.019] [event] number: 32, value: 13, address: tcp://0.0.0.0:2551
[7.001] [main] Unbind...
[7.009] [event] number: 128, value: 12, address: tcp://0.0.0.0:2551
[8.003] [main] Close...
[8.004] [event] number: 1024, value: 0, address:
[9.004] [main] Finish...

connect側の出力

vagrant@vagrant-ubuntu-trusty-64:~$ ./a.out connect tcp://192.168.0.1:2551
[0.000] [main] Create...
[2.000] [main] Connect...
[2.003] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551
[2.004] [event] number: 1, value: 12, address: tcp://192.168.0.1:2551
[3.002] [main] Disconnect...
[4.005] [main] Close...
[4.006] [event] number: 1024, value: 0, address:
[5.012] [main] Create...
[6.014] [main] Connect...
[6.015] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551
[6.016] [event] number: 1, value: 11, address: tcp://192.168.0.1:2551
[8.013] [event] number: 512, value: 11, address: tcp://192.168.0.1:2551
[8.014] [event] number: 4, value: 183, address: tcp://192.168.0.1:2551
[8.196] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551
[8.197] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551
[8.197] [event] number: 4, value: 125, address: tcp://192.168.0.1:2551
[8.316] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551
[8.317] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551
[8.317] [event] number: 4, value: 141, address: tcp://192.168.0.1:2551
[8.466] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551
[8.466] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551
[8.467] [event] number: 4, value: 186, address: tcp://192.168.0.1:2551
[8.652] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551
[8.653] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551
[8.654] [event] number: 4, value: 197, address: tcp://192.168.0.1:2551
[8.848] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551
[8.849] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551
[8.849] [event] number: 4, value: 110, address: tcp://192.168.0.1:2551
[8.957] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551
[8.958] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551
[8.959] [event] number: 4, value: 138, address: tcp://192.168.0.1:2551
[9.015] [main] Close...
[9.015] [main] Finish...

一番左側の列は経過時間を表しています。 numberはZeroMQで定義されているイベント番号で、以下のようになっています。

/*  Socket transport events (tcp and ipc only)                                */
#define ZMQ_EVENT_CONNECTED 1
#define ZMQ_EVENT_CONNECT_DELAYED 2
#define ZMQ_EVENT_CONNECT_RETRIED 4

#define ZMQ_EVENT_LISTENING 8
#define ZMQ_EVENT_BIND_FAILED 16

#define ZMQ_EVENT_ACCEPTED 32
#define ZMQ_EVENT_ACCEPT_FAILED 64

#define ZMQ_EVENT_CLOSED 128
#define ZMQ_EVENT_CLOSE_FAILED 256
#define ZMQ_EVENT_DISCONNECTED 512
#define ZMQ_EVENT_MONITOR_STOPPED 1024

この結果からわかることは、

  • bindした直後にbind側でZMQ_EVENT_LISTENINGが通知される
  • connectした直後にbind側でZMQ_EVENT_ACCEPTED、connect側でZMQ_EVENT_CONNECT_DELAYEDZMQ_EVENT_CONNECTEDが通知される
  • disconnectしてもbind側、connect側共に何も通知されない
  • unbindするとbind側にはZMQ_EVENT_CLOSEDが通知されるが、connect側には何も通知されない
  • connect側をcloseするとbind側にZMQ_EVENT_DISCONNECTEDが通知される
  • bind側をcloseするとconnect側にZMQ_EVENT_DISCONNECTEDが通知され、connect側は再接続を試みる
  • connect側が再接続を試みている間は、ZMQ_EVENT_CLOSEDZMQ_EVENT_CONNECT_RETRIEDZMQ_EVENT_CONNECT_DELAYEDが通知され続ける
  • ソケットをcloseすると、ZMQ_EVENT_MONITOR_STOPPEDが通知される

ちなみに、プロセスを強制的に落とした場合はcloseしたときと同じ挙動になり、 ZMQ_EVENT_DISCONNECTEDが通知されます。

今回は通知するイベントをZMQ_EVENT_ALLにしましたが、 実際に使うときには適切なイベントを選択して通知した方が良いかと思います。

※ZeroMQ v4.2.1を使用