Linuxはどのように外来のパケットDNATをLoopbackに着きます

7101 ワード

前に文章を書いた.
iptablesを使用して、外部からアクセスできないパケットNATを127.0.0.1にする理由」は、多くの知識にかかわっており、最終的な結論はそうはいかない.この結論は少し気分が悪くて、長いこと言って、それがどうしてできないのかを述べて、もし私がどうしてもバッグNATをloopbackに行かなければならないならば.例えば、ポートやアドレスを外部に露出させないために、エージェントサービスに127.0.0.1というアドレスをリスニングさせたいのですが、何か方法はありませんか?
Linuxを使っているので、答えは間違いなく肯定的で、やるべきことはルーティング時の制限条件を取り除くことにほかならない.2つの方向に進むと出ると、2つのルーティングロジックがあり、1つは
ip_route_input_slow,    ip_route_output_slow,           :
if (ipv4_is_lbcast(daddr) || ipv4_is_zeronet(daddr) ||
        ipv4_is_loopback(daddr))
    goto martian_destination;
その後の関数は、呼び出された__に制限されるmkroute_output:
if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))
    return -EINVAL;
私たちはいったいどうやって迂回すればいいのでしょうか.NATロジックはターゲットアドレスを127.0.0.1に変更したが、ソースアドレスは変わらず、このパケットは入るとip_route_input_slowはブロックされ、ソースアドレスがloopbackではないためloopbackをターゲットとして破棄されますが、破棄するときはルーティングがまだ見つかっていないことに注意してください.このパッケージが過ぎ去ったと仮定すると、戻りパケットがip_をうまく通過できるかどうかroute_output_slowは?答えは間違いなく否定的であり、標準ルーティングはターゲットアドレスに基づいているため、パケットを返すターゲットアドレスは実は順方向パケットのソースアドレスであり、ルーティングは間違いなく通過可能であるが、ルーティング前はターゲットデバイスを知らないため、簡単に破棄することはできず、ルーティング後にターゲットデバイスがloopbackでないことを確定してから破棄することができる.
以上がLinuxプロトコルスタックルーティングモジュールが「火星アドレス」に対応する論理である.まとめてみると、
1.パケットに入る場合、loopbackから送られてきたパケットはルーティングロジックを通さず、ルーティングによって検索されたものはすべて外来パケットであり、ルーティング前にソースアドレスとターゲットアドレスを知っており、ターゲットアドレスがloopbackであるかどうかによって破棄するかどうかを判断することができる.2.パケットの発行については、無条件(ルーティングcacheを考慮すると意味は同じ)にルーティングを経なければならず、ルーティング前にソースアドレスを必ずしも知るとは限らず、ソースアドレスを知っていてもターゲットデバイスがloopbackデバイスであるか否かを特定することはできず、ルーティング(localテーブルヒットターゲットデバイスがloopbackデバイス)によってのみ全ての情報を知ることができるため、ルーティング後、ルーティングcacheを生成する前に、ソースアドレスがloopbackアドレスであり、非loopbackデバイスに送信されたと判断して破棄する.
大まかな論理を知っています.私たちは2つのステップに分けてそれらを迂回します.
0.NATカーネルモジュールを変更する必要があります.$KERL/net/ipv 4/netfilter/nf_を変更する必要があります.nat_standalone.cこの書類.
1.入る順方向パケットに対するアドレス制限を迂回するこれがip_を迂回するroute_input_slowの制限は、もちろんプロトコルスタックコードを修正するのが最も効果的ですが、kernelを再コンパイルする必要があります.そこで、DNATの後、dst_を直接entryは、プロトコルスタックがルーティングロジックを介さずにルーティングが検索されたと判断し、上述した最初の制限を回避するために、このskbに付着する.DNATから127.0.0.1までのすべてのパケットについて同じdst_を使用するentryです.そのため、システムに1部だけ残しておく必要があります.
struct rtable *dummy_rth = (struct rtable*)kmalloc(sizeof(struct rtable), GFP_ATOMIC);
注意insmodのとき、つまりmoduleのinitを呼び出すときにこのrtableを生成する準備をしている場合、GFP_を使用する必要はありません.ATOMICマーク.
そしてnf_nat_standalone_Initにフィールドを入力します.
struct dst_entry dst = dummy_rth->u.dst;
memset(&dummy_rth->u.dst, 0, sizeof(struct dst_entry));
dummy_rth->u.dst.ops = NULL;
dummy_rth->u.dst.path = &dst;
dummy_rth->u.dst.flags= DST_HOST;
dummy_rth->u.dst.flags |= DST_NOXFRM;
dummy_rth->u.dst.flags |= DST_NOPOLICY;
dummy_rth->u.dst.dev = init_net.loopback_dev;
dummy_rth->rt_iif = init_net.loopback_dev->ifindex;

dummy_rth->rt_type = RTN_LOCAL;
dummy_rth->u.dst.input = (int (*)(struct sk_buff*))0xffffffff812734eb;  //ip_local_deliver   
atomic_set(&dummy_rth->u.dst.__refcnt, 1);
次に、DNATからloopbackへのパケットごとに上記のrtableを適用する必要がありますが、どこで適用しますか?明らかにPREROUTINGというHOOKポイントのNATの後、つまりnf_nat_Inフック関数の中:
static unsigned int
nf_nat_in(unsigned int hooknum,
          struct sk_buff *skb,
          const struct net_device *in,
          const struct net_device *out,
          int (*okfn)(struct sk_buff *))
{
        unsigned int ret;
        __be32 daddr = ip_hdr(skb)->daddr;

        ret = nf_nat_fn(hooknum, skb, in, out, okfn);
        if (ret != NF_DROP && ret != NF_STOLEN &&
            daddr != ip_hdr(skb)->daddr)
                skb_dst_drop(skb);
        if (daddr != ip_hdr(skb)->daddr && ipv4_is_loopback(ip_hdr(skb)->daddr)) {
                atomic_inc(&dummy_rth->u.dst.__refcnt);    //        。
                skb_dst_set(skb, &dummy_rth->u.dst);      // rtable dst_entry   skb 。
    }
    return ret;
}
以上で順方向パケットのルーティング前火星アドレスチェックが完了しました.上記のdummy_rthはシステムのルーティングcacheハッシュテーブルに挿入され、多くのフィールドも埋め込まれていません.これは、私たちがルーティングcacheとして使用したくないし、削除したくないし、通常の管理を受けたくないためです.NATモジュールがまだあるときに、パケットをローカル転送層以上にルーティングするために使用しています.
2.戻りパケットに対するアドレス制限を迂回することは、第1点と同様に、__を修正することもできます.mkroute_outputのソースコードはこれを実現しますが、これは良い方法ではありません.比較的良い方法の考え方は第1点と似ています.それはrtableを増やすことで標準的なルーティングを迂回することですが、上記の第1点よりずっと難しいです.これは、このrtableが上記のようにdummyを作ることができません.ソースアドレスが異なる可能性があります.さらに、ほとんどのrtableのフィールドが必要であり、今回は実際にルーティングとして使用され、ローカルにパケットをインポートするだけでなく、arp、neighbourなどのより下位層の問題に関連しています.また、このrtableは一意ではなく、固定されていないため、システムのルーティングcacheハッシュテーブルに組み込まれて管理することが望ましいです.
具体的にはnf_nat_inに論理を追加します.
if (daddr != ip_hdr(skb)->daddr && ipv4_is_loopback(ip_hdr(skb)->daddr)) {
    struct rtable *rev_dummy = dst_alloc(&ipv4_dst_ops);
    int err;
    unsigned hash;
    atomic_inc(&dummy_rth->u.dst.__refcnt);    //        。
    skb_dst_set(skb, &dummy_rth->u.dst);      // rtable dst_entry   skb 。
    //          rtable,      ,         ,         
    atomic_set(&rev_dummy->u.dst.__refcnt, 1);
    rev_dummy->u.dst.flags= DST_HOST;
    rev_dummy->u.dst.flags |= DST_NOXFRM;
    rev_dummy->u.dst.flags |= DST_NOPOLICY;
    rev_dummy->fl.fl4_dst    = ip_hdr(skb)->saddr;
    rev_dummy->fl.fl4_tos    = (u32)(RT_TOS(ip_hdr(skb)->tos) & (IPTOS_RT_MASK | RTO_ONLINK));;
    rev_dummy->fl.fl4_src    = ip_hdr(skb)->daddr;
    ...
    rev_dummy->rt_dst    = ip_hdr(skb)->saddr;
    rev_dummy->rt_src    = ip_hdr(skb)->daddr;
    hash = rt_hash(ip_hdr(skb)->saddr, ip_hdr(skb)->daddr, 0,
                               rt_genid(dev_net(skb->dev)));
    err = rt_intern_hash(hash, rev_dummy, rev_dummy, NULL);
}
このようにすれば、具体的なテスト方法は簡単です.それは、127.0.0.1:abcをリスニングするTCPプログラムを書き、nf_を再コンパイルしてロードすることです.nat.koおよびiptable_nat.koモジュールは、次のルールを設定します.
iptables -t nat -A PREROUTING -p tcp --dport 1234 -j DNAT --to-destination 127.0.0.1
できます.
またWindowsでhackをすることに慣れていると、上記の2点目をより効率的に処理する方法があります.それは「バイナリリセット」で、カーネルシンボルテーブルで__を見つけることです.mkroute_outputのアドレスは、フィーチャーコードで見つかります.
if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))

if (ipv4_is_loopback(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))
この判断は、その後、バイナリ命令で上書きして書き換え、直接スキップしたり、DNATからloopbackへの戻りパケットかどうかをフラグを追加したりして、判断結果に基づいて選択します...
附:デバッグ中に発生した問題
1.VMWare仮想マシンでデバッグされたため、XシステムとVMWare-toolsがインストールされていない.最初はrtableのフィールドが設定されていなかったため、devフィールドが設定されていないなど、後のtcp処理ロジックにはこのフィールドが必要で、システムpanicを招き、traceを印刷したが、仮想マシンの解像度が低いため、N行しか表示できなかった.N行の上には見えないし、sysrqでも効果がないので、なんとか端末の解像度を上げてgoogleしてみると、grubを使ったkernelのvgaカーネル起動パラメータが設定できることに気づき、設定したら、すぐに問題点がわかりました.2.多くの記号が導き出されていないため、そのアドレスを使用しなければならず、強制的に変換しなければならない.もちろん書き出された記号をなんとか使うのも無理はありませんが、リンク特定のobjファイルなど、より多くの作業をする必要があります.いっそアドレスを直接使うようにしましょう...3.本当はkprobeを使いたかったのですが、どういうことかWindowsプログラマー風な気がします.(まさかバイナリカバー技術を真っ青にするために、兄はすべての機械のアセンブリをマスターしなければなりませんか?実際には、兄はx 86しかできません-以前Windowsをやっていたときに学んだので、x 86-64はあまり分かりませんでした)