Plan9を理想としたネットワークのファイル抽象化ファイルシステム

Plan9を知ってからリソースをファイルで抽象化するという概念をネットワークにも適用するという考えがLinuxでも実現出来ればと思っていました。
Linuxでどうやったらそれっぽく実装できるか考えた結果、ファイルシステムを作ってプロセスのopen,read,write,closeといったファイルへのシステムコールをフックするような感じでやればいけると思い、簡単ではありますが実装しました。

Github: pfpacket / fuse_planetfs

ファイルシステムを作ると言ってもLinuxカーネルVFSに自分でゴニョゴニョするには、自分の知識が足りなさそうだったのでFUSE(Filesystem in Userspace)を使いました。

どのようにネットワークをファイルで抽象化するかはPlan9Extending UNIX File Abstraction for General-Purpose Networking (pdf)を参考にしました。

つまりネットワークをファイルシステム名前空間マッピングすることで、プロセスからopen,read,writeだけでアクセスできるようにします。

例えばこのファイルシステムを/netにマウントした場合、単純なHTTPクライアントはCで下のようになります。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    int fd, size;
    char buffer[65535] = {0};
    static char const *request = 
                "GET /index.html HTTP/1.1\r\n"
                "Host: www.google.co.jp\r\n"
                "Connection: close\r\n\r\n";

    fd = open("/net/eth/ip/tcp/74.125.235.240:80", O_CREAT | O_RDWR, S_IRWXU);
    if (fd < 0) {
        perror("open");
        return EXIT_FAILURE;
    }

    /* Send a HTTP request */
    size = write(fd, request, strlen(request));
    if (size < 0) {
        perror("write");
        return EXIT_FAILURE;
    }

    /* Receive the response of the server */
    do {
        size = read(fd, buffer, sizeof (buffer));
        if (size < 0) {
            perror("read");
            break;
        }
        /* Display it */
        write(STDOUT_FILENO, buffer, size);
    } while (size != 0);

    close(fd);
    return EXIT_SUCCESS;
}

また単純なTCPサーバーは下のようになります。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void die(char const *prefix)
{
    perror(prefix);
    exit(EXIT_FAILURE);
}

int main(int argc, char **argv)
{
    int fd, client_fd, size;
    char client_path[1024] = {0}, buffer[65535] = {0};

    /* Establish a server waiting on port 10000 */
    fd = open("/net/eth/ip/tcp/*:10000", O_CREAT | O_RDWR, S_IRWXU);
    if (fd < 0)
        die("open");

    while (1) {
        /* Accept a client's connection */
        size = read(fd, client_path, sizeof (client_path));
        if (size < 0)
            die("read");
        printf("accepted client path: %s\n", client_path);

        /* Open a connection to the client */
        client_fd = open(client_path, O_RDWR);
        if (client_fd < 0)
            die("open");

        /* Receive the response of the remote host */
        do {
            size = read(client_fd, buffer, sizeof (buffer));
            if (size < 0) {
                perror("read");
                break;
            }
            /* Display it */
            write(STDOUT_FILENO, buffer, size);
        } while (size != 0);
        close(client_fd);
    }
    close(fd);
    return EXIT_SUCCESS;
}

名前解決もファイル抽象化されています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    int fd, size;
    char buffer[1024];
    static char const *request = "resolve_inet www.google.co.jp";

    fd = open("/net/dns", O_RDWR, S_IRWXU);
    if (fd < 0) {
        perror("open");
        return EXIT_FAILURE;
    }

    /* 
     * Request DNS service. format:
     * 'resolve hostname'       - AF_UNSPEC
     * 'resolve_inet hostname'  - AF_INET
     * 'resolve_inet6 hostname' - AF_INET6
     */
    size = write(fd, request, strlen(request));
    if (size < 0) {
        perror("write");
        return EXIT_FAILURE;
    }

    /* Read /net/dns to get the result */
    for (;;) {
        size = read(fd, buffer, sizeof (buffer));
        if (size <= 0)  // read error or EOF
            break;
        /* Display it */
        printf("%s\n", buffer);
    }

    close(fd);
    return EXIT_SUCCESS;
}

こんな感じでTCPサーバーやパケットキャプチャなどに対応しています。
コード自体はかなり粗削りで、もっと簡単にファイルへの操作を定義、追加できるよう作業中です。
ちなみに作り始めて半分くらい作ってからExtending UNIX File Abstraction for General-Purpose Networkingを知ったのですが、この中ではカーネルモジュールからprocfsで/proc/netfsを作るとことになっています。そっちの方が簡単だと思います。FUSEだとファイルシステムの中を全部作らなければなりません。