web-dev-qa-db-ja.com

Cソケットsockaddrおよびsockaddr_storageの背後にある理由

Cソケットのconnect()bind()などの関数を見て、sockaddr構造体へのポインターを取得していることに気付きました。私は読んでいて、アプリケーションをAFに依存しないようにするには、_sockaddr_storage_構造体ポインターを使用してsockaddrポインターにキャストすると便利です。 。

私が疑問に思っているのは、sockaddrポインターを要求するconnect()bind()のような関数が、期待している1つ。もちろん、提供する構造体のサイズを渡しますが、_struct *sockaddr_にキャストしたより大きな構造体へのポインターからIPアドレスを取得するために関数が使用する実際の構文は何ですか?

おそらく、私はOOP言語から来たからでしょうが、それは一種のハックで少し厄介なようです。

47
Matt Vaughan

struct sockaddrへのポインターを期待する関数は、struct sockaddr_storageへのポインターを送信するときに、おそらくsockaddrに送信するポインターを型キャストします。そのようにして、彼らはstruct sockaddrであるかのようにそれにアクセスします。

struct sockaddr_storageは、struct sockaddr_instruct sockaddr_in6の両方に適合するように設計されています

独自のstruct sockaddrを作成するのではなく、使用しているIPバージョンに応じて、通常struct sockaddr_inまたはstruct sockaddr_in6を作成します。使用するIPバージョンを把握しようとするのを避けるために、どちらかを保持できるstruct sockaddr_storageを使用できます。これは、connect()、bind()などの関数によってstruct sockaddrに型キャストされ、その方法でアクセスされます。

以下にこれらの構造体をすべて見ることができます(パディングは、アライメントのために実装固有です):

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


struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // zero this if you want to
};


struct sockaddr_in6 {
    u_int16_t       sin6_family;   // address family, AF_INET6
    u_int16_t       sin6_port;     // port number, Network Byte Order
    u_int32_t       sin6_flowinfo; // IPv6 flow information
    struct in6_addr sin6_addr;     // IPv6 address
    u_int32_t       sin6_scope_id; // Scope ID
};

struct sockaddr_storage {
    sa_family_t  ss_family;     // address family

    // all this is padding, implementation specific, ignore it:
    char      __ss_pad1[_SS_PAD1SIZE];
    int64_t   __ss_align;
    char      __ss_pad2[_SS_PAD2SIZE];
};

ご覧のとおり、関数がIPv4アドレスを予期している場合、最初の4バイトを読み取るだけです(構造体のタイプがstruct sockaddrであると想定しているためです。

45
theprole

C++では、少なくとも1つの仮想関数を持つクラスにTAGが与えられます。このタグを使用すると、クラスが派生する任意のクラスにdynamic_cast<>()できます。 TAGは、dynamic_cast<>()を機能させるものです。多かれ少なかれ、これは数字または文字列です...

Cでは、構造に限定されています。ただし、構造にTAGを割り当てることもできます。実際、theproleが彼の回答で投稿されたすべての構造を見ると、それらはすべて2バイト(符号なしのショート)で始まり、アドレスのファミリーと呼ばれるものを表していることがわかります。これは、構造が何であるか、したがってサイズ、フィールドなどを正確に定義します。

したがって、次のようなことができます。

_int bind(int fd, struct sockaddr *in, socklen_t len)
{
  switch(in->sa_family)
  {
  case AF_INET:
    if(len < sizeof(struct sockaddr_in))
    {
      errno = EINVAL; // wrong size
      return -1;
    }
    {
      struct sockaddr_in *p = (struct sockaddr_in *) in;
      ...
    }
    break;

  case AF_INET6:
    if(len < sizeof(struct sockaddr_in6))
    {
      errno = EINVAL; // wrong size
      return -1;
    }
    {
      struct sockaddr_in6 *p = (struct sockaddr_in6 *) in;
      ...
    }
    break;

  [...other cases...]

  default:
    errno = EINVAL; // family not supported
    return -1;

  }
}
_

ご覧のとおり、関数はlenパラメーターをチェックして、長さが期待される構造に適合するのに十分であることを確認できます。したがって、reinterpret_cast<>()(C++で呼び出される場合)あなたのポインター。構造内のデータが正しいかどうかは、呼び出し元次第です。そのための選択肢はあまりありません。これらの関数は、データを使用する前にあらゆる種類のものを検証し、問題が検出されるたびに-1とerrnoを返すことが期待されています。

したがって、実際には、_struct sockaddr_in_または_struct sockaddr_in6_があり、それを_struct sockaddr_にキャスト(再解釈)し、bind()関数(およびその他)がそのポインターをキャストします_struct sockaddr_in_メンバーをチェックし、サイズを確認した後の_struct sockaddr_in6_または_sa_family_.

10
Alexis Wilke