PF_INETとAF_INETの微妙な違い

C言語についての話です。C言語でネットワークプログラムを作成するときにソケットを利用します。ソケットAPIを利用するときの書式は以下です。

int socket(int protocolFamily, int type, int protocol)

ソケットAPIは汎用的になるよう設計されたため、いくつかのプロトコルファミリーをサポートします。第一パラメータの protocolFamilyにはどのプロトコルファミリーを使用するのかを指定します。プロトコルファミリーには以下のようなものがあります。接頭辞のPFはプロトコルファミリーの略です。

PF_UNIX, PF_LOCAL・・・Unix上でのローカル通信
PF_INET・・・IPv4での通信
PF_INET6・・・IPv6での通信
PF_IPX・・・IPXでの通信
PF_APPLETALK・・・AppleTalkでの通信
PF_PACKET・・・データリンク層での通信

今から20〜30年ぐらい前はOSI参照モデルのネットワーク層としてIPのほかに、ノベル社のネットワークOSで使用されたIPX、AppleのMacintoshで使用されたAppleTalkなどがありました。PF_IPX や PF_APPLETALK はそれらのプロトコルを使用する際にソケットパラメータに指定しました。インターネットが普及してTCP/IPがネットワーク/トランスポート層のデファクトスタンダートになり、IPXやAppleTalkを見かけることは今はなくなってしまいました。

第二パラメータの type ですが、ここにはソケットの種類を指定します。

SOCK_STREAM・・・ストリームソケット
SOCK_DGRAM・・・データグラムソケット

ストリームソケットは信頼性があり双方向に接続されたバイトストリームでの通信の提供、データグラムソケットはベストエフォート型の固定最大長メッセージの通信を提供します。

第三パラメータの protocol ですが、エンドツーエンドで使用される固有のプロトコルを指定します。以下のものがあります。

IPPROTO_TCP・・・TCPでの通信
IPPROTO_UDP・・・UDPでの通信

第三パラメータにゼロを指定した場合はOSが自動的に適切なプロトコル(通常はストリームソケットの場合はIPPROTO_TCP、データグラムソケットの場合はIPPROTO_UDP)を設定してくれます。ただ、もともとのソケットの設計思想ではストリームソケット(もしくは、データグラムソケット)を実現するプロトコルは複数あってもよくて、そのなかのどれを使うかを第三パラメータに設定するという考えです。例えば、TCP以外で双方向で信頼性を保証する何か新しいプロトコルが登場した場合は、第二パラメータにSOCK_STREAM、第三パラメータに新しいプロトコルの設定、となります。

実際は、TCP、UDP以外に新しいプロトコルは出てきておらず、またインターネットのルーティングはIPで行われているためTCP/IP、UDP/IPとなり、ソケットのパラメータの組み合わせはほぼ決まっています。

PF_INET+SOCK_STREAM+IPPROTO_TCP・・TCP/IPv4
PF_INET+SOCK_DGRAM+IPPROTO_UDP・・UDP/IPv4

ここまでプロトコルの話をしたのですが、通信を行うためには相手のネットワークアドレスが必要です。C言語ではアドレスをsockaddr構造体に格納して利用します。sockaddr構造体は以下のようになっています。

struct sockaddr
{
 unsigned short sa_family;
 char sa_data[14];
}

sa_family にはアドレスファミリーの指定、sa_data には実際のアドレス情報の格納をします。プロトコルによってアドレッシングの方法は異なるので、sa_dataの使われ方もプロトコルごとに差異がでます。そのためプロトコルごとに使いやすくなるよう、それぞれのプロトコルに特化した構造体が用意されています。PF_UNIXの場合は sockaddr_un構造体、PF_INETの場合は sockaddr_in構造体、PF_INET6の場合は sockaddr_in6構造体です。以下にIPv4で利用されるsockaddr_in構造体を示します。

struct sockaddr_in
{
 unsigned short sin_family;
 unsigned short sin_port;
 struct in_addr sin_addr;
 char sin_zero[8];
}

sin_family にはアドレスファミリー(IPv4の場合は AF_INET)を指定し、sin_port と sin_addr にはそれぞれポート番号とIPアドレスを格納します。sin_zero[8]は未使用です。sockaddr_in構造体は汎用的なsockaddr構造体をIPv4用に再定義(sa_data[14] を sin_port、sin_addr、sin_zero[8] に分割)したものとなります。なお、AF_INETの接頭辞であるAFはアドレスファミリーの略です。

ここまでで、PF_INETはプロトコルファミリーを表し、AF_INETはアドレスファミリーを表すことを書いてみました。ここで重要なことは「プロトコルファミリーとアドレスファミリーをわけて考えている」ということです。あるプロトコルファミリーがあって、そのプロトコルファミリーのアドレス指定方法は1つでも、その後拡張されて複数になったとしても構わないということです。IPv4の場合は、XXX.XXX.XXX.XXXの32ビットで表す形式1種類(AF_INET)しかないのですが、それが別のアドレッシング方法が出来たとしても(その場合、そのアドレッシング方法に対して、AF_INET_2などとして)ソケットAPIは対応できるということです。

現状はIPv4に対して複数のアドレス指定方式はないので、PF_INET と AF_INET は同じ値で定義されています。ですので、プロトコルファミリーにAF_INETを指定しても問題なくプログラムは動くのですけれど。

2020/2/20追記
IPv4とIPv6は互換性がありません。なのでプロトコルファミリーは別々となっています。もしIPv6がIPv4と互換性があり、IPプロトコルのアドレス指定方法が32ビットだけでなく128ビットの指定方法でも機能したのであれば、PF_INETとAF_INET6の組み合わせも出来たんだろうと思います(そういうことだと思う)。

ubuntuでFirefoxのTLS通信をキャプチャする。

ブラウザにFirefoxを使っている場合、TLS通信をキャプチャできる方法があるので書いてみます。試した環境は以下です。

OS:ubuntu 18.04 LTS
ブラウザ:Firefox Quantum
キャプチャツール:Wireshark(Version 2.6.3)

まず、TLSの通信で使う鍵をファイルに書き出すように環境変数を設定します。設定する環境変数は「SSLKEYLOGFILE」です。コンソール画面を立ち上げて、以下のようにします。

$ export SSLKEYLOGFILE=/home/user/sslkey.log
$ env | grep SSLKEYLOGFILE
SSLKEYLOGFILE=/home/user/sslkey.log

※「/home/user/sslkey.log」は任意の場所、ファイル名で構いません。

Firefoxをコンソールからコマンドで立ち上げます。

$ which firefox
/usr/bin/firefox
$ /usr/bin/firefox

Firefoxが立ち上がり、コンソールは待ち状態になります。別ウィンドウでコンソールを開いて、SSLKEYLOGFILE が書き出されていることを確認します。

$ ls -l /home/user/sslkey.log

[広告]

ここまででキャプチャする準備が整いました。ここからはWiresharkの設定をします。Wiresharkを起動して、上部の「編集」メニューから「設定..」を選択します。設定画面が表示されるので左ウィンドウから「Protocols」を選び、そのなかの「SSL」を選択します。

(Pre)-Master-Secret log filename に SSLKEYLOGFILE の場所を指定します。これでキャプチャを開始するとTLS通信の中身が解析できるようになります。

注意点です。
マウス操作でFirefoxを立ち上げてもSSLKEYLOGFILEで指定したファイルには書き込まれません。コンソールで環境変数を設定した上で、コマンドでFirefoxを立ち上げてください。

なお、Firefoxをマウスを使って閉じるとコンソール画面では「接続が相手からリセットされました」となりますが、気にしなくても良いでしょう。