Upgrade to Pro — share decks privately, control downloads, hide ads and more …

関数型言語で始めるネットワークプログラミング

kamijin_fanta
November 11, 2018
960

 関数型言語で始めるネットワークプログラミング

kamijin_fanta

November 11, 2018
Tweet

More Decks by kamijin_fanta

Transcript

  1. 簡単な例 .! は変数抽出 !Hoge ~ Fuga は、先読みを行ってHogeが否定マッチすれば、Fugaに進む Hoge.rep は繰り返し min,max,sepが指定可能 val

    input = "yummy_cookie=choco; tasty_cookie=strawberry" val key = (!"=" ~ AnyChar).rep.! // "=" 以外の文字列にマッチ val value = (!CharIn(";", " ") ~ AnyChar).rep.! val field = P(key ~ "=" ~ value) val cookie = P(Start ~ field.rep(sep = "; ") ~ End) val res = cookie.parse(input).get.value assert(res === Seq( "yummy_cookie" ‐> "choco", "tasty_cookie" ‐> "strawberry", )) cookieの俺俺パーサーって人類の8割が書いていると思う 8
  2. JSON を雑にパースする 指数/少数の数値・文字列エスケープなどを扱わない 記法 型 記法 型 {....} JsObject(member: Map[String,

    JsValue]) [...] JsArray(items: List[JsValue]) 1234 1.23 10e3 JsNumber(value: BigDecimal) true false JsBoolean(value: Boolean) "" JsString(value: String) null JsNull type Json = JsObject | JsArray | JsNumber | JsBoolean | JsString | JsNull 9
  3. AST っぽい感じの型を定義 ast=abstract syntax tree object Json { // like:

    type JsonExpr = JsObject | JsArray | JsNumber | JsBoolean | JsString | JsNull sealed trait JsonExpr case class JsObject(obj: Map[String, JsonExpr]) extends JsonExpr case class JsArray(values: Seq[JsonExpr]) extends JsonExpr case class JsBoolean(value: Boolean) extends JsonExpr case class JsNumber(value: Int) extends JsonExpr case class JsString(value: String) extends JsonExpr case object JsNull extends JsonExpr } 10
  4. val json = JsArray(List( JsObject(Map( "id" ‐> JsNumber(1234), "name" ‐>

    JsString("hoge") )) )) [{ "id": 1234, "name": "hoge"}] 11
  5. object JsonParser { val space = CharsWhileIn(" \r\n").rep // 空白のいずれかにマッチ

    val char = (!CharIn("\"\\") ~ AnyChar).! // "\ 以外の文字列にマッチ val chars = space ~ "\"" ~ char.rep.! ~ "\"" ~ space // ""に囲まれた文字列 val digit = CharIn('0' to '9').! val bool = P("true").map(_=>JsBoolean(true)) | P("false").map(_=>JsBoolean(false)) val string = chars.map(s => JsString(s)) val nul = P("null").map(_ => JsNull) val number = P(CharIn("+‐").? ~ digit.rep(min=1)).!.map(v => JsNumber(v.toInt)) val objPare = P(chars ~/ ":" ~/ json) val obj = P("{" ~/ objPare.rep(sep = ",".~/) ~/ "}").map(s => JsObject(s.toMap)) val array = P("[" ~/ json.rep(sep = ",".~/) ~/ "]").map(s => JsArray(s)) val json: all.Parser[JsonExpr] = P(space ~ (obj | string | array | nul | number | bool) ~ space) } val input = """ { "text": "value", "array": [null, 1234, "str"]} """ 12
  6. DHCP Dynamic Host Configuration Protocol(ダイナミック ホスト コンフィギュレーション プロトコル、DHCP)とは、コンピュータがネットワーク接続する際に必要な情報を自 動的に割り当てるプロトコルのことをいう。 DHCP

    は BOOTP の上位互換であり、メッセージ構造などは変わっていない。DHCP で は、BOOTP に比べて自動設定できる情報が増え、より使いやすくなっている。 https://ja.wikipedia.org/wiki/Dynamic̲Host̲Configuration̲Protocol 自動設定可能な項目 IPアドレス・デフォルトゲートウェイ・DNS ホスト名 NTP プリンター 16
  7. otpion Type(1) Length(1) Data(n) Type(1) Length(1) Data(n) 53 1 01

    (discover) 50 4 c0 a8 6f 69 (192.168.111.105) 12 8 PC103553 22
  8. 型定義 case class BootpPacket( bootpType: Short, transactionId: Long, yourClientIp: Inet4Address,

    clientMac: MacAddress, options: Seq[BootpOption]) case class DhcpOptions( messageType: Int, mask: Option[String], router: Option[String], dns: Option[String], leaseTime: Option[Int] ) 23
  9. ついでに列挙体も定義 object BootpOptionType extends Enumeration { val SubnetMask = 1.toShort

    val Router = 3.toShort val DNS = 6.toShort val HostName = 12.toShort val RequestedIpAddress = 50.toShort val IpAddressLeaseTime = 51.toShort val DhcpMessageType = 53.toShort val DhcpServerIdentify = 54.toShort val ParameterRequestList = 55.toShort } object DhcpMessageType extends Enumeration { val Discover = 1 val Offer = 2 val Request = 3 val Ack = 5 val Release = 7 } 24
  10. fastparse val parser: core.Parser[BootpPacket, Byte, all.Bytes] = P( // bootpType

    eth addrLen Hops Trans UInt8 ~ BS(0x01, 0x06) ~ AnyByte ~ UInt32 ~ AnyBytes(8) ~ // ip mac address AnyBytes(4).! ~ AnyBytes(8) ~ UInt8.rep(exactly = 6) ~ AnyBytes(10) ~ // ServerHostName BootFileName Magic Cookie AnyBytes(64) ~ AnyBytes(128) ~ AnyBytes(4) ~ // options type(1) length(1) value(n) (Int8.filter(_ != ‐1) ~ Int8.flatMap(size => AnyBytes(size).!)) .map(x => BootpOption(x._1, x._2)) .rep ~ BS(0xff) // option end ).map(x => { BootpPacket( bootpType = x._1, transactionId = x._2, yourClientIp = ByteArrays.getInet4Address(x._3.toArray, 0), clientMac = MacAddress.getByAddress(x._4.map(_.toByte).toArray), options = x._5 ) }) 25
  11. パケット生成 case class BootpPacket( bootpType: Short, transactionId: Long, yourClientIp: Inet4Address,

    clientMac: MacAddress, options: Seq[BootpOption]) { def asBytes: Bytes = Bytes(bootpType) ++ Bytes(0x01, 0x06, 0) ++ Bytes.fromLong(transactionId, size = 4) ++ Bytes.fill(8)(0) ++ Bytes(yourClientIp.getAddress) ++ Bytes.fill(8)(0) ++ Bytes(clientMac.getAddress) ++ Bytes.fill(10)(0) ++ Bytes.fill(64)(0) ++ Bytes.fill(128)(0) ++ Bytes(0x63, 0x82, 0x53, 0x63) ++ Bytes.concat(options.map(o => Bytes(o.optionType) ++ Bytes.fromLong(o.value.length, Bytes(0xff) } 26
  12. test // udp payload val discover = "01010600a1290416000000000000000000000000000000000000000000ac79792a1a0000000000 it("discover decode

    & encode") { // 本物のパケットのパース→バイト列への変換→パースを行っている val result = BootpPacket.parse(hexToBytes(discover)) val value = result.get.value assert(value.bootpType === 1) assert(value.transactionId.toHexString === "a1290416") assert(value.yourClientIp.getHostAddress === "0.0.0.0") assert(value.clientMac.toString === "00:ac:79:79:2a:1a") assert(getBootpOption(value, BootpOptionType.DhcpMessageType).toInt() === DhcpMessageType assert(getBootpOption(value, BootpOptionType.RequestedIpAddress) === Bytes(0xc0, 0xa8, val packetBytes = value.asBytes assert(BootpPacket.parse(packetBytes).get.value === value) } 27
  13. pcap4j val nif: PcapNetworkInterface = Pcaps.getDevByName(deviceName) // uuid val nif:

    PcapNetworkInterface = Pcaps.getDevByAddress(address) // ip val handle: PcapHandle = nif.openLive(65536, PromiscuousMode.PROMISCUOUS, 10) handle.setFilter("udp and ( port 67 or port 68 )", BpfCompileMode.OPTIMIZE) handle.loop(packetCount = ‐1, new PacketListener { override def gotPacket(packet: Packet): Unit = handlePacket(packet) }) def handlePacket(packet: Packet) = packet match { case eth: EthernetPacket => println(s"Ethernet ${eth.getHeader.getSrcAddr} ‐> ${eth.getHeader.getDstAddr}") eth.getPayload match { case ipv4: IpV4packet => ipv4.getPayload match { case udp: UdpPacket => val arr = udp.getPayload.getRawData // process } } } 30
  14. pcap4j 送信 val src = InetAddress.getByName("10.0.0.1").asInstanceOf[Inet4Address] val dst = value.yourClientIp

    val newUdp = new UdpPacket.Builder() .srcAddr(src).dstAddr(dst) .srcPort(UdpPort.BOOTPS).dstPort(UdpPort.BOOTPC) .payloadBuilder(bootpPayload.getBuilder) .correctChecksumAtBuild(true) .correctLengthAtBuild(true) val newIp = new IpV4Packet.Builder(ipv4) // 受信したipv4パケットを継承 .srcAddr(src).dstAddr(dst) .ttl(64) .payloadBuilder(newUdp) .correctChecksumAtBuild(true) .correctLengthAtBuild(true) val newEth = new EthernetPacket.Builder(eth) .srcAddr(macAddress).dstAddr(eth.getHeader.getSrcAddr) .payloadBuilder(newIp) .paddingAtBuild(true) pcapHandle.sendPacket(newEth.build()) // 送信 31