bit

複数のNICを持つ場合のデフォルトゲートウェイ

先日、仮想Linuxマシン(CentOS7)に複数のネットワークインターフェイス(以下、NICa、NICbとする)を持たせて、NICbが接続している側のゲートウェイをマシン全体のデフォルトゲートウェイ(以下、デフォゲ)にしようとしたのに、起動時にどうしてもルーティングテーブルのデフォゲがNICa側のゲートウェイになって困った。

状況としては、以下の通り:

  • /etc/sysconfig/networkのGATEWAY設定はNICb側
  • nmcliコマンドで、NICa, bともにそれぞれのゲートウェイを設定

調べたところ、以下のことが分かった。

  • NICには優先順位がある。("in numerically ascending order"らしい)
  • NICは、最初に/etc/sysconfig/networkのGATEWAY設定、次に個々のifcfgファイル (/etc/sysconfig/network-scripts内)にあるGATEWAY設定を読み込み、最後のGATEWAY設定がそのNICゲートウェイとして設定される(参考)。
  • そして、全体ルーティングテーブルのデフォルト設定は先勝ちで、優先順位の高いNICゲートウェイがデフォゲになる。

今回のケースでは、NICaのゲートウェイ設定が優先的にデフォゲとして設定されてしまい、/etc/sysconfig/networkのGATEWAYは無視されていた。

ではどうすべきか。原理主義的に行くなら、デフォゲは使わず必要なルーティングはすべて静的に設定というのもある。それは大変という場合は、以下がおすすめ。

メリットは以後の操作をすべてnmcliで出来るようになるのと、グローバル設定をなくせること。デメリットは、sysconfig/network のGATEWAY設定しか知らない人を混乱させるかもということ。まあ、それはコミュニケーションを取ればよい。

そんな感じにすると以下のようになる(192.168.0.0側がNICa、192.168.20.0側がNICbで外につながっている)。

# grep GATEWAY /etc/sysconfig/network
# nmcli con mod enp0s3 ipv4.addresses "192.168.0.98/24"
# nmcli con mod enp0s8 ipv4.addresses "192.168.20.98/24 192.168.20.1"
# nmcli con mod enp0s3 ipv4.never-default yes
# nmcli con mod enp0s8 ipv4.never-default no
# systemctl restart network
# nmcli con show enp0s3 | egrep  'ipv4.(addresses|never)'
ipv4.addresses:                         { ip = 192.168.0.98/24, gw = 0.0.0.0 }
ipv4.never-default:                     yes
# nmcli con show enp0s8 | egrep  'ipv4.(addresses|never)'
ipv4.addresses:                         { ip = 192.168.20.98/24, gw = 192.168.20.1 }
ipv4.never-default:                     no
# ip route
default via 192.168.20.1 dev enp0s8  proto static  metric 1024
192.168.0.0/24 dev enp0s3  proto kernel  scope link  src 192.168.0.98
192.168.20.0/24 dev enp0s8  proto kernel  scope link  src 192.168.20.98
# ping -c 2 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=48 time=50.6 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=48 time=42.2 ms

--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 42.265/46.482/50.699/4.217 ms

ボンディングの場合も似たようなポリシーでできると思うが、サブアドレスで一つのNICにまったくセグメントの異なるアドレスを設定している場合は、どうすべきかは知らない。

ちなみに、NICごとにゲートウェイが設定できてしまうのは、デフォゲ先にあるルータが通信できなくなった場合のバックアップゲートウェイを設定する場合に必要な機能とのこと(参考:マルチホーム コンピュータのデフォルトゲートウェイ設定)らしいのだけど、では障害発生時にどうやって切り替えるかは知らない。RHEL HAアドオンみたいなHAミドルでがガリガリ書くしかないような気がする。

bashのwhileループ内の変数をループ外で使う

化石みたいな問題。
よく知られていることだが、下記のような各行を足し算するシェルスクリプトを書くと、最後の結果が0になってびっくりする。

$ cat get_nothing.sh
__calculate_total() {
  local total=0 fpath=$1

  cat $fpath | while read line; do
    total=$(( $total + ${line:-0} ))
  done

  echo "Total reaches $total"
}

__calculate_total $@
$ cat numbers.txt
101
-40
4566
32
$ bash ./get_nothing.sh numbers.txt
Total reaches 0

これはwhile等の制御文だったかパイプの先だったか忘れたが、bashが別プロセス起こしちゃうんで、戻ってくるときにせっかく変更した変数の内容が破棄されるかららしい。
解決方法としては、UNIX FAQとかにexecでファイルディスクリプタを一時的に変更して云々な話が書かれていたりするが、現在の超高度に発達したSIerの開発現場において、そんなマイナーなテク使われても誰も読めなくて迷惑するのでやめてほしい。あと、一時ファイルにwhileループごとに値を書き出すのもやめて。コードレビューする人の 疲労感を無駄に積み増すんじゃない。

一番よい解決方法は、bashやめてPerlなりPythonなりまともな言語で書く。

でも、現在の超高度に発達したSIerの開発現場では、言語一つ変更するのに、周囲とのネゴとか試験仕様書の更新とか、誰も幸せにならないエネルギーが必要なので、シェルスクリプトの範囲内で何とかしてみる方法を考えてみた。

たぶんこう書くと16倍(当社比)くらいすっきり感が出ると思う。

$ cat goes_well.sh
__sum_up() {
  local line total=0

  while read line; do
    total=$(( $total + ${line:-0} ))
  done

  echo $total
  return 0
}

main() {
  local total=0 fpath=$1

  total=`cat $fpath | __sum_up`
  echo "Total reaches $total"

  return 0
}

つまり、while部分を別関数に切り出す。切り出しただけなので、行数はそれほど変わらない。
ファイル読み込み中にエラーが出た場合の処理を書きたい場合も容易に拡張できる。

$ cat goes_well2.sh
__sum_up() {
  local line total=0

  while read line; do
    if [[ "$line" =~ ^-?[0-9]+$ ]]; then
      total=$(( $total + ${line} ))
    else
      return 1
    fi
  done

  echo $total
  return 0
}

main() {
  local rc total=0 fpath=$1

  # Todo: check the fpath is valid.

  total=`cat $fpath 2>/dev/null | __sum_up`
  rc=$?
  if [ $rc -ne 0 ]; then 
    return $rc
  fi

  echo "Total reaches $total"

  return 0
}

main $@
$ bash ./goes_well2.sh numbers.txt ;echo "rc=$?"
Total reaches 4659
rc=0
$ cat numbers2.txt
101
-40
NOT NUMBER
4566
32
$ bash ./calc_total.sh numbers2.txt ;echo "rc=$?"
rc=1

欠点は、"total="のパイプより前のコマンドでエラーが出ても$?が0になってしまう点で、そこのところはあまりいい書き方思いつかないので、まじめに直前にファイル存在チェック書いたほうが読みやすくてよいと思う。

まあでも、やはりbashを使わないのが正解。

ちなみにfor 文で

for nb in `cat numbers.txt`; do
  ..(足し算)..
done
echo $total

などとしても動くが、numbers.txt のファイルの中身が文字列としてメモリに全展開されるので、ファイルサイズが小さいことが保証されていない限りお勧めしない。

ANTLR v4 の文法ファイルのサンプル

最近までいろいろ ANTLR v4 をいじっていて、ようやく文法ファイルの書き方がわかってきた。
でも初心者向けの解説記事を書けるほど頭の中がこなれていないので、サンプルだけ示すことにする。

こんなファイルを考える。

"This"
 "is
   a "
 "\"quoted\" text."
""

このファイルから、「This」「is<改行> a 」「"quoted" text.」「(空文字)」という四つの文字列を認識するパーサを書きたいとする。

ファイル解析のプログラムはスクラッチからがりがり書いてもよいけど、文法が複雑になってくるとプログラムが汚くなりがちである。そこで、構文解析プログラムジェネレータである ANTLR が便利ということになる。

ANTLR 自体の細かい説明は面倒なのでしないけど、要は文法ファイルを与えるとその文法に従ったファイルを解釈するための Java プログラムを生成してくれる。
といった流れは簡単なのだが、文法ファイルの書き方はそれほど初心者には優しくない。

文法ファイルは、字句解析部分と構文解析部分の二つの部分から成り立つが、最近はこれを個別のファイルとして書くのが流行り(?)である。

まず字句解析の文法ファイル StringLexer.g4

lexer grammar StringLexer;

QUOTE: '"' -> pushMode(STRING_MODE) ;
WS: ( ' ' | '\t' ) -> skip;
NL : ( '\r' '\n'? | '\n') -> skip ;
OUTER_CHAR: . -> skip ;

mode STRING_MODE;
RQUOTE: '"' -> popMode ;
ESC: '\\' -> skip, pushMode(ESCCHAR_MODE) ;
NOT_ESCCHAR: . ;

mode ESCCHAR_MODE;
ESCCHAR: ( '\\' | '"' ) -> popMode ;

「lexer」の行はおまじない。
最初の「mode」より上のグローバル部分と、二つの「mode」で、グローバル部分を一つのモードと考えると、合計三つのモードから構成される。
それで、グローバルの上のほうにある「QUOTE」の行では、引用符(") を読んだら、STRING_MODE というモードに移行する、という意味であり、STRING_MODE の最初の「RQUOTE」は、引用符を読み込んだら元のモードに戻るという意味である。

STRING_MODE は引用符の中を処理する部分であり、ESCCHAR_MODE はさらにその中のエスケープ文字を処理する部分である。まあ、あとは説明しなくても分かるでしょ。
エスケープ可能な文字はここでは2種類 (\と")に限定しているが、字句解析の段階でエラーにすると現場プログラム的にはエラー処理が面倒になるので、任意文字にしてもよいかもしれない。

この mode はとても便利である。字句解析ルールは何もしないとグローバルに効いてしまうので、その場合、字句解析プログラムは文字列の中だろうがコメントだろうが、お構いなしに字句解析ルールを当てはめてしまう。
実は最近まで mode を知らなかったので、このグローバル対策のためのバッドノウハウの塊みたいな文法ファイルを書いていたが、mode を使うときれいに字句解析文法ファイルを書ける。素晴らしい。
ただし mode は字句解析文法部分にしか使えないので、必然的に字句解析と構文解析とで文法ファイルを分ける必要が出てくる。

次に構文解析の文法ファイル。

StringParser.g4

parser grammar StringParser;
options { tokenVocab=StringLexer; }

strings
  : string*
  ;

string returns [ String text ]
  :
  QUOTE v=string_text RQUOTE {
    $text = $v.text;
  }
  ;

string_text
  :
  ( ESCCHAR
  | NOT_ESCCHAR
  )*
  ;

options のところが、さっきの字句解析ファイルのトークンを使っているよという宣言。
string ルールのところの RQUOTE の後の部分が、アクションというやつである。何もしないと 「"This"」と左右の引用符がくっついたままの文字列しかプログラムから取得できず面倒になるので、string_text のところだけを取り出して $text という変数に返すようにしている。

二つの文法ファイルができたらあとは、ANTLRに食わせて Java コードを生成させて、動かすだけである。

java -classath antlr-4.2.2-complete.jar org.antlr.v4.Tool -o <出力先> -package<パッケージ> StringLexer.g4 StringParser.g4

Java からの呼び出し方は以下の通り:

public void test(String filepath) throws IOException {
  ANTLRFileStream fromFileStream = new ANTLRFileStream(filepath);
  StringLexer lexer = new StringLexer(fromFileStream);
  CommonTokenStream tokens = new CommonTokenStream(lexer);

  StringParser parser = new StringParser(tokens);
  StringsContext stringsContext = parser.strings();
  for (StringParser.StringContext stringContext : stringsContext.string()) {
    System.out.println("[[" + stringContext.text + "]]");
  }
}

出力結果。

[[This]]
[[is
   a ]]
[["quoted" text.]]
[[]]

ジョギング音楽

ジョギングのときに聞いている曲の紹介。2年前に書いた記事から、だいぶ変わったので再編集。

曲名 アーティスト名 BPM リンク
Holy Ground Taylor Swift 156 iTunes / Youtube
I'll Fight Daughtry 156 iTunes / Youtube
Blurry Puddle of Mudd 157 iTunes / Youtube
Everything Has Changed Taylor Swift 160 iTunes / Youtube
Never Say Never The Fray 160 iTunes / Youtube
What About Now Daughtry 162 iTunes / Youtube
Keep It Together Puddle of Mudd 162 iTunes
We Don't Have To Look Back Now Puddle of Mudd 163 Youtube
Back to the Beginning Again Switchfoot 167 iTunes / iTunes
We Are Never Ever Getting Back Together Taylor Swift 171 iTunes / Youtube
Wild Heart Daughtry 177 iTunes / Youtube

Puddle of Mudd は、ボーカルがよく捕まるためか最近はあまり新曲を出していないが、BPMがちょうどよいので2年前から生き残っている。

Permutation: Javaでの順列組合せの列挙

順列を全部生成するコードを書く必要があって世の中のコードを探したのだけど、あまりきれいなコードがなかったので自分で書いた、単なる備忘録的記事。
まあ、このコードがきれいかというと、うーん、でも使いやすさとシンプルさでは、いろいろ検索した中ではきれいだと思うんだけど。

public class Permutation<T extends <Serializable> {

  private int baseIndex;
  private int index;
  private T[] objs;

  private Permutation<T> subPermutation;

  public Permutation(T[] objs) {
    this(0, 0, objs.clone());
  }

  private Permutation(int baseIndex, int index, T[] objs) {
    if (objs == null || objs.length == 0) {
      throw new IllegalArgumentException();
    }

    this.baseIndex = baseIndex;
    this.index = index;
    this.objs = objs;

    if (this.index < this.objs.length - 1) {
      this.subPermutation =
        new Permutation<T>(this.baseIndex + 1, this.index + 1, this.objs);
    }
  }

  public T[] getTarget() {
    return this.objs;
  }

  public boolean next() {
    if (this.subPermutation == null) {
      return false;
    }

    boolean result = this.subPermutation.next();
    if (result == true) {
      return true;
    }

    this.swap(this.baseIndex, this.index);

    ++this.index;
    if (this.objs.length <= this.index) {
      this.index = this.baseIndex;
      return false;
    }

    this.swap(this.index, this.baseIndex);
    return true;
  }

  @Override
  public String toString() {
    // snip.
  }

  private void swap(int index1, int index2) {
    T tmp = this.objs[index1];
    this.objs[index1] = this.objs[index2];
    this.objs[index2] = tmp;
  }
}

呼び出し方は、

Permutation<String> p = new Permutation<>(new String[] { "a", "b", "c" });
do {
  System.out.println(p.toString());
} while (p.next());

で、次のようになる。

[a,b,c]
[a,c,b]
[b,a,c]
[b,c,a]
[c,b,a]
[c,a,b]

メモリも大して食わないし、そこそこ高速だと思う。

Apache antのjavaタスクのarg要素に空白を含むパスを複数指定するには

antもANTLRも今更感はあるけど、久しぶりにいじると楽しい。

でも、結構はまる。文法ファイル(*.g4) が複数あって、かつパスに空白が含まれる場合(Google Driveとか)に、java タスクの arg 要素で pathref 属性を使おうとすると、ant が空白でパスを切ってしまって java タスクに思い通りに渡らない。そこで、 pathconvert を以下のように使って、文字列化してしまうとうまくいく。

  <path id="src.antlr">
    <fileset dir="${basedir}/src/antlr">
      <include name="**/*.g4" />
    </fileset>
  </path>

  <target name="antlr_compile">
    <echo message="[DEBUG] ${toString:src.antlr}" />

    <pathconvert property="antlr.args" refid="src.antlr" pathsep=" ">
      <regexpmapper from="^(.*)$" to='"\1"' />
    </pathconvert>

    <echo message="[DEBUG] ${antlr.args}" />

    <java classname="org.antlr.v4.Tool">
      <classpath>
        <pathelement path="${basedir}/lib/antlr-4.1-complete.jar" />
      </classpath>
      <arg line='-o "${basedir}/src/java/iwsttty/example_antlr"' />
      <arg line="-package iwsttty.example_antlr" />
      <arg line="${antlr.args}" />
    </java>
  </target>

antlr タスクでは複数の文法ファイル指定はうまくいかなかったです。

VirtualBox ネットワーク設定

(12/10) 再度見てみたら、NAT Networkのところも問題なく接続できていた。確認方法がいまいちなだけだった。というわけで、この記事はあたかも最初からうまく設定できてたように書き直す。
# 嘘はついていない。あくまで確認が不足していただけ…。

(3/8) バージョン4.3.8から、ping proxyが実装されているので、NAT Network でもpingが通るようになっていた。なんかこの記事、追記が多すぎるので書き直したい。


とにかくVirtualBoxのネットワーク設定である。構築に使用したOS やVirtualBoxのバージョンは以下の通り。

ホストOS
Windows 8.1
ゲストOS
CentOS 6.5
VirtualBox
4.3.4 r91027 *1

一つのホストOS上に以下のような仮想ネットワークを構成する。
f:id:iwsttty:20131210042402p:plain
想定としては、管理用セグメントをHost-Onlyネットワークで、サービス用セグメントを「NAT Network」ネットワークで、サーバ間セグメントをInternalネットワークで実現し、おのおの複数NICを持つサーバというか仮想PCを接続しようとしている。NATは本当は作るつもりなかったけど、最初はNAT Networkがうまく疎通できないと思っていた関係で手っ取り早くインターネットへの口がほしかったのと、全部使った感がほしかったので入れてみた。
疎通可否を表にするとこんな感じ (日曜日なのに仕事っぽいな)。

NAT(w/o port forwarding) NAT Network(w/o port forwarding) Host Only ネットワーク Internal ネットワーク
ゲストOS間 -
ゲストOS → ホストOS/GW -
ゲストOS → インターネット - -
ホストOS → ゲストOS - - -
DHCP disabled disabled disabled N/A

○は疎通OK、ハイフン(-)は仕様上通らないもの。DHCPは全部OFF(実際のサーバ構築では、そんなもの使わない)。ポートフォワーディングは後回し。

順番に概要だけ説明。

NAT

今のところゲストOSからインターネットに出ていくための唯一のインターフェイス
設定上の注意点は、VirtualBox が 10.0.N.0 のネットワークアドレスを勝手に割り当てること。自由なアドレスは使えない(仕様) (12/12追記 じゃなくてデフォルト値だった)。したがってゲストOS 側はこれに合わせて設定を入れる必要がある。今回、図で言うところの仮想PC O00の三番目のネットワークインターフェイスLinuxから見るとeth2)をNATにしたので、10.0.2.0から数えて三番目の 10.0.4.0 が自動的に割り当てられた。ゲートウェイは 10.0.4.2 になるので、これをデフォゲにする。このNATネットワークはちゃんと設定すれば、ゲストOS側から簡単にインターネットに疎通してくれる。

[root@vso00 ~]# ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=43 time=43.6 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=43 time=43.2 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=43 time=43.1 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2047ms
rtt min/avg/max/mdev = 43.116/43.355/43.670/0.334 ms
Host-only ネットワーク

最初、ゲストからホストへは疎通せず悩んでいたが、ホストOSのWindowsにて「ゲストまたはパブリックネットワーク」側のWindowsファイアウォールを落としたら通ることが分かった。ファイアウォールを無効にするのは嫌なので、受信規則を追加した。すなわち、「スコープ」の「ローカルIPアドレス」「リモートIP アドレス」ともに「192.168.0.0/24」とし、詳細のプロファイルは「パブリック」をチェック、「操作」は「接続を許可する」。これで想定どおり動くようになった。

(3/1追記: 一つ書き忘れた。下の VBoxManage.exe list hostonlyifs の出力にあるけど、VirtualBox が生成するHost-onlyネットワーク用仮想NICIPアドレスは、Host-onlyネットワークのネットワークアドレスに合わせてデフォルトのものから変更している(GUIのファイル→環境設定→ネットワーク→Host-Onlyネットワーク→ドライバーアイコン)。これをやらないと、ホストからゲストにルーティングしてくれない。)

Internal ネットワーク

特に問題なし。寝ながら設定してもつながる。

NAT Network ネットワーク / NAT サービスネットワーク / NatNetwork

GUIでは「NAT ネットワーク」と書かれているが、マニュアルはNATサービス、どっちが正式名称なんだろう。とりあえず、GUIに合わせるが、この名称はググりにくい…。
それはともかく、NatNetwork は、Host-Onlyネットワークの機能を持ちながらも、インターネットに出ていけるネットワークである。
最初、うまく接続できないと思っていたが実はできていた。マニュアルには以下のように書いてある。

but letting systems inside communicate with each other and with systems outside using TCP and UDP over IPv4 and IPv6.
Chapter 6. Virtual networking

ICMPでつながるとはどこにも書いてなかった。不覚。(追記: 上述のとおり、4.3.8 以降は ping proxyが実装されている)

[root@vsw01 ~]# ping -c 3 www.yahoo.co.jp
PING www.g.yahoo.co.jp (203.216.231.189) 56(84) bytes of data.

--- www.g.yahoo.co.jp ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 12001ms

[root@vsw01 ~]# curl http://www.yahoo.co.jp | head -10
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta http-equiv="content-style-type" content="text/css">
<meta http-equiv="content-script-type" content="text/javascript">
<meta name="description" content="日本最大級のポータルサイト。検索、オークション、ニュース、天気、スポーツ、メール、ショッピングなど多数のサービスを展開。あなたの生活をより豊かにする「課題解決エンジン」を目指していきます。">
<meta property="og:title" content="Yahoo! JAPAN">
<meta property="og:type" content="article">
<meta property="og:url" content="http://www.yahoo.co.jp/">
101  4774    0  4774    0     0  78196      0 --:--:-- --:--:-- --:--:--  172k
curl: (23) Failed writing body (3418 != 7300)

以上。とりあえず、VirtualBox の設定をさらしておく。

PS ...\virtualbox> & 'C:\Program Files\VirtualBox\VBoxManage.exe' list hostonlyifs
Name:            VirtualBox Host-Only Ethernet Adapter
GUID:            d2098a99-d909-4084-a50d-c0c828cc8a49
DHCP:            Disabled
IPAddress:       192.168.0.1
NetworkMask:     255.255.255.0
IPV6Address:     XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX
IPV6NetworkMaskPrefixLength: 64
HardwareAddress: 08:00:27:XX:XX:XX
MediumType:      Ethernet
Status:          Up
VBoxNetworkName: HostInterfaceNetworking-VirtualBox Host-Only Ethernet Adapter

PS ...\virtualbox> & 'C:\Program Files\VirtualBox\VBoxManage.exe' list intnets
Name:        InternalNetwork
Name:        intnet
(使っているのは上のほう)

PS ...\virtualbox> & 'C:\Program Files\VirtualBox\VBoxManage.exe' list natnets
NetworkName:    ServiceNetwork
IP:             192.168.2.1
Network:        192.168.2.0/24
IPv6 Enabled:   No
IPv6 Prefix:
DHCP Enabled:   No
Enabled:        Yes
loopback mappings (ipv4)
        127.0.0.1=2

Linuxのルーティング設定や/etc/sysconfig/network-scripts/ifcfg-* は、基本通り書いているだけなので詳細は省略。

手順だけ書いておく。まずVirtualboxGUIで各仮想PCのネットワークインターフェイスをNATにするとか、Host-Onlyにするとか種類を設定する。このとき、MACアドレスを確認しておく。仮想Linuxを起動、「ip link」コマンドで、設定したインターフェイスが仮想OSに認識されていることを確認。/etc/udev/rules.d/70-persistent-net.rules を見て、MACアドレスNIC名の対応付ルールに想定外のものが自動生成されている場合は適宜変更する(ATTR{address}とNAMEの対応のこと)。特にVirtualBox側でMACアドレスを再生成した場合にeth4 とかができてしまうので、例えば古いeth1行を削除し新しいeth4のNAMEをeth1 にしたりする作業が必要。変更した場合、仮想OS再起動。んで、/etc/sysconfig/network-scripts/ifcfg-ethN を修正する。見るのはBOOTPROTO(dhcpになっていないこと)、HWADDR、IPADDRとNETMASK。HWADDRがVirtualBoxGUI で設定した値と違うと、NICが起動しない。大事。外に出ていけるインターフェイスだけはGATEWAYも設定。あとはまあ「ifdown ethN; ifup ethN」して「ip addr」してpingで確認という感じ。(今回、このping確認が仇となったわけだが)
ちなみに最初の、ホストOS側のGUIで設定する部分のうち必要分は全部 VBoxManage コマンドで実施できることは確認済み。

*1:2/28追記: NAT Network について言うと、4.3.6はダメ。4.3.8はOK。