ECN KG 発表 カーネルをハックしてみよう ネットワークスタックをいぢる FreeBSDのカーネルコードにprintfをいれて処 理を追ってみる 今日のターゲットはICMPです! まずはQEMUを手に入れよう 既にQEMUをダウンロードしてありますよね? http://www.ht.sfc.keio.ac.jp/~sada/etc/qemu0.7.zip 下記媒体でも配布します。 CD-R SDカード コンパクトフラッシュ QEMUを手に入れたら、ローカルの好きな場所 においてください FreeBSD@QEMU の起動 qemu-freebsd.batをダブル クリック! ユーザはroot、パスワードは・ ・・ 下記のようにタイプするとIPア ドレスがとれます dhclient ed0 外部とはport 22しか繋がり ません。 qemu-freebsd.batを編集する と、好きなportで通信できます 仮想ルータ 10.0.2.2 FreeBSD 10.0.2.16 QEMU カーネルのソースコード構成 どんなファイルがあるか、ざっ くりチェックしてみてください。 i386 X86特有 kern 一般的なカーネル net netinet / usr src sys 一般的なネットワーク TCP/IP sys カーネルヘッダ ufs Unixファイルシステム vm カーネルヘッダ ICMP Echo Request, Reply application application ip_output() rip_input() ether_output() icmp_input() arpresolve() ip_input() ether_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ヘッダができるまで) ICMP ip_output(){ …… ip = mtod(m, struct ip *); IP ICMP 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 なので、 ここでは、初期化されない) 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 tos ip_tos 全体の長さ ip_len 識別子 ip_id TTL ip_ttl フラグ、フラグメントオフセット ip_off プロトコル 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 splbio splimp 端末I/O ディスクとテープのI/O ネットワークデバイスI/O splclock splhigh splx(x) 高優先度クロック処理 全ての割り込みがブロック 前の優先度に戻す 低 高 別 ether_output() sys/net/if_ethersubr.c内の関数 処理は フレーム構築 インタフェースキューイング ether_output(ARP解決) int ether_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(フレーム構築) int ether_output(){ …… M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT); if (m == NULL) senderr(ENOBUFS); eth 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ヘッダの中身を埋めていく IP ICMP IP ICMP ether_output_frame(出力へのキューイング) int ether_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 で書いてもいいかも。 void mac_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にあります int arpresolve(){ …… 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要求) int arpresolve(){ …… 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 void ether_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 *); IP ICMP if (in_cksum(m, icmplen)) { mbufのデータポインタを調整後、icmpヘッダへのポイン タを取り出し、妥当性チェック 受信したICMPヘッダのメンバを色々表示してみよう 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.そんな餌におれは釣られないクマ――等の巨大 AAが出てくるなど。個人的に見てみたい人はどうぞ)
© Copyright 2024 ExpyDoc