blog
/
Article
C言語でpingを再実装してみた

C言語でpingを再実装してみた

公開日 2025/7/24 / 更新日 2025/8/5

カテゴリー

Technology

タグ

42Tokyo

はじめに

ネットワークプログラミングの学習として、pingコマンドの実装に挑戦しました。この記事では、その実装過程で学んだTCP/IPの基礎知識と、実際のコード例を交えながら解説します。

なぜpingを実装しようと思ったのか

かつてコンピュータネットワークの世界は、今よりもはるかに分断されていました。各メーカーや組織が独自のプロトコルを開発・採用し、IBMの「SNA(Systems Network Architecture)」、DECの「DECnet」、Appleの「AppleTalk」など、互換性のないネットワークが乱立していました。異なるシステム同士は直接通信できず、ネットワークごとに「壁」が存在していたのです。そんな状況を打破したのがTCP/IPでした。TCP/IPはインターネットの標準プロトコルとして登場し、異なるネットワークやコンピュータ間の分断を乗り越え、世界中のシステムをつなぐ基盤となりました。

pingはTCP/IPの基本的な機能の一つであり、その実装を通じてTCP/IPの仕組みを深く理解できると考えました。

pingとICMPの基礎知識

pingは、ネットワークの状態を確認するためのツールです。ICMP(Internet Control Message Protocol)という仕組みを使い、指定したホスト(コンピューターやサーバー)に「パケット」と呼ばれる小さなデータを送信します。そして、そのパケットが相手から返ってくるまでにかかった時間(応答時間)を測定します。これにより、ホストが正常に通信できるかどうかや、ネットワークの遅延状況を簡単に調べることができます。

ICMPの仕組み

ICMPはIPパケットの一部として送信され、以下のような特徴があります:

  • ICMPヘッダーには、タイプ、コード、チェックサムなどの情報が含まれます
      • タイプ:ICMPメッセージの種類(例:Echo Requestは8、Echo Replyは0)を示します。
      • コード:タイプごとの詳細な意味を指定します(多くの場合0ですが、エラー通知などでは値が変わります)。
      • チェックサム:ヘッダーとデータ部分の誤り検出のために使われます。
      • その他、識別子(ID)やシーケンス番号など、通信の識別や追跡に使われるフィールドも含まれます。
  • pingリクエストはEcho Requestメッセージとして送信され、対応するレスポンスはEcho Replyメッセージとして返されます
  • ネットワークの問題が発生した場合、ICMPは送信元(pingを実行したコンピュータ)に対して、Destination UnreachableやTime Exceededなどのエラーメッセージを返します。これにより、送信元は通信経路上の問題を把握できます。

実装の詳細

それでは、実際の実装について見ていきましょう。

必要なヘッダーファイル

#include <arpa/inet.h>

#include <errno.h>

#include <math.h>

#include <netdb.h>

#include <netinet/in.h>

#include <netinet/ip_icmp.h>

#include <signal.h>

#include <stdbool.h>

#include <stddef.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <sys/time.h>

#include <unistd.h>

ソケットの作成

int create_socket() {
    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sock < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    return sock;
}

ICMPパケットの構築とチェックサム計算

static u_short calculate_checksum(u_int16_t *addr, int len) {
	u_int32_t sum = 0;

	for (int i = 0; i < len / 2; i++) {
		sum += *addr++;
	}

	if (len % 2 == 1)
		sum += ((u_int8_t *)addr)[len - 1] << 8;

	while (sum >> 16) {
		sum = (sum & 0xFFFF) + (sum >> 16);
	}

	return ~sum;
}

void create_icmp_package(u_int8_t *buffer, u_int16_t id, u_int16_t seq) {
	struct icmp icmp_header = create_header(id, seq);
	memcpy(buffer, &icmp_header, ICMP_HEADER_SIZE);

	u_int8_t data[ICMP_DATA_SIZE] = {0};
	struct timeval tv;
	gettimeofday(&tv, NULL);
	memcpy(data, &tv, sizeof(struct timeval));
	memcpy(buffer + ICMP_HEADER_SIZE, data, ICMP_DATA_SIZE);

	icmp_header.icmp_cksum = calculate_checksum((u_int16_t *)buffer, ICMP_PACKET_SIZE);
	memcpy(buffer, &icmp_header, ICMP_HEADER_SIZE);
}

パケットの送信と応答の受信

int send_icmp_request(int sd, u_int8_t *buffer, struct sockaddr_in *dest) {
	ssize_t bytes_sent = sendto(sd, buffer, ICMP_PACKET_SIZE, 0, (struct sockaddr *)dest, sizeof(*dest));

	if (bytes_sent <= 0) {
		if (bytes_sent < 0)
			fprintf(stderr, "Error sending ICMP request: %s\n", strerror(errno));
		close(sd);
		return 1;
	}
	return 0;
}

t_ping_response_result receive_icmp_response(int sd, u_int8_t *buffer, struct sockaddr_in *dest, u_int16_t expected_pid) {
	t_ping_response_result response = {0};
	size_t buf_size = sizeof(struct ip) + ICMP_PACKET_SIZE;

	ssize_t bytes_received = recvfrom(sd, buffer, buf_size, 0, NULL, NULL);
	if (bytes_received <= 0) {
		if (bytes_received < 0) {
			if (errno == EAGAIN || errno == EWOULDBLOCK) {
				response.status = PING_ERROR_TIMEOUT;
				fprintf(stderr, "ICMP response timed out\n");
			} else {
				response.status = PING_ERROR_SYSTEM;
				fprintf(stderr, "Error receiving ICMP response: %s\n", strerror(errno));
			}
		} else {
			response.status = PING_ERROR_ZERO_BYTES;
			fprintf(stderr, "Received packet with zero bytes\n");
		}
		close(sd);
		return response;
	}
	/* 省略 */
	return response;
}

メイン関数

int main(int argc, char **argv) {
	signal(SIGINT, handle_sigint);
	t_parse_result result = parse_args(argc, argv);
	if (is_success_parse_result(result) == false)
		return 1;

	t_setup_ping_result setup_ping_result = setup_ping_connection(argv[optind], result.flag);
	if (!is_success_setup_ping_result(setup_ping_result))
		return 1;

	t_ping ping_context = setup_ping_result.context;
	print_ping_header(&ping_context, argv[optind]);
	ping_loop(&ping_context, &g_stats);
	print_statistics(&g_stats, argv[optind]);
	close(ping_context.sd);

	return 0;
}

実装のポイント

  1. ソケットの作成
      • SOCK_RAWタイプのソケットを使用
      • root権限が必要
  2. パケットの構築
      • ICMPヘッダーの設定
      • チェックサムの計算が重要
  3. エラーハンドリング
      • ネットワークエラーの適切な処理
      • タイムアウト処理の実装
  4. シグナルハンドリング
      • Ctrl+Cでの適切な終了処理

実行方法

gcc -o ping ping.c

sudo ./ping <destination-ip>

まとめ

pingの実装を通じて、以下のような知識を得ることができました:

  • TCP/IPの基本的な仕組み
  • ICMPプロトコルの詳細
  • ソケットプログラミングの基礎
  • ネットワークパケットの構造

ネットワークプログラミングの理解を深める良い機会となりました。

追記

42の課題pdfのマンダトリーに以下の文があります。私は実装しそびれてしまいましたので、注意してください。

the modification of the TTL value can help to force an error

0

いいね!

この記事をシェアする