├── .gitignore ├── project ├── build.properties └── p.sbt └── src └── main └── scala ├── inet.scala ├── pcap.scala └── PcapExample.scala /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | *.iml 4 | 5 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.13 2 | -------------------------------------------------------------------------------- /project/p.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.7") 2 | 3 | -------------------------------------------------------------------------------- /src/main/scala/inet.scala: -------------------------------------------------------------------------------- 1 | package com.scalawilliam.scalanative 2 | 3 | import scala.scalanative.native 4 | import scala.scalanative.native.CUnsignedInt 5 | 6 | /** 7 | * We use this to avoid our own byte manipulation. 8 | * Ironically I have to do this with bytes in Java, so scala-native is already proving itself! 9 | */ 10 | @native.extern 11 | object inet { 12 | 13 | def inet_ntoa(input: CUnsignedInt): native.CString = native.extern 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/pcap.scala: -------------------------------------------------------------------------------- 1 | package com.scalawilliam.scalanative 2 | 3 | import scala.scalanative.native 4 | import scala.scalanative.native.{CInt, CString} 5 | 6 | /** 7 | * @see https://linux.die.net/man/3/pcap 8 | */ 9 | @native.link("pcap") 10 | @native.extern 11 | object pcap { 12 | 13 | /** This is just a pointer for us, we don't care what is inside **/ 14 | type pcap_handle = native.Ptr[Unit] 15 | 16 | type pcap_pkthdr = native.CStruct4[native.CUnsignedLong, 17 | native.CUnsignedLong, 18 | native.CUnsignedInt, 19 | native.CUnsignedInt] 20 | 21 | def pcap_open_live(deviceName: CString, 22 | snapLen: CInt, 23 | promisc: CInt, 24 | to_ms: CInt, 25 | errbuf: CString): pcap_handle = 26 | native.extern 27 | 28 | def pcap_open_offline(fname: CString, errbuf: CString): pcap_handle = 29 | native.extern 30 | 31 | def pcap_next(p: native.Ptr[Unit], 32 | h: native.Ptr[pcap_pkthdr]): native.CString = native.extern 33 | 34 | def pcap_close(p: native.Ptr[Unit]): Unit = native.extern 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/PcapExample.scala: -------------------------------------------------------------------------------- 1 | // seems I can't use package directories with scala native just yet 2 | package com.scalawilliam.scalanative 3 | 4 | import scala.scalanative.native 5 | import scala.scalanative.native._ 6 | 7 | /** 8 | * This example app reads online and offline via libpcap. 9 | * We're assuming it's installed on your system. 10 | * Basic usages: 11 | * live 12 | * live cooked 13 | * 14 | * cooked 15 | * 16 | * "Cooked": "Linux Cooked Capture" happens when we read from all interfaces on a Linux box. 17 | * These frames have an extra 2 bytes in front of them. For more, see: https://wiki.wireshark.org/SLL 18 | */ 19 | object PcapExample { 20 | 21 | val IpVersionByteOffset: Int = 14 22 | val PcapSourceIpv4AddressOffset: Int = 26 23 | val PcapDestinationIpv4AddressOffset: Int = PcapSourceIpv4AddressOffset + 4 24 | 25 | /** 26 | * We have a separate processing function to separate out the plumbing. 27 | * 28 | * @param data remember this is a pointer! But note that it may contain byte 0x00 29 | * which is typically a string termination character - so we must pass dataLength explicitly. 30 | */ 31 | def process_packet(epochSecond: Long, 32 | dataLength: Int, 33 | data: CString, 34 | cooked: Boolean): Unit = { 35 | 36 | val offsetBytes = if (cooked) 2 else 0 37 | 38 | val hasEnoughData = dataLength > (offsetBytes + PcapDestinationIpv4AddressOffset + 4) 39 | if (!hasEnoughData) return 40 | 41 | /** IP version is stored in the first nibble of the target byte **/ 42 | val isIpv4 = (!(data + IpVersionByteOffset + offsetBytes) >> 4) == 4 43 | if (!isIpv4) return 44 | 45 | /** 46 | * We move pointer a few bytes, then extract an Unsigned Int which 47 | * then we pass into the IP address call. It's quite verbose for 48 | * the time being because it's only a POC. 49 | ***/ 50 | val sourceIp = { 51 | val ip = !(data + PcapSourceIpv4AddressOffset + offsetBytes) 52 | .cast[Ptr[CUnsignedInt]] 53 | fromCString(inet.inet_ntoa(ip)) 54 | } 55 | val destIp = { 56 | val ip = !(data + PcapDestinationIpv4AddressOffset + offsetBytes) 57 | .cast[Ptr[CUnsignedInt]] 58 | fromCString(inet.inet_ntoa(ip)) 59 | } 60 | print(s"Time: $epochSecond, $sourceIp --> $destIp, $dataLength bytes: [") 61 | (0 to Math.min(dataLength, 12)) 62 | .map { n => 63 | !(data + offsetBytes + n) 64 | } 65 | .foreach { v => 66 | native.stdio.printf(c"%02X", v) 67 | } 68 | println("...]") 69 | } 70 | 71 | def main(args: Array[String]): Unit = { 72 | if (!args.isEmpty) 73 | Zone { implicit zone => run(args) } 74 | else 75 | println("Incorrect usage.") 76 | } 77 | 78 | def run(args: Array[String])(implicit zone: Zone): Unit = { 79 | val cooked = args.contains("cooked") 80 | val live = args.contains("live") 81 | val errorBuffer = native.stackalloc[Byte](256) 82 | val pcapHandle = if (live) { 83 | pcap.pcap_open_live( 84 | deviceName = c"any", 85 | snapLen = Short.MaxValue, 86 | promisc = 0, 87 | to_ms = 10, 88 | errbuf = errorBuffer 89 | ) 90 | } else { 91 | pcap.pcap_open_offline(fname = toCString(args.last), 92 | errbuf = errorBuffer) 93 | } 94 | if (pcapHandle == null) { 95 | println(s"Failed to open reader: ${fromCString(errorBuffer)}") 96 | sys.exit(1) 97 | } 98 | val packetHeaderPointer: native.Ptr[pcap.pcap_pkthdr] = 99 | native.stackalloc[pcap.pcap_pkthdr] 100 | var packetReadData = pcap.pcap_next(pcapHandle, packetHeaderPointer) 101 | var continue = true 102 | while (continue) { 103 | if (packetReadData != null) { 104 | process_packet( 105 | epochSecond = (!packetHeaderPointer._1).toLong, 106 | dataLength = (!packetHeaderPointer._3).toInt, 107 | data = packetReadData, 108 | cooked = cooked 109 | ) 110 | } else if (!live) { 111 | continue = false 112 | } 113 | if (continue) { 114 | packetReadData = pcap.pcap_next(pcapHandle, packetHeaderPointer) 115 | } 116 | } 117 | 118 | pcap.pcap_close(pcapHandle) 119 | } 120 | } 121 | --------------------------------------------------------------------------------