トップ 追記

本 日 の h o g e

hogeとはワイルドカードのようなものです。日々起こった、さまざまなこと −すなわちワイルドカード− を取り上げて日記を書く、という意味で名付けたのかというとそうでもありません。適当に決めたらこんな理由が浮かんできました。

更新情報の取得には rdflirs を使ってもらえると嬉しいです.


01/23/2019 うむ [長年日記]

tDiary 5935日目

[Linux][小ネタ] Linux で Private VLAN (pvlan) もどき

本物の pvlan を実装するのはちょっと面倒な気がしたので,簡単にできそうな擬似的な "もどき" を作ってみる.ひとまず Cisco で言うところの switchport mode private-vlan trunk promiscuous な uplink を想定.

実現には,ブリッジにおいて uplink 想定のポートとの通信以外を遮断して,uplink との通信時にはネットワーク毎に primary vlan 相当のタグを付加してやれば良いはずなので,vlan_filtering と ebtables を用いて実装してみる.テストのためのお膳立てが多いので長くなってしまっているが,メインは "# create nsx" というコメントから.

ip netns add nsx  # pseudo pvlan box
ip netns add ns1  # uplink (trunk)
ip netns add ns2  # downlink (10)
ip netns add ns3  # downlink (10)
ip netns add ns4  # downlink (20)
ip netns add ns5  # downlink (20)

# create ns1
ip link add veth0 type veth peer name veth1
ip link set veth0 netns ns1
ip link set veth1 netns nsx
ip -n ns1 link add veth0.10 link veth0 type vlan id 10
ip -n ns1 link add veth0.20 link veth0 type vlan id 20
ip -n ns1 addr add 10.0.10.1/24 dev veth0.10
ip -n ns1 addr add 10.0.20.1/24 dev veth0.20
for dev in lo veth0 veth0.10 veth0.20; do
       ip -n ns1 link set "${dev}" up
done

# create ns2
ip link add veth0 type veth peer name veth2
ip link set veth0 netns ns2
ip link set veth2 netns nsx
ip -n ns2 addr add 10.0.10.2/24 dev veth0
for dev in lo veth0; do
       ip -n ns2 link set "${dev}" up
done

# create ns3
ip link add veth0 type veth peer name veth3
ip link set veth0 netns ns3
ip link set veth3 netns nsx
ip -n ns3 addr add 10.0.10.3/24 dev veth0
for dev in lo veth0; do
       ip -n ns3 link set "${dev}" up
done

# create ns4
ip link add veth0 type veth peer name veth4
ip link set veth0 netns ns4
ip link set veth4 netns nsx
ip -n ns4 addr add 10.0.20.4/24 dev veth0
for dev in lo veth0; do
       ip -n ns4 link set "${dev}" up
done

# create ns5
ip link add veth0 type veth peer name veth5
ip link set veth0 netns ns5
ip link set veth5 netns nsx
ip -n ns5 addr add 10.0.20.5/24 dev veth0
for dev in lo veth0; do
       ip -n ns5 link set "${dev}" up
done

# create nsx
ip -n nsx link add brx  type bridge vlan_filtering 1
ip -n nsx link add br10 type bridge
ip -n nsx link add br20 type bridge
ip -n nsx link add p10a type veth peer name p10b
ip -n nsx link add p20a type veth peer name p20b
for dev in lo brx br10 br20 p10a p10b p20a p20b veth1 veth2 veth3 veth4 veth5; do
       ip -n nsx link set "${dev}" up
done

ip -n nsx link set veth1 master brx
ip -n nsx link set p10a  master brx
ip -n nsx link set p20a  master brx

ip -n nsx link set p10b  master br10
ip -n nsx link set veth2 master br10
ip -n nsx link set veth3 master br10

ip -n nsx link set p20b  master br20
ip -n nsx link set veth4 master br20
ip -n nsx link set veth5 master br20

# allow vid 10,20 as tagged on veth1 (uplink)
bridge -n nsx vlan add dev veth1 vid 10
bridge -n nsx vlan add dev veth1 vid 20
bridge -n nsx vlan del dev veth1 vid 1

# allow vid 10 as untagged on p10a (veth2,3)
bridge -n nsx vlan add dev p10a vid 10 pvid untagged
bridge -n nsx vlan del dev p10a vid 1

# allow vid 20 as untagged on p20a (veth4,5)
bridge -n nsx vlan add dev p20a vid 20 pvid untagged
bridge -n nsx vlan del dev p20a vid 1

# deny downlink-to-downlink communication on br10
ip netns exec nsx ebtables -A FORWARD --logical-in  br10 -i p10b -j ACCEPT
ip netns exec nsx ebtables -A FORWARD --logical-out br10 -o p10b -j ACCEPT
ip netns exec nsx ebtables -A FORWARD --logical-in  br10 -j DROP
ip netns exec nsx ebtables -A FORWARD --logical-out br10 -j DROP

# deny downlink-to-downlink communication on br20
ip netns exec nsx ebtables -A FORWARD --logical-in  br20 -i p20b -j ACCEPT
ip netns exec nsx ebtables -A FORWARD --logical-out br20 -o p20b -j ACCEPT
ip netns exec nsx ebtables -A FORWARD --logical-in  br20 -j DROP
ip netns exec nsx ebtables -A FORWARD --logical-out br20 -j DROP

そんなに大したことをやっているわけではなく,ブリッジを 2段に分けて,上位ブリッジでは下位ブリッジとやりとりするフレームにタグ付けを行い,下位ブリッジではポート間通信制御を行っているだけ.

当然 "もどき" であって本物の pvlan ではないので,例えば trunk port を用意して延伸するとかはできない.何せこの実装には secondary vlan が存在しないので当たり前である.

かしこ

追記

ちなみにわざわざブリッジを分けずとも,downlink port 毎に pvid を指定していけばブリッジ 1つで済ませることもできる.のだが,そうすると downlink port が増えるごとに,ブリッジへの参加だけでなく pvid の設定もやる必要が出てきて面倒なので,ここでは分離する方式を採っている.例えば libvirt/kvm で VM を生やして,I/F を上記 br10, 20 に参加させるといったことを考えると分かりやすい.


12/16/2018 ふむ [長年日記]

tDiary 5897日目

[Py][小ネタ] NAPT 前のアドレスを知る

例えば下記のようなネットワークがあるとする.Client が Web Server にアクセスした際,NAPT Router によって NAPT される前の Client の情報を Web Server が知りたいとする.

+--------+          +-------------+        +------------+
| Client |----------| NAPT Router |--------| Web Server |
+--------+          +-------------+        +------------+
172.16.0.193   172.16.0.1   10.207.9.88    10.207.9.87

NAPT Router が Linux 箱なら,conntrack エントリがカーネルの中に保持されているので,下記のようなサービスを NAPT Router の上で動かしておくことができるだろう.

import flask
from pyroute2.netlink.exceptions import NetlinkError
from pyroute2.netlink.nfnetlink.nfctsocket import NFCTSocket, NFCTAttrTuple

app = flask.Flask(__name__)
cts = NFCTSocket()


@app.route('/conntrack', methods=['GET'])
def conntrack_get():
    saddr = flask.request.args.get('saddr')
    daddr = flask.request.args.get('daddr')
    proto = int(flask.request.args.get('proto'))
    sport = int(flask.request.args.get('sport'))
    dport = int(flask.request.args.get('dport'))

    tpl = NFCTAttrTuple(saddr=saddr, daddr=daddr,
                        proto=proto, sport=sport, dport=dport)
    try:
        entry = cts.entry('get', tuple_reply=tpl)
    except NetlinkError:
        return flask.Response(status=404)

    tuple_orig = entry[0].get_attr('CTA_TUPLE_ORIG')
    tuple_ip = tuple_orig.get_attr('CTA_TUPLE_IP')
    tuple_proto = tuple_orig.get_attr('CTA_TUPLE_PROTO')

    return flask.jsonify(saddr=tuple_ip.get_attr('CTA_IP_V4_SRC'),
                         daddr=tuple_ip.get_attr('CTA_IP_V4_DST'),
                         proto=tuple_proto.get_attr('CTA_PROTO_NUM'),
                         sport=tuple_proto.get_attr('CTA_PROTO_SRC_PORT'),
                         dport=tuple_proto.get_attr('CTA_PROTO_DST_PORT'))


if __name__ == '__main__':
    app.run('10.207.9.88')

その上で Web Server が下記のように,このサービスを叩く.

import flask
import requests

app = flask.Flask(__name__)


@app.route('/', methods=['GET'])
def root():
    saddr = flask.request.environ['SERVER_NAME']
    daddr = flask.request.environ['REMOTE_ADDR']
    sport = flask.request.environ['SERVER_PORT']
    dport = flask.request.environ['REMOTE_PORT']
    response = requests.get('http://10.207.9.88:5000/conntrack?'
                            'saddr=%(saddr)s&daddr=%(daddr)s&proto=6&'
                            'sport=%(sport)s&dport=%(dport)s' % locals())
    if not response.ok:
        return flask.Response(status=404)

    entry = response.json()
    return flask.jsonify(saddr=entry['saddr'],
                         daddr=entry['daddr'],
                         proto=entry['proto'],
                         sport=entry['sport'],
                         dport=entry['dport'])


if __name__ == '__main__':
    app.run('10.207.9.87')

Client から Web Server にアクセスしてみると,

$ curl -s http://10.207.9.87:5000/ | jq .
{
  "daddr": "10.207.9.87",
  "dport": 5000,
  "proto": 6,
  "saddr": "172.16.0.193",
  "sport": 55208
}

Web Server から,NAPT 前のオリジナルの Client のデータが返ってきていることが確認できる.

だからどうしたぼくドラえもん.


11/18/2017 ふむ [長年日記]

tDiary 5504日目

[Py][小ネタ] SQLAlchemy でグラフ

相変わらず長いこと放置しているので,秋の夜長にと言うには少し遅いけど,何となく遊び.

ノードに種類を持たせつつ,多対多でノード同士を繋いでみたりするなど.

import uuid
import json

from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy import Table, Column, ForeignKey, String
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
Engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=Engine)

NodeAssoc = Table(
    'node_assoc', Base.metadata,
    Column('parent_id', String(36),
           ForeignKey('node.id', onupdate='CASCADE', ondelete='CASCADE'),
           nullable=False),
    Column('child_id', String(36),
           ForeignKey('node.id', onupdate='CASCADE', ondelete='CASCADE'),
           nullable=False),
)


@contextmanager
def txn():
    session = Session()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()


class ModelBase:
    def __init__(self, **kwargs):
        self.id = str(uuid.uuid4())
        self.apply(**kwargs)

    def apply(self, **kwargs):
        for key, val in kwargs.items():
            setattr(self, key, val)

    def dictify(self, ignore=['id']):
        return {n: getattr(self, n)
                for n in self.__table__.columns.keys() if n not in ignore}

    def __repr__(self):
        return json.dumps(self.dictify())


class Node(ModelBase, Base):
    TYPE = None

    id = Column(String(36), primary_key=True)
    type = Column(String(255), nullable=False)
    value = Column(String(255))
    children = relationship(
        'Node', secondary=NodeAssoc,
        primaryjoin=id == NodeAssoc.columns.parent_id,
        secondaryjoin=id == NodeAssoc.columns.child_id,
        backref='parents', uselist=True)

    __tablename__ = 'node'
    __mapper_args__ = {'polymorphic_on': type}

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.type = self.TYPE


class CircleNode(Node):
    TYPE = 'circle'
    __mapper_args__ = {'polymorphic_identity': TYPE}


class SquareNode(Node):
    TYPE = 'square'
    __mapper_args__ = {'polymorphic_identity': TYPE}


class TriangleNode(Node):
    TYPE = 'triangle'
    __mapper_args__ = {'polymorphic_identity': TYPE}


if __name__ == '__main__':
    def dump(mode, nodes):
        def _dump(mode, node, level=1):
            print(' ' * level, node)
            for n in getattr(node, mode):
                _dump(mode, n, level + 2)

        print(mode)
        for node in nodes:
            _dump(mode, node)
        print()

    Base.metadata.create_all(bind=Engine)

    with txn() as session:
        t1 = TriangleNode(value='1')
        t2 = TriangleNode(value='2')
        t3 = TriangleNode(value='3')
        s1 = SquareNode(value='1', children=[t1, t2])
        s2 = SquareNode(value='2', children=[t2, t3])
        c1 = CircleNode(value='1', children=[s1, s2])
        session.add(c1)

    with txn() as session:
        for mode, cls in [('children', CircleNode), ('parents', TriangleNode)]:
            dump(mode, session.query(cls).all())

実行するとこうなる.

children
  {"type": "circle", "value": "1"}
    {"type": "square", "value": "1"}
      {"type": "triangle", "value": "1"}
      {"type": "triangle", "value": "2"}
    {"type": "square", "value": "2"}
      {"type": "triangle", "value": "2"}
      {"type": "triangle", "value": "3"}

parents
  {"type": "triangle", "value": "1"}
    {"type": "square", "value": "1"}
      {"type": "circle", "value": "1"}
  {"type": "triangle", "value": "2"}
    {"type": "square", "value": "2"}
      {"type": "circle", "value": "1"}
    {"type": "square", "value": "1"}
      {"type": "circle", "value": "1"}
  {"type": "triangle", "value": "3"}
    {"type": "square", "value": "2"}
      {"type": "circle", "value": "1"}

だからどうしたぼくドラえもん.

本日のツッコミ(全3件) [ツッコミを入れる]

mpet [クワッ!!って、来たら、ササッとやってスッ…て、避けたら、おぉっ! って、感じですな。 これはこれで、趣がありま..]

mpet [ちなみに近況報告。 atzmもっさり元気してる? こっちは、もっさもっさしてる。安定のいっぱいいっぱいさ..]

atzm [あけおめ. 明鏡止水の心持ちにて真を見れば,風呂の物体もホットプレートの浮島も泡沫夢幻の如く盛者必衰の理を表して候..]


05/28/2017 ふむ [長年日記]

tDiary 5330日目

[Py][小ネタ] lesync

exFAT なパーティションにディレクトリツリーを同期したいことがあるのだが,

  • 10 年以上前からデスクトップのファイルシステムが EUC-JP で,convert するのも面倒なので放置している
  • しかし exFAT はファイル名が UTF-8 じゃないと書き込みを拒否するくさい

といったようなことがあって,面倒臭いなあと思っていた.

ファイルの同期といえば rsync(1) 大先生なのだが,単に -a オプションを使うと chown(2) でエラーを吐くとかまあ色々あって,概ね下記のようなオプションを指定することになる.

# rsync -rpt --iconv eucjp,utf8 SRC DST

とはいえ覚えられないし打つのも面倒だし,alias するとか wrapper 書くとかすることになると思うのだが,どうせ rsync(1) ほど高機能/高性能なものを求めているわけでもなし,せっかくなのでフルスクラッチで書いてみた. それが lesync

とはいえどうせなら zero-copy でいっちょやってみるかと,データ本体を user space にメモリコピーすることはないようにした. その結果が下記.

$ sudo bash -c 'echo 1 > /proc/sys/vm/drop_caches'
$ time sudo lesync -S hoge /mnt/card/tmp/.
real    0m3.328s
user    0m0.467s
sys     0m0.347s

$ rm -rf /mnt/card/tmp/hoge
$ sudo bash -c 'echo 1 > /proc/sys/vm/drop_caches'
$ time sudo rsync -rpt --iconv eucjp,utf8 hoge /mnt/card/tmp/.
real    0m3.633s
user    0m1.296s
sys     0m0.304s

rsync(1) はデータを user space に持ってきてから kernel space に送る (read(2) したデータを write(2) する) という動作をするので,zero-copy にすることで user 時間に差が現れる. まあ,real は大差ないので大した意味はないけれど.

lesync はデフォルトでは差分をチェックせず cp -r のような動作をするので,更新されたファイルだけコピーしたい場合は上記のように -S オプションを指定する. 差分のチェックはデフォルトでサイズ,mtime,ファイル名の比較だけど,プラットフォームが Linux Kernel Crypto API をサポートしていれば md5 等のハッシュ値を比較することもできる. 例えば sha1 ハッシュ値を比較したい場合は下記のようにする.

$ sudo lesync -S -D sha1 hoge /mnt/card/tmp/.

hashlib を使わなかったのは,言わずもがな zero-copy を堅持するため. とはいえ如何に zero-copy と言えど,ハッシュ値の比較を使うとめちゃくちゃ遅くなるので,使いどころはあまりないのだけれど.

ちなみにハッシュ値を算出する部分は独立したモジュールに分けておいたので,下記のようにハッシュ値を求めることができる.

$ sudo bash -c 'echo 1 > /proc/sys/vm/drop_caches'
$ time python3 -m lehash -a sha1 data
440a94499f1862fab752280ab7c4470b7d859a69  data
real    0m4.592s
user    0m0.071s
sys     0m2.279s

$ sudo bash -c 'echo 1 > /proc/sys/vm/drop_caches'
$ time sha1sum data
440a94499f1862fab752280ab7c4470b7d859a69  data
real    0m4.393s
user    0m2.332s
sys     0m0.272s

zero-copy のため user 時間と sys 時間に差が生まれているのが見て取れるけど,こちらもまあ real は大差ないので大した意味はない.


05/20/2016 ふむ [長年日記]

tDiary 4957日目

[日記] ロマサガ 2

ついカッとなって PS Vita 版ロマサガ 2 を買ってしまったので,どうせなら 4000 年プレイでもするかーとちまちまプレイ.ようやく完走したので記録しておく.

1011年  第二皇子皇帝 ジェラール        術研究所建設
1505年  サイゴ族皇帝 ハールファグル    カンバーランドを帝国領にする
1756年  ホーリーオーダー皇帝 ピーター  アバロン帝国大学建設
2008年  人形皇帝 コッペリア            ヴィクトール運河奪回
2258年  ハンター皇帝 バイ              ヴィクトール運河にレオンブリッジ建設
3010年  イーリス皇帝 クラウディア      ボクオーンを倒し 地上戦艦を撃沈
3260年  ノーマッド皇帝 ベスマ          七英雄のリーダー ワグナスを浮遊城にて撃破
3510年  陰陽師皇帝 ドウマン            サイゴ族の子供を助け ダンターグ出現 これを撃破
3761年  海女皇帝 ナタリー              氷海においてスービエを撃破
4013年  最終皇帝                       アバロン新市街建設
4014年  最終皇帝                       封印の地にてクジンシーを再び撃破
4016年  最終皇帝                       サラマットを解放しロックブーケを撃破
4018年  最終皇帝                       さまよえる湖にてノエルを打倒
4019年  最終皇帝                       ラストダンジョンにてスービエを撃破
4019年  最終皇帝                       七英雄との戦いにピリオドを打つ!

陣形ロスが何よりイヤなので最終皇帝前に陣形持ちクラスを皇帝にすべく各所を制圧している. これにより 2 年のロスがあるはずなので,おそらく PS Vita 版の最大は 4021 年だろうか. ちなみにヒラガは 99 世,トーマは 80 世だった.

なお可哀想なスービエはいつも二度死ぬ.