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の組み合わせも出来たんだろうと思います(そういうことだと思う)。

最後に、、、
ここに書いたことは「TCP/IPソケットプログラミング C言語編」という書籍の情報をもとに、少しアレンジさせていただきました。

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です