#norelated
 #navi(Linux socket プログラミング)
 *目次 [#x96e8c85]
 #contents
 
 *概要 [#t8cbce87]
 いわゆるポートスキャンと言われるプログラムです。他人のマシンのポートをスキャンすると、場合によってはハッキングだと思われ て管理者から怒られかねませんので、ここでは自分のマシン(Linux)のポートを調査するプログラムを紹介します。&br;
 自分のマシンのポートなんて調べてもしょうがないし、もっと簡単な別の方法があるだろう!と言われる方もいらっしゃると思います が、まぁこれは勉強のためでして。&br;
 
 *プログラムのイメージ [#w461895d]
 このプログラムで使われる関数のイメージを以下に示します。&br;
 サーバプログラムとは inetd デーモンやその他 各種サービスのデーモンをさします。 このプログラムは for() を使ってポート番号1〜1024 宛ての 接続要求(connect(2))を次々送出し、エラーが返らなかった ら そのポートを報告すると言う単純なものです。
 
 |このプログラム|BGCOLOR(white):| 	サーバプログラム|h
 |uname()|BGCOLOR(white):|socket()|
 |gethostbyname()|BGCOLOR(white):|bind()|
 |socket()|BGCOLOR(white):|listen()|
 |connect()|BGCOLOR(white):接続要求&br;------->|accept()|
 
 *プログラム [#pbfaebbf]
 CENTER:[[selfck.c>source:selfck.c]]
 CENTER:[[selfck.c>http://github.com/kaizawa/linux-socket/raw/master/selfck.c]]
 
      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>source:selfck.c]]
 ソースファイル [[selfck.c>http://github.com/kaizawa/linux-socket/raw/master/selfck.c]]
 
 *実行例 [#q271a3e2]
 プログラムの実行例を以下に示します。
  % ./selfck
  myhost 22 < ssh > accepted.
  myhost 53 < domain > accepted.
  %
 
 エラーにならなかったポートがあると、ホスト名、ポート番号、サービス名 の順に出力します。 このサービス名の出力に、前述のサービス名照会プログラムが生きています(笑)&br;
 
 *解説 [#c9ecb6c8]
 このプログラムの流れは 最初、自分のホスト名、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()|h
 |概要|ホスト名から hostent 構造体を得る|
 |インクルードファイル	|#include <netdb.h>|
 |形式	|struct hostent *gethostbyname(const char *name);|
 |引数 	|const char *name	ホスト名が格納されている文字配列のポインタ|
 |戻り値 	|成功時:	ホスト情報を格納する hostent 構造体へのポインタ&br;エラー:	-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()|h
 |概要|ソケットを作成する|
 |インクルードファイル	|#include <sys/types.h>&br;#include <sys/socket.h>|
 |形式	|int socket(int domain, int type, int protocol);|
 |引数 	|int domain : 	通信領域を指定&br;int type : 	コネクション型 または コネクションレス型&br;int protocol : 	TCP または UDP|
 |戻り値 	|成功時:	socket 記述子&br;エラー:	-1|
 
 socket()の戻り値は socket 記述子です。これはファイル記述子と同等のもので socket()はファイルの読み込み/書き込み時に使う open() システムコールに相当する関数といえます。&br;
 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()|h
 |概要|他のソケットとの接続要求|
 |インクルードファイル 	|#include <sys/types.h>&br;#include <sys/socket.h>|
 |形式 	|int connect(int sockfd, struct sockaddr *serv_addr, int addrlen )|
 |引数 	|int sockfd : 	socket 記述子&br; struct sockaddr *serv_addr : アドレス構造体(sockaddr_in) へのポインタ&br;int addrlen : 	アドレス構造体のサイズ|
 |戻り値 	|成功時:	0&br;エラー:	-1|
 
 ここで 先ほど設定したアドレス構造体「sa」を使います。&br;
 簡単に言ってしまうととしてはアドレス構造体に接続相手(今回の場合自分)の情報をセットし、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()|h
 |概要|コネクションを切断する|
 |インクルードファイル |	#include <sys/socket.h>|
 |形式 	|int shutdown(int s, int how);|
 |引数 	|int s : 	socket 記述子&br;int how : 送受信の設定&br;		0:以降の受信禁止&br;		1:以降の送信禁止&br;2:以降の送受信禁止|
 |戻り値 	|成功時:	0&br;エラー:	-1|
 
 最後に shutdown() で確立したコネクションを切断しましょう。(57 行目)&br;
 close()システムコールは ファイルの open 時と同様 不要に開いている socket を破棄します。


トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS