Linux socket プログラミング/利用可能ポート検査プログラム

目次

概要

いわゆるポートスキャンと言われるプログラムです。他人のマシンのポートをスキャンすると、場合によってはハッキングだと思われ て管理者から怒られかねませんので、ここでは自分のマシン(Linux)のポートを調査するプログラムを紹介します。
自分のマシンのポートなんて調べてもしょうがないし、もっと簡単な別の方法があるだろう!と言われる方もいらっしゃると思います が、まぁこれは勉強のためでして。

プログラムのイメージ

このプログラムで使われる関数のイメージを以下に示します。
サーバプログラムとは inetd デーモンやその他 各種サービスのデーモンをさします。 このプログラムは for() を使ってポート番号1〜1024 宛ての 接続要求(connect(2))を次々送出し、エラーが返らなかった ら そのポートを報告すると言う単純なものです。

このプログラムサーバプログラム
uname()socket()
gethostbyname()bind()
socket()listen()
connect()接続要求
------->
accept()

プログラム

    1  /*
    2   * Scan local tcp ports
    3   * selfck.c
    4   * cc selfck.c -lnsl -o selfck
    5   */
    6
    7  #include <stdio.h>
    8  #include <sys/socket.h>
    9  #include <netinet/in.h>
   10  #include <errno.h>
   11  #include <netdb.h>
   12  #include <signal.h>
   13  #include <string.h>
   14  #include <sys/utsname.h>
   15  #include <stdlib.h>
   16
   17  #define MAXPORT 1024
   18  #define MAXSVCNAME 15
   19
   20  int main()
   21  {
   22      int net, portnum;
   23      struct hostent *host;
   24      struct servent *svc_ent;
   25      struct sockaddr_in sa;
   26      struct utsname  h_name[1];
   27      char   svc_name[MAXSVCNAME];
   28
   29      memset(&sa, 0, sizeof(struct sockaddr_in));
   30      if ( (uname(h_name)) < 0 ){
   31          perror("uname");
   32          exit(1);
   33      }
   34      if ((host = gethostbyname(h_name->nodename)) == NULL){
   35          perror("gethostbyname()");
   36          exit(1);
   37      }
   38      memcpy(&sa.sin_addr, host->h_addr, sizeof(struct in_addr));
   39      sa.sin_family = AF_INET;
   40
   41      for (portnum = 1; portnum <= MAXPORT ; portnum++) {
   42          sa.sin_port = htons(portnum);
   43          if((net = socket(AF_INET, SOCK_STREAM, 0)) < 0){
   44              perror("socket");
   45              exit(1);
   46          }
   47          if (connect(net, (struct sockaddr *) &sa, sizeof(struct sockaddr_in)) < 0){
   48              printf("%s  %-5d %s\r", h_name->nodename, portnum, strerror(errno));
   49              fflush(stdout);
   50          } else {
   51              if ((svc_ent = getservbyport( htons(portnum) ,"tcp")) == NULL) {
   52                  sprintf(svc_name,"Unknown");
   53              } else {
   54                  snprintf(svc_name, MAXSVCNAME, "%s", svc_ent->s_name);
   55              }
   56              printf("%s  %-5d < %-15s>   accepted.  \n",h_name->nodename,portnum, svc_name);
   57              if (shutdown(net, 2) < 0) {
   58                  perror("shutdown()");
   59                  exit(1);
   60              }
   61          }
   62          close(net);
   63      }
   64      printf("                                                   \r");
   65      exit(0);
   66  }

ソースファイル selfck.c

実行例

プログラムの実行例を以下に示します。

% ./selfck
myhost 22 < ssh > accepted.
myhost 53 < domain > accepted.
%

エラーにならなかったポートがあると、ホスト名、ポート番号、サービス名 の順に出力します。 このサービス名の出力に、前述のサービス名照会プログラムが生きています(笑)

解説

このプログラムの流れは 最初、自分のホスト名、IP アドレス を調べ、connect()システムコールを使って自ホストにコネクション要求を出し、connect() が エラーを返した ら、エラーを出力し、正常であればポート番号を報告するとものです。以下、順を追って説明して行きます。

   26      struct utsname  h_name[1];

ここで utsname という構造体が出てきます。utsname の中身は以下のとおりです。

         struct utsname{
             char sysname[_UTSNAME_SYSNAME_LENGTH];
             char nodename[_UTSNAME_NODENAME_LENGTH];
             char release[_UTSNAME_RELEASE_LENGTH];
             char version[_UTSNAME_VERSION_LENGTH];
             char machine[_UTSNAME_MACHINE_LENGTH];
             char domainname[_UTSNAME_DOMAIN_LENGTH];
         };

この utsname 構造体は uname() を使って 自分の node 名( = ホスト名)を参照する時に利用します。 uname()についての説明はしませんが、指定した utsname 構造体へ自ホストの情報を格納してくれる関数です。

   29      memset(&sa, 0, sizeof(struct sockaddr_in)); 

25 行目で宣言した sockaddr_in 構造体「sa」を 29 行目で初期化しています。この初期化をしないと おかしな値が入る事があるそうですが、ためしてないのでわかりません。

   34      if ((host = gethostbyname(h_name->nodename)) == NULL){
   35          perror("gethostbyname()");
   36          exit(1);
   37      }
   38      memcpy(&sa.sin_addr, host->h_addr, sizeof(struct in_addr));

34 行目で、上の uname で得られた ホスト名を引数にして gethostbyname() を呼び出しています。

gethostbyname()
概要ホスト名から hostent 構造体を得る
インクルードファイル#include <netdb.h>
形式struct hostent *gethostbyname(const char *name);
引数const char *name ホスト名が格納されている文字配列のポインタ
戻り値成功時: ホスト情報を格納する hostent 構造体へのポインタ
エラー: -1

以下に hostent 構造体を示します。

          struct hostent {
                  char    *h_name;        /* official name of host */
                  char    **h_aliases;    /* alias list */
                  int     h_addrtype;     /* host address type */
                  int     h_length;       /* length of address */
                  char    **h_addr_list;  /* list of addresses */
          }

33 行目で gethostbyname()で得られた hostent 構造体 host のメンバーである「h_addr」(ホストアドレス)を sockaddr_in 構造体 sa のメンバーである sin_addr にコピーしています。

   41      for (portnum = 1; portnum <= MAXPORT ; portnum++) {

ここで ポート番号を1番から MAXPORT(1024) まで順に指定していきます。 あっ、説明いらないですか?

   43          if((net = socket(AF_INET, SOCK_STREAM, 0)) < 0){
   44              perror("socket");
   45              exit(1);
   46          }

43 行目で socket() を呼び出しています。

socket()
概要ソケットを作成する
インクルードファイル#include <sys/types.h>
#include <sys/socket.h>
形式int socket(int domain, int type, int protocol);
引数int domain : 通信領域を指定
int type : コネクション型 または コネクションレス型
int protocol : TCP または UDP
戻り値成功時: socket 記述子
エラー: -1

socket()の戻り値は socket 記述子です。これはファイル記述子と同等のもので socket()はファイルの読み込み/書き込み時に使う open() システムコールに相当する関数といえます。
socket 記述子は後述の connect() システムコールで利用します。

   47          if (connect(net, (struct sockaddr *) &sa, sizeof(struct sockaddr_in)) < 0){
   48              printf("%s  %-5d %s\r", h_name->nodename, portnum, strerror(errno));
   49              fflush(stdout);
   50          } else {
   51              if ((svc_ent = getservbyport( htons(portnum) ,"tcp")) == NULL) {
   52                  sprintf(svc_name,"Unknown");
   53              } else {
   54                  snprintf(svc_name, MAXSVCNAME, "%s", svc_ent->s_name);
   55              }
   56              printf("%s  %-5d < %-15s>   accepted.  \n",h_name->nodename,portnum, svc_name);
   57              if (shutdown(net, 2) < 0) {
   58                  perror("shutdown()");
   59                  exit(1);
   60              }
   61          }
connect()
概要他のソケットとの接続要求
インクルードファイル#include <sys/types.h>
#include <sys/socket.h>
形式int connect(int sockfd, struct sockaddr *serv_addr, int addrlen )
引数int sockfd : socket 記述子
struct sockaddr *serv_addr : アドレス構造体(sockaddr_in) へのポインタ
int addrlen : アドレス構造体のサイズ
戻り値成功時: 0
エラー: -1

ここで 先ほど設定したアドレス構造体「sa」を使います。
簡単に言ってしまうととしてはアドレス構造体に接続相手(今回の場合自分)の情報をセットし、connect() システムコールを使ってコネクションをはる。connect() がマイナスの値を返してきたら、それはコネクションの確立が失敗した事を意味するので、エラーメッセージを出力して次のポート番号に進みます。( 48 行目)connect() が 0 を返してきたらコネクションが張れた事を意味するので、そのポート番号 を出力します。( 56 行目)

   57              if (shutdown(net, 2) < 0) {
   58                  perror("shutdown()");
   59                  exit(1);
   60              }
   61          }
   62          close(net);
   63      }
shutdown()
概要コネクションを切断する
インクルードファイル#include <sys/socket.h>
形式int shutdown(int s, int how);
引数int s : socket 記述子
int how : 送受信の設定
0:以降の受信禁止
1:以降の送信禁止
2:以降の送受信禁止
戻り値成功時: 0
エラー: -1

最後に shutdown() で確立したコネクションを切断しましょう。(57 行目)
close()システムコールは ファイルの open 時と同様 不要に開いている socket を破棄します。


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2022-01-03 (月) 20:17:59