bashによるネイティブDHCPサーバー

プロセスを自動化し、自分自身の自転車を書いて、この資料やその資料を研究するのが大好きです。 私の新しい目標は、機器の初期設定を行えるように小規模ネットワークでアドレスを提供するDHCPサーバーでした。

この記事では、DHCPプロトコルとbashの微妙な点について少し説明します。



最終結果


最後から始めましょう。そうすれば、私たちが何のために戦っているのかがはっきりします。

仕事のデモンストレーション:



スクリプトを使用したリポジトリ: firemoon777 / bash-dhcp-server

初期問題


必要な構成は次のように行われます。ツイストペアケーブルを介して機器に直接接続し、DHCPを介して一時アドレスを発行し、作成済みのスクリプトで構成します。 そして、10から20回連続で。

多くの人にとって、有名なisc-dhcp-serverはうまく機能しますが、残念ながら、アドレスが発行されたことをスクリプトに通知しないため、アドレスが発行されるまで実行をブロックする必要があります。

解決策は表面上にあるようです:顔が青くなるまで、機器が応答するまでpingを実行します。

while ! ping -c1 -W1 "$DHCP" | grep -q "time=" do echo "Waiting for $DHCP..." done 

しかし、この決定には間違いなく冒険主義が欠けています。

理論部


単一のDHCPサーバーでアドレスを取得する



DHCPは、ポート67および68でUDPの上で動作します。サーバーは常に67でのみ動作し、クライアントは68でのみ動作します。クライアントにはアドレスがないため(アドレス0.0.0.0がある)、DHCPパケットはブロードキャストされます。 つまり クライアントは常にアドレス0.0.0.0:68からアドレス255.255.255.255:67にパケットを送信し、サーバーはアドレス67からアドレス255.255.255.255:68にパケットを送信します。

クライアントは、 4つのパケット( DORA )でアドレスを受信します。

  1. クライアントはDHCPサーバーの場所を見つけます( D iscover)
  2. サーバーは応答し、そのアドレスを提供します( O ffer)
  3. クライアントは、特定のサーバーに提案されたアドレスを要求します( R equest)
  4. サーバーは同意してアドレスを発行します( A ck)

視覚的には、スキームは次のように表すことができます。



複数のDHCPサーバーでアドレスを取得する



クライアントがDiscoverを送信すると、聞くことができるすべてのサーバーがクライアントにオファーを送信します。 ただし、クライアントはいずれかを選択する必要があります。 クライアントの選択は、優先DHCPサーバーのIPアドレスを含むオプション54(DHCPサーバー)の要求メッセージで通知されます。 要求はネットワーク上の全員にも送信されますが、オプション54でIPが指定されているDHCPサーバーのみが応答します。



DHCPパケットの内容


DHCPパケットは、サイズが236バイトの定数と、オプションを伝送する変数(DHCPオプション)の2つの部分で構成されています。

ウィキペディアのDHCPパケットのすべてのフィールドを含む表
フィールド説明長さ(バイト単位)
op
メッセージのタイプ。 たとえば、BOOTREQUEST(0x01、クライアントからサーバーへの要求)およびBOOTREPLY(0x02、サーバーからクライアントへの応答)の値を取ることができます。
1
htype
ハードウェアアドレスのタイプ。 このフィールドの有効な値は、RFC 1700 Assigned Numbersで定義されています。 たとえば、イーサネットMACアドレスの場合、このフィールドは0x01に設定されます。
1
ヘレン
ハードウェアアドレスの長さ(バイト単位)。 イーサネットMACアドレスは0x06です。
1
ホップ
メッセージが通過した中間ルーター(いわゆるDHCPリレーエージェント )の数。 クライアントはこのフィールドを0x00に設定します。
1
xid
アドレス取得プロセスの開始時にクライアントによって生成された4バイトの一意のトランザクション識別子。
4

アドレス取得プロセスの開始からの秒数。 使用できません(この場合、0x0000に設定されています)。
2

フラグのフィールドは、DHCPプロトコルの特別なパラメーターです。
2
ciaddr
クライアントIPアドレス。 クライアントが既に独自のIPアドレスを持ち、ARP要求に応答できる場合にのみ満たされます(これは、クライアントがリースの期限切れ後にアドレスを更新する手順を実行する場合に可能です)。
4
yiaddr
サーバーによって提案された新しいクライアントIPアドレス。
4
siaddr
サーバーのIPアドレス。 DHCP句で返されます(以下を参照)。
4
ギアド
DHCPメッセージをサーバーに配信するプロセスに関与している場合、リレーエージェントのIPアドレス。
4
chaddr
クライアントのハードウェアアドレス(通常はMACアドレス)。
16

NULLで終わる文字列としてのオプションのサーバー名。
64
ファイル
リモートでダウンロードするときにディスクレスワークステーションが使用するオプションのサーバーファイル名。 snameと同様に、nullで終わる文字列として表されます。
128
オプション
DHCPオプションフィールド。 さまざまな追加の構成オプションがここに示されています。 このフィールドの先頭には、値99、130、83、99(「マジックナンバー」)を持つ4つの特別なバイトが示され、サーバーがこのフィールドの存在を判断できるようにします。 フィールドの長さは可変ですが、DHCPクライアントは576バイトのDHCPメッセージを受信する準備ができている必要があります(このメッセージでは、 オプションフィールドの長さは340バイトです)。
可変


RFC 2132のすべてのDHCPオプションのリスト

DHCPオプションは次のようにエンコードされます。
長さデータ

たとえば、パラメータ3(提案されたゲートウェイ)の値は10.0.0.1です。
3410001

複数のパラメーターを渡す必要がある場合、パラメーターの長さが長くなります。
たとえば、パラメーター6(DNSサーバー)では、2つのアドレス(1.1.1.1および8.8.4.4)を送信します。
6811118844

オプションフィールドの終わりのサインは、数値が255(0xFF)で長さが0のパラメーターです。

ほとんどの場合、クライアントはDHCP 55にパラメータ55(応答として受信するパラメータのリスト)を入れますが、要求したすべてではないものを提供する権利があります。

実用部


元々、この目的のために、より適切な言語(C)でサーバーを作成することを計画していましたが、それは平凡で単純なものです。 それは、dhcpサーバーの機能を引き継ぐスクリプトを書くことの問題です。

単純化


開発中のサーバーは、パッチで接続された2つのノードのネットワークで使用されることになっていたため、次の単純化が採用されました。


リスナー


まず、パケットを受信する方法を学ぶ必要があります。 これには、ncなどの認定された交感神経リスナーが必要です。 しかし、すべてのncがこれらの目的に適しているわけではありません。 DebianのOpenBSD netcat 1.130が適していますが、Ubuntuの1.105はなくなりました。 ncを実行して、ポート67に到着するすべてのUDPパケットをリッスンします。

 nc -l 0.0.0.0 -up 67 -w0 

値が0の-wスイッチのため、OpenBSD netcatも必要です。1パケット(UDPブロードキャスト)を受信した後、従来のncはそれ以上パケットを受信しませんが、終了しません。

生バイト処理


シェルでは、ヌル文字などの印刷不可能な文字を扱うのは非常に困難です。単に無視します。 DHCPパケットには、多くのバイト0x00(たとえば、ファイルフィールド)が含まれます。 問題の解決策は、16進ダンプの形式で提供されます。

  nc -l 0.0.0.0 -up 67 -w0 | stdbuf -o0 od -v -w1 -t x1 -An 

アドレスを出力せず、重複バイトをスキップせずに、1行に1バイト。 出力がバッファリングされないように、stdbuf -o0にスパイスを加えることもできます。

パッケージの受け取り、保管、処理


odコマンドの標準出力から、バイトは読み取りコマンドによって取得され、配列に追加されます。

 msg=() for i in {0..235}; do read -r tmp msg[$i]=$tmp done 

すべての値は16進表記で送信されますが、DHCPオプション番号とオプションの長さは、画面/ログの通常の10進数形式で最適に表示されます。 これを行うには、短いエントリbash'aを使用できます。

 $ op=AC $ echo $((16#$op)) 172 

受信したパケットは、要求のタイプ(検出または要求)に従って編集され、返送されます。

応答


ただし、パッケージの送信はそれほど簡単な作業ではありません。 最初に、ダンプのバイトを生のバイトに変換し、1つのパケットですべてを一度に送信する必要があります。

エスケープシーケンスを使用して、printfユーティリティで変換を行うことができます。 そして、何も失われないように、すぐにファイルにバイトを書き込みます。

 #   >/tmp/dhcp.payload #     for i in ${msg[*]}; do printf "\x$i" >> /tmp/dhcp.payload done 

OpenBSD netcatは送信にも使用されます。 ただし、Ubuntuを含むバージョン1.105がリスナーとして適している場合、UDPメッセージのブロードキャストには適していません。プロトコルが利用できないというエラーが発生します。

 cat /tmp/dhcp.payload | nc -ub 255.255.255.255 68 -s $SERVER -p 67 -w0 

-bスイッチを使用すると、ブロードキャストメッセージを送信できます。これが、サーバーをスーパーユーザーで実行する必要がある2番目の理由です。

制限は何ですか?


このDHCPサーバーは、ネットワーク上の単一のクライアントのように単純化されて設計されました。 ただし、複数のクライアントで動作します。 最速の住所を取得するだけです。



おわりに


bashスクリプトを本格的なプログラミング言語と呼ぶことはほとんどできませんが、それでも、必要に応じて、特別に設計されたソフトウェアを使用せずにネットワーク上でIPアドレスを発行するなどの問題を解決できます。 また、特定の問題を解決することは喜びをもたらすだけでなく、解決時に開かれた新しい知識ももたらします。

ソース


  1. DHCP-ウィキペディア
  2. DHCPおよびBOOTPパラメーター-IANA

Source: https://habr.com/ru/post/J435148/


All Articles