2012/10/28

DVDイメージを一括mount

DVDのコピーが違法行為なので、意味がないけど。
DVDイメージコピーをDLNAサーバに突っ込んでおくと、いちいちDVDを入れ替えなくてもDLNA機器のリモコン操作だけで次々見れるので、快適。というわけで、DVDイメージを一括でmount/unmountするscript。FreeBSD用。むかーしむかーしにイメージを作っていたという人にしか役に立たないけど。
ネットで視聴なら、こんなことしなくてもリモコン操作だけでラクラクだけどね。

一括マウント。マウント先は/media。DVD毎にフォルダを分けてイメージを作成したと想定。

#!/usr/local/bin/bash
FILES=$(/usr/bin/locate ISO | /usr/bin/grep '^/int.*ISO$')

for isofile in $FILES
do
  devnum=$(/sbin/mdconfig -a -t vnode -f "${isofile}")
  dir=$(/usr/bin/dirname ${isofile}|/usr/bin/sed 's/\.ISO$//')
  name=$(/usr/bin/basename $dir)
  /bin/mkdir /media/$name > /dev/null 2>&1
  /sbin/mount -r -t cd9660 /dev/$devnum /media/$name
done

一括アンマウントは

#!/usr/local/bin/bash
DEVS=$(/bin/ls /dev/md* |/usr/bin/grep -v ctl)

for target in $DEVS
do
  num=$(/bin/echo $target|/usr/bin/sed 's/^.*md//')
  /sbin/umount $target
  /sbin/mdconfig -d -u $num
done
/bin/rmdir /media/*

スクリプトは可読性を高めるため、bash用の拡張構文を使用。たいしたことはやっていないので、/bin/sh の範囲でも書けるけど、仕事でメンテナンス性の高いコードを書けるよう、普段から練習。


クックブックは電子書籍の方が便利だと思う。O'Reilly Japan - bashクックブック



2012/10/27

メジャーアップデートしたMunin

Muninがアップデートして、2.0になった。FreeBSDのportsにも反映されたので、アップデート。
Node(監視される側)は一部1.4.6のままだけど、情報は問題なく取得できている。
何より嬉しいのは、ちゃんとIPv6に対応していること。munin.conf に address を書くとき、[]で囲むようにすればOK。というわけで、v6パッチのお役目終了。


2012/10/21

Software Design 11月号

第2特集はMunin。仕事でもプライベートでも、サーバのリソース監視で絶賛活躍中。監視対象となるリソースを接続or動作させている状態でMuninをインストールすると、リソース監視の設定をある程度やってくれる。というか、多くの場合は、それでOK。監視のための設定コストを低く始められるので、取り敢えず始めて、見たいものが出てきたら順次追加、というパターンでOK。死活監視としての利用はちょっと微妙なので、Monitという別のソフトを利用している。

そういえば、日本Muninユーザ会というのができたんだそうだ。
Munin.jp - Munin User Group Japan



2012/10/13

最近買った本

ガベージコレクションのアルゴリズムと実装 まぁ、マニアックといえばマニアックかもしれないけど。どんな仕組みで動いているのか知っていて損はないし、そういった奥行きが足腰の強さに現れてくるのではないか?


リバースエンジニアリング ―Pythonによるバイナリ解析技法 (Art Of Reversing)こっちもまぁマニアックだけど、上とはまたちょっと違ったレイヤー。どんな動きをしているのか、出来上がっているものを調べる側。




2012/09/30

FreeBSDのVPN ServerでIPv6をバラまく

Hiroaki's blog: LinuxのVPN ServerでIPv6をバラまくと同じことをFreeBSDでやってみる。

L2TP/IPsecを使うので、Kernelのパラメータを修正してbuild。これはIPsecを使おうとしたときには毎度おなじみの話なので、ここでは省略。詳しくは、[FreeBSD] L2TP/IPsecサーバを立てる | 雑記帳などを参照。
次。mpd5の話。インストールについては、L2TP/IPsecをやるときと同じなので、略。IPv6をばら撒くための、設定について。IPv6 Part 6: Configuring An IPv6 Network Assigned Over PPPoE Using FreeBSD » mmacleod.caがとても参考になる。
/usr/local/etc/mpd5/mpd.conf の内容。
startup:
        set user kanri irnak admin
        set console self 127.0.0.1
        set console open
        log +ipcp +ipv6cp +lcp +link +auth +ecp +ccp
        set console enable logging

default:
        load l2tp_server

l2tp_server:
        create bundle template B4
        set bundle enable ipv6cp
        set iface idle 1800
        set iface mtu 1454
        set iface enable tcpmssfix
        set iface up-script /usr/local/etc/mpd5/mpd.linkup
        set iface down-script /usr/local/etc/mpd5/mpd.linkdown
        create link template L4 l2tp
        set link action bundle B4
        set link enable multilink
        set auth enable system-acct
        set link keep-alive 10 60
        set link disable chap eap
        set link enable chap-msv2
        set link mtu 1454
        set link mru 1454
        set link enable incoming
ポイントは、ipv6cpを有効にしていること。IPv4の割り当ては行わないので、ipcpは記述しない。
つながったらroutingやfirewallの設定の変更が必要になるので、それらは up-sciptで指定した /usr/local/etc/mpd5/mpd.linkup に書く。
1つ目の引数がInterface(ng0とかng1とか)なので、
/sbin/ifconfig $1 inet6 割り当てるアドレス prefixlen 割り当てるprefix alias
とかしてあげればOK。ルーティングは適当に。例えば、P2Pだからってprefixを長くしてあげると、アドレスが一番マッチしたルートを使おうとするので、ルーティングを意識しなくてもよきに計らってくれたりする。
同様に、切断されたときの処理を down-scriptで指定した /usr/local/etc/mpd5/mpd.linkdown に書く。

クライアントのMac OSX側。こちらは、Hiroaki's blog: LinuxのVPN ServerでIPv6をバラまくと同じ。


Wifiスポットなど、まだIPv6を割り当ててくれないサービスも多く、6to4が通らないことも多いので、こういう無理やりな方法がまだまだ必要。


2012/09/17

OSXでDNS Serverを変更する

Mt. Lion(に限らず、OSX)では、/etc/resolv.conf は飾りなので、書き換えたところで実質的には意味が無い。Configure DNS lookups from the terminal - Mac OS X HintsにDNS Serverの変更方法が説明されている。それを参考に作ったスクリプトが、これ。
#!/bin/bash
if [ $# -lt 1 ]; then
    echo "$(/usr/bin/basename $0) DNSServer [DNSServer ...]"
    exit 1
fi
key=$(echo 'show State:/Network/Global/IPv4' | /usr/sbin/scutil | /usr/bin/awk '/PrimaryService/{print $3;}')
domain=$(echo "show State:/Network/Service/${key}/DNS" | /usr/sbin/scutil | /usr/bin/awk '/DomainName/{print $3;}')

/usr/sbin/scutil <<EOF
open
d.init
d.add ServerAddresses * $*
d.add DomainName ${domain:-localdomain}
set State:/Network/Service/${key}/DNS
quit
EOF

って、上記記事のコメントにも同じようなスクリプトが載っていたorz。まぁ、こっちは、割り当てられたDNS ServerがAAAA filterしちゃっているときに変更するのが目的なので、ドメイン名は既に割り当てられているものを使うようにしている。


2012/08/27

IPsecでNASへのバックアップ通信を暗号化する(NAS側)

Hiroaki's blog: IPsecでNASへのバックアップ通信を暗号化するの続き。AFP(Netatalk)の動いている、バックアップデータを受け取るサーバ側の設定。FreeBSD 7.3 + IPsec Toolsで。

kernelの再構築や、IPsec Toolsのインストールは略。
まずは/usr/local/etc/racoon/racoon.conf の内容。
# $Id: racoon.conf,v 1.3 2012/08/25 04:47:32 root Exp root $
#
# $KAME: racoon.conf.in,v 1.18 2001/08/16 06:33:40 itojun Exp $

# "path" must be placed before it should be used.
# You can overwrite which you defined, but it should not use due to confusing.
path include "/usr/local/etc/racoon" ;
#include "remote.conf" ;

# search this file for pre_shared_key with various ID key.
path pre_shared_key "/usr/local/etc/racoon/psk.txt" ;

# racoon will look for certificate file in the directory,
# if the certificate/certificate request payload is received.
path certificate "/usr/local/etc/cert" ;

# "log" specifies logging level.  It is followed by either "notify", "debug"
# or "debug2".
log info;

# "padding" defines some parameter of padding.  You should not touch these.
padding
{
        maximum_length 20;      # maximum padding length.
        randomize off;          # enable randomize length.
        strict_check off;       # enable strict check.
        exclusive_tail off;     # extract last one octet.
}

# if no listen directive is specified, racoon will listen to all
# available interface addresses.
listen
{
        #isakmp ::1 [7000];
        #isakmp 202.249.11.124 [500];
        #admin [7002];          # administrative's port by kmpstat.
        #strict_address;        # required all addresses must be bound.
#       isakmp IPv4アドレス [500];
        isakmp IPv6アドレス [500];
}

# Specification of default various timer.
timer
{
        # These value can be changed per remote node.
        counter 10;             # maximum trying count to send.
        interval 3 sec; # maximum interval to resend.
        persend 1;              # the number of packets per a send.

        # timer for waiting to complete each phase.
        phase1 30 sec;
        phase2 30 sec;
}

remote anonymous
{
        exchange_mode aggressive, main;
        doi             ipsec_doi;
        situation       identity_only;
        generate_policy on;
        proposal_check  obey;
        dpd_delay       20;
        passive         on;
        support_proxy   on;
        ike_frag        on;
        verify_identifier off;
    proposal {
            authentication_method rsasig;
            encryption_algorithm 3des;
            hash_algorithm sha1;
            dh_group 2;
    }
    proposal {
        encryption_algorithm    aes;
        hash_algorithm          sha256;
        authentication_method   pre_shared_key;
        dh_group                modp1024;
    }
    proposal {
        encryption_algorithm    3des;
        hash_algorithm          sha256;
        authentication_method   pre_shared_key;
        dh_group                modp1024;
    }
   proposal {
      authentication_method pre_shared_key;
      hash_algorithm sha1;
      encryption_algorithm aes 256;
      lifetime time 3600 sec;
      dh_group 2;
   }
   proposal {
      authentication_method pre_shared_key;
      hash_algorithm sha1;
      encryption_algorithm aes;
      lifetime time 3600 sec;
      dh_group 2;
   }
   

   proposal_check strict;
}

# Phase 2 proposal (for IPsec SA)
sainfo anonymous
{
        pfs_group 2;
        lifetime time 10 minutes;
        encryption_algorithm aes, rijndael, 3des;
        authentication_algorithm hmac_sha1;
        compression_algorithm deflate;
}
client側のIPアドレスは固定ではないため、anonymousで定義する必要がある。remote {} も sainfo{} も、client側とパラメータをそろえること。proposalは一致するものがあればよい。

次。共有キー。psk.txtは
メールアドレス 共有キー

とする。メールアドレスは、client側のconfファイルのremote{}で指定したメールアドレスとする。aggressive modeで接続してくるため、IDとしてIPアドレス以外のものも使用できるのだ。クライアント側でmy_identifier  user_fqdnとしているので、同じメールアドレスを使う。

最後に、どの通信を暗号化対象にするかを /usr/local/etc/racoon/setkey.conf に指定する。
# $Id: setkey.conf,v 1.2 2012/08/25 11:53:25 root Exp $
flush;
spdflush;
spdadd ::1 ::1 any -P in none;
spdadd ::1 ::1 any -P out none;
spdadd NASのあるネットワーク/マスク[0] NASのアドレス/0[548] any -P in none;
spdadd NASのアドレス/0[548] NASのあるネットワーク/マスク/マスク[0] any -P out none;
spdadd ::/0[0] NASのアドレス/0[548] any -P in ipsec esp/transport//require;
spdadd NASのアドレス/0[548] ::/0[0] any -P out ipsec esp/transport//require;
noneで終わっている行は、暗号化を行わない指定。自分自身と通信する場合、自分と同じネットワーク内と通信する場合は、暗号化を行わないようにする。
暗号化を行う条件は、ちょうどクライアント側とin/outを逆にした関係になる。

これでOK。動作確認。Wiresharkなどでパケットをキャプチャしながら、クライアント側で
telnet NASのアドレス 548
とする。IKASMPのパケットの後ESPのパケットになり、かつ、telnetが接続できていればOK。

racoon.conf でlogをdebugにすると、内容が膨大で接続の確認には向かない。tcpdumpやwiresharkでパケットをキャプチャして、どこまで進んでいるかを確認しながら設定を見直すのが現実的。
Phase1が確立していなければ、remote {} の部分とpsk.txtを確認。Phase2であればsainfo。また、暗号化されずにtcp/548に接続しに行っているようであれば、setkeyに喰わせるファイルを確認。firewallでrejectされていることもあるので、設定を見直すことも必要。UDP 500/4500とプロトコル番号50/51。
ログやtcpdumpの出力は意外と見かけないのが、また切り分けを難しくしていると思う。
が参考になる。
また、他に参考にしたページがいくつか。



2012/08/26

IPsecでNASへのバックアップ通信を暗号化する

外出先であっても時間がくればTimeMachineが動いてバックアップを行うので、ネットワークに繋がっているのであれば、普段使っているNASに取って欲しいところ。
この時、バックアップで使うAFPというプロトコルは暗号化を行わないので、別の手段で暗号化を行う必要がある。そこで、IPsecで暗号化を行なってみる。

  • IPv6での通信とする
  • NASのあるネットワークにいる場合、暗号化は行わない
  • 事前共有キーによる暗号化

の3点を条件とする。

  • クライアント側はMountain Lion。racoonを使う。
  • サーバ側はFreeBSD。racoonを使う。

直接設定ファイルを書くことによって、「AFPだけ暗号化する」ようにする。
お約束だが、内容の真偽も含めて、自己責任で。最悪、どこでもMy Macが動かなくなることもあるので。

Mountain Lion側。
/var/run/racoon/ に動的生成した設定ファイルを置くようになっている様子だが、消されそうなので、/etc/racoon/remote/ を作って、そこに置くことにする。
そこに置いた設定ファイルが取り込まれるよう、/etc/racoon/racoon.conf に手を加える。racoonはどこでもMy Macで使っているので、ミスがあると動かなくなるので注意。
racoon.confの最後に
include "/var/run/racoon/*.conf" ;
という行があるので、その手前(上の行)に
include "/etc/racoon/remote/*.conf" ;
を加える。
次に、/etc/racoon/remote/ に、NASに接続するための設定ファイルを作成する。ファイル名は.confで終わっていればなんでも良いので、例えば、NASのhost名にする。
中身は
remote NASのIPアドレス {
   exchange_mode aggressive;
   doi ipsec_doi;
   situation identity_only;
   verify_identifier off;
   my_identifier  user_fqdn       "メールアドレス";
   initial_contact off;
   support_proxy on;
   proposal_check obey;
   
    proposal {
        encryption_algorithm    aes;
        hash_algorithm          sha256;
        authentication_method   pre_shared_key;
        dh_group                modp1024;
    }
    proposal {
        encryption_algorithm    aes;
        hash_algorithm          sha1;
        authentication_method   pre_shared_key;
        dh_group                modp1024;
    }
    proposal {
        encryption_algorithm    3des;
        hash_algorithm          sha256;
        authentication_method   pre_shared_key;
        dh_group                modp1024;
    }
    proposal {
        encryption_algorithm    3des;
        hash_algorithm          sha1;
        authentication_method   pre_shared_key;
        dh_group                modp1024;
    }
 
   proposal {
      authentication_method pre_shared_key;
      hash_algorithm sha1;
      encryption_algorithm aes 256;
      lifetime time 3600 sec;
      dh_group 2;
   }
   
   proposal {
      authentication_method pre_shared_key;
      hash_algorithm md5;
      encryption_algorithm aes 256;
      lifetime time 3600 sec;
      dh_group 2;
   }
    proposal {
      authentication_method pre_shared_key;
      hash_algorithm sha1;
      encryption_algorithm aes;
      lifetime time 3600 sec;
      dh_group 2;
   }
   
   proposal {
      authentication_method pre_shared_key;
      hash_algorithm md5;
      encryption_algorithm aes;
      lifetime time 3600 sec;
      dh_group 2;
   }
   
   proposal {
      authentication_method pre_shared_key;
      hash_algorithm sha1;
      encryption_algorithm 3des;
      lifetime time 3600 sec;
      dh_group 2;
   }
   
   proposal {
      authentication_method pre_shared_key;
      hash_algorithm md5;
      encryption_algorithm 3des;
      lifetime time 3600 sec;
      dh_group 2;
   }
}
とする。
ポイントはexchange_mode。これをaggressiveだけにしているので、Phase 1はaggressive modeで折衝が開始される。そのため、事前共有キーファイルでの相手のIDとしてIP Address以外のものも使えるようになる。 受け側のracoonでは相手のIDとして*(任意の文字列)が使えないため、割り当てられるAddressが固定ではないモバイル環境からアクセスする場合、メールアドレスなど、IP Address以外のものを指定する必要があるため、aggressive modeは必須となる。
通常であれば、この後にsainfoを定義するのだが、既に定義されているのだ。/var/run/racoon/anonymous.conf がそれ。
sainfo の部分はこんな感じ
sainfo anonymous { 
  pfs_group 2;
  lifetime time 10 min;
  encryption_algorithm aes;
  authentication_algorithm hmac_sha1;
  compression_algorithm deflate;
}
ここまでで、暗号化するときの各種パラメータを定義した。後は、どの通信を暗号化するのかを定義する。これは、「NASにバックアップする通信」なので、NASに送る奴とNASから返ってくる奴とを定義する。
例えば、/etc/racoon/NASのhost名.key というファイルに
spdadd NASのいるネットワーク/マスク[0] NASのアドレス[548] any -P out none;
spdadd NASのアドレス[548] NASのいるネットワーク/マスク[0] any -P in none;
spdadd ::/0[0] NASのアドレス[548] any -P out ipsec esp/transport//require;
spdadd NASのアドレス[548] ::/0[0] any -P in ipsec esp/transport//require;
と書く。これをsetkey -f で喰わせてやればよいのだが、マシンを起動するたびに忘れずに実行する必要があるので、例えば、rootでcrontab -eして
@reboot /usr/sbin/setkey -f /etc/racoon/NASのhost名.key
と書いておく。

最後に、事前共有キー。/etc/racoon/psk.txt が既にあるので、同じように、
NASのアドレス 事前共有キー
の1行を追加しておく。

サーバに繋ぐ前に、一度Macを再起動させて問題なさそうなことを確認しておく。
再起動してログインしたら、アプリケーション→ユーティリティ→コンソール.app を開く。左側のログリストで「すべてのメッセージ」を選択し、右側に表示されるracoonのメッセージを確認する。問題なければ、「IPSec Phase2 established」の行が表示されているはず。何らかのエラーが出ているようであれば、/etc/racoon/racoon.conf および /etc/racoon/remote/*.conf の中身を確認して修正、racoonの再起動(わからなければ、Macの再起動)。放置しておくと、どこでもMy Macで接続できないことがある!

サーバ側の設定についてはまた今度。


2012/08/19

LinuxのVPN ServerでIPv6をバラまく

IPv4アドレスが枯渇したと言われているが、3Gや公衆のWifiスポットで未だにIPv6アドレスが割り当てられることはない。しかも、割り当てられるIPv4アドレスがPrivateアドレスで、6to4などのtunnelが使えなかったりする。
割り当てられないのであれば、自分で割り当てよう!ということで、L2TPで繋いだVPNでIPv6を流してみる。

今回は、IPv6アドレスを割り当てられているLinux(CloudCoreのCentOS 5.8)に対してOSX(Mountain Lion)からVPN接続し、踊る亀を見られるようにする。

まずはLinux側。すでに何らかの形で固定のIPv6アドレスを、/64よりも大きなブロックで割り当てていることが前提。6to4で割り当てられるのは/48なので、それでもOK。
最初は普通にL2TPのサーバにする。さくらのVPSでiPhone用の野良WiFi通信傍受対策のL2TP/IPsec(VPN)を設定したメモ(CentOS5) - nori_no のメモなどを参考に。openswanもxl2tpdもrpmがあったので、それでインストール。とりあえずはIPv4のアドレスを割り当てるようにする。

次に、Mountain Lion側。システム環境設定→ネットワーク で、新しいサービスを追加する。
  • インターフェイス:VPN
  • VPN タイプ:L2TP over IPSec
  • サービス名:適当に
さらに
  • サーバアドレス:Linuxのホスト名またはIPv4アドレス
  • アカウント名:Linuxの/etc/ppp/chap-secrets に書いたclientの名前
とし、認証設定ボタンを押下。
  • コンピュータ認証:/etc/ipsec.secret に書いたパスワード
  • パスワード:/etc/ppp/chap-secrets に書いたパスワード
をセットして、OK。
適用ボタンを押して設定を保存したら、接続ボタンを押して、繋がるかどうか確認。詳細...ボタンを押して、TCP/IPの欄でIPv4 アドレスやサブネットマスクなどが割り当てられていることを確認する。
  • Linux側では、UDPの1701, 500, 4500が通信できるようにiptablesが設定されていること。
  • 設定に問題ないようであれば、ipsecを再起動してみる(sudo /sbin/service ipsec restart)
  • Mountain Lion側では、パスワードを正しく設定していること。
が確認ポイント。IPv4で使う訳ではないので、NATやルーティングは設定不要。

ここまでできたら、IPv6用の設定に移る。
Linux側の /etc/ppp/options.xl2tpd の最後に、

+ipv6
ipv6cp-accept-local
を追加。 /etc/sysconfig/network-scripts にifcfg-ppp0 というファイル(ppp1やppp2かもしれないが、通常は0でOK)を新たに作る。中身は

IPV6INIT=yes
IPV6ADDR=受け側のIPv6アドレス/64
とする。

次。/etc/ppp/ipv6-up がバグっているような気がしてならない。ので、ちょっと手を入れる。
47行目前後にある

CONFIG=$REALDEVICE


CONFIG=$LOGDEVICE
にする。
そして、/etc/ppp にipv6-up.local というファイルを作る。中身は

#!/bin/bash

PREFIX="割り振るIPv6のネットワーク"
/sbin/ip6tables -P FORWARD ACCEPT
/sbin/ip6tables -A FORWARD -i $1 -j ACCEPT 
/sbin/ip6tables -A INPUT -i $1 -j ACCEPT 
とする。ifcfg-ppp0で/64としているので、PREFIXは::/64 で終わるアドレスとなる。このファイルに実行権限をつけることを忘れないこと。これは、IPv6アドレスが割り当てられた時に実行される。

Mountain Lion側は、システム環境設定→ネットワーク でさっき作ったサービスを選択し、詳細... のTCP/IP タブを選択。IPv4 の構成を「切」にする。
また、/etc/ppp/ipv6-up というファイルを作り、実行権限を付けておく。中身は

#!/bin/sh
/sbin/ifconfig $1 inet6 add 割り当てるIPv6アドレス/64
/sbin/route add -inet6 default $4%$1
/sbin/route change -inet6 default $4%$1
とする。Linux側で最後を::1として、Mountain Lion側では::2とするとわかりやすいかも。
これで設定はOK。作った「サービス」に繋いだ後、踊る亀にアクセスして、亀の絵がアニメーションしていればIPv6で接続できている。


本来であれば、Linux側でradvdを動かしていればMountain Lion側のIPv6アドレスは自動で割り当てられるのだが、 IPv6 Address on L2TP VPN: Apple Support Communitiesで指摘されているように、アドレスの割当が行われない。そのため、/etc/ppp/ipv6-up を用意して手動で割当を行っている。


2012/08/11

もうちょっと数当てゲーム

Hiroaki's blog: もう一回 数当てゲームで残した乱数の部分を見てみる。
isUsedNum :: Char->String->Int
isUsedNum _ [] = 0
isUsedNum a (x:xs)
             | a == x    = 1 + isUsedNum a xs
             | otherwise = isUsedNum a xs

isSuitableNum :: String->Bool
isSuitableNum [] = True
isSuitableNum (x:xs) = isUsedNum x xs == 0 && isSuitableNum xs

normNum :: String->String
normNum xs
        | (l < 4)       = normNum $ "0" ++ xs
        | otherwise     = xs
        where l = length xs

targetNum :: [Int]->String
targetNum (x:xs)
          | (isSuitableNum strX) = strX
          | otherwise            = targetNum xs
          where strX = normNum $ show x

main = do
     r <- getStdRandom $ randomR (0, 99)
     let t = targetNum $ randomRs (0,9999) (mkStdGen r)
  • 乱数で4ケタの数字を求める。この時、数字はいくつも生成してリストにしておく
  • リストの頭から数字を1つ取り出し、数当てゲームで使える数字だったら採用。違ったら、リストの頭の数字を捨てて、リストの頭から数字を取りだすところからやり直し。→ targetNum
  • 採用した数字が3ケタだったら、頭に0をつける。→ normNum
  • 数当てゲームで使えるかどうかは isSuitableNum。一桁ずつ取り出して、既に使っているかどうかを順番にチェック→isUsedNum
isUsedNumって、elem使えばもっと簡単になるよね。あと、「数字がダブらないこと」という条件があるから、求める4ケタの数字は0123~9876になる。もっとも、これはコメント入れておかないと混乱することになるけど。

では、数当て問題 - Hit & Blow -: ツムジのひとりごとのコードを拝見。

-- xs からランダムに重複しない n 個の要素を取り出す
randomPicUp :: Eq a => Int -> [a] -> IO [a]
randomPicUp n xs = loop n (length xs - 1) xs []
  where
    loop 0 _ _ prd = return prd
    loop c m xs prd = do
      g <- newStdGen
      let (i, _) = randomR (0, m) g
      let x = xs !! i
      loop (c - 1) (m - 1) (delete x xs) (x : prd)

main :: IO ()
main = do
  target <- randomPicUp 4 ['0'..'9']
大きな違いは、
  • 0~9から数字を1個選ぶ。これを4回行って、4けたの数字とする
という考え方だろう。randomPicUpの中にある、loop c m xs prdがそれだ。
  • cはループカウンタかな?xsは選び出す数字の候補で、最初は0~9だけど、選ばれた数字を除外していく。mは選び出す数字の候補の数のようだから、xsの長さから求めればいいよね。
  • prdは選んだ数字のリスト。これを数えるようにすれば、cはいらなくなるかな?
とはいえ、ここまでシンプルに書けるのね~。


2012/08/09

もう一回 数当てゲーム

数当て問題 - Hit & Blow -: ツムジのひとりごとで取り上げていただいたので、自分のコードと比べてみる。

このゲームの肝であるHitとBlowを求める処理のうち、まずはBlowから。
Blowは、「数字として当たっているし、場所(桁)も当たり」ということ。

countBlow :: String->String->Int
countBlow [] _ = 0
countBlow _ [] = 0
countBlow (x:xs) (y:ys)
          | x == y    = 1 + countBlow xs ys
          | otherwise = countBlow xs ys
  • 文字列を2つ受け取って、それぞれの先頭を比較する。一致していたら、戻り値に1を加える。
  • 文字列が終わりでなかったら、比較しなかった残りの文字列それぞれを引数にして、自分を呼び出す。
  • 受け取った文字列のうち、どちらかが空だったら、0を返す
で、それを
  • 文字列を2つ受け取って、それぞれの桁について、同じ数字だったらTrue、違ったらFalseにする→受け取った文字列と同じ長さの、Boolのリストができる
  • そのBoolのリストのうち、Trueの数がBlow
と考える。
そうすると、文字(Char)のリストである文字列を2つ受け取って、それぞれの要素に対して関数を適用する→zipWithの出番
また、BoolのリストでTrueを数えるのは
  • filterでリストのうちTrueのものだけにする
  • それからリストの要素数を数える
とすればよいので、

countTrue :: [Bool] -> Int
countTrue = length . filter (== True)

となる。
結局、Blowは

countBlow :: String -> String -> Int
countBlow target = countTrue . zipWith (==) target

で求められると。


次。Hit。
Hitは、「数字としては当たっているけど、場所(桁)は違う」ということ。
単純に「数字として当たっている」ものを数えてしまうと、Blowもカウントしてしまう。でも、Blowも求めるのだから、「数字として当たりの数」- Blow として求めればよい。
ということで、どうやって「数字として当たりの数」を求めるか?

isUsedNum :: Char->String->Int
isUsedNum _ [] = 0
isUsedNum a (x:xs)
             | a == x    = 1 + isUsedNum a xs
             | otherwise = isUsedNum a xs

countUsedNum :: String->String->Int
countUsedNum [] _ = 0
countUsedNum (x:xs) ys = isUsedNum x ys + countUsedNum xs ys

うーん、回りくどい。2つの文字列を引数として受け取って、1つ目の文字列を1文字ずつ、2つ目の文字列の構成要素か見ている(isUsedNum)。これも、文字列を1文字ずつ比較しているから、遅くなるはず。
こっちも、Blowと同様、「構成要素であればTrue」のリストを作って、最後にTrueの数を数えればいいわけだ。

countHit :: String -> String -> Int
countHit target = countTrue . map (`elem` target)

mapが肝かな?関数とリストを受け取って、リストの1つ1つの要素に対して関数を適用する(関数で処理する)。だから、mapの結果のリストって、与えたリストと同じ長さになる
で、mapにどんな関数を与えるかというと、target の構成要素かどうかを返す関数を与える。あとは、Trueの数を数えればOK。


リストの使い方がうまくない。まぁ、もっといろいろプログラム書いて慣れないとね。


2012/08/05

数あてゲームのバグをつぶす

Hiroaki's blog: Haskellで数あてゲームを書いてみる
にはバグがある。
当てる数を0以上9999以下の乱数で求めているのだが、この数字を文字列にする際、頭の0が失われるのだ。
だから、求める数字が4つにならないことがある。また、数が100未満の場合、0が複数回使われていることをチェックできないのだ。
というわけで、ゲームのルールに従っている数字かどうかをチェックする前に、4桁になるような処理を追加してみた。

import System.Random
import Control.Monad(when)

isUsedNum :: Char->String->Int
isUsedNum _ [] = 0
isUsedNum a (x:xs)
             | a == x    = 1 + isUsedNum a xs
             | otherwise = isUsedNum a xs

countUsedNum :: String->String->Int
countUsedNum [] _ = 0
countUsedNum (x:xs) ys = isUsedNum x ys + countUsedNum xs ys

countBlow :: String->String->Int
countBlow [] _ = 0
countBlow _ [] = 0
countBlow (x:xs) (y:ys)
          | x == y    = 1 + countBlow xs ys
          | otherwise = countBlow xs ys

resultString :: String->String->(Bool, String)
resultString xs ys =
             (endFlag, show hit ++ " hit(s) " ++ show blow ++ " blow(es).")
             where blow = countBlow xs ys
                   hit = countUsedNum xs ys - blow
                   endFlag = blow == 4

isSuitableNum :: String->Bool
isSuitableNum [] = True
isSuitableNum (x:xs) = isUsedNum x xs == 0 && isSuitableNum xs

checkNum :: String->IO ()
checkNum compNum = do
         putStrLn "Number ? "
         guessNum <- getLine
         when (not $ null guessNum) $ do
              let (flag, msg) = resultString compNum guessNum
              putStrLn msg
              when (not flag) $ checkNum compNum
         return ()

normNum :: String->String
normNum xs
        | (l < 4)       = normNum $ "0" ++ xs
        | otherwise     = xs
        where l = length xs

targetNum :: [Int]->String
targetNum (x:xs)
          | (isSuitableNum strX) = strX
          | otherwise            = targetNum xs
          where strX = normNum $ show x

main = do
     r <- getStdRandom $ randomR (0, 99)
     let t = targetNum $ randomRs (0,9999) (mkStdGen r)
     checkNum t
     putStrLn t


2012/07/29

Haskellで数あてゲームを書いてみる

言語は実際にプログラムを書いてみないと身に付かない。ということで、昔、ポケコンでよく見かけた数あてゲームをHaskellで書いてみた。
コンピュータが生成した数字をあてる。
コンピュータは、予想した数に対して
 Hit: 数字はあっているが、場所が異なる数字の数
 Blow:数字も場所もあっている数字の数
の2種類のヒントを返す

import System.Random
import Control.Monad(when)

isUsedNum :: Char->String->Int
isUsedNum _ [] = 0
isUsedNum a (x:xs)
             | a == x    = 1 + isUsedNum a xs
             | otherwise = isUsedNum a xs

countUsedNum :: String->String->Int
countUsedNum [] _ = 0
countUsedNum (x:xs) ys = isUsedNum x ys + countUsedNum xs ys

countBlow :: String->String->Int
countBlow [] _ = 0
countBlow _ [] = 0
countBlow (x:xs) (y:ys)
          | x == y    = 1 + countBlow xs ys
          | otherwise = countBlow xs ys

resultString :: String->String->(Bool, String)
resultString xs ys =
             (endFlag, show hit ++ " hit(s) "
                                ++ show blow ++ " blow(s).")
             where blow = countBlow xs ys
                   hit = countUsedNum xs ys - blow
                   endFlag = blow == 4

isSuitableNum :: String->Bool
isSuitableNum [] = True
isSuitableNum (x:xs) = isUsedNum x xs == 0 && isSuitableNum xs

checkNum :: String->IO ()
checkNum compNum = do
         putStr "Number ? "
         guessNum <- getLine
         when (not $ null guessNum) $ do
              let (flag, msg) = resultString compNum guessNum
              putStrLn msg
              when (not flag) $ checkNum compNum
         return ()

targetNum :: [Int]->String
targetNum (x:xs)
          | (isSuitableNum strX) = strX
          | otherwise            = targetNum xs
          where strX = show x

main = do
     r <- getStdRandom $ randomR (0, 99)
     let t = targetNum $ randomRs (0,9999) (mkStdGen r)
     checkNum t
     putStrLn t

乱数はすごいHaskellたのしく学ぼう! の通りにはいかなかった。パッケージングが変更になったんだそうだ。
cabal install random
としてパッケージを追加。また、使い方はRandom モジュールの使い方 : tnomuraのブログが参考になった。



2012/07/27

Mountain LionでPostfixの設定が変わった

OS Xのmail daemonはPostfixなのだが、Mountain Lionになってディレクトリがちょっと変わったようだ。Lionの時の設定のままだと、起動に失敗する。
確認すべき設定は、main.cfの2カ所。queue_directoryとdata_directoryに指定しているディレクトリがなくなっているので、Mountain Lion用の設定に変える。
queue_directory = /var/spool/postfix
data_directory = /var/lib/postfix


2012/07/15

これならすぐに実践投入できる!リーダブルコード

ちょっと話題のリーダブルコード を早速読んだ。いいよ、これ。
人によっては、この程度のことはもう実践しているというだろうけど、実践できていないコードも山のようにある訳で。ちゃんとテストコードを書いてリファクタリングするときに、この本のテクニックを使うようにすれば、次に手を入れるとき、もっとやりやすくなっているはずだ。
Eclipseでは、関数なり変数なりにマウスカーソルをあてればJava Doc形式のコメントを表示するので、コメントを付けることも多いと思う。でも、メソッドの中はどう?
コメントなしで、コードで表現しきるのがbestなんだろうけど、名前が長くなりすぎて処理の流れが追いにくくなるのは本末転倒。どういう方針で付ければよいのか、ヒントというか答えの1つがこの本には書いてある。
そうそう、メソッドというか、関数の出口(return)を1カ所にしろというのは昔むかしの授業で言われた。 流れが見にくくなるし、無駄だなぁと思いながらも、レポートや試験ではフラグを追加して逃げた。 最終的にどういうコードに落ちるかを考えたら、用が済んだらとっととreturnすべきだし、本書でもそれを勧めている。
付録には、おすすめの本がリストされている。その中の1冊、JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス は眺めたことがあるけど、やっぱり、JavaScriptは言語仕様的に大規模なソフトウェアを書くのには向いていないんじゃないかな?


2012/07/08

広告を消す!

広告を表示するために時間がかかったり、セキュリティ的に問題のある中身が表示されたりということがあったりするので、表示しないようにしてみる。
すべてを非表示にするのは難しいけど、MicroAd を無効に!! の巻を参考にして設定変更したDNSを見るように、うちのネットワーク設定を変更。
広告がFlashだとそれだけでパワー喰うから、効果あるはず。


2012/07/01

LionでPogoplugネットワークにFirewallを設定する。

WindowsについてはHiroaki's blog: Pogoplugのネットワーク設定を見直す!でFirewallを設定したので、今度はMac OSX Lionで設定する。
LionのFirewallはPF: The OpenBSD Packet Filterなので、/etc/pf.*が設定ファイル。pf.confにルールを追加する。
PogoplugはInterface xcetap0を作成するので、xcetap0に関するルールを作成する。最後に0がついているということは、環境によってはxcetap1だったりxcetap2だったりすることも考えられると。
で、ルール。詳しいことはpfのマニュアルなり設定例なりをみればよいので、省略。
頭の方にpogo_if = "xcetap0"と書いて、一番最後に

#
# PogoPlug
#
block in on $pogo_if all
block out on $pogo_if all
pass out on $pogo_if proto tcp to any port { 80, 443 } keep state
pass out on $pogo_if proto udp to any port { 3333, 4365 } keep state

を追加。3333はkeep stateじゃなくて、inもpassにした方がいいのかな?この修正したルールを読み込み直せばOK。こういったことをやろうとする人はわかっているだろうから、読み込み直す方法とか細かい話は省略。
というか、わからないまま丸写しするようなことをしてはいけない。


2012/06/25

VPN上でIPv6する

これだけIPv6と言われているにも関わらず、公衆無線LANサービスでは、IPv6 addressを配っていない。それどころか、private addressを配布してキャリアグレードNATなんてところもある。global addressが割り当てられないということは、6to4できないということだ。
文句を言っても解決しないので、なんとかする方法を考えてみる。

LionだとOCN IPv6を使おうとしても、PPPが異常終了してしまう。IPv6 addressを持っているServerとVPNを張って、そこでIPv6 over IPv4すればよいのではないか?VPNが割り当てるIPv4のアドレスが固定ではないが、DTCP (Dynamic Tunnel Configuration Protocol)を使ってIPv6トンネルを掘ればよい。
というわけで、まずdtcpcへのLionパッチ。

--- dtcpc.rb-dist 2006-01-09 03:24:01.000000000 +0900
+++ dtcpc.rb 2012-02-26 19:34:35.000000000 +0900
@@ -96,7 +96,8 @@
 TUNIF_CLONING = true
 TUNNEL_CREATE = 'ifconfig %s tunnel %s %s'
 TUNNEL_DELETE = 'ifconfig %s deletetunnel'
-ROUTE_METHOD = ROUTE_IFP
+#ROUTE_METHOD = ROUTE_IFP
+ROUTE_METHOD = ROUTE_INTERFACE
 
 def usage()
   $stderr.print "usage: #{File.basename($0)} [-cdDlnU] [-b udpport] [-i if] [-m mtu] [-p port] [-t tuntype] [-u username] [-A addr] [-R dest] [-P prefix-delegation] server\n"

これを当ててmake installする。
dtcpcは自動で動かすことにして、/usr/local/etc/dtcpc.auth を用意する。これはマニュアル通り。
launchdで自動起動させるため、/Library/LaunchDaemons にdtcpc.plistを作る。中身は
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <key>Label</key>
 <string>dtcpc</string>
 <key>ProgramArguments</key>
 <array>
  <string>/usr/local/sbin/dtcpc</string>
  <string>-t</string>
  <string>host</string>
  <string>-u</string>
  <string>dtcpで使うユーザ名</string>
  <string>dtcpsのアドレスまたはマシン名</string>
 </array>
 <key>RunAtLoad</key>
 <true/>
 <key>ServiceDescription</key>
 <string>Turmpet Dynamic Tunnel Configuration Protocol client</string>
</dict>
</plist>

sudo launchctl load /Library/LaunchDaemons/dtcpc.plist して起動させるが、サーバー側が用意できてからにする。

で、下のようなネットワーク構成で考える。
  • 3つのサーバが同じネットワークに別々に存在している。
  • 割り当てられているIPv6 addressは/64。

DTCP Serverはマニュアル通りにセットアップする。dtcpc.plist にあるように、dtcpcはhostモードで動かすので、prefixに/64のアドレスを設定する。
dtcpcのアドレスはdtcpsが自動で割り当てる。このとき、IPv6 Gatewayから見ると、dtcpcにはDTCP Server経由となるのだが、割り当てているのは同一の/64のアドレスなので、proxy arpのように、DTCP Serverが代理で応答してくれないと、通信できない。
/64よりも大きいaddress blockが割り当てられているのであれば、別の/64アドレスを使うこともできるのだが、その手は使えない。
そこで、dtcpsが割り当てるaddressが/64の頭の方であることに注目し、IPv6 GatewayにDTCP Server経由の/96(/80でも可)のルートを追加して逃げる。routeはlongest matchなので、これでOK。DTCP ServerでRIP NGか何かを動かすのが正攻法なのだろうけど。3つがバラバラでなければもっと簡単になるんだろう。

まだまだ、IPv6で生活するには手間がかかるな。



2012/06/24

MuninでUPSを監視する

もともと電源の配線がおかしい家なので、サーバーを動かしておくためにはUPSが必須。
長らくUPSのバッテリーが切れていたのだが、さすがに今年の夏は使うことになるかと思い、交換バッテリーを購入した。 当然、監視が必要な訳で、Apcupsd, a daemon for controlling APC UPSesを使ってUSB経由でデータを取得できるようにした上で、hirose31/munin-apcupsd · GitHubMunin でグラフ化。

Muninってついに2.xが出たんだ。


2012/06/18

inline patchでLion上でのEmacsを快適にする

もともとEmacs+Wnn(Egg)という環境で育ってきたので、Lion上でもその使い勝手を再現したい。 FreeWnnという手もあるのだが、できることなら他のアプリと同じかな漢字変換を使いたい。
inline patchを使うと、ことえりやATOKを使って同じようなことができるようなので、Emacs 24でやってみた。
Emacs 24のインストールについては、 Emacs24 のインストール と新機能 : 紹介マニアを参考にした。autoconfなどの周辺ツールのバージョンで怒られたので、 Build Emacs 24.1 on Mac OS X Lion | Re: no subjectを見て、brew link autoconfとかbrew link automakeとかしてからconfigureする。
ATOKの新しい体験版がでていたので、Cocoa Emacs(Emacs23)をOS X Lion向けにフルスクリーン、インライン入力対応のパッチを当ててビルドする方法 | Oreradio.memoをまねて、
(setq default-input-method "MacOSX")
(mac-set-input-method-parameter "com.justsystems.inputmethod.atok25.Japanese" `title "漢")
を~/.emacs.d/init.elに加えてみたところ、ばっちりC−\で切り替えられるようになった。ことえりとかGoogle日本語入力でも大丈夫っぽい。