Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

5.4. 通信アプリケーション

ソケットAPIを使うUDPエコーサーバ(udpecho)を作成して、実際に通信できることを確認します。

バイトオーダー変換関数の追加

ポート番号などはネットワークバイトオーダー(ビッグエンディアン)で扱う必要があります。ユーザ空間向けにhtons()/ntohs()/htonl()/ntohl()user/ulib.cに実装します。

📝 user/ulib.c

...

 sbrklazy(int n)
 {
   return sys_sbrk(n, SBRK_LAZY);
 }
+
+//
+// byte order
+//
+
+static int
+byteorder(void)
+{
+  uint x = 1;
+
+  return *(uchar *)&x ? 1 : 0; // 1 if little endian
+}
+
+static ushort
+byteswap16(ushort v)
+{
+  return (v & 0x00ff) << 8 | (v & 0xff00) >> 8;
+}
+
+static uint
+byteswap32(uint v)
+{
+  return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 |
+         (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24;
+}
+
+ushort
+htons(ushort h)
+{
+  return byteorder() ? byteswap16(h) : h;
+}
+
+ushort
+ntohs(ushort n)
+{
+  return byteorder() ? byteswap16(n) : n;
+}
+
+uint
+htonl(uint h)
+{
+  return byteorder() ? byteswap32(h) : h;
+}
+
+uint
+ntohl(uint n)
+{
+  return byteorder() ? byteswap32(n) : n;
+}

📝 user/user.h

...
 char *sbrk(int);
 char *sbrklazy(int);
+ushort htons(ushort);
+ushort ntohs(ushort);
+uint htonl(uint);
+uint ntohl(uint);
 
 // printf.c

...

ユーザ空間向けソケットヘッダ

ソケットAPIの定数(AF_INETなど)とアドレス構造体(struct sockaddr_inなど)を、ユーザ空間から使えるようにuser/socket.hとして用意します。構造体のレイアウトはカーネル側のkernel/net/sock.hと一致させる必要があります。

📝 user/socket.h

//
// Socket API definitions for user programs.
// Struct layouts must match the kernel's definitions in kernel/net/sock.h.
//

#define PF_UNSPEC  0
#define PF_INET    2
#define PF_INET6  10

#define AF_UNSPEC PF_UNSPEC
#define AF_INET   PF_INET
#define AF_INET6  PF_INET6

#define SOCK_STREAM 1
#define SOCK_DGRAM  2

#define IPPROTO_TCP 6
#define IPPROTO_UDP 17

#define INADDR_ANY 0

struct in_addr {
  uint s_addr;
};

struct sockaddr {
  ushort sa_family;
  char sa_data[14];
};

struct sockaddr_in {
  ushort sin_family;
  ushort sin_port;
  struct in_addr sin_addr;
};

UDPエコーサーバのコード

受信したデータグラムを送信元へそのまま送り返すUDPエコーサーバです。socket()でソケットを作り、bind()でポート7番に割り当て、recvfrom()/sendto()で送受信を繰り返します。

📝 user/udpecho.c

//
// UDP echo server.
//

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/socket.h"
#include "user/user.h"

#define ECHO_PORT 7
#define BUFSIZ 1024

int
main(int argc, char *argv[])
{
  int soc;
  struct sockaddr_in self, peer;
  int peerlen;
  char buf[BUFSIZ];
  int ret;
  uchar *addr;

  soc = socket(AF_INET, SOCK_DGRAM, 0);
  if (soc == -1) {
    printf("socket: failure\n");
    exit(1);
  }
  memset(&self, 0, sizeof(self));
  self.sin_family = AF_INET;
  self.sin_addr.s_addr = INADDR_ANY;
  self.sin_port = htons(ECHO_PORT);
  if (bind(soc, (struct sockaddr *)&self, sizeof(self)) == -1) {
    printf("bind: failure\n");
    close(soc);
    exit(1);
  }
  printf("waiting for message on port %d...\n", ECHO_PORT);
  while (1) {
    peerlen = sizeof(peer);
    ret = recvfrom(soc, buf, sizeof(buf), (struct sockaddr *)&peer, &peerlen);
    if (ret == -1) {
      printf("recvfrom: failure\n");
      break;
    }
    addr = (uchar *)&peer.sin_addr.s_addr;
    printf("%d bytes received from %d.%d.%d.%d:%d\n", ret,
      addr[0], addr[1], addr[2], addr[3], ntohs(peer.sin_port));
    if (sendto(soc, buf, ret, (struct sockaddr *)&peer, sizeof(peer)) == -1) {
      printf("sendto: failure\n");
      break;
    }
  }
  close(soc);
  exit(0);
}

Makefileの修正

udpechoをユーザプログラムのリスト(UPROGS)に追加します。

📝 Makefile

...
 UPROGS=\
...
 	$U/_dorphan\
 	$U/_sync\
+	$U/_udpecho\
 
 fs.img: mkfs/mkfs README $(UPROGS)

動作確認

再ビルドした後、make qemuを実行してxv6を起動します。シェルからudpechoをバックグラウンドで起動します。

$ udpecho &
waiting for message on port 7...

開発環境で別のシェルを開き、ncコマンドで192.0.2.2のポート7番にUDPでメッセージを送ります。

$ echo "hello xv6" | nc -u -w1 192.0.2.2 7
hello xv6

送ったメッセージがそのまま返ってくれば成功です。xv6側にも受信ログが出力されます。

10 bytes received from 192.0.2.1:xxxxx

Note

これでアプリケーションが自作プロトコルスタックを通じてUDP通信できるようになりました。TCP(ストリーム)ソケットへの対応や、pingifconfigといったツールの実装は応用課題で扱います。