ECN KG 発表

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が出てくるなど。個人的に見てみたい人はどうぞ)