Go でテストを記述する際、モックの生成にはしばしば GoMock が使われますが、引数の検証に使う Matcher は標準では限られたもののみが用意されています。

たとえば、以下のテストの 6 行目では gomock.Eq(...) を使って api.Client に対して Get("/v1/info") という呼び出しが 1 回されることを期待しています。

//go:generate mockgen -source=client.go -destination=client_mock.go -package=api

package api

type Client interface {
	Get(url string) (*http.Response, error)
	Post(url, contentType string, body io.Reader) (*http.Response, error)
}
func TestServiceGet(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mock := api.NewMockClient(ctrl)
	mock.EXPECT().Get(gomock.Eq("/v1/info")).Return(nil, nil)

	service := domain.NewService(mock)
	service.DoFancyStuff()
}

このような単純な比較であれば標準の Matcher でも事足りますが、以下のテストの 9 行目のように引数の検証が比較演算でできないケースもあり、gomock.Any() を使って引数の検証自体を省略したままにしてしまうこともあります。

概要

SOCKS プロキシーは OpenSSH などで手軽に建てられるプロキシーの一つですが、redsocks を使用することで TCP のパケットが透過的に SOCKS プロキシーを経由するように設定することができます。今回は EdgeRouter X に redsocks をインストールし、LAN 内のコンピューターから特定の宛先への TCP 通信と名前解決が透過的に SOCKS プロキシーを経由するように設定してみます。

redsocks とは

redsocks は TCP パケットを受け取り、SOCKS/HTTPS プロキシーを通過させて返すソフトウェアです。iptables の REDIRECTDNAT と組み合わせて、redsocks の動作しているポートにプロキシーを通過させたいパケットを転送して使用します。このとき、iptables が REDIRECT や DNAT で付け替える前の本来の宛先アドレスや宛先ポートは、getsockopt() でソケットを取得する際の SO_ORIGINAL_DST というオプションによって取得されるため1、iptables のルールを設定するホストと redsocks が動作するホストは同一のマシンである必要があります。

TCP の場合

iptables の DNAT ルールによって redsocks に向けられたパケットが SOCKS プロキシーに転送されます。

Bash で書かれたシェルスクリプトで引数を処理するためには、ビルトインコマンドの getopts 1が使用できますが、このコマンドは --foo のようなロングオプションをサポートしていません。 ロングオプションを GNU の getopt を持たない macOS などの環境を含めてサポートしたい場合は自前で解析する方法や23getopts-: を渡して処理する方法45がありますが、この記事では後者の getopts を使用した方法で、前述のリンク先の手法を参考にしながら、オプションの引数を受けてショートオプションとロングオプションを共通で扱う方法を考えてみます。

ロングオプションの引数

ロングオプションで引数を取るコマンド群には代表的なもので次の種類があります。

--foo=bar: 区切り文字として = を使って受け入れる

--foo=bar のみを受け入れる実装は見当たりませんでした。
この記事の --foo=bar を処理する方法で扱っています。

--foo bar: 引数を分けて受け入れる

FreeBSD tarcurlRubyPython などで採用されている方式です。
この記事の --foo bar を処理する方法で扱っています。

--foo=bar--foo bar の両方を受け入れる

GNU CoreutilsGNU GrepGitNode.jsGNU Awk など多くで採用されている方式です。
この記事の --foo=bar と --foo bar の両方を処理する方法で扱っています。

EdgeRouter X と Linux サーバーを IPsec over IPv6 の拠点間 VPN で接続する方法です。

So-net 光 プラスは、フレッツ光とプロバイダー契約がセットになっているいわゆる光コラボサービスで、IPv4 が PPPoE で提供されているという特徴があります。 そのため、グローバル IP アドレスが頻繁に変更されたり、NGN 網終端装置の輻輳の影響を受けたり1といった問題があり、IPv6 接続のほうがより安定して VPN 接続を構成することができます。 このサービスの IPv6 アドレスはあくまでも半固定ですが2 IPv6 アドレスの変更頻度は低く、DDNS などを設定しておけば十分に実用できます。

2019/5/20 にリリースされた strongSwan 5.8.01ですが、systemd のユニットが以下のように更新されました。

Renaming of systemd Service Units

The systemd service units have been renamed. The modern unit (charon-systemd with vici/swanctl), which was called strongswan-swanctl, is now called strongswan (the previous name is configured as alias in the unit, for which a symlink is created when the unit is enabled). The legacy unit (starter/charon with ipsec/stroke) is now called strongswan-starter.

このリリースによって、今まで strongswan ユニットを使っていたシステムではその実体が charon-systemd + vici/swanctl を使ったものに変更されます。

バージョン

ユニット

実体

< v5.8.0

strongswan

starter/charon + ipsec/stroke

strongswan-swanctl

charon-systemd + vici/swanctl

>= v5.8.0

strongswan

charon-systemd + vici/swanctl

strongswan-starter

starter/charon + ipsec/stroke

目的

Node.js におけるテストで Jest を使用する場合、DOM API が関連するテストは JSDOM の実装によって実行されます。 この JSDOM はブラウザーと完全に同じ実装を持っているわけではなく、location.href への代入ができないなどの制約があります。 そのため Jest でもこれに合わせて JSDOM の API を使用したテストコードを記述する必要があります。

準備

Jest では JSDOM の API を直接参照することができないため、jest-environment-jsdom-global をインストールします。

$ npm i jest-environment-jsdom-global jest-environment-jsdom

package.jsonjest の項目にも追加しておきます。

{
  "jest": {
    "testEnvironment": "jest-environment-jsdom-global"
  }
}

ロードバランサーとは

EdgeOS は標準で WAN へのロードバランサー機能を搭載しており、パケットのルーティング先を予め指定した方法でインターフェイスごとに振り分けることができます1。 たとえば複数の回線を契約しているような逸般の誤家庭で負荷をそれぞれに分散させたり、一方の回線が利用できなくなったときにもう一方の回線に自動で切り替えたり、といったユースケースがあります。

この記事では So-net 光 プラス を契約している我が家(VDSL 😢)で、フレッツ光の PPPoE 方式による接続と v6 プラス を EdgeRouter から利用しつつ、v6 プラスが利用できないときに PPPoE 接続にフォールバックさせるように設定してみます。

v6 プラスのデメリット

v6 プラスを利用している場合、特定のポート番号を使用するサービスが IPv4 で利用できなくなります。 しかしながらこの記事の方法を使用すると、EdgeRouter からルーティングされるパケットをロードバランシングから除外することで PPPoE がそのまま利用できるため、L2TP や IPsec などを引き続き利用することができます。

So-net による v6 プラスの説明では以下のようにサービスの制約として説明されています3。これは v6 プラスが一つのアドレスを複数のユーザーで共有するサービスであるため、特定のポートを専有することができないことに起因する問題です。

「v6プラス」をご利用の場合、以下のご利用できないサービスがございます。
以下サービスをすでにご利用中、または今後ご利用を予定している場合は「v6プラス」の無効化または解約お手続きをお願いいたします。

※PPPoEのIPv4を有効化いただくことでご利用可能です。 (詳しくはこちら)
※解約お手続きの完了までお時間を要する場合がありますので、無効化を推奨しています。

  • So-net フォン
  • 固定IPサービス
  • ダイナミックDNSサービス
  • また以下の条件にあてはまる場合、ご利用いただけない場合があります。

    • 特定のプロトコル (PPTP、SCTP) を利用するサービス
    • 利用可能なポート番号が制限されているため、特定のポートを使うサービス
    • IPv4グローバルアドレスを共有するネットワークでは利用できないサービス

CI などの環境で Node.js の Intl API をはじめとする国際化サポート1が想定した挙動にならない場合があります。 たとえば Travis CI の Node.js 環境で以下のコマンドを実行すると ja を指定していても en-US 相当の出力になっています。

$ node -p '(new Intl.DateTimeFormat("ja")).format(new Date())'
3/26/2019

Node.js の i18n は ICU を必要としており2、スクリプト実行の前に full-icu をインストールして環境変数に出しておけば正常に動作します。

$ npm i -g full-icu
export NODE_ICU_DATA=$(node-full-icu-path)

$ node -p '(new Intl.DateTimeFormat("ja")).format(new Date())'
2019/3/26

Travis CI の場合は .travis.ymlbefore_install などに指定しておくと期待した出力が得られます3

環境

Node.js v11.10.0
Travis CI Worker v6.2.0

脚注

概要

フレッツ光では OpenSSH が設定する DSCP 値の関係で SSH のパケットを NGN 網で捨てられてしまう問題が発生します12。 端的にはこの記事の指摘通り ssh_config を以下のように修正すれば解決できます。

Host foo.example.com
    HostName  foo.example.com
    IPQoS     0x00

しかしながら、このような設定はルーター側で設定しておいたほうがクライアント側の設定が少なくて済むので楽です。 今回は EdgeRouter X のファイアウォール機能を使って、ルーターにやってきた SSH のパケットの DSCP 値を変更するように設定してみます。

環境

モデル

EdgeRouter X 5-Port

EdgeOS

v1.10.9

Linux

ubnt 3.10.107-UBNT

手順

CLI からファイアウォールの設定を行います。
アドレスやポート番号等は適宜読み替えてください。

Vim の小ネタです。
Markdown では改行を行末に半角スペースを 2 つ並べて表しますが、エディター上で改行かどうかが見つけにくくなります。

そこで Vim の conceal1 機能を活用してちょっとだけ見つけやすくしてみます。
ハイライトも設定できるので通常よりは分かりやすくなるはずです!

markdown line break

設定例

after/syntax/markdown.vim2 などで次のように syntax の設定を追加します。ccharconceal 部分に表示される文字です。

syntax match markdownLineBreak /\s\s$/ conceal cchar=setlocal concealcursor=nvic
setlocal conceallevel=1

もし bronson/vim-trailing-whitespace のような行末の空白をハイライトするプラグインを入れている場合は一緒に無効化しておくと良いかもしれません。

let g:extra_whitespace_ignored_filetypes = [
      \   'markdown',
      \ ]

脚注