Plan9を理想としたネットワークのファイル抽象化ファイルシステム
Plan9を知ってからリソースをファイルで抽象化するという概念をネットワークにも適用するという考えがLinuxでも実現出来ればと思っていました。
Linuxでどうやったらそれっぽく実装できるか考えた結果、ファイルシステムを作ってプロセスのopen,read,write,closeといったファイルへのシステムコールをフックするような感じでやればいけると思い、簡単ではありますが実装しました。
Github: pfpacket / fuse_planetfs
ファイルシステムを作ると言ってもLinuxカーネルのVFSに自分でゴニョゴニョするには、自分の知識が足りなさそうだったのでFUSE(Filesystem in Userspace)を使いました。
どのようにネットワークをファイルで抽象化するかはPlan9とExtending 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だとファイルシステムの中を全部作らなければなりません。