Improve docs, and add tool for sending example data (#16)
* config_example.conf: add documentation * README.md: add more technical details * add respondd-sim.py script, for sending test data back to FFMan Also add some setup/usage notes for the script. The respondd-sim.py script is based on the respondd.py script from https://github.com/ffnord/mesh-announce .
This commit is contained in:
parent
e063fa2e12
commit
60bc6049b9
35
README.md
35
README.md
|
@ -72,9 +72,36 @@ Navigation bar at top of page:
|
||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
List of known access points will be retrieved from [Yanic](https://github.com/FreifunkBremen/yanic) (ie. representing live data from APs).
|
List of known nodes will be retrieved with the [respondd](https://github.com/freifunk-gluon/packages/tree/master/net/respondd) protocol (ie. by periodic UDP multicast requests to all reachable nodes). For this, FFMan uses a built-in [Yanic](https://github.com/FreifunkBremen/yanic) instance. The respondd protocol provides configuration details and statistics for each node.
|
||||||
Additionally, APs can be added manually by visiting a page like /#/n/apname (where "apname" is the node-id of the new AP), and then setting a hostname.
|
|
||||||
|
|
||||||
Each browser tab has a websocket connection to the server, so changes made in one tab will appear immediately in other tabs as well
|
Alternatively, FFMan can also be configured to just listen to respondd responses (without sending requests); this is useful to "listen in" on the responses requested by a separate Yanic process running on the same network. This mode can be enabled by setting `yanic_collect_interval` to `0s` and settings `yanic.send_no_request` to `true`.
|
||||||
|
|
||||||
All changes are saved to state file (eg. /tmp/freifunkmanager.json - can be changed in config file).
|
Additionally, nodes can be added manually by visiting a page like /#/n/apname (where "apname" is the node-id of the new device), and then setting a hostname.
|
||||||
|
|
||||||
|
The web interface displays all nodes that were found (except for nodes which don't respond to SSH - these are blacklisted). The web interface is updated live, by using a websocket connection; this also means that changes made in one tab will appear immediately in other tabs as well.
|
||||||
|
|
||||||
|
When node settings are changed in the web interface, an SSH connection is opened to the node to apply the new settings.
|
||||||
|
|
||||||
|
All changes are also saved to a state file (eg. /tmp/freifunkmanager.json - can be changed in config file).
|
||||||
|
And all of the received node data is also stored in a database (see `db_type` and `db_connection` config options).
|
||||||
|
|
||||||
|
## Creating dummy respondd data
|
||||||
|
|
||||||
|
- create dummy "eth10" network interface:
|
||||||
|
```
|
||||||
|
sudo -s
|
||||||
|
modprobe dummy
|
||||||
|
ifconfig dummy0 hw ether 00:22:22:ff:ff:ff
|
||||||
|
ipaddr=`ipv6calc --in prefix+mac --action prefixmac2ipv6 --out ipv6addr fe80:: 00:22:22:ff:ff:ff`
|
||||||
|
ip a add dev dummy0 scope link $ipaddr/64
|
||||||
|
ip link set dummy0 up
|
||||||
|
```
|
||||||
|
|
||||||
|
- edit tools/example-response.json to set network addresses for each node that are reachable with SSH (otherwise the nodes will be blacklisted immediately)
|
||||||
|
- also, if necessary adjust the `ssh_ipaddress_prefix` setting in config_example.conf to match the addresses from example-response.json
|
||||||
|
- edit config_example.conf: in [yanic] section set ifname="dummy0"
|
||||||
|
- edit $GOPATH/src/github.com/FreifunkBremen/yanic/respond/respond.go: change port=10001
|
||||||
|
- go to $GOPATH/src/github.com/FreifunkBremen/freifunkmanager/ and run "go build"
|
||||||
|
- start FFMan as usual (`./freifunkmanager -config config_example.conf`)
|
||||||
|
- in another shell run `./tools/respondd-sim.py -p 10001 -i dummy0 -f tools/example-response.json`
|
||||||
|
- FFMan should now display two new nodes with the example hostnames
|
||||||
|
|
|
@ -1,27 +1,51 @@
|
||||||
|
# Database connection type (note: only "sqlite3" is supported at the moment)
|
||||||
db_type = "sqlite3"
|
db_type = "sqlite3"
|
||||||
|
# Database connection settings; see https://gorm.io/docs/connecting_to_the_database.html#Supported-Databases for details
|
||||||
db_connection = "/tmp/freifunkmanager.db"
|
db_connection = "/tmp/freifunkmanager.db"
|
||||||
|
|
||||||
|
# Address and port where HTTP server shall listen
|
||||||
webserver_bind = ":8080"
|
webserver_bind = ":8080"
|
||||||
|
# Root directory to serve via HTTP
|
||||||
webroot = "./webroot/"
|
webroot = "./webroot/"
|
||||||
|
|
||||||
|
# Password required for making changes in the web interface
|
||||||
secret = "passw0rd"
|
secret = "passw0rd"
|
||||||
|
|
||||||
|
# How long should a node remain on the blacklist after it has not responded to an SSH connection
|
||||||
|
# (nodes are blacklisted if they have sent updated respondd data but are not reachable by SSH)
|
||||||
blacklist_for = "1w"
|
blacklist_for = "1w"
|
||||||
|
|
||||||
|
# SSH key for logging in on nodes
|
||||||
ssh_key = "~/.ssh/id_rsa"
|
ssh_key = "~/.ssh/id_rsa"
|
||||||
|
# Only IP addresses starting with this prefix are used for SSH connection
|
||||||
ssh_ipaddress_prefix = "fd2f:"
|
ssh_ipaddress_prefix = "fd2f:"
|
||||||
|
# Timeout for SSH connections
|
||||||
ssh_timeout = "1m"
|
ssh_timeout = "1m"
|
||||||
|
|
||||||
# enable receiving
|
# If true, built-in Yanic instance will be used to request and collect respondd data from nodes
|
||||||
yanic_enable = true
|
yanic_enable = true
|
||||||
|
# If set, Yanic startup will be delayed until the next full minute (or hour or whatever is configured here)
|
||||||
# yanic_synchronize = "1m"
|
# yanic_synchronize = "1m"
|
||||||
|
# How often shall Yanic send respondd requests
|
||||||
yanic_collect_interval = "10s"
|
yanic_collect_interval = "10s"
|
||||||
|
|
||||||
|
# More settings for the built-in Yanic instance
|
||||||
[yanic]
|
[yanic]
|
||||||
|
# Interface on which Yanic will send out respondd requests
|
||||||
ifname = "wlp4s0"
|
ifname = "wlp4s0"
|
||||||
# e.g. to receive data of real yanic
|
# e.g. to receive data of real yanic
|
||||||
# - please also disable `yanic_collect_interval`
|
# - please also disable `yanic_collect_interval`
|
||||||
# ifname = "lo"
|
# ifname = "lo"
|
||||||
|
|
||||||
|
# If set, Yanic will listen for response packets on this address only.
|
||||||
# ip_address = "::1"
|
# ip_address = "::1"
|
||||||
|
|
||||||
|
# multicast address where respondd requests shall be sent. Default: ff05::2:1001
|
||||||
|
# multicast_address = "ff05::2:1001"
|
||||||
|
|
||||||
|
# If true, Yanic will not send own respondd request packets but will still listen for response packets
|
||||||
# send_no_request = true
|
# send_no_request = true
|
||||||
|
|
||||||
|
# Local UDP port where Yanic will listen for response packets. Default: a dynamically selected port
|
||||||
|
# (note: request packets to nodes will always be sent to port 1001, regardless of this setting)
|
||||||
# port = 1001
|
# port = 1001
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"nodeinfo": {
|
||||||
|
"node_id": "002222ff1001",
|
||||||
|
"hostname": "testnode_101",
|
||||||
|
"network" : {
|
||||||
|
"addresses" : [
|
||||||
|
"1234::5678",
|
||||||
|
"9012::3456"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeinfo": {
|
||||||
|
"node_id": "002222ff1002",
|
||||||
|
"hostname": "testnode_102",
|
||||||
|
"network" : {
|
||||||
|
"addresses" : [
|
||||||
|
"1234::5678",
|
||||||
|
"9012::3456"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return pre-generated respondd responses (read from a JSON file).
|
||||||
|
# The JSON file must contain a list of responses (one list entry for each simulated node).
|
||||||
|
#
|
||||||
|
|
||||||
|
import socketserver
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import json
|
||||||
|
from zlib import compress
|
||||||
|
|
||||||
|
|
||||||
|
def get_handler(inputFile):
|
||||||
|
class ResponddUDPHandler(socketserver.BaseRequestHandler):
|
||||||
|
def multi_request(self, providernames, nodeData):
|
||||||
|
ret = {}
|
||||||
|
for name in providernames:
|
||||||
|
if name in nodeData:
|
||||||
|
ret[name] = nodeData[name]
|
||||||
|
|
||||||
|
print("reponse: %s" % ret)
|
||||||
|
return compress(str.encode(json.dumps(ret)))[2:-4]
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
requestString = self.request[0].decode('UTF-8').strip()
|
||||||
|
socket = self.request[1]
|
||||||
|
|
||||||
|
print("got request: '%s' from '%s'" % (requestString, self.client_address))
|
||||||
|
if not(requestString.startswith("GET ")):
|
||||||
|
print("unsupported old-style request")
|
||||||
|
return
|
||||||
|
|
||||||
|
fp = open(inputFile)
|
||||||
|
testData = json.load(fp)
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
response = None
|
||||||
|
for nodeData in testData:
|
||||||
|
response = self.multi_request(requestString.split(" ")[1:], nodeData)
|
||||||
|
socket.sendto(response, self.client_address)
|
||||||
|
return ResponddUDPHandler
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-p', dest='port',
|
||||||
|
default=1001, type=int, metavar='<port>',
|
||||||
|
help='port number to listen on (default 1001)')
|
||||||
|
parser.add_argument('-g', dest='group',
|
||||||
|
default='ff02::2:1001', metavar='<group>',
|
||||||
|
help='multicast group (default ff02::2:1001)')
|
||||||
|
parser.add_argument('-i', dest='mcast_ifaces',
|
||||||
|
action='append', metavar='<iface>',
|
||||||
|
help='interface on which the group is joined')
|
||||||
|
parser.add_argument('-f', dest='input_file',
|
||||||
|
type=str, metavar='<file>',
|
||||||
|
required=True,
|
||||||
|
help='JSON file to read responses from')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
socketserver.ThreadingUDPServer.address_family = socket.AF_INET6
|
||||||
|
server = socketserver.ThreadingUDPServer(("", args.port), get_handler(args.input_file))
|
||||||
|
|
||||||
|
if args.mcast_ifaces:
|
||||||
|
group_bin = socket.inet_pton(socket.AF_INET6, args.group)
|
||||||
|
for (inf_id, inf_name) in socket.if_nameindex():
|
||||||
|
if inf_name in args.mcast_ifaces:
|
||||||
|
mreq = group_bin + struct.pack('@I', inf_id)
|
||||||
|
server.socket.setsockopt(
|
||||||
|
socket.IPPROTO_IPV6,
|
||||||
|
socket.IPV6_JOIN_GROUP,
|
||||||
|
mreq
|
||||||
|
)
|
||||||
|
|
||||||
|
server.serve_forever()
|
Loading…
Reference in New Issue