MPEG-TS多重化(TSMF)の仕組みとbondriver_BDA等における実際のTSMF対応実装

■ はじめに

 ケーブルテレビ放送におけるMPEG TSではTSMFと呼ばれる方法によって、複数のTSが一つのTSにまとめられて送信されている場合がある。今回はTSMFによって多重化されたTSの構造を理解し、bondriver_BDAやいわゆるTSMF.txtにおけるTSMF処理を読み解くことにより、TSMFによって多重化されたTSから一つのTSを取り出す方法を検討する。

■ TSMFによって多重化されたTSの構造

◆ 用語の整理

  • 多重フレームヘッダ

TSMFの1フレーム:大体53パケット(後述)の先頭のパケット。

  • TSID

TSを識別するためのID。放送局、放送ストリームによって一意のIDが付与されているらしい。

  • ONID

オリジナルネットワークID。BSの場合0x0004。

  • 相対TS番号(相対ストリーム番号)

TSMFで多重化する際にTSにつける番号。大体1~15の15個用意されている。

  • スロット

TSMFのフレームの多重フレームヘッダの後に続く部分。1フレームが53パケットとすると52スロットある。

◆ 多重フレームの構造

多重フレームは一番最初の多重フレームヘッダのパケットと、(複数のTS)のパケットいくつかで構成されている。(複数のTS)のパケットいくつかのそれぞれの領域はスロットと呼ばれており、未使用のスロットはnullパケットで埋められている。 スロットの数は多重フレームヘッダで定義されるが、現状52しか選択肢が存在しないため、1つの多重フレームは1パケットのヘッダと52パケットの(複数のTS)パケットで構成されることとなる。

◆ 多重フレームヘッダの構造

https://www.soumu.go.jp/main_content/000324860.pdfの242ページ~250ページや、 https://www.soumu.go.jp/main_content/000329701.pdf などに記述がある。

◆ 実際のTSMFフレームの観察してみる

まず、TSMFで放送されているTSを入手する。dvb5-zap等を用いてnullパケットが含まれる形式で保存すると1個のヘッダと52個のスロットという形が認識しやすい。スクランブルを解除する必要はない。

TSを入手したら、Wiresharkを使用してTSを観察する。Wiresharkを使用すると以下の画像のようにパケットをある程度見やすい形で表示できる。選択されているパケットが多重フレームヘッダである。PIDは0x002fとなっている。この後にTSパケットが52個続き、再び多重フレームヘッダが現れる。

上の資料をもとにKaitai Structを使用してヘッダの構造を見やすい形で表示したものが以下。

相対TS1と2が使用されていることや、スロットの割当は相対TS1が最も多く、その次に未使用スロットが多く、そしてTS2が2つのみの割当であることが読み取れる。実際、相対TS1には映像が流れているため一番専有スロットが多いのは自然である。

■ 既存のTSMF対応実装

◆ bondriver_BDA

bondriver_BDAでは

[BonDriver] TSMF処理の追加 · radi-sh/BonDriver_BDA@56f7892 · GitHub

でTSMFの対応実装が行われている。

このコミットのTSMF処理の大まかな構造はTSMF処理が有効であった場合CBonTuner::DecodeProcThreadがTSMF処理をおこなうCTSMFParser::ParseTsBufferを呼び、CTSMFParser::ParseTsBufferが1パケット処理するCTSMFParser::ParseOnePacketを呼び、CTSMFParser::ParseOnePacketが多重フレームヘッダを処理するCTSMFParser::ParseTSMFHeaderを呼ぶという様になっている。

以下で示すソースコードの内、複数行コメントの行は筆者が追加したものである。

以下はParseTsBufferのParseOnePacket呼び出し付近のコード片である。(TSMF.cppの74行~78行)ParseOnePacketにTSパケットを渡し、必要なTSパケットと判断された場合(つまりParseOnePacketがTRUEを返した場合)はバッファにコピーするという処理を行っている。(不要なTSパケットは破棄している)

     if (ParseOnePacket(readBuf + readBufPos, readBufSize - readBufPos)) {
            // 必要なTSMFフレームをテンポラリバッファへ追加
            memcpy(tempBuf + tempBufPos, readBuf + readBufPos, 188);
            tempBufPos += 188;
        }

以下はParseOnePacketの内容である。(TSMF.cppの201行から256行)

BOOL CTSMFParser::ParseOnePacket(const BYTE * buf, size_t len)
{

    /*
   中略
   */

    /*
   設定ファイルから読み込んだTSIDが0xffffである場合、ヘッダを含むすべてのパケットを必要であるとして処理する
   処理しようとしているチャンネルの設定が存在しない場合はTSIDが0xffffとなっている
   */
    if (TSID == 0xffff)
        // TSID指定が0xffffならば全てのスロットを返す
        return TRUE;

    /*
   parseTSMFheaderを呼び出してパケットがヘッダであるかどうか判断する
   パケットがヘッダである場合は現在処理しているスロット番号を保持するslot_counterを0にもどして
   FALSEを返す(呼び出し元でパケットが破棄される
   */
    if (ParseTSMFHeader(buf, len)) {
        // TSMF多重フレームヘッダ
        slot_counter = 0;
        return FALSE;
    }

    /*
   なんらかの原因(例えば↑で処理したパケットがヘッダであるのにも関わらずCRCが合わなかった)で
   slot_counterが0~51の値以外となった場合はパケットを破棄
   この場合は結果的に次多重フレームヘッダを見つけるまではすべてのパケットを破棄することとなる
   */
    if (slot_counter < 0 || slot_counter > 51)
        // TSMF多重フレームの同期がとれていない
        return FALSE;
    /*
   現在処理しているスロット番号を+1
   */
    slot_counter++;

    /*
   設定ファイルで相対TS番号でTSを取り出す設定になっているかどうかを判断し
   設定ファイルで設定されているTSIDを相対TS番号として使用して取り出すか、
   TSIDとして使用しONIDと一緒に使用してTSを取り出すかの処理。
   ts_numberには相対TS番号が入る(1~15) 設定ファイル中で相対TSIDを指定する場合は0~14の値
   */
    int ts_number = 0;
    if (IsRelative) {
        // 相対TS番号を直接指定
        ts_number = (int)TSID + 1;
    }
    else {
        // ONIDとTSIDで指定
        for (int i = 0; i < 15; i++) {
            if (TSMFData.stream_info[i].stream_id == TSID && (ONID == 0xffff || TSMFData.stream_info[i].original_network_id == ONID)) {
                ts_number = i + 1;
                break;
            }
        }
    }
    /*
   ↑で得た相対TS番号を使用してパケットが必要なものか不要なものか判断
   必要ならTRUE
   不要ならFALSE(呼び出し元でパケットが破棄される)が返される
   */
    if (ts_number < 1 || ts_number > 15)
        // 該当する相対TS番号が無い
        return FALSE;

    if (TSMFData.stream_info[ts_number - 1].stream_status == 0)
        // その相対TS番号は未使用
        return FALSE;

    if (TSMFData.relative_stream_number[slot_counter - 1] != ts_number)
        // このスロットは他の相対TS番号用スロットか未割当
        return FALSE;

    return TRUE;
}

以下はParseTSMFHeaderの内容である。(TSMF.cppの129行から199行)

/*
パケットが多重フレームヘッダであった場合はTSMF処理用の変数(TSMFData~)を更新しつつTRUEを返す
パケットが多重フレームヘッダでない場合や破損した多重フレームヘッダであった場合はFALSEを返す
(パケットサイズが合わない、同期バイトが変、固定値が変、CRCが変など)
*/
BOOL CTSMFParser::ParseTSMFHeader(const BYTE * buf, size_t len)
{

    static constexpr WORD FRAME_SYNC_MASK = 0x1fff;
    static constexpr WORD FRAME_SYNC_F = 0x1a86;
    static constexpr WORD FRAME_SYNC_I = ~FRAME_SYNC_F & FRAME_SYNC_MASK;

    // パケットサイズ
    if (len < 188)
        return FALSE;

    // 同期バイト
    BYTE sync_byte = buf[0];
    if (sync_byte != TS_PACKET_SYNC_BYTE)
        return FALSE;

    /*
   多重フレームヘッダのPIDは仕様上は0x0011から0x002fの値を取るが大体0x002fであるため決め打ちとしている
   */
    // 多重フレームPID
    WORD frame_PID = (buf[1] << 8) | buf[2];
    if (frame_PID != 0x002F)
        return FALSE;

    // 固定値
    if ((buf[3] & 0xf0) != 0x10)
        return FALSE;

    // 多重フレーム同期信号
    WORD frame_sync = ((buf[4] << 8) | buf[5]) & FRAME_SYNC_MASK;
    if (frame_sync != FRAME_SYNC_F && frame_sync != FRAME_SYNC_I)
        return FALSE;

    // CRC
    if (crc32(&buf[4], 184) != 0)
        return FALSE;

    // 連続性指標
    TSMFData.continuity_counter = buf[3] & 0x0f;

    // 変更指示
    TSMFData.version_number = (buf[6] & 0xE0) >> 5;

    /*
   スロット配置法の区別は現状0x0しか考えなくて良い
   */
    // スロット配置法の区別
    TSMFData.relative_stream_number_mode = (buf[6] & 0x10) >> 4;
    if (TSMFData.relative_stream_number_mode != 0x0)
        return FALSE;
    /*
   多重フレームの形式は現状0x1しか考えなくて良い
   */
    // 多重フレーム形式
    TSMFData.frame_type = (buf[6] & 0x0f);
    if (TSMFData.frame_type != 0x1)
        return FALSE;

    // 相対ストリーム番号毎の情報
    for (int i = 0; i < 15; i++) {
        // 相対ストリーム番号に対する有効、無効指示
        TSMFData.stream_info[i].stream_status = (buf[7 + (i / 8)] & (0x80 >> (i % 8))) >> (7 - (i % 8));
        // ストリーム識別/相対ストリーム番号対応情報
        TSMFData.stream_info[i].stream_id = (buf[9 + (i * 4)] << 8) | buf[10 + (i * 4)];
        // オリジナルネットワ-ク識別/相対ストリーム番号対応情報
        TSMFData.stream_info[i].original_network_id = (buf[11 + (i * 4)] << 8) | buf[12 + (i * 4)];
        // 受信状態
        TSMFData.stream_info[i].receive_status = (buf[69 + (i / 4)] & (0xc0 >> ((i % 4) * 2))) >> ((3 - (i % 4)) * 2);
    }

    // 緊急警報指示
    TSMFData.emergency_indicator = buf[72] & 0x01;

    // 相対ストリーム番号対スロット対応情報
    for (int i = 0; i < 52; i++) {
        TSMFData.relative_stream_number[i] = (buf[73 + (i / 2)] & (0xf0 >> ((i % 2) * 4))) >> ((1 - (i % 2)) * 4);
    }
    /*
   ここまできたら正常なヘッダであったと判断しTRUEを返す
   */
    return TRUE;
}

◆ TSMF.txt(TVTest用の簡易TSMF対応patch)

TSMF.txtでは存在するTSが2個や3個などの少数であることや映像が流れているTSはいつも単一であることを仮定(?)し、専有するスロットの数が一定数未満であるTSを破棄して一つのTSを取り出している。

専有するスロットの数が一定数以上であるTSが複数個ある場合は出力されるTSに複数のTSが混ざることとなる。(恐らくこの場合はスロットがずれるため、TSMFのTSとしてinvalidなものとなる)

■ TSMF対応実装の検討

既存の放送視聴、録画ソフトウェアや新規にこのようなプログラムを開発する際のTSMF対応実装を検討する。

まず、TSMF処理にスクランブル解除が必要かあるが、不要である。多重フレームヘッダはスクランブルされておらず、スロットに入っているパケットもスクランブルされたまま取り出しても問題は発生しない。

多重フレームにTSが入っているという構図を考えるとデスクランブル前にTSを取り出すという順は自然であるが、逆に、デスクランブル後にTSを取り出すという順序を取ることができるのか気になる。EMMパケットの関係でデスクランブルに問題が発生する気がするが、筆者はスクランブル処理に対しての知識を持ち合わせていないのでコメントなどで指摘していただけると有り難い。

また、TSMF処理の前にNULLパケットを落としてはいけない。スロットがずれて処理が困難になる。

次に、TSMF処理の内容であるが、基本的な方針はbondriver_BDAと変わらない以下のような方針で実装を行えば良い。

  • 多重フレームヘッダの情報を保持する変数とスロットを数える変数を用意する(bondriver_BDAでのTSMFData,slot_counter)
  • 多重フレームの同期を取るためにまずはパケットを破棄しつつ多重フレームヘッダを探す。
  • 何らかの原因で同期が外れた場合もパケットを破棄しつつ多重フレームヘッダを探す。
  • 多重フレームヘッダを見つけた場合(ヘッダのPIDは0x002fの決め打ちとする)は情報を保持する変数を書き換えてスロットを数える変数をリセットする。(ここから同期が取れた状態となる)
  • 同期が取れたら多重フレームヘッダの情報をもとに必要なスロットに入っているパケットを出力、多重フレームヘッダと不要なパケットは破棄

必要なTSをどのように決めるかは、以下の3つの案が考えられる。

  • 案1:ONID,TSIDを指定して一つのTSを取り出す

この方法はbondriver_BDAで採用されている方法の一つで一番確実な方法に思える。しかし、ONIDやTSIDを指定するとなるとユーザーがONIDやTSIDを自分で調査して設定しないといけないという点や、ONIDやTSIDを設定する入力を用意しないとならないという実装の面倒さがある。

  • 案2:相対TS番号を指定して一つのTSを取り出す

この方法はbondriver_BDAで採用されているもう一つの方法である。相対TS番号は大体1~15の15個までしかないので相対TS番号がわからない場合でも総当りで一つのTSを取り出すことができる。相対TS番号を設定する入力を実装しないとならないという実装の面倒さは案1とあまり変わらない。しかし、この場合はヘッダのTSIDやONIDなどを取得せずとも分離処理が行える。

  • 案3:スロットを専有している数が最も多いTSを取り出す

この方法はTSMF.txtが採用している方法に近いが、常に単一のTSを取り出すことができる。簡易的な方法であるが、映像が入っているTSが一つであることを仮定すると、必然的に映像が入っているTSが一番多くのスロットを専有すると考えられるため多くの場合でうまく1つのTSを取り出すことができると考えられる。案1、案2と異なり、ユーザーが設定を行う必要がないのが利点である。

最後に、TSMFを分離するための最小限の実装を考えたとき必要となる多重フレームヘッダのフィールドを以下に示す。

  • ( PID )

多重フレームヘッダの認識に必要。0x002Fであるか確認

  • TSIDと相対TSの対応

相対TSを指定して分離する場合には不要

  • スロットと相対TSの対応

■ おわり

次回はオープンソースの録画ソフトウェアであるMirakurunを例に実際にTSMFの処理の実装、検証を行い、TSMF対応の実装を引き続き検討する。

追記: 多重フレーム(TSMF)の処理への対応 by akkkix · Pull Request #90 · Chinachu/Mirakurun · GitHub で実装を行った。

間違い、意見等あればぜひコメントやTwitter等で教えていただけると嬉しいです。

OSが書けなくても画面にお絵描きがしたい

PCでお絵描きがしたい、けど…

冬です

外は寒いのでこたつに入ってPCでお絵描きでもしたいところです

しかし、筆者のパソコンはOSもペイントソフトも入っておらずお絵描きをすることができません

どうしてもお絵描きがしたいので、今日は頑張ってPCの画面にお絵描きできるようにします

この記事はWORDIAN Advent Calendar 2018 - 23日目の記事です

戦略

さて、PCを動かすためにまずはブートローダが必要です。ブートローダBIOSの仕様に沿って実装する訳ですが、アセンブリを書いてプログラムがメモリの0x7c00にロードされ...32bitプロテクトモードに移行して...のように非常に難解なことをしないといけません。冬が終わってしまいます。世の中は便利なものでGRUBと呼ばれる素晴らしいブートローダが存在するため、ありがたく利用することにします。GRUBはよろしくやってくれた後にフレームバッファの場所を教えてくれるのでそこに色を書き込むことでお絵描きができます。

GRUBを用意したりプログラムをコンパイルしたりするためのPCは別途用意することにしました。

GRUBを用意する

PCにGRUBをインストールします。ArchLinuxの人は

sudo pacman -S grub

で入ります。

GRUBで読み込めるプログラムの形式について

GRUBではmultibootと呼ばれるプログラムの形式を使用することができます。お絵描きするために最低限必要な事項は本記事で記述しますが、詳しい仕様についてはMultiboot Specification version 0.6.96をご確認ください。

Multiboot Specificationの3.1 OS image formatではプログラムの形式についての規定がされています。いろいろ書いてありますが、どうやらプログラムにはMultiboot headerが必要でファイルの先頭8192バイトに格納されている必要があるようです。3.2 Machine stateと3.3 Boot information formatではmultibootが読み込まれた後にGRUBが返してくれるマシンの状態に関する記述があります。これについても後述します。

Multibot headerの形式

Offset Type Field Name Note
0 unsigned 32bit magic required
4 unsigned 32bit flags required
8 unsigned 32bit checksum required
12 unsigned 32bit header_addr if flags[16] is set 今回は使わない
16 unsigned 32bit load_addr if flags[16] is set 今回は使わない
20 unsigned 32bit load_end_addr if flags[16] is set 今回は使わない
24 unsigned 32bit bss_end_addr if flags[16] is set 今回は使わない
28 unsigned 32bit entry_addr if flags[16] is set 今回は使わない
32 unsigned 32bit mode_type if flags[2] is set
36 unsigned 32bit width if flags[2] is set
40 unsigned 32bit height if flags[2] is set
44 unsigned 32bit depth if flags[2] is set
  • magic
    マジックナンバーです。必ず0x1BADB002を格納します。

  • flags
    bit 0,1,2,15を使います。bit 2を1にするとビデオモードを設定することができます。今回はとりあえず1にしましょう。 bit 0,1,15はそれぞれ1,1,0にしておきます。お絵描きに関係ないので説明は割愛します。

  • checksum
    チェックサムです。-(magic+flags)を格納します。

  • mode_type
    ビデオモードの希望です。グラフィックモード(0)かテキストモード(1)のどちらかに設定することができます。お絵描きするにはグラフィックモードのほうが嬉しいので今回は0に設定しておきます。

  • width,height
    画面のサイズの希望を設定することができます。お絵描きにおけるキャンパスサイズとなります。とりあえず最初はwidthを640、heightを480くらいにしておくことにします。

  • depth
    ビット深度の希望です。32にします。

  • その他
    今回は使わないため説明は割愛します。値は適当に0にしておきましょう。

multibootが読み込まれた後のマシンのレジスタの状態

色々と記述がありますがとりあえずEBXとついて気にしておけば良さそうです。

GRUBから渡されるブート情報について

multibootがGRUBに読み込まれるとEBXにブート情報構造体のポインタが格納されます。お絵描きをするために必要なフレームバッファの情報もあります。

             +-------------------+
     0       | flags             |    (required)
             +-------------------+
     4       | mem_lower         |    (present if flags[0] is set)
     8       | mem_upper         |    (present if flags[0] is set)
             +-------------------+
     12      | boot_device       |    (present if flags[1] is set)
             +-------------------+
     16      | cmdline           |    (present if flags[2] is set)
             +-------------------+
     20      | mods_count        |    (present if flags[3] is set)
     24      | mods_addr         |    (present if flags[3] is set)
             +-------------------+
     28 - 40 | syms              |    (present if flags[4] or
             |                   |                flags[5] is set)
             +-------------------+
     44      | mmap_length       |    (present if flags[6] is set)
     48      | mmap_addr         |    (present if flags[6] is set)
             +-------------------+
     52      | drives_length     |    (present if flags[7] is set)
     56      | drives_addr       |    (present if flags[7] is set)
             +-------------------+
     60      | config_table      |    (present if flags[8] is set)
             +-------------------+
     64      | boot_loader_name  |    (present if flags[9] is set)
             +-------------------+
     68      | apm_table         |    (present if flags[10] is set)
             +-------------------+
     72      | vbe_control_info  |    (present if flags[11] is set)
     76      | vbe_mode_info     |
     80      | vbe_mode          |
     82      | vbe_interface_seg |
     84      | vbe_interface_off |
     86      | vbe_interface_len |
             +-------------------+
     88      | framebuffer_addr  |    (present if flags[12] is set)
     96      | framebuffer_pitch |
     100     | framebuffer_width |
     104     | framebuffer_height|
     108     | framebuffer_bpp   |
     109     | framebuffer_type  |
     110-115 | color_info        |
             +-------------------+

色々ありますが必要な部分はオフセット88からのフレームバッファに関する情報のみです。

Offset Type Field Name
88 unsigned 64bit framebuffer_addr
96 unsigned 32bit framebuffer_pitch
100 unsigned 32bit framebuffer_width
104 unsigned 32bit framebuffer_height
108 unsigned 32bit framebuffer_bpp
109 unsigned 32bit framebuffer_type
110-115 unsigned 32bit color_info
  • framebuffer_addr
    フレームバッファの先頭のアドレスです。64bitです。注意

  • framebuffer_pitch
    バイトのピッチです。単位はバイト

  • framebuffer_width , framebuffer_hight GRUBが設定したフレームバッファの縦横のサイズです。

  • framebuffer_bpp GRUBが設定したフレームバッファのビット深度です。

  • framebuffer_type GRUBが設定したフレームバッファの種類です。0だとインデックスカラー、1だとダイレクトカラー、2だとテキストモードになっています。また、0,1ではこの後のcolor_infoが使用されます。

  • color_info
    framebuffer_typeが1(ダイレクトカラーの場合)

             +----------------------------------+
     110     | framebuffer_red_field_position   |
     111     | framebuffer_red_mask_size        |
     112     | framebuffer_green_field_position |
     113     | framebuffer_green_mask_size      |
     114     | framebuffer_blue_field_position  |
     115     | framebuffer_blue_mask_size       |
             +----------------------------------+

32bit中に各色がどのように配置されるかが格納されています。positionが下位ビットから何bit目からかを、mask_sizeがビットの幅を表しています。 今回はダイレクトカラーに設定されることを前提とするため、インデックスカラーの場合の説明は割愛します。

実装

Multiboot Specificationの4.3 Example OS codeにはサンプルコードがあります。せっかくなので利用します。

プリプロセッサ定義や構造体が含まているmultiboot.hはそのまま使います。

multiboot.h (クリックで展開)

boot.SではMultiboot headerを配置してC言語コンパイルした関数を呼びます。サンプルから不要な部分を削除して最小限必要な部分のみを残しました。

画像データを描画するCプログラムです。ダイレクトカラーでdepthが32bitに設定されていることが前提です。とりあえず画面を緑色で塗りました。

コードが書けたら

$ gcc -m32 -ffreestanding -fno-common -fno-builtin -fomit-frame-pointer -O2 -c -o oekaki.o oekaki.c
$ gcc -m32 -ffreestanding -fno-common -fno-builtin -fomit-frame-pointer -O2 -c -o boot.o boot.S
$ ld  -melf_i386  -Ttext=0x100000 --oformat elf32-i386 -o Oekaki boot.o oekaki.o

のようにコンパイルとリンクを行います。テキストエディタgrub.cfgも作成しましょう。

menuentry "oekaki" {
    multiboot /boot/Oekaki
    boot
}

grub-mkrescueでブートイメージを作成します。

$ mkdir -p iso/boot/grub
$ mv Oekaki iso/boot
$ mv grub.cfg iso/boot/grub
$ grub-mkrescue -o oekaki.iso iso

作成したプログラムのブートイメージが作成されました。試しにqemuでブートしてみましょう。

これでフレームバッファの中身を画面に出せるようになりました。後はソースコードを適当にいじれば画像等も表示することができます。

めでたしめでたし

セキュリティ・キャンプ2016の思い出

応募まで

気づいたら締め切り3日前だった。
土日寝ないで書いたから文章がヤバい。
応募するだけでもためになると思う。

選考結果発表

授業中に結果のメールがスマートウォッチに来た。
受かると思ってなかったので嬉しすぎてその日は授業に身が入らなかった。
去年の発表は6/14だったので今年もそろそろですね。

前日

正直当日起きれるか微妙だったので夜行バスで東京に行くことにした。
しかし、夜行バスに乗り遅れて死

1日目

早朝に新宿に到着し時間まで東京をふらつく。
(当時流行っていたアニメ「NewGame!!」の聖地巡礼で阿佐ヶ谷へ行ったり秋葉原行ったりしてました)

時間ギリギリになりつつも会場に到着し名刺交換等...

3日目

寝坊した。
運営からのモーニングコール(絶望)で飛び起き、急いで支度して部屋を出ようとしていたところに運営の人が部屋まで来た。

5日目

貰いました

修了証書授与の時に「眠い?」って聞かれた。(大丈夫です。ちゃんと起きてます。) このあと適当に東京ふらついて赤羽の宿へ

X日目(コミックマーケット90)

二回目のコミケ参加。

セキュリティキャンプ関係者のサークル等を廻る。
その後新幹線で帰路へ

グループ「おねぇちゃぁん(裏声)」の人たちへ

お役に立てず本当に申し訳ありませんでした

BrainFu*kインタプリタを書いた

この記事は#一人GWハッカソン 5/3の記事です

今日のテーマはなんかのインタプリタを作るです

最初は自分で言語作る気満々でした

11:00~12:00 言語仕様の妄想

  • 四則演算ができる
  • スタックが用意されていて自由に使える
  • 変数は予め定められた8つ
  • 配列なんてなかった
  • 標準で用意されている関数はprintのみ
  • if,while,breakが使える
  • for文なんてなかった

12:30~13:55

AbemaTVで「変態王子と笑わない猫。」一挙放送やってたので見ました。
脳が溶けました。

13:00 ~ 14:00 感覚でコードを書き始める

javascriptで作った四則演算のプログラムを参考にprintとかを実装し始めるものの、
途中でわけわからなくなって死。

14:00 ~ 調査

流石に感覚で書くには無理があると感じ、ネットの情報を漁り始める。
一般的なインタプリタでは字句解析と構文解析というものが行われているらしい。
そして構文解析によって生成された構文木を元にプログラムが実行される。
なるほどなるほど。構文木とかどうやって実装したら良いのか全くわからないんですけど…ウケる…

15:00~16:00 (:3 」∠ )

17:00~ BrainがFu*ck

結局何もできてねーなとか思いながら微妙な気分になる。
何も成果物が無いのは流石にアレなので最終手段として考えていたBrainFu*ckを書き始める…

BrainFuckの実装は[,]さえ実装してしまえば後は簡単だった。

BrainFu*k f:id:akkkix:20170504084158j:plain

なんの捻りもないただのBrainFuc*kが完成した。

反省…

昔四則演算のプログラム書いたことあるしいけると思ってた 実装力がなくてつらいなぁ…
GW後半らへんにネタ切れになったらまた挑戦したい…

参考:

Brainfuck - Wikipedia

http://www.hpcs.cs.tsukuba.ac.jp/~msato/lecture-note/comp-lecture/

プログラミング言語を作る

四則演算(昔書いたやつ。再帰下降構文解析っていうらしい。):

sisokuenzan

OSなしでHelloWorld

この記事は#一人GWハッカソン 5/2の記事です

本日のテーマはOSなしでHelloWorldをするです

最初はOSを作ろうと思っていましたが一日では無理でした。

10:00~12:00 環境構築

Windowsで環境を構築しようとしたけど上手くいかない… 仕方なくVirtualBoxの上のLinuxを使用する

OSはXubuntu16.04、アセンブラはnasm、仮想マシンにはqemuvirtualboxを使用

12:00~13:00 初めてのプログラム

 [BITS 16] 
ORG 0x7C00
BOOT: 
CLI
HLT 
TIMES 510 - ($ - $$) DB 0
DW 0xAA55

起動した後にブートセクタがメモリの0x7C00に読み込まれるらしいのでORGで0x7C00をセット(GASではORGは使えないみたいですね)
CLIで割り込み禁止、HLTでプログラム終了
最後に位置合わせに0x00を出力しブートセクタ終端の0xAA55
C言語でいう

#include ‹stdio.h›
int main(void){
   return 0;
}

みたいなプログラムらしい。

13:00~14:00 いろいろ

スタックとかセグメントとか

14:00~15:00 INT10

画面入出力とかディスクの読み書きとかするためにはBIOSが提供している機能を使う
画面入出力をするためにはINT 0x10を使う。

INT0x10を使って文字を出力するためには
AHに0x0E
ALに文字のASCIIコード
BHにビデオページ番号
(よくわからないけど0x00で良いらしい)
BLに色を指定

 [BITS 16] 
ORG 0x7C00
BOOT: 
CLI
MOV AH, 0x0E
MOV AL, 0x41 ;'A'
MOV BH, 0x00
MOV BL, 0x07 ;グレー
INT 0x10
HLT 
TIMES 510 - ($ - $$) DB 0
DW 0xAA55

色が変わらないと思ったらビデオモードというのを変えないといけないらしい
グラフィックモードを指定するためにはAXに0x4f02を入れてBXにビデオモードを入れる ビデオモードは VESA BIOS Extensions - Wikipedia で解説されている

 [BITS 16] 
ORG 0x7C00
BOOT: 
CLI
MOV AX, 0x4f02
MOV BX, 0x0102 ;800x600 16色モード
INT 0x10

MOV AH, 0x0E
MOV AL, 0x41 ;'A'
MOV BH, 0x00
MOV BL, 0x0A ;ライトグリーン
INT 0x10
HLT 
TIMES 510 - ($ - $$) DB 0
DW 0xAA55

AHに0x13を入れてINT 0x10すると文字列が表示できるらしいです
なんかちょっと感動

15:00~16:00 寝

昼寝

16:00~ ゚Ω\ζ°)チーン

ディスクの読み書きにハマる
解決方法が分からず終

反省とか

またいつか挑戦したい
こんな感じであと5日浅く広く色々やれればと思う

参考:

http://softwaretechnique.jp/OS_Development/scratchbuild.html

https://en.wikipedia.org/wiki/INT_10H

VyOSでルーターを作る

お久しぶりです。

訳あって家のネットワークを分ける必要がでてきたので、今日はVyOSを使ってルーターを作ってみます。

VyOSとは

今日の目標

トポロジ
f:id:akkkix:20170127173204j:plain
いわゆる2重ルーターです

ファイアウォール

送信元 送信先 操作
192.168.2.0/24 192.168.1.0/24 DROP

ポート開放

受信ポート 送信先 送信ポート
1234 192.168.2.2 22

インストール

https://vyos.io/
stableをダウンロードしました。(v1.1.7)

イメージから起動し、ログインします。

install image

インストールが始まるので少し待ちましょう。

インターフェイスへのIPアドレスの割当

インストールが終わったら、起動させてログインします。

configure

設定モードへ入ります。
eth0を192.168.1.2/24、eth1を192.168.2.1/24にします。

set interfaces ethernet eth0 address 192.168.1.2/24
set interfaces ethernet eth1 address 192.168.2.1/24

NATの設定

外部側がeth0、内部側がeth1です。

set nat source rule 1 outbound-interface eth0
set nat source rule 1 source address 192.168.2.0/24
set nat translation address masquerade

これで192.168.2.0/24内のホストがNATされて192.168.1.0/24側に出られるようになりました。

デフォルトゲートウェイの設定

このままでは192.168.2.0/24側のホストはインターネットに出られません。
次に経由するルーター(トポロジのPR-S300NE)を指定します。

protocols static route 0.0.0.0/0 next-hop 192.168.1.1

※systemのgateway-addressでも設定できるみたいです

set system gateway-address 192.168.1.1

ファイアウォールの設定

今回は192.168.2.0/24から送信されたパケットの宛先が192.168.1.0/24だった場合のみDROPします。

set firewall name private-public default action accept
set firewall name private-public rule 1 destination address 192.168.1.0/24

お好みでログを設定します。

set firewall name private-public rule 1 log enable

インターフェイスファイアウォールを設定します。

set interfaces ethernet eth1 firewall in name private-to-public

ポート開放

内部のホストにsshできるようにしておきます。

set nat destination rule 1 inbound-interface eth0
set nat destination rule 1 protocol tcp
set nat destination rule 1 destination address 192.168.1.2
set nat destination rule 1 port 1234
set nat destination rule 1 translation address 192.168.2.2
set nat destination rule 1 port 22

2重ルーターになっているのでインターネットからアクセスしたい場合はインターネットに接続しているルーターもポート開放します。

VyosにSSHで接続できるようにする

面倒なのでSSHでどこからでもVyOSを設定できるようにします。

set service ssh port 22

NTPの設定

ログ等に正確な時間が記録されるようにVyOSをNTPで時刻合わせします。

set system ntp server ntp.jst.mfeed.ad.jp

DNSIPアドレスの設定

DHCP等は特に設定していないのでホスト名を解決できるようにDNSを設定します。

set system name-server 8.8.8.8

設定の反映と保存

設定を変更したら忘れずに

commit
save

やりましょう。

静的ルーティングの設定

最後に192.168.1.0/24から192.168.2.0/24にアクセスできるように静的ルーティングを設定しておきます。
(PR-S300NEの例)
f:id:akkkix:20170127184035j:plain

まとめ

ネットワークの勉強がてらVyOSでルーターを作ってみましたが案外簡単に設定できて拍子抜けでした。
BGPやOSPF等にも対応しているそうなので別の機会にそちらも試してみたいです。
わざわざ高いルーターを買わなくてもこのような形で勉強できるのは素晴らしいと思います。

RTX1000のIPアドレス割り当てとファームウェア更新

ちょっと前のことですが某所でRTX1000を買いました。

(下のやつです)

IPアドレスの設定

初期状態ではIPアドレスを持ってないので設定にはコンソールケーブルかRARPIPアドレスを設定して上げる必要がある。

コンソールケーブルなんて持ってないのでRARPIPアドレスを設定することにした。

早速手持ちのPC(ArchLinux)でrarpdを動かそうとしたらよくわからないエラーがでてこけるので仕方なくArchのインストールメディアを引っ張り出してきてRARPを動かしたところ無事IPアドレスが設定された。

f:id:akkkix:20160626200943p:plain

> vim /etc/ethers
00:A0:DE:xx:xx:xx xxx.xxx.xxx.xxx
> rarpd -dvae

ここでRTX1000を起動するとIPアドレスが割り当てられる

早速telnetでログインして管理者権限へ

> telnet xxx.xxx.xxx.xxx
> administrator

文字化けしまくってるので文字コードをASCIIにしておいた。

> console character ascii

忘れないうちにIPアドレスを設定しておく

> ip lan1 address xxx.xxx.xxx.xxx/xx

保存

> save

ファームウェアの更新

YAMAHAのサイトで確認したところ、ファームウェアが最新ではなかったのでファームウェアも更新しておく。

http://www.rtpro.yamaha.co.jp/RT/firmware/index.php

ファームウェアのアップロードには RT-Tftp Client という便利なソフトがあったのでありがたく使わせてもらった。

http://www.rtpro.yamaha.co.jp/RT/utility/rt-tftp.html

f:id:akkkix:20160626185413p:plain

ファームウェアを指定しアップロードしたところ…

f:id:akkkix:20160626185423p:plain

なぜか「転送は失敗しました」と出てかなりヒヤッっとしたが無事アップデートされた。

f:id:akkkix:20160626201228p:plain