├── .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 | 
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 | 
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 | 
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 | 
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 | 
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 |
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 | *
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 | *
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 | *
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 | *
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; ithis
895 | * page for further information about supported formats.
896 | *
897 | * @method createVideo
898 | * @param {String|Array} src path to a video file, or array of paths for
899 | * supporting different browsers
900 | * @param {Object} [callback] callback function to be called upon
901 | * 'canplaythrough' event fire, that is, when the
902 | * browser can play the media, and estimates that
903 | * enough data has been loaded to play the media
904 | * up to its end without having to stop for
905 | * further buffering of content
906 | * @return {Object|p5.Element} pointer to video p5.Element
907 | */
908 | p5.prototype.createVideo = function(src, callback) {
909 | return createMedia(this, 'video', src, callback);
910 | };
911 |
912 | /** AUDIO STUFF **/
913 |
914 | /**
915 | * Creates a hidden HTML5 <audio> element in the DOM for simple audio
916 | * playback. Appends to the container node if one is specified,
917 | * otherwise appends to body. The first parameter
918 | * can be either a single string path to a audio file, or an array of string
919 | * paths to different formats of the same audio. This is useful for ensuring
920 | * that your audio can play across different browsers, as each supports
921 | * different formats. See this
922 | * page for further information about supported formats.
923 | *
924 | * @method createAudio
925 | * @param {String|Array} src path to an audio file, or array of paths for
926 | * supporting different browsers
927 | * @param {Object} [callback] callback function to be called upon
928 | * 'canplaythrough' event fire, that is, when the
929 | * browser can play the media, and estimates that
930 | * enough data has been loaded to play the media
931 | * up to its end without having to stop for
932 | * further buffering of content
933 | * @return {Object|p5.Element} pointer to audio p5.Element
934 | */
935 | p5.prototype.createAudio = function(src, callback) {
936 | return createMedia(this, 'audio', src, callback);
937 | };
938 |
939 |
940 | /** CAMERA STUFF **/
941 |
942 | p5.prototype.VIDEO = 'video';
943 | p5.prototype.AUDIO = 'audio';
944 |
945 | navigator.getUserMedia = navigator.getUserMedia ||
946 | navigator.webkitGetUserMedia ||
947 | navigator.mozGetUserMedia ||
948 | navigator.msGetUserMedia;
949 |
950 | /**
951 | *
Creates a new <video> element that contains the audio/video feed
952 | * from a webcam. This can be drawn onto the canvas using video().
953 | *
More specific properties of the feed can be passing in a Constraints object.
954 | * See the
955 | * W3C
956 | * spec for possible properties. Note that not all of these are supported
957 | * by all browsers.
958 | *
Security note: A new browser security specification requires that getUserMedia,
959 | * which is behind createCapture(), only works when you're running the code locally,
960 | * or on HTTPS. Learn more here
961 | * and here.
962 | *
963 | * @method createCapture
964 | * @param {String|Constant|Object} type type of capture, either VIDEO or
965 | * AUDIO if none specified, default both,
966 | * or a Constraints object
967 | * @param {Function} callback function to be called once
968 | * stream has loaded
969 | * @return {Object|p5.Element} capture video p5.Element
970 | * @example
971 | *
1004 | */
1005 | p5.prototype.createCapture = function() {
1006 | var useVideo = true;
1007 | var useAudio = true;
1008 | var constraints;
1009 | var cb;
1010 | for (var i=0; i
1066 | * var h2 = createElement('h2','im an h2 p5.element!');
1067 | *
1068 | */
1069 | p5.prototype.createElement = function(tag, content) {
1070 | var elt = document.createElement(tag);
1071 | if (typeof content !== 'undefined') {
1072 | elt.innerHTML = content;
1073 | }
1074 | return addElement(elt, this);
1075 | };
1076 |
1077 |
1078 | // =============================================================================
1079 | // p5.Element additions
1080 | // =============================================================================
1081 | /**
1082 | *
1083 | * Adds specified class to the element.
1084 | *
1085 | * @for p5.Element
1086 | * @method addClass
1087 | * @param {String} class name of class to add
1088 | * @return {Object|p5.Element}
1089 | * @example
1090 | *
1091 | * var div = createDiv('div');
1092 | * div.addClass('myClass');
1093 | *
1094 | */
1095 | p5.Element.prototype.addClass = function(c) {
1096 | if (this.elt.className) {
1097 | // PEND don't add class more than once
1098 | //var regex = new RegExp('[^a-zA-Z\d:]?'+c+'[^a-zA-Z\d:]?');
1099 | //if (this.elt.className.search(/[^a-zA-Z\d:]?hi[^a-zA-Z\d:]?/) === -1) {
1100 | this.elt.className = this.elt.className+' '+c;
1101 | //}
1102 | } else {
1103 | this.elt.className = c;
1104 | }
1105 | return this;
1106 | }
1107 |
1108 | /**
1109 | *
1110 | * Removes specified class from the element.
1111 | *
1112 | * @method removeClass
1113 | * @param {String} class name of class to remove
1114 | * @return {Object|p5.Element}
1115 | */
1116 | p5.Element.prototype.removeClass = function(c) {
1117 | var regex = new RegExp('(?:^|\\s)'+c+'(?!\\S)');
1118 | this.elt.className = this.elt.className.replace(regex, '');
1119 | this.elt.className = this.elt.className.replace(/^\s+|\s+$/g, ""); //prettify (optional)
1120 | return this;
1121 | }
1122 |
1123 | /**
1124 | *
1125 | * Attaches the element as a child to the parent specified.
1126 | * Accepts either a string ID, DOM node, or p5.Element.
1127 | * If no argument is specified, an array of children DOM nodes is returned.
1128 | *
1129 | * @method child
1130 | * @param {String|Object|p5.Element} [child] the ID, DOM node, or p5.Element
1131 | * to add to the current element
1132 | * @return {p5.Element}
1133 | * @example
1134 | *
1135 | * var div0 = createDiv('this is the parent');
1136 | * var div1 = createDiv('this is the child');
1137 | * div0.child(div1); // use p5.Element
1138 | *
1139 | *
1140 | * var div0 = createDiv('this is the parent');
1141 | * var div1 = createDiv('this is the child');
1142 | * div1.id('apples');
1143 | * div0.child('apples'); // use id
1144 | *
1145 | *
1146 | * var div0 = createDiv('this is the parent');
1147 | * var elt = document.getElementById('myChildDiv');
1148 | * div0.child(elt); // use element from page
1149 | *
1150 | */
1151 | p5.Element.prototype.child = function(c) {
1152 | if (typeof c === 'undefined'){
1153 | return this.elt.childNodes
1154 | }
1155 | if (typeof c === 'string') {
1156 | if (c[0] === '#') {
1157 | c = c.substring(1);
1158 | }
1159 | c = document.getElementById(c);
1160 | } else if (c instanceof p5.Element) {
1161 | c = c.elt;
1162 | }
1163 | this.elt.appendChild(c);
1164 | return this;
1165 | };
1166 |
1167 | /**
1168 | * Centers a p5 Element either vertically, horizontally,
1169 | * or both, relative to its parent or according to
1170 | * the body if the Element has no parent. If no argument is passed
1171 | * the Element is aligned both vertically and horizontally.
1172 | *
1173 | * @param {String} align passing 'vertical', 'horizontal' aligns element accordingly
1174 | * @return {Object|p5.Element} pointer to p5.Element
1175 | * @example
1176 | *
1184 | */
1185 | p5.Element.prototype.center = function(align) {
1186 | var style = this.elt.style.display;
1187 | var hidden = this.elt.style.display === 'none';
1188 | var parentHidden = this.parent().style.display === 'none';
1189 | var pos = { x : this.elt.offsetLeft, y : this.elt.offsetTop };
1190 |
1191 | if (hidden) this.show();
1192 |
1193 | this.elt.style.display = 'block';
1194 | this.position(0,0);
1195 |
1196 | if (parentHidden) this.parent().style.display = 'block';
1197 |
1198 | var wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth);
1199 | var hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight);
1200 | var y = pos.y;
1201 | var x = pos.x;
1202 |
1203 | if (align === 'both' || align === undefined){
1204 | this.position(wOffset/2, hOffset/2);
1205 | }else if (align === 'horizontal'){
1206 | this.position(wOffset/2, y);
1207 | }else if (align === 'vertical'){
1208 | this.position(x, hOffset/2);
1209 | }
1210 |
1211 | this.style('display', style);
1212 |
1213 | if (hidden) this.hide();
1214 |
1215 | if (parentHidden) this.parent().style.display = 'none';
1216 |
1217 | return this;
1218 | };
1219 |
1220 | /**
1221 | *
1222 | * If an argument is given, sets the inner HTML of the element,
1223 | * replacing any existing html. If true is included as a second
1224 | * argument, html is appended instead of replacing existing html.
1225 | * If no arguments are given, returns
1226 | * the inner HTML of the element.
1227 | *
1228 | * @for p5.Element
1229 | * @method html
1230 | * @param {String} [html] the HTML to be placed inside the element
1231 | * @param {boolean} [append] whether to append HTML to existing
1232 | * @return {Object|p5.Element|String}
1233 | * @example
1234 | *
1235 | * var div = createDiv('').size(100,100);
1236 | * div.html('hi');
1237 | *
1238 | *
1239 | * var div = createDiv('Hello ').size(100,100);
1240 | * div.html('World', true);
1241 | *
1242 | */
1243 | p5.Element.prototype.html = function() {
1244 | if (arguments.length === 0) {
1245 | return this.elt.innerHTML;
1246 | } else if (arguments[1]) {
1247 | this.elt.innerHTML += arguments[0];
1248 | return this;
1249 | } else {
1250 | this.elt.innerHTML = arguments[0];
1251 | return this;
1252 | }
1253 | };
1254 |
1255 | /**
1256 | *
1257 | * Sets the position of the element relative to (0, 0) of the
1258 | * window. Essentially, sets position:absolute and left and top
1259 | * properties of style. If no arguments given returns the x and y position
1260 | * of the element in an object.
1261 | *
1262 | * @method position
1263 | * @param {Number} [x] x-position relative to upper left of window
1264 | * @param {Number} [y] y-position relative to upper left of window
1265 | * @return {Object|p5.Element}
1266 | * @example
1267 | *
1268 | * function setup() {
1269 | * var cnv = createCanvas(100, 100);
1270 | * // positions canvas 50px to the right and 100px
1271 | * // below upper left corner of the window
1272 | * cnv.position(50, 100);
1273 | * }
1274 | *
1275 | */
1276 | p5.Element.prototype.position = function() {
1277 | if (arguments.length === 0){
1278 | return { 'x' : this.elt.offsetLeft , 'y' : this.elt.offsetTop };
1279 | }else{
1280 | this.elt.style.position = 'absolute';
1281 | this.elt.style.left = arguments[0]+'px';
1282 | this.elt.style.top = arguments[1]+'px';
1283 | this.x = arguments[0];
1284 | this.y = arguments[1];
1285 | return this;
1286 | }
1287 | };
1288 |
1289 | /* Helper method called by p5.Element.style() */
1290 | p5.Element.prototype._translate = function(){
1291 | this.elt.style.position = 'absolute';
1292 | // save out initial non-translate transform styling
1293 | var transform = '';
1294 | if (this.elt.style.transform) {
1295 | transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
1296 | transform = transform.replace(/translate[X-Z]?\(.*\)/g, '');
1297 | }
1298 | if (arguments.length === 2) {
1299 | this.elt.style.transform = 'translate('+arguments[0]+'px, '+arguments[1]+'px)';
1300 | } else if (arguments.length > 2) {
1301 | this.elt.style.transform = 'translate3d('+arguments[0]+'px,'+arguments[1]+'px,'+arguments[2]+'px)';
1302 | if (arguments.length === 3) {
1303 | this.elt.parentElement.style.perspective = '1000px';
1304 | } else {
1305 | this.elt.parentElement.style.perspective = arguments[3]+'px';
1306 | }
1307 | }
1308 | // add any extra transform styling back on end
1309 | this.elt.style.transform += transform;
1310 | return this;
1311 | };
1312 |
1313 | /* Helper method called by p5.Element.style() */
1314 | p5.Element.prototype._rotate = function(){
1315 | // save out initial non-rotate transform styling
1316 | var transform = '';
1317 | if (this.elt.style.transform) {
1318 | var transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
1319 | transform = transform.replace(/rotate[X-Z]?\(.*\)/g, '');
1320 | }
1321 |
1322 | if (arguments.length === 1){
1323 | this.elt.style.transform = 'rotate('+arguments[0]+'deg)';
1324 | }else if (arguments.length === 2){
1325 | this.elt.style.transform = 'rotate('+arguments[0]+'deg, '+arguments[1]+'deg)';
1326 | }else if (arguments.length === 3){
1327 | this.elt.style.transform = 'rotateX('+arguments[0]+'deg)';
1328 | this.elt.style.transform += 'rotateY('+arguments[1]+'deg)';
1329 | this.elt.style.transform += 'rotateZ('+arguments[2]+'deg)';
1330 | }
1331 | // add remaining transform back on
1332 | this.elt.style.transform += transform;
1333 | return this;
1334 | };
1335 |
1336 | /**
1337 | * Sets the given style (css) property (1st arg) of the element with the
1338 | * given value (2nd arg). If a single argument is given, .style()
1339 | * returns the value of the given property; however, if the single argument
1340 | * is given in css syntax ('text-align:center'), .style() sets the css
1341 | * appropriatly. .style() also handles 2d and 3d css transforms. If
1342 | * the 1st arg is 'rotate', 'translate', or 'position', the following arguments
1343 | * accept Numbers as values. ('translate', 10, 100, 50);
1344 | *
1345 | * @method style
1346 | * @param {String} property property to be set
1347 | * @param {String|Number|p5.Color} [value] value to assign to property (only String|Number for rotate/translate)
1348 | * @return {String|Object|p5.Element} value of property, if no value is specified
1349 | * or p5.Element
1350 | * @example
1351 | *
1475 | */
1476 | p5.Element.prototype.removeAttribute = function(attr) {
1477 | this.elt.removeAttribute(attr);
1478 | return this;
1479 | };
1480 |
1481 |
1482 | /**
1483 | * Either returns the value of the element if no arguments
1484 | * given, or sets the value of the element.
1485 | *
1486 | * @method value
1487 | * @param {String|Number} [value]
1488 | * @return {String|Object|p5.Element} value of element if no value is specified or p5.Element
1489 | * @example
1490 | *
1491 | * // gets the value
1492 | * var inp;
1493 | * function setup() {
1494 | * inp = createInput('');
1495 | * }
1496 | *
1497 | * function mousePressed() {
1498 | * print(inp.value());
1499 | * }
1500 | *
1501 | *
1502 | * // sets the value
1503 | * var inp;
1504 | * function setup() {
1505 | * inp = createInput('myValue');
1506 | * }
1507 | *
1508 | * function mousePressed() {
1509 | * inp.value("myValue");
1510 | * }
1511 | *
1550 | * var div = createDiv('this is a div');
1551 | * div.hide();
1552 | *
1553 | */
1554 | p5.Element.prototype.hide = function() {
1555 | this.elt.style.display = 'none';
1556 | return this;
1557 | };
1558 |
1559 | /**
1560 | *
1561 | * Sets the width and height of the element. AUTO can be used to
1562 | * only adjust one dimension. If no arguments given returns the width and height
1563 | * of the element in an object.
1564 | *
1565 | * @method size
1566 | * @param {Number} [w] width of the element
1567 | * @param {Number} [h] height of the element
1568 | * @return {Object|p5.Element}
1569 | * @example
1570 | *
1571 | * var div = createDiv('this is a div');
1572 | * div.size(100, 100);
1573 | *
1574 | */
1575 | p5.Element.prototype.size = function(w, h) {
1576 | if (arguments.length === 0){
1577 | return { 'width' : this.elt.offsetWidth , 'height' : this.elt.offsetHeight };
1578 | }else{
1579 | var aW = w;
1580 | var aH = h;
1581 | var AUTO = p5.prototype.AUTO;
1582 | if (aW !== AUTO || aH !== AUTO) {
1583 | if (aW === AUTO) {
1584 | aW = h * this.width / this.height;
1585 | } else if (aH === AUTO) {
1586 | aH = w * this.height / this.width;
1587 | }
1588 | // set diff for cnv vs normal div
1589 | if (this.elt instanceof HTMLCanvasElement) {
1590 | var j = {};
1591 | var k = this.elt.getContext('2d');
1592 | for (var prop in k) {
1593 | j[prop] = k[prop];
1594 | }
1595 | this.elt.setAttribute('width', aW * this._pInst._pixelDensity);
1596 | this.elt.setAttribute('height', aH * this._pInst._pixelDensity);
1597 | this.elt.setAttribute('style', 'width:' + aW + 'px; height:' + aH + 'px');
1598 | this._pInst.scale(this._pInst._pixelDensity, this._pInst._pixelDensity);
1599 | for (var prop in j) {
1600 | this.elt.getContext('2d')[prop] = j[prop];
1601 | }
1602 | } else {
1603 | this.elt.style.width = aW+'px';
1604 | this.elt.style.height = aH+'px';
1605 | this.elt.width = aW;
1606 | this.elt.height = aH;
1607 | this.width = aW;
1608 | this.height = aH;
1609 | }
1610 |
1611 | this.width = this.elt.offsetWidth;
1612 | this.height = this.elt.offsetHeight;
1613 |
1614 | if (this._pInst) { // main canvas associated with p5 instance
1615 | if (this._pInst._curElement.elt === this.elt) {
1616 | this._pInst._setProperty('width', this.elt.offsetWidth);
1617 | this._pInst._setProperty('height', this.elt.offsetHeight);
1618 | }
1619 | }
1620 | }
1621 | return this;
1622 | }
1623 | };
1624 |
1625 | /**
1626 | * Removes the element and deregisters all listeners.
1627 | * @method remove
1628 | * @example
1629 | *
1630 | * var myDiv = createDiv('this is some text');
1631 | * myDiv.remove();
1632 | *
1633 | */
1634 | p5.Element.prototype.remove = function() {
1635 | // deregister events
1636 | for (var ev in this._events) {
1637 | this.elt.removeEventListener(ev, this._events[ev]);
1638 | }
1639 | if (this.elt.parentNode) {
1640 | this.elt.parentNode.removeChild(this.elt);
1641 | }
1642 | delete(this);
1643 | };
1644 |
1645 |
1646 |
1647 | // =============================================================================
1648 | // p5.MediaElement additions
1649 | // =============================================================================
1650 |
1651 |
1652 | /**
1653 | * Extends p5.Element to handle audio and video. In addition to the methods
1654 | * of p5.Element, it also contains methods for controlling media. It is not
1655 | * called directly, but p5.MediaElements are created by calling createVideo,
1656 | * createAudio, and createCapture.
1657 | *
1658 | * @class p5.MediaElement
1659 | * @constructor
1660 | * @param {String} elt DOM node that is wrapped
1661 | * @param {Object} [pInst] pointer to p5 instance
1662 | */
1663 | p5.MediaElement = function(elt, pInst) {
1664 | p5.Element.call(this, elt, pInst);
1665 |
1666 | var self = this;
1667 | this.elt.crossOrigin = 'anonymous';
1668 |
1669 | this._prevTime = 0;
1670 | this._cueIDCounter = 0;
1671 | this._cues = [];
1672 | this._pixelDensity = 1;
1673 |
1674 | /**
1675 | * Path to the media element source.
1676 | *
1677 | * @property src
1678 | * @return {String} src
1679 | */
1680 | Object.defineProperty(self, 'src', {
1681 | get: function() {
1682 | var firstChildSrc = self.elt.children[0].src;
1683 | var srcVal = self.elt.src === window.location.href ? '' : self.elt.src;
1684 | var ret = firstChildSrc === window.location.href ? srcVal : firstChildSrc;
1685 | return ret;
1686 | },
1687 | set: function(newValue) {
1688 | for (var i = 0; i < self.elt.children.length; i++) {
1689 | self.elt.removeChild(self.elt.children[i]);
1690 | }
1691 | var source = document.createElement('source');
1692 | source.src = newValue;
1693 | elt.appendChild(source);
1694 | self.elt.src = newValue;
1695 | },
1696 | });
1697 |
1698 | // private _onended callback, set by the method: onended(callback)
1699 | self._onended = function() {};
1700 | self.elt.onended = function() {
1701 | self._onended(self);
1702 | }
1703 | };
1704 | p5.MediaElement.prototype = Object.create(p5.Element.prototype);
1705 |
1706 |
1707 |
1708 |
1709 | /**
1710 | * Play an HTML5 media element.
1711 | *
1712 | * @method play
1713 | * @return {Object|p5.Element}
1714 | */
1715 | p5.MediaElement.prototype.play = function() {
1716 | if (this.elt.currentTime === this.elt.duration) {
1717 | this.elt.currentTime = 0;
1718 | }
1719 |
1720 | if (this.elt.readyState > 1) {
1721 | this.elt.play();
1722 | } else {
1723 | // in Chrome, playback cannot resume after being stopped and must reload
1724 | this.elt.load();
1725 | this.elt.play();
1726 | }
1727 | return this;
1728 | };
1729 |
1730 | /**
1731 | * Stops an HTML5 media element (sets current time to zero).
1732 | *
1733 | * @method stop
1734 | * @return {Object|p5.Element}
1735 | */
1736 | p5.MediaElement.prototype.stop = function() {
1737 | this.elt.pause();
1738 | this.elt.currentTime = 0;
1739 | return this;
1740 | };
1741 |
1742 | /**
1743 | * Pauses an HTML5 media element.
1744 | *
1745 | * @method pause
1746 | * @return {Object|p5.Element}
1747 | */
1748 | p5.MediaElement.prototype.pause = function() {
1749 | this.elt.pause();
1750 | return this;
1751 | };
1752 |
1753 | /**
1754 | * Set 'loop' to true for an HTML5 media element, and starts playing.
1755 | *
1756 | * @method loop
1757 | * @return {Object|p5.Element}
1758 | */
1759 | p5.MediaElement.prototype.loop = function() {
1760 | this.elt.setAttribute('loop', true);
1761 | this.play();
1762 | return this;
1763 | };
1764 | /**
1765 | * Set 'loop' to false for an HTML5 media element. Element will stop
1766 | * when it reaches the end.
1767 | *
1768 | * @method noLoop
1769 | * @return {Object|p5.Element}
1770 | */
1771 | p5.MediaElement.prototype.noLoop = function() {
1772 | this.elt.setAttribute('loop', false);
1773 | return this;
1774 | };
1775 |
1776 |
1777 | /**
1778 | * Set HTML5 media element to autoplay or not.
1779 | *
1780 | * @method autoplay
1781 | * @param {Boolean} autoplay whether the element should autoplay
1782 | * @return {Object|p5.Element}
1783 | */
1784 | p5.MediaElement.prototype.autoplay = function(val) {
1785 | this.elt.setAttribute('autoplay', val);
1786 | return this;
1787 | };
1788 |
1789 | /**
1790 | * Sets volume for this HTML5 media element. If no argument is given,
1791 | * returns the current volume.
1792 | *
1793 | * @param {Number} [val] volume between 0.0 and 1.0
1794 | * @return {Number|p5.MediaElement} current volume or p5.MediaElement
1795 | * @method volume
1796 | */
1797 | p5.MediaElement.prototype.volume = function(val) {
1798 | if (typeof val === 'undefined') {
1799 | return this.elt.volume;
1800 | } else {
1801 | this.elt.volume = val;
1802 | }
1803 | };
1804 |
1805 | /**
1806 | * If no arguments are given, returns the current playback speed of the
1807 | * element. The speed parameter sets the speed where 2.0 will play the
1808 | * element twice as fast, 0.5 will play at half the speed, and -1 will play
1809 | * the element in normal speed in reverse.(Note that not all browsers support
1810 | * backward playback and even if they do, playback might not be smooth.)
1811 | *
1812 | * @method speed
1813 | * @param {Number} [speed] speed multiplier for element playback
1814 | * @return {Number|Object|p5.MediaElement} current playback speed or p5.MediaElement
1815 | */
1816 | p5.MediaElement.prototype.speed = function(val) {
1817 | if (typeof val === 'undefined') {
1818 | return this.elt.playbackRate;
1819 | } else {
1820 | this.elt.playbackRate = val;
1821 | }
1822 | };
1823 |
1824 | /**
1825 | * If no arguments are given, returns the current time of the element.
1826 | * If an argument is given the current time of the element is set to it.
1827 | *
1828 | * @method time
1829 | * @param {Number} [time] time to jump to (in seconds)
1830 | * @return {Number|Object|p5.MediaElement} current time (in seconds)
1831 | * or p5.MediaElement
1832 | */
1833 | p5.MediaElement.prototype.time = function(val) {
1834 | if (typeof val === 'undefined') {
1835 | return this.elt.currentTime;
1836 | } else {
1837 | this.elt.currentTime = val;
1838 | }
1839 | };
1840 |
1841 | /**
1842 | * Returns the duration of the HTML5 media element.
1843 | *
1844 | * @method duration
1845 | * @return {Number} duration
1846 | */
1847 | p5.MediaElement.prototype.duration = function() {
1848 | return this.elt.duration;
1849 | };
1850 | p5.MediaElement.prototype.pixels = [];
1851 | p5.MediaElement.prototype.loadPixels = function() {
1852 | if (!this.canvas) {
1853 | this.canvas = document.createElement('canvas');
1854 | this.drawingContext = this.canvas.getContext('2d');
1855 | }
1856 | if (this.loadedmetadata) { // wait for metadata for w/h
1857 | if (this.canvas.width !== this.elt.width) {
1858 | this.canvas.width = this.elt.width;
1859 | this.canvas.height = this.elt.height;
1860 | this.width = this.canvas.width;
1861 | this.height = this.canvas.height;
1862 | }
1863 | this.drawingContext.drawImage(this.elt, 0, 0, this.canvas.width, this.canvas.height);
1864 | p5.Renderer2D.prototype.loadPixels.call(this);
1865 | }
1866 | return this;
1867 | }
1868 | p5.MediaElement.prototype.updatePixels = function(x, y, w, h){
1869 | if (this.loadedmetadata) { // wait for metadata
1870 | p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
1871 | }
1872 | return this;
1873 | }
1874 | p5.MediaElement.prototype.get = function(x, y, w, h){
1875 | if (this.loadedmetadata) { // wait for metadata
1876 | return p5.Renderer2D.prototype.get.call(this, x, y, w, h);
1877 | } else if (typeof x === 'undefined') {
1878 | return new p5.Image(1, 1);
1879 | } else if (w > 1) {
1880 | return new p5.Image(x, y, w, h);
1881 | } else {
1882 | return [0, 0, 0, 255];
1883 | }
1884 | };
1885 | p5.MediaElement.prototype.set = function(x, y, imgOrCol){
1886 | if (this.loadedmetadata) { // wait for metadata
1887 | p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
1888 | }
1889 | };
1890 | p5.MediaElement.prototype.copy = function(){
1891 | p5.Renderer2D.prototype.copy.apply(this, arguments);
1892 | };
1893 | p5.MediaElement.prototype.mask = function(){
1894 | this.loadPixels();
1895 | p5.Image.prototype.mask.apply(this, arguments);
1896 | };
1897 | /**
1898 | * Schedule an event to be called when the audio or video
1899 | * element reaches the end. If the element is looping,
1900 | * this will not be called. The element is passed in
1901 | * as the argument to the onended callback.
1902 | *
1903 | * @method onended
1904 | * @param {Function} callback function to call when the
1905 | * soundfile has ended. The
1906 | * media element will be passed
1907 | * in as the argument to the
1908 | * callback.
1909 | * @return {Object|p5.MediaElement}
1910 | * @example
1911 | *
1922 | */
1923 | p5.MediaElement.prototype.onended = function(callback) {
1924 | this._onended = callback;
1925 | return this;
1926 | };
1927 |
1928 |
1929 | /*** CONNECT TO WEB AUDIO API / p5.sound.js ***/
1930 |
1931 | /**
1932 | * Send the audio output of this element to a specified audioNode or
1933 | * p5.sound object. If no element is provided, connects to p5's master
1934 | * output. That connection is established when this method is first called.
1935 | * All connections are removed by the .disconnect() method.
1936 | *
1937 | * This method is meant to be used with the p5.sound.js addon library.
1938 | *
1939 | * @method connect
1940 | * @param {AudioNode|p5.sound object} audioNode AudioNode from the Web Audio API,
1941 | * or an object from the p5.sound library
1942 | */
1943 | p5.MediaElement.prototype.connect = function(obj) {
1944 | var audioContext, masterOutput;
1945 |
1946 | // if p5.sound exists, same audio context
1947 | if (typeof p5.prototype.getAudioContext === 'function') {
1948 | audioContext = p5.prototype.getAudioContext();
1949 | masterOutput = p5.soundOut.input;
1950 | } else {
1951 | try {
1952 | audioContext = obj.context;
1953 | masterOutput = audioContext.destination
1954 | } catch(e) {
1955 | throw 'connect() is meant to be used with Web Audio API or p5.sound.js'
1956 | }
1957 | }
1958 |
1959 | // create a Web Audio MediaElementAudioSourceNode if none already exists
1960 | if (!this.audioSourceNode) {
1961 | this.audioSourceNode = audioContext.createMediaElementSource(this.elt);
1962 |
1963 | // connect to master output when this method is first called
1964 | this.audioSourceNode.connect(masterOutput);
1965 | }
1966 |
1967 | // connect to object if provided
1968 | if (obj) {
1969 | if (obj.input) {
1970 | this.audioSourceNode.connect(obj.input);
1971 | } else {
1972 | this.audioSourceNode.connect(obj);
1973 | }
1974 | }
1975 |
1976 | // otherwise connect to master output of p5.sound / AudioContext
1977 | else {
1978 | this.audioSourceNode.connect(masterOutput);
1979 | }
1980 |
1981 | };
1982 |
1983 | /**
1984 | * Disconnect all Web Audio routing, including to master output.
1985 | * This is useful if you want to re-route the output through
1986 | * audio effects, for example.
1987 | *
1988 | * @method disconnect
1989 | */
1990 | p5.MediaElement.prototype.disconnect = function() {
1991 | if (this.audioSourceNode) {
1992 | this.audioSourceNode.disconnect();
1993 | } else {
1994 | throw 'nothing to disconnect';
1995 | }
1996 | };
1997 |
1998 |
1999 | /*** SHOW / HIDE CONTROLS ***/
2000 |
2001 | /**
2002 | * Show the default MediaElement controls, as determined by the web browser.
2003 | *
2004 | * @method showControls
2005 | */
2006 | p5.MediaElement.prototype.showControls = function() {
2007 | // must set style for the element to show on the page
2008 | this.elt.style['text-align'] = 'inherit';
2009 | this.elt.controls = true;
2010 | };
2011 |
2012 | /**
2013 | * Hide the default mediaElement controls.
2014 | *
2015 | * @method hideControls
2016 | */
2017 | p5.MediaElement.prototype.hideControls = function() {
2018 | this.elt.controls = false;
2019 | };
2020 |
2021 | /*** SCHEDULE EVENTS ***/
2022 |
2023 | /**
2024 | * Schedule events to trigger every time a MediaElement
2025 | * (audio/video) reaches a playback cue point.
2026 | *
2027 | * Accepts a callback function, a time (in seconds) at which to trigger
2028 | * the callback, and an optional parameter for the callback.
2029 | *
2030 | * Time will be passed as the first parameter to the callback function,
2031 | * and param will be the second parameter.
2032 | *
2033 | *
2034 | * @method addCue
2035 | * @param {Number} time Time in seconds, relative to this media
2036 | * element's playback. For example, to trigger
2037 | * an event every time playback reaches two
2038 | * seconds, pass in the number 2. This will be
2039 | * passed as the first parameter to
2040 | * the callback function.
2041 | * @param {Function} callback Name of a function that will be
2042 | * called at the given time. The callback will
2043 | * receive time and (optionally) param as its
2044 | * two parameters.
2045 | * @param {Object} [value] An object to be passed as the
2046 | * second parameter to the
2047 | * callback function.
2048 | * @return {Number} id ID of this cue,
2049 | * useful for removeCue(id)
2050 | * @example
2051 | *