ecn kg 発表
DESCRIPTION
ECN KG 発表. カーネルをハックしてみよう. ネットワークスタックをいぢる. FreeBSD のカーネルコードに printf をいれて処理を追ってみる 今日のターゲットは ICMP です!. まずは QEMU を手に入れよう. 既に QEMU をダウンロードしてありますよね? http://www.ht.sfc.keio.ac.jp/~sada/etc/qemu-0.7.zip 下記媒体でも配布します。 CD-R SD カード コンパクトフラッシュ QEMU を手に入れたら、ローカルの好きな場所においてください. FreeBSD@QEMU の起動. - PowerPoint PPT PresentationTRANSCRIPT
ECN KG 発表
カーネルをハックしてみよう
ネットワークスタックをいぢる
FreeBSD のカーネルコードに printf をいれて処理を追ってみる今日のターゲットは ICMP です!
まずは QEMU を手に入れよう
既に QEMU をダウンロードしてありますよね? http://www.ht.sfc.keio.ac.jp/~sada/etc/qemu-0.7
.zip
下記媒体でも配布します。 CD-R SD カード コンパクトフラッシュ
QEMU を手に入れたら、ローカルの好きな場所においてください
FreeBSD@QEMU の起動
qemu-freebsd.bat をダブルクリック!ユーザは root 、パスワードは・・・下記のようにタイプすると IP アドレスがとれます dhclient ed0
外部とは port 22 しか繋がりません。 qemu-freebsd.bat を編集する
と、好きな port で通信できます
QEMU
仮想ルータ10.0.2.2
FreeBSD10.0.2.16
カーネルのソースコード構成
どんなファイルがあるか、ざっくりチェックしてみてください。
/ usr src sys
i386
kern
net
netinet
sys
ufs
vm
X86 特有
一般的なカーネル
一般的なネットワーク
TCP/IP
カーネルヘッダ
Unix ファイルシステム
カーネルヘッダ
ICMP Echo Request, Reply
ip_output()
application
ether_output()
arpresolve()
rip_input()
application
ip_input()
ether_input()
icmp_input()
データ送信の流れ
ICMP ECHO Request の送信
ICMP Echo Request を送信する ping プログラムで実際にパケットの流れをみてみ
る ip_output()
• IP ヘッダの初期化• 経路選択、アドレス選択• フラグメンテーション
ether_output()• フレーム構築• インタフェースへのキューイング
arpresolve()• arp 解決
実際にコードを見ながら printf を入れよう
printf の基本的な使い方は大丈夫ですよね??だめ?#define DBG(fmt, arg...) printf("%s: "fmt "\n", __FUNCTION__, ## arg)↑ みたく書くと、後で色々楽です。
と、いうわけで、取りあえず送信からいきましょip_output 関数は sys/netinet/ip_output.c にあります
ip_output() (IP ヘッダができるまで )
ip_output(){……
ip = mtod(m, struct ip *);
if ((flags & (IP_FORWARDING|IP_RAWOUTPUT)) == 0) { ip->ip_v = IPVERSION; ip->ip_hl = hlen >> 2; ip->ip_id = ip_newid(); ipstat.ips_localout++; } else { hlen = ip->ip_hl << 2; } (RAW output なので、 ここでは、初期化されない)
ICMP
ICMPIP
ip_output() ( 経路選択、アドレス選択 )
ip_output(){…… 経路キャッシュや経路テーブルをみて宛て先決定
dst = (struct sockaddr_in *)&ro->ro_dst;again:……送信元の決定 if (ip->ip_src.s_addr == INADDR_ANY) { /* Interface may have no addresses. */ if (ia != NULL) { ip->ip_src = IA_SIN(ia)->sin_addr; } }
ip_output() ( 経路選択、アドレス選択 )
ip_output(){…… フラグメントの必要がなければ送信
error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); goto done; }}
送信前に IP ヘッダのメンバを表示してみよう
IP header
バージョンip_v
ヘッダ長ip_hl
tosip_tos
全体の長さip_len
識別子ip_id
フラグ、フラグメントオフセットip_off
TTLip_ttl
プロトコルip_p
ヘッダチェックサムip_sum
発信元 IP アドレスip_src
データ
宛て先 IP アドレスip_dst
32bit アドレスの表示方法
#define IP_ADDR_FORMAT(addr) \ ((addr) & 0xff), \ (((addr) >> 8) & 0xff), \ (((addr) >> 16) & 0xff), \ (((addr) >> 24) & 0xff)
DBG(“ip_src %d.%d.%d.%d ip_dst %d.%d.%d.%d", IP_ADDR_FORMAT(ip->ip_src.s_addr),
IP_ADDR_FORMAT(ip->ip_dst.s_addr));
ここでカーネルをコンパイルしてみよう
コンパイル方法 cd /usr/src/sys/i386/compile/ECN make kernel make kernel-install
ぜーーったい、 make clean をしないでください! 一から make しようとすると、それだけでこの授業時間が終わります。。。
再起動 shutdown -r now
Ping してみよう
タイプしてみてください dhclient ed0 ping 10.0.2.2
dmesg すると、出力はどうなりましたか?
spl… 関数
ip_output で splnet(), splx() を見ませんでしたか?FreeBSD では、割り込み処理に優先度が割り当てていますネットワーク周りだとこの2つが使われます。 splnet(), splimp()splimp は splnet より優先度が高く、ネットワークデバイスからの割り込みを禁止する。インタフェースキューを操作する際は、 splimp が使用される
spl… 関数一覧
関数 説明spl0 通常の動作モードsplsoftclock 低優先度クロック処理splnet ネットワークプロトコル処
理spltty 端末 I/Osplbio ディスクとテープの I/Osplimp ネットワークデバイス I/Osplclock 高優先度クロック処理splhigh 全ての割り込みがブロックsplx(x) 前の優先度に戻す
高
低
別
ether_output()
sys/net/if_ethersubr.c内の関数
処理は フレーム構築 インタフェースキューイング
ether_output(ARP 解決 )
intether_output(){…… case AF_INET: error = arpresolve(ifp, rt0, m, dst, edst); if (error) return (error == EWOULDBLOCK ? 0 : error); type = htons(ETHERTYPE_IP); break;
もし、 ARP がまだ解決されてなければ、返る( arpresolv が ARP 解決後、再び ether_output を呼び出す)。解決されていれば、フレームの構築を行なう。
ether_output( フレーム構築 )
intether_output(){…… M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT); if (m == NULL) senderr(ENOBUFS); eh = mtod(m, struct ether_header *); (void)memcpy(&eh->ether_type, &type, sizeof(eh->ether_type)); (void)memcpy(eh->ether_dhost, edst, sizeof (edst));
M_PREPEND で IP ヘッダの前に領域を確保後、ETHER ヘッダの中身を埋めていく
ICMPIP
ICMPIPeth
ether_output_frame(出力へのキューイング )
intether_output_frame(){…… IFQ_HANDOFF(ifp, m, error); return (error);}送信キューにデータをキューイングする。
というわけで、イーサヘッダも printf しよう。
Ether Header
発信元アドレス 送信元アドレス タイプ
struct ether_header{ u_char ether_dhost[6]; u_char ether_shost[6]; u_short ether_type;}
IPv4の場合 type は 0x0800 となる
MAC アドレスの表示方法は?
以下のような感じで表示させてみようマクロにしたり、 define で書いてもいいかも。
voidmac_print(u_char *macaddr){ int n; printf("mac = ["); for (n=0; n<6; n++) printf("%02x ",(u_int)macaddr[n]); printf("]\n");}
arpresolve
ether_output() で ARP 解決がまだされていないときに、 ARP 解決がされますARP は大丈夫ですよね。。。。?主に 2つの関数が使われる arpresolve
• ARPエントリを検索• なければ、 arprequest を呼び出す
arprequest• ARP パケットを作成• インタフェースへ出力する
arpresolve(ARPエントリの検索 )
sys/netinet/if_ether.c にありますintarpresolve(){……
rt = arplookup(SIN(dst)->sin_addr.s_addr, 1, 0);……. if ((rt->rt_expire == 0 || rt->rt_expire > time_second) && sdl->sdl_family == AF_LINK && sdl->sdl_alen != 0) {
arplookup で ARPエントリを検索後、エントリが妥当かどうか調べるもし、妥当であればその ARP 解決がされている
arpresolve(ARP 要求 )intarpresolve(){…… if (rt->rt_expire) { rt->rt_flags &= ~RTF_REJECT; if (la->la_asked == 0 || rt->rt_expire != time_second) { rt->rt_expire = time_second; if (la->la_asked++ < arp_maxtries) { struct in_addr sin = SIN(rt->rt_ifa->ifa_addr)->sin_addr;
RT_UNLOCK(rt); arprequest(ifp, &sin, &SIN(dst)->sin_addr, IF_LLADDR(ifp));
ARP タイマ、 ARP回数のチェック設定後、 ARP 要求送信を行う arprequest()を呼び出す
arprequest(ARP 要求パケット構築、送信 )
static void arprequest(){……… if ((m = m_gethdr(M_DONTWAIT, MT_DATA)) == NULL) return;
ARP パケット用mbuf を確保……
ah->ar_pro = htons(ETHERTYPE_IP); ah->ar_hln = ifp->if_addrlen;
ah->ar_pln = sizeof(struct in_addr); ………
パケットの中身を埋める
(*ifp->if_output)(ifp, m, &sa, (struct rtentry *)0);
インタフェースへ出力
ARP ヘッダ ( メンバを色々出力させてみよう )■if_ether.h で定義されていますstruct arphdr { u_short ar_hrd; HW アドレスのフォーマット u_char ar_hln; ハードウェアアドレスの長さ u_char ar_pln; プロトコルアドレスの長さ u_short ar_op; ARP パケットの種類};
struct ether_arp { struct arphdr ea_hdr; ARP の固定サイズヘッダ u_char arp_sha[ETHER_ADDR_LEN]; 送信元 MAC アドレス u_char arp_spa[4]; 送信元のプロトコルアドレス u_char arp_tha[ETHER_ADDR_LEN]; ターゲットの MAC アドレス u_char arp_tpa[4]; ターゲットのプロトコルアドレス};
ether_arp
arphdr
ここまで分かると色々できそう?
IP アドレスを詐称MAC アドレスを詐称ARP フラッディング発生
みたいなカーネルが簡単にできそうだね!!でも、よいこのみんなはやっちゃだめだよ!
!
ここでまたカーネルをコンパイルしてみよう
コンパイル方法 cd /usr/src/sys/i386/compile/ECN make kernel make kernel-install
ぜーーったい、 make clean をしないでください! 一から make しようとすると、それだけでこの授業時間が終わります。。。
再起動 shutdown -r now
カーネルの出力は?
dmesg で確認してみよう
データ受信の流れ
ICMP ECHO Reply の受信
ICMP Echo Reply を受信する ping プログラムで実際にパケットの流れをみて
みる ether_input()
• ether ヘッダの妥当性チェック• 受信キューに入れる
ip_input()• IP パケットの妥当性チェック• 再組み立てやオプション、転送処理• 振り分け
icmp_input()• メッセージの妥当性チェック• ICMP メッセージに応じた処理
ether_input( フレームのチェック)
sys/net/if_ethersubr.c 内にありますstatic voidether_input(){……… eh = mtod(m, struct ether_header *); etype = ntohs(eh->ether_type);……… ether_demux(ifp, m);}
フレームの妥当性をチェックする。ether_demux を呼び出しデマルチプレクス。ここでも、 ether ヘッダのメンバを色々表示してみよう
ether_demux( デマルチプレクス )void ether_demux(){…… m_adj(m, ETHER_HDR_LEN); ether ヘッダ削除 switch (ether_type) { case ETHERTYPE_IP: if (ip_fastforward(m)) return; isr = NETISR_IP; break;……… netisr_dispatch(isr, m); return;
ether_type により上位層への割り込みのスケジューリングがされ、適切なキューへキューイング
ip_input(IP パケットの妥当性チェック )
schednetisr により呼び出されるsys/netinet/ip_input.c にあります。
void ip_input(){………… ip = mtod(m, struct ip *); if (ip->ip_v != IPVERSION) { ipstat.ips_badvers++; goto bad; }妥当性のチェックを行う受信した IP ヘッダの中身を表示させよう
ip_input(オプション処理&転送 )
void ip_input(){………… if (hlen > sizeof (struct ip) && ip_dooptions(m, 0)) return;
IP ヘッダのサイズが20より大きい場合、オプション処理を行う LIST_FOREACH(ia, INADDR_HASH(ip->ip_dst.s_addr), ia_hash) { if (IA_SIN(ia)->sin_addr.s_addr == ip->ip_dst.s_addr && (!checkif || ia->ia_ifp == m->m_pkthdr.rcvif))
アドレスリストをチェックし、自分宛か、転送するべきか判断する。
ip_input( リアセンブリ&振り分け )
void ip_input(){………… if (ip->ip_off & (IP_MF | IP_OFFMASK)) { m = ip_reass(m);オフセットやフラグがあったならば、フラグメントパケット。リアセンブリ処理を行う
(*inetsw[ip_protox[ip->ip_p]].pr_input)(m, hlen); return;
プロトコルスイッチにより、適切なトランスポート層へデータを渡す
icmp_input(ICMP の妥当性チェック )
sys/netinet/ip_icmp.c内にありますvoid icmp_input(){ ip = mtod(m, struct ip *); m->m_len -= hlen; m->m_data += hlen; icp = mtod(m, struct icmp *);
if (in_cksum(m, icmplen)) {
mbuf のデータポインタを調整後、 icmp ヘッダへのポインタを取り出し、妥当性チェック受信した ICMP ヘッダのメンバを色々表示してみよう
ICMPIP
icmp_input( メッセージの処理& raw input)
void icmp_input(){……… code = icp->icmp_code; switch (icp->icmp_type) {……….
case ICMP_ECHOREPLY: default: break; }raw: rip_input(m, off); return;
ここでまたまたカーネルをコンパイルしてみよう
コンパイル方法 cd /usr/src/sys/i386/compile/ECN make kernel make kernel-install
ぜーーったい、 make clean をしないでください! 一から make しようとすると、それだけでこの授業時間が終わります。。。
再起動 shutdown -r now
ping の送受信の流れは分かりましたか?余力があれば以下の処理を見つけて表示させてみよう ICMP ECHO Request を受信後、 reply を返す所 arp request受信後、 reply を返す所 フラグメントを発生させて、フラグメント&組み立て
処理
さらに余力があれば、ネットワークスタックとは関係ないですが、、、、 カーネル起動時のデバイス初期化メッセージを色々変
えてみたら楽しいかも (e.g.そんな餌におれは釣られないクマ――等の巨大 A
A が出てくるなど。個人的に見てみたい人はどうぞ )