connect.c

さて、いよいよソケットプログラミングに入ります。
これさえあれば、TCP接続で好きなアドレスになにかを送ったり、
また、それと同じようにしてSSLの暗号化通信もできるようになります。

作る関数としましては、

 ① アドレス(char *型、"google.com"など)→IPアドレス情報(struct in_addr型)変換するcon_atoaddr関数
 ② ①を使いつつ、アドレスとポートを引数としてそこにTCP接続を確立しソケット(int型)を返す、con_tcp関数
 ③ ソケットを引数として、over SSLしたやつ(SSL *型)を返す、con_ssl関数
 ④ ②、③を使い、host,port,sslかどうかを引数として受け取り、接続を確立した後ソケットおよびSSL *(sslでないときはNULL)を返す、con_connect関数
 ⑤ ソケットおよびSSL *を受け取り、SSL *がNULLでなければssl通信、NULLなら普通のtcpのみの通信を行い、引数にとったデータを送る、con_send関数
 ⑥ 同様にしてうまいこと受け取るcon_recv関数
 ⑦ これもまたうまいことconnectionを閉じる、con_close関数

となります。
まずはじめに、もう全部のっけちゃいましょう。
connect.cです。
#ifndef _CONNECT
#define _CONNECT

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

int con_connect(char *host, int port, int isssl, int *sock, SSL **ssl);
int con_send(int sock, SSL *ssl, char *m, int msize);
int con_recv(int sock, SSL *ssl, char *m, int msize);
void con_close(int sock, SSL *ssl);

struct in_addr *con_atoaddr(char *a);
int con_tcp(char *host, int port);
SSL *con_ssl(int tcpsock);

int con_connect(char *host, int port, int isssl, int *sock, SSL **ssl){
   if((*sock = con_tcp(host, port)) == -1) return 0;
   if(isssl){if((*ssl = con_ssl(*sock)) == NULL) return 0;}
   else if(ssl != NULL) *ssl = NULL;
   return 1;
}

int con_send(int sock, SSL *ssl, char *m, int msize){
   if(ssl == NULL) return send(sock, m, msize, 0);
   else return SSL_write(ssl, m, msize);
}

int con_recv(int sock, SSL *ssl, char *m, int msize){
   if(ssl == NULL) return recv(sock, m, msize, 0);
   else return SSL_read(ssl, m, msize);
}

void con_close(int sock, SSL *ssl){
   if (ssl != NULL) SSL_shutdown(ssl);
   else close(sock);
}


SSL *con_ssl(int tcpsock){
   SSL_load_error_strings();
   SSL_library_init();
   SSL_CTX *ctx = SSL_CTX_new(SSLv23_method());/* make SSL_CONTEXT(SSLv2|SSLv3|TSLv1) */
   SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);  /* without SSLv2 */
   SSL *ssl = SSL_new(ctx);                    /* make SSL(SSLv3|TSLv1) */
   if(SSL_set_fd(ssl, tcpsock) == -1){
      printf("can't set fd\n");
      return NULL;
   }
   if(SSL_connect(ssl) < 0){                  /* SSL_handshake */
      printf("can't connect\n");
      return NULL;
   }
   printf("SSL connected!\n");
   return ssl;
}

int con_tcp(char *host, int port){
   int s = socket(PF_INET, SOCK_STREAM, 0);
   struct sockaddr_in addr;
   addr.sin_family = AF_INET;
   addr.sin_port = htons(port);
   struct in_addr *address = con_atoaddr(host);
   if(address == NULL) return -1;
   addr.sin_addr = *address;
   free(address);
   printf("connect to ...\nName : %s\n  IP : %s\n", host, inet_ntoa(addr.sin_addr));
   
   if(connect(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) return -1;
   else{
      printf("connection OK !\n");
      return s;
   }
}

struct in_addr *con_atoaddr(char *a){
   struct hostent *host = gethostbyname(a);
   if(host == NULL) return NULL;
   struct in_addr *re = (struct in_addr *)malloc(sizeof(struct in_addr));
   *re = *(struct in_addr *)(host->h_addr);
   return re;
}

#endif

① struct in_addr *con_atoaddr(char *a)
yahoo.co.jpやgoogle.comなどのホスト名には、IPアドレスが割り当てられています。
netdb.hをインクルードすると使えるgethostbyname関数は、それを求めてくれます。
帰ってくるのはstruct hostentという構造体。この中のh_addrが、アドレスデータの先頭アドレスです。
今回は、そのアドレスを、エラー処理や、このあと代入することを考えてstruct in_addr *に型変換して返しております。

でもこのデータの中身はいたって単純。例えばwww59.jimdo.com(IPアドレス: 122.200.253.146)なら、4バイトに順に、122, 200, 253, 146が格納されているだけです。

bit.cを使って中身をみれば、一目瞭然です。
#include <stdio.h>
#include "connect.c"
#include "bit.c"

int main(int argc, char *argv[]){
   struct in_addr *addr = con_atoaddr(argv[1]);
   bit b[32];
   ctob(b, (char *)addr, 0, 32);
   int k;
   for(k = 0; k < 32; ++k){
      printf("%d", b[k]);
      if((k+1) % 8 == 0) printf(" ");
   }
   printf("\n");
   return 0;
}
こんなプログラムを作り、
 $ ./test www59.jimdo.com
と入力すれば、
 01111010 11001000 11111101 10010010
と表示されます。
確かに、 122, 200, 253, 146が入っています。
② int con_tcp(char *host, int port)
ここで、ソケットプログラミングなるややこしいものの登場です。
TCPで接続する手順としては、
(1)ソケットの作成(受話器を持つ)
(2)送り先情報の作成(電話番号を入れる)
(3)コネクト(電話をかける)
(4)以降そのソケットを用いて通信可能(その受話器を用いて通話可能)
となっております。

(1)に使うのが、socket関数。
socket(PF_INET, SOCK_STREAM, 0)と、訳わかりませんが、
SOCK_STREAMの代わりにSOCK_DGRAMを入れると、UDP通信ができます。
この場合(1)と(2)だけで通信(というか送信および受信)できます。

(2)に使うのが、struct sockaddr_in。
ひとまず宣言し、その中身を入れていきます。
.sin_family = AF_INET は、お決まり。
.sin_port = htons(port)
は、port番号を入れるのですが、そのときhtons関数でネットワークバイトオーダーにします。
つまりリトルエンディアンだったらビッグエンディアンに並び替えるだけです。
下のプログラムを見れば一目瞭然でしょう。
#include <stdio.h>
#include "connect.c"
#include "bit.c"

int main(int argc, char *argv[]){
   uint16_t u1 = 80;
   uint16_t u2 = htons(u1);
   bit b[sizeof(u1) * 8];
   int k;
   printf("from: ");
   ctob(b, (char *)&u1, 0, sizeof(u1) * 8);
   for(k = 0; k < sizeof(u1) * 8; ++k) printf("%d%s", b[k], (k + 1) % 8 == 0 ? " " : "");
   printf("\n  to: ");
   ctob(b, (char *)&u2, 0, sizeof(u2) * 8);
   for(k = 0; k < sizeof(u2) * 8; ++k) printf("%d%s", b[k], (k + 1) % 8 == 0 ? " " : "");
   printf("\n");
   return 0;
}
$ ./test
from: 01010000 00000000 
  to: 00000000 01010000 
.sin_addrには、先程の関数で得た、アドレス情報の先頭アドレス(ややこしい笑)を与えます。
さっきは言っておりませんでしたが、簡単にその中にちゃんと入ってるか見たければ、inet_ntoa関数を使えばいいでしょう。
というわけで、struct sockaddr_inに、全て代入出来ました。

(3)に使うのが、 connect関数。
ソケット、sockaddr_inのアドレスをより一般化したsockaddr構造体のアドレスとしてキャストしたもの、その構造体のサイズ
の3つを渡せば、connectできるはずです。
成功して-1以外の値が帰ってきたら、今後はread/writeまたはrecv/sendを使って、データの送受信が可能です。

③ SSL *con_ssl(int tcpsock)
これはもう、②でゲットしたソケットをもとに、SSL *を作る関数です。
コピペですし、やってることも意味してることもよくわかりません。笑
自分で実装できないレベルのものは、わかりません…

④ con_connect
は、②を用いてソケットを作った後、
int issslが1であればそれを使ってSSL通信も確保します。
その場合にはSSL **sslの指す場所にSSL *を代入し、
SSLでつながないのであればNULLを代入します。

⑤⑥⑦
SSLがNULLかどうかで、SSL通信をするか否か決め、適宜sendやrecvを行います。


というわけで、最後の方はあまり中身もありませんが、connect.c、こんな感じです。
次回はいよいよこのconnect.cを使った、http.cの説明をします。
画像のダウンロードとかはできるようになります。

コメントをお書きください

コメント: 1
  • #1

    nop (金曜日, 14 11月 2014 00:29)

    SSLを使ったサンプルありがとうございます。
    大変参考になりました。

    気になる点がありますのでご連絡します。

    void con_close(int sock, SSL *ssl){
    if (ssl != NULL) SSL_shutdown(ssl);
    else close(sock);
    }

    これは以下の方が良くないでしょうか??

    void con_close(int sock, SSL *ssl, SSL_CTX *ctx){
    if (ssl != NULL) {
    SSL_shutdown(ssl);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    }
    close(sock);
    }