Elmでぷよぷよ
前提として、Javascriptも関数型言語もほとんどやったことないので、慣れてない部分が多くコードも綺麗じゃないのはご勘弁いただきたい。
また、途中まで qiita.com の丸パクリなので、最初は↑読んだ方が良いかも。
とりあえずコードは
に上げる。
盤面を定義
まずは、Elmでどうやって盤面を定義・表示するかを考えてみる。
ElmではModel(アプリの状態)、Veiw(画面表示)、Update(状態の更新)の3つの要素で構成されるらしい。 The Elm Architecture · An Introduction to Elm まずはわかりやすいようにUpdateは考えずに、「Modelを定義して、初期値入れて、Viewで表示する」ってところまでやってみたい。
Modelの定義します。後々の処理をわかりやすくするために、盤面は2次元配列的な感じで定義する。 下のような感じ。
type alias Model = { board: Board } type alias Board = Array Row type alias Row = Array Cell type Cell = Tile Int | Empty
Boardは2次元配列になっていて、各配列にはCell情報が入っている。
CellはTileとEmptyの2つの状態を持っていて、Emptyはその名の通り何もないCell、Tileは何らかのぷよがある状態。
Tile Int
とすることで、Intの部分でぷよの種類を識別するようにしている。
次にModelに初期値をセットする。
init : () -> ( Model, Cmd Msg ) init _ = let board = Array.repeat 10 <| Array.repeat 8<| Empty in ( Model board , Cmd.none )
let 〜 in
の部分で初期値となる2次元配列(board)を生成する。Array.repeat
はelm coreで用意されている関数で、指定した長さの配列を自動で生成してくれる。
Elmでは処理内容に応じて多くのmoduleが用意されているのでどんどん使っていく。
続いてView
先ほどModelで定義した2次元配列をひたすらdiv
囲って表示することで何となく盤面っぽいのを作る。
-- VIEW view : Model -> Html Msg view model = div [] [ div [] [ viewBoard model.board ] ] viewBoard : Board -> Html Msg viewBoard board = div Styles.boardStyle <| Array.toList (Array.map viewRow board) viewRow : Row -> Html Msg viewRow row = div [] <| Array.toList (Array.map viewCell row) viewCell : Cell -> Html Msg viewCell cell = case cell of Tile number -> div Styles.cellStyle [ text t ] Empty -> div Styles.cellStyle [ text "" ]
少しわかりにくいが、viewBoardでboardをRowに切り分けてviewRowへ。viewRowではRowをCellに分けてviewCellへ。 viewCellではboardに定義されたcellの状態をcaseで分類して、何を表示するか決めている。
Style.cellStyle
は外出ししたスタイルシートを読み込んでいるだけなので、今は無視でOK。
最後に(最初に書くべきだが)main文を書く
-- MAIN main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view }
もはや呪文に近いが、副作用のあるコードを書く場合はBrowser.element
というBrowserモジュールを利用する。
Browserモジュールについては
がわかりやすかった。
今の状態では副作用の無いコードだが、後々副作用を入れてくのでこれにする。
subscriptions
はまだ使わないので側だけ定義
-- SUB subscriptions model = Sub.none
ここまでのコードをまとめ
import Browser import Array exposing (Array) import Html exposing (..) import Html.Attributes exposing (..) import Styles -- Model type alias Model = { board: Board } type alias Board = Array Row type alias Row = Array Cell type Cell = Tile Int | Empty init : () -> ( Model, Cmd Msg ) init _ = let board = Array.repeat 10 <| Array.repeat 8<| Empty in ( Model board , Cmd.none ) -- Update type Msg = Demo update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Demo -> ( model , Cmd.none ) -- VIEW view : Model -> Html Msg view model = div [] [ div [] [ viewBoard model.board ] ] viewBoard : Board -> Html Msg viewBoard board = div Styles.boardStyle <| Array.toList (Array.map viewRow board) viewRow : Row -> Html Msg viewRow row = div [] <| Array.toList (Array.map viewCell row) viewCell : Cell -> Html Msg viewCell cell = case cell of Tile number -> div Styles.cellStyle [ text "foo" ] Empty -> div Styles.cellStyle [ text "" ] -- SUB subscriptions model = Sub.none -- MAIN main = Browser.element { init = init , update = update , subscriptions = subscriptions , view = view }
とりあえず盤面はこれで完成。
盤面の変更
次に盤面の変更を行う。
今は全部Emptyなので、盤面を変更する。
やることは、2次元配列に値をセットするだけ。ただし、Elmで配列を参照する際は、必ず結果がMaybeで帰ってくるので、そこだけ注意して書く必要がある。
setBoard : (Int, Int) -> Cell -> Board -> Board setBoard ( x, y ) cell board = Array.get y board |> Maybe.map (\oldRow -> Array.set x cell oldRow) |> Maybe.map (\newRow -> Array.set y newRow board) |> Maybe.withDefault board
最初のArray.get y
で縦軸のRowをgetし、Maybe.mapでMaybe Row
を受け取る。
(\oldRow -> Array.set x cell oldRow)
はコールバック関数で、バックスラッシュは無名関数の意味を持つ。ということでここではcellを新しい値に更新し、新しいRowを作成。
2こ目のMaybe.map
で更新されたRowをBoardにセットし、更新されたBoardが完成。
Maybe.withDefault
ではMaybeの値がNothingになった場合の値をセットしている。ここではNotingになったら元のboardを返すという処理になる。
Maybeについては
がわかりやすかった。
setBoard
関数をinitで使うと
board = setBoard (0,0) (Tile 1) <| Array.repeat 10 <| Array.repeat 8<| Empty
こんな感じで盤面の特定のCellを更新できる。
次はいよいよぷよぷよ的な盤面の動きを実装していく。
kubernetesを使ってみる。
mysql clusterをdockerで構築できるvitessというプロダクトがあるらしい。
youtubeがオープンソースで公開している、mysqlをスケールアウトさせるためのデータベースツール。 kubernetesで簡単にスケールアウトができるらしい。
vitessはdockerを使って簡単に試すこともできるが、クラスターはkubernetesを利用して作成する。
ということで、ローカルな環境にkubernetesをデプロイするところから始める。
kubernetesはkubeadmを使用すればかなり簡単にデプロイすることができる。
とりあえず今回はCentOSでkubernetesクラスターを作成する。
準備
今回の構成はmaster1台とslave node2台で構築する。
すべてのノードに対して以下の操作を行う。
必要な資材はyumでインストールできるのだが、新し目のkubernetesをインストールするためにリポジトリを更新しておく。
/etc/yum.repos.d/kubernetes.repo
を以下のようにして作成。
[kubernetes] name=Kubernetes baseurl=http://yum.kubernetes.io/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
したらkubeadmをインストール
$ yum -y update $ yum install -y docker-registry docker kubeadm
あとからいろいろと怒られないようにネットワークのフォアディングとブリッチを許可しておく。
$ sysctl -w net.ipv4.ip_forward=1 $ sysctl -w net.bridge.bridge-nf-call-iptables=1
ひとまず準備はこれで終了
kubeadmの実行
masterノード
masterノードで以下のコマンドを実行
[root@master ~]# kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.56.201 [kubeadm] WARNING: kubeadm is in beta, please do not use it for production clusters. [init] Using Kubernetes version: v1.7.7 [init] Using Authorization modes: [Node RBAC] ...(中略)... mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: http://kubernetes.io/docs/admin/addons/ You can now join any number of machines by running the following on each node as root: kubeadm join --token 8a2d44.2cc20d08cf7967f3 192.168.56.201:6443
これでマスターノードが起動し、コマンド実行結果の最後に表示されているkubeadm join --token ....
を外のノードで実行すれば完了
slaveノード
[root@node01 ~]# kubeadm join --token 8a2d44.2cc20d08cf7967f3 192.168.56.201:6443
これを全ノードで実行
masterノードで確認
[root@master ~]# kubectl get node NAME STATUS AGE VERSION master.atomichost NotReady 8m v1.7.5 node01.atomichost NotReady 3m v1.7.5 node02.atomichost NotReady 1m v1.7.5
kubeadmにより立ち上がっているPodを確認
[root@master ~]# kubectl get pods --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system etcd-master.atomichost 1/1 Running 0 8m kube-system kube-apiserver-master.atomichost 1/1 Running 0 8m kube-system kube-controller-manager-master.atomichost 1/1 Running 0 8m kube-system kube-dns-2425271678-ch19z 0/3 Pending 0 9m kube-system kube-proxy-0msfg 1/1 Running 0 1m kube-system kube-proxy-b8pkz 1/1 Running 0 3m kube-system kube-proxy-c9rrc 1/1 Running 0 9m kube-system kube-scheduler-master.atomichost 1/1 Running 0 8m
WARNING: Running with swap on is not supported. Please disable swap or set kubelet's --fail-swap-on flag to false.
こんなエラーが出てきたら/etc/systemd/system/kubelet.service.d/10-kubeadm.conf
の最後の起動コマンドに--fail-swap-on=false
と付け加えて再実行する。 手順はこんな感じ。
[root@master ~]# kubeadm reset [root@master ~]# cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf [Service] ...(中略)... Environment="KUBELET_CERTIFICATE_ARGS=--rotate-certificates=true" ExecStart= ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_NETWORK_ARGS $KUBELET_DNS_ARGS $KUBELET_AUTHZ_ARGS $KUBELET_CADVISOR_ARGS $KUBELET_CGROUP_ARGS $KUBELET_CERTIFICATE_ARGS $KUBELET_EXTRA_ARGS --fail-swap-on=false [root@master ~]# systemctl daemon-reload [root@master ~]# kubeadm join --token 8a2d44.2cc20d08cf7967f3 192.168.56.201:6443
ネットワーク設定
今回はflannelでkubernetesクラスター間のネットワークを構築する。
公式サイトにはcoreosが提供しているkube-flannel.ymlでサービスを起動すればOKって書いてある。今回は適当にgithubにあがってたflannelのymlファイルを持ってきた。
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml wget https://raw.githubusercontent.com/coreos/flannel/317b7d199e3fe937f04ecb39beed025e47316430/Documentation/k8s-manifests/kube-flannel-rbac.yml
たしかにこれでOKなのだが、各ノードの接続に使用しているネットワーク・インターフェース(kubeadmでadvertise設定したip)がeth0とかデバイスの先頭のものでないときはymlファイルを弄る必要がある。
このため、kube-flannel.ymlを以下のように変更した。
--- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: ...(中略)... containers: - name: kube-flannel image: quay.io/coreos/flannel:v0.9.0-amd64 command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr", "--iface=eth1" ] ...(後略)...
あとは立ち上げるだけ。
[root@master ~]# kubectl apply -f kube-flannel-rbac.yml [root@master ~]# kubectl apply -f kube-flannel.yml
これでネットワークが設定されると、無事にkune-dnsも立ち上がる。
[root@master ~]# kubectl get pod --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system etcd-master.atomichost 1/1 Running 0 4h kube-system kube-apiserver-master.atomichost 1/1 Running 0 4h kube-system kube-controller-manager-master.atomichost 1/1 Running 0 4h kube-system kube-dns-545bc4bfd4-zmgg8 3/3 Running 0 4h kube-system kube-flannel-ds-4tshg 1/1 Running 0 1h kube-system kube-flannel-ds-crpt7 1/1 Running 0 1h kube-system kube-flannel-ds-fkd6b 1/1 Running 0 1h kube-system kube-flannel-ds-n6jhv 1/1 Running 0 1h kube-system kube-proxy-c6px7 1/1 Running 0 4h kube-system kube-proxy-r7ppv 1/1 Running 0 1h kube-system kube-proxy-w6m84 1/1 Running 0 1h kube-system kube-proxy-zv42h 1/1 Running 0 1h kube-system kube-scheduler-master.atomichost 1/1 Running 0 4h
Lua Copasを使ってみる
Copasとは、lua-socketを使う際に、非同期通信を行うためのライブラリです。
すごく便利な割に、使い方があまり出てこなかったので、ここで書こうかと思います。
http://keplerproject.github.io/copas/index.html
lua-socketを使用して、単純にTCPリクエストを受け付けるサーバをluaで書いてみると、以下のような感じになります。
local socket = require("socket") local server = assert(socket.bind("*",80)) local ip, port = server:getsockname() while true do local client = server:accept() local line, err = client:receive() print(line) if not err then client:send("OK\n") end client:close() end
しかし、これだと一つのTCPのセッションが完了するまで、プログラムが待ち状態になってしまい、他のユーザからの通信を受け付けません。
この状況、luaのコルーチンとtimeout機能を使えば回避できるのですが、これをいちいち書いているのはめんど臭いです。
そこで登場するのがCopas
まずは使い方を見てみます。
luaはマルチスレッド環境に対応していませんので、コルーチンを作っても擬似的な並列処理になります。その点、Golangのゴルーチンは勝手にOSのスレッドに多重化してくれるので優秀ですね。
Copasのインストール
luarocks(luaのライブラリマネージャ)でcopasをインストール出来ます。
luarocks install copas
ubuntuだったらapt-getでも入ります。
apt-get install lua-copas
※lua-socketも同じ方法で導入することが出来ます。
Copasの使い方
Copasでは通信を処理するHandler関数を定義します。
内部的には、このHandlerをコルーチンで呼び出し、Handlerの処理がビジーになったら別のHandlerに切り替える、といったことを行なっているようです。
以下プログラムのように適当にHandlerを定義します。
中身はなんでも良いです。
ちなみに引数のc
の部分はどこかにバインドされたソケットが入ります。
次にcopas.addserver()
で上記のHandlerををセットします。
これはなんらかの通信に対するディスパッチャの役割を担ってくれる関数になります。
copas.addserver(server, handler[, timeout])
の引数は3つあり、1つ目がソケット、2つ目がHandler(またはHandlerを呼び出す関数)、3つ目がtimeoutの時間です。
copas.wrap(socket)
はCopasでsocketを扱う際に便利な関数です。通常、copasで呼び出したhandler内でsend、receiveを行う際には、copas.send(socket)
copas.receive(socket)
みたいな感じで、いちいちcopasを呼び出さないといけませんが、copas.wrap(socket)
としてしまえば、lua-socketと同じくsocket:send()
という感じでデータの送受信が可能になります。
最後にcopas.loop()
で全てのserverが呼び出されます。
local socket = require("socket") local copas = require("copas") function Handler(c,host,port) local peer = host .. ":" .. port print("connection form",peer) c:send("Hello\r\n") print("data from",peer,(c:receive"*l")) end local server = assert(socket.bind("*",80)) copas.addserver(server ,function(c) return Handler(copas.wrap(c),c:getpeername()) end) copas.loop()
これで、一度に複数のリクエストが来ても対応できるようになりました。
Copasを使うことで、非同期通信のプログラムがものの数行で書けます。
すごく便利です。
少し応用
今度は、先ほどのプログラムの応用編ということで、通信するポートによって呼び出すハンドラを変えてみます。
以下のプログラムでは、80番ポートへの通信には英語で、90番ポートへの通信にはスペイン語で挨拶を返すプログラムです。
例では、ポート毎に応答を変えていますが、同じ書き方でsrc-ip毎に返答を変えることもできます。
このように、複数の条件でcopas.addserver
を呼び出すことでポート番号や通信元アドレスに応じたHandler呼び出しができるようになります。
local socket = require("socket") local copas = require("copas") function Router(address, port, handler) local server = assert(socket.bind(address,port)) return copas.addserver(server , function(c) return handler(copas.wrap(c),c:getpeername()) end ) end function HandlerEnglish(c,host,port) local peer = host .. ":" .. port print("connection form",peer) c:send("hello!\r\n") print("data from",peer,(c:receive"*l")) end function HandlerSpanish(c,host,port) local peer = host .. ":" .. port print("connection form",peer) c:send("hola!\r\n") print("data from",peer,(c:receive"*l")) end Router("*",80,HandlerEnglish) Router("*",90,HandlerSpanish) copas.loop()
LuaでTOMLのパースプログラムを書く
今日はlua言語でTOMLフォーマットを読み込んで見ます。
luaってあまり聞かない言語ですが、使ってみるとすごい便利だったりします。
なにが良いかって、文字列が非常に扱いやすいこと、そして軽量。
C言語ライクにプログラムが書ける謎のスクリプト言語って感じです。
基本的なところは以下のサイトで勉強しました。
http://starcode.web.fc2.com
TOMLとは?
JSONのように設定ファイルを記述するフォーマットです。 まだ仕様が固まりきっていないみたいなのですが、人間が読みやすく、かつ、記述しやすいように作られています。 最近はやりのGolangでもよく使われていて、GoBGPなどで設定ファイルとして採用しています。
例えば、以下のように記述すると、
[hoge] a = "HOGE" [hoge.foo] b = 1 [bar] [[bar.barbar]] c = 2 [[bar.barbar]] d = 3
JSONてきにはこんな感じになります。
{"hoge":{"a" : "HOGE"}, {"foo" : {"b": 1 } } } {"bar" : { "barbar" : [ { "c" = 2 }, { "d" = 3 } ] } }
二重括弧でくくるだけで配列のような構造ができるので、とても記述しやすいし、いちいち括弧の数を数えなくても良いので、とても読みやすく書きやすいです。
Luaで文字列操作
Luaでの文字列操作は、正規表現を使うことが多くなります。
逆に言えば、正規表現で指定した文字列を簡単に取り出したり、配列に入れたりができるので、使いやすいものです。
よく使う、3つくらいを紹介します。
line = string.gmatch(str,"[^\n]+")
これは、文字列中に含まれる改行コードとその前の文字列をを全て抜き取ってline配列に入れています。
(一行ずづ抜き出しています。)
x = string.split("hello world", /\s+/)
これは、文字列をスペースに区切って配列に入れます。
x = string.sub("ABCDE", 2, 4)
これはABCDE→ BCD に削ります。
Luaの配列みたいなの
LuaではPythonでいうリストのような形で、Tableという型が使われる。 これが面白く、
hoge = {} hoge["foo"] = 2 print(hoge.foo) --[[ 結果 2 ]]--
こんな感じで、入れ子のTableにした場合、要素をドットでつなげることで参照できる。上記プログラム中の"foo"の部分を何も指定しなかった場合、1からインクリメントされる数字が勝手に入ってくるのでご注意。
この辺0からじゃなくて1からってのが、Luaが気持ち悪いって言われる所以なのかもしれません。
確かに気持ち悪いです。
LuaでTOMLを読み込み
ここまでくるとなんとなく、TOMLの読み込み方がわかってきました。
TOMLは行毎に意味を持つので、行毎に読み込んで、構造化し、LuaのTableに入れてるプログラムを書きます。
[Global] [Global.GlobalConfig] As = 1 HoldTime = 180 RouterId = "172.17.0.1" [Neighbors] [[Neighbors.NeighborList]] NeighborAddress = "10.0.1.2" [[Neighbors.NeighborList]] NeighborAddress = "10.0.1.3"
上記のようなTOMLファイル(BGPの設定ファイルっぽいもの)を読み込むようなプログラムを作って見ます。
function split(line,delim) local ret = {} local pat = "[^" .. delim .. "]+" local i = 1 for group in string.gmatch(line, pat) do ret[i] = group i = i + 1 end return ret end function loadstr(str) InLabel = {} InLabelArray = {} ret = {} Label = ret ArrayTable = 0 count = 0 for line in string.gmatch(str,"[^\n]+") do line = string.format( "%s", line:match( "^%s*(.-)%s*$" )) if string.sub(line,1,1) == "[" then if string.sub(line,1,2) == "[[" then ArrayTable = 1 line = string.sub(line,3,#line -2) if InLabelArray[line] == nil then InLabelArray[line] = true count = 0 end else line = string.sub(line,2,#line -1) ArrayTable = 0 end Label = ret arrays = split(line,".") for i, value in pairs(arrays) do if Label[value] ~= nil then --if in the value if i == table.maxn(arrays) then if InLabel[value] ~= nil then InLabel[value] = nil else print("double") end end else if i ~= table.maxn(arrays) then InLabel[value] = true end Label[value] = {} end Label = Label[value] end if ArrayTable == 1 then count = count + 1 Label[count] = {} end elseif line:match("=") ~= nil then formula = split(line,"=") formula[1] = string.format( "%s", formula[1]:match( "^%s*(.-)%s*$" )) formula[2] = string.format( "%s", formula[2]:match( "^%s*(.-)%s*$" )) if ArrayTable == 1 then Label[count][formula[1]]=formula[2] else Label[formula[1]]=formula[2] end end end return ret end
以下のように呼び出すことができます。
このプログラムではパラメータを全て文字列で呼び出していますが、このあたりはまだ改変する必要があります。
file = io.open("bgp.conf", "r") string = file:read("*all") file:close() test = loadstr(string) print(test.Global.GlobalConfig.As) print(test.Neighbors.NeighborList[1].NeighborAddress) print(test.Neighbors.NeighborList[2].NeighborAddress) --[[ 実行結果 1 "10.0.1.2" "10.0.1.3" ]]--
dhcpサーバを作る(C言語) その3
前回の続き
前回はDHCPリクエストのパケットをキャプチャするところまで成功しました。
そして今回はいよいよ、DHCPリクエストに返信をしてみます。
DHCPプロトコルを勉強するにあたって以下のサイトを参考にしました。
http://www.picfun.com/lan09a.html
DHCPプロトコルについて簡単に説明すると、 以下の図のような手順でアドレスをゲットしてきます。
client server discover ------------> offer <------------ request ------------> PACK <------------ IP アドレスGET!!
ブロードキャストされたdiscoverメッセージに対し、DHCPサーバがofferメッセージで応答。
Clientは気に入ったDHCPサーバを選択し、Requestメッセージをブロードキャストすることで、自分がどのDHCPサーバを選択したかを全サーバに知らせる。
選択されたDHCPサーバは、IP払い出し完了の応答(PACK)を返して、完了。
という流れになっています。
一気に全部作成するのは大変なので、まずはOFFERメッセージからまともに返せるようになろうぜ!
ということで、今日はOFFERパケットを作成します。
が、基本的にパラメータはハードコードで書いてます。 しょぼい
(ある意味わかりやすい!)
パケット作成
パケットを作成する前に、まずはメモリの確保を行います。 ここでも受信時同様、L2パケットを作成する必要があるので、ether_header+iphdr+udphdr+dhcp_packetの合計サイズのメモリを確保して行きます。 メモリは一度ゼロクリアしないと、何かをミスった時に訳の分からない値が出てきてしまうので、ゼロクリアも忘れずに。
int packetsiz; packetsiz = sizeof(struct ether_header)+sizeof(struct ip) + sizeof(struct udphdr)+sizeof(struct dhcp_packet); char *rep_buf; if ((rep_buf = (char *)malloc(packetsiz)) == NULL){ perror("malloc"); } memset(rep_buf, 0, packetsiz);
Ether Headerの作成
はじめに、Ether Headerの作成をします。パケットのどこから埋めていくかはどうでも良いのですが、
TCP/UDPなどのチェックサムが関わるところでは、順番に気をつけなければなりません。
ざっくり説明すると、先ほど確保した領域(rep_buf)をパケットキャプチャの時と同じ要領でether_header構造体にはめ、中の値を埋めてきます。
下のプログラムでは、DHCPクライアントから受け取ったパケット(buf)からmacアドレスやether_typeを読み取って、rep_bufに当てはめています。
int set_ethernet_header(void *rep_buf, int rep_size, void *buf, int size, unsigned char *smac){ struct ether_header *rep_eth; struct ether_header *eth; rep_eth = (struct ether_header *)rep_buf; eth = (struct ether_header *)buf; memcpy(&rep_eth->ether_dhost,ð->ether_shost,ETH_ALEN); memcpy(&rep_eth->ether_shost,smac,ETH_ALEN); rep_eth->ether_type = eth->ether_type; return 0; }
チェックサムの計算
IP Header、UDP Headerの作成をする前に、チェックサムの計算方法について説明しておきます。
調べて思ったのですが、チェックサムって概要はたくさん説明があるけど、なにすれば良いのかなかなか掴みにくい。
下記のサイトを見ると、要はチェックサム対象フィールドを固定長に区切って足すだけ。
http://www.fenix.ne.jp/~thomas/memo/ip/checksum.html
http://www.fenix.ne.jp/~thomas/memo/ip/checksum.html
やっていること。 例:IP Header
- IP Headerを16bit毎に区切る
- 全部を足して
- 最後にbit反転する。
足す時に桁が上がったら、くり上がった値を再び足します。
unsigned short csum(unsigned short *ptr,int nbytes) { register long sum; sum=0; while(nbytes>1) { sum+=*ptr++; nbytes-=2; } if(nbytes==1) { sum += *(u_int8_t *)ptr; } sum = (sum>>16)+(sum & 0xffff); sum = (sum>>16)+(sum & 0xffff); return ~sum; }
IP Headerの作成
ここもEtherHeaderと同じです。適当にパラメータを設定します。
(ttlとか意図的に変更してるしw)
注意することは、以下の点
csum()の第二引数にはIP Headerの長さであるip->ihlを入れるのですが、そのまま入れるとint で5が入ってしまいます。
これは、2バイトのフィールドを無理やりunsigned intに当てはめているからで、実際のIP Header長は20のため、今回は愚直に4を掛けています。
(こんなんでいいのか?なんだか、普通に全部をビット列で扱った方が早い気がしてきた)
int set_ip_header(void *rep_buf, struct in_addr *src, struct in_addr *dst){ struct iphdr *ip; ip = (struct iphdr *)(rep_buf + sizeof(struct ether_header)); ip->version = 4; ip->ihl = 5; ip->tos = 16; ip->tot_len = htons(sizeof(struct iphdr)+sizeof(struct udphdr)+sizeof(struct dhcp_packet)); ip->id = htons(0); ip->frag_off = htons(0); ip->ttl = 0x80; ip->protocol = IPPROTO_UDP; ip->saddr = src->s_addr; ip->daddr = dst->s_addr; ip->check = 0; ip->check = csum ((unsigned short *) (rep_buf+sizeof(struct ether_header)), ip->ihl*4); return 0; }
UDP Header作成
ここも基本的には今までと変わらないのですが、UDPのチェックサム計算は少し特殊です。
チェックサムの計算に、擬似UDPヘッダと呼ばれるものを付け足す必要があります。
csumで処理する値は、UDP擬似Header + UDP Header + データ(DHCPパケット)です。
int set_udp_header(void *rep_buf, int sport, int dport,struct in_addr *src, struct in_addr *dst){ struct udphdr *udp; struct iphdr *ip; struct pseudo_header pse; udp = (struct udphdr *)(rep_buf + sizeof(struct ether_header)+sizeof(struct iphdr)); udp->uh_sport = htons(sport); udp->uh_dport = htons(dport); udp->uh_ulen = htons(sizeof(struct udphdr)+sizeof(struct dhcp_packet)); udp->uh_sum = 0; pse.source_address = src->s_addr; pse.dest_address = dst->s_addr; pse.placeholder = 0; pse.protocol = IPPROTO_UDP; pse.udp_length = htons(sizeof(struct udphdr) + sizeof(struct dhcp_packet)); char *pseudogram; int psize = sizeof(struct pseudo_header) + sizeof(struct udphdr) + sizeof(struct dhcp_packet); pseudogram = malloc(psize); memcpy(pseudogram , (char*) &pse , sizeof (struct pseudo_header)); memcpy(pseudogram + sizeof(struct pseudo_header) , udp , sizeof(struct udphdr) + sizeof(struct dhcp_packet)); udp->uh_sum = csum( (unsigned short*) pseudogram , psize); free(pseudogram); return 0; }
DHCP Packet
いよいよ本題のDHCPパケットを作成。
まず、決まっていることとして、dhcp->xidとcookieはDHCP Discoverパケットと同じ値にする必要があります。
また、OFFERパケットとACKパケットのオプションコード(dhcp->op)は2です。
OFFERパケットでは、払い出す予定のアドレスをdhcp->yiaddr(Your IP address)に、サーバのIPをdhcp->siaddr (Server IP address)に、さらに、dhcp->chaddrにはクライアントのmacアドレスを入れます。
snameやfileは全てゼロで問題ありません。
そして重要なのが、最後のオプションコード。
全体としては以下の表のようになっている。これをOptionに埋め込んでいく必要があります。
Option | Option code | length(byte) | data(example) |
---|---|---|---|
DHCP Message Type | 53 | 1 | OFFER=2, ACK=5 |
DHCP Server Identifier | 54 | 4 | 192.168.10.1 |
IP Address Lease Time | 51 | 4 | 600 |
Subnet Mask | 1 | 4 | 255.255.255.0 |
Router | 3 | 4 | 192.168.10.1 |
Domain Name | 15 | any | example.org |
End Flag | 255 |
Linuxでdhcpサーバを立てたことがある人ならば、なんとなく設定したことある値ばかりになっています。
少し注意が必要なのが、lease_timeの部分。int型をそのままメモリにコピーしようとすると上位バイトから値が入ってしまうため、下位バイトから埋めていくようにしました。
int set_dhcp(void *rep_buf, void *buf,int dhcp_type,struct in_addr *dst, struct in_addr *siaddr, struct in_addr *subnetmask){ struct dhcp_packet *dhcp, *org_dhcp; dhcp = (struct dhcp_packet *)(rep_buf + sizeof(struct ether_header)+sizeof(struct iphdr)+sizeof(struct udphdr)); org_dhcp = (struct dhcp_packet *)(buf + sizeof(struct ether_header)+sizeof(struct iphdr)+sizeof(struct udphdr)); int i; memcpy(dhcp,org_dhcp,240); dhcp->op = 0x02; dhcp->ciaddr = (uint32_t)0; dhcp->yiaddr = (uint32_t)(dst->s_addr); dhcp->siaddr_nip = (uint32_t)(siaddr->s_addr);; dhcp->gateway_nip = (uint32_t)0; uint8_t option_code[60]; memset(option_code, 0, sizeof(option_code)); /***set DHCP Message Type****/ option_code[0] = 53; option_code[1] = 1; option_code[2] = (uint8_t)dhcp_type; /***DHCP Server identifier***/ option_code[3] = 54; option_code[4] = 4; memcpy(&option_code[5],&siaddr->s_addr,option_code[4]); /*** IP address lease time ***/ int lease_time = 600; unsigned char* cp; option_code[9] = 51; option_code[10] = 4; cp = (unsigned char *)&lease_time; for(i=0;i<4;i++){ option_code[14-i]=(uint8_t)(*cp++); } /*** Subnet Mask ***/ option_code[15] = 1; option_code[16] = 4; memcpy(&option_code[17],&subnetmask->s_addr,option_code[16]); /*** Router ***/ option_code[21] = 3; option_code[22] = 4; memcpy(&option_code[23],&siaddr->s_addr,option_code[22]); /*** domain name ***/ char *domain="example.org"; option_code[27] = 15; option_code[28] = strlen(domain); memcpy(&option_code[29],domain,option_code[28]); /**LAST**/ option_code[29+strlen(domain)]=255; memcpy(&dhcp->options,&option_code,60); return 0; }
さあ、これでDHCPメッセージの完成!
パケットの送信
パケットの送信にはsendto()を使います。送信に使うソケットは受信の際に使ったソケットディスクリプタをそのまま使ってあげれば良いです。
また、送信のプロトコルも受信時のものをそのまま受け継ぎます。
今回はDiscoveryメッセージに対してはOfferを返し、Requestメッセージに対してはACKを返す、簡単なプログラムを作っています。
でもこれで、指定したIPを振ることができるはず。
int dhcp_mt = check_dhcp_message_type(buf,sizeof(buf)); printf("dhcp_option %d\n",dhcp_mt); struct in_addr src ,dst,subnetmask; inet_aton("192.168.10.1",&src); inet_aton("192.168.10.65",&dst); inet_aton("255.255.255.0",&subnetmask); switch(dhcp_mt){ case 1: printf("OFFER\n"); set_dhcp(rep_buf,buf,2,&dst,&src,&subnetmask); break; case 3: printf("ACK\n"); set_dhcp(rep_buf,buf,5,&dst,&src,&subnetmask); break; default: printf("This message is not relevance to dhcp protocol"); break; } set_ethernet_header(rep_buf, sizeof(rep_buf), buf, sizeof(buf),ifr.ifr_hwaddr.sa_data); int dport=68, sport=67; set_ip_header(rep_buf, &src,&dst); set_udp_header(rep_buf,sport,dport,&src,&dst); messagedump(buf, sizeof(buf)); messagedump(rep_buf,sizeof(rep_buf)); int send_len; if(sendto(sockfd, rep_buf, packetsiz, 0, (struct sockaddr*)&senderinfo, sizeof(senderinfo))< 0){ perror("send sockfd"); break; }
結果確認
最後に、このDHCPサーバ(っぽい何か)に向かってDHCP ClientよりDiscoverメッセージを投げてみて、ちゃんと帰ってきてるか確かめます。
結果
# ip netns exec dhcp-client dhclient -v veth_in Internet Systems Consortium DHCP Client 4.3.3 Copyright 2004-2015 Internet Systems Consortium. All rights reserved. For info, please visit https://www.isc.org/software/dhcp/ Listening on LPF/veth_in/b6:9d:80:e6:ae:be Sending on LPF/veth_in/b6:9d:80:e6:ae:be Sending on Socket/fallback DHCPDISCOVER on veth_in to 255.255.255.255 port 67 interval 3 (xid=0x30c0f26c) DHCPREQUEST of 192.168.10.65 on veth_in to 255.255.255.255 port 67 (xid=0x6cf2c030) DHCPOFFER of 192.168.10.65 from 192.168.10.1 DHCPACK of 192.168.10.65 from 192.168.10.1 bound to 192.168.10.65 -- renewal in 232 seconds. # ip netns exec dhcp-client ip addr 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 19: veth_in@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether b6:9d:80:e6:ae:be brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.10.65/24 brd 192.168.10.255 scope global veth_in valid_lft forever preferred_lft forever
ちゃんとIPを取ることができました!!
あとは、アドレスの管理とか、色々やらないといけないけど、とりあえずここまでは完成!
今回までのコードは大きくなってきたのでGithubに載せます。
MacBook+Wiresharkで無線のパケットをキャプチャ
小ネタです。
無線パケットをキャプチャするにはMacBookProが優秀と言う話を聞いたので、やってみました。
キャプチャしたいのはビーコン信号などの無線のL2パケットです。
まずはMacbookでWiresharkを開き、上のメニューから
「キャプチャ」→「オプション」
の順に開きます。
「Wireshark・キャプチャインターフェース」
の画面が出てくるので、インターフェースを指定し、右側の方にある「モニターモード」の欄にチェックをつけます。
後は右下の「開始」ボタンを押せば無線のL2パケットのキャプチャが始まります。
このモードだと自分以外の無線のパケットをキャプチャできます。ただ、周りに大量の電波が飛んでいると、全て受け切れず(または電波干渉を起こし)パケットが壊れていることがよくあります。
こういったパケットをキャプチャするためには電波暗室とかあった方が良いんですね…
dhcpサーバを作る(C言語) その2
前回の続きで、今回はRAW_SOCKETから受け取ったパケットを表示する方法について書きます。
目標のDHCPサーバの作成まではまだまだ程遠いですが、あしからず…
まず、生のパケットの処理から。
パケットは例えばethernet→IP→TCP→…のように順々にデータが格納されています。
ですので、パケットの皮を剥がしていくイメージで、ヘッダーを読み込んでいく必要があります。
ひとまず今回はdhcpの内容が見れる部分にたどり着くまでやります。
ethernet header
まず、一番外側にあるethernet headerを見ていきます。
bufferをバイナリのまま見ても良いのですが、linuxで定義されている構造体に当てはめていくとWiresharkで見るような値の表示をすることができます。
ethernet headerは以下のような構造体で定義されています。
linux/if_ehter.h
struct ethhdr { unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ unsigned char h_source[ETH_ALEN]; /* source ether addr */ unsigned short h_proto; /* packet type ID field */ };
下記のプログラムのように
eth = (struct eher_header *) buf;
としてあげれば、構造体にパケットがハマりますので、後は表示をしてあげるだけです。
ただ、パケットの内容はintとかstringとかいつも使ってるやつとは型が違うので、ntohs()やinet_ntoa()を使用して見やすい文字列に変換してあげる必要があります。
/*view ether_header */ struct ether_header *eth; char smac[20], dmac[20]; char sip[16], dip[20]; int protocol; eth = (struct ether_header *)buf; sprintf(dmac, "%02x:%02x:%02x:%02x:%02x:%02x", eth->ether_dhost[0], eth->ether_dhost[1], eth->ether_dhost[2], eth->ether_dhost[3], eth->ether_dhost[4], eth->ether_dhost[5]); sprintf(smac, "%02x:%02x:%02x:%02x:%02x:%02x", eth->ether_shost[0], eth->ether_shost[1], eth->ether_shost[2], eth->ether_shost[3], eth->ether_shost[4], eth->ether_shost[5]); protocol = ntohs(eth->ether_type); printf("dmac:%s\n",dmac); printf("smac:%s\n",smac); printf("protocol:%d\n",protocol);
IP header
IPヘッダーも要領は同じです。
注意したいのは、(struct iphdr )bufとしてしまうと、先頭にあるether headerを読み込んでしまうので、
ip =(struct iphdr )(buf + sizeof(struct ether_header));
としてあげる必要があります。
IP headerの構造体は以下のように定義されています。
netinet/ip.h
struct iphdr { #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ihl:4; unsigned int version:4; #elif __BYTE_ORDER == __BIG_ENDIAN unsigned int version:4; unsigned int ihl:4; #else # error "Please fix <bits/endian.h>" #endif u_int8_t tos; u_int16_t tot_len; u_int16_t id; u_int16_t frag_off; u_int8_t ttl; u_int8_t protocol; u_int16_t check; u_int32_t saddr; u_int32_t daddr; /*The options start here. */ };
後はethernet headerの時と同じなんですが、IPアドレスに関してはu_int32_t型で定義されているので、そのまま表示すると4バイトのIPアドレスが10進数に変換されて表示されます。 これでは正直わかりにくいので、これを普段見るような「192.168.10.11」とかに変換するため、一度in_addr構造体に変換して、さらにinet_ntoa()でstringに変換してもらいます。
/*view ip_header */ struct iphdr *ip; struct in_addr saddr, daddr; ip = (struct iphdr *)(buf+sizeof(struct ether_header)); printf("ihl: %u\n",ip->ihl); printf("version: %u\n",ip->version); printf("tos: %u\n",ip->tos); printf("tot_len: %u\n",ntohs(ip->tot_len)); printf("id: %u\n",ntohs(ip->id)); printf("frag_off: %u\n",ip->frag_off); printf("ttl: %u\n",ip->ttl); printf("ip_protocol: %u\n",ip->protocol); printf("check: 0x%x\n",ntohs(ip->check)); saddr.s_addr = ip->saddr; daddr.s_addr = ip->daddr; printf("src_ip %s\n",inet_ntoa(saddr)); printf("dest_ip %s\n",inet_ntoa(daddr));
ここのプロトコル(ip->protocol)を見ることで、IP headerの次にくるプロトコル(UDPとかIPとかICMPとか)が分かります。 今回はDHCPのパケットをキャプチャすることが目的なので、protocol 17番のUDPプロトコルの時だけパケットを表示するプログラムを作成します。
UDP header
ここはもうほとんど言うことはありませんが、以下のようにUDPヘッダーをキャプチャします。
if(ip->protocol == 17){ udp = (struct udphdr *)(buf + sizeof(struct ether_header)+sizeof(struct iphdr)); printf("src port: %u\n",ntohs(udp->uh_sport)); printf("dest port: %u\n",ntohs(udp->uh_dport)); printf("uh_len: %u\n",ntohs(udp->uh_ulen)); printf("uh_sum: %u\n",ntohs(udp->uh_sum)); if(ntohs(udp->uh_dport) == 68 || ntohs(udp->uh_dport) == 67){ dumpdhcp(buf, sizeof(buf)); } }
DHCPはポート68と67番を使うので、この二つのポート番号の場合は次のDHCPパケットのキャプチャに移ります。
DHCP Packet
やっとDHCPのパケットまでたどり着きました。
DHCPに関してはlinuxのどこに構造体が定義されているかわからなかったので、適当にググってそれらしい構造体をコピペしました。
プロトコルについては下記のサイトが分かりやすかったです。
http://www.picfun.com/lan09a.html
また、以下のようにDHCPの中を整理するための構造体です。
/* DHCP packet */ #define EXTEND_FOR_BUGGY_SERVERS 80 #define DHCP_OPTIONS_BUFSIZE 308 /* See RFC 2131 */ struct dhcp_packet { uint8_t op; uint8_t htype; uint8_t hlen; uint8_t hops; uint32_t xid; uint16_t secs; uint16_t flags; uint32_t ciaddr; uint32_t yiaddr; uint32_t siaddr_nip; uint32_t gateway_nip; uint8_t chaddr[16]; uint8_t sname[64]; uint8_t file[128]; uint32_t cookie; uint8_t options[DHCP_OPTIONS_BUFSIZE + EXTEND_FOR_BUGGY_SERVERS]; } __attribute__((packed));
この中で気になるのはattribute*1の部分。
使用するCPUによって何バイトづつ値を読み込むかが違いにより発生するエラーをなくすためのものらしい。
(従来構造体の間にある空白のバイトをなくして、1バイトづつ読み込ませることで、アライメントのエラーを防ぐ)
詳しくは下のサイトを参考にすると良いが、ハードウェアの事とか知らないからよろしくやってくれれば良いと言う方は、気にせずにつけてれば良さそうです。
http://www.kumikomi.net/archives/2008/05/08hard2.php?page=1
後は例のごとく上記の構造体にバッファを当てはめれば良いのですが、uint32_tとかどうやって出力すれば良いの?って言う疑問は残りますね。
ここでは#include <inttypes.h>と言う便利なものをインクルードします。これを使う事で、printfの際にPRlu8とか入れるだけでバイトコードを全部intで表示してくれます。
ただ、DHCPのパケットを理解しようと思ったらそこはバイナリに直さないといけないんですけどね…
とりあえずキャプチャの結果が正しそうかだけでも分かれば良いのでこれで良いでしょう。
static void dumpdhcp(void *buf, int size){ struct dhcp_packet *dhcp; dhcp = (struct dhcp_packet *)(buf + sizeof(struct ether_header)+sizeof(struct iphdr)+sizeof(struct udphdr)); char* chaddr; printf("--------------------DHCP--------------------\n"); printf("opcode: %" PRIu32 "\n",dhcp->op); printf("hw_type: %" PRIu8 "\n",dhcp->htype); printf("hw_len: %" PRIu8 "\n",dhcp->hlen); printf("gw_hops: %" PRIu8 "\n",dhcp->hops); printf("tx_id: %" PRIu32 "\n",dhcp->xid); printf("bp_secs; %" PRIu16 "\n",dhcp->secs); printf("bp_flags; %" PRIu16 "\n",dhcp->flags); printf("CIaddr; %" PRIu32 "\n",dhcp->ciaddr); printf("YIaddr; %" PRIu32 "\n",dhcp->yiaddr); printf("SIaddr; %" PRIu32 "\n",dhcp->siaddr_nip); printf("GIaddr; %" PRIu32 "\n",dhcp->gateway_nip); int i; printf("chaddr: "); for(i=0;i<16;i++){ printf("%" PRIu8 ",",dhcp->chaddr[i]); } printf("\n"); printf("sname: "); for(i=0;i<64;i++){ printf("%" PRIu8 ",",dhcp->sname[i]); } printf("\n"); printf("file: "); for(i=0;i<128;i++){ printf("%" PRIu8 ",",dhcp->file[i]); } printf("\n"); printf("cookie: %" PRIu32 "\n",dhcp->cookie); printf("options: "); for(i=0;i<sizeof(dhcp->options);i++){ printf("%" PRIu8 ",",dhcp->options[i]); } printf("\n"); printf("--------------------DHCP--END---------------\n"); }
ちなみにこんな感じで出力されます。
dmac:ff:ff:ff:ff:ff:ff smac:b6:9d:80:e6:ae:be protocol:2048 ihl: 5 version: 4 tos: 16 tot_len: 328 id: 0 frag_off: 0 ttl: 128 ip_protocol: 17 check: 0x3996 src_ip 0.0.0.0 dest_ip 255.255.255.255 src port: 68 dest port: 67 uh_len: 308 uh_sum: 47077 --------------------DHCP-------------------- opcode: 1 hw_type: 1 hw_len: 6 gw_hops: 0 tx_id: 2070773066 bp_secs; 0 bp_flags; 0 CIaddr; 0 YIaddr; 0 SIaddr; 0 GIaddr; 0 chaddr: 182,157,128,230,174,190,0,0,0,0,0,0,0,0,0,0, sname: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, file: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, cookie: 1666417251 options: 53,1,3,50,4,192,168,10,60,12,16,105,112,45,49,55,50,45,51,49,45,49,55,45,49,49,53,55,13,1,28,2,3,15,6,119,12,44,47,26,121,42,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, --------------------DHCP--END---------------
これでパケットキャプチャは終了です。 これでDHCPリクエストのパケットは解析できるようになったので、次はいよいよDHCPサーバとして機能するように、応答を返します。
ソースコード
ここまでのコードを載せます。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/udp.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <linux/if.h> #include <net/ethernet.h> #include <error.h> #include <sys/types.h> #include <arpa/inet.h> #include <resolv.h> #include <ifaddrs.h> #include <fcntl.h> #include <arpa/inet.h> #include <netpacket/packet.h> #include <netinet/tcp.h> #include <inttypes.h> /* DHCP packet */ #define EXTEND_FOR_BUGGY_SERVERS 80 #define DHCP_OPTIONS_BUFSIZE 308 /* See RFC 2131 */ struct dhcp_packet { uint8_t op; uint8_t htype; uint8_t hlen; uint8_t hops; uint32_t xid; uint16_t secs; uint16_t flags; uint32_t ciaddr; uint32_t yiaddr; uint32_t siaddr_nip; uint32_t gateway_nip; uint8_t chaddr[16]; uint8_t sname[64]; uint8_t file[128]; uint32_t cookie; uint8_t options[DHCP_OPTIONS_BUFSIZE + EXTEND_FOR_BUGGY_SERVERS]; } __attribute__((packed)); static void messagedump(void *, int); static void dumpdhcp(void *, int); int main(){ char buf[ETHER_MAX_LEN] = {0}; int sockfd = 0, len = 0; sockfd = socket(AF_PACKET,SOCK_RAW, htons(ETH_P_ALL)); if(sockfd < 0){ perror("socket error"); return -1; } struct ifreq ifr; memset(&ifr,0,sizeof(ifr)); strcpy(ifr.ifr_name,"br0"); if(ioctl(sockfd,SIOCGIFINDEX, &ifr) < 0){ perror("ioctl"); return -1; } /*struct packet_mreq mreq; mreq.mr_type = PACKET_MR_PROMISC; mreq.mr_ifindex = ifr.ifr_ifindex; mreq.mr_alen = 0; mreq.mr_address[0] = '\0'; if(setsockopt(sockfd,SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&mreq,sizeof(mreq))<0){ perror("setopt"); close(sockfd); return -1; }*/ struct sockaddr_ll sa; int sockfd_index; sockfd_index = ifr.ifr_ifindex; sa.sll_family = AF_PACKET; sa.sll_protocol = htons(ETH_P_ALL); sa.sll_ifindex = sockfd_index; if(bind(sockfd,(struct sockaddr *)&sa, sizeof(sa))<0){ perror("bind"); close(sockfd); return -1; } struct sockaddr_ll senderinfo; socklen_t addrlen; while(1){ addrlen=sizeof(senderinfo); len = recvfrom(sockfd, buf, sizeof(buf), 0,(struct sockaddr *)&senderinfo, &addrlen); if(len < 0 ){ perror("receive error\n"); break; } messagedump(buf, sizeof(buf)); } close(sockfd); return 0; } static void messagedump(void *buf, int size){ /*view ether_header */ struct ether_header *eth; char smac[20], dmac[20]; char sip[16], dip[20]; int protocol; eth = (struct ether_header *)buf; sprintf(dmac, "%02x:%02x:%02x:%02x:%02x:%02x", eth->ether_dhost[0], eth->ether_dhost[1], eth->ether_dhost[2], eth->ether_dhost[3], eth->ether_dhost[4], eth->ether_dhost[5]); sprintf(smac, "%02x:%02x:%02x:%02x:%02x:%02x", eth->ether_shost[0], eth->ether_shost[1], eth->ether_shost[2], eth->ether_shost[3], eth->ether_shost[4], eth->ether_shost[5]); protocol = ntohs(eth->ether_type); printf("dmac:%s\n",dmac); printf("smac:%s\n",smac); printf("protocol:%d\n",protocol); /*view ip_header */ struct iphdr *ip; struct in_addr saddr, daddr; ip = (struct iphdr *)(buf+sizeof(struct ether_header)); printf("ihl: %u\n",ip->ihl); printf("version: %u\n",ip->version); printf("tos: %u\n",ip->tos); printf("tot_len: %u\n",ntohs(ip->tot_len)); printf("id: %u\n",ntohs(ip->id)); printf("frag_off: %u\n",ip->frag_off); printf("ttl: %u\n",ip->ttl); printf("ip_protocol: %u\n",ip->protocol); printf("check: 0x%x\n",ntohs(ip->check)); saddr.s_addr = ip->saddr; daddr.s_addr = ip->daddr; printf("src_ip %s\n",inet_ntoa(saddr)); printf("dest_ip %s\n",inet_ntoa(daddr)); /*view udp_header */ struct udphdr *udp; if(ip->protocol == 17){ udp = (struct udphdr *)(buf + sizeof(struct ether_header)+sizeof(struct iphdr)); printf("src port: %u\n",ntohs(udp->uh_sport)); printf("dest port: %u\n",ntohs(udp->uh_dport)); printf("uh_len: %u\n",ntohs(udp->uh_ulen)); printf("uh_sum: %u\n",ntohs(udp->uh_sum)); if(ntohs(udp->uh_dport) == 67 || ntohs(udp->uh_dport) == 68){ dumpdhcp(buf, sizeof(buf)); } } } static void dumpdhcp(void *buf, int size){ struct dhcp_packet *dhcp; dhcp = (struct dhcp_packet *)(buf + sizeof(struct ether_header)+sizeof(struct iphdr)+sizeof(struct udphdr)); char* chaddr; printf("--------------------DHCP--------------------\n"); printf("opcode: %" PRIu32 "\n",dhcp->op); printf("hw_type: %" PRIu8 "\n",dhcp->htype); printf("hw_len: %" PRIu8 "\n",dhcp->hlen); printf("gw_hops: %" PRIu8 "\n",dhcp->hops); printf("tx_id: %" PRIu32 "\n",dhcp->xid); printf("bp_secs; %" PRIu16 "\n",dhcp->secs); printf("bp_flags; %" PRIu16 "\n",dhcp->flags); printf("CIaddr; %" PRIu32 "\n",dhcp->ciaddr); printf("YIaddr; %" PRIu32 "\n",dhcp->yiaddr); printf("SIaddr; %" PRIu32 "\n",dhcp->siaddr_nip); printf("GIaddr; %" PRIu32 "\n",dhcp->gateway_nip); int i; printf("chaddr: "); for(i=0;i<16;i++){ printf("%" PRIu8 ",",dhcp->chaddr[i]); } printf("\n"); printf("sname: "); for(i=0;i<64;i++){ printf("%" PRIu8 ",",dhcp->sname[i]); } printf("\n"); printf("file: "); for(i=0;i<128;i++){ printf("%" PRIu8 ",",dhcp->file[i]); } printf("\n"); printf("cookie: %" PRIu32 "\n",dhcp->cookie); printf("options: "); for(i=0;i<sizeof(dhcp->options);i++){ printf("%" PRIu8 ",",dhcp->options[i]); } printf("\n"); printf("--------------------DHCP--END---------------\n"); }
*1:packed