libsslを使った暗号化と復号のサンプル

ちょっと必要になったので、軽い気持ちで書いてみました。
とりあえず手軽なところでAESとblowfishのサンプルです。
比較的簡単に書けるのでいろんなとこで使えそう。
こんな便利なライブラリを書いてくれた方々の偉大さを噛みしめながら利用したいと思います。

test.c

#include<stdio.h>
#include<stdint.h>
#include<string.h>
#include<openssl/md5.h>
#include<openssl/aes.h>
#include<openssl/blowfish.h>

char *encpass = "hoge";

void md5sum(char *data, unsigned char *digest)
{
  MD5_CTX ctx;
  MD5_Init(&ctx);
  MD5_Update(&ctx, data, strlen(data));
  MD5_Final(digest, &ctx);
}

void dump(unsigned char *data, int len)
{
  int i;
  printf("str: ");
  for(i=0;i<len;i++){
    if(data[i] < 32 || data[i] > 126){
      printf(".");
    }else{
      printf("%c", data[i]);
    }
  }
  printf("\n");
  printf("hex: ");
  for(i=0;i<len;i++){
    printf("%02x ",data[i]);
  }
  printf("\n");
}
void aes_demo()
{
  char *data = "1234567890123456";
  char encdata[1024];
  char decdata[1024];
  AES_KEY enckey;
  AES_KEY deckey;
  uint8_t md5[16];
  md5sum(encpass, md5);
  AES_set_encrypt_key(md5, 128, &enckey);
  AES_set_decrypt_key(md5, 128, &deckey);

  printf("===== AES =====\n");
  printf("[data]\n");
  dump(data, 16);

  printf("\n[encrypt]\n");
  AES_encrypt(data, encdata, &enckey);
  dump(encdata, 16);

  printf("\n[decrypt]\n");
  AES_decrypt(encdata, decdata, &deckey);
  dump(decdata, 16);
}

void blowfish_demo()
{
  BF_LONG data = 0x34333231;
  BF_KEY key;
  BF_set_key(&key, 4, "hoge");

  printf("===== blowfish =====\n");
  printf("[data]\n");
  dump((unsigned char *)&data, sizeof(data));

  printf("\n[encrypt]\n");
  BF_encrypt(&data, &key);
  dump((unsigned char *)&data, sizeof(data));

  printf("\n[decrypt]\n");
  BF_decrypt(&data, &key);
  dump((unsigned char *)&data, sizeof(data));
}

int main(int argc, char *argv[])
{
  aes_demo();
  printf("\n");
  blowfish_demo();
  return(0);
}

コンパイル

$ gcc -lssl -o test test.c

実行結果

$ ./test
===== AES =====
[data]
str: 1234567890123456
hex: 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36

[encrypt]
str: vo.c......aG....
hex: 76 6f db 63 cd ef d4 93 ac 8f 61 47 a4 bf ac dc

[decrypt]
str: 1234567890123456
hex: 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36

===== blowfish =====
[data]
str: 1234
hex: 31 32 33 34

[encrypt]
str: X.GA
hex: 58 c7 47 41

[decrypt]
str: 1234
hex: 31 32 33 34

Linuxでcからnetstatっぽいことをしたかった

詳しい話はまた後日ということで、とりあえず今回はソースと実行結果だけ貼り付けます。
netlinkの資料は比較的少ないので、カーネルソースを追いながら試行錯誤してみました。
TCPDIAG_GETSOCKでNLM_F_DUMPを指定するとソケットを列挙できるようです。
sportやdportを指定すると条件にマッチしたソケットだけ列挙してくれるので結構便利そう。

wbuf.req.id.idiag_sport=htons(3306);

とかするとMySQLの接続数を調べることができたりなんかして。

ntest.c

#include<stdio.h>
#include<sys/socket.h>
#include<linux/netlink.h>
#include<linux/inet_diag.h>
#include<net/tcp_states.h>

int scount(int n)
{
  int r=0;
  int len;
  static int  seq=1;
  static char rbuf[65535];
  struct {
    struct nlmsghdr      nlh;
    struct inet_diag_req req;
  } wbuf;
  struct nlmsghdr      *nlh;
  struct inet_diag_msg *msg;
  wbuf.nlh.nlmsg_len          = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(wbuf.req)));
  wbuf.nlh.nlmsg_type         = TCPDIAG_GETSOCK;
  wbuf.nlh.nlmsg_flags        = NLM_F_REQUEST | NLM_F_DUMP;
  wbuf.nlh.nlmsg_seq          = seq++;
  wbuf.req.idiag_family       = AF_INET;
  wbuf.req.idiag_src_len      = 0;
  wbuf.req.idiag_dst_len      = 0;
  wbuf.req.idiag_ext          = 0;
  wbuf.req.idiag_states       = TCPF_ESTABLISHED;
  wbuf.req.idiag_dbs          = 0;
  wbuf.req.id.idiag_sport     = 0;
  wbuf.req.id.idiag_dport     = 0;
  wbuf.req.id.idiag_cookie[0] = INET_DIAG_NOCOOKIE;
  wbuf.req.id.idiag_cookie[1] = INET_DIAG_NOCOOKIE;
  len = write(n, &wbuf, wbuf.nlh.nlmsg_len);
  if(len < 0){
    printf("netlink write error!!\n");
    return(-1);
  }
  while(1){
    len=read(n, rbuf, sizeof(rbuf));
    for(nlh=(struct nlmsghdr *)rbuf;NLMSG_OK(nlh, len);nlh=NLMSG_NEXT(nlh, len)){
      if(nlh->nlmsg_seq != wbuf.nlh.nlmsg_seq)
        continue;
      msg=(struct inet_diag_msg *)((char *)nlh + sizeof(struct nlmsghdr));
      if(nlh->nlmsg_type == NLMSG_ERROR){
        fprintf(stderr,"netlink msg error\n");
        return(-1);
      }
      if(nlh->nlmsg_type == NLMSG_DONE){
        return(r);
      }
      /*
      printf("%02d: state=%02d sport=%05d dport=%05d\n", 
        r, msg->idiag_state, 
        ntohs(msg->id.idiag_sport), 
        ntohs(msg->id.idiag_dport));
      */
      r++;
    }
  }
  return(0);
}

int main(int argc, char *argv[])
{
  int c;
  int n;

  n = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG);
  if(n == -1){
    printf("Can't Open NetLink!!\n");
    return(0);
  }

  c = scount(n);
  if(c == -1)
    return(1);
  printf("%d\n",c);
  return(0);
}

実行結果

$ ./ntest
7
$ netstat --inet -n | grep ESTABLISHED | wc -l
7

Linuxでcからnetstatっぽいことしたい

$ netstat --inet -n | wc -l
みたいなことを比較的短い周期(1秒毎とか)でやりたいんですが何かいい方法ないでしょうか。
/proc/net/tcpを嘗めればできるんですが、もう少し軽くて優しいと嬉しいです。
いまのとこ、netlinkを使ってどうにかならないかと調べ中。
TCPDIAG_GETSOCKっぽくTCPDIAG_ENUMSOCKみたいなのないのかな。

サタデーコードフィーバー

ウノウさんの「サタデーコードフィーバー」というイベント(?)にきています。
こういう雰囲気は結構好きです。ボールで転がりながらまったりとコード書いてます。
で、今やっているのはこんな事

Windows版のGrowlを目指せ!ってとこです。
ちなみに名前はWhineいいます。元気にくんくん泣いてます(w
週明けくらいにアルファリリースとかできるといいんだけど(^^;

カーネルモジュールことはじめ #9

だいぶ間があいてしまいましたが、気を取り直して再開します。「カーネルモジュールことはじめ」といいつつIPVSに偏ったネタばかりですが気にせずに進めます。今日は、こないだ書いたコード を見ながらIPVSの挙動を少しだけ追ってみたいと思います。

IPVSに新しいスケジューラを登録するには、module_init() で指定した初期化関数の中で、register_ip_vs_scheduler() を使います。登録用のエントリとして利用する ip_vs_scheduler 構造体は、include/net/ip_vs.h で以下のように定義されています。

/*
 *  The scheduler object
 */
struct ip_vs_scheduler {
  struct list_head  n_list;   /* d-linked list head */
  char      *name;    /* scheduler name */
  atomic_t    refcnt;   /* reference counter */
  struct module   *module;  /* THIS_MODULE/NULL */

  /* scheduler initializing service */
  int (*init_service)(struct ip_vs_service *svc);
  /* scheduling service finish */
  int (*done_service)(struct ip_vs_service *svc);
  /* scheduler updating service */
  int (*update_service)(struct ip_vs_service *svc);

  /* selecting a server from the given service */
  struct ip_vs_dest* (*schedule)(struct ip_vs_service *svc,
               const struct sk_buff *skb);
};

この構造体では、以下のメンバに関数ポインタを指定します。

  • init_service
  • done_service
  • update_service
  • schedule

今回は、これらの関数がどのタイミングで実行されるのかを調べてみます。こないだ書いたコードでは、それぞれの関数の中から IP_VS_DBG() を呼んでいます。これは、/proc/sys/net/ipv4/vs/debug_level で指定されたデバッグレベル未満のメッセージをカーネルログに出力するものです。ここでは、IP_VS_DBG(7,"") としているので、デバッグレベルを 8に設定します。

# sysctl -w net.ipv4.vs.debug_level=8

そして、ログを眺めながら以下の操作をやってみます

  • モジュールをロード
  • 仮想サービスを追加
  • リアルサーバを追加
  • 仮想サービスへ接続
  • リアルサーバを削除
  • 仮想サービスを削除
# modprobe ip_vs_test
# ipvsadm -A -t 10.0.0.1:80 -s test
kernel: IPVS: ip_vs_sched_getbyname(): sched_name "test"
kernel: IPVS: ip_vs_test: init_svc

# ipvsadm -a -t 10.0.0.1:80 -r 192.168.0.1:80
kernel: Enter: ip_vs_add_dest, net/ipv4/ipvs/ip_vs_ctl.c line 780
kernel: Enter: ip_vs_new_dest, net/ipv4/ipvs/ip_vs_ctl.c line 732
kernel: Leave: ip_vs_new_dest, net/ipv4/ipvs/ip_vs_ctl.c line 764
kernel: IPVS: ip_vs_test: update_svc
kernel: Leave: ip_vs_add_dest, net/ipv4/ipvs/ip_vs_ctl.c line 869

# telnet 10.0.0.1 80
kernel: IPVS: Bind-dest TCP c:10.211.55.5:2892 v:10.0.0.1:80 d:192.168.0.1:80 fwd:R s:0 conn->flags:183 conn->refcnt:1 dest->refcnt:2
kernel: IPVS: Schedule fwd:R c:10.211.55.5:2892 v:10.0.0.1:80 d:192.168.0.1:80 conn->flags:1C3 conn->refcnt:2
kernel: IPVS: TCP input  [S...] 192.168.0.1:80->10.211.55.5:2892 state: NONE->SYN_RECV conn->refcnt:2
kernel: IPVS: Unbind-dest TCP c:10.211.55.5:2892 v:10.0.0.1:80 d:192.168.0.1:80 fwd:R s:3 conn->flags:183 conn->refcnt:1 dest->refcnt:2

# ipvsadm -d -t 10.0.0.1:80 -r 192.168.0.1:80
kernel: Enter: ip_vs_del_dest, net/ipv4/ipvs/ip_vs_ctl.c line 997
kernel: IPVS: ip_vs_test: update_svc
kernel: Leave: ip_vs_del_dest, net/ipv4/ipvs/ip_vs_ctl.c line 1024

# ipvsadm -D -t 10.0.0.1:80
kernel: IPVS: ip_vs_test: done_svc

今日のまとめ

init_service

仮想サービスを追加するときに呼び出されます(ipvsadm -A)

done_service

仮想サービスを削除するときに呼び出されます(ipvsadm -D)

update_service

リアルサーバを追加するときに呼び出されます(ipvsadm -a)
リアルサーバを削除するときに呼び出されます(ipvsadm -d)

schedule

仮想サービスへ接続されたときに呼び出されます

SpamAssassin結構いいかも

昨日から Mail-SpamAssassin-3.2.1.tar.gz を入れて様子をみてますが、なかなかいい感じです。でもまあしばらくは大事なメールがSPAM判定されていないかをチェックする日々が続きそうです。
ただ困ったことが一点あります。スパム判定されたメールにはレポートメッセージがマルチパートで追加されて「Content preview:」に元メールの先頭部分が挿入されます。これを見ることで本当にSPAMなのかどうかの確認がしやすくなるのだと思いますが、このレポートメッセージの「Content-Type:」 には'charset=iso-8859-1'が入ってるんですね。そのため、日本語のメールが化けてしまうので、毎回わざわざオリジナルメッセージを確認しなければなりません。Content-Typeは引き継いでくれてもいいような気はするんですけどね。なにか問題あるのかな。

spamassassin-3.2.1 入れてみた

なにげに手作業での処理がめんどくさくなってきたので、いまさらながら Mail-SpamAssassin-3.2.1.tar.gz を入れてみました。とはいえ、今のところなんにも考えずに動かしてみて、なんにも考えなくてもそれなりに動いているので、なんにも書くことはないです(^^;;
このまましばらく様子をみつつ、何かネタが見つかったら出してみたいとおもいます。

#でもあれだ、スパムフィルタって、何も考えずに動かしても期待する動作が得られるのが理想かね(w