【ITニュース解説】Building a simple TCP port scanner in C
2025年10月03日に「Dev.to」が公開したITニュース「Building a simple TCP port scanner in C」について初心者にもわかりやすく解説しています。
ITニュース概要
C言語でTCPポートスキャナーの作り方を解説。TCPソケットを使い、ターゲットPCの開いているポートを特定する基本的な仕組みがわかる。ネットワーク通信の基礎やセキュリティへの理解を深めるのに役立つ。
ITニュース解説
この解説では、C言語を使ってごく簡単なTCPポートスキャナーをどのように構築するか、その技術的な背景やコードの中身をシステムエンジニアを目指す初心者にもわかりやすく説明する。ポートスキャンとは、ネットワークにつながったコンピューター(ターゲットホストと呼ぶ)上で、どのような通信窓口(ポート)が開いているかを探る技術である。これは、ネットワーク管理者にとっては、自分の管理するシステムが意図せず外部に公開しているサービスがないかを確認するために重要であり、セキュリティの専門家にとっては、システムの脆弱性を特定するための基本的な手法の一つとなる。この知識は、現代のITシステムを安全に運用する上で欠かせない基礎知識の一つだ。
今回紹介するシンプルなポートスキャナーは、特定のターゲットホストに対して、よく使われるポート番号(具体的には1番から1024番まで)を一つずつ調べていく。ターゲットホストはデフォルトで自分のコンピューター(ローカルホスト、IPアドレスは127.0.0.1)に設定されており、スキャンして見つかった開いているポートは画面に表示される仕組みになっている。
では、このポートスキャナーが具体的にどのように動作するのかを見ていこう。その中心にあるのは「TCPソケット」という技術だ。ソケットとは、ネットワーク上で通信を行うためのプログラム的な窓口のようなもので、OSが提供するAPIを通じて利用する。TCP(Transmission Control Protocol)は、インターネットで最も広く使われている通信プロトコルの一つで、信頼性の高いデータ転送を保証する。ポートスキャナーは、このTCPソケットを使って、ターゲットホストの各ポートに対して接続を試みる。もし接続が成功すれば、そのポートは「開いている」と判断できる。逆に、接続が拒否されたり、タイムアウトしたりすれば、そのポートは「閉じている」または「フィルターされている」と判断できる。このプログラムでは、各ポートへの接続試行を順番に(シーケンシャルに)実行する。
このプログラムを動かすには、いくつかの準備が必要だ。まず、C言語のプログラムをコンパイルするための「GCC」のようなCコンパイラが必要になる。GCCはGNU Compiler Collectionの略で、オープンソースの非常に有名なコンパイラだ。また、プログラムは「Unix系のOS」(オペレーティングシステム)上で動作するように設計されている。具体的にはLinuxやmacOSがこれに該当する。Windows環境で実行するには、WSL(Windows Subsystem for Linux)のような互換環境を使うか、Windows向けのソケットプログラミングに合わせた修正が必要になる場合がある。
プログラムのビルド(コンパイル)方法は非常に簡単だ。port_scanner.cという名前のソースコードファイルがあると仮定して、ターミナル(コマンドライン)でgcc -o port_scanner port_scanner.cというコマンドを実行する。これにより、C言語のソースコードがコンピューターが直接実行できる形式(実行ファイル)に変換され、port_scannerという名前のファイルが生成される。この実行ファイルを動かすには、./port_scannerとコマンドを入力するだけだ。すると、プログラムが自動的にローカルホストのポートスキャンを開始し、開いているポートを次々に画面に表示していく。
次に、プログラムのC言語コードの中身を詳しく見ていこう。コードは、stdio.h、sys/socket.h、netinet/in.h、arpa/inet.h、unistd.hといったヘッダーファイルを読み込むことから始まる。これらは、標準入出力、ソケットの操作、ネットワークアドレスの定義、IPアドレスの変換、そしてファイルやソケットのクローズといった、システムコールや関数を使うために必要だ。
main関数の中では、sockfdという整数変数が宣言される。これはソケットを識別するための番号(ソケットディスクリプタ)として使われる。ファイルを開いたときに返されるファイルディスクリプタと似たようなものと考えるとよい。また、struct sockaddr_in serv_addr;は、接続先のサーバーのアドレス情報(IPアドレスとポート番号)を格納するための特別な構造体だ。
serv_addr.sin_family = AF_INET;という行は、使用するアドレスファミリをIPv4(インターネットプロトコルバージョン4)に設定している。現代のインターネット通信の基盤となっている方式の一つだ。serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");では、ターゲットのIPアドレスを「127.0.0.1」に設定している。これは特別なIPアドレスで、「ローカルホスト」、つまりプログラムを実行している自分自身のコンピューターを指す。inet_addr関数は、人間が読める文字列形式のIPアドレスを、プログラムが扱える数値形式に変換する役割がある。
その後のforループは、ポート番号1から1024までを順番に繰り返す。この範囲のポートは「ウェルノウンポート」(well-known ports)と呼ばれ、HTTP(80番)、FTP(21番)、SSH(22番)など、特定のサービスに割り当てられていることが多い。
ループの各イテレーションでは、まずsockfd = socket(AF_INET, SOCK_STREAM, 0);という行で新しいソケットを作成する。AF_INETはIPv4を使用すること、SOCK_STREAMはTCPのような接続指向のストリームソケットを使用することを示す。最後の0は、デフォルトのプロトコルを選択するという意味だ。もしソケットの作成に失敗した場合は、perror関数でエラーメッセージを表示し、continueで現在のループの残りの処理をスキップして次のポートへと移る。
次に、serv_addr.sin_port = htons(port);の行で、現在のループで処理しているポート番号をserv_addr構造体に設定する。ここで重要なのがhtons関数だ。これは「ホストバイトオーダーからネットワークバイトオーダーへショート整数を変換する」という意味で、コンピューター内部での数値表現と、ネットワーク上での数値表現が異なる場合があるため、通信プロトコルが要求する統一されたネットワークバイトオーダーに変換する必要がある。
そして、if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0)という行が、実際に接続を試みる部分だ。connect関数は、指定されたソケット(sockfd)を、serv_addrで指定されたアドレスとポートを持つサーバーに接続しようと試みる。第2引数の&serv_addrは、サーバーのアドレス情報へのポインタを渡し、sizeof(serv_addr)はそのアドレス情報のサイズを教えている。connect関数が0を返した場合、それは接続が成功したことを意味する。その場合、printf関数を使って「Port %d is open\n」というメッセージと、開いているポート番号を表示する。
最後に、close(sockfd);という行で、開いたソケットを必ず閉じている。これを怠ると、システムのリソースが消費されたままになり、プログラムが多くのポートをスキャンするにつれてシステムが不安定になる「リソースリーク」という問題が発生する可能性がある。
この基本的なポートスキャナーは、さらに多くの機能を追加して拡張できる。現在のプログラムはポートを一つずつ順番にスキャンしているため、スキャンに時間がかかる可能性がある。これを改善するために、「マルチスレッド」という技術を使って複数のポートを同時にスキャンすることで、スキャン速度を大幅に向上させることができる。また、開いていないポートへの接続試行が完了するまでプログラムが待機してしまうと、処理が停滞してしまうことがあるため、「タイムアウト」を設定することで、一定時間内に応答がない場合は接続試行を打ち切るようにできる。これにより、プログラムがフリーズするのを防ぎ、効率を上げることが可能だ。
さらに柔軟性を高めるには、スキャン対象のIPアドレスやポート範囲をプログラムの実行時にユーザーが指定できるように、引数として受け取るようにコードを修正することも考えられる。現在のプログラムはローカルホスト(127.0.0.1)しかスキャンしないが、inet_addr関数の引数を変更すれば、ネットワーク上の他のコンピューター(リモートホスト)をスキャンすることも可能だ。また、スキャン結果を画面に表示するだけでなく、ファイルに保存するように変更すれば、後から結果を確認したり、他のツールと連携させたりするのに便利になるだろう。
このプロジェクトを通じて、システムエンジニアを目指す初心者がC言語でのネットワークプログラミングの基礎を学び、ソケットがどのように機能するか、TCP接続がどのように確立されるかといった重要な概念を理解する手助けになるだろう。さらに深く学ぶには、C言語とシステムプログラミングの基礎を固め、ネットワークの基礎知識(TCP/IPプロトコル、IPアドレス、ポート番号など)をしっかりと学ぶことが推奨される。そして、マルチスレッドや非同期I/Oといった高度なトピックを探求していくことで、より高性能で複雑なネットワークアプリケーションを開発する能力を身につけることができる。