Last active
September 21, 2025 09:04
-
-
Save miminashi/f42d8a47de59f9e53b5e6684db83792c to your computer and use it in GitHub Desktop.
UDPでイーサネットフレームをカプセル化するだけのインチキVPN by Gemini
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 簡易VPNプログラム (simple_vpn) | |
| 概要 | |
| このプログラムは、VPNの基本的な原理(L2 over UDP)を学習するために作成された、非常にシンプルなVPNもどきです。 | |
| 指定した2つのホスト間で、仮想的なイーサネットセグメントを構築します。 | |
| 具体的には、片方のホストのTAPデバイス(仮想ネットワークインターフェース)を通過するイーサネットフレームをキャプチャし、UDPパケットに詰めて(カプセル化して)もう片方のホストに送信します。受信側は、UDPパケットから元のイーサネットフレームを取り出し、自身のTAPデバイスに書き込みます。これにより、あたかも2つのホストが同じLANに接続されているかのように通信できます。 | |
| 注意: このプログラムには認証も暗号化も実装されていません。通信内容はすべて平文でネットワーク上を流れます。学習目的でのみ使用してください。 | |
| 動作環境 | |
| * Linux | |
| * Cコンパイラ (gccなど) | |
| コンパイル方法 | |
| gcc -o simple_vpn simple_vpn.c | |
| 使い方 | |
| このプログラムを実行するには、2台のホスト(ホストA, ホストB)が必要です。 | |
| 以下の手順は、両方のホストで root 権限で実行する必要があります。 | |
| 1. ネットワーク設定 | |
| ホストA | |
| (物理IPアドレスが 192.168.1.10 の場合) | |
| # TAPデバイスを作成 | |
| # (プログラムが自動で作成しますが、事前に手動で作成・設定することも可能です) | |
| # ip tuntap add dev tap0 mode tap user $(whoami) | |
| # TAPデバイスにIPアドレスを割り当てる | |
| ip addr add 10.0.0.1/24 dev tap0 | |
| # TAPデバイスを有効化する | |
| ip link set tap0 up | |
| ホストB | |
| (物理IPアドレスが 192.168.1.20 の場合) | |
| # TAPデバイスにIPアドレスを割り当てる | |
| ip addr add 10.0.0.2/24 dev tap0 | |
| # TAPデバイスを有効化する | |
| ip link set tap0 up | |
| *10.0.0.0/24 の部分は、既存のネットワークと重複しないプライベートIPアドレス空間を任意に選んでください。 | |
| 2. プログラムの実行 | |
| 作成した simple_vpn を実行します。コマンドライン引数には、相手ホストの物理IPアドレスを指定します。 | |
| ホストA | |
| 相手(ホストB)のIPアドレス 192.168.1.20 を指定します。 | |
| sudo ./simple_vpn 192.168.1.20 | |
| ホストB | |
| 相手(ホストA)のIPアドレス 192.168.1.10 を指定します。 | |
| sudo ./simple_vpn 192.168.1.10 | |
| sudo が必要なのは、TAPデバイス /dev/net/tun にアクセスするために特権が必要だからです。 | |
| 3. 動作確認 | |
| プログラムが両方のホストで起動したら、新しいターミナルを開き、TAPデバイスに割り当てたIPアドレスに対して ping を実行します。 | |
| ホストAからホストBへ | |
| ping 10.0.0.2 | |
| ホストBからホストAへ | |
| ping 10.0.0.1 | |
| ping の応答があれば、仮想的なネットワークが正しく構築されています。simple_vpn を実行しているターミナルには、パケットを送受信するたびにログが表示されます。 | |
| 仕組みのポイント | |
| * TAPデバイス: tap0 は、OSカーネルが提供する仮想的なネットワークインターフェースです。このインターフェースに書き込まれたデータはイーサネットフレームとして扱われ、このインターフェースから読み出されるデータもイーサネットフレームです。 | |
| * カプセル化: プログラムはtap0からイーサネットフレームを丸ごと読み込み、それをUDPパケットのペイロード(データ部分)として相手に送信します。 | |
| * select()システムコール: tap0からの入力と、UDPソケットからの入力を同時に待ち受ける(I/O多重化)ためにselect()を使用しています。これにより、効率的に双方向の通信を実現しています。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <net/if.h> | |
| #include <linux/if_tun.h> | |
| #include <sys/types.h> | |
| #include <sys/socket.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <arpa/inet.h> | |
| #include <sys/select.h> | |
| #include <errno.h> | |
| #define BUFSIZE 2000 | |
| #define PORT 5555 | |
| #define TAP_DEVICE_NAME "tap0" | |
| /** | |
| * @brief TAPデバイスを作成し、ファイルディスクリプタを返す | |
| * @param dev デバイス名 (e.g., "tap0") | |
| * @return 成功した場合はファイルディスクリプタ、失敗した場合は-1 | |
| */ | |
| int tap_alloc(char *dev) { | |
| struct ifreq ifr; | |
| int fd, err; | |
| char *clonedev = "/dev/net/tun"; | |
| // /dev/net/tunキャラクタデバイスを開く | |
| if ((fd = open(clonedev, O_RDWR)) < 0) { | |
| perror("Opening /dev/net/tun"); | |
| return fd; | |
| } | |
| // ifreq構造体をゼロクリア | |
| memset(&ifr, 0, sizeof(ifr)); | |
| /* | |
| * IFF_TAP: TAPデバイス(イーサネットフレーム)を指定 | |
| * IFF_NO_PI: パケット情報ヘッダを付けない | |
| */ | |
| ifr.ifr_flags = IFF_TAP | IFF_NO_PI; | |
| // デバイス名をifr構造体にコピー | |
| if (*dev) { | |
| strncpy(ifr.ifr_name, dev, IFNAMSIZ); | |
| } | |
| // ioctlでカーネルにTAPデバイスの作成を要求 | |
| if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) { | |
| perror("ioctl(TUNSETIFF)"); | |
| close(fd); | |
| return err; | |
| } | |
| // 実際に作成されたデバイス名がifr.ifr_nameに返ってくるのでコピー | |
| strcpy(dev, ifr.ifr_name); | |
| return fd; | |
| } | |
| int main(int argc, char *argv[]) { | |
| int tap_fd, sock_fd; | |
| struct sockaddr_in local, remote; | |
| char tap_name[IFNAMSIZ]; | |
| unsigned char buffer[BUFSIZE]; | |
| // --- コマンドライン引数のチェック --- | |
| if (argc != 2) { | |
| fprintf(stderr, "Usage: %s <remote_ip>\n", argv[0]); | |
| exit(1); | |
| } | |
| char *remote_ip = argv[1]; | |
| // --- TAPデバイスの初期化 --- | |
| strcpy(tap_name, TAP_DEVICE_NAME); | |
| tap_fd = tap_alloc(tap_name); | |
| if (tap_fd < 0) { | |
| fprintf(stderr, "Failed to create TAP device.\n"); | |
| exit(1); | |
| } | |
| printf("Successfully created TAP device: %s\n", tap_name); | |
| // --- UDPソケットの初期化 --- | |
| // ソケット作成 | |
| if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { | |
| perror("socket()"); | |
| exit(1); | |
| } | |
| // 自ホストのアドレス設定 | |
| memset(&local, 0, sizeof(local)); | |
| local.sin_family = AF_INET; | |
| local.sin_addr.s_addr = htonl(INADDR_ANY); | |
| local.sin_port = htons(PORT); | |
| // ソケットにアドレスをバインド | |
| if (bind(sock_fd, (struct sockaddr*)&local, sizeof(local)) < 0) { | |
| perror("bind()"); | |
| exit(1); | |
| } | |
| // 相手ホストのアドレス設定 | |
| memset(&remote, 0, sizeof(remote)); | |
| remote.sin_family = AF_INET; | |
| remote.sin_port = htons(PORT); | |
| if (inet_aton(remote_ip, &remote.sin_addr) == 0) { | |
| fprintf(stderr, "Invalid remote IP address\n"); | |
| exit(1); | |
| } | |
| printf("Connecting to remote peer at %s:%d\n", remote_ip, PORT); | |
| // --- メインループ (I/O多重化) --- | |
| while (1) { | |
| fd_set read_fds; | |
| int max_fd; | |
| FD_ZERO(&read_fds); | |
| FD_SET(tap_fd, &read_fds); | |
| FD_SET(sock_fd, &read_fds); | |
| max_fd = (tap_fd > sock_fd) ? tap_fd : sock_fd; | |
| // tapデバイスかUDPソケットのどちらかでデータが読み込み可能になるまで待機 | |
| if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) { | |
| perror("select()"); | |
| exit(1); | |
| } | |
| // --- TAPデバイスから読み込み -> UDPソケットへ書き込み --- | |
| if (FD_ISSET(tap_fd, &read_fds)) { | |
| int nread = read(tap_fd, buffer, BUFSIZE); | |
| if (nread < 0) { | |
| perror("read() from TAP"); | |
| exit(1); | |
| } | |
| // 読み込んだイーサネットフレームをUDPで相手に送信 | |
| int nwrite = sendto(sock_fd, buffer, nread, 0, (struct sockaddr*)&remote, sizeof(remote)); | |
| if (nwrite < 0) { | |
| perror("sendto()"); | |
| exit(1); | |
| } | |
| printf("TAP -> UDP: %d bytes sent\n", nwrite); | |
| } | |
| // --- UDPソケットから読み込み -> TAPデバイスへ書き込み --- | |
| if (FD_ISSET(sock_fd, &read_fds)) { | |
| socklen_t remote_len = sizeof(remote); | |
| int nread = recvfrom(sock_fd, buffer, BUFSIZE, 0, NULL, NULL); | |
| if (nread < 0) { | |
| perror("recvfrom()"); | |
| exit(1); | |
| } | |
| // 受信したデータをTAPデバイスに書き込む | |
| int nwrite = write(tap_fd, buffer, nread); | |
| if (nwrite < 0) { | |
| perror("write() to TAP"); | |
| exit(1); | |
| } | |
| printf("UDP -> TAP: %d bytes written\n", nwrite); | |
| } | |
| } | |
| close(sock_fd); | |
| close(tap_fd); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment