root.hint の謎なアップデート

昨日、DNSOPS summer 2019 に参加して、自宅 DNS を参照していた。
で、DNS を構築して以来、ルートヒントを更新してなかったなぁと思い出して、ちょっと見てみた。
BIND のルートヒント(named.cache) がアップデートされているようだったので、どんだけ差分があるんだろうと思ったけど……

# cd /tmp
# wget https://www.internic.net/domain/named.cache
# cd /etc/bind/zonefiles
# diff db.root /tmp/named.cache
12,13c12,13
< ;       last update:     November 16, 2017
< ;       related version of root zone:     2017111601
---
> ;       last update:     June 27, 2019
> ;       related version of root zone:     2019062701
92c92
< ; End of file
---
> ; End of file
\ ファイル末尾に改行がありません
#

差分どこー?

Raspberry pi 4 model b が発売されてた

Raspberry pi 4 model b が発売されてた。

www.raspberrypi.org

spec を見てみた。

A 1.5GHz quad-core 64-bit ARM Cortex-A72 CPU (~3× performance)
1GB, 2GB, or 4GB of LPDDR4 SDRAM
Full-throughput Gigabit Ethernet
Dual-band 802.11ac wireless networking
Bluetooth 5.0
Two USB 3.0 and two USB 2.0 ports
Dual monitor support, at resolutions up to 4K
VideoCore VI graphics, supporting OpenGL ES 3.x
4Kp60 hardware decode of HEVC video
Complete compatibility with earlier Raspberry Pi products

まず、CPU が Raspberry pi 3 の 1 GHz から 1.5 GHz へ上がってる。
また、メモリも 1 GB しか選択肢がなかったのが、一気に 4GB モデルまで選択肢が広がっている。
もっとも、お値段もそれなりに……ん?

RAM Retail price
1GB $35
2GB $45
4GB $55

4GB でも、安いね……。

それから、電源が USB micro-B から USB Type-C に変わってた。
3A と書かれているからには PD に対応しているのかな?
5V * 3A = 15W だよね?

USB Type-C を使うとなると、電源の選択も少し慎重になるかも。
USB PD なら、対応してないケーブルだと大電流は流してくれなくなる規格だったはずだし、火事に対する安心材料は増えるね。


日本発売は、いつになるんだろう?

WiMAX2+ でのネット接続

生体肝移植の入院前に契約していた WiMAX2+ なんだけど、それまで節約していたぶんネット接続時間が爆発的に増えた。

でもね。結構、3日で 10GB とか、使うのしんどいぞ?

自宅へ RDP はそれなりにパケットを使うものの、自宅側(RDP接続されている側)のパケット送信分には 1日 1GB くらいだったか制限がかかっている。
あまり接続していると、プロバイダにアクセス制限を食らってしまうんだよね。

じゃあ、動画かな?
んー、そんなに出先で見たいとも思わないな。
病院では痛みがひどくて、落ち着くまでほとんどネットに触らなかった。
むしろ気分悪くて触りたくなかった。
手術直後は、運動不足解消目的の機械の微振動で内臓が揺らされて気分わるくなったし、ある程度よくなってからは痛み止めの薬が原因だと思うけど、ある意味バッドトリップしていた。目を閉じてもはっきりと映像が見えるとか、今までにない経験。仏像とかマリア像とかにポツポツとした穴が開いてるとか、僕がトライポフォビアだったら絶叫モノだったね。

次点で、音楽か。
……ちょうどよさげなサービスとして、Amazon Music があるね。
いまは図書館にいるし、イヤホンでヒーリング系音楽を聴きながら事務処理。

すごく久しぶりに図書館にきたけど、民営化してた。
いろいろ制度が変わってびっくりだ。

自宅へ Android で RDP 接続

自宅へ Android で RDP 接続

自宅 Windows 端末へ RDP でリモート接続をするのに、Android 端末から ssh 接続して、ポート転送によってリモートデスクトップを行ってみた。

接続に利用した環境

端末 モデル バージョン
スマホ F-01K Android 8.1.0
Windows BTO PC Windows 10 Pro 1803

Android にインストールするアプリ

https://play.google.com/store/apps/details?id=com.microsoft.rdc.android
検証バージョン:8.1.71.387

  • ConnectBot

https://play.google.com/store/apps/details?id=org.connectbot&hl=ja
検証バージョン:「デバイスにより異なります」とあったが、Android 上では 1.9.5 だった。(更新日は 2018年11月9日)

操作方法

大雑把な流れを言うと

  1. Microsoft Remote Desktop」でアカウントと接続先サーバ情報を設定。
  2. 「ConnecctBot」で、接続サーバにアクセスするための踏み台となる SSH サーバにログインして、ローカルから接続先サーバへのポート転送を行う。
  3. Microsoft Remote Desktop」で、ポート転送されるローカルのポート番号にアクセスする。
初期設定

各環境にて読み替えが必要なものの、ここでは以下をサンプルとして記入する。

Windows 端末:192.168.0.1:3389
Android 端末:127.0.0.1:13389 → 192.168.0.1:3389 へポート転送

まず、Microsoft Remote Desktop の接続設定を行っておく。

起動したところの設定ボタン(画面左上の三の線)から「User Accounts」を選択する。
右上の「+」ボタンから、アクセス先 Windows に RDP を受け付けるアカウントを登録しておく。

Remote Desktop のホーム画面右上「+」ボタンをタップして「Desktop」を選択する。

この時、Wi-Fi 接続しているとローカルネットワークにある Windows 端末を表示してくれるみたい。
「ADD MANUALLY」をタップする。

「PC name」 に、ポート転送を行うローカルのアクセス先を入力する。
たとえば、127.0.0.1:13389 等。

「User name」 は、上記で入力した User Acccounts を選択する。

アクセス開始

「ConnectBot」で自宅 Linux サーバ等、ポート転送を有効にできる端末へログインする。

ConnectBot 右上の設定ボタン(3つの点)をタップして「ポート転送」を選択する。
右下の「+」をタップして、ポート転送設定を入力する。
鍵の名前:RDP(任意:SSHポート転送設定の名前)
タイプ:「ローカル」を選択
ソースポート:13389
転送先:192.168.0.1:3389

入力したら「ポート転送の作成」ボタンをタップして確定する。
ポート転送はもう有効になっているので、取り消し線で消されていないことを確認して画面左上の「←」で元の画面に戻る。
(取り消し線は、設定が無効であることを示すため)

接続が有効(レが入っていること)を確認したら、ホームボタンでホームに戻れば、バックグラウンド動作してくれている。

Microsoft Remote Desktop」を起動して、ポート転送元「127.0.0.1:13389」へアクセスする。
接続してよいか聞かれるので、内容を確認して「CONNECT」をタップする。

あとは、通常の RDP 接続と同じなので割愛。

アクセス終了

Microsoft Remote Desktop」を終了する。
「ConnectBot」で exit を入力するなど、セッションを終える。

終わらせておかないと、いつまでもセッションが残ったままなので注意する。


覚えだけ覚えておいて使っていなかったんだけど、出先で Windows 端末がなく、取りに戻る時間的余裕も体力もなく、のに自宅端末内にしかない資料を送付したい……ということがあって助かった経験あり。
そんなの本当に極まれなんだけど、緊急時っていうのは本当にあるもんだねと。

自宅へリモートデスクトップ SSH ポート転送版

自宅 Windows 端末へ RDP でリモート接続をするのに、今までは専用の Windows 端末を用意して、ポート番号を変更して使っていたのだけれど、とうとう壊れてしまった。
困った。
少なくとも、アクセス手段は 2 種類欲しい。

ということで、今まで資料を読んでもよく理解できなくて使ってなかった、TeraTerm のポート転送を勉強し直してみて、あっさりと繋がったのでメモ。


まず、SSH ポート転送(SSH ポートフォワーディング)の概念を勘違いしてたことに気づいた。
TeraTerm がどのポートで受けて、どこへ転送するのか。
僕の場合は、そこを押さえきれてなかったので失敗してた。

さて、今回ちゃんと繋がった SCP での接続が、こんなイメージ。

f:id:KuroNeko666:20190526152739p:plain
SCP

それから、リモートデスクトップを使う場合のイメージがこちら。

f:id:KuroNeko666:20190526153713p:plain
TeraTerm で RDP

TeraTerm 設定

これを TeraTerm で設定する場合、自分が何番のポートで受けて、どこへ繋ぐのかを入力。

接続方法としては、まず TeraTerm で接続先ネットワーク上のサーバ(今回の例だと y.y.y.y )を踏み台として使うので、ログインまで済ませておく。

TeraTermSSH転送設定は、メニューから「設定(S)」「SSH転送(O)」を選択する。

f:id:KuroNeko666:20190526154231p:plain
TeraTerm SSH転送 設定メニュー

SSHポート転送画面で、追加ボタンをクリック。

f:id:KuroNeko666:20190526154435p:plain
TeraTerm SSHポート転送 画面

SSHポート転送画面で、自分の端末の何番ポートでうけて、どの相手に転送するのかを設定する。

f:id:KuroNeko666:20190526153903p:plain
TeraTerm RDP 設定例

設定が終わったら、SSHポート転送の、ポート転送を行う向きの選択画面で OK をクリック。
SSHポート転送の、ポート転送設定一覧画面で OK をクリック。

ちなみに、Xクライアントアプリケーションの転送にチェックを入れると、こちらは TeraTerm の再起動が必要な設定なので、TERATERM.ini といったファイルへ設定を保存することと、TERATERM 起動時の読み出しが必要になってくる。
(今回のポート転送だけだったら、TeraTerm 再起動は不要)

リモートデスクトップ (RDP) 設定

上記のリモートログイン及びポート転送設定で、接続先ネットワークの端末 z.z.z.z:3389 へのアクセス経路ができているので、今度は RDP での指定方法を。

リモートデスクトップは、TeraTerm の転送元ポートを指定してあげる。

f:id:KuroNeko666:20190526155657p:plain
RDP 設定例

TeraTerm でログインした y.y.y.y:22 を通して 127.0.0.1:13389 → z.z.z.z:3389 と転送されるので、ファイアウォール等で制限されていない限り、これでリモートデスクトップが成立する。

戯言

うーん……
僕の場合は「まず、何がどう繋がっているか」が全部見えていないと、設定のできない人ってことだな。
とりあえず、自宅 PC へ接続する方法としては、これで2種類できた。
・Open VPN を使って端末自体を自宅ネットワークに属させ、プライベート IP アドレスで RDP する方法。
TeraTerm のポート転送を使って RDP する方法。

簡単に NAS へアクセスしたいので、基本はこれからも Open VPN を使うことになると思うけど、Open VPN のためにログインするサーバが故障した場合でも、SSH は別なサーバなので、副次的なアクセス手段ができたので満足。


両方死んだら……?


帰宅するまでは、どうしようもないね。

引数に与えられたコマンドを実行して、その証跡を残す関数

引数に与えられたコマンドを実行して、その証跡を残す関数の作成。

まず、やりたいことができるかどうかの確認。
引数に与えられたコマンドを実行できるか。

$ aaa="echo sample message"
$ eval $aaa
sample message

基本は、これでやれそう。

次に、証跡を残すこと。
実行結果を変数に入れて、適当に出力できるようにしてみる。

$ type eval
eval はシェル組み込み関数です
$
$ aaa="echo sample message"
$ bbb=$(eval $aaa)
$ echo $bbb
sample message

やりたいことは、ほぼ完成。
エラー処理各種を試す。

$ aaa="echo sample message 1>&2"
$ bbb=$(eval $aaa)
sample message

標準エラー出力は xxx=$() という形式では含んでくれないので、含めておこうか。
ついでに、正常な場合の Exit Status も確認。

$ aaa="echo sample message 1>&2"
$ bbb=$(eval $aaa 2>&1)
$ ccc=$?
$ logger -t test $bbb
$ tail -1 /var/log/messages
May 25 08:55:37 tsrw01 test: sample message
$ echo $ccc
0

コマンドが異常終了した場合の Exit Status も確認

$ ls /etc/dummy
ls: '/etc/dummy' にアクセスできません: そのようなファイルやディレクトリはありません
$ echo $?
2
$ 
$ aaa="ls /etc/dummy"
$ bbb=$(eval $aaa 2>&1)
$ ccc=$?
$ echo "$(date +%Y/%m/%d %H:%M:%S) $bbb" > /tmp/ddd
$ cat /tmp/ddd
2019/05/25 09:04:53 ls: '/etc/dummy' にアクセスできません: そのようなファイルやディレクトリはありません
$ echo $ccc
2

ということで、内容を見やすく整理しつつ関数にまとめる。

#!/bin/bash

pLogFile=cmd.log

fnLog() {
    echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogFile}
}

fnCmd() {
    fnLog "Command line: $*"

    # Command exec
    pOutput=$(eval "$*" 2>&1)
    pExitStatus=$?

    # Log output
    fnLog "=== standard output / standard error output ========="
    echo "${pOutput}" >> ${pLogFile}
    fnLog "====================================================="
    fnLog "Exit status: ${pExitStatus}"

    return ${pExitStatus}
}

こうしておくと、実行したコマンドの出力結果を自由に出力しつつ、戻り値も確認できる。

ログの中身は各行に必ず日時がないとヤダ……という場合は、こんな感じ。

#!/bin/bash

pLogFile=cmd.log

fnLog() {
    echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogFile}
}

fnCmd() {
    fnLog "Command line: $*"

    # Command exec
    pOutput=$(eval "$*" 2>&1)
    pExitStatus=$?

    # Log output
    fnLog "=== standard output / standard error output ========="
    echo "${pOutput}" | while read line
        do
            fnLog "$line"
        done
    fnLog "====================================================="
    fnLog "Exit status: ${pExitStatus}"

    return ${pExitStatus}
}


個人的には、スクリプトログなんて実行が失敗した時しか見ないんだから、コマンドの出力結果に日時情報を入れるだけムダだし、紛らわしくて困ると思うんだけど……。
パッと見のキレイさの方が重要みたい。
どうせなら、4桁くらいで固定で行番号を入れてみる?

#!/bin/bash

pLogFile=cmd.log

fnLog() {
    echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogFile}
}

fnCmd() {
    fnLog "Command line: $*"

    # Command exec
    pOutput=$(eval "$*" 2>&1)
    pExitStatus=$?

    # Log output
    fnLog "=== standard output / standard error output ========="
    i=1
    echo "${pOutput}" | while read line
        do
            fnLog $(printf "%04d: %s" $i "$line")
            i=$(($i + 1))
        done
    fnLog "====================================================="
    fnLog "Exit status: ${pExitStatus}"

    return ${pExitStatus}
}

fnCmd "ls /etc/hosts*"

これを chk.sh に書き込んで実行してみた結果。

$ bash ./chk.sh
$ cat cmd.log
2019/05/25 09:39:11 Command line: ls /etc/hosts*
2019/05/25 09:39:12 === standard output / standard error output =========
2019/05/25 09:39:12 0001: /etc/hosts
2019/05/25 09:39:12 0002: /etc/hosts.allow
2019/05/25 09:39:12 0003: /etc/hosts.deny
2019/05/25 09:39:12 =====================================================
2019/05/25 09:39:12 Exit status: 0
$ 

うーん……
他の分も、同じコマンドを実行させるように sample1.sh / sample2.sh として書き込んで実行してみる。

sample1.sh は、実行結果に日時情報のないもの。

$ [ -f cmd.log ] && rm cmd.log
$ bash ./sample1.sh
$ cat cmd.log
2019/05/25 09:43:56 Command line: ls /etc/hosts*
2019/05/25 09:43:56 === standard output / standard error output =========
/etc/hosts
/etc/hosts.allow
/etc/hosts.deny
2019/05/25 09:43:56 =====================================================
2019/05/25 09:43:56 Exit status: 0


sample2.sh は、実行結果に日時情報を入れたもの。

$ [ -f cmd.log ] && rm cmd.log
$ bash ./sample2.sh
$ cat cmd.log
2019/05/25 09:47:00 Command line: ls /etc/hosts*
2019/05/25 09:47:00 === standard output / standard error output =========
2019/05/25 09:47:00 /etc/hosts
2019/05/25 09:47:00 /etc/hosts.allow
2019/05/25 09:47:00 /etc/hosts.deny
2019/05/25 09:47:00 =====================================================
2019/05/25 09:47:00 Exit status: 0

実行結果に、日時情報と行番号を入れたものを再度。

$ bash ./chk.sh
$ cat cmd.log
2019/05/25 09:39:11 Command line: ls /etc/hosts*
2019/05/25 09:39:12 === standard output / standard error output =========
2019/05/25 09:39:12 0001: /etc/hosts
2019/05/25 09:39:12 0002: /etc/hosts.allow
2019/05/25 09:39:12 0003: /etc/hosts.deny
2019/05/25 09:39:12 =====================================================
2019/05/25 09:39:12 Exit status: 0
$ 


……こうして並べてみると、印象がすごく違うね。

シェルスクリプト ログ出力関数あれこれ

スクリプトの実行ログ関係

シェルスクリプトを実行した際、デバッグするなら bash -x xxxx.sh という感じで、デバッグ実行することが多い。
でも、一部の管理職には不評なんだよね。
実行ログで出してくれと。

そんな理由で、スクリプトログを出力する関数を使うことが多いため、自分で使いやすい関数を汲んでおきたくなった。
毎回、現場に合わせて作るのも結構辛くなってきたので。

仕様

・指定のディレクトリにスクリプトログを置く。
・ログファイル名は、スクリプト名の拡張子を .log に置き換え、日付を含めたもの。
・ログは、yyyy/mm/dd hh:mm:ss のあとにメッセージを入れる。
・出力先は、変数指定がなければ自動で決める。


この仕様から、細かい条件を各環境に合わせれば良い。


自分の趣味を色濃く反映するものとして、
・関数名は fnLogger とする。
・出力先変数は pLogFile とする。

関数は英語で function と言うので、関数定義には fn を付けている。
変数なら、特に例外規定がなければ Parameter とか Variable なので p を。


fXxxx だと、フラグ関係に。
nXxxx だと、数値関係に。


それから、使い慣れているキャメルケースで変数名を定義する。
(スネークケースは好きではないが、使わないとは言っていない)


他のスクリプト等で変数が重複する確率が低いので、特に規定がない場合はこれを使っていたりする。
word とかの MS-Office 製品で、説明資料を作るときに下手に _ が入っていると、波線で見えなくなることがあるんだよね。
いや、全部大文字にしていれば問題ないんだけど、スネークケースを使う人って、なぜか小文字にしたがる……。


ということで、シェルスクリプトの変数は、会社で使うなら全部大文字が良いです。

スクリプト(関数)

基本形となる関数が、こちら。

fnLogger() {
    pLogFile="${pLogFile:=$(/usr/bin/basename ${0} .sh)_$(date +'%Y%m%d').log}"
    echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogFile}
}

上記 pLogFile という変数を、関数外で定義しておけば、ログ出力先はその指定に従う。



フラグ次第で、出力先を変えるタイプ。

### Parameter and Variable

## Directory
pLogDir=/var/log/script

## File
pLogFile=$(/usr/bin/basename ${0} .sh)_$(date +'%Y%m%d').log

## Path
pLogPath=${pLogDir}/${pLogFile}

## Flag
# 0 = Script log
# 1 = /var/log/messages
fOutput=0

### Function
fnLogger() {
    if [ "${fOutput}" -eq "0" ]; then
        echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogPath}
    else
        logger "$*"
    fi
}


作りこもうとすると、ログを出力したいだけでもこんなに長くなってしまうんだよね……
まぁ、他のファイルで定義しておいて、それを . で読み込むことが多くなってきたから気にならないんだけど。


いくつか出力先を選びたいなら、こんな手法も。

### Parameter and Variable

## Parameter
pID=$(/usr/bin/basename ${0} .sh)
pYYYYMMDD=$(date +'%Y%m%d')

## Directory
pLogDir=/var/log/script

## File
pLogStdFile=${pID}_${pYYYYMMDD}.log
pLogErrFile=${pID}_${pYYYYMMDD}_Err.log

## Path
pLogStdPath=${pLogDir}/${pLogStdFile}
pLogErrPath=${pLogDir}/${pLogErrFile}

## Flag
# 0 = Script log - Standard
# 1 = Script log - Error
# 2 = /var/log/messages
fOutput=0

### Function
fnLogger() {
    p1st=$1
    shift

    case ${p1st} in
        0 ) echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogStdPath};;
        1 ) echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogErrPath};;
        2 ) logger "$*";;
    esac
}

### Main

# 0 Standard
fnLogger 0 "Message"

# 1 Error
fnLogger 1 "Error Message"

# 2 /var/log/messages
fnLogger 2 "/var/log/messages output message"


パッと見て、0~2 の数字も一緒に出力されそうに見えるけど、実行結果はこんな感じ。

$ bash ./sample.sh
$ 
$ cat sss_20190519.log
2019/05/19 14:04:13 Message
$ 
$ cat sss_20190519_Err.log
2019/05/19 14:04:13 Error Message
$ 
$ tail -1 /var/log/messages
May 19 14:04:13 (ホスト名) (ユーザ名): /var/log/messages output message


shift コマンドが引数をひとつ消してくれる効果を持っている。


それから、コードを利用したログ出力方法もこんな一案。
コード自体を変数名としたもの。

$ cat mcode.txt
A000E="Sample essage."
A001E="Message 1st"
A002E="Message 2nd"
$ 
$ cat sample2.sh
#!/bin/bash

. ./mcode.txt

fnLogCode() {
    echo "${1}: $(eval echo '$'${1})"
}

fnLogCode A000E
$ 
$ bash ./sample2.sh
A000E: Sample essage.
$ 


コードは csv 形式で記載されているもの。

$ cat code.csv
I0000,Sample message
I0001,Information message.
W0001,Warning message.
E0001,Error message.

これを抽出する方法を考えてみる。まずはシンプルに grep で。

$ grep I0000 code.csv
I0000,Sample message
$ 
$ grep I0000 code.csv | cut -d "," -f 1
I0000
$ 
$ grep I0000 code.csv | cut -d "," -f 2
Sample message
$ 
### Parameter and Variable

## Parameter
pID=$(/usr/bin/basename ${0} .sh)
pYYYYMMDD=$(date +'%Y%m%d')

## Directory
pLogDir=/var/log/script

## File
pLogCSVFile=code.csv
pLogStdFile=${pID}_${pYYYYMMDD}.log

## Path
pLogStdPath=${pLogDir}/${pLogStdFile}
pLogCSVPath=${pLogDir}/${pLogCSVFile}

### Function
fnLogger() {
    [ -r "${pLogCSVPath}" ] || return 1
    pMessage=$(grep $1 ${pLogCSVPath} | cut -d "," -f 2)
    [ "${pBaseMessage}" == "" ] && return 1

    echo "$(date +'%Y/%m/%d %H:%M:%S') ${pMessage}" >> ${pLogStdPath};;
}

これだとメッセージ内容に , を含められないので、含められるようにしたバージョン。

### Parameter and Variable

## Parameter
pID=$(/usr/bin/basename ${0} .sh)
pYYYYMMDD=$(date +'%Y%m%d')

## Directory
pLogDir=/var/log/script

## File
pLogCSVFile=code.csv
pLogStdFile=${pID}_${pYYYYMMDD}.log

## Path
pLogStdPath=${pLogDir}/${pLogStdFile}
pLogCSVPath=${pLogDir}/${pLogCSVFile}

### Function
fnLogger() {
    [ -r "${pLogCSVPath}" ] || return 1
    tMessage=$(grep $1 ${pLogCSVPath})
    [ "${tBaseMessage}" == "" ] && return 1

    pMessage=${tMessage#*,}

    echo "$(date +'%Y/%m/%d %H:%M:%S') ${pMessage}" >> ${pLogStdPath};;
}

考えていくと、いろいろとやり方はあるもんだね。