OS/2 の TCP/IP って…

なんかこういう書き込みがあったので、せっかくだからやってみました。
とりあえず ioctl で SIOCGARP かければ、(TCP/IP にバインドされた)ネットワークインターフェースの MAC アドレスは取得できるようです。しかし流儀が古風すぎてネット上のわかりやすいところにそれっぽいサンプルが見当たらないよ(SIOCGARP でぐぐってもなんか期待はずれというか)。
結局「UNIX ネットワークプログラミング」をべさべさとめくらざるを得ない…。


で、こんな感じで書いてみたり。くやしいので Linux(と Cygwin)でも動くようにしてみた。
ほんとは SIOCGIFFLAGS を発行してインターフェースの状態を確認しないといけないような気がしますが、めんどくさいのでやってません。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__OS2__) && !defined(__EMX__)
# include <types.h>
#else
# include <sys/types.h>
# include <unistd.h>
#endif
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <net/if.h>
#if defined(SIOCGARP)
# include <net/if_arp.h>
#endif

/* quick check (has TCPIP32 on OS/2) */
#if defined __OS2__
# if AF_INET6 && !defined(TCPV40HDRS)
#  define HAVE_INET_NTOP 1
#  define HAVE_SOCKADDR_SA_LEN 1
   /* not defined in os2tk and gcc335csd3-klibc... */
#  ifndef INET6_ADDRSTRLEN
#   define INET6_ADDRSTRLEN 46
#  endif
# endif
  /* not defined in os2tk...? */
# ifndef INET_ADDRSTRLEN
#  define INET_ADDRSTRLEN 16
# endif
#elif defined __linux__
# define HAVE_INET_NTOP 1
#elif defined __CYGWIN__
# define HAVE_INET_NTOP 1
#else
# define HAVE_INET_NTOP 1
# define HAVE_SOCKADDR_SA_LEN 1
#endif


static
const char *
my_sockaddr_in_ntop(const struct sockaddr_in *sin)
{
#if HAVE_INET_NTOP
  static char buf[INET6_ADDRSTRLEN];
  return inet_ntop(sin->sin_family, &(sin->sin_addr), buf, sizeof(buf));
#else
  return inet_ntoa(sin->sin_addr);
#endif
}

static
const char *
#if defined(SIOCGIFHWADDR)
my_inet_ntop_mac(const struct ifreq *ifr)  /* linux & cygwin */
{
  const unsigned char *p = (const unsigned char *)ifr->ifr_hwaddr.sa_data;
#elif defined(SIOCGARP)
my_inet_ntop_mac(const struct arpreq *arp)
{
  const unsigned char *p = (const unsigned char *)arp->arp_ha.sa_data;
#else
# error not supported...
#endif
  static char buf[65];
  
  sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X",
                p[0],p[1],p[2],p[3],p[4],p[5]
         );
  return buf;
}


int
main(void)
{
  char  buf[16384];
  struct ifconf ifc;
  int i, numnic, fd;
  int rc;
  
  fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (fd == -1) {
    fprintf(stderr, "socket err\n");
    exit(1);
  }
  memset(buf, 0, sizeof(buf));
  memset(&ifc, 0, sizeof(ifc));
  ifc.ifc_req = (struct ifreq *)buf;
  ifc.ifc_len = sizeof(buf);
  errno = 0;
  rc = ioctl(fd, SIOCGIFCONF, &ifc);
  if (rc == -1 || errno == EINVAL) {
    fprintf(stderr, "ioctl SIOCGIFCONF err\n");
    exit(1);
  }
  
  for(i=0, numnic=0; i<ifc.ifc_len; ) {
    struct ifreq  *ifr;
#if defined(SIOCGIFHWADDR)
    struct ifreq req;
#elif defined(SIOCGARP)
    struct arpreq req;
#endif
    
    ifr = (struct ifreq *)(buf + i);
    switch (ifr->ifr_addr.sa_family) {
      case AF_INET:
        printf("%3d \"%s\" ", numnic, ifr->ifr_name);
        printf("AF_INET %s", my_sockaddr_in_ntop((struct sockaddr_in *)&ifr->ifr_addr));
#if defined(SIOCGIFHWADDR)
        memcpy(&req, ifr, sizeof(req));
        rc = ioctl(fd, SIOCGIFHWADDR, &req);  /* linux */
#elif defined(SIOCGARP)
        memset(&req, 0, sizeof(req));
        memcpy(&req.arp_pa, &ifr->ifr_addr, sizeof(struct sockaddr_in));
        rc = ioctl(fd, SIOCGARP, &req);
#else
# error not supported...
#endif
        if (rc >= 0) {
          printf("  %s", my_inet_ntop_mac(&req));
        }
        printf("\n");
        break;
    default:
        printf("%3d \"%s\" family=%d\n", numnic, ifr->ifr_name, ifr->ifr_addr.sa_family);
      break;
    }
    ++numnic;
#if HAVE_SOCKADDR_SA_LEN
    i += ifr->ifr_addr.sa_len <= sizeof(struct sockaddr) ?
      sizeof(struct ifreq) :
      sizeof(struct ifreq) - sizeof(struct sockaddr) + ifr->ifr_addr.sa_len;
#else
    i += sizeof(struct ifreq);
#endif
  }
  
  return 0;
}

コンパイルgcc だと gcc test.c -lsocket 、OpenWatcom の場合は wcl386 -bt=os2 test.c とかそんな感じで。

以下、(主に OS/2 の TCPIP に関する)個人的なメモ:

  • tcpip32 のほうは sockaddr の sa_len が定義されている。古い tcpipv4 にはない。
  • gcc じゃないコンパイラ(ツールキット遵守のヘッダ構成)を使うときは sys/types.h のかわりに types.h をインクルードする。OpenWatcom 標準の sys/types.h だと型定義が足りない。めんどくせえ…。
    OS/2 以外への移植性を黙殺していいなら types.h だけインクルードすればいいのだろうか。
  • GCC 3.3.5 csd3 のヘッダには INET6_ADDRSTRLEN が定義されてないっぽい。いやそりゃ IPv6 なんて OS/2 はサポートしてませんけど…csd5 だとどうだったか、後で確認しないと。
  • しかしツールキットのヘッダだと INET_ADDRSTRLEN さえも定義されていないっぽい。えー…。
  • なんか OpenWatcom は tcpip32 しか使えないんだよなあ。コンパイラ同梱のヘッダにも古い定義はないし、tcpip32 化していない(so32 と tcp32 の)Warp V4で TCP/IP したい時はどうすればいいのだ。Warp V4 時代のツールキットぶち込んでランタイムライブラリを再構築とかすんの?
  • ためしに Cygwin で実行してみたらインターフェース名が uuid 化しててキモかった。

…なんか今気づいたけど netstat -n の出力結果を取り込むほうが安直で楽な気がしてきた。