トップ 追記

本 日 の h o g e

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

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


10/04/2019 ふむ [長年日記]

tDiary 6189日目

[Linux][小ネタ] netplan と systemd-networkd と macvlan とわたし

現状の netplan (0.97) では直接 macvlan インタフェースを作れないらしい.しょうがないので直接 networkd の設定を書いて作ることを考える.

下記のようなインタフェースがあったとして,

# cat /etc/netplan/02-netdev.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    ens3:
      dhcp4: no
    ens4:
      dhcp4: no
    ens5:
      dhcp4: no

ens5 から macvlan インタフェース vmac0 を生やしてみる. networkd の設定はこんな感じになる.

# cat /etc/systemd/network/00-ens5.network
[Match]
Name=ens5

[Network]
MACVLAN=vmac0
LinkLocalAddressing=no

# cat /etc/systemd/network/90-vmac0.netdev
[NetDev]
Name=vmac0
Kind=macvlan
MACAddress=00:00:00:00:00:01

[MACVLAN]
Mode=private

# cat /etc/systemd/network/90-vmac0.network
[Match]
Name=vmac0

[Network]
LinkLocalAddressing=no

上記はうまくいく例で,下記のように,意図通りにインタフェースが生成される.

# ip -d link show dev vmac0
6: vmac0@ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 00:00:00:00:00:01 brd ff:ff:ff:ff:ff:ff promiscuity 0 
    macvlan mode private addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 

さてここからがハマりどころの話.

netplan はその設定情報を元に /run/systemd/network/10-netplan-<インタフェース名>.network というファイルを生成するようだ.上記の例では ens5 が netplan の設定に書かれているため,下記のようなファイルが生成される.

# cat /run/systemd/network/10-netplan-ens5.network
[Match]
Name=ens5

[Network]
LinkLocalAddressing=ipv6

勘の良い諸兄はもうお分かりだろう.上記「うまくいく例」がうまくいくのは,ens5 の networkd 設定を 00-ens5.network という名前にして,netplan が書き出すものより優先的に使用されるようにしたからである. これを例えば 90-ens5.network などと netplan が書き出すものより弱いファイル名にすると,意図通りにインタフェースが生成されず,涙で枕を濡らすことになってしまうことだろう.

もちろん macvlan の元インタフェース (上記例では ens5) については netplan の設定に書かない,という選択もある. しかしいずれにしても設定が分散することになるので,全国津々浦々の macvlan ファンにとって現状の netplan が嬉しくないことに変わりはないだろう.netplan 先生の次回作に期待である.


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 に参加させるといったことを考えると分かりやすい.

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

mped [色々、お久しぶりです。 昔、O2やオクティン、SGI辺りのUNIX…で、フィンガーデスグリップっていう、WINDO..]

mpet [mpedって、madな感じだな。久々過ぎて間違えたmpetです。 まぁ、unixとか、知らん世代だと、どっちにしろ..]


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 は大差ないので大した意味はない.