├── .gitignore ├── LICENSE ├── README.md ├── app ├── Module.scala ├── controllers │ ├── ExtAssets.scala │ └── Home.scala └── services │ ├── NifDispatcherActor.scala │ ├── PacketToJsonTransfer.scala │ ├── PcapInitializer.scala │ └── WebSocketActor.scala ├── build.sbt ├── conf ├── application.conf ├── glimpse.conf ├── logback.xml └── routes ├── docs ├── ng-logo2_l.png ├── ng-logo2_sm.png ├── ng-logo2_xl.png ├── ng-logo3_l.png ├── ng-logo3_sm.png ├── ng-logo3_xl.png ├── ng-logo_l.png ├── ng-logo_sm.png ├── ng-logo_xl.png ├── schema.png ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png ├── screenshot4.png ├── screenshot5.png └── screenshot6.png ├── project ├── build.properties └── plugins.sbt ├── public ├── etherglimpse.html ├── glimpse.html ├── graph.js ├── images │ └── favicon.ico ├── index.html ├── ipglimpse.html ├── javascripts │ ├── jquery-3.2.1.min.js │ ├── toxiclibs.js │ └── toxiclibs.min.js ├── netDataReceiver.js ├── p5 │ ├── addons │ │ ├── p5.dom.js │ │ ├── p5.dom.min.js │ │ ├── p5.sound.js │ │ └── p5.sound.min.js │ ├── empty-example │ │ ├── index.html │ │ └── sketch.js │ ├── p5.js │ └── p5.min.js ├── p5visu.js ├── stylesheets │ ├── glimpse.css │ └── index.css └── utils.js ├── sbt ├── sbt-dist └── conf │ ├── sbtconfig.txt │ └── sbtopts └── sbt.bat /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | project/project/ 3 | project/target/ 4 | target/ 5 | /.target 6 | bin 7 | tmp 8 | .history 9 | dist 10 | /play.crypto.secret 11 | /.idea 12 | /*.iml 13 | /webSocketOut 14 | /.idea_modules 15 | *.classpath 16 | .project 17 | /RUNNING_PID 18 | /.settings 19 | /.sbtserver 20 | /study_assets_root/ 21 | /database/ 22 | .DS_Store 23 | .sbtserver 24 | .sbtserver.lock 25 | play-fork-run.sbt 26 | play-fork-run.sbtold 27 | sbt-ui.sbt 28 | sbt-ui.sbtold 29 | .cache-main 30 | /.cache-tests 31 | org.eclipse.core.resources.prefs 32 | org.scala-ide.sdt.core.prefs 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | net-glimpse is distributed under the MIT license. 2 | 3 | Copyright (c) 2011-2017 Pcap4J.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 13 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![screenshot](docs/ng-logo2_l.png) 2 | 3 | I wanted a little tool that gives me a quick overview on what's going in my network right now, displaying it in the browser so I can access it easily whenever I want. Tools like tcpdump or [Wireshark](https://www.wireshark.org/) are great but don't give you this 'glimpse'. 4 | 5 | net-glimpse has two parts: 1) Visualization of network traffic (Ethernet and/or Internet) in real-time, and 2) Streaming of header data from your network interfaces via WebSockets. 6 | 7 | **Have a look at [this video](https://youtu.be/Nvm5NaTZLGY).** 8 | 9 | ![screenshot](docs/screenshot6.png) 10 | 11 | ### Used technologies 12 | 13 | * Scala, JavaScript 14 | * Pcap4J (https://github.com/kaitoy/pcap4j) to access network interfaces 15 | * Play Framework and sbt 16 | * [Akka](http://akka.io/) to distribute network interface data to multiple WebSockets 17 | * Graphics with [p5js](https://p5js.org/) and physics with [toxiclibs](https://github.com/hapticdata/toxiclibsjs) 18 | 19 | ### Contents 20 | 21 | * [How to run](#how-to-run) 22 | * [Prerequisites](#prerequisites) 23 | * [Install and run](#install-and-run) 24 | * [Visualization of network traffic](#visualization-of-network-traffic) 25 | * [Endpoints](#endpoints) 26 | * [Visualization Details](#visualization-details) 27 | * [Visualization Configuration](#visualization-configuration) 28 | * [Streaming of header data from your network interfaces via WebSockets](#streaming-of-header-data-from-your-network-interfaces-via-websockets-backend) 29 | * [Usage in JavaScript](#usage-in-javascript) 30 | * [Backend configuration](#backend-configuration) 31 | * [Build yourself and modify the source code](#build-yourself-and-modify-the-source-code) 32 | 33 | 34 | ## How to run 35 | 36 | net-glimpse works on **Linux/Unix** (including **Mac OS X**) and **Windows**. 37 | 38 | ### Prerequisites 39 | 40 | * net-glimpse needs **Java 8** (JRE is enough) to run. 41 | * For the visualizations you need a modern browser (one that supports WebSockets and WebGL). 42 | * On **Windows** it is necessary to install _Npcap_ (https://nmap.org/npcap/) **OR** _WinPcap_ (https://www.winpcap.org/). On **Linux/Unix** _libpcap_ is needed. 43 | 44 | ### Install and run 45 | 46 | 1. [Download the net-glimpse-x.x.zip](https://github.com/kristian-lange/net-glimpse/releases) 47 | 48 | 1. Unzip and change into the unzipped folder 49 | 50 | 1. On **Linux or Unix** to access network interfaces you have to start the net-glimpse either as **root** or give Java special capabilities, e.g. with `sudo setcap cap_net_raw,cap_net_admin=eip /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java` (but exchange the path to your Java installation). On **Windows** you will be ask after starting net-glimpse's batch file if you want to grant access to the network interfaces. 51 | 52 | 1. On **Unix** it might be necessary to make the run script executable: `chmod u+x ./bin/net-glimpse` 53 | 54 | 1. Run on **Linux or Unix** in the terminal `./bin/net-glimpse` - on **Windows** double-click the `./bin/net-glimpse.bat` 55 | 56 | You can specify IP and port with the parameters `-Dhttp.address` and `-Dhttp.port`. E.g. `./bin/net-glimpse -Dhttp.address=172.23.1.81 -Dhttp.port=8080` binds net-glimpse to IP `172.23.1.81` and port `8080`. If you don't specify these parameters the defaults `0.0.0.0` (listens all addresses) and `9000` are used. 57 | 58 | If net-glimpse does not want to start have a look in its installation folder whether you find a file RUNNING_PID. Delete it and start again. 59 | 60 | 1. Try in a browser, e.g. with `http://localhost:9000/?nif=myNetworkInterface` (instead of `myNetworkInterface` use the name of the network interface you want to intercept) 61 | 62 | net-glimpse prints out potential network interface names (NIF) in it's log. You can copy-paste one from there. This is especially handy on **Windows** where they have names like, e.g. `\Device\NPF_{998BB72F-3468-413E-813C-7E3A2E7B591B}` which would lead to the URL `http://localhost:9000/?nif=\Device\NPF_{998BB72F-3468-413E-813C-7E3A2E7B591B}`. 63 | 64 | The resulting webpage shows a list of raw packet header data in JSON format. 65 | 66 | ![screenshot](docs/screenshot4.png) 67 | 68 | 1. If you are done with net-glimpse you can stop it with `Ctrl+C`. 69 | 70 | 71 | ## Visualization of network traffic 72 | 73 | ### Endpoints 74 | 75 | 1. `http://localhost:9000/glimpse?nif=myNetworkInterface` - shows both, Ethernet and Internet 76 |   77 | 1. `http://localhost:9000/ipglimpse?nif=myNetworkInterface` - shows only Internet 78 | 79 | 1. `http://localhost:9000/etherglimpse?nif=myNetworkInterface` - shows only Ethernet 80 | 81 | 1. `http://localhost:9000/?nif=myNetworkInterface` - shows raw packet header data in JSON 82 | 83 | E.g. [`http://localhost:9000/glimpse?nif=wlp3s0`](http://localhost:9000/glimpse?nif=wlp3s0) shows a visualization of the Ethernet layer and the Internet layer of the network interface `wlp3s0`. 84 | 85 | ![screenshot](docs/screenshot5.png) 86 | 87 | **You can open multiple pages of the same or different network interface(s) at the same time.** 88 | 89 | ### Visualization Details 90 | 91 | * You can press 'p' to pause the drawing at any time 92 | * It's actually a [force-directed graph](https://en.wikipedia.org/wiki/Force-directed_graph_drawing) 93 | * Nodes represent MAC or IP addresses 94 | * Node colors are determined by the MAC or IP address (means the same MAC or IP address leads always to the same color) 95 | * Nodes with broadcast or multicast IP addresses are white. 96 | * Nodes and edges blink when a new packet is sent 97 | * Edges represent sent packets 98 | * The arrow shows the direction of the sent packet 99 | * The edges get thicker the more packets are sent 100 | * The EtherType (in the Ethernet visualization) is annotated at the edge (scroll down - under the graphic is a glossary) 101 | * In the Internet visualization if it is a TCP or UDP packet and the port is one of the well known or registered ones (port 0 to 49151) the port is annotated at the edge. The most common port numbers are exchanged with their names, eg. port 22 is exchanged with SSH (scroll down - under the graphic is a glossary). 102 | * If it's the Internet and not a TCP or UDP packet then the protocol name is annotated at the edge. 103 | * Edges of unknown EtherTypes or Internet packets are black/gray by default (but you can add new types in the config) 104 | * Nodes and edges get removed after a while if no packets are sent (default is 10 s) 105 | * In fullscreen mode the whole screen is used for the graph(s) - otherwise they have a squared canvas 106 | 107 | ### Visualization Configuration 108 | 109 | Many parameters of the visualizations can be changed, e.g. 110 | 111 | * Edge colors and annotation 112 | * Node size and node repulsion 113 | * Cleaning interval and max age of nodes 114 | * Blacklist and whitelist for IP and MAC addresses 115 | 116 | The configuration file is in [`./config/glimpse.conf`](https://github.com/kristian-lange/net-glimpse/blob/master/conf/glimpse.conf). More details are in the comments of the config file. 117 | 118 | ![screenshot](docs/screenshot2.png) 119 | 120 | 121 | ## Streaming of header data from your network interfaces via WebSockets (backend) 122 | 123 | Usually it's not possible to access network interfaces from within a browser. net-glimpse uses pcap4j to access the interfaces and then streams the header data via WebSockets into the browser. You can use this part of net-glimpse independent of the visualization. 124 | 125 | ### Usage in JavaScript 126 | 127 | If you just want to get the header data without the visualization you have to open a WebSocket with the URL `/netdata` and the network interface you want to intercept has to be specified in the query string with the parameter 'nif'. 128 | 129 | E.g. in JavaScript (browser) to get traffic from the network interface `wlp3s0` one could write 130 | 131 | ```javascript 132 | var socket = new WebSocket("ws://myhost/netdata/?nif=wlp3s0"); 133 | ``` 134 | 135 | or more generally with secure WebSockets and assuming net-glimpse runs on the same host as your JavaScript is served from. 136 | 137 | ```javascript 138 | var socket = new WebSocket( 139 | ((window.location.protocol === "https:") ? "wss://" : "ws://") + 140 |      window.location.host + "/netdata/?nif=wlp3s0"); 141 | ``` 142 | 143 | The streamed packet header data are in JSON format. 144 | 145 | * It is possible to **stream different network interfaces in parallel**. 146 | * It is also possible to **stream the same network interface to multiple destinations**. 147 | * Only header data are captured and streamed via WebSockets - the actual payload is not read. 148 | 149 | ### Backend configuration 150 | 151 | #### Via -D run parameters 152 | 153 | net-glimpse takes a couple of parameters: 154 | 155 | * `-Dnif` - Specifies the default network interface. If you specify it here you can leave it out in the URL query. It has no default. 156 | * `-DskipOwnTraffic` - If true net-glimpse's own network traffic (via WebSockets) is not streamed. Default is `true`. 157 | * `-Dsnaplen` - Sets the snap length (see [wiki.wireshark.org/SnapLen](https://wiki.wireshark.org/SnapLen) for more info). Default is `128` byte. 158 | * `-Dhttp.address` - Specifies the IP address net-glimpse runs on. Default is `0.0.0.0` (listens on all IPs). 159 | * `-Dhttp.port` - Specifies the port net-glimpse runs on. Default is `9000`. 160 | 161 | e.g. `./bin/net-glimpse -Dhttp.address=192.168.178.160 -Dhttp.port=80 -Dnif=wlp3s0 -DskipOwnTraffic=false` 162 | 163 | #### Via `conf/application.conf` 164 | 165 | All parameters that can be specified via -D run parameters can be set in `./conf/application.conf` too. 166 | 167 | 168 | ## Build yourself and modify the source code 169 | 170 | If you don't trust net-glimpse' pre-build releases you can build it yourself. It uses [sbt](http://www.scala-sbt.org/) as build tool. Just download the source code and run `sbt dist`. In `./target/universal/` will be the built `.zip` file. More information can be found in https://www.playframework.com/documentation/2.6.x/Deploying or https://www.playframework.com/documentation/2.6.x/BuildOverview. 171 | 172 | If you want to modify the source code (e.g. you need more data from the network packets) the following files are probably where you want to start: 173 | 174 | * [/app/services/PacketToJsonTransfer.scala](https://github.com/kristian-lange/net-glimpse/blob/master/app/services/PacketToJsonTransfer.scala) - serialises the packet data into JSON (backend-side) 175 | * [/public/netDataReceiver.js](https://github.com/kristian-lange/net-glimpse/blob/master/public/netDataReceiver.js) - deserialising JSON in the browser 176 | * [/public/p5visu.js](https://github.com/kristian-lange/net-glimpse/blob/master/public/p5visu.js) - visalisation with p5.js 177 | * [/public/graph.js](https://github.com/kristian-lange/net-glimpse/blob/master/public/graph.js) - graph data structure and physics 178 | -------------------------------------------------------------------------------- /app/Module.scala: -------------------------------------------------------------------------------- 1 | import com.google.inject.AbstractModule 2 | import org.pcap4j.core.{PcapNativeException, Pcaps} 3 | import play.api.{Configuration, Environment, Logger} 4 | 5 | import scala.collection.JavaConverters._ 6 | 7 | class Module(environment: Environment, configuration: Configuration) extends AbstractModule { 8 | 9 | private val logger: Logger = Logger(this.getClass) 10 | 11 | def configure() = { 12 | // Print out all network interfaces so users know which names they have 13 | try { 14 | val nifs = Pcaps.findAllDevs.asScala 15 | 16 | nifs.foreach(nif => Logger.info("NIF: " + nif.toString)) 17 | 18 | System.out.println("List of available network interfaces:") 19 | nifs.foreach(nif => System.out.println(s" ${nif.getName}")) 20 | 21 | var httpAddress = configuration.get[String]("play.server.http.address") 22 | if (httpAddress == "0.0.0.0") httpAddress = "localhost" 23 | val httpPort = configuration.get[Int]("play.server.http.port") 24 | val firstNif = if (nifs.nonEmpty) nifs.head.getName else "myNif" 25 | System.out.println(s"E.g. in your browser use URL " + 26 | s"$httpAddress:$httpPort/glimpse?nif=$firstNif") 27 | } catch { 28 | case e: PcapNativeException => 29 | // do nothing 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/controllers/ExtAssets.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import java.io.File 4 | import javax.inject.Inject 5 | 6 | import play.Environment 7 | import play.api.Logger 8 | import play.api.mvc.{AbstractController, ControllerComponents} 9 | import scala.concurrent.ExecutionContext.Implicits.global 10 | 11 | /** 12 | * Controller for loading of external files (outside of jar) 13 | * 14 | * Created by Kristian Lange in 2017. 15 | * 16 | */ 17 | class ExtAssets @Inject()(environment: Environment, 18 | controllerComponents: ControllerComponents) 19 | extends AbstractController(controllerComponents) { 20 | 21 | private val logger: Logger = Logger(this.getClass) 22 | 23 | /** 24 | * Generates an `Action` that serves a static resource from within the 25 | * application's folder or if the application was started from the bin/ 26 | * folder it uses the parent (important for Windows) 27 | * 28 | * @param filePath the file path 29 | */ 30 | def at(filePath: String) = Action { _ => 31 | var rootPath = environment.rootPath.getAbsolutePath 32 | if (rootPath.endsWith("bin") || rootPath.endsWith("bin/")) 33 | rootPath = environment.rootPath.getParent 34 | 35 | val fileToServe = new File(rootPath + filePath) 36 | if (fileToServe.exists) { 37 | logger.info("Loading external asset file " + fileToServe.getAbsolutePath) 38 | Ok.sendFile(fileToServe, inline = true) 39 | } else { 40 | logger.info("Couldn't find external asset file " + fileToServe.getAbsolutePath) 41 | NotFound 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/controllers/Home.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import javax.inject._ 4 | 5 | import akka.actor.ActorSystem 6 | import akka.stream.Materializer 7 | import play.api.Configuration 8 | import play.api.libs.json.JsValue 9 | import play.api.libs.streams.ActorFlow 10 | import play.api.mvc._ 11 | import services.{PcapInitializer, WebSocketActor} 12 | 13 | import scala.concurrent.Future 14 | 15 | /** 16 | * Created by Kristian Lange on 2017. 17 | */ 18 | @Singleton 19 | class Home @Inject()(implicit actorSystem: ActorSystem, 20 | materializer: Materializer, 21 | configuration: Configuration, 22 | pcapInitializer: PcapInitializer, 23 | controllerComponents: ControllerComponents) 24 | extends AbstractController(controllerComponents) { 25 | 26 | /** 27 | * Default network interface (specified in application.conf) 28 | */ 29 | private val defaultNifName = configuration.get[String]("nif") 30 | 31 | /** 32 | * This endpoint serves WebSockets that stream network header data 33 | * 34 | * @param nif Network interface name of the interface to be intercepted 35 | * @return WebSocket that streams network header data or 'forbidden' in case something went wrong 36 | */ 37 | def netdata(nif: String = defaultNifName): WebSocket = WebSocket.acceptOrResult[JsValue, JsValue] { 38 | _ => { 39 | val nifDispatcherOption = 40 | if (nif != null && nif.nonEmpty) pcapInitializer.getNifDispatcher(nif) 41 | else pcapInitializer.getNifDispatcher(defaultNifName) 42 | 43 | Future.successful(nifDispatcherOption match { 44 | case None => Left(Forbidden) 45 | case Some(nifDispatcher) => Right(ActorFlow.actorRef(out => WebSocketActor.props(out, 46 | nifDispatcher))) 47 | }) 48 | } 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/services/NifDispatcherActor.scala: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import akka.actor.{Actor, ActorRef, Props} 4 | import play.api.Logger 5 | import play.api.libs.json.JsObject 6 | import services.NifDispatcherActor.{Subscribe, Unsubscribe} 7 | 8 | import scala.collection.mutable.ArrayBuffer 9 | 10 | /** 11 | * Akka actor handling a single network interface and forwards all arriving 12 | * messages to all subscribing [[WebSocketActor]]. It also cares for a 13 | * WebSocket register where WebSocket actors can subscribe and unsubscribe. 14 | * 15 | * A new NifDispatcherActor is created by the [[PcapInitializer]]. 16 | * 17 | * Created by Kristian Lange on 2017. 18 | */ 19 | object NifDispatcherActor { 20 | 21 | def props(nifName: String) = Props(new NifDispatcherActor(nifName)) 22 | 23 | case object Subscribe 24 | 25 | case object Unsubscribe 26 | 27 | } 28 | 29 | class NifDispatcherActor(nifName: String) extends Actor { 30 | 31 | private val logger: Logger = Logger(this.getClass) 32 | 33 | private val webSocketRegister: ArrayBuffer[ActorRef] = ArrayBuffer() 34 | 35 | def receive = { 36 | case msg: JsObject => 37 | webSocketRegister.foreach(ws => ws ! msg) 38 | case Subscribe => 39 | webSocketRegister += sender 40 | logger.info("subscribed WebSocket to network interface " + nifName) 41 | case Unsubscribe => 42 | webSocketRegister -= sender 43 | logger.info("unsubscribed WebSocket from network interface " + nifName) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/services/PacketToJsonTransfer.scala: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import java.sql.Timestamp 4 | 5 | import org.pcap4j.packet._ 6 | import play.api.libs.json.{JsString, JsValue, Json} 7 | 8 | /** 9 | * Utility class that extracts data from network packets and puts them into JSON 10 | * 11 | * Created by Kristian Lange on 2017. 12 | */ 13 | object PacketToJsonTransfer { 14 | 15 | def packageToJson(packet: Packet, timestamp: Timestamp): JsValue = { 16 | var json = Json.obj() 17 | json += ("timestamp", JsString(timestamp.toString)) 18 | if (packet.contains(classOf[EthernetPacket])) 19 | json += ("ethernet", getEthernetPacketMetrics(packet.get(classOf[EthernetPacket]))) 20 | if (packet.contains(classOf[ArpPacket])) 21 | json += ("arp", getArpPacketMetrics(packet.get(classOf[ArpPacket]))) 22 | if (packet.contains(classOf[IpPacket])) 23 | json += ("ip", getIpPacketMetrics(packet.get(classOf[IpPacket]))) 24 | if (packet.contains(classOf[TcpPacket])) 25 | json += ("tcp", getTcpPacketMetrics(packet.get(classOf[TcpPacket]))) 26 | if (packet.contains(classOf[UdpPacket])) 27 | json += ("udp", getUdpPacketMetrics(packet.get(classOf[UdpPacket]))) 28 | json 29 | } 30 | 31 | private def getEthernetPacketMetrics(ethernetPacket: EthernetPacket) = Json.obj( 32 | "macSrcAddr" -> ethernetPacket.getHeader.getSrcAddr.toString, 33 | "macDstAddr" -> ethernetPacket.getHeader.getDstAddr.toString, 34 | "etherType" -> ethernetPacket.getHeader.getType.name 35 | ) 36 | 37 | private def getArpPacketMetrics(arpPacket: ArpPacket) = Json.obj( 38 | "srcHardwareAddr" -> arpPacket.getHeader.getSrcHardwareAddr.toString, 39 | "srcProtocolAddr" -> arpPacket.getHeader.getSrcProtocolAddr.toString, 40 | "dstHardwareAddr" -> arpPacket.getHeader.getDstHardwareAddr.toString, 41 | "dstProtocolAddr" -> arpPacket.getHeader.getDstProtocolAddr.toString, 42 | "hardwareType" -> arpPacket.getHeader.getHardwareType.name, 43 | "operation" -> arpPacket.getHeader.getOperation.name, 44 | "protocolType" -> arpPacket.getHeader.getProtocolType.name 45 | ) 46 | 47 | private def getIpPacketMetrics(ipPacket: IpPacket) = Json.obj( 48 | "srcAddr" -> ipPacket.getHeader.getSrcAddr.getHostAddress, 49 | "dstAddr" -> ipPacket.getHeader.getDstAddr.getHostAddress, 50 | "dstIsMc" -> ipPacket.getHeader.getDstAddr.isMulticastAddress, 51 | "protocol" -> ipPacket.getHeader.getProtocol.name, 52 | "version" -> ipPacket.getHeader.getVersion.name 53 | ) 54 | 55 | private def getTcpPacketMetrics(tcpPacket: TcpPacket) = Json.obj( 56 | "srcPort" -> tcpPacket.getHeader.getSrcPort.valueAsInt, 57 | "srcPortName" -> tcpPacket.getHeader.getSrcPort.name, 58 | "dstPort" -> tcpPacket.getHeader.getDstPort.valueAsInt, 59 | "dstPortName" -> tcpPacket.getHeader.getDstPort.name 60 | ) 61 | 62 | private def getUdpPacketMetrics(udpPacket: UdpPacket) = Json.obj( 63 | "srcPort" -> udpPacket.getHeader.getSrcPort.valueAsInt, 64 | "srcPortName" -> udpPacket.getHeader.getSrcPort.name, 65 | "dstPort" -> udpPacket.getHeader.getDstPort.valueAsInt, 66 | "dstPortName" -> udpPacket.getHeader.getDstPort.name 67 | ) 68 | 69 | } 70 | -------------------------------------------------------------------------------- /app/services/PcapInitializer.scala: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import java.io.EOFException 4 | import javax.inject.{Inject, Singleton} 5 | 6 | import akka.actor.{ActorRef, ActorSystem} 7 | import org.pcap4j.core._ 8 | import org.pcap4j.packet.{IpPacket, Packet, TcpPacket} 9 | import play.api.{Configuration, Logger} 10 | import play.api.inject.ApplicationLifecycle 11 | 12 | import scala.collection.mutable 13 | import scala.concurrent.ExecutionContext.Implicits.global 14 | import scala.concurrent.{Future, TimeoutException} 15 | 16 | /** 17 | * Using Pcap4J (https://github.com/kaitoy/pcap4j) to access network 18 | * interfaces and forward packages' data (metrics) to the appropriate 19 | * [[NifDispatcherActor]]. 20 | * 21 | * Created by Kristian Lange on 2017. 22 | */ 23 | @Singleton 24 | class PcapInitializer @Inject()(implicit actorSystem: ActorSystem, 25 | configuration: Configuration, 26 | lifecycle: ApplicationLifecycle) { 27 | 28 | private val logger = Logger(this.getClass) 29 | 30 | /** 31 | * If false net-glimpse filters out its own traffic 32 | * (specified in application.conf) 33 | */ 34 | private val skipOwnTraffic = configuration.get[Boolean]("skipOwnTraffic") 35 | 36 | /** 37 | * IP / host the Play framework is bound to (default 0.0.0.0) 38 | */ 39 | private val httpAddress = configuration.get[String]("play.server.http.address") 40 | 41 | /** 42 | * Port the Play framework is bound to (default 9000) 43 | */ 44 | private val httpPort = configuration.get[Int]("play.server.http.port") 45 | 46 | /** 47 | * Specifies the portion of the network packet to capture 48 | * https://serverfault.com/questions/253613 49 | */ 50 | private val snaplen = configuration.get[Int]("snaplen") 51 | 52 | /** 53 | * Map: network interface name -> actor reference to [[NifDispatcherActor]] 54 | */ 55 | val nifDispatcherMap: mutable.HashMap[String, ActorRef] = mutable.HashMap() 56 | 57 | /** 58 | * @param nifName Name of the network interface to intercept 59 | * @return Returns an Option to the actor reference of the [[NifDispatcherActor]] that handles 60 | * this nif 61 | */ 62 | def getNifDispatcher(nifName: String): Option[ActorRef] = { 63 | 64 | // If it dispatcher exists already just return it 65 | if (nifDispatcherMap.contains(nifName)) return Some(nifDispatcherMap(nifName)) 66 | 67 | // Get new dispatcher actor for this nif 68 | val pcapDispatcherActorRef = actorSystem.actorOf(NifDispatcherActor.props(nifName)) 69 | nifDispatcherMap += (nifName -> pcapDispatcherActorRef) 70 | 71 | // Open pcap 72 | val pcapHandle = openPcap(nifName, snaplen).getOrElse(return None) 73 | 74 | // Send packets to dispatcher (do it async in parallel) 75 | Future { 76 | try { 77 | pcapToDispatcher(pcapHandle, nifName) 78 | } catch { 79 | case e: NotOpenException => // Do nothing 80 | } 81 | } 82 | 83 | // Close pcap when application stops 84 | lifecycle.addStopHook(() => 85 | if (pcapHandle.isOpen) Future.successful(pcapHandle.close()) 86 | else Future.successful({}) 87 | ) 88 | 89 | Some(pcapDispatcherActorRef) 90 | } 91 | 92 | private def openPcap(nifName: String, snaplen: Int): Option[PcapHandle] = { 93 | try { 94 | val nif = Pcaps.getDevByName(nifName) 95 | if (nif == null) { 96 | Logger.error("Couldn't open network interface " + nifName) 97 | return None 98 | } 99 | logger.info("Forward network traffic from " + nif.getName + "(" + nif.getAddresses + ")") 100 | Some(nif.openLive(snaplen, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, 10)) 101 | } catch { 102 | case e: PcapNativeException => 103 | if (nifName == "empty") logger.error("No network interface specified!") 104 | logger.error("Couldn't open network interface " + nifName, e) 105 | None 106 | } 107 | } 108 | 109 | private def pcapToDispatcher(pcapHandle: PcapHandle, nifName: String) { 110 | while (true) { 111 | try { 112 | val packet = pcapHandle.getNextPacketEx 113 | if (packet != null && (!skipOwnTraffic || !isOurPacket(packet))) { 114 | val json = PacketToJsonTransfer.packageToJson(packet, pcapHandle.getTimestamp) 115 | nifDispatcherMap(nifName) ! json 116 | } 117 | } catch { 118 | case _: PcapNativeException | _: EOFException | _: TimeoutException => // Just ignore and continue 119 | } 120 | } 121 | } 122 | 123 | /** 124 | * Check if this packet is one of our WebSocket packets: our WebSocket 125 | * packets are TCP packets that originate from our IP and port - or are 126 | * destined to our IP and port 127 | */ 128 | private def isOurPacket(packet: Packet): Boolean = { 129 | if (!packet.contains(classOf[IpPacket]) || !packet.contains(classOf[TcpPacket])) return false 130 | 131 | val ipHeader = packet.get(classOf[IpPacket]).getHeader 132 | val srcAddr = ipHeader.getSrcAddr.getHostAddress 133 | val dstAddr = ipHeader.getDstAddr.getHostAddress 134 | val tpcHeader = packet.get(classOf[TcpPacket]).getHeader 135 | val tcpSrcPort = tpcHeader.getSrcPort.valueAsInt 136 | val tcpDstPort = tpcHeader.getDstPort.valueAsInt 137 | 138 | // Check if our IP and port are equal to the packet's source IP and port 139 | // If our IP is 0.0.0.0 we listen on every IP 140 | if ((httpAddress == "0.0.0.0" || httpAddress == srcAddr) && httpPort == tcpSrcPort) return true 141 | 142 | // Check if our IP and port are equal to the packet's destination IP and port 143 | // If our IP is 0.0.0.0 we listen on every IP 144 | if ((httpAddress == "0.0.0.0" || httpAddress == dstAddr) && httpPort == tcpDstPort) return true 145 | 146 | false 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /app/services/WebSocketActor.scala: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import akka.actor._ 4 | import play.api.libs.json.JsObject 5 | 6 | /** 7 | * Akka actor handling a single WebSocket. Sends messages to the WebSocket's 8 | * out, subscribes and unsubscribes itself to/from the [[NifDispatcherActor]]. 9 | * 10 | * Created by Kristian Lange on 2017. 11 | */ 12 | object WebSocketActor { 13 | def props(out: ActorRef, nifDispatcherActor: ActorRef) = 14 | Props(new WebSocketActor(out, nifDispatcherActor)) 15 | } 16 | 17 | class WebSocketActor(out: ActorRef, nifDispatcherActor: ActorRef) extends Actor { 18 | 19 | override def preStart() = { 20 | nifDispatcherActor ! NifDispatcherActor.Subscribe 21 | } 22 | 23 | override def postStop() = { 24 | nifDispatcherActor ! NifDispatcherActor.Unsubscribe 25 | } 26 | 27 | def receive = { 28 | case msg: JsObject => 29 | out ! msg 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := """net-glimpse""" 2 | 3 | version := "1.8" 4 | 5 | lazy val root = (project in file(".")).enablePlugins(PlayScala) 6 | 7 | scalaVersion := "2.12.2" 8 | 9 | libraryDependencies ++= Seq( 10 | guice, 11 | "com.typesafe.play" %% "play-json" % "2.6.0", 12 | "org.pcap4j" % "pcap4j-core" % "1.7.1", 13 | "org.pcap4j" % "pcap4j-packetfactory-static" % "1.7.1" 14 | ) 15 | 16 | // No source docs in distribution 17 | sources in (Compile, doc) := Seq.empty 18 | 19 | // No source docs in distribution 20 | publishArtifact in (Compile, packageDoc) := false 21 | 22 | // Don't include Java docs to distribution 23 | mappings in Universal := (mappings in Universal).value filter { 24 | case (file, path) => !path.contains("share/doc") 25 | } 26 | -------------------------------------------------------------------------------- /conf/application.conf: -------------------------------------------------------------------------------- 1 | # Defines which network interface to listen to in case there is none specified 2 | # in the URL query string. 3 | # It uses the run parameter '-Dnif' (e.g. -Dnif=wlp3s0). 4 | nif="empty" 5 | nif=${?nif} 6 | 7 | # If true net-glimpse filters out its own traffic. 8 | # It uses the run parameter '-DskipOwnTraffic' (e.g. -DskipOwnTraffic=false) 9 | skipOwnTraffic=true 10 | skipOwnTraffic=${?skipOwnTraffic} 11 | 12 | # snaplen specifies the portion in byte of the network packet to capture 13 | # It uses the run parameter '-Dsnaplen' (e.g. -Dsnaplen=96) 14 | # https://serverfault.com/questions/253613 15 | snaplen=128 16 | snaplen=${?snaplen} 17 | 18 | # Where to find asset files 19 | # https://www.playframework.com/documentation/2.6.x/AssetsOverview 20 | play.assets.path="/public" 21 | play.assets.urlPrefix="/assets" 22 | 23 | # https://www.playframework.com/documentation/2.6.x/SecurityHeaders 24 | play.filters.headers.contentTypeOptions=null 25 | play.filters.headers.contentSecurityPolicy=null 26 | play.filters.disabled += play.filters.hosts.AllowedHostsFilter 27 | 28 | # It's not necessary to change Play's secret since net-glimpse doesn't use 29 | # session cookies or encryption 30 | # https://www.playframework.com/documentation/2.6.x/ApplicationSecret 31 | play.http.secret.key="zj97lcqp896relv8dsZdAsGeTivm72pq3p52nLfdoa5DCfuKjGoc4Rj" 32 | -------------------------------------------------------------------------------- /conf/glimpse.conf: -------------------------------------------------------------------------------- 1 | /* 2 | net-glimpse configuration 3 | */ 4 | 5 | 6 | /* 7 | ################################### 8 | ### Visualisation configuration ### 9 | ################################### 10 | */ 11 | 12 | // Config for Ethernet visualization 13 | var configEther = { 14 | 'canvas': { 15 | 'width': 0, // Will be set dynamically during start-up (in px) 16 | 'height': 0, // Will be set dynamically during start-up (in px) 17 | 'margin': 20, // Canvas margin (so nodes keep away from the borders) (in px) 18 | 'fps': 30, // Frames per seconds 19 | 'backgroundColor': 255 // Only shades of gray 20 | }, 21 | 'physics': { // All parameters for toxiclib library 22 | 'drag': 0.5, // Physics' drag 23 | 'nodeRepulsion': 0.012, // Repulsion between two nodes 24 | 'nodeRepulsionRange': 0.2, // How does the repulsion reach 25 | 'springRestLength': 0.2, // Spring length in rest between two nodes 26 | 'springStrength': 0.1 // Spring strength between two nodes 27 | }, 28 | 'graph': { 29 | 'cleaningIntervall': 2000, // Removing old nodes and edges every x ms 30 | 'maxAge': 10000 // After how many ms will a node be removed if it haven't had an incoming package in the meantime 31 | }, 32 | 'edge': { 33 | 'width': 4, // Minimum edge line width (in px) 34 | 'tickWidth': 6, // Width after a packet was sent (=tick) (in px) 35 | 'widthStep': 0.2, // Each frame reduces the width by this value until the normal width is reached (in px) 36 | 'weightMax': 20, // Max weight value (weight is added to the width) (in px) 37 | 'weightStepIncr': 0.05, // Each sent packet increases the weight by this value until weightMax is reached (in px) 38 | 'weightStepDecr': 0.1, // Each frame reduces the weight by this value until normal width is reached (in px) 39 | 'showText': true, // Show edge annotation 40 | 'textSize': 12, // Font size in px 41 | 'arrowLength': 30, // Arrow length of each edge (in px) 42 | 'arrowWidth': 7, // Arrow width of each node (in px) 43 | 'transparency': 150, // Transparency in resting state 44 | 'transparencyStep': 3 // Each frame reduces the transparency by this value until the normal value is reached 45 | }, 46 | 'node': { 47 | 'width': 20, // Minimum node circle width (in px) 48 | 'tickWidth': 40, // Width after a packet was sent (in px) 49 | 'showText': true, // Show node annotation 50 | 'textSize': 14, // Font size in px 51 | 'border': 2, // Node border line thickness (in px) 52 | 'transparency': 150, // Transparency in resting state 53 | 'transparencyStep': 3 // How much each tick the transparency is reduced until it returns to its normal value 54 | } 55 | } 56 | 57 | // Config for IP visualization 58 | var configIp = { 59 | 'canvas': { 60 | 'width': 0, // Will be set dynamically (in px) 61 | 'height': 0, // Will be set dynamically (in px) 62 | 'margin': 20, // Canvas margin (so nodes keep away from the borders) (in px) 63 | 'fps': 30, // Frames per seconds 64 | 'backgroundColor': 255 // Only shades of gray 65 | }, 66 | 'physics': { // All parameters for toxiclib library 67 | 'drag': 0.5, // Physics' drag 68 | 'nodeRepulsion': 0.012, // Repulsion between two nodes 69 | 'nodeRepulsionRange': 0.2, // How does the repulsion reach 70 | 'springRestLength': 0.2, // Spring length in rest between two nodes 71 | 'springStrength': 0.1 // Spring strength between two nodes 72 | }, 73 | 'graph': { 74 | 'cleaningIntervall': 2000, // Removing old nodes and edges every x ms 75 | 'maxAge': 10000 // After how many ms will a node be removed if it haven't had an incoming package in the meantime 76 | }, 77 | 'edge': { 78 | 'width': 4, // Minimum edge line width (in px) 79 | 'tickWidth': 6, // Width after a packet was sent (=tick) (in px) 80 | 'widthStep': 0.2, // Each frame reduces the width by this value until the normal width is reached (in px) 81 | 'weightMax': 20, // Max weight value (weight is added to the width) (in px) 82 | 'weightStepIncr': 0.05, // Each sent packet increases the weight by this value until weightMax is reached (in px) 83 | 'weightStepDecr': 0.1, // Each frame reduces the weight by this value until normal width is reached (in px) 84 | 'showText': true, // Show edge annotation 85 | 'showPortNames': true, // Show port names instead of their numbers 86 | 'showWellKnownPorts': true, // Show well-known ports 0 - 1023 87 | 'showRegisteredPorts': true, // Show registered ports 1024 - 49151 88 | 'showOtherPorts': false, // Show other ports 49151 - 65535 89 | 'textSize': 12, // Font size in px 90 | 'arrowLength': 30, // Arrow length of each edge (in px) 91 | 'arrowWidth': 7, // Arrow width of each node (in px) 92 | 'transparency': 150, // Transparency in resting state 93 | 'transparencyStep': 3 // Each frame reduces the transparency by this value until the normal value is reached 94 | }, 95 | 'node': { 96 | 'width': 20, // Minimum node circle width (in px) 97 | 'tickWidth': 40, // Width after a packet was sent (in px) 98 | 'showText': true, // Show node annotation 99 | 'textSize': 14, // Font size in px 100 | 'border': 2, // Node border line thickness (in px) 101 | 'transparency': 150, // Transparency in resting state 102 | 'transparencyStep': 3 // How much each tick the transparency is reduced until it returns to its normal value 103 | } 104 | } 105 | 106 | 107 | /* 108 | ######################################### 109 | ### Blacklist, whitelist of addresses ### 110 | ######################################### 111 | */ 112 | 113 | // Blacklist for Ethernet: array of JavaScript regex expressions 114 | // The expressions are evaluated on the src and dst MAC addresses. 115 | // A packet is filtered out if the src OR dst address is in the blacklist - 116 | // or in other words, a packet is only shown if both, the src AND the dst address, are NOT in the blacklist. 117 | // Ethernet blacklist and whitelist are independent from their IP counterparts. This means that even if an IP is filtered out its MAC can still being displayed. 118 | // e.g. var etherBlacklist = [/33:33:.*/]; filters out all packets where at least one MAC (src or dst) starts with '33:33:' 119 | // e.g. var etherBlacklist = [/00:00:00.*/, /00:00:0[3-8]{1}.*/, /00:55:00.*/]; filters out all packets where at least one MAC (src or dst) is from XEROX 120 | var etherBlacklist = []; 121 | 122 | // Whitelist for Ethernet: array of JavaScript regex expressions 123 | // The expressions are evaluated on the src and dst MAC addresses. 124 | // A packet is shown if the src OR dst address is in the whitelist. 125 | // An empty whitelist means that all MACs are allowed (equals '[/.*/]'). 126 | // Ethernet blacklist and whitelist are independent from their IP counterparts. This means that even if an IP is filtered out its MAC can still being displayed. 127 | // e.g. var etherWhitelist = [/10:.*/]; will only show packets where at least one MAC (src or dst) starts with '10:' 128 | // e.g. var etherWhitelist = [/00:00:00.*/, /00:00:0[3-8]{1}.*/, /00:55:00.*/]; will show only packets where at least one MAC (src or dst) is from XEROX 129 | var etherWhitelist = []; 130 | 131 | // Blacklist for Internet: array of JavaScript regex expressions 132 | // The expressions are evaluated on the src and dst addresses (format is 'IP:port'). 133 | // A packet is filtered out if the src OR dst address is in the blacklist - 134 | // or in other words, a packet is only shown if both, the src AND the dst address, are NOT in the blacklist. 135 | // IP blacklist and whitelist are independent from their Ethernet counterparts. This means that even if an MAC is filtered out its IP can still being displayed. 136 | // e.g. var ipBlacklist = [/172..*/, /192..*/]; filters out all packets where at least one IP (src or dst) start with '172.' or '192.' 137 | // e.g. var ipBlacklist = [/:4001$/, /:4002$/]; filters out all packets where at least one port (src or dst) is 4001 or 4002 138 | var ipBlacklist = []; 139 | 140 | // Whitelist for Internet: array of JavaScript regex expressions 141 | // The expressions are evaluated on the src and dst addresses (format is 'IP:port'). 142 | // A packet is shown if the src OR dst address is in the whitelist. 143 | // An empty whitelist means that all IPs are allowed (equals '[/.*/]'). 144 | // IP blacklist and whitelist are independent from their Ethernet counterparts. This means that even if an MAC is filtered out its IP can still being displayed. 145 | // e.g. var ipWhitelist = [/172..*/, /192..*/]; will show only packets where at least one IP (src or dst) starts with '172.' or '192.' 146 | // e.g. var ipWhitelist = [/:\d{0,3}$/]; will show only packets where at least one port (src or dst) has at most 3 digits 147 | var ipWhitelist = []; 148 | 149 | 150 | /* 151 | ############################################## 152 | ### Colors and names of edges and glossary ### 153 | ############################################## 154 | */ 155 | 156 | // Colors and names for Ethernet packets (name is used as edge annotation, desc as description in the glossary, and color as edge color) 157 | // You can extend this list with your own 158 | var etherTypeConfig = { 159 | "IPv4": { 160 | "color": [255, 0, 0], 161 | "name": "IPv4", 162 | "desc": "IPv4" 163 | }, 164 | "IPv6": { 165 | "color": [0, 255, 0], 166 | "name": "IPv6", 167 | "desc": "IPv6" 168 | }, 169 | "ARP": { 170 | "color": [00, 255, 255], 171 | "name": "ARP", 172 | "desc": "ARP" 173 | }, 174 | "RARP": { 175 | "color": [0, 0, 255], 176 | "name": "RARP", 177 | "desc": "RARP" 178 | }, 179 | "PPP": { 180 | "color": [255, 255, 0], 181 | "name": "PPP", 182 | "desc": "PPP" 183 | }, 184 | "PPPoE Discovery Stage": { 185 | "color": [255, 255, 0], 186 | "name": "PPPoE Discovery", 187 | "desc": "PPPoE Discovery Stage" 188 | }, 189 | "PPPoE Session Stage": { 190 | "color": [255, 255, 0], 191 | "name": "PPPoE Session", 192 | "desc": "PPPoE Session Stage" 193 | }, 194 | "MPLS": { 195 | "color": [255, 100, 0], 196 | "name": "MPLS", 197 | "desc": "MPLS" 198 | }, 199 | "IEEE 802.1Q VLAN-tagged frames": { 200 | "color": [255, 0, 255], 201 | "name": "VLAN", 202 | "desc": "IEEE 802.1Q VLAN-tagged frames" 203 | }, 204 | "Appletalk": { 205 | "color": [50, 160, 10], 206 | "name": "Appletalk", 207 | "desc": "Appletalk" 208 | }, 209 | "unknown": { 210 | "color": [0, 0, 0], 211 | "name": "unknown", 212 | "desc": "unknown" 213 | } 214 | } 215 | 216 | // Colors and names for IP packets (name is used as edge annotation, desc as description in the glossary, and color as edge color) 217 | // You can extend this list with your own 218 | var ipPacketConfig = { 219 | "7": { 220 | "name": "Echo", 221 | "desc": "Echo - port 7, UDP or TCP", 222 | "color": [255, 0, 0] 223 | }, 224 | "13": { 225 | "name": "Daytime", 226 | "desc": "Daytime - port 13, UDP or TCP", 227 | "color": [255, 0, 0] 228 | }, 229 | "20": { 230 | "name": "FTP", 231 | "desc": "FTP data - port 20, TCP", 232 | "color": [255, 128, 0] 233 | }, 234 | "21": { 235 | "name": "FTP", 236 | "desc": "FTP control - port 21, TCP", 237 | "color": [255, 128, 0] 238 | }, 239 | "22": { 240 | "name": "SSH", 241 | "desc": "SSH - port 22, TCP", 242 | "color": [255, 255, 0] 243 | }, 244 | "23": { 245 | "name": "telnet", 246 | "desc": "telnet - port 23, TCP", 247 | "color": [0, 191, 255] 248 | }, 249 | "25": { 250 | "name": "SMTP", 251 | "desc": "SMTP (Simple Mail Transfer Protocol) - port 25, TCP", 252 | "color": [128, 255, 0] 253 | }, 254 | "42": { 255 | "name": "Host Name Server", 256 | "desc": "Host Name Server - port 42, UDP or TCP", 257 | "color": [255, 0, 0] 258 | }, 259 | "43": { 260 | "name": "Whois", 261 | "desc": "Whois - port 43, TCP", 262 | "color": [255, 0, 0] 263 | }, 264 | "53": { 265 | "name": "DNS", 266 | "desc": "DNS (Domain Name Server) - port 53, UDP or TCP", 267 | "color": [0, 255, 191] 268 | }, 269 | "67": { 270 | "name": "DHCP", 271 | "desc": "DHCP / Bootstrap Server - port 67, UDP (or TCP)", 272 | "color": [255, 0, 212] 273 | }, 274 | "68": { 275 | "name": "DHCP", 276 | "desc": "DHCP / Bootstrap Client - port 68, UDP (or TCP)", 277 | "color": [255, 0, 212] 278 | }, 279 | "69": { 280 | "name": "TFTP", 281 | "desc": "TFTP (Trivial File Transfer Protocol) - port 69, UDP", 282 | "color": [255, 128, 0] 283 | }, 284 | "70": { 285 | "name": "Gopher", 286 | "desc": "Gopher - port 70, TCP", 287 | "color": [255, 0, 0] 288 | }, 289 | "80": { 290 | "name": "HTTP", 291 | "desc": "HTTP - port 80, TCP", 292 | "color": [64, 64, 255] 293 | }, 294 | "88": { 295 | "name": "Kerberos", 296 | "desc": "Kerberos - port 88, UDP or TCP", 297 | "color": [66, 244, 215] 298 | }, 299 | "109": { 300 | "name": "POP v2", 301 | "desc": "POP v2 - port 109, TCP", 302 | "color": [25, 0, 255] 303 | }, 304 | "110": { 305 | "name": "POP", 306 | "desc": "POP v3 - port 110, TCP", 307 | "color": [25, 0, 255] 308 | }, 309 | "113": { 310 | "name": "Authentication Sevice", 311 | "desc": "Authentication Sevice - port 113, TCP", 312 | "color": [170, 0, 255] 313 | }, 314 | "123": { 315 | "name": "NTP", 316 | "desc": "NTP (Network Time Protocol) - port 123, UDP", 317 | "color": [255, 0, 255] 318 | }, 319 | "137": { 320 | "name": "NetBIOS", 321 | "desc": "NetBIOS - port 137, UDP or TCP", 322 | "color": [93, 0, 255] 323 | }, 324 | "138": { 325 | "name": "NetBIOS", 326 | "desc": "NetBIOS - port 138, UDP or TCP", 327 | "color": [93, 0, 255] 328 | }, 329 | "139": { 330 | "name": "NetBIOS", 331 | "desc": "NetBIOS - port 139, UDP or TCP", 332 | "color": [93, 0, 255] 333 | }, 334 | "143": { 335 | "name": "IMAP", 336 | "desc": "IMAP (Internet Message Access Protocol) - port 143, TCP", 337 | "color": [150, 0, 20] 338 | }, 339 | "161": { 340 | "name": "SNMP", 341 | "desc": "SNMP (Simple Network Management Protocol) - port 161, UDP or TCP", 342 | "color": [25, 0, 255] 343 | }, 344 | "162": { 345 | "name": "SNMP", 346 | "desc": "SNMP (Simple Network Management Protocol) - port 162, UDP or TCP", 347 | "color": [25, 0, 255] 348 | }, 349 | "194": { 350 | "name": "IRC", 351 | "desc": "IRC (Internet Relay Chat) - port 194, TCP", 352 | "color": [0, 55, 255] 353 | }, 354 | "213": { 355 | "name": "IPX over IP", 356 | "desc": "IPX over IP - port 213, UDP", 357 | "color": [255, 0, 0] 358 | }, 359 | "389": { 360 | "name": "LDAP", 361 | "desc": "LDAP (Lightweight Directory Access Protocol) - port 389, UDP or TCP", 362 | "color": [0, 135, 255] 363 | }, 364 | "443": { 365 | "name": "HTTPS", 366 | "desc": "HTTPS - port 443, TCP", 367 | "color": [0, 0, 200] 368 | }, 369 | "445": { 370 | "name": "CIFS", 371 | "desc": "Microsoft CIFS - port 445, UDP or TCP", 372 | "color": [128, 244, 215] 373 | }, 374 | "464": { 375 | "name": "Kerberos", 376 | "desc": "Kerberos (v5) - port 464, UDP or TCP", 377 | "color": [66, 244, 215] 378 | }, 379 | "500": { 380 | "name": "IPSec", 381 | "desc": "IPSec (Internet Key Exchange) - port 500, UDP", 382 | "color": [255, 0, 0] 383 | }, 384 | "525": { 385 | "name": "Timeserver", 386 | "desc": "Timeserver - port 525, UDP", 387 | "color": [255, 0, 0] 388 | }, 389 | "530": { 390 | "name": "RPC", 391 | "desc": "RPC (Remote Procedure Call) - port 530, UDP or TCP", 392 | "color": [0, 169, 255] 393 | }, 394 | "531": { 395 | "name": "IRC chat", 396 | "desc": "IRC chat - port 531, TCP", 397 | "color": [0, 55, 255] 398 | }, 399 | "546": { 400 | "name": "DHCPv6", 401 | "desc": "DHCPv6 client - port 546, UDP", 402 | "color": [255, 0, 212] 403 | }, 404 | "547": { 405 | "name": "DHCPv6", 406 | "desc": "DHCPv6 server - port 547, UDP", 407 | "color": [255, 0, 212] 408 | }, 409 | "636": { 410 | "name": "LDAPS", 411 | "desc": "LDAPS (Lightweight Directory Access Protocol over TLS/SSL) - port 636, UDP or TCP", 412 | "color": [0, 135, 255] 413 | }, 414 | "989": { 415 | "name": "FTPS", 416 | "desc": "FTPS (FTP over TLS/SSL) control - port 989, TCP", 417 | "color": [255, 128, 0] 418 | }, 419 | "990": { 420 | "name": "FTPS", 421 | "desc": "FTPS (FTP over TLS/SSL) data - port 990, TCP", 422 | "color": [255, 128, 0] 423 | }, 424 | "1194": { 425 | "name": "OpenVPN", 426 | "desc": "OpenVPN - port 1194, UDP or TCP", 427 | "color": [255, 255, 0] 428 | }, 429 | "1433": { 430 | "name": "Microsoft SQL", 431 | "desc": "Microsoft SQL - port 1433, UDP or TCP", 432 | "color": [0, 255, 0] 433 | }, 434 | "1434": { 435 | "name": "Microsoft SQL", 436 | "desc": "Microsoft SQL - port 1434, UDP or TCP", 437 | "color": [0, 255, 0] 438 | }, 439 | "1900": { 440 | "name": "SSDP", 441 | "desc": "SSDP (Simple Service Discovery Protocol) - port 1900, UDP or TCP", 442 | "color": [66, 244, 215] 443 | }, 444 | "2483": { 445 | "name": "Oracle DB", 446 | "desc": "Oracle DB - port 2483, UDP or TCP", 447 | "color": [0, 255, 0] 448 | }, 449 | "2484": { 450 | "name": "Oracle DB", 451 | "desc": "Oracle DB - port 2484, UDP or TCP", 452 | "color": [0, 255, 0] 453 | }, 454 | "3074": { 455 | "name": "XBOX Live", 456 | "desc": "XBOX Live - port 3074, UDP or TCP", 457 | "color": [255, 0, 0] 458 | }, 459 | "3306": { 460 | "name": "MySQL", 461 | "desc": "MySQL - port 3306, UDP or TCP", 462 | "color": [0, 255, 0] 463 | }, 464 | "5000": { 465 | "name": "UPnP", 466 | "desc": "UPnP - port 5000, UDP or TCP", 467 | "color": [66, 244, 215] 468 | }, 469 | "5222": { 470 | "name": "XMPP/Jabber", 471 | "desc": "XMPP/Jabber - port 5222, UDP or TCP", 472 | "color": [0, 55, 255] 473 | }, 474 | "5223": { 475 | "name": "XMPP/Jabber", 476 | "desc": "XMPP/Jabber - port 5223, UDP or TCP", 477 | "color": [0, 55, 255] 478 | }, 479 | "5353": { 480 | "name": "mDNS", 481 | "desc": "Multicast DNS (mDNS) - port 5353, UDP", 482 | "color": [0, 255, 191] 483 | }, 484 | "5432": { 485 | "name": "PostgreSQL", 486 | "desc": "PostgreSQL - port 5432, UDP or TCP", 487 | "color": [0, 255, 0] 488 | }, 489 | "5500": { 490 | "name": "VNC Server", 491 | "desc": "VNC Server - port 5500, UDP or TCP", 492 | "color": [255, 0, 0] 493 | }, 494 | "5800": { 495 | "name": "VNC over HTTP", 496 | "desc": "VNC over HTTP - port 5800, UDP or TCP", 497 | "color": [255, 0, 0] 498 | }, 499 | "6000": { 500 | "name": "X11", 501 | "desc": "X11 - port 6000, UDP or TCP", 502 | "color": [255, 255, 0] 503 | }, 504 | "6001": { 505 | "name": "X11", 506 | "desc": "X11 - port 6001, UDP or TCP", 507 | "color": [255, 255, 0] 508 | }, 509 | "6665": { 510 | "name": "IRC", 511 | "desc": "IRC - port 6665, UDP or TCP", 512 | "color": [0, 55, 255] 513 | }, 514 | "6666": { 515 | "name": "IRC", 516 | "desc": "IRC - port 6666, UDP or TCP", 517 | "color": [0, 55, 255] 518 | }, 519 | "6667": { 520 | "name": "IRC", 521 | "desc": "IRC - port 6667, UDP or TCP", 522 | "color": [0, 55, 255] 523 | }, 524 | "6668": { 525 | "name": "IRC", 526 | "desc": "IRC - port 6668, UDP or TCP", 527 | "color": [0, 55, 255] 528 | }, 529 | "6669": { 530 | "name": "IRC", 531 | "desc": "IRC - port 6669, UDP or TCP", 532 | "color": [0, 55, 255] 533 | }, 534 | "6679": { 535 | "name": "IRC over SSL", 536 | "desc": "IRC over SSL - port 6679, UDP or TCP", 537 | "color": [0, 55, 255] 538 | }, 539 | "8080": { 540 | "name": "HTTP Proxy", 541 | "desc": "HTTP Proxy (non standard) - port 8080, TCP", 542 | "color": [64, 64, 255] 543 | }, 544 | "8443": { 545 | "name": "HTTPS Proxy", 546 | "desc": "HTTPS Proxy (non standard) - port 8443, TCP", 547 | "color": [0, 0, 200] 548 | }, 549 | "9000": { 550 | "name": "9000", 551 | "desc": "HTTP (Play Framework) - port 9000, TCP", 552 | "color": [64, 64, 255] 553 | }, 554 | "9443": { 555 | "name": "9443", 556 | "desc": "HTTPS (Play Framework) - port 9443, TCP", 557 | "color": [0, 0, 200] 558 | }, 559 | "9800": { 560 | "name": "WebDAV", 561 | "desc": "WebDAV - port 9800, TCP", 562 | "color": [66, 244, 215] 563 | }, 564 | "unknown": { 565 | "name": "unknown", 566 | "desc": "unknown", 567 | "color": [0, 0, 0] 568 | } 569 | } -------------------------------------------------------------------------------- /conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${application.home:-.}/logs/application.log 8 | 9 | %date [%level] from %logger in %thread - %message%n%xException 10 | 11 | 12 | 13 | 14 | 15 | %coloredLevel %logger{15} - %message%n%xException{10} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | GET / controllers.Assets.at(file="index.html") 6 | GET /glimpse controllers.Assets.at(file="glimpse.html") 7 | GET /ipglimpse controllers.Assets.at(file="ipglimpse.html") 8 | GET /etherglimpse controllers.Assets.at(file="etherglimpse.html") 9 | GET /netdata controllers.Home.netdata(nif ?= null) 10 | 11 | GET /assets/glimpse.conf controllers.ExtAssets.at(filePath="/conf/glimpse.conf") 12 | 13 | # Map static resources from the /public folder to the /assets URL path 14 | GET /assets/*file controllers.Assets.at(file) 15 | -------------------------------------------------------------------------------- /docs/ng-logo2_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo2_l.png -------------------------------------------------------------------------------- /docs/ng-logo2_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo2_sm.png -------------------------------------------------------------------------------- /docs/ng-logo2_xl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo2_xl.png -------------------------------------------------------------------------------- /docs/ng-logo3_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo3_l.png -------------------------------------------------------------------------------- /docs/ng-logo3_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo3_sm.png -------------------------------------------------------------------------------- /docs/ng-logo3_xl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo3_xl.png -------------------------------------------------------------------------------- /docs/ng-logo_l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo_l.png -------------------------------------------------------------------------------- /docs/ng-logo_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo_sm.png -------------------------------------------------------------------------------- /docs/ng-logo_xl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/ng-logo_xl.png -------------------------------------------------------------------------------- /docs/schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/schema.png -------------------------------------------------------------------------------- /docs/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/screenshot1.png -------------------------------------------------------------------------------- /docs/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/screenshot2.png -------------------------------------------------------------------------------- /docs/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/screenshot3.png -------------------------------------------------------------------------------- /docs/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/screenshot4.png -------------------------------------------------------------------------------- /docs/screenshot5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/screenshot5.png -------------------------------------------------------------------------------- /docs/screenshot6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/docs/screenshot6.png -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.15 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // The Play plugin 2 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") 3 | -------------------------------------------------------------------------------- /public/etherglimpse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net-glimpse 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |

Ethernet (edge color)

26 | 27 |
28 |
29 | 30 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /public/glimpse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net-glimpse 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |

Ethernet (edge color)

26 | 27 |
28 |
29 |

Internet (edge color)

30 | 31 |
32 |
33 | 34 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /public/graph.js: -------------------------------------------------------------------------------- 1 | /* 2 | net-glimps 3 | Created by Kristian Lange on 2017. 4 | */ 5 | 6 | var VerletPhysics2D = toxi.physics2d.VerletPhysics2D, 7 | VerletParticle2D = toxi.physics2d.VerletParticle2D, 8 | VerletSpring2D = toxi.physics2d.VerletSpring2D, 9 | VerletMinDistanceSpring2D = toxi.physics2d.VerletMinDistanceSpring2D, 10 | AttractionBehavior = toxi.physics2d.behaviors.AttractionBehavior, 11 | Vec2D = toxi.geom.Vec2D, 12 | Rect = toxi.geom.Rect; 13 | 14 | 15 | function Graph(config) { 16 | 17 | var physics = new VerletPhysics2D(); 18 | physics.setDrag(config.physics.drag); 19 | 20 | this.nodes = {}; // Maps addr to node 21 | setCleaning(this, config); 22 | 23 | this.fill = function (para) { 24 | var srcNode = getOrAddNode(this, para.srcAddr, config); 25 | var dstNode = getOrAddNode(this, para.dstAddr, config); 26 | var edge = getOrAddEdge(srcNode, dstNode, para.edgeText, config); 27 | 28 | var dateNow = Date.now(); 29 | srcNode.tick(dateNow, para.srcNodeColor); 30 | dstNode.tick(dateNow, para.dstNodeColor); 31 | edge.tick(para.edgeColor, para.edgeText); 32 | } 33 | 34 | this.update = function () { 35 | physics.update(); 36 | } 37 | 38 | this.getWorldBounds = function () { 39 | return physics.getWorldBounds(); 40 | } 41 | 42 | this.setWorldBounds = function () { 43 | // Use the proper width to height ratio to prevent graphics stretching 44 | var width = config.canvas.width / config.canvas.height; 45 | var height = 1; 46 | physics.setWorldBounds(new Rect(0, 0, width, height)); 47 | } 48 | 49 | var getOrAddNode = function (graph, addr, config) { 50 | var node = graph.nodes[addr]; 51 | if (!node) { 52 | var node = new Node(addr, physics, config); 53 | graph.nodes[addr] = node; 54 | } 55 | return node; 56 | } 57 | 58 | var getOrAddEdge = function (srcNode, dstNode, edgeText, config) { 59 | var edge = srcNode.edges[dstNode.addr]; 60 | if (!edge) { 61 | edge = new Edge(srcNode, dstNode, edgeText, physics, config); 62 | srcNode.edges[dstNode.addr] = edge; 63 | dstNode.incomingEdges[srcNode.addr] = edge; 64 | } 65 | return edge; 66 | } 67 | 68 | function setCleaning(graph, config) { 69 | window.setInterval(function () { 70 | graph.removeOldNodesAndEdges(); 71 | }, config.graph.cleaningIntervall); 72 | } 73 | 74 | this.removeOldNodesAndEdges = function () { 75 | var dateNow = Date.now(); 76 | 77 | var allNodes = this.nodes; 78 | var oldNodes = []; 79 | Object.keys(allNodes).forEach(function (addr) { 80 | var node = allNodes[addr]; 81 | if ((dateNow - node.lastSeen) > config.graph.maxAge) { 82 | oldNodes.push(node); 83 | } 84 | }); 85 | 86 | removeNodes(allNodes, oldNodes); 87 | } 88 | 89 | // Remove nodes from graph, nodes' physics, and edges and their phyics 90 | function removeNodes(allNodes, oldNodes) { 91 | for (i = 0, len = oldNodes.length; i < len; i++) { 92 | var oldNode = oldNodes[i]; 93 | 94 | // Delete all outgoing edges (only physics - object will be deleted with node) 95 | Object.keys(oldNode.edges).forEach(function (dstAddr) { 96 | var edge = oldNode.edges[dstAddr]; 97 | edge.removePhysics(); 98 | }); 99 | 100 | // Delete all edges from other nodes that might target this old node 101 | Object.keys(oldNode.incomingEdges).forEach(function (srcAddr) { 102 | var otherNode = allNodes[srcAddr]; 103 | if (!otherNode) { 104 | return; 105 | } 106 | var edge = otherNode.edges[oldNode.addr]; 107 | if (!edge) { 108 | return; 109 | } 110 | edge.removePhysics(); 111 | delete otherNode.edges[oldNode.addr]; 112 | }); 113 | 114 | // Delete old node 115 | oldNode.removePhysics(); 116 | delete allNodes[oldNode.addr]; 117 | } 118 | } 119 | } 120 | 121 | // Nodes have an address and an arbitrary number of directed edges 122 | function Node(addr, physics, config) { 123 | 124 | this.addr = addr; 125 | this.color = [0, 0, 0, 255]; // Set anew in every tick 126 | this.width = config.node.width; // Set anew in every tick 127 | this.edges = {}; // Outgoing edges: maps edge's dst addr to edge 128 | this.incomingEdges = {}; // Maps edge's src addr to edge 129 | this.lastSeen = {}; 130 | this.particle = new VerletParticle2D( 131 | getFloatAroundCenter(physics.getWorldBounds().width), 132 | getFloatAroundCenter(physics.getWorldBounds().height)); 133 | physics.addParticle(this.particle); 134 | this.behavior = new AttractionBehavior( 135 | this.particle, 136 | config.physics.nodeRepulsionRange, 137 | -config.physics.nodeRepulsion, 138 | 0 // jitter 139 | ); 140 | physics.addBehavior(this.behavior); 141 | 142 | // tick is called each time this node was either the src node dst node of a packet 143 | this.tick = function (dateNow, color) { 144 | this.lastSeen = dateNow; 145 | this.color[0] = color[0]; 146 | this.color[1] = color[1]; 147 | this.color[2] = color[2]; 148 | this.color[3] = 255; 149 | this.width = config.node.tickWidth; 150 | } 151 | 152 | // update is called during each frame drawing 153 | this.update = function() { 154 | if (this.color[3] > config.node.transparency) { 155 | // Return to normal transparency after a tick 156 | this.color[3] -= config.node.transparencyStep; 157 | } 158 | if (this.width > config.node.width) { 159 | // Return to normal width after a tick 160 | this.width--; 161 | } 162 | } 163 | 164 | this.removePhysics = function () { 165 | physics.removeBehavior(this.behavior); 166 | physics.removeParticle(this.particle); 167 | } 168 | } 169 | 170 | function getFloatAroundCenter(size) { 171 | return Math.random() * 0.1 + size / 2; 172 | } 173 | 174 | // Edges are directed from srcNode to dstNode 175 | function Edge(srcNode, dstNode, text, physics, config) { 176 | 177 | this.dstNode = dstNode; 178 | this.color = [0, 0, 0, 255]; 179 | this.text = text; 180 | this.width = config.edge.width; 181 | this.weight = 1; 182 | 183 | this.spring = new VerletSpring2D( 184 | srcNode.particle, 185 | dstNode.particle, 186 | config.physics.springRestLength, 187 | config.physics.springStrength 188 | ); 189 | physics.addSpring(this.spring); 190 | 191 | // tick is called if a packet was sent on this edge 192 | this.tick = function (color, text) { 193 | this.color[0] = color[0]; 194 | this.color[1] = color[1]; 195 | this.color[2] = color[2]; 196 | this.color[3] = 255; 197 | this.text = text; 198 | this.width = config.edge.tickWidth; 199 | if (this.weight < config.edge.weightMax) { 200 | this.weight += config.edge.weightStepIncr; 201 | } 202 | } 203 | 204 | // update is called during each frame drawing 205 | this.update = function() { 206 | if (this.width > config.edge.width) { 207 | // Return to normal width after a tick 208 | this.width -= config.edge.widthStep; 209 | } 210 | if (this.weight > 1) { 211 | // Regress to normal width weight 212 | this.weight -= config.edge.weightStepDecr; 213 | } 214 | if (this.color[3] > config.edge.transparency) { 215 | // Return to normal transparency after a tick 216 | this.color[3] -= config.edge.transparencyStep; 217 | } 218 | } 219 | 220 | this.removePhysics = function () { 221 | physics.removeSpring(this.spring); 222 | } 223 | } -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristian-lange/net-glimpse/c6a06960ed615437cc5c1053c991337c0df83d05/public/images/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net-glimpse 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /public/ipglimpse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net-glimpse 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |

Internet (edge color)

26 | 27 |
28 |
29 | 30 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /public/netDataReceiver.js: -------------------------------------------------------------------------------- 1 | /* 2 | net-glimpse 3 | Created by Kristian Lange on 2017. 4 | */ 5 | 6 | var netDataReceiver = {}; 7 | 8 | (function () { 9 | 10 | // Start WebSocket to get a stream of network package header data 11 | netDataReceiver.start = function () { 12 | var nif = getUrlQueryParameterByName("nif"); 13 | var urlQueryStr = (nif) ? "?nif=" + nif : ""; 14 | var socket = new WebSocket( 15 | ((window.location.protocol === "https:") ? "wss://" : "ws://") + 16 | window.location.host + "/netdata" + urlQueryStr); 17 | 18 | socket.onmessage = function (event) { 19 | var packet = JSON.parse(event.data); 20 | if (packet.ethernet && typeof graphEther !== 'undefined' && 21 | isInEtherWhitelistAndNotInEtherBlacklist(packet)) { 22 | graphEther.fill(getParameterForEtherVisu(packet)); 23 | } 24 | if (packet.ip && typeof graphIp !== 'undefined' && 25 | isInIpWhitelistAndNotInIpBlacklist(packet)) { 26 | graphIp.fill(getParameterForIPVisu(packet)); 27 | } 28 | } 29 | } 30 | 31 | function getParameterForEtherVisu(packet) { 32 | var para = {}; 33 | para.srcAddr = packet.ethernet.macSrcAddr; 34 | para.dstAddr = packet.ethernet.macDstAddr; 35 | para.srcNodeColor = getColorFromMac(para.srcAddr); 36 | para.dstNodeColor = getColorFromMac(para.dstAddr); 37 | var etherType = packet.ethernet.etherType; 38 | if (etherTypeConfig[etherType]) { 39 | para.edgeColor = etherTypeConfig[etherType].color; 40 | para.edgeText = etherTypeConfig[etherType].name; 41 | } else { 42 | para.edgeColor = etherTypeConfig["unknown"].color; 43 | para.edgeText = etherType; 44 | } 45 | return para; 46 | } 47 | 48 | function getParameterForIPVisu(packet) { 49 | var para = {}; 50 | 51 | para.srcAddr = packet.ip.srcAddr; 52 | para.dstAddr = packet.ip.dstAddr; 53 | 54 | if (packet.ip.version === "IPv4") { 55 | para.srcNodeColor = getColorFromIPv4(para.srcAddr); 56 | para.dstNodeColor = getColorFromIPv4(para.dstAddr); 57 | } else if (packet.ip.version === "IPv6") { 58 | para.srcNodeColor = getColorFromIPv6(para.srcAddr); 59 | para.dstNodeColor = getColorFromIPv6(para.dstAddr); 60 | } 61 | // Multicast address's nodes are white (broadcast are already white) 62 | if (packet.ip.dstIsMc === true) { 63 | para.dstNodeColor = [255, 255, 255]; 64 | } 65 | 66 | fillIpEdgeColorAndText(packet, para); 67 | return para; 68 | } 69 | 70 | function fillIpEdgeColorAndText(packet, para) { 71 | if (packet.tcp) { 72 | var srcPort = packet.tcp.srcPort; 73 | var dstPort = packet.tcp.dstPort; 74 | fillIpEdgeColorAndTextWithConfigData(srcPort, dstPort, para); 75 | } else if (packet.udp) { 76 | var srcPort = packet.udp.srcPort; 77 | var dstPort = packet.udp.dstPort; 78 | fillIpEdgeColorAndTextWithConfigData(srcPort, dstPort, para); 79 | } 80 | 81 | // If it's not a TCP or UDP packet show the protocol name as text 82 | if (!para.edgeText) { 83 | para.edgeText = packet.ip.protocol; 84 | } 85 | if (!para.edgeColor) { 86 | para.edgeColor = ipPacketConfig["unknown"].color; 87 | } 88 | } 89 | 90 | function fillIpEdgeColorAndTextWithConfigData(srcPort, dstPort, para) { 91 | // Always show port names (instead of numbers) if the port is in configIp and configIp.edges.showPortNames is true 92 | if (configIp.edge.showPortNames) { 93 | if (ipPacketConfig[srcPort]) { 94 | // Show src port name like specified in ipPacketConfig 95 | para.edgeText = ipPacketConfig[srcPort].name; 96 | para.edgeColor = ipPacketConfig[srcPort].color; 97 | return; 98 | } 99 | if (ipPacketConfig[dstPort]) { 100 | // Show dst port name like specified in ipPacketConfig 101 | para.edgeText = ipPacketConfig[dstPort].name; 102 | para.edgeColor = ipPacketConfig[dstPort].color; 103 | return; 104 | } 105 | } 106 | 107 | // Show port numbers if configured in configIp 108 | var srcPortText = getPortText(srcPort); 109 | var dstPortText = getPortText(dstPort); 110 | if (srcPortText !== null && dstPortText !== null) { 111 | para.edgeText = srcPortText + ":" + dstPortText; 112 | para.edgeColor = ipPacketConfig["unknown"].color; 113 | } else if (srcPortText !== null && dstPortText === null) { 114 | para.edgeText = srcPortText; 115 | para.edgeColor = ipPacketConfig["unknown"].color; 116 | } else if (srcPortText === null && dstPortText !== null) { 117 | para.edgeText = dstPortText; 118 | para.edgeColor = ipPacketConfig["unknown"].color; 119 | } else { 120 | para.edgeText = ""; 121 | para.edgeColor = ipPacketConfig["unknown"].color; 122 | } 123 | } 124 | 125 | function getPortText(port) { 126 | if ((configIp.edge.showWellKnownPorts && port < 1024) || 127 | (configIp.edge.showRegisteredPorts && port >= 1024 && port < 49152) || 128 | (configIp.edge.showOtherPorts && port >= 49152)) { 129 | return port; 130 | } else { 131 | return null; 132 | } 133 | } 134 | 135 | function getColorFromMac(macAddr) { 136 | var addrArray = macAddr.split(":"); 137 | var color = []; 138 | color[0] = (parseInt(addrArray[0], 16) + parseInt(addrArray[3], 16)) / 2; 139 | color[1] = (parseInt(addrArray[1], 16) + parseInt(addrArray[4], 16)) / 2; 140 | color[2] = (parseInt(addrArray[2], 16) + parseInt(addrArray[5], 16)) / 2; 141 | return color; 142 | } 143 | 144 | function getColorFromIPv4(addr) { 145 | var addrArray = addr.split("."); 146 | var color = []; 147 | color[0] = (parseInt(addrArray[0]) + parseInt(addrArray[1])) / 2; 148 | color[1] = (parseInt(addrArray[1]) + parseInt(addrArray[2])) / 2; 149 | color[2] = (parseInt(addrArray[2]) + parseInt(addrArray[3])) / 2; 150 | return color; 151 | } 152 | 153 | function getColorFromIPv6(addr) { 154 | return intToColor(hashCode(addr)); 155 | } 156 | 157 | function hashCode(str) { 158 | var hash = 0; 159 | for (var i = 0; i < str.length; i++) { 160 | hash = str.charCodeAt(i) + ((hash << 5) - hash); 161 | } 162 | return hash; 163 | } 164 | 165 | function intToColor(i) { 166 | var color = []; 167 | color[0] = ((i >> 24) & 0xFF); 168 | color[1] = ((i >> 16) & 0xFF); 169 | color[2] = ((i >> 8) & 0xFF); 170 | return color; 171 | } 172 | 173 | // Check if the src AND dst MAC address is not in the blacklist and 174 | // check if the src OR dst address is in the whitelist - but only if the whitelist has at least one element. 175 | // If the whitelist has no element it is ignored. 176 | function isInEtherWhitelistAndNotInEtherBlacklist(packet) { 177 | var srcAddr = packet.ethernet.macSrcAddr; 178 | var dstAddr = packet.ethernet.macDstAddr; 179 | return !isInList(srcAddr, etherBlacklist) && !isInList(dstAddr, etherBlacklist) && 180 | (etherWhitelist.length === 0 || isInList(srcAddr, etherWhitelist) || isInList(dstAddr, etherWhitelist)) ; 181 | } 182 | 183 | // Check if the src AND dst Internet address (format 'IP:port') is not in the blacklist and 184 | // check if the src OR dst address is in the whitelist - but only if the whitelist has at least one element. 185 | // If the whitelist has no element it is ignored. 186 | function isInIpWhitelistAndNotInIpBlacklist(packet) { 187 | var srcAddr; 188 | var dstAddr 189 | if (packet.tcp) { 190 | srcAddr = packet.ip.srcAddr + ":" + packet.tcp.srcPort; 191 | dstAddr = packet.ip.dstAddr + ":" + packet.tcp.dstPort; 192 | } else if (packet.udp) { 193 | srcAddr = packet.ip.srcAddr + ":" + packet.udp.srcPort; 194 | dstAddr = packet.ip.dstAddr + ":" + packet.udp.dstPort; 195 | } else { 196 | srcAddr = packet.ip.srcAddr; 197 | dstAddr = packet.ip.dstAddr; 198 | } 199 | return !isInList(srcAddr, ipBlacklist) && !isInList(dstAddr, ipBlacklist) && 200 | (ipWhitelist.length === 0 || isInList(srcAddr, ipWhitelist) || isInList(dstAddr, ipWhitelist)); 201 | } 202 | 203 | // Evalutates all elements of an array of regexes if they fulfill the given addr 204 | // Used for whitelists and blacklists 205 | function isInList(addr, regexArray) { 206 | return regexArray.some(function (regexp) { 207 | return regexp.test(addr); 208 | }); 209 | } 210 | 211 | })(); -------------------------------------------------------------------------------- /public/p5/addons/p5.dom.js: -------------------------------------------------------------------------------- 1 | /*! p5.dom.js v0.3.3 May 10, 2017 */ 2 | /** 3 | *

The web is much more than just canvas and p5.dom makes it easy to interact 4 | * with other HTML5 objects, including text, hyperlink, image, input, video, 5 | * audio, and webcam.

6 | *

There is a set of creation methods, DOM manipulation methods, and 7 | * an extended p5.Element that supports a range of HTML elements. See the 8 | * 9 | * beyond the canvas tutorial for a full overview of how this addon works. 10 | * 11 | *

Methods and properties shown in black are part of the p5.js core, items in 12 | * blue are part of the p5.dom library. You will need to include an extra file 13 | * in order to access the blue functions. See the 14 | * using a library 15 | * section for information on how to include this library. p5.dom comes with 16 | * p5 complete or you can download the single file 17 | * 18 | * here.

19 | *

See tutorial: beyond the canvas 20 | * for more info on how to use this libary. 21 | * 22 | * @module p5.dom 23 | * @submodule p5.dom 24 | * @for p5.dom 25 | * @main 26 | */ 27 | 28 | (function (root, factory) { 29 | if (typeof define === 'function' && define.amd) 30 | define('p5.dom', ['p5'], function (p5) { (factory(p5));}); 31 | else if (typeof exports === 'object') 32 | factory(require('../p5')); 33 | else 34 | factory(root['p5']); 35 | }(this, function (p5) { 36 | 37 | // ============================================================================= 38 | // p5 additions 39 | // ============================================================================= 40 | 41 | /** 42 | * Searches the page for an element with the given ID, class, or tag name (using the '#' or '.' 43 | * prefixes to specify an ID or class respectively, and none for a tag) and returns it as 44 | * a p5.Element. If a class or tag name is given with more than 1 element, 45 | * only the first element will be returned. 46 | * The DOM node itself can be accessed with .elt. 47 | * Returns null if none found. You can also specify a container to search within. 48 | * 49 | * @method select 50 | * @param {String} name id, class, or tag name of element to search for 51 | * @param {String} [container] id, p5.Element, or HTML element to search within 52 | * @return {Object|p5.Element|Null} p5.Element containing node found 53 | * @example 54 | *

55 | * function setup() { 56 | * createCanvas(100,100); 57 | * //translates canvas 50px down 58 | * select('canvas').position(100, 100); 59 | * } 60 | *
61 | *
62 | * // these are all valid calls to select() 63 | * var a = select('#moo'); 64 | * var b = select('#blah', '#myContainer'); 65 | * var c = select('#foo', b); 66 | * var d = document.getElementById('beep'); 67 | * var e = select('p', d); 68 | *
69 | * 70 | */ 71 | p5.prototype.select = function (e, p) { 72 | var res = null; 73 | var container = getContainer(p); 74 | if (e[0] === '.'){ 75 | e = e.slice(1); 76 | res = container.getElementsByClassName(e); 77 | if (res.length) { 78 | res = res[0]; 79 | } else { 80 | res = null; 81 | } 82 | }else if (e[0] === '#'){ 83 | e = e.slice(1); 84 | res = container.getElementById(e); 85 | }else { 86 | res = container.getElementsByTagName(e); 87 | if (res.length) { 88 | res = res[0]; 89 | } else { 90 | res = null; 91 | } 92 | } 93 | if (res) { 94 | return wrapElement(res); 95 | } else { 96 | return null; 97 | } 98 | }; 99 | 100 | /** 101 | * Searches the page for elements with the given class or tag name (using the '.' prefix 102 | * to specify a class and no prefix for a tag) and returns them as p5.Elements 103 | * in an array. 104 | * The DOM node itself can be accessed with .elt. 105 | * Returns an empty array if none found. 106 | * You can also specify a container to search within. 107 | * 108 | * @method selectAll 109 | * @param {String} name class or tag name of elements to search for 110 | * @param {String} [container] id, p5.Element, or HTML element to search within 111 | * @return {Array} Array of p5.Elements containing nodes found 112 | * @example 113 | *
114 | * function setup() { 115 | * createButton('btn'); 116 | * createButton('2nd btn'); 117 | * createButton('3rd btn'); 118 | * var buttons = selectAll('button'); 119 | * 120 | * for (var i = 0; i < buttons.length; i++){ 121 | * buttons[i].size(100,100); 122 | * } 123 | * } 124 | *
125 | *
126 | * // these are all valid calls to selectAll() 127 | * var a = selectAll('.moo'); 128 | * var b = selectAll('div'); 129 | * var c = selectAll('button', '#myContainer'); 130 | * var d = select('#container'); 131 | * var e = selectAll('p', d); 132 | * var f = document.getElementById('beep'); 133 | * var g = select('.blah', f); 134 | *
135 | * 136 | */ 137 | p5.prototype.selectAll = function (e, p) { 138 | var arr = []; 139 | var res; 140 | var container = getContainer(p); 141 | if (e[0] === '.'){ 142 | e = e.slice(1); 143 | res = container.getElementsByClassName(e); 144 | } else { 145 | res = container.getElementsByTagName(e); 146 | } 147 | if (res) { 148 | for (var j = 0; j < res.length; j++) { 149 | var obj = wrapElement(res[j]); 150 | arr.push(obj); 151 | } 152 | } 153 | return arr; 154 | }; 155 | 156 | /** 157 | * Helper function for select and selectAll 158 | */ 159 | function getContainer(p) { 160 | var container = document; 161 | if (typeof p === 'string' && p[0] === '#'){ 162 | p = p.slice(1); 163 | container = document.getElementById(p) || document; 164 | } else if (p instanceof p5.Element){ 165 | container = p.elt; 166 | } else if (p instanceof HTMLElement){ 167 | container = p; 168 | } 169 | return container; 170 | } 171 | 172 | /** 173 | * Helper function for getElement and getElements. 174 | */ 175 | function wrapElement(elt) { 176 | if(elt.tagName === "INPUT" && elt.type === "checkbox") { 177 | var converted = new p5.Element(elt); 178 | converted.checked = function(){ 179 | if (arguments.length === 0){ 180 | return this.elt.checked; 181 | } else if(arguments[0]) { 182 | this.elt.checked = true; 183 | } else { 184 | this.elt.checked = false; 185 | } 186 | return this; 187 | }; 188 | return converted; 189 | } else if (elt.tagName === "VIDEO" || elt.tagName === "AUDIO") { 190 | return new p5.MediaElement(elt); 191 | } else if ( elt.tagName === "SELECT" ){ 192 | return createSelect( new p5.Element(elt) ); 193 | } 194 | else { 195 | return new p5.Element(elt); 196 | } 197 | } 198 | 199 | /** 200 | * Removes all elements created by p5, except any canvas / graphics 201 | * elements created by createCanvas or createGraphics. 202 | * Event handlers are removed, and element is removed from the DOM. 203 | * @method removeElements 204 | * @example 205 | *
206 | * function setup() { 207 | * createCanvas(100, 100); 208 | * createDiv('this is some text'); 209 | * createP('this is a paragraph'); 210 | * } 211 | * function mousePressed() { 212 | * removeElements(); // this will remove the div and p, not canvas 213 | * } 214 | *
215 | * 216 | */ 217 | p5.prototype.removeElements = function (e) { 218 | for (var i=0; i 246 | * var myDiv; 247 | * function setup() { 248 | * myDiv = createDiv('this is some text'); 249 | * } 250 | * 251 | */ 252 | 253 | /** 254 | * Creates a <p></p> element in the DOM with given inner HTML. Used 255 | * for paragraph length text. 256 | * Appends to the container node if one is specified, otherwise 257 | * appends to body. 258 | * 259 | * @method createP 260 | * @param {String} html inner HTML for element created 261 | * @return {Object|p5.Element} pointer to p5.Element holding created node 262 | * @example 263 | *
264 | * var myP; 265 | * function setup() { 266 | * myP = createP('this is some text'); 267 | * } 268 | *
269 | */ 270 | 271 | /** 272 | * Creates a <span></span> element in the DOM with given inner HTML. 273 | * Appends to the container node if one is specified, otherwise 274 | * appends to body. 275 | * 276 | * @method createSpan 277 | * @param {String} html inner HTML for element created 278 | * @return {Object|p5.Element} pointer to p5.Element holding created node 279 | * @example 280 | *
281 | * var mySpan; 282 | * function setup() { 283 | * mySpan = createSpan('this is some text'); 284 | * } 285 | *
286 | */ 287 | var tags = ['div', 'p', 'span']; 288 | tags.forEach(function(tag) { 289 | var method = 'create' + tag.charAt(0).toUpperCase() + tag.slice(1); 290 | p5.prototype[method] = function(html) { 291 | var elt = document.createElement(tag); 292 | elt.innerHTML = typeof html === undefined ? "" : html; 293 | return addElement(elt, this); 294 | } 295 | }); 296 | 297 | /** 298 | * Creates an <img> element in the DOM with given src and 299 | * alternate text. 300 | * Appends to the container node if one is specified, otherwise 301 | * appends to body. 302 | * 303 | * @method createImg 304 | * @param {String} src src path or url for image 305 | * @param {String} [alt] alternate text to be used if image does not load 306 | * @param {Function} [successCallback] callback to be called once image data is loaded 307 | * @return {Object|p5.Element} pointer to p5.Element holding created node 308 | * @example 309 | *
310 | * var img; 311 | * function setup() { 312 | * img = createImg('http://p5js.org/img/asterisk-01.png'); 313 | * } 314 | *
315 | */ 316 | p5.prototype.createImg = function() { 317 | var elt = document.createElement('img'); 318 | var args = arguments; 319 | var self; 320 | var setAttrs = function(){ 321 | self.width = elt.offsetWidth || elt.width; 322 | self.height = elt.offsetHeight || elt.height; 323 | if (args.length > 1 && typeof args[1] === 'function'){ 324 | self.fn = args[1]; 325 | self.fn(); 326 | }else if (args.length > 1 && typeof args[2] === 'function'){ 327 | self.fn = args[2]; 328 | self.fn(); 329 | } 330 | }; 331 | elt.src = args[0]; 332 | if (args.length > 1 && typeof args[1] === 'string'){ 333 | elt.alt = args[1]; 334 | } 335 | elt.onload = function(){ 336 | setAttrs(); 337 | } 338 | self = addElement(elt, this); 339 | return self; 340 | }; 341 | 342 | /** 343 | * Creates an <a></a> element in the DOM for including a hyperlink. 344 | * Appends to the container node if one is specified, otherwise 345 | * appends to body. 346 | * 347 | * @method createA 348 | * @param {String} href url of page to link to 349 | * @param {String} html inner html of link element to display 350 | * @param {String} [target] target where new link should open, 351 | * could be _blank, _self, _parent, _top. 352 | * @return {Object|p5.Element} pointer to p5.Element holding created node 353 | * @example 354 | *
355 | * var myLink; 356 | * function setup() { 357 | * myLink = createA('http://p5js.org/', 'this is a link'); 358 | * } 359 | *
360 | */ 361 | p5.prototype.createA = function(href, html, target) { 362 | var elt = document.createElement('a'); 363 | elt.href = href; 364 | elt.innerHTML = html; 365 | if (target) elt.target = target; 366 | return addElement(elt, this); 367 | }; 368 | 369 | /** INPUT **/ 370 | 371 | 372 | /** 373 | * Creates a slider <input></input> element in the DOM. 374 | * Use .size() to set the display length of the slider. 375 | * Appends to the container node if one is specified, otherwise 376 | * appends to body. 377 | * 378 | * @method createSlider 379 | * @param {Number} min minimum value of the slider 380 | * @param {Number} max maximum value of the slider 381 | * @param {Number} [value] default value of the slider 382 | * @param {Number} [step] step size for each tick of the slider (if step is set to 0, the slider will move continuously from the minimum to the maximum value) 383 | * @return {Object|p5.Element} pointer to p5.Element holding created node 384 | * @example 385 | *
386 | * var slider; 387 | * function setup() { 388 | * slider = createSlider(0, 255, 100); 389 | * slider.position(10, 10); 390 | * slider.style('width', '80px'); 391 | * } 392 | * 393 | * function draw() { 394 | * var val = slider.value(); 395 | * background(val); 396 | * } 397 | *
398 | * 399 | *
400 | * var slider; 401 | * function setup() { 402 | * colorMode(HSB); 403 | * slider = createSlider(0, 360, 60, 40); 404 | * slider.position(10, 10); 405 | * slider.style('width', '80px'); 406 | * } 407 | * 408 | * function draw() { 409 | * var val = slider.value(); 410 | * background(val, 100, 100, 1); 411 | * } 412 | *
413 | */ 414 | p5.prototype.createSlider = function(min, max, value, step) { 415 | var elt = document.createElement('input'); 416 | elt.type = 'range'; 417 | elt.min = min; 418 | elt.max = max; 419 | if (step === 0) { 420 | elt.step = .000000000000000001; // smallest valid step 421 | } else if (step) { 422 | elt.step = step; 423 | } 424 | if (typeof(value) === "number") elt.value = value; 425 | return addElement(elt, this); 426 | }; 427 | 428 | /** 429 | * Creates a <button></button> element in the DOM. 430 | * Use .size() to set the display size of the button. 431 | * Use .mousePressed() to specify behavior on press. 432 | * Appends to the container node if one is specified, otherwise 433 | * appends to body. 434 | * 435 | * @method createButton 436 | * @param {String} label label displayed on the button 437 | * @param {String} [value] value of the button 438 | * @return {Object|p5.Element} pointer to p5.Element holding created node 439 | * @example 440 | *
441 | * var button; 442 | * function setup() { 443 | * createCanvas(100, 100); 444 | * background(0); 445 | * button = createButton('click me'); 446 | * button.position(19, 19); 447 | * button.mousePressed(changeBG); 448 | * } 449 | * 450 | * function changeBG() { 451 | * var val = random(255); 452 | * background(val); 453 | * } 454 | *
455 | */ 456 | p5.prototype.createButton = function(label, value) { 457 | var elt = document.createElement('button'); 458 | elt.innerHTML = label; 459 | elt.value = value; 460 | if (value) elt.value = value; 461 | return addElement(elt, this); 462 | }; 463 | 464 | /** 465 | * Creates a checkbox <input></input> element in the DOM. 466 | * Calling .checked() on a checkbox returns if it is checked or not 467 | * 468 | * @method createCheckbox 469 | * @param {String} [label] label displayed after checkbox 470 | * @param {boolean} [value] value of the checkbox; checked is true, unchecked is false.Unchecked if no value given 471 | * @return {Object|p5.Element} pointer to p5.Element holding created node 472 | * @example 473 | *
474 | * var checkbox; 475 | * 476 | * function setup() { 477 | * checkbox = createCheckbox('label', false); 478 | * checkbox.changed(myCheckedEvent); 479 | * } 480 | * 481 | * function myCheckedEvent() { 482 | * if (this.checked()) { 483 | * console.log("Checking!"); 484 | * } else { 485 | * console.log("Unchecking!"); 486 | * } 487 | * } 488 | *
489 | */ 490 | p5.prototype.createCheckbox = function() { 491 | var elt = document.createElement('div'); 492 | var checkbox = document.createElement('input'); 493 | checkbox.type = 'checkbox'; 494 | elt.appendChild(checkbox); 495 | //checkbox must be wrapped in p5.Element before label so that label appears after 496 | var self = addElement(elt, this); 497 | self.checked = function(){ 498 | var cb = self.elt.getElementsByTagName('input')[0]; 499 | if (cb) { 500 | if (arguments.length === 0){ 501 | return cb.checked; 502 | }else if(arguments[0]){ 503 | cb.checked = true; 504 | }else{ 505 | cb.checked = false; 506 | } 507 | } 508 | return self; 509 | }; 510 | this.value = function(val){ 511 | self.value = val; 512 | return this; 513 | }; 514 | if (arguments[0]){ 515 | var ran = Math.random().toString(36).slice(2); 516 | var label = document.createElement('label'); 517 | checkbox.setAttribute('id', ran); 518 | label.htmlFor = ran; 519 | self.value(arguments[0]); 520 | label.appendChild(document.createTextNode(arguments[0])); 521 | elt.appendChild(label); 522 | } 523 | if (arguments[1]){ 524 | checkbox.checked = true; 525 | } 526 | return self; 527 | }; 528 | 529 | /** 530 | * Creates a dropdown menu <select></select> element in the DOM. 531 | * It also helps to assign select-box methods to p5.Element when selecting existing select box 532 | * @method createSelect 533 | * @param {boolean} [multiple] true if dropdown should support multiple selections 534 | * @return {p5.Element} 535 | * @example 536 | *
537 | * var sel; 538 | * 539 | * function setup() { 540 | * textAlign(CENTER); 541 | * background(200); 542 | * sel = createSelect(); 543 | * sel.position(10, 10); 544 | * sel.option('pear'); 545 | * sel.option('kiwi'); 546 | * sel.option('grape'); 547 | * sel.changed(mySelectEvent); 548 | * } 549 | * 550 | * function mySelectEvent() { 551 | * var item = sel.value(); 552 | * background(200); 553 | * text("it's a "+item+"!", 50, 50); 554 | * } 555 | *
556 | */ 557 | /** 558 | * @method createSelect 559 | * @param {Object} existing DOM select element 560 | * @return {p5.Element} 561 | */ 562 | 563 | p5.prototype.createSelect = function() { 564 | var elt, self; 565 | var arg = arguments[0]; 566 | if( typeof arg === 'object' && arg.elt.nodeName === 'SELECT' ) { 567 | self = arg; 568 | elt = this.elt = arg.elt; 569 | } else { 570 | elt = document.createElement('select'); 571 | if( arg && typeof arg === 'boolean' ) { 572 | elt.setAttribute('multiple', 'true'); 573 | } 574 | self = addElement(elt, this); 575 | } 576 | self.option = function(name, value) { 577 | var opt = document.createElement('option'); 578 | opt.innerHTML = name; 579 | if (arguments.length > 1) 580 | opt.value = value; 581 | else 582 | opt.value = name; 583 | elt.appendChild(opt); 584 | }; 585 | self.selected = function(value) { 586 | var arr = []; 587 | if (arguments.length > 0) { 588 | for (var i = 0; i < this.elt.length; i++) { 589 | if (value.toString() === this.elt[i].value) { 590 | this.elt.selectedIndex = i; 591 | } 592 | } 593 | return this; 594 | } else { 595 | if (arg) { 596 | for (var i = 0; i < this.elt.selectedOptions.length; i++) { 597 | arr.push(this.elt.selectedOptions[i].value); 598 | } 599 | return arr; 600 | } else { 601 | return this.elt.value; 602 | } 603 | } 604 | }; 605 | return self; 606 | }; 607 | 608 | /** 609 | * Creates a radio button <input></input> element in the DOM. 610 | * The .option() method can be used to set options for the radio after it is 611 | * created. The .value() method will return the currently selected option. 612 | * 613 | * @method createRadio 614 | * @param {String} [divId] the id and name of the created div and input field respectively 615 | * @return {Object|p5.Element} pointer to p5.Element holding created node 616 | * @example 617 | *
618 | * var radio; 619 | * 620 | * function setup() { 621 | * radio = createRadio(); 622 | * radio.option("black"); 623 | * radio.option("white"); 624 | * radio.option("gray"); 625 | * radio.style('width', '60px'); 626 | * textAlign(CENTER); 627 | * fill(255, 0, 0); 628 | * } 629 | * 630 | * function draw() { 631 | * var val = radio.value(); 632 | * background(val); 633 | * text(val, width/2, height/2); 634 | * } 635 | *
636 | *
637 | * var radio; 638 | * 639 | * function setup() { 640 | * radio = createRadio(); 641 | * radio.option('apple', 1); 642 | * radio.option('bread', 2); 643 | * radio.option('juice', 3); 644 | * radio.style('width', '60px'); 645 | * textAlign(CENTER); 646 | * } 647 | * 648 | * function draw() { 649 | * background(200); 650 | * var val = radio.value(); 651 | * if (val) { 652 | * text('item cost is $'+val, width/2, height/2); 653 | * } 654 | * } 655 | *
656 | */ 657 | p5.prototype.createRadio = function() { 658 | var radios = document.querySelectorAll("input[type=radio]"); 659 | var count = 0; 660 | if(radios.length > 1){ 661 | var length = radios.length; 662 | var prev=radios[0].name; 663 | var current = radios[1].name; 664 | count = 1; 665 | for(var i = 1; i < length; i++) { 666 | current = radios[i].name; 667 | if(prev != current){ 668 | count++; 669 | } 670 | prev = current; 671 | } 672 | } 673 | else if (radios.length == 1){ 674 | count = 1; 675 | } 676 | var elt = document.createElement('div'); 677 | var self = addElement(elt, this); 678 | var times = -1; 679 | self.option = function(name, value){ 680 | var opt = document.createElement('input'); 681 | opt.type = 'radio'; 682 | opt.innerHTML = name; 683 | if (arguments.length > 1) 684 | opt.value = value; 685 | else 686 | opt.value = name; 687 | opt.setAttribute('name',"defaultradio"+count); 688 | elt.appendChild(opt); 689 | if (name){ 690 | times++; 691 | var ran = Math.random().toString(36).slice(2); 692 | var label = document.createElement('label'); 693 | opt.setAttribute('id', "defaultradio"+count+"-"+times); 694 | label.htmlFor = "defaultradio"+count+"-"+times; 695 | label.appendChild(document.createTextNode(name)); 696 | elt.appendChild(label); 697 | } 698 | return opt; 699 | }; 700 | self.selected = function(){ 701 | var length = this.elt.childNodes.length; 702 | if(arguments.length == 1) { 703 | for (var i = 0; i < length; i+=2){ 704 | if(this.elt.childNodes[i].value == arguments[0]) 705 | this.elt.childNodes[i].checked = true; 706 | } 707 | return this; 708 | } else { 709 | for (var i = 0; i < length; i+=2){ 710 | if(this.elt.childNodes[i].checked == true) 711 | return this.elt.childNodes[i].value; 712 | } 713 | } 714 | }; 715 | self.value = function(){ 716 | var length = this.elt.childNodes.length; 717 | if(arguments.length == 1) { 718 | for (var i = 0; i < length; i+=2){ 719 | if(this.elt.childNodes[i].value == arguments[0]) 720 | this.elt.childNodes[i].checked = true; 721 | } 722 | return this; 723 | } else { 724 | for (var i = 0; i < length; i+=2){ 725 | if(this.elt.childNodes[i].checked == true) 726 | return this.elt.childNodes[i].value; 727 | } 728 | return ""; 729 | } 730 | }; 731 | return self 732 | }; 733 | 734 | /** 735 | * Creates an <input></input> element in the DOM for text input. 736 | * Use .size() to set the display length of the box. 737 | * Appends to the container node if one is specified, otherwise 738 | * appends to body. 739 | * 740 | * @method createInput 741 | * @param {Number} [value] default value of the input box 742 | * @param {String} [type] type of text, ie text, password etc. Defaults to text 743 | * @return {Object|p5.Element} pointer to p5.Element holding created node 744 | * @example 745 | *
746 | * function setup(){ 747 | * var inp = createInput(''); 748 | * inp.input(myInputEvent); 749 | * } 750 | * 751 | * function myInputEvent(){ 752 | * console.log('you are typing: ', this.value()); 753 | * } 754 | * 755 | *
756 | */ 757 | p5.prototype.createInput = function(value, type) { 758 | var elt = document.createElement('input'); 759 | elt.type = type ? type : 'text'; 760 | if (value) elt.value = value; 761 | return addElement(elt, this); 762 | }; 763 | 764 | /** 765 | * Creates an <input></input> element in the DOM of type 'file'. 766 | * This allows users to select local files for use in a sketch. 767 | * 768 | * @method createFileInput 769 | * @param {Function} [callback] callback function for when a file loaded 770 | * @param {String} [multiple] optional to allow multiple files selected 771 | * @return {Object|p5.Element} pointer to p5.Element holding created DOM element 772 | * @example 773 | * var input; 774 | * var img; 775 | * 776 | * function setup() { 777 | * input = createFileInput(handleFile); 778 | * input.position(0, 0); 779 | * } 780 | * 781 | * function draw() { 782 | * if (img) { 783 | * image(img, 0, 0, width, height); 784 | * } 785 | * } 786 | * 787 | * function handleFile(file) { 788 | * print(file); 789 | * if (file.type === 'image') { 790 | * img = createImg(file.data); 791 | * img.hide(); 792 | * } 793 | * } 794 | */ 795 | p5.prototype.createFileInput = function(callback, multiple) { 796 | 797 | // Is the file stuff supported? 798 | if (window.File && window.FileReader && window.FileList && window.Blob) { 799 | // Yup, we're ok and make an input file selector 800 | var elt = document.createElement('input'); 801 | elt.type = 'file'; 802 | 803 | // If we get a second argument that evaluates to true 804 | // then we are looking for multiple files 805 | if (multiple) { 806 | // Anything gets the job done 807 | elt.multiple = 'multiple'; 808 | } 809 | 810 | // Function to handle when a file is selected 811 | // We're simplifying life and assuming that we always 812 | // want to load every selected file 813 | function handleFileSelect(evt) { 814 | // These are the files 815 | var files = evt.target.files; 816 | // Load each one and trigger a callback 817 | for (var i = 0; i < files.length; i++) { 818 | var f = files[i]; 819 | var reader = new FileReader(); 820 | function makeLoader(theFile) { 821 | // Making a p5.File object 822 | var p5file = new p5.File(theFile); 823 | return function(e) { 824 | p5file.data = e.target.result; 825 | callback(p5file); 826 | }; 827 | }; 828 | reader.onload = makeLoader(f); 829 | 830 | // Text or data? 831 | // This should likely be improved 832 | if (f.type.indexOf('text') > -1) { 833 | reader.readAsText(f); 834 | } else { 835 | reader.readAsDataURL(f); 836 | } 837 | } 838 | } 839 | 840 | // Now let's handle when a file was selected 841 | elt.addEventListener('change', handleFileSelect, false); 842 | return addElement(elt, this); 843 | } else { 844 | console.log('The File APIs are not fully supported in this browser. Cannot create element.'); 845 | } 846 | }; 847 | 848 | 849 | /** VIDEO STUFF **/ 850 | 851 | function createMedia(pInst, type, src, callback) { 852 | var elt = document.createElement(type); 853 | 854 | // allow src to be empty 855 | var src = src || ''; 856 | if (typeof src === 'string') { 857 | src = [src]; 858 | } 859 | for (var i=0; i