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(ストリーム)ソケットへの対応や、
ping・ifconfigといったツールの実装は応用課題で扱います。