【Rust】ネットワークプログラミング ICMP編


前回はRustでちょっとしたツールを作りました。


Rustのことを少しでも知るためにスキャンツールを作成したいと思います。
そしてRustに僕のことを知ってもらえたらと思います。

ちなみにルーティングをスキャンするツールは以前に作ったことがあります。


こちらはc言語で作りました。
これと同じことをRustでやるということです。
つまり前回同様icmpを使って検証します。

必要なものを揃える


当然ながらpnetです。
Rustでネットワークに関して低レイヤなことをしたい場合、pnetを使います。

今回はスキャナーの作成に使えそうなものを探してみると以下のものを見つけました。
使うとしたらこのあたりでしょうな。

  • MutableIpv4Packet
  • MutableEchoRequestPacket


実際に作ってみたところまだ結構使うライブラリーはありますが、上の2つが今回の主役になります。

実装する


ライブラリーをuseします。
必要なものをその都度追加していったらこうなりました。

extern crate pnet;

use pnet::packet::ipv4::{MutableIpv4Packet, Ipv4Packet};
use pnet::packet::ip::IpNextHeaderProtocols;
use pnet::packet::icmp::{IcmpPacket, IcmpTypes, time_exceeded};
use pnet::packet::icmp::echo_request::{MutableEchoRequestPacket, IcmpCodes};
use pnet::transport::{TransportChannelType, transport_channel, ipv4_packet_iter};
use pnet::packet::Packet;

use std::env;
use std::net::{IpAddr,Ipv4Addr};
use std::str::FromStr;


main関数からです。
ipアドレスをコマンド引数として受け取ります。

使用するパケットはipとicmpですのでそれぞれ設定します。
Ipのチェックサムに関してはカーネルがどうにかしてくれるみたいなので、icmpのみチェックサムの計算をします。

諸々設定が完了したら送信処理に入ります。

ここでは特別なことはしてません。

fn main() {
  let args: Vec<String> = env::args().collect();
  if args.len() < 2  {
    panic!("Arg [Target IP]");
  }

  // Ipパケット設定
  let mut ip_packet:[u8; IP_SIZE] = [0; IP_SIZE];
  let mut ip = MutableIpv4Packet::new(&mut ip_packet).unwrap();
  make_ip_packet(&mut ip, Ipv4Addr::from_str(&args[1]).unwrap());

  // Icmpパケット設定
  let mut icmp_packet:[u8; ICMP_SIZE] = [0; ICMP_SIZE];
  let mut icmp = MutableEchoRequestPacket::new(&mut icmp_packet).unwrap();
  make_icmp_echo_packet(&mut icmp);
  let checksum = checksum(icmp.packet());
  icmp.set_checksum(checksum);

  // IPパケットに追加
  ip.set_payload(&icmp.packet());

  send_recv_packet(&mut ip);
}

// Ipv4パケット設定
fn make_ip_packet(ip: &mut MutableIpv4Packet, target_ip: Ipv4Addr) {
  ip.set_version(4);
  ip.set_header_length(5);
  ip.set_total_length(28);
  ip.set_identification(1);
  ip.set_ttl(1);
  ip.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
  ip.set_destination(target_ip);
}

// icmpパケット設定
fn make_icmp_echo_packet(icmp: &mut MutableEchoRequestPacket) {
  icmp.set_icmp_type(IcmpTypes::EchoRequest);
  icmp.set_icmp_code(IcmpCodes::NoCode);
  icmp.set_checksum(0);
  icmp.set_identifier(1);
  icmp.set_sequence_number(1);
}

fn checksum(data: &[u8]) -> u16 {
  let mut sum: u32 = 0;
  let mut s = data.len();
  for i in 0..s / 2 {
    sum += ( ( (data[i*2+1] as u16) << 8) + data[i*2] as u16) as u32;
    s -= 2;
    if sum & 0x80000000 == 1 {
      sum = (sum >> 16) + (sum & 0xffff);
    }
  }

  if s == 1 {
    sum += data[data.len()-1] as u32;
  }

  while sum >> 16 > 1 {
    sum = (sum >> 16) + (sum & 0xffff);
  }

  sum = (sum >> 8) + ( (sum << 8) & 0xff00);

  !sum as u16
}


送信処理は少し長くなりました。
まずはチャンネル(ソケットのことだと思う)を取得します。
transport_channelの第二引数に送信するプロトコルを指定します。
今回はicmpを指定します。

ipv4パケットを受信するためにipv4_packet_iterに戻り値のtransport_receiverを渡します。

パケットの送信はループのなかで繰り返し行います。
1からスタートしてインクリメントしていきます。

送信したらそのうちパケットを受信するのでその中身を確認します。
echo replyならターゲットに到達したということでループを終了します。
time exceededならパケット送信処理に戻ります。

// パケット送信処理
fn send_recv_packet(ip: &mut MutableIpv4Packet) {

  let (mut tx, mut rx) = transport_channel(512, TransportChannelType::Layer3(IpNextHeaderProtocols::Icmp)).unwrap();
  // 一応ipパケットを受信する
  let mut rx = ipv4_packet_iter(&mut rx);

  let mut reach = 0;
  println!("[*] Scaning route => {}", ip.get_destination());
  for i in 1..255 {

    // パケット設定
    ip.set_ttl(i);
    let ipv4 = Ipv4Packet::new(&ip.packet()).unwrap();
    tx.send_to(ipv4, IpAddr::V4(ip.get_destination())).unwrap();

    for _ in 0..3 {
      match rx.next() {
        Ok(ip_response) => {
          let i_pac = Ipv4Packet::new(ip_response.0.packet()).unwrap();
          let res = analyze(&i_pac);
          if res == 1 {
            reach = 1;
            println!("[*] Reach ttl: {} from {}",i, i_pac.get_source());
            break;
          } else if res == 0 {
            println!("- ttl: {} from {}",i, i_pac.get_source());
            break;
          }
        }
        _ => {
          println!(".");
        }
      }

    }
    if reach == 1 {
      break;
    }
  }
}

// パケットの中身を確認する
fn analyze(i_pac: &Ipv4Packet) -> u8 {
  match i_pac.get_next_level_protocol() {
    IpNextHeaderProtocols::Icmp => {
      let ic_pac = IcmpPacket::new(i_pac.payload()).unwrap();
      match ic_pac.get_icmp_type() {
        IcmpTypes::EchoReply => {
          return 1;
        }
        IcmpTypes::TimeExceeded => {
          match ic_pac.get_icmp_code() {
            time_exceeded::IcmpCodes::TimeToLiveExceededInTransit =>
            {
              return 0;
            }
            _ => { println!("[*] IcmpType:TimeExceeded Unknown Response")}
          }
        }
        _ => {}
      }
    }
    _ => {}
  }
  2
}


全体で150行ほどです。
Rustを使いこなせる人ならもう少し行数を抑えられるのかなと思います。

ただ僕もすこしは話のわかる人間です。
Rustのボローチェッカーやドキュメントとの冷静な話し合いの結果ですので、ご了承ください

こうやっていくつかRustで実装してみてやっと所有権や借用について少し理解できたような気がします。
Rustについての理解を助ける資源がだんだんと増えてきていると思います。