├── .gitignore ├── CONTRIBUTING.md ├── Changelog.md ├── LICENSE ├── README.md ├── VERSION ├── doc ├── README.md ├── archived │ ├── Alternative-mosquitto-use.md │ ├── Blocking-with-iptables.md │ ├── Configurable-items-in-Spin.md │ ├── Consistent-naming.md │ ├── Device-cache.md │ ├── Nodes-and-UI.md │ ├── RPC-binding.md │ ├── RPC-mechanisms.md │ ├── SpinSuggestions.md │ ├── Usermode-spind.md │ ├── code-cleanup.md │ ├── mud_implementation_overview.md │ └── performance_tests.md ├── code │ ├── Flow-statistics.md │ ├── JSONRPC-in-Spind.md │ ├── Statistics.md │ ├── external_source.md │ └── mqtt_protocol.md ├── images │ ├── mobilescreenshot201703012248.png │ ├── prototype-20170103.png │ └── screenshot20170301-2244.png ├── proposals │ ├── new_repos_layout_proposal.md │ └── provider_api.txt ├── to_update │ ├── Spin-API.md │ ├── Table-of-software-interfaces.md │ ├── install_package.md │ └── web_api.md └── user │ ├── DOTS_prototype.md │ ├── pcap_reader.md │ └── spin_configuration.md ├── scripts ├── create_debian_tarball.sh ├── create_tarball.sh ├── create_version_c.sh ├── dots_request.py ├── pkg_create_tarball.sh ├── spin_jrpc_client.py └── spin_list_ips └── src ├── Makefile.am ├── configure.ac ├── include ├── arp.h ├── cJSON.h ├── dns.h ├── dns_cache.h ├── extsrc.h ├── ipl.h ├── jsmn.h ├── netlink_commands.h ├── node_cache.h ├── node_names.h ├── pkt_info.h ├── spin_cfg.h ├── spin_config.h ├── spin_list.h ├── spin_log.h ├── spindata.h ├── spindata_type.h ├── spinhook.h ├── statistics.h ├── tree.h ├── util.h └── version.h ├── lib ├── Makefile.am ├── arp.c ├── dns.c ├── dns_cache.c ├── extsrc.c ├── ip_store.c ├── ip_store.h ├── ipl.c ├── jsmn.c ├── node_cache.c ├── node_names.c ├── pkt_info.c ├── spin_config_common.c ├── spin_config_uci.c ├── spin_list.c ├── spin_log.c ├── spindata_type.c ├── statistics.c ├── tests │ ├── Makefile.am │ ├── arp_test.c │ ├── disable_statistics.h │ ├── dns_cache_test.c │ ├── netlink_commands_test.c │ ├── node_cache_test.c │ ├── node_names_test.c │ ├── run_tests.sh │ ├── test_helper.h │ ├── testdata │ │ ├── node_names_dhcp.conf │ │ └── node_names_spin_userdata.conf │ ├── tree_test.c │ └── util_test.c ├── tree.c └── util.c ├── lua ├── arp.lua ├── collect.lua ├── config.lua ├── dns_cache.lua ├── incident_report_listener.lua ├── incident_report_tool.lua ├── json.lua ├── luamud.lua ├── mio.lua ├── node_cache.lua ├── show_ips.lua ├── spin_enforcer.lua ├── spin_mudd.lua ├── tests │ ├── mud_dns_only.json │ ├── test.json │ ├── test_mud.lua │ └── testdata │ │ └── firewall1.config ├── util.lua └── wirefmt.lua ├── m4 └── ax_lua.m4 ├── spind ├── Makefile.am ├── cJSON.c ├── core2block.c ├── core2block.h ├── core2conntrack.c ├── core2conntrack.h ├── core2extsrc.c ├── core2extsrc.h ├── core2nflog_dns.c ├── core2nflog_dns.h ├── core2pubsub.c ├── core2pubsub.h ├── dnshooks.c ├── dnshooks.h ├── dots.c ├── dots.h ├── jsondump.c ├── mainloop.c ├── mainloop.h ├── nflogroutines.c ├── nflogroutines.h ├── nfqroutines.c ├── nfqroutines.h ├── process_pkt_info.c ├── process_pkt_info.h ├── rpc_calls.c ├── rpc_calls.h ├── rpc_common.c ├── rpc_common.h ├── rpc_json.c ├── rpc_json.h ├── rpc_ubus.c ├── spind.c ├── spind.h ├── spindata.c ├── spinhook.c └── statistics.c ├── spinweb ├── Makefile.am ├── files.c ├── files.h ├── rpc_client.c ├── rpc_client.h ├── rpc_ubus_client.c ├── rpc_ubus_client.h ├── spinweb.c ├── static │ ├── index.html │ └── spin_graph │ │ ├── css │ │ ├── bootstrap-4.3.1.min.css │ │ ├── bootstrap-select-1.13.14.min.css │ │ ├── debug.css │ │ └── spin.css │ │ ├── graph.html │ │ ├── img │ │ ├── favicon.ico │ │ └── sidnlabs_logo.png │ │ ├── index.html │ │ └── js │ │ ├── bootstrap-4.3.1.bundle.min.js.gz │ │ ├── bootstrap-select-1.13.14.min.js.gz │ │ ├── jquery-3.1.1.min.js.gz │ │ ├── jquery-3.6.0.min.js.gz │ │ ├── jquery-ui-1.13.0.custom │ │ ├── AUTHORS.txt │ │ ├── LICENSE.txt │ │ ├── external │ │ │ └── jquery │ │ │ │ └── jquery.js │ │ ├── images │ │ │ ├── ui-icons_444444_256x240.png │ │ │ ├── ui-icons_555555_256x240.png │ │ │ ├── ui-icons_777620_256x240.png │ │ │ ├── ui-icons_777777_256x240.png │ │ │ ├── ui-icons_cc0000_256x240.png │ │ │ └── ui-icons_ffffff_256x240.png │ │ ├── index.html │ │ ├── jquery-ui.css │ │ ├── jquery-ui.js │ │ ├── jquery-ui.min.css │ │ ├── jquery-ui.min.js │ │ ├── jquery-ui.structure.css │ │ ├── jquery-ui.structure.min.css │ │ ├── jquery-ui.theme.css │ │ ├── jquery-ui.theme.min.css │ │ └── package.json │ │ ├── mqtt_client.js │ │ ├── mqttws31.js.gz │ │ ├── paho-mqtt.js.gz │ │ ├── ponyfill.min.js.gz │ │ ├── port_services.js │ │ ├── spingraphs.js │ │ └── vis │ │ ├── moment.js.gz │ │ ├── vis-data.min.js.gz │ │ ├── vis-network.min.js.gz │ │ ├── vis-timeline-graph2d.min.css.gz │ │ └── vis-timeline-graph2d.min.js.gz ├── templates │ ├── base.html │ ├── capture.html │ ├── index.html │ ├── tcpdump.html │ └── tcpdump_status.html ├── traffic_capture.c └── traffic_capture.h └── tools ├── Makefile.am ├── peak-detection ├── anomaly.go ├── history.go ├── main.go ├── mips.txt ├── mqtt.go └── persistence.go ├── profile-util ├── .gitignore ├── README ├── db.schema ├── enum.lua ├── enums.lua ├── fw.lua ├── generate-fw.lua ├── generate-profile.lua ├── mqtt_nm.lua ├── nm.lua ├── profile.lua ├── rsync-valibox.sh ├── stdin_nm.lua └── util_validate.lua └── spin-pcap-reader ├── .gitignore ├── Makefile.am ├── README ├── arpupdate.c ├── arpupdate.h ├── external ├── external.h ├── extract.h ├── interface.h └── tree.h ├── fuzzing ├── FUZZING └── afl │ ├── README │ └── afl.sh ├── ipt.c ├── ipt.h ├── macstr.c ├── macstr.h ├── pcap.c ├── sleep.c ├── sleep.h ├── socket.c ├── socket.h └── spinhook.c /.gitignore: -------------------------------------------------------------------------------- 1 | aclocal.m4 2 | autom4te.cache/ 3 | build-aux/ 4 | config.h 5 | config.h.in 6 | config.log 7 | config.status 8 | configure 9 | libtool 10 | Makefile 11 | Makefile.in 12 | .deps 13 | src/tools/spin_config/spin_config 14 | src/tools/spin_print/spin_print 15 | stamp-h1 16 | *.ko 17 | *.ko.cmd 18 | *.a 19 | *.la 20 | *.o 21 | *.o.cmd 22 | *.mod.c 23 | publish.sh 24 | src/tests/*_test 25 | *.gcov 26 | *.gcda 27 | *.gcno 28 | src/lib/version.c 29 | src/lib/.deps 30 | src/m4/ 31 | .idea/ 32 | tags 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | SPIN uses GitHub to manage reviews of pull requests. 4 | 5 | * If you have a trivial fix or improvement, go ahead and create a pull 6 | request. 7 | 8 | * If you find a bug, like to suggest an enhancement or have any other issue, please create an issue in Github. 9 | 10 | * If you plan to get otherwise involved, for example discuss your ideas, there is also a 11 | [mailing list](https://mailman.sidn.nl/cgi-bin/mailman/listinfo/spin). 12 | This will avoid unnecessary work and surely give you and us a good deal 13 | of inspiration. 14 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | 2 | # SPIN documentation 3 | 4 | This is the documentation for the SPIN project. The documentation is structured as follows: 5 | 6 | * [User documentation](./user/) Documentation about configuring and running SPIN 7 | * [Code](./code/) Documentation about code internals, APIs and protocols 8 | * [Proposals](./proposals) (Internal code) proposals and plans 9 | * [Archived](./archived) Archived and obsolete documentation from any of the above sections 10 | * [To Update](./to_update) Documents that are still relevant in terms of content, but need updating -------------------------------------------------------------------------------- /doc/archived/Alternative-mosquitto-use.md: -------------------------------------------------------------------------------- 1 | # Usage of Mosquitto 2 | 3 | We might be able to use Mosquitto more efficient while solving our current problem with big nodes, which would need the traffic topic to be changed anyhow. 4 | 5 | **This document is a proposal to be verified by stakeholders.** 6 | 7 | ## Current Usage 8 | 9 | Mosquitto uses a hierarchy of topics with a / as separator. 10 | We until recently used two topics *SPIN/commands* and *SPIN/traffic*. 11 | 12 | *SPIN/commands* is used as a sort of RPC procedure call topic, and *SPIN/traffic* contains traffic reports and results of requests done on the command topic. 13 | We currently also use *SPIN/stat* for statistic reports but this could change. 14 | 15 | ## Mosquitto possibilities 16 | 17 | Mosquitto allows you to subscribe to multiple topics. 18 | This is done by either giving multiple subscribe commands, one per topic, or using wildcards, for example you could subscribe to SPIN/# and receive messages for all current topics. 19 | When receiving messages you can, but do not have to, look at the topic they were posted to. 20 | 21 | Furthermore Mosquitto allows for one message per topic that is retained, meaning that if a retained message is sent to topic x, even while nobody is listening, as soon as someone subscribes to topic x he will get this retained message immediately followed by all new messages to topic x. 22 | 23 | This retained message can be deleted by posting an empty retained message. 24 | 25 | ## Proposal 26 | 27 | We split up the traffic topic: 28 | 29 | - SPIN/traffic/admin 30 | - SPIN/traffic/flow 31 | - SPIN/traffic/dns 32 | - SPIN/traffic/node/(nodenum) 33 | - SPIN/traffic/list/listname 34 | 35 | Backwards compatibility for all current listeners could be to listen to *SPIN/traffic/#* and get everything. 36 | 37 | Information published: 38 | 39 | On *SPIN/traffic/admin* administrative messages, including but not limited to messages about nodes ending existence, either by being merged into other nodes, or by lack of traffic. Also the serverRestart message is a good one for this. 40 | 41 | On *SPIN/traffic/list/allow* the current allowed list as a retained message, meaning all listeners do not have to send a command anymore to get the latest list. Same of course for block and ignore and all other lists we can come up with. Blocked flows perhaps? 42 | 43 | On *SPIN/traffic/node/1* the information about node 1. This is published as a retained message anytime the information about node 1 changes, perhaps slightly delayed(TBD). Any listeners to *SPIN/traffic/node/+* or more will always be up to date with all the latest node info. When a node disapperas the message will be replaced by the empty string. 44 | 45 | On *SPIN/traffic/flow* the flows seen recently. There will be no node information here, nodes will be just named by their number. 46 | 47 | On *SPIN/traffic/dns* the DNS requests. Again information about nodes will only be their number. 48 | 49 | This does not give Blocked messages their own place. Separate channel, or perhaps with admin or flow? 50 | 51 | For the statistics, assuming we keep them on Mosquitto, we could use something like: *SPIN/stat/module/counter* topics making it easy for someone just to query one counter. Making all counters retainable will get you the latest value with one call. -------------------------------------------------------------------------------- /doc/archived/Blocking-with-iptables.md: -------------------------------------------------------------------------------- 1 | #Implement block-list with iptables 2 | ###IPv4 only as example 3 | IPv6 can be done with same commands, but using ip6tables 4 | 5 | ###Ignore and except? 6 | Kernel module does not block packets depending on Block list but also on Ignore or Exept. Figure out intended meaning. 7 | - B is (Bs or Bd), same for I and E 8 | - If !B let through 9 | - if E let through with DEBUG print 10 | - if !I report DROP 11 | 12 | This document states the way to mimic exact same behaviour(minus the DEBUG print), but perhaps this should be flow-based. 13 | 14 | Above behaviour is from a blocking perspective identical to: 15 | - if E let through 16 | - if B block 17 | 18 | ###Iptables setup 19 | 20 | The idea is to make a set of chains to be called on each packet that will either block the packet(DROP in iptables lingo), or just return and let other chains decide. If the packet must be dropped another chain will make a decision on logging or not. 21 | 22 | - Make a new chain, let us call it SpinCheck 23 | 1 iptables -N SpinCheck 24 | 25 | - Add the chain to all standard chains 26 | 1 iptables -A INPUT -j SpinCheck 27 | 2 iptables -A OUTPUT -j SpinCheck 28 | 3 iptables -A FORWARD -j SpinCheck 29 | perhaps -I or so should be used to get the rule in front. Is this always OK? Try to find out the best place. 30 | 31 | - Make another chain for logging, let us call it SpinLog. This chain will log except for ignored addresses. 32 | 1 iptables -N SpinLog 33 | 2 iptables -A SpinLog -j NFQUEUE --queue-num 2 34 | The queueing command will stay at the end of the chain. We currently use queue number 2 for this. This will become configurable, but still the problem remains how we can use this while interacting with other software that uses Netfilter queues. The packets queued are handled by the Spin Daemon, which will report them and tell the kernel to DROP them. 35 | 36 | - For each address a.b.c.d to be ignored jump back from beginning of SpinLog, so logging will not occur. Another rule(see below) will still drop them. 37 | 1 iptable -I SpinLog -s a.b.c.d -j RETURN 38 | 2 iptable -I SpinLog -d a.b.c.d -j RETURN 39 | 40 | - Make another chain for actual blocking, let us call it SpinBlock. This list will end by calling SpinLog and then DROP the packet 41 | 1 iptables -N SpinBlock 42 | 2 iptables -A SpinBlock -j SpinLog 43 | 3 iptables -A SpinBlock -j DROP 44 | 45 | - For each address a.b.c.d to be blocked let SpinCheck call SpinBlock. 46 | 1 iptables -A SpinCheck -s a.b.c.d -j SpinBlock 47 | 2 iptables -A SpinCheck -d a.b.c.d -j SpinBlock 48 | This is the place to perhaps look at interfaces, and whether the address as source or destination matters. 49 | 50 | - For each address a.b.c.d to be excepted let SpinBlock return to prevent blocking. 51 | 1 iptables -I SpinBlock -s a.b.c.d -j RETURN 52 | 2 iptables -I SpinBlock -d a.b.c.d -j RETURN 53 | 54 | For unblock or unexcept or unignore do the same but with -D instead of -A or -I. 55 | 56 | This can be implemented with system("iptables ...") as a quick start, optimizing using proper library(libiptc?) after it works. 57 | 58 | This software will not use logging from the kernel, so there is no problem with growing files. 59 | 60 | ### Spin Daemon code 61 | Spin Daemon currently reads the queued packets and reports them through the Mosquitto interface. It reports the complete dropped packet, source, destination, ports et al. But there is not now, and not with the old code, a way to see which address caused the block. Potentially that could also be aggregated, like periodic reports about how many times certain addresses were blocked, in which direction, on what interface. This is surely for the TODO list. 62 | -------------------------------------------------------------------------------- /doc/archived/Consistent-naming.md: -------------------------------------------------------------------------------- 1 | # Standard naming in Spind 2 | 3 | The three lists are now officially named: 4 | BLOCK, IGNORE and ALLOW 5 | 6 | the old names FILTER and EXCEPT are deprecated 7 | 8 | ## Spind code 9 | The old names still occur, but only in the pubsub interface where they are used on the Mosquiito channels 10 | 11 | get_filters" PSC_V_GET, PSC_O_IGNORE}, 12 | get_blocks PSC_V_GET, PSC_O_BLOCK}, 13 | get_alloweds PSC_V_GET, PSC_O_ALLOW}, 14 | get_names PSC_V_GET, PSC_O_NAME }, 15 | add_filter PSC_V_ADD, PSC_O_IGNORE}, // Backw 16 | add_filter_node PSC_V_ADD, PSC_O_IGNORE}, 17 | add_name PSC_V_ADD, PSC_O_NAME}, 18 | add_block_node PSC_V_ADD, PSC_O_BLOCK}, 19 | add_allow_node PSC_V_ADD, PSC_O_ALLOW}, 20 | remove_filter PSC_V_REM_IP, PSC_O_IGNORE}, // Backw 21 | remove_filter_node PSC_V_REM, PSC_O_IGNORE}, 22 | remove_filter_ip PSC_V_REM_IP, PSC_O_IGNORE}, 23 | remove_block_node PSC_V_REM, PSC_O_BLOCK}, 24 | remove_block_ip PSC_V_REM_IP, PSC_O_BLOCK}, 25 | remove_allow_nodePSC_V_REM, PSC_O_ALLOW}, 26 | remove_allow_ip PSC_V_REM_IP, PSC_O_ALLOW}, 27 | reset_filters PSC_V_RESET, PSC_O_IGNORE}, 28 | 29 | Here there is also a discrepancy where filter means filter_ip in remove, but node in add 30 | 31 | Proposal: 32 | 33 | get_blocks 34 | get_ignores 35 | get_alloweds 36 | get_names 37 | 38 | add_block_node 39 | add_ignore_node 40 | add_allow_node 41 | add_name 42 | 43 | remove_block_node 44 | remove_ignore_node 45 | remove_allow_node 46 | 47 | remove_block_ip 48 | remove_ignore_ip 49 | remove_allow_ip 50 | 51 | reset_ignores (TODO, seems unimplemented at the moment) 52 | 53 | These changes need to be made to all Mosquitto talkers 54 | 55 | Furthermore in the bubble-app UI the word filter still occurs 56 | -------------------------------------------------------------------------------- /doc/archived/Device-cache.md: -------------------------------------------------------------------------------- 1 | # Node cache vs device_cache thingies 2 | 3 | ## Node cache 4 | 5 | Do we keep mac here? Name? 6 | MAXNODES? 7 | ip_refs? 8 | 9 | 10 | Separate devices is really clumsy. Cannot use nodenum, because dependent on which nodecache... 11 | 12 | Proposal: 13 | Make device info a separate structure pointed at by selected nodes: 14 | 15 | nodes are selected when they have a mac address, so mac address goes to device sub-struct. Name also? 16 | 17 | The RPC's for devices have the mac-address as identifier, so we make a tree, lookup on mac address info is nodenum(or pointer). 18 | 19 | When merging nodes, when one is a device merge into the device. 20 | Both devices cannot be possible, since device is identified by mac address 21 | -------------------------------------------------------------------------------- /doc/archived/RPC-binding.md: -------------------------------------------------------------------------------- 1 | # RPC binding in Spind 2 | 3 | Currently to implement an RPC in Spind it needs some small amount of code in rpc_json.c(and/or rpc_ubus.c) to get parameters of RPC and to call the actual work routine, and of course the work routine itself. 4 | 5 | Perhaps it is a good idea to let the module of the work routine register the routine to the RPC layer. This makes it possible to implement a varying set of RPC's depending on which code is running in spind. 6 | 7 | If we do that the description of parameters and result might need to be included. Currently we use a cJSON (nicknamed spin_data) struct both as input and as output. 8 | 9 | ## Current code 10 | 11 | This is now implemented, file rpc_common.h 12 | 13 | 14 | 15 | typedef enum { 16 | RPCAT_INT, 17 | RPCAT_STRING, 18 | RPCAT_COMPLEX 19 | } rpc_argtype; 20 | 21 | typedef struct { 22 | char * rpca_name; 23 | rpc_argtype rpca_type; 24 | } rpc_arg_desc_t; 25 | 26 | typedef union { 27 | int rpca_ivalue; 28 | char * rpca_svalue; 29 | spin_data rpca_cvalue; 30 | } rpc_arg_val_t; 31 | 32 | typedef struct { 33 | rpc_arg_desc_t rpc_desc; 34 | rpc_arg_val_t rpc_val; 35 | } rpc_arg_t; 36 | 37 | typedef int (*rpc_func_p)(rpc_arg_val_t *args, rpc_arg_val_t *result); 38 | 39 | void rpc_register(char *name, rpc_func_p func, int nargs, rpc_arg_desc_t *args, rpc_argtype result_type); 40 | 41 | Implement your to be called function as the type *rpc_func_p* and call rpc_register to set it up. 42 | 43 | Implementation(for now just JSON-RPC) will call the function with the correct arguments if a call has been made, and send the return value back. 44 | 45 | A test example: 46 | 47 | rpc_arg_desc_t tf_args[] = { 48 | { "arg1", RPCAT_INT }, 49 | { "arg2", RPCAT_STRING }, 50 | }; 51 | 52 | static int 53 | testfunc(rpc_arg_val_t *args, rpc_arg_val_t *result) { 54 | static char buf[100]; 55 | 56 | sprintf(buf, "Int:%d, String: %s", args[0].rpca_ivalue,args[1].rpca_svalue); 57 | result->rpca_svalue = buf; 58 | return 0; 59 | } 60 | 61 | // to be registered by the following call 62 | 63 | rpc_register("testfunc", testfunc, 2, tf_args, RPCAT_STRING); 64 | 65 | 66 | ## Ubus 67 | 68 | It is not so easy with Ubus. Currently all methods of Ubus are initialized at the same time, and you cannot add methods on the fly. 69 | 70 | So if you want to have the methods spin.spind.get_devices and spin.spind.blockflow both implemented you have to set them up together and make one call to initialize the spin.spind object. 71 | 72 | There are two ways to implement the same RPC binding mechanism as described above: 73 | - First do all the rpc_register calls for all the implemented RPC's, and after that have the common code call Ubus to set up the reception of the RPC's. This will mean another carefully placed init-call. 74 | - Make one Ubus method, spin.spind.rpc and when that is invoked get the real method and all the arguments from the JSON passed to that spin.spind.rpc method. 75 | 76 | This needs discussion. 77 | -------------------------------------------------------------------------------- /doc/archived/RPC-mechanisms.md: -------------------------------------------------------------------------------- 1 | # RPC mechanisms 2 | Various RPC mechanism are used and can be used in the SPIN system. A short discussion. 3 | 4 | ## Now 5 | 6 | The spind program (ab)uses the Mosquitto pub/sub system as an RPC mechanism, by sending JSON messages over the SPIN/commands channel. There are no real returns for this RPC, except that all commands to change lists send the new value of the list on the traffic channel. 7 | 8 | The web_ui uses a REST API to send commands with parameters partly encoded in the URL and partly sent as JSON data. Results are sent in JSON format over HTTP. 9 | 10 | ## Other mechanisms 11 | 12 | ### Ubus 13 | 14 | OpenWRT would like us to use ubus as an RPC mechanism. Ubus is a mechanism using serialized data structures, like JSON, but more compact and binary. 15 | Ubus rendez-vous is through a local ubusd program, so not networked. There is however a mechanism to connect through the uhttpd process somehow, which should make it available over the network. The uhttpd plugin uses JSON_RPC 2.0. 16 | Ubus contains a sort of broadcast mechanism to all listeners. 17 | 18 | The ubus system has a set of shell commands to talk to ubusd where all the calls are encoded in JSON. Easy for testing and interfacing to scripts. 19 | 20 | ### JSON-RPC 21 | 22 | A generic type of RPC mechanism called JSON-RPC is used in various projects. JSON-RPC is a relatively simple mechanism, and is transport agnostic. JSON-RPC often runs on HTTP making it look like the REST API of the web_ui. JSON-RPC could also run on simple TCP sockets, but there problems of rendez-vous and message delimitation have no standard solution. 23 | 24 | There is even a document explaining how to use JSON-RPC on top of Mosquitto making it look like the bubble-app to spind mechanism. 25 | 26 | JSON-RPC contains a notify mechanism, which is an RPC without result. 27 | 28 | JSON-RPC can be accessed using the standard *curl* program. Easy for testing and interfacing to scripts. 29 | 30 | ### Comparison 31 | 32 | Ubus and JSON-RPC seem to be more or less identical in expressability. 33 | JSON-RPC is more standard than Ubus, and is easier to run over a network, but it does not matter that much. 34 | 35 | Marshalling and de-marshalling code will undoubtedly differ and will have to be hidden from the rest of the code somehow. With the real RPC calls in SPIN arguments and results are relatively straightforward. The complex stuff such as nodes and flows currently only occur in the traffic mechanism. 36 | 37 | ## How to progress 38 | We should probably first define all our RPC calls in a way that does not depend on the RPC mechanism, let alone the transport. As long as the call and the return are implementable in JSON. 39 | 40 | Then we could choose our RPC mechanism depending on platform, just like we choose the configuration mechanism now. 41 | 42 | If we really go wild we could make a translation process that interfaces between RPC mechanisms. 43 | 44 | ## Performance considerations 45 | As far as I can see now we do not need to care very much about performance issues here. At least now we do not do too many RPC's per second. 46 | -------------------------------------------------------------------------------- /doc/archived/SpinSuggestions.md: -------------------------------------------------------------------------------- 1 | #Spin Suggestions for TODO 2 | Some stuff that seems useful 3 | 4 | *** 5 | 6 | ## Issues with node info (on Mosquitto traffic channel) 7 | 8 | Basically they grow too big 9 | 10 | ### Nodes IP address lists 11 | 12 | Nodes are collections of info of the same host. In a sense they are clouds of IP addresses and domain names that are deemed to be the same 13 | 14 | Especially with CDN nodes this can grow quite big. Two nodes with two lists of IP addresses will be merged into one if only one of the IP addresses overlap. 15 | 16 | This can be good or not, but it leads to a problem. 17 | The messages on the traffic channel, when presenting a node(maybe in a set of flows) will give all the information of a node, and this can be very large. We have already seen nodes with 200 IP addresses in short tests. Every time this node passes all this information gets on the channel. Sometimes the same node occurs more than once in one message, and even there all the information is duplicated. 18 | 19 | ### A possible solution 20 | We separate the information about nodes from the information that mentions the nodes. Basically instead of saying: 21 | 22 | Hey, I saw traffic from node A(IP:blah blah, domain blah blah) to node B(blah blah) 23 | 24 | We could say: 25 | 26 | Node A(blah blah) 27 | Node B(Blah) 28 | Hey I saw traffic from Node A to Node B 29 | 30 | and a further optimization could be to only send the information about the nodes if it changed from last time, or a certain amount of time has passed. This would be an optimization similar to video compression. 31 | 32 | *** 33 | 34 | ## Statistics 35 | 36 | To get an idea what the Spin Daemon is doing it would seem handy to add some statistics counters to the code, and a way to query/report them 37 | 38 | - Allocated/freed data structures 39 | - Packets going through 40 | - MQTT messages going through 41 | - Maximum node size(maybe buckets??) 42 | 43 | 44 | ## UI and Mosquitto 45 | 46 | The commands need to be cleaned up for new names etc. 47 | Perhaps a SPIN/statistics channel? 48 | 49 | ## Spind code 50 | 51 | List maintenance code in separate file? 52 | Better separation of Spin vs MQTT vs JSON code 53 | Destroy nodes after long idle time 54 | -------------------------------------------------------------------------------- /doc/archived/code-cleanup.md: -------------------------------------------------------------------------------- 1 | # Code cleanup in spind.c 2 | 3 | ### Cleaner code with lists 4 | The code maintains three lists, the block, ignore and allow list. All lists contain IP addresses. The code used to handle all lists separately but now they are handled roughly the same. The lists are numbered 0,1 and 2, for IPLIST_BLOCK, IPLIST_IGNORE and IPLIST_ALLOW. The boolean fields in the node_cache code for is_blocked and is_allowed are now aliases for one of three array fields, one per list. 5 | 6 | All calls dealing with lists are now the same, just specifying the list by number. The semantic differences are all handled in the code below. 7 | ###New files 8 | There are three new files, core2kernel.c, core2pubsub.c and core2block.c 9 | 10 | The idea is to separate the core code(list maintenance, etc) from the interfaces to the world 11 | - The core2kernel file implements the interface to the old kernel module using the netlink protocol 12 | - The core2pubsub file implements the Mosquitto interface to (among others) the bubble web application 13 | - The core2block file implements the new blocking code using iptables 14 | 15 | ### Code hooks 16 | All interfaces can attach to the core code by two registration functions: 17 | 18 | 1. The mainloop_register function. This can be called with a file descriptor and a timeout and will call back a function when either the filedescriptor has data or the timeout went off 19 | 2. The spin_register function. This can be called with an array of three integers(which lists am I interested in?) and will call back when one of the interesting lists has an entry added or deleted. 20 | 21 | Multiple subsystems can register with one or more of these registration functions. 22 | 23 | Initialization functions still need to be called from spind.c, in theory we could setup some sort of /etc/rc.d type of startup/shutdown. 24 | 25 | -------------------------------------------------------------------------------- /doc/archived/mud_implementation_overview.md: -------------------------------------------------------------------------------- 1 | 2 | MUD manager solution requirements. 3 | 4 | Some requirements to choose or build a mud manager. 5 | 6 | Potential Requirements: 7 | 1. available or compilable for OpenWRT 8 | 2. available or compilable for other operating systems 9 | 3. support for OpenWRT Firewall implementation 10 | 4. support for other firewall implementations 11 | 5. lateral traffic support not necessary (wouldn't be able to do it from SPIN either) 12 | 6. support for latest draft(s) 13 | 7. Completeness of support for MUD file format 14 | 8. support for DHCP servers 15 | * dnsmasq 16 | * odhcpd 17 | 9. licence 18 | 10. amount of work (relative) 19 | 11. support for extensions like merging or superceding/overriding profiles? 20 | 12. (internal abstract representation vs direct JSON parsing) 21 | 13. integreerbaarheid(even kijken wat we daar precies onder verstaan, deels license en upstreamen van changes, maar ook changes in het algemeen kunnen maken?) 22 | 14. hoe actief is het project 23 | 15. CPE-support (tegenhanger van de lateral/switch support) 24 | 25 | Potential solutions: 26 | 1. OSMud [https://osmud.org](https://osmud.org) 27 | 2. CIRA MUD Manager [https://github.com/CIRALabs/shg-mud-controller](https://github.com/CIRALabs/shg-mud-controller) 28 | 3. Cisco DevNet MUD Manager [https://github.com/CiscoDevNet/MUD-Manager](https://github.com/CiscoDevNet/MUD-Manager) 29 | 4. NIST-MUD [https://github.com/usnistgov/nist-mud](https://github.com/usnistgov/nist-mud) 30 | 5. Figshare [https://figshare.com/articles/Manufacturer_Usage_Description_Specification_Implementation/5552923](https://figshare.com/articles/Manufacturer_Usage_Description_Specification_Implementation/5552923) 31 | 7. Self-developed version 32 | 33 | (MasterPeace also worked on mud, but apparently contributed to osmud) 34 | 35 | | Name | Openwrt support | other OS | OpenWRT FW | Other FW | Lateral traffic | Latest draft/RFC? | Complete format support | 36 | |---|---|---|---|---|---|---|---| 37 | |osMUD | yes | linux, builds on ubuntu | yes | no (probably any iptables) | no | documentation mentions draft, not rfc | Unspecified | | | | | | | 38 | |CIRA SHG-MUD | yes | unknown | yes | unknown |no | unspecified | unspecified | 39 | | Cisco DevNet MUD Manager | no (might compile but nothing in docs) | ubuntu (probably all debian) | unspecified | cisco router? | no | unspecified | unspecified | 40 | | NIST-MUD | no | openflow-devices | no | openflow-devices | unknown (probably) | no | yes | 41 | | Figshare | no | no | no | no | no | no | no | 42 | |self-developed|yes|yes|yes|yes|no|yes|partial| 43 | 44 | 45 | | Name | DHCP support | Licence | Est. amount of work needed | Support for extensions | Internal abstraction vs direct json parsing | Integratability (fwiw) | Project activity | CPE support | 46 | |---|---|---|---|---|---|---|---|---| 47 | |osmud | [indirect](https://github.com/osmud/osmud/tree/master/src/dnsmasq) | Apache 2.0 | Reasonable (may need to contribute) | Yes (not built-in but source available) | direct parsing | active | decent | yes 48 | |cira | indirect | Apache 2.0 | Reasonable (may need to contribute) | Likely | Direct parsing | unspecified | active | yes 49 | |cisco | yes | BSD 3-clause | N/A | Unlikely | Direct parsing | hard (seems cisco-specific) | active | yes (assuming cisco) | 50 | |nist-mud| no | NIST public | N/A | Unlikely | YANG-derived models | hard (openflow-based) | active| no (targets switches) | 51 | |figshare| unknown | unknown | unknown| unknown| unknown | unknown | unknown | unknown | 52 | |self-developed| yes | GPL or BSD | A lot | Yes | Abstraction | Full | not active | yes | 53 | 54 | 55 | -------------------------------------------------------------------------------- /doc/code/JSONRPC-in-Spind.md: -------------------------------------------------------------------------------- 1 | # JSON-RPC in spind 2 | 3 | Spind has an interface for JSON-RPC calls. 4 | JSON-RPC is described in https://en.wikipedia.org/wiki/JSON-RPC and we implement version 2.0. 5 | 6 | The interface in spind is a routine 7 | 8 | char *call_string_jsonrpc(char *args); 9 | 10 | which gets a string, decodes it as a JSON-RPC, executes it, and returns a valid JSON-RPC reply string, or NULL if the JSON-RPC call was a notification. 11 | 12 | For testing purposes we currently get the call as a message on the Mosquitto topic SPIN/jsonrpc/q and we send the reply(if any) on SPIN/jsonrpc/a 13 | 14 | The JSON-RPC calls are expected to come from a lua server running on the same machine, so we could change this to communication on a UNIX-socket. 15 | 16 | There is already code to translate UBUS calls to JSON-RPC, but this code can maybe better move to the lua server. 17 | 18 | -------------------------------------------------------------------------------- /doc/code/Statistics.md: -------------------------------------------------------------------------------- 1 | # Statistics 2 | 3 | Gather and distribute statistics in a structured and efficient way 4 | 5 | Per counter a struct: 6 | 7 | Name of module 8 | Name of variable 9 | Type (total, max, ...?) 10 | Value (just an int) 11 | Count (just an int) 12 | Flags and pointers or whatever 13 | 14 | 15 | The fields value and count together can be used for averages in UI. 16 | In code this just looks like 17 | 18 | #include "statistics.h" 19 | STAT_MODULE(module-name) 20 | 21 | at the top of the module, and for each counter a definition(inside or outside a function) 22 | 23 | STAT_COUNTER(ctr1, counter1-name, STAT_TOTAL); 24 | STAT_COUNTER(ctr2, counter2-name, STAT_MAX); 25 | 26 | calls while running 27 | 28 | STAT_VALUE(ctr1, value); 29 | 30 | Statistics package keeps track of number of times counter was accessed, and either the TOTAL or MAX of the values. 31 | 32 | All names, module and counter, can be typed without quotes. The statistics.h file takes care of all that. 33 | 34 | 35 | At regular intervals the statistics package reports JSON data containing all values with multiple reports of the form: 36 | 37 | package, name, type, value, count 38 | 39 | These reports are published on the SPIN/stat channel, and it is left to external programs to make sense of them. 40 | 41 | All counters and all code will disappear from compiled code at the unsetting of one preprocessor variable. -------------------------------------------------------------------------------- /doc/code/external_source.md: -------------------------------------------------------------------------------- 1 | # External source (extsrc) 2 | 3 | ## Introduction 4 | 5 | *External source* (extsrc) is a facility in SPIN that allows 6 | an external program to feed information to spind through a socket. 7 | The idea is that this facility can be used for simulating network traffic 8 | or for testing spind, for example. 9 | 10 | spind either opens a UNIX domain socket on `EXTSRC_SOCKET_PATH` 11 | (currently defined as `/var/run/spin-extsrc.sock`) 12 | or an Internet socket on port `EXTSRC_PORT`. 13 | Applications can send messages through this socket; 14 | see `src/include/extsrc.h` for more details. 15 | Applications can use the `extsrc_msg_create_*` functions to create messages 16 | that can be sent through the socket. 17 | 18 | The spin-pcap-reader, 19 | found in `src/tools/spin-pcap-reader/`, 20 | is a user of the extsrc interface. 21 | 22 | ## Location of the code 23 | 24 | The spind component of extsrc can be found in the following files: 25 | 26 | - `src/include/extsrc.h` 27 | - `src/spind/core2extsrc.c` 28 | - `src/spind/core2extsrc.h` 29 | 30 | And the ''external program''-side of extsrc can be found in the following files: 31 | 32 | - `src/include/extsrc.h` 33 | - `src/lib/extsrc.c` 34 | 35 | Currently, some code which can be used by an application to connect to the 36 | socket and send messages to the socket resides in the following files: 37 | 38 | - `src/tools/spin-pcap-reader/socket.c` 39 | - `src/tools/spin-pcap-reader/socket.h` 40 | 41 | In the future, 42 | we may want to move this code to `src/lib/`. 43 | 44 | -------------------------------------------------------------------------------- /doc/images/mobilescreenshot201703012248.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/doc/images/mobilescreenshot201703012248.png -------------------------------------------------------------------------------- /doc/images/prototype-20170103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/doc/images/prototype-20170103.png -------------------------------------------------------------------------------- /doc/images/screenshot20170301-2244.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/doc/images/screenshot20170301-2244.png -------------------------------------------------------------------------------- /doc/proposals/new_repos_layout_proposal.md: -------------------------------------------------------------------------------- 1 | ### Overall repository structure 2 | 3 | Some directories (such as src/tools/) will be subdivided further, with another directory for the relevant group of files (e.g. src/tools/pcap_reader/). 4 | 5 | | Directory | Description | 6 | | ------ | ----------- | 7 | | / | root directory | 8 | | doc/ | Documentation | 9 | | dist/ | Distribution-specific files and data | 10 | | dist/debian/ | Debian-specific files and data 11 | | dist/openwrt/ | OpenWRT-specific files and data 12 | | scripts/ | General helper scripts for repository and building 13 | | src/ | Source code | 14 | | src/spind/ | Main spin daemon source | 15 | | src/web_ui | SPIN web API 16 | | src/lib/ | shared (c) libraries 17 | | src/lib_lua/ | shared (Lua) libraries 18 | | src/include/ | shared (c) include files 19 | | src/tools/ | SPIN tools (any language) 20 | | src/tools_poc/ | SPIN tools in an early stage (c) 21 | -------------------------------------------------------------------------------- /doc/proposals/provider_api.txt: -------------------------------------------------------------------------------- 1 | 2 | Incident reports by provider 3 | 4 | This is a prototype of the incident report system so that providers 5 | can help stop malicious traffic without resorting to a full quarantine 6 | of the home network. 7 | 8 | The underlying idea is that the SPIN system has a history of local 9 | traffic, whereas the provider (or any reporter of incidents) only knows 10 | the public IP address(es), and not the originator of the malicious 11 | traffic. 12 | 13 | The incident report contains a timestamp and traffic information, and 14 | SPIN can search its history for the offending device. Depending on the 15 | type of report, and user settings, SPIN can block that device until the 16 | issue is resolved. 17 | 18 | This is just a prototype, and it only does full blocking without 19 | configuration right now. 20 | 21 | From a high overview, the system works as follows 22 | 23 | - The provider (or trusted reporter) contacts the router stating that 24 | it has an incident report 25 | - The router fetches the incident report, which contains (at least) the 26 | following data: 27 | * timestamp 28 | * source address (likely the public address of the router in case of IPv4) 29 | * source port 30 | * target address 31 | * target port 32 | * severity (not implemented yet) 33 | * type (not implemented yet) 34 | - The router pushes this information to the MQTT broker, on the channel 35 | "SPIN/incidents" 36 | - The incident report handler (part of SPIN), reads that message and 37 | searches its history for the offending device 38 | - If found, it sends a message to the MQTT broker to block that device. 39 | Note: right now this only works on the router itself, as it uses 40 | the spin_config tool. We need an mqtt channel operation for this as 41 | well. 42 | 43 | 44 | JSON format: 45 | { 46 | "incident": { 47 | "timestamp": int, 48 | "src_addr": string, 49 | "src_port": int, 50 | "dst_addr": string, 51 | "dst_port": int, 52 | "severity": string? (hmm, maybe int with a definition list), 53 | "type": string 54 | } 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /doc/to_update/Spin-API.md: -------------------------------------------------------------------------------- 1 | # SPIN generic API 2 | 3 | Description of all entry points into SPIN. Not currently all to the same endpoint. 4 | 5 | All entry points get a list(0+) of arguments and return a list(0+) of results. 6 | 7 | The names are currently suggestions to be discussed. 8 | 9 | ## web_ui entry points 10 | 11 | get_devices 12 | args: NULL 13 | result: dictionary, indexed by mac-address, containing: 14 | appliedProfiles: list of profile identifiers 15 | enforcement: 16 | lastSeen: timestamp 17 | logging: 18 | mac: mac-address (same as index) 19 | name: user set name 20 | new: true if no profile applied 21 | 22 | *** 23 | 24 | get_profile 25 | args: mac-address 26 | result: list of profile identifiers 27 | 28 | *** 29 | set_profile 30 | args: mac-address, profile identifier 31 | result: NULL 32 | 33 | *** 34 | toggle_new 35 | args: mac-address 36 | result: NULL 37 | 38 | *** 39 | get_notifications 40 | args: NULL 41 | result: list of dictionaries(structs) containing: 42 | id (integer): the unique identifier of the message 43 | message (string): the message to show to the user 44 | messageKey (string): a unique message key that can be used for i18n 45 | messageArgs (list of strings): variable data arguments that may be used in the message (such as names) 46 | timestamp (integer): A UNIX timestamp (seconds since epoch), set to the time the message was created. 47 | deviceMac (string, optional): The MAC address of the device this notification refers to 48 | deviceName (string, optional): The name of the device this notification refers to, see /spin_api/devices for information on what value is used for the name 49 | 50 | *** 51 | set_notification 52 | args: message 53 | *** 54 | delete_notification 55 | args: notification-id 56 | *** 57 | get_profiles 58 | args: NULL 59 | result: list of dictionaries(structs) containing: 60 | id (string): A unique identifier for the profile 61 | name (string): A human-readable name for the profile 62 | description (string): A description of the profile 63 | type (string): One of CREATED_BU_USER, or CREATED_BY_SYSTEM 64 | 65 | ## Spind entry points 66 | 67 | add_name 68 | args: node-id, name 69 | result: NULL 70 | *** 71 | get_blocks 72 | args: NULL 73 | result: list of blocked IP addresses 74 | 75 | *** 76 | Same for ignores and alloweds 77 | *** 78 | add_block_node 79 | args: node-id 80 | result: list of blocked IP addresses 81 | *** 82 | Same for ignores and alloweds 83 | *** 84 | remove_block_node 85 | args: node-id 86 | result: list of blocked IP addresses 87 | *** 88 | Same for ignores and alloweds 89 | *** 90 | remove_block_ip 91 | Tuesday, 19. March 2019 12:46PM 92 | args: ip-address 93 | result: list of blocked IP addresses 94 | *** 95 | Same for ignores and alloweds 96 | *** 97 | reset_ignores 98 | Tuesday, 19. March 2019 12:48PM 99 | args: NULL 100 | result: list of ignored IP addresses 101 | 102 | 103 | -------------------------------------------------------------------------------- /doc/to_update/Table-of-software-interfaces.md: -------------------------------------------------------------------------------- 1 | # Spin software interfaces 2 | The Spin software can be controlled by several interfaces. It is in rapid development so this list of interfaces is a snapshot. 3 | 4 | The columns describe the availibility of the interfaces using various mechanisms. 5 | The WebAPI is a REST mechanism currently implemented by software listening to and aggregating traffic information published using MQTT. 6 | We are migrating to interfaces using either JSON-RPC or UBUS depending on the platform Spin is running on. 7 | Currently a lot of interfaces are implemented using a pseudo-rpc mechanism running over MQTT topics. This is planned to be obsoleted and migrated to real RPC's. 8 | 9 | 10 | Functional name | Tech name | WebAPI availibility | RPC/Ubus availability | MQTT 11 | --- | --- | --- | --- | --- 12 | Retreive devices and information | devicelist | yes | in 0.10 | no 13 | Get profile of device | get_profile | yes | no | no 14 | Change profile of device | set_profile | yes | no | no 15 | ??? | toggle_new | yes | no | no 16 | Get notifications | get_notifications | yes | no |no 17 | Set notification | set_notification | yes | no | no 18 | Get profiles | get_profiles | yes | no | no 19 | Add name of to node | add_name | no | no | yes 20 | Get list of blocked nodes | get_blocks | no | no | yes 21 | Add all IP addresses from node to list of blocked nodes | add_block_node | no | no | yes 22 | Remove all IP addresses from node from list of blocked nodes | remove_block_node | no | no | yes 23 | Remove IP address of blocked list | remove_block_ip | no | no | yes 24 | Add all IP addresses from node to list of ignored nodes | add_ignore_node | no | no | yes 25 | Remove all IP addresses from node from list of ignored nodes | remove_ignore_node | no | no | yes 26 | Remove IP address of ignored list | remove_ignore_ip | no | no | yes 27 | Add all IP addresses from node to list of allowed nodes | add_allow_node | no | no | yes 28 | Remove all IP addresses from node from list of allowed nodes | remove_allow_node | no | no | yes 29 | Remove IP address of allowed list | remove_allow_ip | no | no | yes 30 | Reset list of ignored IP addresses to default | reset_ignores | no | no| yes 31 | Block flow between two nodes | blockflow | no | in 0.10 | no 32 | Block flow between device and node|devblockflow|no|in 0.10|no 33 | Get list of blocked flows | get_blockflow | no | in 0.10| no 34 | Get flows of device | get_deviceflow | yes | in 0.10 | no 35 | Create node | create_node | yes | in 0.10 | no 36 | Add IP address to node | add_ip_to_node | yes | in 0.10 | no 37 | 38 | The WebAPI calls and MQTT calls are documented in the file Spin-API.md 39 | -------------------------------------------------------------------------------- /doc/to_update/install_package.md: -------------------------------------------------------------------------------- 1 | ## Installation on existing OpenWRT router 2 | 3 | You can install spin on an existing OpenWRT installation. 4 | 5 | At this moment this requires a few additional steps. 6 | 7 | ### Add the custom package feed 8 | 9 | Log into your openWRT router with ssh, and add the following line to `/etc/opkg/customfeeds.conf`: 10 | 11 | src/gz sidn https://valibox.sidnlabs.nl/downloads/packages///sidn 12 | 13 | Version is either 'snapshots' (for the latest openWRT snapshot) or 'openwrt-18.06' (for the latest openWRT release). 14 | 15 | Architecture depends on your specific router model, you can see which one you need in `/etc/opkg/distfeeds.conf`. 16 | 17 | Add the SIDN feed key to opkg: 18 | 19 | cd /tmp 20 | wget https://valibox.sidnlabs.nl/downloads/packages/sidn_public.key 21 | opkg-key add sidn_public.key 22 | 23 | Update the package feeds: 24 | 25 | opkg update 26 | 27 | Install spin and its dependencies: 28 | 29 | opkg install spin 30 | 31 | SPIN is now installed, but in order to use the front-end part, there are a few additional steps: 32 | 33 | Configure mosquitto to use websockets. Add the following lines to /etc/mosquitto/mosquitto.conf (if your internal IP address is different, modify as necessary): 34 | 35 | port 1883 127.0.0.1 192.168.1.1 36 | 37 | listener 1884 192.168.1.1 38 | protocol websockets 39 | 40 | allow_anonymous true 41 | 42 | Configure the web server to serve the static pages and reverse proxy, we currently have example files for nginx only, but intend to add lighttpd config as well: 43 | 44 | If you are running nginx, copy the following sections into the relevant server settings (the local network part): 45 | 46 | location /spin { 47 | root /www; 48 | index index.html; 49 | } 50 | location /spin_graph { 51 | alias /usr/lib/spin/web_ui/static/spin_api; 52 | index graph.html; 53 | } 54 | 55 | location /spin_api { 56 | proxy_set_header Host $host; 57 | proxy_set_header X-Real-IP $remote_addr; 58 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 59 | proxy_set_header X-Forwarded-Proto $scheme; 60 | proxy_http_version 1.1; 61 | proxy_pass_request_headers on; 62 | proxy_set_header Upgrade $http_upgrade; 63 | proxy_set_header Connection 'upgrade'; 64 | 65 | # Fix the “It appears that your reverse proxy set up is broken" error. 66 | proxy_pass http://localhost:8002; 67 | proxy_read_timeout 90; 68 | } 69 | 70 | Start or restart the spin daemon and webui: 71 | 72 | /etc/init.d/spin restart 73 | /etc/init.d/spin_webui restart 74 | 75 | 76 | **TODO IN DOCUMENTATION:** 77 | 78 | - better way to configure nginx? the default nginx config does not have something akin to 'include /etc/nginx/conf.d/*.conf'. 79 | - what to do when (only) uhttpd is installed and no webserver. can uhttpd be reverse proxy too? 80 | - lighttpd example 81 | -------------------------------------------------------------------------------- /scripts/create_debian_tarball.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PACKAGE=spin 3 | VERSION=`cat VERSION` 4 | 5 | OUTDIR="/tmp/spin_release_file/" 6 | # remove it if it exists, hardcoded for protecting a bit against, say OUTDIR="/" 7 | if [ -d "/tmp/spin_release_file/" ]; then 8 | rm -rf /tmp/spin_release_file/ 9 | fi 10 | 11 | BNAME="${PACKAGE}-${VERSION}" 12 | 13 | CHECK=1 14 | OPTION=$1 15 | if [ "$OPTION" = "-n" ]; then 16 | CHECK=0 17 | fi 18 | 19 | mkdir -p ${OUTDIR}${BNAME} &&\ 20 | cp -r . ${OUTDIR}${BNAME}/ &&\ 21 | cd ${OUTDIR}${BNAME} &&\ 22 | git clean -fxd &&\ 23 | echo "remove git files" &&\ 24 | find . -name .gitignore -exec rm {} \; &&\ 25 | rm -rf .git &&\ 26 | echo "create version" &&\ 27 | ./scripts/create_version_c.sh VERSION &&\ 28 | echo "Remove test files" &&\ 29 | rm -rf src/tests lua/tests &&\ 30 | echo "remove all non-source files" &&\ 31 | rm -rf conntrack-tools scripts doc Changelog.md CONTRIBUTING.md LICENSE README.md VERSION &&\ 32 | mv src/* ./ &&\ 33 | rmdir src &&\ 34 | cd .. &&\ 35 | tar -czvf ${BNAME}.tar.gz ${BNAME} &&\ 36 | #rm -rf ${OUTDIR}${BNAME} 37 | echo "Done." 38 | 39 | -------------------------------------------------------------------------------- /scripts/create_tarball.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PACKAGE=spin 3 | VERSION=`cat VERSION` 4 | 5 | OUTDIR="/tmp/spin_release_file/" 6 | # remove it if it exists, hardcoded for protecting a bit against, say OUTDIR="/" 7 | if [ -d "/tmp/spin_release_file/" ]; then 8 | rm -rf /tmp/spin_release_file/ 9 | fi 10 | 11 | BNAME="${PACKAGE}-${VERSION}" 12 | 13 | CHECK=1 14 | OPTION=$1 15 | if [ "$OPTION" = "-n" ]; then 16 | CHECK=0 17 | fi 18 | 19 | echo "cp -r * ${OUTDIR}${BNAME}/" 20 | 21 | mkdir -p ${OUTDIR}${BNAME} &&\ 22 | cp -r . ${OUTDIR}${BNAME}/ &&\ 23 | (cd ${OUTDIR}${BNAME}; git clean -fxd) &&\ 24 | (cd ${OUTDIR}${BNAME}/src/; autoreconf --install && ./configure && rm -rf lua/tests && rm -rf src/tests) &&\ 25 | (cd ${OUTDIR}${BNAME} && rm -rf .git .gitignore && rm -rf build) &&\ 26 | if [ $CHECK -eq 1 ]; then 27 | (cd ${OUTDIR}${BNAME}/src/; make && make distclean) 28 | fi &&\ 29 | (cd ${OUTDIR}; tar -czvf ${BNAME}.tar.gz ${BNAME}) &&\ 30 | echo "Created ${OUTDIR}${BNAME}.tar.gz" &&\ 31 | rm -rf ${OUTDIR}${BNAME} &&\ 32 | sha256sum ${OUTDIR}${BNAME}.tar.gz 33 | -------------------------------------------------------------------------------- /scripts/create_version_c.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | VERSIONFILE=$1 3 | if [ -x ../.git ]; then 4 | BUILD_VERSION=`git describe --abbrev=8 --dirty --always --tags` 5 | else 6 | BUILD_VERSION=`cat ${VERSIONFILE}` 7 | fi 8 | BUILD_DATE=`date` 9 | echo "#include \"version.h\"" > version.c 10 | echo "const char * BUILD_VERSION = \"${BUILD_VERSION}\";" >> version.c 11 | echo "const char * BUILD_DATE = \"${BUILD_DATE}\";" >> version.c 12 | -------------------------------------------------------------------------------- /scripts/pkg_create_tarball.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PACKAGE=spin 3 | VERSION=`cat VERSION` 4 | 5 | OUTDIR="/tmp/spin_release_file/" 6 | # remove it if it exists, hardcoded for protecting a bit against, say OUTDIR="/" 7 | if [ -d "/tmp/spin_release_file/" ]; then 8 | rm -rf /tmp/spin_release_file/ 9 | fi 10 | 11 | BNAME="${PACKAGE}-${VERSION}" 12 | 13 | CHECK=1 14 | OPTION=$1 15 | if [ "$OPTION" = "-n" ]; then 16 | CHECK=0 17 | fi 18 | 19 | echo "cp -r * ${OUTDIR}${BNAME}/" 20 | 21 | mkdir -p ${OUTDIR}${BNAME} &&\ 22 | cp -r . ${OUTDIR}${BNAME}/ &&\ 23 | (cd ${OUTDIR}${BNAME}; git clean -fxd) &&\ 24 | (cd ${OUTDIR}${BNAME}/src; autoreconf --install && ./configure && rm -rf lua/tests && rm -rf tests) &&\ 25 | if [ $CHECK -eq 1 ]; then 26 | (cd ${OUTDIR}${BNAME}/src; make && make distclean) 27 | fi &&\ 28 | (cd ${OUTDIR}${BNAME} && rm -rf .git .gitignore && rm -rf build) &&\ 29 | (cd ${OUTDIR}${BNAME}/src && rm -f spinweb/static/spin_graph/js/jquery-3.1.1.min.js.gz) &&\ 30 | (cd ${OUTDIR}${BNAME}/src && rm -rf spinweb/static/spin_graph/js/jquery-ui-1.12.1.custom) &&\ 31 | (cd ${OUTDIR}${BNAME}/src && rm -f lib/version.c) &&\ 32 | (cd ${OUTDIR}; tar -czvf ${BNAME}.tar.gz ${BNAME}) &&\ 33 | echo "Created ${OUTDIR}${BNAME}.tar.gz" &&\ 34 | rm -rf ${OUTDIR}${BNAME} &&\ 35 | sha256sum ${OUTDIR}${BNAME}.tar.gz 36 | -------------------------------------------------------------------------------- /scripts/spin_jrpc_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # 4 | # Very basic json-rpc client for SPIN WEB API 5 | # 6 | 7 | # Usage: spin_jrpc_client.py [parameter] 8 | 9 | import argparse 10 | import json 11 | import sys 12 | import random 13 | import requests 14 | 15 | SPIN_WEB_URI = "192.168.8.1:8080/spin_api/jsonrpc" 16 | 17 | 18 | class JsonRPCClient(object): 19 | def __init__(self, command, params, uri, verbose=False): 20 | self.command = command 21 | self.params = params 22 | self.uri = uri 23 | self.verbose = verbose 24 | 25 | def vprint(self, msg): 26 | if self.verbose: 27 | print(msg) 28 | 29 | def build_json_command(self): 30 | result = { 31 | 'jsonrpc': '2.0', 32 | 'id': random.randint(1, 65535), 33 | 'method': self.command 34 | } 35 | # Parameters come in two's: 36 | # 37 | if self.params: 38 | result['params'] = {} 39 | for i in range(0, len(self.params), 2): 40 | # value may be a json value, but we'll quote it if it 41 | # is a string 42 | try: 43 | param_value = json.loads(self.params[i+1]) 44 | except json.decoder.JSONDecodeError: 45 | param_value = json.loads('"%s"' % self.params[i+1]) 46 | result['params'][self.params[i]] = param_value 47 | return result 48 | 49 | def send(self, json_cmd): 50 | response = requests.post(url = self.uri, json=json_cmd) 51 | self.vprint("Return code: %d" % response.status_code) 52 | self.vprint("Raw response content: %s" % response.content.decode("utf-8")) 53 | return response.json() 54 | 55 | def run(self): 56 | json_cmd = self.build_json_command() 57 | self.vprint("JSON Command: %s" % json_cmd) 58 | result = self.send(json_cmd) 59 | # TODO: check id? 60 | if 'error' in result: 61 | print("Error from server!") 62 | print("Error code: %d" % result['error']['code']) 63 | print("Error message: %s" % result['error']['message']) 64 | elif 'result' in result: 65 | print(json.dumps(result['result'], indent=2)) 66 | else: 67 | print(json.dumps(result, indent=2)) 68 | 69 | if __name__ == '__main__': 70 | arg_parser = argparse.ArgumentParser(prog="spin_jrpc_client.py") 71 | arg_parser.add_argument('-u', '--uri', default='http://192.168.8.1/spin_api/jsonrpc', 72 | help='base URI of the JSON-RPC web api endpoint') 73 | arg_parser.add_argument('-v', '--verbose', action="store_true", help="be verbose") 74 | arg_parser.add_argument('command', help='name of the rpc command') 75 | arg_parser.add_argument('params', nargs='*', help='command parameters; name, value pairs as separate arguments') 76 | 77 | args = arg_parser.parse_args() 78 | 79 | if len(args.params) % 2 != 0: 80 | sys.stderr.write("Error: method parameters must be parameter_name, parameter value pairs, as separate arguments\n") 81 | sys.exit(1) 82 | 83 | client = JsonRPCClient(args.command, args.params, args.uri, args.verbose) 84 | client.run() 85 | -------------------------------------------------------------------------------- /scripts/spin_list_ips: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # List the IP addresses on this machine, one per line, no netmask 3 | force= 4 | outfile= 5 | while getopts "fho:" opt; do 6 | case $opt in 7 | h) 8 | echo "Usage: foobar" 9 | exit 10 | ;; 11 | f) force="yes" 12 | ;; 13 | o) outfile="$OPTARG" 14 | ;; 15 | \?) echo "Invalid option -$OPTARG" >&2 16 | ;; 17 | esac 18 | done 19 | 20 | RESULT=`ip addr show | grep inet | sed "s/^\s*//" | cut -f 2 -d ' ' | cut -f 1 -d '/'` 21 | 22 | if [ -n "$outfile" ]; then 23 | if [ -e "$outfile" ]; then 24 | if [ -n "$force" ]; then 25 | echo "${RESULT}" > ${outfile}; 26 | else 27 | echo "File exists already, use -f to overwrite"; 28 | fi 29 | else 30 | echo "${RESULT}" > ${outfile}; 31 | fi 32 | else 33 | echo "${RESULT}" 34 | fi 35 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | ACLOCAL_AMFLAGS = -I m4 3 | 4 | EXTRA_DIST = $(man_MANS) 5 | dist_bin_SCRIPTS = ../scripts/spin_list_ips 6 | 7 | AM_CFLAGS=-O0 8 | AM_CPPFLAGS = -I$(top_srcdir)/include 9 | SUBDIRS = lib spind spinweb tools 10 | 11 | CXXFLAGS=-O0 12 | CPPFLAGS=-O0 13 | CFLAGS=-O0 14 | -------------------------------------------------------------------------------- /src/include/arp.h: -------------------------------------------------------------------------------- 1 | // should we cache this? do we need to update it on every lookup anyway? 2 | // hmm, maybe only when it's not found anyway 3 | 4 | #ifndef SPIN_ARP_H 5 | #define SPIN_ARP_H 1 6 | 7 | #include "tree.h" 8 | #include "util.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /* 15 | * arp_table_t contains an ARP table. Depending on the backend type that is 16 | * specified upon creation with arp_table_create(), some functions have 17 | * different behavior. See below for details. 18 | */ 19 | 20 | enum arp_table_backend { 21 | ARP_TABLE_LINUX, 22 | ARP_TABLE_VIRTUAL, 23 | }; 24 | 25 | typedef struct { 26 | enum arp_table_backend backend; 27 | tree_t* entries; 28 | } arp_table_t; 29 | 30 | arp_table_t* arp_table_create(enum arp_table_backend backend); 31 | void arp_table_destroy(arp_table_t* arp_table); 32 | 33 | /* 34 | * arp_table_add() can be used to add an entry to the specified ARP table. 35 | * It should only be necessary to use this function when the backend type is 36 | * ARP_TABLE_VIRTUAL. When the backend type is ARP_TABLE_LINUX, various calls 37 | * to the arp_table_read() function spread throughout the SPIN code make sure 38 | * the ARP table is populated and up-to-date. 39 | */ 40 | void arp_table_add(arp_table_t* arp_table, ip_t* ip, char* mac); 41 | 42 | /* 43 | * When the backend type of this ARP table is ARP_TABLE_LINUX, this function 44 | * queries the Linux neighbour table (ARP/NDISC) and updates the specified 45 | * ARP table. When the backend type is ARP_TABLE_VIRTUAL, this function is 46 | * a NOOP. 47 | */ 48 | void arp_table_read(arp_table_t* arp_table); 49 | 50 | char* arp_table_find_by_ip(arp_table_t* arp_table, ip_t* ip); 51 | 52 | #endif // SPIN_ARP_H 53 | -------------------------------------------------------------------------------- /src/include/dns.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_DNS_H 2 | #define SPIN_DNS_H 1 3 | 4 | #include "pkt_info.h" 5 | 6 | struct handle_dns_ctx; 7 | 8 | struct handle_dns_ctx *handle_dns_init(void (*query_hook)(dns_pkt_info_t *, int, uint8_t *), void (*answer_hook)(dns_pkt_info_t *)); 9 | void handle_dns_cleanup(struct handle_dns_ctx* ctx); 10 | 11 | void handle_dns_query(const struct handle_dns_ctx *ctx, const u_char *bp, u_int length, uint8_t* src_addr, int family); 12 | void handle_dns_answer(const struct handle_dns_ctx *ctx, const u_char *bp, u_int length, int protocol); 13 | 14 | #endif // SPIN_DNS_H 15 | -------------------------------------------------------------------------------- /src/include/dns_cache.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef DNS_CACHE_H 3 | #define DNS_CACHE_H 1 4 | 5 | #include "pkt_info.h" 6 | 7 | #include "tree.h" 8 | #include "util.h" 9 | 10 | /** 11 | * cache of dns requests 12 | */ 13 | 14 | #define MAX_SIZE 8000 15 | 16 | typedef struct dns_cache_entry_s { 17 | // this is the domain(string)->expiry (timestamp + ttl, uint32_t) mapping 18 | tree_t* domains; 19 | } dns_cache_entry_t; 20 | 21 | // todo: make this a tree or dict 22 | typedef struct { 23 | // this tree maps ip's to trees that map domain names to expiry timestamps 24 | // 192.0.2.1 25 | // |- example.com 123451245 26 | // |- example.net 123461234 27 | tree_t* entries; 28 | } dns_cache_t; 29 | 30 | 31 | dns_cache_entry_t* dns_cache_entry_create(); 32 | void dns_cache_entry_destroy(dns_cache_entry_t* dns_cache_entry); 33 | 34 | void dns_cache_entry_print(dns_cache_entry_t* entry); 35 | 36 | 37 | dns_cache_t* dns_cache_create(); 38 | void dns_cache_destroy(dns_cache_t* dns_cache); 39 | 40 | // note: this copies the data 41 | void dns_cache_add_dname_ip(dns_cache_t* cache, uint8_t family, uint32_t ttl, char* dname, const ip_t* ip, uint32_t timestamp); 42 | void dns_cache_add(dns_cache_t* cache, dns_pkt_info_t* dns_pkt_info, uint32_t timestamp); 43 | 44 | void dns_cache_clean(dns_cache_t* dns_cache, size_t clean_early); 45 | void dns_cache_print(dns_cache_t* dns_cache); 46 | 47 | dns_cache_entry_t* dns_cache_find(dns_cache_t* dns_cache, ip_t* ip); 48 | 49 | #endif // DNS_CACHE_H 50 | -------------------------------------------------------------------------------- /src/include/ipl.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_IPL_H 2 | #define SPIN_IPL_H 1 3 | 4 | /* 5 | * Lowlevel functionality to manipulate lists of IP addresses 6 | */ 7 | #include "spin_list.h" 8 | 9 | struct list_info { 10 | tree_t * li_tree; // Tree of IP addresses 11 | char * li_listname; // Name of list 12 | int li_modified; // File should be written 13 | }; 14 | 15 | char* ipl_filename(struct list_info *lip); 16 | void init_ipl(struct list_info *lip); 17 | void init_all_ipl(struct list_info *ipl_list_ar); 18 | void clean_all_ipl(); 19 | void add_ip_to_li(ip_t* ip, struct list_info *lip); 20 | void add_ip_tree_to_li(tree_t* tree, struct list_info *lip); 21 | void remove_ip_tree_from_li(tree_t *tree, struct list_info *lip); 22 | void remove_ip_from_li(ip_t* ip, struct list_info *lip); 23 | int ip_in_li(ip_t* ip, struct list_info* lip); 24 | int ip_in_ignore_list(ip_t* ip); 25 | int addr_in_ignore_list(int family, uint8_t* addr); 26 | 27 | #endif // SPIN_IPL_H 28 | -------------------------------------------------------------------------------- /src/include/jsmn.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2010 Serge A. Zaitsev 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | https://github.com/zserge/jsmn 24 | 25 | */ 26 | 27 | #ifndef __JSMN_H_ 28 | #define __JSMN_H_ 29 | 30 | #include 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | /** 37 | * JSON type identifier. Basic types are: 38 | * o Object 39 | * o Array 40 | * o String 41 | * o Other primitive: number, boolean (true/false) or null 42 | */ 43 | typedef enum { 44 | JSMN_UNDEFINED = 0, 45 | JSMN_OBJECT = 1, 46 | JSMN_ARRAY = 2, 47 | JSMN_STRING = 3, 48 | JSMN_PRIMITIVE = 4 49 | } jsmntype_t; 50 | 51 | enum jsmnerr { 52 | /* Not enough tokens were provided */ 53 | JSMN_ERROR_NOMEM = -1, 54 | /* Invalid character inside JSON string */ 55 | JSMN_ERROR_INVAL = -2, 56 | /* The string is not a full JSON packet, more bytes expected */ 57 | JSMN_ERROR_PART = -3 58 | }; 59 | 60 | /** 61 | * JSON token description. 62 | * type type (object, array, string etc.) 63 | * start start position in JSON data string 64 | * end end position in JSON data string 65 | */ 66 | typedef struct { 67 | jsmntype_t type; 68 | int start; 69 | int end; 70 | int size; 71 | #ifdef JSMN_PARENT_LINKS 72 | int parent; 73 | #endif 74 | } jsmntok_t; 75 | 76 | /** 77 | * JSON parser. Contains an array of token blocks available. Also stores 78 | * the string being parsed now and current position in that string 79 | */ 80 | typedef struct { 81 | unsigned int pos; /* offset in the JSON string */ 82 | unsigned int toknext; /* next token to allocate */ 83 | int toksuper; /* superior token node, e.g parent object or array */ 84 | } jsmn_parser; 85 | 86 | /** 87 | * Create JSON parser over an array of tokens 88 | */ 89 | void jsmn_init(jsmn_parser *parser); 90 | 91 | /** 92 | * Run JSON parser. It parses a JSON data string into and array of tokens, each describing 93 | * a single JSON object. 94 | */ 95 | int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 96 | jsmntok_t *tokens, unsigned int num_tokens); 97 | 98 | #ifdef __cplusplus 99 | } 100 | #endif 101 | 102 | #endif /* __JSMN_H_ */ 103 | -------------------------------------------------------------------------------- /src/include/netlink_commands.h: -------------------------------------------------------------------------------- 1 | #ifdef notdef 2 | #ifndef SPIN_NETLINK_COMMANDS_H 3 | #define SPIN_NETLINK_COMMANDS_H 1 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "util.h" 11 | 12 | #include "spin_cfg.h" 13 | 14 | 15 | typedef struct { 16 | ip_t* ips; 17 | size_t ip_count; 18 | size_t ip_max; 19 | char* error; 20 | } netlink_command_result_t; 21 | 22 | netlink_command_result_t* netlink_command_result_create(void); 23 | void netlink_command_result_destroy(netlink_command_result_t* command_result); 24 | void netlink_command_result_add_ip(netlink_command_result_t* command_result, uint8_t ip_fam, uint8_t* ip); 25 | int netlink_command_result_contains_ip(netlink_command_result_t* command_result, ip_t* ip); 26 | void netlink_command_result_set_error(netlink_command_result_t* command_result, char* error); 27 | 28 | netlink_command_result_t* send_netlink_command_buf(size_t cmdbuf_size, unsigned char* cmdbuf); 29 | netlink_command_result_t* send_netlink_command_noarg(config_command_t cmd); 30 | //netlink_command_result_t* send_netlink_command_iparg(config_command_t cmd, uint8_t* ip); 31 | netlink_command_result_t* send_netlink_command_iparg(config_command_t cmd, ip_t* ip); 32 | 33 | 34 | #endif // SPIN_NETLINK_COMMANDS_H 35 | #endif 36 | -------------------------------------------------------------------------------- /src/include/node_names.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This module looks up names for nodes 3 | * 4 | * It will check if a user has set a name (stored in /etc/spin/userdata.c) 5 | * If not user-set, the mac address will be looked up in the DHCP config 6 | * (/etc/config/dhcp) 7 | * 8 | * Note: these are only read *once* (in a future update, maybe we can re-read if the file changes? 9 | */ 10 | 11 | #include "tree.h" 12 | #include "util.h" 13 | 14 | // hmm. just make two trees per input for easy reference? 15 | typedef struct { 16 | // referenced by IP 17 | tree_t* user_names_by_ip; 18 | tree_t* user_names_by_mac; 19 | // referenced by MAC 20 | tree_t* dhcp_names_by_ip; 21 | tree_t* dhcp_names_by_mac; 22 | } node_names_t; 23 | 24 | node_names_t* node_names_create(void); 25 | void node_names_destroy(node_names_t*); 26 | 27 | int node_names_read_dhcpconfig(node_names_t* node_names, const char* filename); 28 | int node_names_read_dhcpleases(node_names_t* node_names, const char* filename); 29 | int node_names_read_userconfig(node_names_t* node_names, const char* filename); 30 | int node_names_write_userconfig(node_names_t* node_names, const char* filename); 31 | 32 | char* node_names_find_ip(node_names_t* node_names, ip_t* ip); 33 | char* node_names_find_mac(node_names_t* node_names, char* mac); 34 | 35 | void node_names_add_user_name_ip(node_names_t* node_names, ip_t* ip, char* name); 36 | void node_names_add_user_name_mac(node_names_t* node_names, char* mac, char* name); 37 | -------------------------------------------------------------------------------- /src/include/pkt_info.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared functions and structures for information about packets 3 | */ 4 | #ifndef SPIN_PKT_INFO_H 5 | #define SPIN_PKT_INFO_H 1 6 | 7 | #ifdef __KERNEL__ 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #else 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #endif // __KERNEL 31 | 32 | typedef enum { 33 | SPIN_TRAFFIC_DATA = 1, 34 | SPIN_DNS_ANSWER = 2, 35 | SPIN_BLOCKED = 3, 36 | SPIN_DNS_QUERY = 4, 37 | SPIN_ERR_BADVERSION = 250 38 | } message_type_t; 39 | 40 | 41 | // Note: when changing this structure, check pkt_info_equals, 42 | // which assumes all equality data is in the first 39 bytes 43 | typedef struct packet_info { 44 | uint8_t family; // 4, 6, etc 45 | uint8_t protocol; // value for tcp/udp/icmp/etc. 46 | uint8_t src_addr[16]; // v4 just uses last 4 bytes 47 | uint8_t dest_addr[16]; // v4 just uses last 4 bytes 48 | // Source port (in host endianness) 49 | uint16_t src_port; 50 | // Destination port (in host endianness) 51 | uint16_t dest_port; 52 | uint8_t icmp_type; 53 | uint64_t payload_size; 54 | uint64_t packet_count; // amount of packets for this set of 55 | // fam, proto, source, dest, and ports 56 | uint16_t payload_offset; // only relevant if packet_count == 1 57 | } pkt_info_t; 58 | 59 | // note: all values are stored in network order 60 | // family is 4 or 6 61 | // dns_cache and node_cache assume first 17 bytes are family + address 62 | typedef struct dns_packet_info { 63 | uint8_t family; 64 | uint8_t ip[16]; 65 | uint32_t ttl; 66 | char dname[256]; 67 | } dns_pkt_info_t; 68 | 69 | // Writes a string representation of the given IP address (in 70 | // wire format) to the given destination address. 71 | void ntop(int fam, char* dest, const uint8_t* src, size_t max); 72 | 73 | // Writes packet info data to target in string format 74 | // writes until max_len is reached 75 | void pktinfo2str(char* dest, pkt_info_t* pkt_info, size_t max_len); 76 | 77 | // returns true if the packets are considered equal 78 | // (same family, protocol, addresses and ports) 79 | int pkt_info_equal(pkt_info_t* a, pkt_info_t* b); 80 | 81 | void dns_pktinfo2str(char* dest, dns_pkt_info_t* dns_pkt_info, size_t max_len); 82 | void dns_dname2str(char* dest, char* src, size_t max_len); 83 | 84 | 85 | #endif // SPIN_PKT_INFO 86 | -------------------------------------------------------------------------------- /src/include/spin_cfg.h: -------------------------------------------------------------------------------- 1 | #ifdef notdef 2 | 3 | #ifndef SPIN_CONFIG_H 4 | #define SPIN_CONFIG_H 1 5 | 6 | typedef enum { 7 | // commands from client to kernelmod 8 | SPIN_CMD_GET_IGNORE = 1, 9 | SPIN_CMD_ADD_IGNORE = 2, 10 | SPIN_CMD_REMOVE_IGNORE = 3, 11 | SPIN_CMD_CLEAR_IGNORE = 4, 12 | SPIN_CMD_GET_BLOCK = 5, 13 | SPIN_CMD_ADD_BLOCK = 6, 14 | SPIN_CMD_REMOVE_BLOCK = 7, 15 | SPIN_CMD_CLEAR_BLOCK = 8, 16 | SPIN_CMD_GET_EXCEPT = 9, 17 | SPIN_CMD_ADD_EXCEPT = 10, 18 | SPIN_CMD_REMOVE_EXCEPT = 11, 19 | SPIN_CMD_CLEAR_EXCEPT = 12, 20 | // commands from kernelmod to client 21 | SPIN_CMD_IP = 100, 22 | SPIN_CMD_END = 200, 23 | SPIN_CMD_ERR = 250 24 | } config_command_t; 25 | 26 | void config_test(void); 27 | 28 | #endif 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/include/spin_config.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_CONFIG_H 2 | #define SPIN_CONFIG_H 3 | #include "config.h" 4 | // if USE_UCI is defined we use UCI with section spin.spind 5 | // if not we use a file /etc/spin/spind.conf 6 | 7 | #if USE_UCI 8 | #define UCI_SECTION_NAME "spin.spind" 9 | #endif 10 | 11 | #define CONFIG_FILE "/etc/spin/spind.conf" 12 | 13 | void init_config(); 14 | void config_set_option(char*, char*); 15 | int get_config_entries(const char* config_file, int must_exist); 16 | void spinconfig_print_defaults(); 17 | 18 | int spinconfig_log_usesyslog(); 19 | int spinconfig_log_loglevel(); 20 | char *spinconfig_log_file(); 21 | char *spinconfig_pid_file(); 22 | char *spinconfig_pubsub_host(); 23 | int spinconfig_pubsub_port(); 24 | char *spinconfig_pubsub_websocket_host(); 25 | int spinconfig_pubsub_websocket_port(); 26 | char *spinconfig_pubsub_channel_traffic(); 27 | int spinconfig_pubsub_timeout(); 28 | int spinconfig_pubsub_omitnode(); 29 | int spinconfig_pubsub_run_mosquitto(); 30 | char *spinconfig_pubsub_run_password_file(); 31 | char* spinconfig_pubsub_run_pid_file(); 32 | char* spinconfig_pubsub_run_user(); 33 | int spinconfig_iptable_nflog_dns_group(); 34 | int spinconfig_iptable_queue_block(); 35 | int spinconfig_iptable_place_block(); 36 | char *spinconfig_iptable_debug(); 37 | // The time (in seconds) that node_cache entries 38 | // are kept after they have last been seen 39 | int spinconfig_node_cache_retain_time(); 40 | int spinconfig_dots_enabled(); 41 | int spinconfig_dots_log_only(); 42 | char *spinconfig_spinweb_pid_file(); 43 | char* spinconfig_spinweb_interfaces(); 44 | int spinconfig_spinweb_port(); 45 | char* spinconfig_spinweb_tls_certificate_file(); 46 | char* spinconfig_spinweb_tls_key_file(); 47 | char* spinconfig_spinweb_password_file(); 48 | #endif // SPIN_CONFIG_H 49 | -------------------------------------------------------------------------------- /src/include/spin_list.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_LIST_H 2 | #define SPIN_LIST_H 3 | 4 | /* 5 | * SPIN internally maintains 3 lists of IP addresses, which influence 6 | * its behaviour: 7 | * - ignore: traffic containing one of these ips is completely ignored; 8 | * it is not shown in the traffic stream and cannot otherwise 9 | * be interacted with 10 | * - block: traffic from and to these ips is blocked with a firewall 11 | * rule. The rule is set to log messages, so this blocked 12 | * traffic shows up in the traffic stream as blocked 13 | * - allow: traffic from and to these ips is allowed, even if the 14 | * other ip address is blocked. For example, if you block 15 | * all traffic from and to 192.0.2.1 for all your devices, 16 | * but want one specific device to be able to contact it, 17 | * you add the device's ips to 'allow'. 18 | * 19 | * Low-level functionality, such as manipulation of lists themselves, 20 | * is in ipl.[ch]. Please not that modifying these lists may require 21 | * some calls to the node_cache as well, since it has some information 22 | * about these lists for efficiency. 23 | */ 24 | 25 | #include "util.h" 26 | 27 | enum iplist_index { 28 | IPLIST_BLOCK, 29 | IPLIST_IGNORE, 30 | IPLIST_ALLOW, 31 | N_IPLIST 32 | }; 33 | 34 | enum spinfunc_command { 35 | SF_ADD, 36 | SF_REM, 37 | N_SF 38 | }; 39 | 40 | typedef void (*spinfunc)(void*, int listid, int addrem, ip_t *ip_addr); 41 | 42 | void spin_register(char *name, spinfunc wf, void *arg, int list[N_IPLIST]); 43 | 44 | struct list_info* get_spin_iplists(); 45 | struct list_info* get_spin_iplist(int index); 46 | /* 47 | * Returns -1 if list not found 48 | */ 49 | int get_spin_iplist_id_by_name(const char* name); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/include/spin_log.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_LOG_H 2 | #define SPIN_LOG_H 1 3 | 4 | #include 5 | #include 6 | 7 | // Initialize logging; set logger to level, 8 | // and choose between syslog (default) or logging to stdout 9 | void spin_log_init(int use_syslog, int log_stdout, const char* log_filename, int verbosity, const char* ident); 10 | 11 | void spin_log_close(); 12 | 13 | void spin_log(int level, const char* format, ...) __attribute__((__format__ (printf, 2, 3))); 14 | 15 | void spin_vlog(int level, const char* format, va_list arg); 16 | 17 | #endif //SPIN_LOG_H 18 | -------------------------------------------------------------------------------- /src/include/spindata.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_DATA_H 2 | #define SPIN_DATA_H 1 3 | #include "spindata_type.h" 4 | #include "node_cache.h" 5 | #include "tree.h" 6 | 7 | spin_data spin_data_nodes_merged(int node1, int node2); 8 | spin_data spin_data_node_deleted(int node); 9 | spin_data spin_data_ipar(tree_t *iptree); 10 | spin_data spin_data_node(node_t* node); 11 | spin_data spin_data_pkt_info(node_cache_t* node_cache, pkt_info_t* pkt_info); 12 | spin_data spin_data_dns_query_pkt_info(node_cache_t* node_cache, dns_pkt_info_t* dns_pkt_info); 13 | spin_data spin_data_create_mqtt_command(const char* command, char* argument, spin_data result); 14 | spin_data spin_data_create_traffic(node_cache_t* node_cache, flow_list_t* flow_list, uint32_t timestamp); 15 | spin_data spin_data_nodepairtree(tree_t* tree); 16 | spin_data spin_data_devicelist(node_cache_t *node_cache); 17 | spin_data spin_data_flowlist(node_t *node); 18 | #endif 19 | -------------------------------------------------------------------------------- /src/include/spindata_type.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_DATA_TYPE_H 2 | #define SPIN_DATA_TYPE_H 1 3 | 4 | #include "cJSON.h" 5 | 6 | // basic data type for serializing spin data 7 | // This is just a low-level wrapper for cJSON data structures 8 | 9 | typedef cJSON *spin_data; 10 | 11 | char *spin_data_serialize(spin_data sd); 12 | void spin_data_ser_delete(char *str); 13 | void spin_data_delete(spin_data sd); 14 | 15 | #endif // SPIN_DATA_TYPE_H -------------------------------------------------------------------------------- /src/include/spinhook.h: -------------------------------------------------------------------------------- 1 | #include "spindata.h" 2 | 3 | typedef void (*trafficfunc)(node_cache_t *, node_t *, node_t *, int, int, uint32_t, int, int); 4 | 5 | void spinhook_nodesmerged(node_cache_t *node_cache, node_t *dest_node, node_t *src_node); 6 | void spinhook_nodedeleted(node_cache_t *node_cache, node_t *node); 7 | void spinhook_traffic(node_cache_t *node_cache, node_t *src_node, node_t *dest_node, int packetcnt, int packetbytes, uint32_t timestamp, int, int); 8 | void spinhook_block_dev_node_flow(device_t *dev, node_t *node, int blocked); 9 | void spinhook_clean(node_cache_t *node_cache); 10 | -------------------------------------------------------------------------------- /src/include/statistics.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Statistics module include 3 | */ 4 | #ifndef SPIN_STATISTICS_H 5 | #define SPIN_STATISTICS_H 1 6 | 7 | /* 8 | * DO_SPIN_STATS 9 | * 10 | * 0: no statistics 11 | * 1: statistics runtime in kernel 12 | * 2: statistics at end for unit test 13 | */ 14 | 15 | #ifndef DO_SPIN_STATS 16 | #define DO_SPIN_STATS 1 17 | #endif 18 | 19 | #if DO_SPIN_STATS != 0 20 | 21 | #define SPIN_STAT_START() spin_stat_start(); 22 | #define SPIN_STAT_FINISH() spin_stat_finish(); 23 | 24 | void spin_stat_start(); 25 | void spin_stat_finish(); 26 | 27 | typedef enum { 28 | STAT_TOTAL, 29 | STAT_MAX, 30 | N_STAT 31 | } spin_stat_type_t; 32 | 33 | typedef struct spin_stat *stat_p; 34 | typedef struct spin_stat { 35 | const char * stat_module; /* Group of counters */ 36 | const char * stat_name; /* Name of counter */ 37 | spin_stat_type_t stat_type; /* Type enum */ 38 | int stat_value; 39 | int stat_count; 40 | int stat_lastpubcount; /* Count when last published */ 41 | stat_p stat_next; 42 | } spin_stat_t; 43 | 44 | extern spin_stat_t spin_stat_end; 45 | extern stat_p spin_stat_chain; 46 | 47 | void spin_stat_val(stat_p, int); 48 | 49 | #define STAT_CONCAT(x, y) x ## y 50 | #define STAT_PREF _spin_stat_ 51 | 52 | #define STAT_MODULE(modulename) static const char STAT_CONCAT(STAT_PREF, modname)[] = #modulename ; 53 | 54 | #define STAT_COUNTER(ctr, descr, type) static spin_stat_t STAT_CONCAT(STAT_PREF, ctr) = { STAT_CONCAT(STAT_PREF, modname), #descr, type, 0, 0, 0, NULL } 55 | 56 | #define STAT_VALUE(ctr, val) spin_stat_val(&STAT_CONCAT(STAT_PREF, ctr), val) 57 | 58 | #else // DO_SPIN_STATS 59 | 60 | #define SPIN_STAT_START() 61 | #define SPIN_STAT_FINISH() 62 | 63 | #define STAT_MODULE(x) ; 64 | #define STAT_COUNTER(x, y, z) 65 | #define STAT_VALUE(x, y) 66 | 67 | #endif // DO_SPIN_STATS 68 | 69 | #endif // SPIN_STATISTICS_H 70 | -------------------------------------------------------------------------------- /src/include/tree.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SPIN_TREE_H 3 | #define SPIN_TREE_H 1 4 | 5 | #include 6 | #include 7 | 8 | typedef struct tree_entry_s { 9 | size_t key_size; 10 | void* key; 11 | size_t data_size; 12 | void* data; 13 | struct tree_entry_s* parent; 14 | struct tree_entry_s* left; 15 | struct tree_entry_s* right; 16 | } tree_entry_t; 17 | 18 | typedef struct { 19 | tree_entry_t* root; 20 | int (*cmp_func)(size_t key_a_size, const void* key_a, size_t key_b_size, const void* key_b); 21 | } tree_t; 22 | 23 | tree_entry_t* tree_entry_create(size_t key_size, void* key, size_t data_size, void* data, int copy); 24 | void tree_entry_destroy(tree_entry_t* tree_entry, int destroy_children); 25 | 26 | tree_t* tree_create(int (*cmp_func)(size_t key_a_size, const void* key_a, size_t key_b_size, const void* key_b)); 27 | void tree_destroy(tree_t* tree); 28 | int tree_add(tree_t* tree, size_t key_size, void* key, size_t data_size, void* data, int copy); 29 | tree_entry_t* tree_find(tree_t* tree, size_t key_size, const void* key); 30 | void tree_remove_entry(tree_t* tree, tree_entry_t* entry); 31 | void tree_remove(tree_t* tree, size_t key_size, void* key); 32 | tree_entry_t* tree_first(tree_t* tree); 33 | tree_entry_t* tree_entry_first(tree_entry_t* current); 34 | tree_entry_t* tree_entry_last(tree_entry_t* current); 35 | tree_entry_t* tree_next(tree_entry_t* current); 36 | int tree_entry_depth(tree_entry_t* current); 37 | tree_entry_t* tree_entry_balance(tree_entry_t* current); 38 | int tree_empty(tree_t* tree); 39 | int tree_size(tree_t* tree); 40 | void tree_print(tree_t* tree, void(*print_func)(size_t size, void*key)); 41 | void tree_clear(tree_t* tree); 42 | 43 | #endif // SPIN_TREE_H 44 | -------------------------------------------------------------------------------- /src/include/util.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_UTIL_H 2 | #define SPIN_UTIL_H 1 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "tree.h" 13 | 14 | int cmp_ints(size_t size_a, const void* key_a, size_t size_b, const void* key_b); 15 | int cmp_2ints(size_t size_a, const void* key_a, size_t size_b, const void* key_b); 16 | int cmp_strs(size_t size_a, const void* key_a, size_t size_b, const void* key_b); 17 | int cmp_ipdata_raw(size_t size_a, const void* key_a, size_t size_b, const void* key_b); 18 | int cmp_ips(size_t size_a, const void* key_a, size_t size_b, const void* key_b); 19 | int cmp_domains(size_t size_a, const void* a, size_t size_b, const void* b); 20 | int cmp_pktinfos(size_t size_a, const void* a, size_t size_b, const void* b); 21 | 22 | typedef struct { 23 | uint8_t family; 24 | uint8_t netmask; 25 | uint8_t addr[16]; 26 | } ip_t; 27 | 28 | int spin_pton(ip_t* dest, const char* ip); 29 | size_t spin_ntop(char* dest, ip_t* ip, size_t dest_size); 30 | 31 | typedef struct { 32 | char* data; 33 | size_t pos; 34 | size_t max; 35 | int finished; 36 | int ok; 37 | int allow_resize; 38 | } buffer_t; 39 | 40 | void phexdump(const uint8_t* data, unsigned int size); 41 | 42 | buffer_t* buffer_create(size_t size); 43 | void buffer_destroy(buffer_t* buffer); 44 | 45 | 46 | void buffer_allow_resize(buffer_t* buffer); 47 | int buffer_finish(buffer_t* buffer); 48 | void buffer_reset(buffer_t* buffer); 49 | size_t buffer_size(buffer_t* buffer); 50 | char* buffer_str(buffer_t* buffer); 51 | 52 | int buffer_write(buffer_t* buffer, const char* format, ...) __attribute__((__format__ (printf, 2, 3))); 53 | 54 | int buffer_ok(buffer_t* buffer); 55 | 56 | /* 57 | * Stores a tree of IP values in the given files 58 | * 59 | * The tree should be keyed by the IP values and the tree data is 60 | * ignored 61 | * Returns 1 on success, 0 on failure. 62 | */ 63 | int store_ip_tree(tree_t* tree, const char* filename); 64 | /* 65 | * Same for nodepair 66 | */ 67 | int store_nodepair_tree(tree_t* tree, const char* filename); 68 | 69 | /* 70 | * Read the given filename, which should consist of one IP string 71 | * per line, and add them with NULL data to the given tree 72 | * 73 | * Returns the number of IP values read (note; does not look at 74 | * duplicates, so the number of elements added to the tree may be 75 | * different from the number of items read) 76 | */ 77 | int read_ip_tree(tree_t* dest, const char* filename); 78 | 79 | void hexdump(uint8_t* data, unsigned int size); 80 | 81 | int ip_in_net(ip_t* ip, ip_t* net); 82 | 83 | /* 84 | * Set IP address data in an ip_t structure 85 | * The dest MUST be allocated with sizeof(ip_t) bytes of memory 86 | * 87 | * If netmask is not 0, it is set too 88 | * If netmask is 0, it is set to either 32 (ipv4) or 128 (ipv6) 89 | * ip_data should point to a memory structure that has the ip address 90 | * in ip_t format; e.g. 16 octets of data for ipv6, or 12 zero bytes 91 | * followed by 4 octets of data for ipv4 92 | */ 93 | void copy_ip_data(ip_t* dest, int family, int netmask, const void* ip_data); 94 | 95 | /* 96 | * Returns 1 if the given string represents an IPv4 address, 97 | * 0 otherwise 98 | */ 99 | int is_ipv4_address(const char* str); 100 | 101 | /* 102 | * Returns 1 if the given string represents an IPv6 address, 103 | * 0 otherwise 104 | */ 105 | int is_ipv6_address(const char* str); 106 | 107 | #endif // SPIN_UTIL_H 108 | -------------------------------------------------------------------------------- /src/include/version.h: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H 2 | #define VERSION_H 1 3 | 4 | extern const char* BUILD_VERSION; 5 | extern const char* BUILD_DATE; 6 | 7 | #endif // VERSION_H 8 | -------------------------------------------------------------------------------- /src/lib/Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | AM_CPPFLAGS = -I$(top_srcdir)/include 3 | AM_CFLAGS = ${regular_CFLAGS} -g -Wall -Werror 4 | 5 | SUBDIRS = @TESTDIR@ 6 | 7 | noinst_LIBRARIES = libspin.a 8 | 9 | libspin_a_SOURCES = pkt_info.c \ 10 | spin_log.h \ 11 | spin_log.c \ 12 | dns.c \ 13 | dns.h \ 14 | dns_cache.h \ 15 | dns_cache.c \ 16 | extsrc.c \ 17 | tree.h \ 18 | tree.c \ 19 | node_cache.h \ 20 | node_cache.c \ 21 | util.h \ 22 | util.c \ 23 | arp.h \ 24 | arp.c \ 25 | ipl.c \ 26 | ipl.h \ 27 | node_names.h \ 28 | node_names.c \ 29 | jsmn.h \ 30 | jsmn.c \ 31 | spin_config_common.c \ 32 | spin_config_uci.c \ 33 | spindata_type.c \ 34 | spin_list.c \ 35 | statistics.c \ 36 | version.c 37 | 38 | #libspin_a_CFLAGS = 39 | #libspin_a_LIBADD = 40 | 41 | version.c: 42 | $(top_srcdir)/../scripts/create_version_c.sh $(top_srcdir)/../VERSION 43 | 44 | .PHONY: version.c 45 | -------------------------------------------------------------------------------- /src/lib/extsrc.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "extsrc.h" 4 | #include "spin_log.h" 5 | 6 | uint8_t 7 | extsrc_af_to_wire(uint8_t af) 8 | { 9 | switch (af) { 10 | case AF_INET: 11 | return EXTSRC_AF_INET; 12 | case AF_INET6: 13 | return EXTSRC_AF_INET6; 14 | default: 15 | spin_log(LOG_WARNING, "%s: unknown value %d\n", __func__, af); 16 | return af; // XXX 17 | } 18 | } 19 | 20 | uint8_t 21 | extsrc_af_from_wire(uint8_t x) 22 | { 23 | switch (x) { 24 | case EXTSRC_AF_INET: 25 | return AF_INET; 26 | case EXTSRC_AF_INET6: 27 | return AF_INET6; 28 | default: 29 | spin_log(LOG_WARNING, "%s: unknown value %d\n", __func__, x); 30 | return x; // XXX 31 | } 32 | } 33 | 34 | static void * 35 | extsrc_msg_payload(struct extsrc_msg *msg) 36 | { 37 | return msg->data + sizeof(struct extsrc_msg_hdr); 38 | } 39 | 40 | /******************************************************************************/ 41 | 42 | struct extsrc_msg * 43 | extsrc_msg_create(char *payload, uint32_t payload_len, uint32_t msg_type) 44 | { 45 | struct extsrc_msg *msg; 46 | struct extsrc_msg_hdr hdr; 47 | 48 | hdr.type = msg_type; 49 | hdr.length = payload_len; 50 | 51 | msg = malloc(sizeof(struct extsrc_msg)); 52 | if (!msg) { 53 | err(1, "malloc"); 54 | } 55 | 56 | msg->length = sizeof(hdr) + payload_len; 57 | msg->data = malloc(msg->length); 58 | if (!msg->data) { 59 | err(1, "malloc"); 60 | } 61 | 62 | memcpy(msg->data, &hdr, sizeof(hdr)); 63 | memcpy(msg->data + sizeof(hdr), payload, payload_len); 64 | 65 | return msg; 66 | } 67 | 68 | struct extsrc_msg * 69 | extsrc_msg_create_pkt_info(pkt_info_t *pkt) 70 | { 71 | struct extsrc_msg *res = extsrc_msg_create((char *)pkt, sizeof(*pkt), 72 | EXTSRC_MSG_TYPE_PKT_INFO); 73 | 74 | ((pkt_info_t *)extsrc_msg_payload(res))->family = extsrc_af_to_wire(pkt->family); 75 | 76 | return res; 77 | } 78 | 79 | struct extsrc_msg * 80 | extsrc_msg_create_dns_query(dns_pkt_info_t *dns_pkt, int family, 81 | uint8_t *src_addr) 82 | { 83 | struct extsrc_dns_query_hdr hdr; 84 | char *buf; 85 | size_t buf_len; 86 | struct extsrc_msg *msg; 87 | 88 | hdr.family = family; 89 | memcpy(hdr.src_addr, src_addr, sizeof(hdr.src_addr)); 90 | 91 | buf_len = sizeof(hdr) + sizeof(*dns_pkt); 92 | buf = malloc(buf_len); 93 | if (!buf) { 94 | err(1, "malloc"); 95 | } 96 | 97 | memcpy(buf, &hdr, sizeof(hdr)); 98 | memcpy(buf + sizeof(hdr), dns_pkt, sizeof(*dns_pkt)); 99 | 100 | msg = extsrc_msg_create(buf, buf_len, EXTSRC_MSG_TYPE_DNS_QUERY); 101 | 102 | free(buf); 103 | 104 | return msg; 105 | } 106 | 107 | struct extsrc_msg * 108 | extsrc_msg_create_dns_answer(dns_pkt_info_t *dns_pkt) 109 | { 110 | return extsrc_msg_create((char *)dns_pkt, sizeof(*dns_pkt), 111 | EXTSRC_MSG_TYPE_DNS_ANSWER); 112 | } 113 | 114 | struct extsrc_msg * 115 | extsrc_msg_create_arp_table_update(struct extsrc_arp_table_update *up) 116 | { 117 | return extsrc_msg_create((char *)up, sizeof(*up), 118 | EXTSRC_MSG_TYPE_ARP_TABLE_UPDATE); 119 | } 120 | 121 | void 122 | extsrc_msg_free(struct extsrc_msg *msg) 123 | { 124 | free(msg->data); 125 | free(msg); 126 | } 127 | -------------------------------------------------------------------------------- /src/lib/ip_store.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SPIN_IP_STORE_H 3 | #define SPIN_IP_STORE_H 1 4 | 5 | #include "pkt_info.h" 6 | 7 | typedef struct ip_store_el { 8 | uint64_t k1; 9 | uint64_t k2; 10 | char* val; 11 | struct ip_store_el* next; 12 | } ip_store_el_t; 13 | 14 | typedef struct { 15 | ip_store_el_t* elements; 16 | } ip_store_t; 17 | 18 | ip_store_t* ip_store_create(void); 19 | void ip_store_destroy(ip_store_t* ip_store); 20 | int ip_store_contains_ip(ip_store_t* ip_store, unsigned char ip[16]); 21 | void ip_store_add_ip(ip_store_t* ip_store, int ipv6, unsigned char ip[16]); 22 | void ip_store_remove_ip(ip_store_t* ip_store, unsigned char ip[16]); 23 | void ip_store_for_each(ip_store_t* ip_store, 24 | void(*cb)(unsigned char[16], int is_ipv6, void* data), 25 | void* data); 26 | 27 | void log_set_verbosity(int new_verbosity); 28 | int log_get_verbosity(void); 29 | int* log_get_verbosity_ptr(void); 30 | void log_packet(pkt_info_t* pkt_info); 31 | void printv(int module_verbosity, const char* format, ...); 32 | void hexdump_k(uint8_t* data, unsigned int offset, unsigned int size); 33 | 34 | 35 | #endif // SPIN_IP_STORE_H 36 | -------------------------------------------------------------------------------- /src/lib/ipl.c: -------------------------------------------------------------------------------- 1 | #include "ipl.h" 2 | #include "spin_log.h" 3 | 4 | static struct list_info* ipl_list_ar; 5 | 6 | #define ipl_block ipl_list_ar[IPLIST_BLOCK] 7 | #define ipl_ignore ipl_list_ar[IPLIST_IGNORE] 8 | #define ipl_allow ipl_list_ar[IPLIST_ALLOW] 9 | 10 | // Make name of shadow file 11 | char* 12 | ipl_filename(struct list_info *lip) { 13 | static char listname[30]; 14 | 15 | sprintf(listname, "/etc/spin/%s.list", lip->li_listname); 16 | return listname; 17 | } 18 | 19 | void init_ipl(struct list_info *lip) { 20 | int cnt; 21 | char *fname; 22 | 23 | lip->li_tree = tree_create(cmp_ips); 24 | fname = ipl_filename(lip); 25 | cnt = read_ip_tree(lip->li_tree, fname); 26 | spin_log(LOG_DEBUG, "File %s, read %d entries\n", fname, cnt); 27 | } 28 | 29 | void init_all_ipl(struct list_info *ipl_list_ar_a) { 30 | int i; 31 | struct list_info *lip; 32 | 33 | ipl_list_ar = ipl_list_ar_a; 34 | 35 | for (i=0; ili_tree); 48 | } 49 | } 50 | 51 | void 52 | add_ip_tree_to_li(tree_t* tree, struct list_info *lip) { 53 | tree_entry_t* cur; 54 | 55 | if (tree == NULL) 56 | return; 57 | cur = tree_first(tree); 58 | while(cur != NULL) { 59 | tree_add(lip->li_tree, cur->key_size, cur->key, cur->data_size, cur->data, 1); 60 | cur = tree_next(cur); 61 | } 62 | lip->li_modified++; 63 | } 64 | 65 | void add_ip_to_li(ip_t* ip, struct list_info *lip) { 66 | tree_add(lip->li_tree, sizeof(ip_t), ip, 0, 0, 1); 67 | lip->li_modified++; 68 | } 69 | 70 | 71 | void 72 | remove_ip_tree_from_li(tree_t *tree, struct list_info *lip) { 73 | tree_entry_t* cur; 74 | 75 | if (tree == NULL) { 76 | return; 77 | } 78 | cur = tree_first(tree); 79 | while(cur != NULL) { 80 | tree_remove(lip->li_tree, cur->key_size, cur->key); 81 | cur = tree_next(cur); 82 | } 83 | lip->li_modified++; 84 | } 85 | 86 | void remove_ip_from_li(ip_t* ip, struct list_info *lip) { 87 | 88 | tree_remove(lip->li_tree, sizeof(ip_t), ip); 89 | lip->li_modified++; 90 | } 91 | 92 | int ip_in_li(ip_t* ip, struct list_info* lip) { 93 | 94 | return tree_find(lip->li_tree, sizeof(ip_t), ip) != NULL; 95 | } 96 | 97 | int ip_in_ignore_list(ip_t* ip) { 98 | 99 | return ip_in_li(ip, &ipl_list_ar[IPLIST_IGNORE]); 100 | } 101 | 102 | // hmz, we probably want to refactor ip_t/the tree list 103 | // into something that requires less data copying 104 | int addr_in_ignore_list(int family, uint8_t* addr) { 105 | ip_t ip; 106 | 107 | copy_ip_data(&ip, family, 0, addr); 108 | return ip_in_ignore_list(&ip); 109 | } 110 | -------------------------------------------------------------------------------- /src/lib/spin_list.c: -------------------------------------------------------------------------------- 1 | #include "ipl.h" 2 | #include "node_cache.h" 3 | 4 | /* 5 | * The three lists of IP addresses are now kept in memory in spind, with 6 | * a copy written to file. 7 | * BLOCK, IGNORE, ALLOW 8 | */ 9 | struct list_info ipl_list_ar[N_IPLIST] = { 10 | { 0, "block", 0 }, 11 | { 0, "ignore", 0 }, 12 | { 0, "allow", 0 }, 13 | }; 14 | 15 | struct list_info* get_spin_iplists() { 16 | return ipl_list_ar; 17 | }; 18 | 19 | struct list_info* get_spin_iplist(int index) { 20 | return &ipl_list_ar[index]; 21 | }; 22 | 23 | // Returns -1 if list not found 24 | int get_spin_iplist_id_by_name(const char* name) { 25 | int i; 26 | for (i=0; i < N_IPLIST; i++) { 27 | if (strncmp(name, ipl_list_ar[i].li_listname, strlen(name)) == 0) { 28 | return i; 29 | } 30 | } 31 | return -1; 32 | }; 33 | 34 | 35 | // TODO: REMOVE 36 | void add_ip_to_list(node_cache_t* node_cache, int list_id, ip_t* ip) { 37 | add_ip_to_li(ip, &ipl_list_ar[list_id]); 38 | } 39 | 40 | void remove_ip_from_list(node_cache_t* node_cache, int list_id, ip_t* ip) { 41 | remove_ip_from_li(ip, &ipl_list_ar[list_id]); 42 | } 43 | 44 | void add_node_to_list(node_cache_t* node_cache, int list_id, node_t* node) { 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/lib/spin_log.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "spin_log.h" 7 | 8 | int use_syslog_ = 1; 9 | int log_stdout_ = 0; 10 | int log_verbosity = 6; 11 | FILE* logfile = NULL; 12 | 13 | void spin_log_init(int use_syslog, int log_stdout, const char* log_filename, int verbosity, const char* ident) { 14 | log_verbosity = verbosity; 15 | if (log_filename && strlen(log_filename) > 0) { 16 | if (logfile) { 17 | fclose(logfile); 18 | logfile = NULL; 19 | } 20 | logfile = fopen(log_filename, "a"); 21 | if (!logfile) { 22 | fprintf(stderr, "Error opening logfile %s: %s\n", log_filename, strerror(errno)); 23 | } 24 | } 25 | if (use_syslog) { 26 | use_syslog_ = use_syslog; 27 | openlog(ident, 0, LOG_DAEMON); 28 | } 29 | log_stdout_ = log_stdout; 30 | } 31 | 32 | void spin_log_close() { 33 | if (logfile) { 34 | fclose(logfile); 35 | } 36 | closelog(); 37 | } 38 | 39 | void spin_log(int level, const char* format, ...) { 40 | va_list arg[3]; 41 | int arg_count = 0; 42 | int arg_current = 0; 43 | 44 | /* Write the error message */ 45 | if (level > log_verbosity) { 46 | return; 47 | } 48 | 49 | if (use_syslog_) { 50 | arg_count++; 51 | } 52 | if (log_stdout_) { 53 | arg_count++; 54 | } 55 | if (logfile) { 56 | arg_count++; 57 | } 58 | 59 | va_start(arg[0], format); 60 | for (int i = 1; i < arg_count; ++i) { 61 | va_copy(arg[i], arg[0]); 62 | } 63 | 64 | if (use_syslog_) { 65 | vsyslog(level, format, arg[arg_current]); 66 | arg_current++; 67 | } 68 | if (log_stdout_) { 69 | vprintf(format, arg[arg_current]); 70 | arg_current++; 71 | } 72 | if (logfile) { 73 | vfprintf(logfile, format, arg[arg_current]); 74 | arg_current++; 75 | fflush(logfile); 76 | } 77 | 78 | va_end(arg[0]); 79 | for (int i = 1; i < arg_count; ++i) { 80 | va_end(arg[i]); 81 | } 82 | } 83 | 84 | void spin_vlog(int level, const char* format, va_list arg) { 85 | if (level > log_verbosity) { 86 | return; 87 | } 88 | vsyslog(level, format, arg); 89 | } 90 | -------------------------------------------------------------------------------- /src/lib/spindata_type.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "spindata_type.h" 4 | 5 | char * 6 | spin_data_serialize(spin_data sd) { 7 | char *result; 8 | 9 | result = cJSON_PrintUnformatted(sd); 10 | 11 | // result is malloced, should be freed 12 | return result; 13 | } 14 | 15 | void 16 | spin_data_ser_delete(char *str) { 17 | 18 | free(str); 19 | } 20 | 21 | void 22 | spin_data_delete(spin_data sd) { 23 | 24 | cJSON_Delete(sd); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/lib/statistics.c: -------------------------------------------------------------------------------- 1 | #include "statistics.h" 2 | 3 | /* 4 | * Routine to do statistics 5 | */ 6 | 7 | spin_stat_t spin_stat_end = { 0 }; 8 | stat_p spin_stat_chain = &spin_stat_end; 9 | 10 | void 11 | spin_stat_val(stat_p sp, int val) { 12 | 13 | if (sp->stat_next == 0) { 14 | // First time use 15 | 16 | // Prepend to list, currently in reverse chronological order 17 | // Perhaps TODO, although UI should solve this 18 | sp->stat_next = spin_stat_chain; 19 | spin_stat_chain = sp; 20 | } 21 | sp->stat_count++; 22 | switch (sp->stat_type) { 23 | case STAT_TOTAL: 24 | sp->stat_value += val; 25 | break; 26 | case STAT_MAX: 27 | if (val > sp->stat_value) { 28 | sp->stat_value = val; 29 | } 30 | break; 31 | case N_STAT: 32 | // should not happen. 33 | break; 34 | } 35 | } 36 | 37 | #if DO_SPIN_STATS == 2 38 | 39 | spin_stat_start() { 40 | 41 | } 42 | 43 | spin_stat_finish() { 44 | spin_stat_t *sp; 45 | 46 | for (sp = spin_stat_chain; sp->stat_module; sp = sp->stat_next) { 47 | fprintf(stderr, "{ \"module\": \"%s\", \"name\": \"%s\", \"type\": %d, \"value\": %d, \"count\": %d }\n", 48 | sp->stat_module, sp->stat_name, 49 | sp->stat_type, sp->stat_value, sp->stat_count); 50 | } 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /src/lib/tests/Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CPPFLAGS = -I$(top_srcdir)/include -Wall -Werror -DTESTDATA_PATH='"$(abs_srcdir)/testdata"' 2 | AM_CFLAGS = ${regular_CFLAGS} -g 3 | 4 | CLEANFILES = *.gcda *.gcno *.gcov 5 | 6 | bin_PROGRAMS = tree_test node_cache_test arp_test node_names_test util_test dns_cache_test 7 | 8 | tree_test_SOURCES = tree_test.c ../tree.c ../util.c ../spin_log.c 9 | tree_test_CFLAGS = -I../ -fprofile-arcs -ftest-coverage 10 | tree_test_LDFLAGS = -L../ 11 | 12 | node_cache_test_SOURCES = node_cache_test.c 13 | node_cache_test_CFLAGS = -I../ -fprofile-arcs -ftest-coverage 14 | node_cache_test_LDFLAGS = -L../ 15 | #node_cache_test_LDADD = $(top_builddir)/lib/libspin.a 16 | 17 | dns_cache_test_SOURCES = dns_cache_test.c ../dns_cache.c ../util.c ../tree.c ../pkt_info.c ../spin_log.c 18 | dns_cache_test_CFLAGS = -I../ -fprofile-arcs -ftest-coverage 19 | dns_cache_test_LDFLAGS = -L../ 20 | 21 | 22 | arp_test_SOURCES = ../util.c ../tree.c ../spin_log.c arp_test.c 23 | arp_test_CFLAGS = -I../ -fprofile-arcs -ftest-coverage 24 | arp_test_LDFLAGS = -L../ 25 | 26 | node_names_test_SOURCES = node_names_test.c ../node_names.c ../util.c ../tree.c ../spin_log.c 27 | node_names_test_CFLAGS = -I../ -fprofile-arcs -ftest-coverage 28 | node_names_test_LDFLAGS = -L../ 29 | 30 | util_test_SOURCES = util_test.c ../util.c ../tree.c ../pkt_info.c ../spin_log.c 31 | util_test_CFLAGS = -I../ -fprofile-arcs -ftest-coverage 32 | util_test_LDFLAGS = -L../ 33 | 34 | all-local: 35 | $(srcdir)/run_tests.sh 36 | -------------------------------------------------------------------------------- /src/lib/tests/arp_test.c: -------------------------------------------------------------------------------- 1 | 2 | #include "disable_statistics.h" 3 | 4 | #include "arp.h" 5 | 6 | #include "../arp.c" 7 | 8 | #include 9 | 10 | void 11 | test_read() { 12 | arp_table_t* arp_table = arp_table_create(); 13 | arp_table_read(arp_table); 14 | arp_table_print(arp_table); 15 | arp_table_destroy(arp_table); 16 | } 17 | 18 | void 19 | test_add() { 20 | arp_table_t* arp_table = arp_table_create(); 21 | 22 | assert(arp_table_size(arp_table) == 0); 23 | 24 | arp_table_add(arp_table, "127.0.0.1", "aa:bb:cc:dd:ee:ff"); 25 | arp_table_add(arp_table, "::1", "aa:bb:cc:dd:ee:ff"); 26 | 27 | assert(arp_table_size(arp_table) == 2); 28 | 29 | arp_table_destroy(arp_table); 30 | } 31 | 32 | void 33 | test_find() { 34 | arp_table_t* arp_table = arp_table_create(); 35 | char* mac; 36 | 37 | assert(arp_table_size(arp_table) == 0); 38 | 39 | arp_table_add(arp_table, "127.0.0.1", "aa:bb:cc:dd:ee:ff"); 40 | arp_table_add(arp_table, "::1", "ff:ee:dd:cc:bb:aa"); 41 | arp_table_add(arp_table, "bad_address", "bb:bb:bb:bb:bb:bb"); 42 | 43 | assert(arp_table_size(arp_table) == 2); 44 | 45 | mac = arp_table_find_by_str(arp_table, "127.0.0.2"); 46 | assert(mac == NULL); 47 | 48 | mac = arp_table_find_by_str(arp_table, "127.0.0.1"); 49 | assert(strcmp(mac, "aa:bb:cc:dd:ee:ff") == 0); 50 | 51 | mac = arp_table_find_by_str(arp_table, "::1"); 52 | assert(strcmp(mac, "ff:ee:dd:cc:bb:aa") == 0); 53 | 54 | mac = arp_table_find_by_str(arp_table, "bad_address"); 55 | assert(mac == NULL); 56 | 57 | arp_table_destroy(arp_table); 58 | } 59 | 60 | int 61 | main(int argc, char** argv) { 62 | test_read(); 63 | test_add(); 64 | test_find(); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /src/lib/tests/disable_statistics.h: -------------------------------------------------------------------------------- 1 | // Workaround for statistics code being too tightly coupled with spind at this moment 2 | // Include this file to disable statistics 3 | #define SPIN_STATISTICS_H 1 4 | 5 | #define STAT_MODULE(a) 6 | #define STAT_VALUE(a,b) {} 7 | #define STAT_COUNTER(a,b,c) 8 | 9 | void spin_stat_val() {} 10 | -------------------------------------------------------------------------------- /src/lib/tests/netlink_commands_test.c: -------------------------------------------------------------------------------- 1 | 2 | #include "util.h" 3 | 4 | #include "test_helper.h" 5 | 6 | #define ASSERT(stmt) assert(( 7 | 8 | int main(int argc, char** argv) { 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/tests/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | make *_test 3 | TESTS=`find . -name \*_test` 4 | for i in ${TESTS}; do 5 | echo "run $i" 6 | $i >& /dev/null 7 | RESULT=$? 8 | if [ $RESULT -ne 0 ]; then 9 | $i 10 | echo "$i failed" 11 | exit $RESULT 12 | else 13 | echo "$i succeeded" 14 | fi 15 | done 16 | 17 | mv ../*_test-* ./ 18 | gcov tree_test-tree.c 19 | gcov node_cache_test-node_cache.c 20 | gcov node_names_test-node_names.c 21 | gcov arp_test-arp.c 22 | gcov util_test-util.c 23 | gcov dns_cache_test-dns_cache.c 24 | rm *.gcda *.gcno 25 | -------------------------------------------------------------------------------- /src/lib/tests/test_helper.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define clean_errno() (errno == 0 ? "None" : strerror(errno)) 8 | #define log_error(M, ...) fprintf(stdout, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__) 9 | #define assertf(A, M, ...) if(!(A)) {log_error(M, ##__VA_ARGS__); assert(A); } 10 | -------------------------------------------------------------------------------- /src/lib/tests/testdata/node_names_dhcp.conf: -------------------------------------------------------------------------------- 1 | config host 2 | option name 'some host' 3 | option mac '18:aa:6a:23:55:dc' 4 | option ip '192.0.2.1' 5 | 6 | config host 7 | option name Foo 8 | option mac '00:11:55:0a:ed:bf' 9 | option ip '192.0.2.155' 10 | 11 | config host 12 | option name 'Foo \' dus' 13 | option mac '00:11:55:0a:ed:bb' 14 | option ip '192.0.2.156' 15 | 16 | 17 | 18 | config dhcp 'test_subnet' 19 | option start '100' 20 | option leasetime '12h' 21 | option limit '150' 22 | option interface 'test_subnet' 23 | option ra 'server' 24 | option dhcpv6 'server' 25 | option ra_management '1' 26 | list dhcp_option '6,192.0.2.1' 27 | 28 | config host 29 | option mac '00:02:aa:c1:23:92' 30 | option name 'bar stuff' 31 | option ip '192.0.2.50' 32 | 33 | config host 34 | option mac '00:02:aa:c1:23:93' 35 | option name "bar\" enzo" 36 | option ip '192.0.2.51' 37 | -------------------------------------------------------------------------------- /src/lib/tests/testdata/node_names_spin_userdata.conf: -------------------------------------------------------------------------------- 1 | name: 192.0.2.1 some_other_host 2 | name: 192.0.2.2 number two 3 | name: 2001:DB8:beef:1::80 server 4 | name: 213.152.224.1 open 5 | name: 00:02:aa:c1:23:92 replaces bar 6 | filter: fe80::6f0:21ff:fe23:2c5c 7 | filter: 192.168.1.1 8 | filter: 192.168.1.3 9 | filter: 192.168.1.10 10 | name: 2001:DB8:beef:1::123:aaaa barserver 11 | filter: 0.0.0.0 12 | filter: ::1 13 | block: 192.168.1.10 14 | block: 192.168.1.11 15 | block: 192.168.1.12 16 | name: aa:bb:cc:dd:ee:ff asdfasdf 17 | name: 192.0.2.123 one two three 18 | block: 192.168.1.13 19 | block: 192.168.1.14 20 | -------------------------------------------------------------------------------- /src/lua/arp.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ARP lookup functions 3 | -- 4 | 5 | local util = require 'util' 6 | local wirefmt = require 'wirefmt' 7 | 8 | local arp = {} 9 | 10 | -- arp table contents: 11 | -- -> { 'hw': , 'device': } 12 | 13 | function arp:get_hw_address(ip_address) 14 | -- load if not loaded, or if ip is not in currently loaded table 15 | -- normalize the ip address 16 | ip_address = wirefmt.ntop(wirefmt.pton(ip_address)) 17 | if not arp.arptable or not arp.arptable[ip_address] then 18 | arp.arptable = util:get_arp_table() 19 | end 20 | if arp.arptable[ip_address] then 21 | return arp.arptable[ip_address] 22 | else 23 | return nil 24 | end 25 | end 26 | 27 | function arp:get_ip_addresses(hw_address) 28 | local result = {} 29 | if not arp.arptable or not arp.arptable[ip_address] then 30 | arp.arptable = util:get_arp_table() 31 | end 32 | for k,v in pairs(arp.arptable) do 33 | if v == hw_address then 34 | table.insert(result, k) 35 | end 36 | end 37 | return result 38 | end 39 | 40 | function arp:print_table() 41 | print("ARP TABLE:") 42 | if not arp.arptable then 43 | print("") 44 | else 45 | for k,v in pairs(arp.arptable) do 46 | print(k .. ": " .. v) 47 | end 48 | end 49 | end 50 | 51 | return arp; 52 | -------------------------------------------------------------------------------- /src/lua/dns_cache.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- This is a SPIN-specific DNS cache 3 | -- 4 | -- It stores information about A/AAAA query answers, 5 | -- keyed by the answer *value* 6 | -- 7 | -- The purpose is to (shortly) remember what DNS queries IP traffic 8 | -- was triggered by. 9 | -- 10 | 11 | local verbose = false 12 | 13 | local _M = {} 14 | 15 | function _M.vprint(msg) 16 | if verbose then 17 | print("[SPIN/DNSCache] " .. msg) 18 | end 19 | end 20 | 21 | local DNSCacheEntry = {} 22 | DNSCacheEntry.__index = DNSCacheEntry 23 | 24 | function _M.DNSCacheEntry_create() 25 | local cache_entry = {} 26 | setmetatable(cache_entry, DNSCacheEntry) 27 | cache_entry.domains = {} 28 | cache_entry.size = 0 29 | return cache_entry 30 | end 31 | 32 | function DNSCacheEntry:add_domain(domain, timestamp) 33 | local existing_domain = self.domains[domain] 34 | if existing_domain == nil then 35 | self.size = self.size + 1 36 | end 37 | self.domains[domain] = timestamp 38 | end 39 | 40 | function DNSCacheEntry:clean(clean_before) 41 | for domain,timestamp in pairs(self.domains) do 42 | if timestamp < clean_before then 43 | self.domains[domain] = nil 44 | self.size = self.size - 1 45 | end 46 | end 47 | end 48 | 49 | function DNSCacheEntry:print(out) 50 | for domain, timestamp in pairs(self.domains) do 51 | out:write(" " .. timestamp .. " " .. domain .. "\n") 52 | end 53 | end 54 | 55 | local DNSCache = {} 56 | DNSCache.__index = DNSCache 57 | 58 | function _M.DNSCache_create() 59 | local cache = {} 60 | setmetatable(cache, DNSCache) 61 | cache.entries = {} 62 | cache.size = 0 63 | return cache 64 | end 65 | 66 | function DNSCache:add(address, domain, timestamp) 67 | _M.vprint("Add to cache: " .. address .. " " .. domain .. " at " .. timestamp) 68 | local entry = self.entries[address] 69 | if entry == nil then 70 | entry = _M.DNSCacheEntry_create() 71 | self.size = self.size + 1 72 | --return true 73 | end 74 | entry:add_domain(domain, timestamp) 75 | self.entries[address] = entry 76 | -- return false if now new? 77 | return true 78 | end 79 | 80 | function DNSCache:clean(clean_before) 81 | for address, entry in pairs(self.entries) do 82 | entry:clean(clean_before) 83 | if entry.size == 0 then 84 | self.entries[address] = nil 85 | self.size = self.size - 1 86 | end 87 | end 88 | _M.vprint("Cache size: " .. self.size) 89 | end 90 | 91 | function DNSCache:get_domains(address) 92 | local result = {} 93 | local entry = self.entries[address] 94 | if entry ~= nil then 95 | for domain,_ in pairs(entry.domains) do 96 | table.insert(result, domain) 97 | end 98 | else 99 | _M.vprint("no cache entry for " .. address) 100 | end 101 | return result 102 | end 103 | 104 | function DNSCache:print(out) 105 | out:write("------- FULL DNS CACHE -------\n") 106 | out:write(" containing " .. self.size .. " entries.\n") 107 | for address, entry in pairs(self.entries) do 108 | out:write(address .. "\n") 109 | entry:print(out) 110 | end 111 | out:write("------ END OF DNS CACHE ------\n") 112 | end 113 | 114 | -- There is one global cache for ease of use 115 | -- (but users are free to initialize their own) 116 | _M.dnscache = _M.DNSCache_create() 117 | 118 | return _M 119 | -------------------------------------------------------------------------------- /src/lua/show_ips.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | require "io" 4 | 5 | function capture(cmd, single) 6 | local f = assert(io.popen(cmd, 'r')) 7 | local s = assert(f:read('*a')) 8 | f:close() 9 | if single then 10 | return s 11 | else 12 | return string.gmatch(s, '[^\n\r]+') 13 | end 14 | end 15 | 16 | function get_all_bound_ip_addresses() 17 | local s = capture("ip addr show", true) 18 | local result = {} 19 | result["0.0.0.0"] = true 20 | for l in string.gmatch(s, "inet %d+.%d+.%d+.%d+") do 21 | result[l:sub(6)] = true 22 | end 23 | for l in string.gmatch(s, "inet6 [%da-f:]+") do 24 | result[l:sub(7)] = true 25 | end 26 | return result 27 | end 28 | 29 | function exists(filename) 30 | local f = io.open(filename, "r") 31 | if f ~= nil then 32 | io.close(f) 33 | return true 34 | else 35 | return false 36 | end 37 | end 38 | 39 | function help(rcode) 40 | print("usage: show_ips.lua [options]") 41 | print("shows the current IP addresses of this system") 42 | print("options:") 43 | print("-h show this help") 44 | print("-o write output to file if it does not exist") 45 | print("-f overwrite file from -o even if it does exist") 46 | os.exit(rcode) 47 | end 48 | 49 | local output_file = nil 50 | local overwrite = false 51 | local i 52 | for i=1,#arg do 53 | local val = arg[i] 54 | if val == "-h" or val == "-help" then 55 | help(0) 56 | elseif val == "-o" then 57 | if #arg > i then 58 | i = i + 1 59 | output_file = arg[i] 60 | if output_file:sub(0,1) == "-" then 61 | help(1) 62 | end 63 | else 64 | help(1) 65 | end 66 | elseif val == "-f" then 67 | overwrite = true 68 | end 69 | end 70 | 71 | addrs = get_all_bound_ip_addresses() 72 | if output_file then 73 | if exists(output_file) then 74 | if not overwrite then 75 | print("file exists, aborting") 76 | return 77 | end 78 | end 79 | local f = io.open(output_file, "w") 80 | for i,v in pairs(addrs) do 81 | f:write(i .. "\n") 82 | end 83 | io.close(f) 84 | else 85 | for i,v in pairs(addrs) do 86 | print(i) 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /src/lua/spin_mudd.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | local mqtt = require 'mosquitto' 4 | local json = require 'json' 5 | local luamud = require 'luamud' 6 | 7 | local TRAFFIC_CHANNEL = "SPIN/traffic" 8 | 9 | local HISTORY_SIZE = 300 10 | local PRINT_INTERVAL = 10 11 | 12 | local verbose = true 13 | 14 | function vprint(msg) 15 | if verbose then 16 | print("[SPIN/mqtt] " .. msg) 17 | end 18 | end 19 | 20 | local client = mqtt.new() 21 | 22 | client.ON_CONNECT = function() 23 | vprint("Connected to MQTT broker") 24 | client:subscribe(TRAFFIC_CHANNEL) 25 | vprint("Subscribed to " .. TRAFFIC_CHANNEL) 26 | end 27 | 28 | function block_node(node) 29 | for _,v in pairs(node["ips"]) do 30 | local cmd = "spin_config block add " .. v 31 | print(cmd) 32 | os.execute(cmd) 33 | end 34 | end 35 | 36 | local mud = luamud.mud_create_from_file("tests/mud_dns_only.json") 37 | 38 | function handle_traffic_message(payload) 39 | --local timestamp = payload["timestamp"] 40 | --history[timestamp] = payload["flows"] 41 | --history_clean() 42 | -- for each flow, we loop through all: 43 | -- - from ip addresses 44 | -- - from domain names 45 | -- - to ip addresses 46 | -- - to domain names 47 | -- Depending on which mac address is set (from or to or none) 48 | -- this should check from_device or to_device policies 49 | for _,flow in pairs(payload["flows"]) do 50 | local from_device 51 | local ips 52 | local domains 53 | local port 54 | if flow["from"]["mac"] ~= nil then 55 | from_device = true 56 | ips = flow["to"]["ips"] 57 | domains = flow["to"]["domains"] 58 | to_port = flow["to_port"] 59 | from_port = flow["from_port"] 60 | elseif flow["to"]["mac"] ~= nil then 61 | to_device = true 62 | ips = flow["from"]["ips"] 63 | domains = flow["from"]["domains"] 64 | to_port = flow["to_port"] 65 | from_port = flow["from_port"] 66 | else 67 | print("[XX] ignoring flow, not from or to device (no mac known)") 68 | break; 69 | end 70 | local actions = mud:get_policy_actions(from_device, ips, domains, from_port, to_port) 71 | print(actions) 72 | end 73 | end 74 | 75 | client.ON_MESSAGE = function(mid, topic, payload) 76 | local pd = json.decode(payload) 77 | if pd["command"] and pd["command"] == "traffic" then 78 | handle_traffic_message(pd["result"]) 79 | end 80 | end 81 | 82 | 83 | vprint("SPIN MUD test tool") 84 | broker = arg[1] -- defaults to "localhost" if arg not set 85 | client:connect(broker) 86 | vprint("connected") 87 | 88 | local last_print = os.time() 89 | while true do 90 | local cur = os.time() 91 | client:loop() 92 | end 93 | -------------------------------------------------------------------------------- /src/lua/tests/mud_dns_only.json: -------------------------------------------------------------------------------- 1 | { 2 | "ietf-mud:mud": { 3 | "mud-url": 4 | "https://dns.example/.well-known/mud/v1/dns_device", 5 | "last-update": "2017-10-07T12:16:24+02:00", 6 | "cache-validity": 48, 7 | "is-supported": true, 8 | "systeminfo": 9 | "https://dns.example/documentation/dnsdevice", 10 | "from-device-policy": { 11 | "access-lists": { 12 | "access-list": [ 13 | { 14 | "acl-name": "dns_only", 15 | "acl-type": "ietf-access-control-list:ipv6-acl" 16 | } 17 | ] 18 | } 19 | } 20 | }, 21 | "ietf-access-control-list:access-lists": { 22 | "acl": [ 23 | { 24 | "acl-name": "dns_only", 25 | "acl-type": "ipv6-acl", 26 | "access-list-entries": { 27 | "ace": [ 28 | { 29 | "rule-name": "dns_queries", 30 | "matches": { 31 | "ipv6-acl": { 32 | "destination-port-range": { 33 | "lower-port": 53 34 | } 35 | } 36 | }, 37 | "actions": { 38 | "forwarding": "accept" 39 | } 40 | } 41 | ] 42 | } 43 | } 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lua/tests/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "ietf-mud:mud": { 3 | "mud-url": 4 | "https://bms.example.com/.well-known/mud/v1/lightbulb2000", 5 | "last-update": "2017-10-07T12:16:24+02:00", 6 | "cache-validity": 48, 7 | "is-supported": true, 8 | "systeminfo": 9 | "https://bms.example.com/descriptions/lightbulb2000", 10 | "from-device-policy": { 11 | "access-lists": { 12 | "access-list": [ 13 | { 14 | "acl-name": "mud-14377-v6fr", 15 | "acl-type": "ietf-access-control-list:ipv6-acl" 16 | } 17 | ] 18 | } 19 | }, 20 | "to-device-policy": { 21 | "access-lists": { 22 | "access-list": [ 23 | { 24 | "acl-name": "mud-14377-v6to", 25 | "acl-type": "ietf-access-control-list:ipv6-acl" 26 | } 27 | ] 28 | } 29 | } 30 | }, 31 | "ietf-access-control-list:access-lists": { 32 | "acl": [ 33 | { 34 | "acl-name": "mud-14377-v6to", 35 | "acl-type": "ipv6-acl", 36 | "access-list-entries": { 37 | "ace": [ 38 | { 39 | "rule-name": "cl0-todev", 40 | "matches": { 41 | "ipv6-acl": { 42 | "ietf-acldns:src-dnsname": 43 | "service.bms.example.com", 44 | "protocol": 6, 45 | "source-port-range": { 46 | "lower-port": 443, 47 | "upper-port": 443 48 | } 49 | }, 50 | "tcp-acl": { 51 | "ietf-mud:direction-initiated": "from-device" 52 | } 53 | }, 54 | "actions": { 55 | "forwarding": "accept" 56 | } 57 | } 58 | ] 59 | } 60 | }, 61 | { 62 | "acl-name": "mud-14377-v6fr", 63 | "acl-type": "ipv6-acl", 64 | "access-list-entries": { 65 | "ace": [ 66 | { 67 | "rule-name": "cl0-frdev", 68 | "matches": { 69 | "ipv6-acl": { 70 | "ietf-acldns:dst-dnsname": 71 | "service.bms.example.com", 72 | "protocol": 6, 73 | "destination-port-range": { 74 | "lower-port": 443, 75 | "upper-port": 443 76 | } 77 | }, 78 | "tcp-acl": { 79 | "ietf-mud:direction-initiated": "from-device" 80 | } 81 | }, 82 | "actions": { 83 | "forwarding": "accept" 84 | } 85 | } 86 | ] 87 | } 88 | } 89 | ] 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/lua/tests/test_mud.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | local luamud = require 'luamud' 4 | 5 | local mud, err = luamud.mud_create_from_file("tests/test.json") 6 | if mud == nil then 7 | print("Error: " .. err) 8 | else 9 | print(mud:to_json()) 10 | print("MUD URL: " .. mud:get_mud_url()) 11 | print("Last update: " .. mud:get_last_update()) 12 | print("Cache validity: " .. mud:get_cache_validity()) 13 | if mud:get_is_supported() then 14 | print("Supported: Yes") 15 | else 16 | print("Supported: No") 17 | end 18 | if mud:get_systeminfo() then 19 | print("Systeminfo: " .. mud:get_systeminfo()) 20 | else 21 | print("Systeminfo not set") 22 | end 23 | 24 | print("Globally defined acls:") 25 | for _,acl in pairs(mud:get_acls()) do 26 | print(" " .. acl:get_name()) 27 | for _,r in pairs(acl:get_rules()) do 28 | print(" " .. r:get_name()) 29 | end 30 | end 31 | 32 | print("From-device policy:") 33 | for acl_n, acl_type in pairs(mud:get_from_device_acls()) do 34 | print(" " .. acl_n .. " (" .. acl_type .. ")") 35 | end 36 | 37 | print("To-device policy:") 38 | for acl_n, acl_type in pairs(mud:get_to_device_acls()) do 39 | print(" " .. acl_n .. " (" .. acl_type .. ")") 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /src/lua/tests/testdata/firewall1.config: -------------------------------------------------------------------------------- 1 | config defaults 2 | option syn_flood 1 3 | option input ACCEPT 4 | option output ACCEPT 5 | option forward REJECT 6 | 7 | config zone 8 | option name lan 9 | list network 'lan' 10 | option input ACCEPT 11 | option output ACCEPT 12 | option forward ACCEPT 13 | 14 | config zone 15 | option name wan 16 | list network 'wan' 17 | list network 'wan6' 18 | option input REJECT 19 | option output ACCEPT 20 | option forward REJECT 21 | option masq 1 22 | option mtu_fix 1 23 | 24 | config forwarding 25 | option src lan 26 | option dest wan 27 | 28 | config rule 29 | option name Allow-DHCP-Renew 30 | option src wan 31 | option proto udp 32 | option dest_port 68 33 | option target ACCEPT 34 | option family ipv4 35 | 36 | config rule 37 | option name Allow-Ping 38 | option src wan 39 | option proto icmp 40 | option icmp_type echo-request 41 | option family ipv4 42 | option target ACCEPT 43 | 44 | config rule 45 | option name Allow-IGMP 46 | option src wan 47 | option proto igmp 48 | option family ipv4 49 | option target ACCEPT 50 | 51 | config rule 52 | option name Allow-DHCPv6 53 | option src wan 54 | option proto udp 55 | option src_ip fe80::/10 56 | option dest_ip fe80::/10 57 | option dest_port 546 58 | option family ipv6 59 | option target ACCEPT 60 | 61 | config rule 62 | option name Allow-MLD 63 | option src wan 64 | option proto icmp 65 | option src_ip fe80::/10 66 | list icmp_type '130/0' 67 | list icmp_type '131/0' 68 | list icmp_type '132/0' 69 | list icmp_type '143/0' 70 | option family ipv6 71 | option target ACCEPT 72 | 73 | config rule 74 | option name Allow-ICMPv6-Input 75 | option src wan 76 | option proto icmp 77 | list icmp_type echo-request 78 | list icmp_type echo-reply 79 | list icmp_type destination-unreachable 80 | list icmp_type packet-too-big 81 | list icmp_type time-exceeded 82 | list icmp_type bad-header 83 | list icmp_type unknown-header-type 84 | list icmp_type router-solicitation 85 | list icmp_type neighbour-solicitation 86 | list icmp_type router-advertisement 87 | list icmp_type neighbour-advertisement 88 | option limit 1000/sec 89 | option family ipv6 90 | option target ACCEPT 91 | 92 | config rule 93 | option name Allow-ICMPv6-Forward 94 | option src wan 95 | option dest * 96 | option proto icmp 97 | list icmp_type echo-request 98 | list icmp_type echo-reply 99 | list icmp_type destination-unreachable 100 | list icmp_type packet-too-big 101 | list icmp_type time-exceeded 102 | list icmp_type bad-header 103 | list icmp_type unknown-header-type 104 | option limit 1000/sec 105 | option family ipv6 106 | option target ACCEPT 107 | 108 | config include 109 | option path /etc/firewall.user 110 | -------------------------------------------------------------------------------- /src/lua/util.lua: -------------------------------------------------------------------------------- 1 | 2 | local wirefmt = require "wirefmt" 3 | local util = {} 4 | 5 | -- 6 | -- External process utility functions 7 | -- 8 | 9 | -- if single is true, the entire output is returned as one value 10 | -- if single is false, an iterator is returned that returns one 11 | -- non-empty line per call 12 | function util:capture(cmd, single) 13 | local f = assert(io.popen(cmd, 'r')) 14 | local s = assert(f:read('*a')) 15 | f:close() 16 | if single then 17 | return s 18 | else 19 | return string.gmatch(s, '[^\n\r]+') 20 | end 21 | end 22 | 23 | function util:get_all_bound_ip_addresses() 24 | local s = util:capture("ip addr show", true) 25 | local result = {} 26 | result["0.0.0.0"] = true 27 | for l in string.gmatch(s, "inet %d+.%d+.%d+.%d+") do 28 | result[l:sub(6)] = true 29 | end 30 | for l in string.gmatch(s, "inet6 [%da-f:]+") do 31 | result[l:sub(7)] = true 32 | end 33 | return result 34 | end 35 | 36 | function parse_ip_neigh_output(s) 37 | local result = {} 38 | for l in s do 39 | local tokens = util:line_to_tokens(l) 40 | -- normalize the ip address 41 | result[wirefmt.ntop(wirefmt.pton(tokens[1]))] = tokens[5] 42 | end 43 | return result 44 | end 45 | 46 | function util:get_arp_table() 47 | local result = parse_ip_neigh_output(util:capture("ip neigh", false)) 48 | util:merge_tables(result, parse_ip_neigh_output(util:capture("ip -6 neigh", false))) 49 | return result 50 | end 51 | 52 | -- reads the dhcp config file (if it exists) 53 | -- returns a table of mac->name (nil if not set) 54 | -- returns nil if file not found 55 | function util:read_dhcp_config_hosts(filename) 56 | local result = {} 57 | local f = io.open(filename, "r") 58 | if not f then return result end 59 | s = f:read("*all") 60 | f:close() 61 | -- err, we probably need a somewhat decent parser here; the order is not fixed 62 | for name,hw in string.gmatch(s, "config host%s+option name '(%S+)'%s+option mac '(%S+)'") do 63 | result[hw] = name 64 | end 65 | return result 66 | end 67 | 68 | -- Calls unbound-host to do a reverse lookup 69 | -- returns the domain name if found, or nil if not 70 | -- (TODO: also try host, bind-host and knot-host?) 71 | function util:reverse_lookup(address) 72 | local s = util:capture("unbound-host " .. address, true) 73 | for token in string.gmatch(s, "domain name pointer (%S+)") do 74 | return token 75 | end 76 | return "No reverse name found" 77 | end 78 | 79 | -- calls the whois command and returns the *first* descr: line value 80 | function util:whois_desc(address) 81 | local s = util:capture("whois " .. address, true) 82 | for token in string.gmatch(s, "OrgName:%s+([^\r\n]+)") do 83 | return token 84 | end 85 | for token in string.gmatch(s, "descr:%s+([^\r\n]+)") do 86 | return token 87 | end 88 | return "Not found" 89 | end 90 | 91 | -- 92 | -- Assorted basic utility functions 93 | -- 94 | function util:merge_tables(a, b) 95 | for k,v in pairs(b) do 96 | a[k] = v 97 | end 98 | end 99 | 100 | function util:line_to_tokens(line) 101 | local result = {} 102 | for token in string.gmatch(line, "%S+") do 103 | table.insert(result, token) 104 | end 105 | return result 106 | end 107 | 108 | return util 109 | -------------------------------------------------------------------------------- /src/spind/Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | AM_CPPFLAGS = -I$(top_srcdir)/include 3 | AM_CFLAGS = ${regular_CFLAGS} -g -Wall -Werror 4 | 5 | bin_PROGRAMS = spind 6 | 7 | spind_SOURCES = spind.c \ 8 | cJSON.c \ 9 | core2block.c \ 10 | core2extsrc.c \ 11 | core2pubsub.c \ 12 | dots.c \ 13 | dots.h \ 14 | dnshooks.c \ 15 | mainloop.c \ 16 | mainloop.h \ 17 | process_pkt_info.c \ 18 | rpc_calls.c \ 19 | rpc_calls.h \ 20 | rpc_common.c \ 21 | rpc_json.c \ 22 | spindata.c \ 23 | spinhook.c \ 24 | statistics.c 25 | if !PASSIVE_MODE_ONLY 26 | spind_SOURCES += core2conntrack.c \ 27 | core2nflog_dns.c \ 28 | nflogroutines.c \ 29 | nfqroutines.c 30 | endif 31 | 32 | 33 | #spind_CFLAGS = 34 | spind_LDADD = $(top_builddir)/lib/libspin.a 35 | 36 | if USE_UBUS 37 | spind_SOURCES += rpc_ubus.c 38 | spind_LDADD += -lubox -lubus -lblobmsg_json 39 | endif 40 | 41 | version.c: 42 | $(top_srcdir)/../scripts/create_version_c.sh $(top_srcdir)/../VERSION 43 | 44 | .PHONY: version.c 45 | -------------------------------------------------------------------------------- /src/spind/core2block.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_CORE2BLOCK_H 2 | #define SPIN_CORE2BLOCK_H 1 3 | 4 | #include "util.h" 5 | #include "node_cache.h" 6 | 7 | int init_core2block(int passive_mode); 8 | void cleanup_core2block(); 9 | 10 | void c2b_changelist(void* arg, int iplist, int add, ip_t *ip_addr); 11 | void c2b_node_persistent_start(int nodenum); 12 | void c2b_node_persistent_end(int nodenum); 13 | void c2b_node_ipaddress(int nodenum, ip_t *ip_addr); 14 | void c2b_blockflow_start(int nodenum1, int nodenum2); 15 | void c2b_blockflow_end(int nodenum1, int nodenum2); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/spind/core2conntrack.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE2CONNTRACK 2 | #define CORE2CONNTRACK 1 3 | #include "node_cache.h" 4 | #include "process_pkt_info.h" 5 | #include "spinhook.h" 6 | 7 | // Initialize the core2conntrack module 8 | // Arguments: 9 | // node_cache_t* node_cache: the global spin node cache 10 | // int local_mode: set to 1 to not ignore this device's data 11 | // 12 | // We may want to add some options here (such as a configurable queue number) 13 | 14 | int init_core2conntrack(node_cache_t* node_cache, int local_mode, trafficfunc); 15 | void cleanup_core2conntrack(); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/spind/core2extsrc.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE2EXTSRC_H 2 | #define CORE2EXTSRC_H 1 3 | #include "dns_cache.h" 4 | #include "node_cache.h" 5 | #include "spinhook.h" 6 | 7 | int init_core2extsrc(node_cache_t *, dns_cache_t *, trafficfunc, char *, char *); 8 | void cleanup_core2extsrc(); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/spind/core2nflog_dns.c: -------------------------------------------------------------------------------- 1 | 2 | #include "dns.h" 3 | #include "dnshooks.h" 4 | #include "nflogroutines.h" 5 | #include "spin_config.h" 6 | #include "spin_log.h" 7 | 8 | static struct handle_dns_ctx *handle_dns_ctx = NULL; 9 | 10 | static void nflog_dns_callback(void* arg, int family, int protocol, 11 | uint8_t* data, int size, 12 | uint8_t* src_addr, uint8_t* dest_addr, 13 | unsigned int src_port, unsigned int dest_port) { 14 | // skip udp header (all packets are udp atm) 15 | size_t header_size = 0; 16 | 17 | spin_log(LOG_DEBUG, "DNS callback for packet src port %u dst port %u\n", src_port, dest_port); 18 | 19 | if (src_port == 53) { 20 | handle_dns_answer(handle_dns_ctx, data + header_size, size - header_size, family); 21 | } else if (dest_port == 53) { 22 | handle_dns_query(handle_dns_ctx, data + header_size, size - header_size, src_addr, family); 23 | } 24 | } 25 | 26 | int init_core2nflog_dns(node_cache_t* node_cache, dns_cache_t* dns_cache) { 27 | int nflog_dns_group; 28 | int result; 29 | 30 | handle_dns_ctx = handle_dns_init(&dns_query_hook, &dns_answer_hook); 31 | if (handle_dns_ctx == NULL) { 32 | spin_log(LOG_ERR, "handle_dns_init failure"); 33 | return 1; 34 | } 35 | 36 | nflog_dns_group = spinconfig_iptable_nflog_dns_group(); 37 | result = nflogroutine_register("core2nflog_dns", nflog_dns_callback, (void *) 0, nflog_dns_group); 38 | if (result != 0) { 39 | spin_log(LOG_ERR, "core2nflog_dns initialization failed"); 40 | } 41 | return result; 42 | } 43 | 44 | void cleanup_core2nflog_dns() { 45 | nflogroutine_close("core2nflog_dns"); 46 | if (handle_dns_ctx != NULL) { 47 | handle_dns_cleanup(handle_dns_ctx); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/spind/core2nflog_dns.h: -------------------------------------------------------------------------------- 1 | #ifndef CORE2NFLOG_DNS 2 | #define CORE2NGLOG_DNS 1 3 | #include "dns_cache.h" 4 | #include "node_cache.h" 5 | 6 | // Initialize the core2nflog_dns module 7 | // Arguments: 8 | // node_cache_t* the global spin node cache 9 | // node_cache_t* the global spin dns cache 10 | // 11 | // We may want to add some options here (such as a configurable queue number) 12 | int init_core2nflog_dns(node_cache_t*, dns_cache_t*); 13 | void cleanup_core2nflog_dns(); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/spind/core2pubsub.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_CORE2PUBSUB_H 2 | #define SPIN_CORE2PUBSUB_H 3 | 4 | #include "spindata.h" 5 | #include "util.h" 6 | 7 | void pubsub_publish(char *, int, const void*, int); 8 | void core2pubsub_publish(buffer_t *); 9 | void core2pubsub_publish_chan(char *channel, spin_data sd, int retain); 10 | int init_mosquitto(int start_own_instance, const char* host, int port, const char* websocket_host, int websocket_port); 11 | void finish_mosquitto(int started_own_instance); 12 | 13 | void broadcast_iplist(int iplist, const char* list_name); 14 | 15 | #endif // SPIN_CORE2PUBSUB_H 16 | -------------------------------------------------------------------------------- /src/spind/dnshooks.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "dnshooks.h" 4 | #include "ipl.h" 5 | #include "spind.h" 6 | #include "statistics.h" 7 | 8 | static node_cache_t* node_cache; 9 | static dns_cache_t* dns_cache; 10 | 11 | STAT_MODULE(dns) 12 | 13 | void dns_query_hook(dns_pkt_info_t *dns_pkt, int family, uint8_t *src_addr) 14 | { 15 | time_t now = time(NULL); 16 | STAT_COUNTER(ctr_query, query, STAT_TOTAL); 17 | STAT_COUNTER(send, send-dns, STAT_TOTAL); 18 | 19 | STAT_VALUE(ctr_query, 1); 20 | 21 | node_cache_add_dns_query_info(node_cache, dns_pkt, now); 22 | 23 | // only send a notification if this node is not ignored (we 24 | // do cache it, should we not do that either?) 25 | if (!addr_in_ignore_list(family, src_addr)) { 26 | STAT_VALUE(send, 1); 27 | send_command_dnsquery(dns_pkt); 28 | } 29 | } 30 | 31 | void dns_answer_hook(dns_pkt_info_t *dns_pkt) 32 | { 33 | time_t now = time(NULL); 34 | STAT_COUNTER(ctr_answer, answer, STAT_TOTAL); 35 | 36 | STAT_VALUE(ctr_answer, 1); 37 | 38 | dns_cache_add(dns_cache, dns_pkt, now); 39 | node_cache_add_dns_info(node_cache, dns_pkt, now); 40 | } 41 | 42 | void dns_hooks_init(node_cache_t* node_cache_a, dns_cache_t* dns_cache_a) 43 | { 44 | node_cache = node_cache_a; 45 | dns_cache = dns_cache_a; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/spind/dnshooks.h: -------------------------------------------------------------------------------- 1 | #ifndef DNSHOOKS_H 2 | #define DNSHOOKS_H 1 3 | 4 | #include 5 | 6 | #include "dns_cache.h" 7 | #include "node_cache.h" 8 | #include "pkt_info.h" 9 | 10 | void dns_query_hook(dns_pkt_info_t *, int, uint8_t *); 11 | void dns_answer_hook(dns_pkt_info_t *); 12 | void dns_hooks_init(node_cache_t *, dns_cache_t *); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/spind/dots.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_DOTS_H 2 | #define SPIN_DOTS_H 1 3 | 4 | #include "rpc_common.h" 5 | #include "node_cache.h" 6 | 7 | int process_dots_signal(node_cache_t* node_cache, spin_data dots_message, char** error); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/spind/jsondump.c: -------------------------------------------------------------------------------- 1 | #ifdef notdef 2 | static int json_dump(const char *js, jsmntok_t *t, size_t count, int indent) { 3 | int i, j, k; 4 | if (count == 0) { 5 | return 0; 6 | } 7 | if (t->type == JSMN_PRIMITIVE) { 8 | printf("%.*s", t->end - t->start, js+t->start); 9 | return 1; 10 | } else if (t->type == JSMN_STRING) { 11 | printf("'%.*s'", t->end - t->start, js+t->start); 12 | return 1; 13 | } else if (t->type == JSMN_OBJECT) { 14 | printf("\n"); 15 | j = 0; 16 | for (k = 0; k < indent; k++) printf(" "); 17 | printf("{\n"); 18 | for (i = 0; i < t->size; i++) { 19 | for (k = 0; k < indent; k++) printf(" "); 20 | j += json_dump(js, t+1+j, count-j, indent+1); 21 | printf(": "); 22 | j += json_dump(js, t+1+j, count-j, indent+1); 23 | printf(" \n"); 24 | } 25 | for (k = 0; k < indent; k++) printf(" "); 26 | printf("}"); 27 | return j+1; 28 | } else if (t->type == JSMN_ARRAY) { 29 | j = 0; 30 | printf("\n"); 31 | for (i = 0; i < t->size; i++) { 32 | for (k = 0; k < indent-1; k++) printf(" "); 33 | printf(" - "); 34 | j += json_dump(js, t+1+j, count-j, indent+1); 35 | printf("\n"); 36 | } 37 | return j+1; 38 | } 39 | return 0; 40 | } 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /src/spind/mainloop.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_MAINLOOP_H 2 | #define SPIN_MAINLOOP_H 3 | 4 | typedef void (*workfunc)(void*, int, int); 5 | 6 | int init_mainloop(); 7 | int mainloop_register(char *name, workfunc wf, void *arg, int fd, int toval, int mustsucceed); 8 | void mainloop_run(); 9 | void mainloop_end(); 10 | #endif 11 | -------------------------------------------------------------------------------- /src/spind/nflogroutines.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_NFLOGROUTINES_H 2 | #define SPIN_NFLOGROUTINES_H 3 | 4 | #include 5 | 6 | typedef void (*nflogfunc)(void* arg, int af, int proto, uint8_t* payload, int payloadsize, uint8_t *src_addr, uint8_t *dest_addr, unsigned src_port, unsigned dest_port); 7 | 8 | int nflogroutine_register(char *name, nflogfunc wf, void *arg, int group_number); 9 | void nflogroutine_close(char* name); 10 | void nflog_close_handle(); 11 | #endif 12 | -------------------------------------------------------------------------------- /src/spind/nfqroutines.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_NFQROUTINES_H 2 | #define SPIN_NFQROUTINES_H 3 | 4 | #include 5 | 6 | typedef int (*nfqrfunc)(void* arg, int af, int proto, uint8_t* payload, int payloadsize, uint8_t *src_addr, uint8_t *dest_addr, unsigned src_port, unsigned dest_port); 7 | 8 | void nfqroutine_register(char *name, nfqrfunc wf, void *arg, int queue); 9 | void nfqroutine_close(char* name); 10 | void nfq_close_handle(); 11 | #endif 12 | -------------------------------------------------------------------------------- /src/spind/process_pkt_info.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ipl.h" 4 | #include "mainloop.h" 5 | #include "process_pkt_info.h" 6 | #include "spind.h" 7 | #include "spin_log.h" 8 | #include "statistics.h" 9 | 10 | STAT_MODULE(processpktinfo) 11 | 12 | void 13 | process_pkt_info(node_cache_t* node_cache, flow_list_t* flow_list, trafficfunc traffic_hook, int local_mode, pkt_info_t* pkt_info) { 14 | // TODO: remove time() calls, use the single one at caller 15 | uint32_t now = time(NULL); 16 | STAT_COUNTER(ctrsf, sendflow, STAT_TOTAL); 17 | STAT_COUNTER(ctrlocal, cb-ignore-local, STAT_TOTAL); 18 | STAT_COUNTER(ctrignore, ignore-ip, STAT_TOTAL); 19 | ip_t ip; 20 | node_t* src_node; 21 | node_t* dest_node; 22 | 23 | if (pkt_info->packet_count > 0 || pkt_info->payload_size > 0) { 24 | node_cache_add_pkt_info(node_cache, pkt_info, now); 25 | 26 | copy_ip_data(&ip, pkt_info->family, 0, pkt_info->src_addr); 27 | src_node = node_cache_find_by_ip(node_cache, &ip); 28 | copy_ip_data(&ip, pkt_info->family, 0, pkt_info->dest_addr); 29 | dest_node = node_cache_find_by_ip(node_cache, &ip); 30 | 31 | if (src_node != NULL && dest_node != NULL) { 32 | // Inform flow accounting layer 33 | (*traffic_hook)(node_cache, src_node, dest_node, pkt_info->packet_count, pkt_info->payload_size, now, pkt_info->dest_port, pkt_info->icmp_type); 34 | 35 | // small experiment, try to ignore messages from and to 36 | // this device, unless local_mode is set 37 | if (!local_mode && src_node->mac==NULL && dest_node->mac==NULL) { 38 | STAT_VALUE(ctrlocal, 1); 39 | return; 40 | } 41 | } 42 | 43 | // check for configured ignores as well 44 | // do we need to cache it or should we do this check earlier? 45 | // do we need to check both source and reply? 46 | if (addr_in_ignore_list(pkt_info->family, pkt_info->src_addr) || 47 | addr_in_ignore_list(pkt_info->family, pkt_info->dest_addr) 48 | ) { 49 | STAT_VALUE(ctrignore, 1); 50 | return; 51 | } 52 | 53 | STAT_VALUE(ctrsf, 1); 54 | flow_list_add_pktinfo(flow_list, pkt_info); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/spind/process_pkt_info.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_PKT_INFO_H 2 | #define PROCESS_PKT_INFO_H 1 3 | 4 | #include "node_cache.h" 5 | #include "spinhook.h" 6 | 7 | /* 8 | * process_pkt_info() is to be called from the layer that assembles pkt_info_t 9 | * structures from low-level information (gathered e.g. from Linux conntrack 10 | * or a PCAP file). Calling process_pkt_info() will insert the information 11 | * contained in the pkt_info_t into the SPIN daemon. 12 | */ 13 | void process_pkt_info(node_cache_t* node_cache, flow_list_t* flow_list, trafficfunc traffic_hook, int local_mode, pkt_info_t* pkt_info); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/spind/rpc_calls.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIND_RPC_CALLS_H 2 | #define SPIND_RPC_CALLS_H 1 3 | 4 | #include "node_cache.h" 5 | #include "rpc_common.h" 6 | 7 | /* 8 | * Manually add an IP address to an existing node 9 | * RPC name: node_ad_ip 10 | * Arguments: 11 | * node (int): the node ID 12 | * ipaddr (string): IP address (can be IPv4 or IPv6) 13 | */ 14 | int addipnodefunc(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 15 | 16 | int blockflowfunc(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 17 | 18 | int devblockflowfunc(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 19 | 20 | int devflowfunc(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 21 | 22 | 23 | /* 24 | * Retrieves a list of all 'local' devices (i.e. those with a known MAC 25 | * address 26 | * RPC Name: 'devicelist' 27 | * Arguments: none 28 | */ 29 | int devlistfunc(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 30 | 31 | int getblockflowfunc(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 32 | 33 | /* 34 | * Sets a name for a specific device (node) 35 | * RPC Name: 'set_device_name' 36 | * Arguments: 37 | * node (int): the node ID to set the name for 38 | * name (string): the name to set 39 | */ 40 | int set_device_name_func(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 41 | 42 | /* 43 | * Read or modify one of the ignore, block en allow lists 44 | */ 45 | /* wait with this one, it uses many functions in spind itself 46 | int spindlistfunc(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 47 | rpc_arg_desc_t list_member_args[] = { 48 | { "list", RPCAT_INT }, 49 | { "addrem", RPCAT_INT }, 50 | { "node", RPCAT_INT }, 51 | }; 52 | */ 53 | //int spindlistfunc(void *cb, rpc_arg_val_t *args, rpc_arg_val_t *result); 54 | 55 | void init_rpcs(node_cache_t *node_cache); 56 | void cleanup_rpcs(); 57 | 58 | #endif //SPIND_RPC_CALLS_H 59 | -------------------------------------------------------------------------------- /src/spind/rpc_common.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_RPC_COMMON_H 2 | #define SPIN_RPC_COMMON_H 1 3 | 4 | #include "spindata.h" 5 | 6 | typedef enum { 7 | RPCAT_INT, 8 | RPCAT_STRING, 9 | RPCAT_COMPLEX, 10 | RPCAT_NONE 11 | } rpc_argtype; 12 | 13 | typedef struct { 14 | char * rpca_name; 15 | rpc_argtype rpca_type; 16 | } rpc_arg_desc_t; 17 | 18 | typedef union { 19 | int rpca_ivalue; 20 | char * rpca_svalue; 21 | spin_data rpca_cvalue; 22 | } rpc_arg_val_t; 23 | 24 | typedef struct { 25 | rpc_arg_desc_t rpc_desc; 26 | rpc_arg_val_t rpc_val; 27 | } rpc_arg_t; 28 | 29 | typedef int (*rpc_func_p)(void *cb,rpc_arg_val_t *args, rpc_arg_val_t *result); 30 | 31 | void rpc_register(char *name, rpc_func_p func, void *cb, int nargs, rpc_arg_desc_t *args, rpc_argtype result_type); 32 | /* 33 | * cleans up all memory for registered functions 34 | */ 35 | void rpc_cleanup(); 36 | int rpc_call(char *name, int nargs, rpc_arg_t *args, rpc_arg_t *result); 37 | //spin_data rpc_list_registered_procedures(); 38 | void register_internal_functions(); 39 | 40 | #endif // SPIN_RPC_COMMON_H 41 | -------------------------------------------------------------------------------- /src/spind/rpc_json.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_RPC_JSON_H 2 | #define SPIN_RPC_JSON_H 1 3 | 4 | #include "rpc_common.h" 5 | #include "spindata.h" 6 | 7 | #define JSON_RPC_SOCKET_PATH "/var/run/spin_rpc.sock" 8 | 9 | char *call_ubus2json(const char *, char*); 10 | char *call_ubus2jsonnew(char*); 11 | 12 | char *call_string_jsonrpc(char *args); 13 | 14 | typedef spin_data (*rpcfunc)(spin_data); 15 | 16 | int init_json_rpc(); 17 | 18 | #endif // SPIN_RPC_JSON_HS 19 | -------------------------------------------------------------------------------- /src/spind/rpc_ubus.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011-2014 Felix Fietkau 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License version 2.1 6 | * as published by the Free Software Foundation 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | */ 13 | 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "libubus.h" 24 | #include "mainloop.h" 25 | #include "rpc_json.h" 26 | #include "spind.h" 27 | #include "spin_log.h" 28 | 29 | static struct ubus_context *ctx; 30 | static struct ubus_subscriber spin_event; 31 | static struct blob_buf b; 32 | 33 | static const struct blobmsg_policy rpc_policy[] = { 34 | }; 35 | 36 | static int spin_rpc(struct ubus_context *ctx, struct ubus_object *obj, 37 | struct ubus_request_data *req, const char *method, 38 | struct blob_attr *msg) 39 | { 40 | char *args, *result; 41 | 42 | args = blobmsg_format_json(msg, true); 43 | result = call_ubus2jsonnew(args); 44 | 45 | blob_buf_init(&b, 0); 46 | blobmsg_add_json_from_string(&b, result); 47 | free(result); // This was malloced 48 | ubus_send_reply(ctx, req, b.head); 49 | return 0; 50 | } 51 | 52 | static const struct ubus_method spin_methods[] = { 53 | UBUS_METHOD("rpc", spin_rpc, rpc_policy), 54 | }; 55 | 56 | static struct ubus_object_type spin_object_type = 57 | UBUS_OBJECT_TYPE("spin", spin_methods); 58 | 59 | static struct ubus_object spin_object = { 60 | .name = "spin", 61 | .type = &spin_object_type, 62 | .methods = spin_methods, 63 | .n_methods = ARRAY_SIZE(spin_methods), 64 | }; 65 | 66 | static int 67 | fd_set_blocking(int fd, int blocking) { 68 | /* Save the current flags */ 69 | int flags = fcntl(fd, F_GETFL, 0); 70 | if (flags == -1) { 71 | return 0; 72 | } 73 | 74 | if (blocking) { 75 | flags &= ~O_NONBLOCK; 76 | } else { 77 | flags |= O_NONBLOCK; 78 | } 79 | return fcntl(fd, F_SETFL, flags) != -1; 80 | } 81 | 82 | void wf_ubus(void *arg, int data, int timeout) { 83 | 84 | spin_log(LOG_DEBUG, "wf_ubus called\n"); 85 | 86 | if (data) { 87 | ubus_handle_event(ctx); 88 | // ctx->sock.cb(&ctx->sock, ULOOP_READ); 89 | } 90 | } 91 | 92 | int ubus_main() 93 | { 94 | const char *ubus_socket = NULL; 95 | int ret; 96 | 97 | ctx = ubus_connect(ubus_socket); 98 | if (!ctx) { 99 | fprintf(stderr, "Failed to connect to ubus\n"); 100 | return -1; 101 | } else { 102 | spin_log(LOG_DEBUG, "Connected to ubus\n"); 103 | } 104 | 105 | fd_set_blocking(ctx->sock.fd, 0); 106 | mainloop_register("ubus", wf_ubus, NULL, ctx->sock.fd, 0, 1); 107 | 108 | ret = ubus_add_object(ctx, &spin_object); 109 | if (ret) { 110 | fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret)); 111 | } 112 | 113 | ret = ubus_register_subscriber(ctx, &spin_event); 114 | if (ret) { 115 | fprintf(stderr, "Failed to add watch handler: %s\n", ubus_strerror(ret)); 116 | } 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /src/spind/spind.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIND_H 2 | #define SPIND_H 1 3 | 4 | // declaration of the functions commonly called by the registered 5 | // modules. 6 | // 7 | // Note: if functions are needed here, it might mean that they should 8 | // really be moved to the library or at least a separate file 9 | #include "node_cache.h" 10 | #include "pkt_info.h" 11 | 12 | void maybe_sendflow(flow_list_t *flow_list, time_t now); 13 | void report_block(int af, int proto, uint8_t *src_addr, uint8_t *dest_addr, unsigned src_port, unsigned dest_port, int payloadsize); 14 | 15 | void publish_nodes(); 16 | 17 | void send_command_dnsquery(dns_pkt_info_t* pkt_info); 18 | 19 | void send_command_nodegone(node_t *node); 20 | 21 | // RPC section 22 | int spinrpc_blockflow(int node1, int node2, int block); 23 | char *spinrpc_get_blockflow(); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/spind/statistics.c: -------------------------------------------------------------------------------- 1 | 2 | #include "mainloop.h" 3 | #include "spindata.h" 4 | #include "statistics.h" 5 | 6 | #if DO_SPIN_STATS 7 | 8 | void core2pubsub_publish_chan(char *, spin_data, int); 9 | 10 | static void 11 | statpub(stat_p sp) { 12 | char tpbuf[100]; 13 | cJSON *statobj, *membobj; 14 | 15 | statobj = cJSON_CreateObject(); 16 | if (statobj == 0) { 17 | return; 18 | } 19 | 20 | membobj = cJSON_CreateStringReference(sp->stat_module); 21 | cJSON_AddItemToObject(statobj, "module", membobj); 22 | 23 | membobj = cJSON_CreateStringReference(sp->stat_name); 24 | cJSON_AddItemToObject(statobj, "name", membobj); 25 | 26 | cJSON_AddNumberToObject(statobj, "type", sp->stat_type); 27 | cJSON_AddNumberToObject(statobj, "value", sp->stat_value); 28 | cJSON_AddNumberToObject(statobj, "count", sp->stat_count); 29 | 30 | sprintf(tpbuf, "SPIN/stat/%s/%s", sp->stat_module, sp->stat_name); 31 | 32 | core2pubsub_publish_chan(tpbuf, statobj, 1); 33 | 34 | cJSON_Delete(statobj); 35 | } 36 | 37 | static void 38 | wf_stat(void * arg, int data, int timeout) { 39 | stat_p sp; 40 | 41 | if (timeout) { 42 | // What else 43 | for (sp = spin_stat_chain; sp->stat_module; sp = sp->stat_next) { 44 | if (sp->stat_lastpubcount != sp->stat_count) { 45 | statpub(sp); 46 | sp->stat_lastpubcount = sp->stat_count; 47 | } 48 | } 49 | } 50 | } 51 | 52 | void 53 | spin_stat_start() { 54 | 55 | mainloop_register("Statistics", wf_stat, (void *) 0, 0, 30000, 1); 56 | } 57 | 58 | void 59 | spin_stat_finish() { 60 | 61 | } 62 | 63 | 64 | #endif // DO_SPIN_STATS 65 | -------------------------------------------------------------------------------- /src/spinweb/Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CPPFLAGS = -I$(top_srcdir)/include 2 | AM_CFLAGS = ${regular_CFLAGS} -g -Wall -Werror 3 | 4 | bin_PROGRAMS = spinweb 5 | spinweb_SOURCES = spinweb.c \ 6 | traffic_capture.c \ 7 | rpc_client.c \ 8 | files.c \ 9 | ../spind/cJSON.c 10 | 11 | 12 | spinweb_CFLAGS = -DDATADIR='"$(datadir)"' -DSRCDIR='"$(srcdir)"' 13 | spinweb_LDADD = -lpthread $(top_builddir)/lib/libspin.a 14 | 15 | if USE_UBUS 16 | spinweb_SOURCES += rpc_ubus_client.c 17 | spinweb_LDADD += -lubox -lubus -lblobmsg_json 18 | endif 19 | 20 | 21 | MYDIR = ${srcdir} 22 | EXTRA_DIST = $(MYDIR)/templates $(MYDIR)/static 23 | 24 | install-data-local: 25 | test -z $(DESTDIR)$(pkgdatadir)/templates || $(MKDIR_P) $(DESTDIR)$(pkgdatadir)/spinweb/templates 26 | cp -r "$(MYDIR)/templates" "$(DESTDIR)$(pkgdatadir)/spinweb/" 27 | test -z $(DESTDIR)$(pkgdatadir)/templates || $(MKDIR_P) $(DESTDIR)$(pkgdatadir)/spinweb/static 28 | cp -r "$(MYDIR)/static" "$(DESTDIR)$(pkgdatadir)/spinweb/" 29 | -------------------------------------------------------------------------------- /src/spinweb/files.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SPINWEB_PASSWORD_H 3 | #define SPINWEB_PASSWORD_H 1 4 | 5 | /* 6 | * Helper functions for file reading 7 | */ 8 | 9 | /* 10 | * Data structure to pass from and to the file scanner 11 | * functions, which check whether a file exists (for instance, 12 | * to find index.html for a query that does not have it specified) 13 | * Any informative data that is found (such as whether it's a gzipped 14 | * file) can be placed into this structure) 15 | */ 16 | typedef struct { 17 | FILE* fp; 18 | int gzipped; 19 | } web_file_t; 20 | 21 | /* 22 | * Returns opened FILE* pointer if the path exists and is a regular 23 | * file. 24 | * NULL otherwise, or if fopen() fails. 25 | */ 26 | FILE* try_file(const char* path); 27 | 28 | /* 29 | * Tries whether the file path exists, and if not, whether the path 30 | * with '.html', or (if url ends with /) 'index.html' exists, in that order 31 | * Returns open file pointer if so, NULL if not 32 | */ 33 | FILE* try_files(const char* base_path, const char* path); 34 | void try_files2(const char* base_path, const char* path, web_file_t* web_file); 35 | 36 | 37 | /* 38 | * Check a username and password against the given file 39 | * File should be in unix password file format 40 | * Returns 1 if password matches 41 | */ 42 | int check_password(const char* password_file_name, const char* username, const char* password); 43 | 44 | /* 45 | * Returns the size of a file 46 | */ 47 | long get_file_size(const char *filename); 48 | 49 | /* 50 | * Returns the entire contents of a file as a string 51 | * Caller must free the data 52 | */ 53 | char *read_file(const char *filename); 54 | 55 | 56 | #endif // SPINWEB_PASSWORD_H -------------------------------------------------------------------------------- /src/spinweb/rpc_client.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | // Sends the given string to the rpc domain socket 5 | // returns the response 6 | // caller must free response data 7 | char* send_rpc_message_raw(const char* request); 8 | 9 | spin_data rpcc_list_devices(); 10 | spin_data rpcc_get_device_by_mac(const char* device_mac); 11 | 12 | 13 | // tmp? 14 | char* rpcc_get_device_name(spin_data device); 15 | char* rpcc_get_device_ips_as_string(spin_data device); 16 | -------------------------------------------------------------------------------- /src/spinweb/rpc_ubus_client.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #define TIMEOUT 2000 8 | 9 | static void 10 | receive_call_result_data(struct ubus_request *req, int type, struct blob_attr *msg) { 11 | if (!msg) { 12 | return; 13 | } 14 | char* res = blobmsg_format_json_indent(msg, true, 0); 15 | char** result = (char**) req->priv; 16 | *result = res; 17 | } 18 | 19 | 20 | static int 21 | ubus_cli_call(struct ubus_context *ctx, char** result, const char* path, const char* method, const char* arguments) { 22 | uint32_t id; 23 | int ret; 24 | struct blob_buf b; 25 | 26 | memset(&b, 0, sizeof(b)); 27 | blob_buf_init(&b, 0); 28 | if (!blobmsg_add_json_from_string(&b, arguments)) { 29 | fprintf(stderr, "Failed to parse message data\n"); 30 | return -1; 31 | } 32 | 33 | ret = ubus_lookup_id(ctx, path, &id); 34 | if (ret) { 35 | return ret; 36 | } 37 | 38 | return ubus_invoke(ctx, id, method, b.head, receive_call_result_data, result, TIMEOUT); 39 | } 40 | 41 | char* 42 | send_ubus_message_raw(const char* request) { 43 | const char* path = "spin"; 44 | const char* method = "rpc"; 45 | struct ubus_context* ctx = NULL; 46 | char* result = NULL; 47 | cJSON* json_request; 48 | char* params_str; 49 | 50 | // The received request is a JSONRPC request 51 | // {"jsonrpc": "2.0", "id": 34154, "method": "list_devices", "params": {"a": "b"}} 52 | // 53 | // It's not very efficient, but we'll convert to cJSON, remove jsonrpc elements 54 | // convert back to string, then send that 55 | 56 | json_request = cJSON_Parse(request); 57 | if (json_request == NULL) { 58 | fprintf(stderr, "Error parsing JSON request: %s\n", request); 59 | goto done; 60 | } 61 | cJSON_DeleteItemFromObject(json_request, "id"); 62 | cJSON_DeleteItemFromObject(json_request, "jsonrpc"); 63 | params_str = cJSON_Print(json_request); 64 | 65 | ctx = ubus_connect(NULL); 66 | if (ctx == NULL) { 67 | fprintf(stderr, "Error connecting to UBUS\n"); 68 | goto done; 69 | } 70 | 71 | int call_result = ubus_cli_call(ctx, &result, path, method, params_str); 72 | if (call_result != 0) { 73 | fprintf(stderr, "Error sending request to ubus: %s\n", ubus_strerror(call_result)); 74 | } 75 | int i = 0; 76 | while (result == NULL && i < 10) { 77 | usleep(200000); 78 | i++; 79 | } 80 | if (result == NULL ) { 81 | fprintf(stderr, "ubus call timeout\n"); 82 | } 83 | 84 | done: 85 | if (ctx != NULL) { 86 | ubus_free(ctx); 87 | } 88 | if (params_str != NULL) { 89 | free(params_str); 90 | } 91 | if (json_request != NULL) { 92 | cJSON_Delete(json_request); 93 | } 94 | return result; 95 | } 96 | -------------------------------------------------------------------------------- /src/spinweb/rpc_ubus_client.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Synchronous ubus message request 3 | * 4 | * request is a string containing a JSON RPC request 5 | * 6 | * Returns the response as a string containing JSON data 7 | * Return data must be freed by called 8 | */ 9 | char* send_ubus_message_raw(const char* request); 10 | -------------------------------------------------------------------------------- /src/spinweb/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index 5 | 6 | 7 | 8 |

9 | Redirecting to the SPIN network traffic viewer 10 |

11 | 12 | 13 | -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/css/debug.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPIN CSS - v0.0.3 - 2017-04-24 3 | * https://valibox.sidn.nl/ 4 | */ 5 | 6 | * { 7 | box-sizing: border-box; /* needed? */ 8 | } 9 | html, 10 | body { 11 | height: calc(100% - 2px); 12 | font-family: Arial, Helvetica, sans-serif; 13 | padding: 0px 0px 0px 0px; 14 | margin-top: 0px; 15 | margin-bottom: 0px; 16 | } 17 | .ui-button 18 | { 19 | padding: 3px 11px 3px 11px !important; 20 | } 21 | .ui-dialog { 22 | box-shadow: 1px 1px 5px #333; 23 | } 24 | body { 25 | } 26 | .initiallyHidden { 27 | display: none; 28 | } 29 | .body-item { 30 | margin: 4px; 31 | } 32 | #buttonbarleft { 33 | display: inline-block; 34 | } 35 | #buttonbarright { 36 | display: inline-block; 37 | vertical-align: top; 38 | position: absolute; 39 | right: 0px; 40 | } 41 | @media screen and (max-width: 875px) { 42 | #buttonbarright { 43 | position: relative;} 44 | } 45 | #visualization { 46 | /*height: 138px;*/ 47 | background-color: #faffff; 48 | box-shadow: 1px 1px 5px #333; 49 | } 50 | #mynetwork { 51 | background-color: white; 52 | background-color: #fafaff; 53 | height: calc(100% - 250px); 54 | box-shadow: 1px 1px 5px #333; 55 | } 56 | #footer { 57 | margin-top: 0px; 58 | margin-bottom: 0px; 59 | font-size: 75%; 60 | text-align: right; 61 | } 62 | #nodeinfo-data { 63 | padding: 10px; 64 | } 65 | #title-logo { 66 | vertical-align: middle; 67 | } 68 | @media screen and (max-width: 550px), screen and (max-height: 550px) { 69 | #title-logo {display:none;} 70 | #visualization {display:none;} 71 | #mynetwork {height: calc(100% - 112px);} 72 | } 73 | #title { 74 | font-weight: bold; 75 | font-size: 1.5em; 76 | } 77 | @media screen and (max-width: 375px) { 78 | #title { font-size: 0.75em;} 79 | } 80 | #statustext { 81 | background-color: #ffcccc; 82 | } 83 | #autozoom-button { 84 | } 85 | #filter-list-button { 86 | } 87 | #filter-list .ui-selecting { 88 | background: #FECA40; 89 | } 90 | #filter-list .ui-selected { 91 | background: #F39814; 92 | color: white; 93 | } 94 | #filter-list { 95 | list-style-type: none; 96 | margin: 0; 97 | padding: 0; 98 | } 99 | #filter-list li { 100 | margin: 3px; 101 | padding: 0.4em; 102 | } 103 | 104 | pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; } 105 | .string { color: green; } 106 | .number { color: darkorange; } 107 | .boolean { color: blue; } 108 | .null { color: magenta; } 109 | .key { color: red; } 110 | -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/css/spin.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPIN CSS - v0.0.3 - 2017-04-24 3 | * https://valibox.sidn.nl/ 4 | */ 5 | 6 | * { 7 | box-sizing: border-box; /* needed? */ 8 | } 9 | html, 10 | body { 11 | height: calc(100% - 2px); 12 | font-family: Arial, Helvetica, sans-serif; 13 | padding: 0px 0px 0px 0px; 14 | margin-top: 0px; 15 | margin-bottom: 0px; 16 | } 17 | .ui-button 18 | { 19 | padding: 3px 11px 3px 11px !important; 20 | } 21 | .ui-dialog { 22 | box-shadow: 1px 1px 5px #333; 23 | } 24 | body { 25 | } 26 | .initiallyHidden { 27 | display: none; 28 | } 29 | .body-item { 30 | margin: 4px; 31 | } 32 | #buttonbarleft { 33 | display: inline-block; 34 | } 35 | #buttonbarright { 36 | display: inline-block; 37 | vertical-align: top; 38 | position: absolute; 39 | right: 0px; 40 | } 41 | @media screen and (max-width: 875px) { 42 | #buttonbarright { 43 | position: relative;} 44 | } 45 | #visualization { 46 | /*height: 138px;*/ 47 | background-color: #faffff; 48 | box-shadow: 1px 1px 5px #333; 49 | } 50 | #mynetwork { 51 | background-color: white; 52 | background-color: #fafaff; 53 | height: calc(100% - 250px); 54 | box-shadow: 1px 1px 5px #333; 55 | } 56 | #footer { 57 | margin-top: 0px; 58 | margin-bottom: 0px; 59 | font-size: 75%; 60 | text-align: right; 61 | } 62 | #nodeinfo-data { 63 | padding: 10px; 64 | } 65 | #title-logo { 66 | vertical-align: middle; 67 | } 68 | @media screen and (max-width: 550px), screen and (max-height: 550px) { 69 | #title-logo {display:none;} 70 | #visualization {display:none;} 71 | #mynetwork {height: calc(100% - 112px);} 72 | } 73 | #title { 74 | font-weight: bold; 75 | font-size: 1.5em; 76 | } 77 | @media screen and (max-width: 375px) { 78 | #title { font-size: 0.75em;} 79 | } 80 | #statustext { 81 | background-color: #ffcccc; 82 | } 83 | #autozoom-button { 84 | } 85 | #filter-list-button { 86 | } 87 | #filter-list .ui-selecting { 88 | background: #FECA40; 89 | } 90 | #filter-list .ui-selected { 91 | background: #F39814; 92 | color: white; 93 | } 94 | #filter-list { 95 | list-style-type: none; 96 | margin: 0; 97 | padding: 0; 98 | } 99 | #filter-list li { 100 | margin: 3px; 101 | padding: 0.4em; 102 | } 103 | 104 | #block-list-button { 105 | } 106 | #block-list .ui-selecting { 107 | background: #FECA40; 108 | } 109 | #block-list .ui-selected { 110 | background: #F39814; 111 | color: white; 112 | } 113 | #block-list { 114 | list-style-type: none; 115 | margin: 0; 116 | padding: 0; 117 | } 118 | #block-list li { 119 | margin: 3px; 120 | padding: 0.4em; 121 | } 122 | 123 | #allowed-list-button { 124 | } 125 | #allowed-list .ui-selecting { 126 | background: #FECA40; 127 | } 128 | #allowed-list .ui-selected { 129 | background: #F39814; 130 | color: white; 131 | } 132 | #allowed-list { 133 | list-style-type: none; 134 | margin: 0; 135 | padding: 0; 136 | } 137 | #allowed-list li { 138 | margin: 3px; 139 | padding: 0.4em; 140 | } 141 | -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/img/favicon.ico -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/img/sidnlabs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/img/sidnlabs_logo.png -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index 5 | 6 | 7 | 8 |

9 | Redirecting to the SPIN network traffic viewer 10 |

11 | 12 | 13 | -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/bootstrap-4.3.1.bundle.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/bootstrap-4.3.1.bundle.min.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/bootstrap-select-1.13.14.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/bootstrap-select-1.13.14.min.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-3.1.1.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/jquery-3.1.1.min.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-3.6.0.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/jquery-3.6.0.min.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery-ui 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | Copyright and related rights for sample code are waived via CC0. Sample 34 | code is defined as all source code contained within the demos directory. 35 | 36 | CC0: http://creativecommons.org/publicdomain/zero/1.0/ 37 | 38 | ==== 39 | 40 | All files located in the node_modules and external directories are 41 | externally maintained libraries used by this software which have their 42 | own licenses; we recommend you read them, as their terms may differ from 43 | the terms above. 44 | -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/jquery-ui-1.13.0.custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-ui", 3 | "title": "jQuery UI", 4 | "description": "A curated set of user interface interactions, effects, widgets, and themes built on top of the jQuery JavaScript Library.", 5 | "version": "1.13.0", 6 | "homepage": "http://jqueryui.com", 7 | "author": { 8 | "name": "jQuery Foundation and other contributors", 9 | "url": "https://github.com/jquery/jquery-ui/blob/1.13.0/AUTHORS.txt" 10 | }, 11 | "main": "ui/widget.js", 12 | "maintainers": [ 13 | { 14 | "name": "Jörn Zaefferer", 15 | "email": "joern.zaefferer@gmail.com", 16 | "url": "http://bassistance.de" 17 | }, 18 | { 19 | "name": "Mike Sherov", 20 | "email": "mike.sherov@gmail.com", 21 | "url": "http://mike.sherov.com" 22 | }, 23 | { 24 | "name": "TJ VanToll", 25 | "email": "tj.vantoll@gmail.com", 26 | "url": "http://tjvantoll.com" 27 | }, 28 | { 29 | "name": "Felix Nagel", 30 | "email": "info@felixnagel.com", 31 | "url": "http://www.felixnagel.com" 32 | }, 33 | { 34 | "name": "Alex Schmitz", 35 | "email": "arschmitz@gmail.com", 36 | "url": "https://github.com/arschmitz" 37 | } 38 | ], 39 | "repository": { 40 | "type": "git", 41 | "url": "git://github.com/jquery/jquery-ui.git" 42 | }, 43 | "bugs": "https://bugs.jqueryui.com/", 44 | "license": "MIT", 45 | "scripts": { 46 | "test": "grunt" 47 | }, 48 | "dependencies": { 49 | "jquery": ">=1.8.0 <4.0.0" 50 | }, 51 | "devDependencies": { 52 | "commitplease": "3.2.0", 53 | "eslint-config-jquery": "3.0.0", 54 | "glob": "7.1.7", 55 | "grunt": "1.4.1", 56 | "grunt-bowercopy": "1.2.5", 57 | "grunt-cli": "1.4.3", 58 | "grunt-compare-size": "0.4.2", 59 | "grunt-contrib-concat": "1.0.1", 60 | "grunt-contrib-csslint": "2.0.0", 61 | "grunt-contrib-qunit": "5.0.1", 62 | "grunt-contrib-requirejs": "1.0.0", 63 | "grunt-contrib-uglify": "5.0.1", 64 | "grunt-eslint": "23.0.0", 65 | "grunt-git-authors": "3.2.0", 66 | "grunt-html": "14.5.0", 67 | "load-grunt-tasks": "5.1.0", 68 | "rimraf": "3.0.2", 69 | "testswarm": "1.1.2" 70 | }, 71 | "keywords": [] 72 | } 73 | -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/mqttws31.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/mqttws31.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/paho-mqtt.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/paho-mqtt.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/ponyfill.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/ponyfill.min.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/vis/moment.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/vis/moment.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/vis/vis-data.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/vis/vis-data.min.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/vis/vis-network.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/vis/vis-network.min.js.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/vis/vis-timeline-graph2d.min.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/vis/vis-timeline-graph2d.min.css.gz -------------------------------------------------------------------------------- /src/spinweb/static/spin_graph/js/vis/vis-timeline-graph2d.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIDN/spin/a5185b76c5f39220657ffaedbb1ce7724bce820f/src/spinweb/static/spin_graph/js/vis/vis-timeline-graph2d.min.js.gz -------------------------------------------------------------------------------- /src/spinweb/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SIDN Labs SPIN prototype 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |

SPIN prototype

15 |
16 | {{= main_html }} 17 |
18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/spinweb/templates/index.html: -------------------------------------------------------------------------------- 1 | {{ --$def with () }} 2 | 3 | 16 | 17 |
18 |

19 | graph test 20 |
21 | debug mode 22 |
23 | Dump test device 24 |

25 |
26 | 27 | 35 | -------------------------------------------------------------------------------- /src/spinweb/templates/tcpdump.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Download device traffic _TEMPLATE_ARG0_ 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

SPIN traffic capture

18 |

Device MAC address: _TEMPLATE_ARG0_

19 |
20 | 21 |
22 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/spinweb/templates/tcpdump_status.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 |
11 |

Closing or reloading this window will stop the capture!

12 |

Bytes sent: _TEMPLATE_ARG2_

13 |

Stop download

14 |
15 |
16 |

Click start to start the capture.

17 |

Start capture

18 |
19 | 20 | -------------------------------------------------------------------------------- /src/spinweb/traffic_capture.h: -------------------------------------------------------------------------------- 1 | int tc_answer_direct_capture_request(struct MHD_Connection* connection, const char* url); 2 | int tc_answer_mqtt_capture_request(struct MHD_Connection* connection, const char* url); 3 | void tc_stop_all_captures(); 4 | int tc_captures_running(); 5 | int tc_capture_running_for(const char* device_mac); 6 | int tc_get_bytes_sent_for(const char* device_mac); 7 | int tc_start_mqtt_capture_for(const char* device_mac); 8 | void tc_stop_capture_for(const char* device_mac); 9 | 10 | -------------------------------------------------------------------------------- /src/tools/Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | AM_CPPFLAGS = -I$(top_srcdir)/include 3 | AM_CFLAGS = ${regular_CFLAGS} -g -Wall -Werror 4 | 5 | SUBDIRS = @SPINPCAPREADER@ 6 | 7 | -------------------------------------------------------------------------------- /src/tools/peak-detection/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * SPIN Network Management Center (NMC) 3 | * Made by SIDN Labs (sidnlabs@sidn.nl) 4 | */ 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | "time" 14 | ) 15 | 16 | const CHANNEL_BUFFER = 100 17 | 18 | func main() { 19 | // Parse commandline arguments 20 | freshPtr := flag.Bool("fresh", false, "start fresh, i.e. do not restore previous state") 21 | restoreFilePtr := flag.String("db", ".spin-nmc-history.db", "restore database file") 22 | mqttHostPtr := flag.String("mqtthost", "valibox.", "Host of mqtt server") 23 | mqttPortPtr := flag.String("mqttport", "1883", "Port of mqtt server") 24 | flag.Parse() 25 | 26 | var hs *HistoryDB = nil 27 | var as *map[int]*FlowSummary = nil 28 | if !*freshPtr { 29 | /* Continue from old state, if present */ 30 | persist, err := load(*restoreFilePtr) 31 | if err != nil { 32 | if !os.IsNotExist(err) { 33 | fmt.Println("Error on loading old state:", err) 34 | } 35 | /* Otherwise, file does not exist, not an error, just nothing to load from */ 36 | } else { 37 | hs = &persist.HistoryState 38 | as = &persist.TrafficHistoryState 39 | } 40 | } 41 | InitHistory(hs) // initialize history service 42 | InitAnomaly(as) // Anomaly detection 43 | 44 | // Connect to MQTT Broker of valibox 45 | ConnectToBroker(*mqttHostPtr, *mqttPortPtr) 46 | HandleKillSignal() 47 | 48 | for { 49 | time.Sleep(5 * time.Minute) 50 | if save(*restoreFilePtr) { 51 | fmt.Println("Saved state to disk") 52 | } else { 53 | fmt.Println("Error, unable to save state to disk") 54 | } 55 | // History.RLock() 56 | // fmt.Printf("History: %+v\n", History) 57 | // History.RUnlock() 58 | } 59 | } 60 | 61 | // Handle kill signals for all modules 62 | func HandleKillSignal() { 63 | // Set a signal handler 64 | csig := make(chan os.Signal, 2) 65 | signal.Notify(csig, os.Interrupt, syscall.SIGTERM) 66 | go func() { 67 | <-csig 68 | fmt.Println("\nShutting down...") 69 | KillBroker() 70 | KillHistory() 71 | os.Exit(1) 72 | }() 73 | } 74 | -------------------------------------------------------------------------------- /src/tools/peak-detection/mips.txt: -------------------------------------------------------------------------------- 1 | Compiling for the valibox on MIPS: 2 | 3 | GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags="-s -w" 4 | 5 | GOOS=linux => linux als OS 6 | GOARCH=mips => mips als instrutieset. 7 | GOMIPS=softfloat => chipsets hebben geen FPU, dus compileer met software-fpu 8 | -ldflags="-s -w" => kleinere binaries, strip debugging information 9 | 10 | -------------------------------------------------------------------------------- /src/tools/peak-detection/persistence.go: -------------------------------------------------------------------------------- 1 | /* 2 | * All procedures for saving and loading state 3 | * Problems to deal with in persistence: 4 | * - how to deal with node identifiers that have changed after a reboot? 5 | * - where to store the databasefile on OpenWRT so that is persists a reboot? 6 | */ 7 | 8 | package main 9 | 10 | import ( 11 | "bufio" 12 | "encoding/json" 13 | "errors" 14 | "fmt" 15 | "io/ioutil" 16 | "os" 17 | ) 18 | 19 | type StorageState struct { 20 | HistoryState HistoryDB `json:"history,omitempty"` 21 | TrafficHistoryState map[int]*FlowSummary `json:"traffichistory,omitempty"` 22 | } 23 | 24 | func save(fp string) bool { 25 | History.RLock() 26 | TrafficHistory.RLock() 27 | defer History.RUnlock() 28 | defer TrafficHistory.RUnlock() 29 | ss := StorageState{History.m, TrafficHistory.h} 30 | return saveToFile(ss, fp) 31 | } 32 | 33 | func load(fp string) (StorageState, error) { 34 | f, err := os.Open(fp) 35 | if err != nil { 36 | return StorageState{}, err 37 | } 38 | defer f.Close() 39 | 40 | bbuf, err := ioutil.ReadAll(f) 41 | if err != nil { 42 | return StorageState{}, errors.New("Error: cannot load from file, failed read") 43 | } 44 | 45 | ss := StorageState{} 46 | err = json.Unmarshal(bbuf, &ss) 47 | if err != nil { 48 | return StorageState{}, errors.New("Error on loading state from json") 49 | } 50 | fmt.Println("Loaded state from disk", fp) 51 | return ss, nil 52 | } 53 | 54 | func saveToFile(ss StorageState, fp string) bool { 55 | b, err := json.Marshal(ss) 56 | if err != nil { 57 | fmt.Println("Error on dumping state to json:", err) 58 | return false 59 | } 60 | 61 | // Now writing to file 62 | f, err := os.Create(fp) 63 | if err != nil { 64 | fmt.Println("Error on opening file", fp, ":", err) 65 | return false 66 | } 67 | defer f.Close() 68 | 69 | w := bufio.NewWriter(f) 70 | n, err := w.Write(b) 71 | if err != nil { 72 | fmt.Println("Error on writing file", fp, ":", err) 73 | return false 74 | } 75 | 76 | w.Flush() 77 | fmt.Println("Wrote history to file in", n, "bytes") 78 | return true 79 | } 80 | -------------------------------------------------------------------------------- /src/tools/profile-util/.gitignore: -------------------------------------------------------------------------------- 1 | profile-pub-mqtt.sh 2 | rsync.sh 3 | setup-if.sh 4 | -------------------------------------------------------------------------------- /src/tools/profile-util/README: -------------------------------------------------------------------------------- 1 | 2 | SPIN profile utilities (prototype): collect information on network 3 | behavior and use it to generate and enforce a profile. 4 | 5 | ==> Introduction 6 | 7 | This repository contains a number of Lua programs: 8 | * mqtt_nm.lua, which implements a prototype of the SPIN Network Measurement 9 | Service. The Lua script subscribes to the SPIN/traffic MQTT channel. 10 | The information published on the channel will be parsed and somehow stored 11 | in the SQLite database. 12 | * stdin_nm.lua, which is similar to mqtt_nm.lua except that it does not read 13 | the SPIN messages from MQTT but from stdin. 14 | * generate-profile.lua, which uses the SQLite database created by mqtt_nm.lua 15 | to create a profile of the network activity of a device. The information 16 | embedded in a profile will be similar to, but not equal to, the information 17 | embedded in a MUD profile. 18 | * generate-fw.lua, which generates iptables firewall rules for the the profile 19 | provided on stdin (use generate-profile.lua with the -p flag to obtain output 20 | which can be fed into generate-fw.lua). 21 | 22 | Caveats: 23 | * The mqtt_nm.lua program should have a better understanding of the concept of 24 | nodes in SPIN. Currently, mqtt_nm.lua does not assign SPIN node IDs to 25 | devices on the network so the "bolletjes app" will not work. 26 | 27 | 28 | ==> Preparing a Unix workstation to use the scripts 29 | 30 | Install Lua libraries as necessary: 31 | 32 | (for generate-fw.lua and mqtt_nm.lua) 33 | $ luarocks --local install luajson 34 | 35 | (for mqtt_nm.lua) 36 | # apt-get install libmosquitto-dev # Or pkg_add mosquitto on OpenBSD 37 | $ luarocks --local install lua-mosquitto 38 | $ luarocks --local install luaposix 39 | 40 | (for generate-profile.lua and mqtt_nm.lua) 41 | $ luarocks --local install lsqlite3 42 | 43 | 44 | ==> Preparing the Valibox to use the scripts 45 | 46 | Note: Valibox 1.6-beta-201805281149 or later is recommended. 47 | 48 | 1. Upload *.lua and db.schema to a directory (e.g. /root/caspar-lua/) on the 49 | Valibox device by hand or invoke ./rsync-valibox.sh to do this automatically 50 | (assumes the device is reachable on 192.168.8.1). 51 | pc# ./rsync-valibox.sh 52 | 2. Copy some libraries from /usr/lib/spin to the directory: 53 | valibox# cat /usr/lib/spin/json.lua >/root/caspar-lua/json.lua 54 | 3. Install the LuaSQLite3 library: 55 | valibox# opkg update 56 | valibox# opkg install lsqlite3 57 | 58 | 59 | ==> Usage 60 | 61 | Collect info from MQTT, store in database: 62 | $ ./mqtt_nm.lua db.sqlite3 192.168.8.1 63 | 64 | Collect info from pcap file, store in database: 65 | $ ../pcap-reader/pcap -r $PCAP | ./stdin_nm.lua db.sqlite3 66 | 67 | generate-profile.lua: 68 | $ ./generate-profile.lua -d db.sqlite3 -a # All devices 69 | $ ./generate-profile.lua -d db.sqlite3 -i 192.168.8.22 70 | $ ./generate-profile.lua -d db.sqlite3 -m 9c:db:75:0e:62:2b 71 | 72 | Enforce generated profile for device with MAC address 00:17:88:71:cd:4a: 73 | $ ./generate-fw.lua -i | ssh root@192.168.8.1 sh -x 74 | $ ./generate-profile.lua -m 00:17:88:71:cd:4a -j | \ 75 | ssh root@192.168.8.1 'cd /root/caspar-fw && ./generate-fw.lua -e | sh -x' 76 | 77 | -------------------------------------------------------------------------------- /src/tools/profile-util/db.schema: -------------------------------------------------------------------------------- 1 | create table flows ( 2 | id integer primary key asc, 3 | mac_from varchar(17), 4 | mac_to varchar(17), 5 | ip_from varchar(46), -- INET6_ADDRSTRLEN 6 | ip_to varchar(46), -- INET6_ADDRSTRLEN 7 | ip_proto int, 8 | tcp_initiated int, 9 | port_from int, 10 | port_to int, 11 | packets int, 12 | bytes int, 13 | first_timestamp int, 14 | last_timestamp int 15 | ); 16 | 17 | create index idx_flows_update on flows( 18 | port_from, 19 | port_to, 20 | ip_proto, 21 | ip_from, 22 | ip_to 23 | ); 24 | 25 | create table dns ( 26 | id integer primary key asc, 27 | domain text, 28 | ip text, 29 | timestamp int 30 | ); 31 | 32 | create view annotated_flows 33 | as 34 | select 35 | flows.id, 36 | flows.mac_from, 37 | flows.ip_from, 38 | dns_from.domain as domain_from, 39 | flows.port_from, 40 | flows.mac_to, 41 | flows.ip_to, 42 | dns_to.domain as domain_to, 43 | flows.port_to, 44 | flows.ip_proto, 45 | flows.tcp_initiated, 46 | flows.packets, 47 | flows.bytes, 48 | flows.first_timestamp, 49 | flows.last_timestamp, 50 | -- dns_from.timestamp, 51 | -- dns_to.timestamp, 52 | (flows.first_timestamp - dns_from.timestamp) as diff_from, 53 | (flows.first_timestamp - dns_to.timestamp) as diff_to, 54 | (flows.last_timestamp - flows.first_timestamp) as conv_seconds 55 | from flows 56 | left join 57 | dns dns_from on dns_from.ip = flows.ip_from 58 | and dns_from.id = ( 59 | select max(dns_from2.id) 60 | from dns dns_from2 61 | where 62 | dns_from2.ip = flows.ip_from 63 | and dns_from2.timestamp <= flows.first_timestamp 64 | ) 65 | left join 66 | dns dns_to on dns_to.ip = flows.ip_to 67 | and dns_to.id = ( 68 | select max(dns_to2.id) 69 | from dns dns_to2 70 | where 71 | dns_to2.ip = flows.ip_to 72 | and dns_to2.timestamp <= flows.first_timestamp 73 | ) 74 | ; 75 | 76 | create view annotated_flows_no_ntp_no_dns 77 | as 78 | select * from annotated_flows 79 | where 80 | not ((port_from == 123 or port_to == 123) and ip_proto == 17) 81 | and not (port_from == 53 or port_to == 53); 82 | 83 | -- More like "flows" actually 84 | create view connections_out 85 | as 86 | select * 87 | from annotated_flows 88 | where 89 | ip_proto != 6 90 | or (ip_proto == 6 and tcp_initiated == 1) 91 | group by ip_to, domain_to, port_to; 92 | 93 | create view connections_out_tcp_initiated 94 | as 95 | select * 96 | from annotated_flows 97 | where 98 | ip_proto = 6 99 | and tcp_initiated = 1 100 | group by ip_to, domain_to, port_to; 101 | 102 | -- Probably not very useful 103 | create view connections_in 104 | as 105 | select * 106 | from annotated_flows 107 | group by ip_from, domain_from, port_from; 108 | 109 | -- 110 | -- No connections_in_tcp_initiated view as that is bogus; tcp_initiated will be 111 | -- 1 when the SYN is sent to the "to" host, not to the "from" host. 112 | -- 113 | 114 | .mode csv 115 | 116 | -------------------------------------------------------------------------------- /src/tools/profile-util/enum.lua: -------------------------------------------------------------------------------- 1 | -- Source: https://github.com/Tjakka5/Enum 2 | -- 3 | -- MIT License 4 | -- 5 | -- Copyright (c) 2017 Justin van der Leij 6 | -- 7 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 8 | -- of this software and associated documentation files (the "Software"), to deal 9 | -- in the Software without restriction, including without limitation the rights 10 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | -- copies of the Software, and to permit persons to whom the Software is 12 | -- furnished to do so, subject to the following conditions: 13 | -- 14 | -- The above copyright notice and this permission notice shall be included in all 15 | -- copies or substantial portions of the Software. 16 | -- 17 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | -- SOFTWARE. 24 | 25 | local Enum = {} 26 | local Meta = { 27 | __index = function(_, k) error("Attempt to index non-existant enum '"..tostring(k).."'.", 2) end, 28 | __newindex = function() error("Attempt to write to static enum", 2) end, 29 | } 30 | 31 | function Enum.new(...) 32 | local values = {...} 33 | 34 | if type(values[1]) == "table" then 35 | values = values[1] 36 | end 37 | 38 | local enum = {} 39 | 40 | for i = 1, #values do 41 | enum[values[i]] = values[i] 42 | end 43 | 44 | return setmetatable(enum, Meta) 45 | end 46 | 47 | return setmetatable(Enum, { 48 | __call = function(_, ...) return Enum.new(...) end, 49 | }) 50 | -------------------------------------------------------------------------------- /src/tools/profile-util/enums.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2018 Caspar Schutijser 3 | -- 4 | -- Permission to use, copy, modify, and distribute this software for any 5 | -- purpose with or without fee is hereby granted, provided that the above 6 | -- copyright notice and this permission notice appear in all copies. 7 | -- 8 | -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | -- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | -- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | -- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | -- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -- 16 | 17 | local enum = require("enum") 18 | 19 | AddressType = enum("IP", "MAC") 20 | CommType = enum("CLIENT", "SERVER") 21 | Column = enum("IP", "DOMAIN") 22 | Protocol = enum("TCP", "UDP", "ELSE") 23 | 24 | -------------------------------------------------------------------------------- /src/tools/profile-util/generate-fw.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- 4 | -- Copyright (c) 2018 Caspar Schutijser 5 | -- 6 | -- Permission to use, copy, modify, and distribute this software for any 7 | -- purpose with or without fee is hereby granted, provided that the above 8 | -- copyright notice and this permission notice appear in all copies. 9 | -- 10 | -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | -- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | -- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | -- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | -- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -- 18 | 19 | local fw = require "fw" 20 | local profile = require "profile" 21 | 22 | function usage(msg) 23 | if msg then 24 | io.stderr:write(msg .. "\n") 25 | end 26 | io.stderr:write[[ 27 | Usage: ./generate-fw.lua [-eip] [-a mac] 28 | -a: do not enforce anything for specified MAC address 29 | -e: enforce profile provided on stdin 30 | -i: initialize firewall 31 | -p: use IP address from profile rather than querying `ip neigh` 32 | ]] 33 | os.exit(1) 34 | end 35 | 36 | function main(args) 37 | local skip = false 38 | local allow = nil 39 | local enforce = false 40 | local initialize = false 41 | 42 | local config = fw.default_config() 43 | 44 | for i = 1, table.getn(args) do 45 | if skip then 46 | skip = false 47 | elseif args[i] == "-a" then 48 | if allow then 49 | usage("cannot specify -a more than once") 50 | end 51 | allow = args[i+1] 52 | if not allow then 53 | usage("missing argument for -a") 54 | end 55 | skip = true 56 | elseif args[i] == "-e" then 57 | if enforce then 58 | usage("cannot specify -e more than once") 59 | end 60 | enforce = true 61 | elseif args[i] == "-i" then 62 | if initialize then 63 | usage("cannot specify -i more than once") 64 | end 65 | initialize = true 66 | elseif args[i] == "-p" then 67 | if config.use_ips_from_profile then 68 | usage("cannot specify -p more than once") 69 | end 70 | config.use_ips_from_profile = true 71 | else 72 | usage() 73 | end 74 | end 75 | 76 | if (allow and enforce) or (allow and initialize) 77 | or (enforce and initialize) then 78 | usage("incompatible arguments") 79 | end 80 | 81 | if not allow and not enforce and not initialize then 82 | usage("must specify either -a, -e or -i") 83 | end 84 | 85 | if not enforce and config.use_ips_from_profile then 86 | usage("-p can only be specified with -e") 87 | end 88 | 89 | if allow then 90 | fw.allow_device(allow) 91 | elseif enforce then 92 | for line in io.lines() do 93 | p = profile.from_json(line) 94 | fw.generate_rules(p, config) 95 | end 96 | elseif initialize then 97 | fw.initialize() 98 | else 99 | assert(false) 100 | end 101 | end 102 | 103 | main(arg) 104 | 105 | -------------------------------------------------------------------------------- /src/tools/profile-util/mqtt_nm.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- 4 | -- Copyright (c) 2018 Caspar Schutijser 5 | -- 6 | -- Permission to use, copy, modify, and distribute this software for any 7 | -- purpose with or without fee is hereby granted, provided that the above 8 | -- copyright notice and this permission notice appear in all copies. 9 | -- 10 | -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | -- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | -- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | -- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | -- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -- 18 | 19 | local nm = require "nm" 20 | 21 | -- https://github.com/flukso/lua-mosquitto 22 | local mqtt = require "mosquitto" 23 | local signal = require "posix.signal" 24 | 25 | local TRAFFIC_CHANNEL = "SPIN/traffic" 26 | 27 | local verbose = true 28 | 29 | function vprintf(msg) 30 | if verbose then 31 | print(msg) 32 | end 33 | end 34 | 35 | local client = mqtt.new() 36 | client.ON_CONNECT = function() 37 | print("Connected to MQTT broker") 38 | client:subscribe(TRAFFIC_CHANNEL) 39 | print("Subscribed to " .. TRAFFIC_CHANNEL) 40 | end 41 | 42 | client.ON_DISCONNECT = function() 43 | print("DISCONNECTED") 44 | shutdown(0) 45 | end 46 | 47 | client.ON_MESSAGE = function(mid, topic, payload) 48 | if topic == TRAFFIC_CHANNEL then 49 | nm.handle_msg(payload) 50 | else 51 | vprintf("message on UNKNOWN channel: " .. topic) 52 | end 53 | end 54 | 55 | function shutdown(signum) 56 | print("Done, exiting\n") 57 | nm.shutdown() 58 | os.exit(128+signum) 59 | end 60 | 61 | if not arg[1] then 62 | io.stderr:write[[ 63 | Usage: ./mqtt_nm.lua db_file [mqtt_host] 64 | ]] 65 | os.exit(1) 66 | end 67 | 68 | signal.signal(signal.SIGINT, shutdown) 69 | signal.signal(signal.SIGKILL, shutdown) 70 | 71 | broker = arg[2] -- defaults to "localhost" if arg not set 72 | if (broker) then 73 | print("Connecting to " .. broker .. "...") 74 | else 75 | print("Connecting to localhost...") 76 | end 77 | assert(client:connect(broker)) 78 | 79 | nm.initialize(verbose, arg[1]) 80 | 81 | while true do 82 | client:loop(1) 83 | nm.periodic_store() 84 | end 85 | 86 | -------------------------------------------------------------------------------- /src/tools/profile-util/profile.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2018 Caspar Schutijser 3 | -- 4 | -- Permission to use, copy, modify, and distribute this software for any 5 | -- purpose with or without fee is hereby granted, provided that the above 6 | -- copyright notice and this permission notice appear in all copies. 7 | -- 8 | -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | -- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | -- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | -- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | -- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -- 16 | 17 | local util_validate = require "util_validate" 18 | 19 | local json = require "json" 20 | 21 | local profile = {} 22 | 23 | function profile.new() 24 | local p = { 25 | mac = "", 26 | ips = {}, 27 | update = false, -- new profile or update of existing profile? 28 | outgoing = { 29 | tcp = { 30 | ips = {}, 31 | domains = {} 32 | }, 33 | udp = { 34 | ips = {}, 35 | domains = {} 36 | }, 37 | other = { 38 | ips = {}, 39 | domains = {} 40 | } 41 | }, 42 | incoming = { 43 | tcp = { 44 | ips = {} 45 | }, 46 | udp = { 47 | ips = {} 48 | }, 49 | other = { 50 | ips = {} 51 | } 52 | } 53 | } 54 | profile.verify(p) 55 | return p 56 | end 57 | 58 | local function verify_dests(dests) 59 | assert(dests) 60 | for _,dest in pairs(dests) do 61 | assert(dest.host) 62 | assert(dest.port) 63 | assert(string.match(dest.port, "^%d+$"), 64 | "invalid port: " .. dest.port) 65 | end 66 | end 67 | 68 | function profile.verify(p) 69 | assert(p.mac) 70 | if (p.mac ~= "") then 71 | util_validate.validate_mac(p.mac) 72 | end 73 | assert(p.ips) 74 | for _,ip in pairs(p.ips) do 75 | util_validate.somewhat_validate_ip(ip) 76 | end 77 | assert(p.update == false or p.update == true) 78 | verify_dests(p.outgoing.tcp.ips) 79 | verify_dests(p.outgoing.tcp.domains) 80 | verify_dests(p.outgoing.udp.ips) 81 | verify_dests(p.outgoing.udp.domains) 82 | verify_dests(p.outgoing.other.ips) 83 | verify_dests(p.outgoing.other.domains) 84 | verify_dests(p.incoming.tcp.ips) 85 | verify_dests(p.incoming.udp.ips) 86 | verify_dests(p.incoming.other.ips) 87 | end 88 | 89 | function profile.from_json(s) 90 | local p = json.decode(s) 91 | 92 | profile.verify(p) 93 | 94 | return p 95 | end 96 | 97 | function profile.to_json(p) 98 | profile.verify(p) 99 | 100 | return json.encode(p) 101 | end 102 | 103 | return profile 104 | -------------------------------------------------------------------------------- /src/tools/profile-util/rsync-valibox.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # rsync.sh: upload the Lua scripts and related files to the Valibox. 4 | # 5 | # Tip: put your SSH public key in /etc/dropbear/authorized_keys on the Valibox. 6 | # 7 | 8 | set -e 9 | 10 | SSH="ssh root@192.168.8.1" 11 | DIR="/root/caspar-lua" 12 | 13 | ${SSH} mkdir -p "${DIR}" 14 | for f in db.schema enum.lua enums.lua fw.lua generate-fw.lua generate-profile.lua profile.lua mqtt_nm.lua nm.lua util_validate.lua ; do 15 | if ! ${SSH} "cmp -s "${DIR}/${f}" -" <"${f}" ; then 16 | echo "Updating ${f}" 17 | ${SSH} "cat >"${DIR}/${f}"" <"${f}" 18 | else 19 | echo "${f} is up to date" 20 | fi 21 | done 22 | 23 | -------------------------------------------------------------------------------- /src/tools/profile-util/stdin_nm.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- 4 | -- Copyright (c) 2018 Caspar Schutijser 5 | -- 6 | -- Permission to use, copy, modify, and distribute this software for any 7 | -- purpose with or without fee is hereby granted, provided that the above 8 | -- copyright notice and this permission notice appear in all copies. 9 | -- 10 | -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | -- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | -- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | -- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | -- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -- 18 | 19 | local nm = require "nm" 20 | 21 | local signal = require "posix.signal" 22 | 23 | function shutdown(signum) 24 | print("Done, exiting\n") 25 | nm.shutdown() 26 | os.exit(128+signum) 27 | end 28 | 29 | if not arg[1] then 30 | io.stderr:write[[ 31 | Usage: ./stdin_nm.lua db_file 32 | ]] 33 | os.exit(1) 34 | end 35 | 36 | nm.initialize(false, arg[1]) 37 | 38 | signal.signal(signal.SIGINT, shutdown) 39 | signal.signal(signal.SIGKILL, shutdown) 40 | 41 | for line in io.lines() do 42 | nm.handle_msg(line) 43 | end 44 | 45 | nm.shutdown() 46 | 47 | -------------------------------------------------------------------------------- /src/tools/profile-util/util_validate.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2018 Caspar Schutijser 3 | -- 4 | -- Permission to use, copy, modify, and distribute this software for any 5 | -- purpose with or without fee is hereby granted, provided that the above 6 | -- copyright notice and this permission notice appear in all copies. 7 | -- 8 | -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | -- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | -- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | -- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | -- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -- 16 | 17 | local util_validate = {} 18 | 19 | -- Assert that specified string somewhat looks like an IP address 20 | function util_validate.somewhat_validate_ip(ip) 21 | assert(string.match(ip, "^[%x%.:/]+$"), 22 | "does not sufficiently look like an IP address: " .. ip) 23 | end 24 | 25 | -- Asserts that specified string looks like a MAC address 26 | function util_validate.validate_mac(mac) 27 | assert(string.match(mac, "^%x%x:%x%x:%x%x:%x%x:%x%x:%x%x$"), 28 | "invalid MAC address: " .. mac) 29 | end 30 | 31 | return util_validate 32 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/.gitignore: -------------------------------------------------------------------------------- 1 | fuzzing/afl/archived-findings 2 | fuzzing/afl/findings 3 | fuzzing/afl/testcases 4 | fuzzing/fuzz.sh 5 | fuzzing/generated-pcaps 6 | fuzzing/randpkt-gen.sh 7 | fuzzing/*.pcap 8 | fuzzing/pcaps 9 | spin-pcap-reader 10 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | AM_CPPFLAGS = -I$(top_srcdir)/include 3 | AM_CFLAGS = ${regular_CFLAGS} -g -Wall -Werror 4 | 5 | bin_PROGRAMS = spin-pcap-reader 6 | spin_pcap_reader_SOURCES = arpupdate.c \ 7 | ipt.c \ 8 | macstr.c \ 9 | pcap.c \ 10 | sleep.c \ 11 | socket.c \ 12 | spinhook.c 13 | spin_pcap_reader_CFLAGS = 14 | spin_pcap_reader_LDADD = $(top_builddir)/lib/libspin.a 15 | 16 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/README: -------------------------------------------------------------------------------- 1 | 2 | spin-pcap-reader: 3 | Parse a pcap file and communicate results to spind 4 | 5 | 6 | ==> Introduction 7 | 8 | The program in this repository parses a PCAP file and communicates its findings 9 | to spind through a socket. 10 | 11 | 12 | ==> Dependencies 13 | 14 | * libpcap 15 | 16 | 17 | ==> Compiling the program 18 | 19 | Follow the SPIN build instructions. When running configure, make sure to pass 20 | --enable-spin-pcap-reader. 21 | 22 | 23 | ==> Usage 24 | 25 | Before making use of the examples below, make sure to cd to the directory where 26 | spin-pcap-reader was built, for instance: 27 | 28 | $ cd build/tools/spin-pcap-reader 29 | 30 | Parse file.pcap. 31 | 32 | $ sudo ./spin-pcap-reader -r file.pcap 33 | 34 | Listen on eth0. 35 | 36 | $ sudo ./spin-pcap-reader -i eth0 37 | 38 | Note: for now, it is necessary to run spin-pcap-reader as root. This is because 39 | the socket created by spind can only be written to by the root user. 40 | 41 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/arpupdate.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /* spind/lib includes */ 7 | #include "extsrc.h" 8 | #include "socket.h" 9 | #include "util.h" 10 | 11 | #include "arpupdate.h" 12 | #include "ipt.h" 13 | #include "macstr.h" 14 | 15 | /* #define ARPUPDATE_DEBUG */ 16 | 17 | static void 18 | send_arp_table_update_to_spind(int fd, struct extsrc_arp_table_update *up) 19 | { 20 | struct extsrc_msg *msg; 21 | 22 | msg = extsrc_msg_create_arp_table_update(up); 23 | 24 | socket_writemsg(fd, msg->data, msg->length); 25 | 26 | extsrc_msg_free(msg); 27 | } 28 | 29 | void 30 | arp_update(int fd, const uint8_t *mac, const uint8_t *ip, uint8_t family) 31 | { 32 | struct extsrc_arp_table_update up; 33 | char ipstr[INET6_ADDRSTRLEN]; 34 | 35 | assert(family == AF_INET || family == AF_INET6); 36 | 37 | ipt_from_uint8t(&up.ip, ipstr, sizeof(ipstr), ip, family); 38 | macstr_from_uint8t(mac, up.mac, sizeof(up.mac)); 39 | 40 | send_arp_table_update_to_spind(fd, &up); 41 | 42 | #ifdef ARPUPDATE_DEBUG 43 | warnx("MAC: %s, IP: %s", up.mac, ipstr); 44 | #endif 45 | } 46 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/arpupdate.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void arp_update(int fd, const uint8_t *mac, const uint8_t *ip, uint8_t family); 4 | 5 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/external/external.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Caspar Schutijser 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef EXTERNAL_H 18 | #define EXTERNAL_H 19 | 20 | #include 21 | 22 | /* util.c */ 23 | void printhex(const char *s, const u_int8_t *buf, size_t len); 24 | 25 | #endif /* EXTERNAL_H */ 26 | 27 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/external/extract.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was copied and adapted from OpenBSD usr.sbin/tcpdump/extract.h 3 | */ 4 | 5 | /* $OpenBSD: extract.h,v 1.9 2007/10/07 16:41:05 deraadt Exp $ */ 6 | 7 | /* 8 | * Copyright (c) 1992, 1993, 1994, 1995, 1996 9 | * The Regents of the University of California. All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that: (1) source code distributions 13 | * retain the above copyright notice and this paragraph in its entirety, (2) 14 | * distributions including binary code include the above copyright notice and 15 | * this paragraph in its entirety in the documentation or other materials 16 | * provided with the distribution, and (3) all advertising materials mentioning 17 | * features or use of this software display the following acknowledgement: 18 | * ``This product includes software developed by the University of California, 19 | * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of 20 | * the University nor the names of its contributors may be used to endorse 21 | * or promote products derived from this software without specific prior 22 | * written permission. 23 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED 24 | * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 25 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 26 | * 27 | * @(#) $Id: extract.h,v 1.9 2007/10/07 16:41:05 deraadt Exp $ (LBL) 28 | */ 29 | 30 | /* Network to host order macros */ 31 | 32 | #define EXTRACT_16BITS(p) \ 33 | ((u_int16_t)*((const u_int8_t *)(p) + 0) << 8 | \ 34 | (u_int16_t)*((const u_int8_t *)(p) + 1)) 35 | 36 | #define EXTRACT_32BITS(p) \ 37 | ((u_int32_t)*((const u_int8_t *)(p) + 0) << 24 | \ 38 | (u_int32_t)*((const u_int8_t *)(p) + 1) << 16 | \ 39 | (u_int32_t)*((const u_int8_t *)(p) + 2) << 8 | \ 40 | (u_int32_t)*((const u_int8_t *)(p) + 3)) 41 | 42 | #define EXTRACT_24BITS(p) \ 43 | ((u_int32_t)*((const u_int8_t *)(p) + 0) << 16 | \ 44 | (u_int32_t)*((const u_int8_t *)(p) + 1) << 8 | \ 45 | (u_int32_t)*((const u_int8_t *)(p) + 2)) 46 | 47 | /* Little endian protocol host order macros */ 48 | 49 | #define EXTRACT_LE_8BITS(p) (*(p)) 50 | #define EXTRACT_LE_16BITS(p) \ 51 | ((u_int16_t)*((const u_int8_t *)(p) + 1) << 8 | \ 52 | (u_int16_t)*((const u_int8_t *)(p) + 0)) 53 | #define EXTRACT_LE_32BITS(p) \ 54 | ((u_int32_t)*((const u_int8_t *)(p) + 3) << 24 | \ 55 | (u_int32_t)*((const u_int8_t *)(p) + 2) << 16 | \ 56 | (u_int32_t)*((const u_int8_t *)(p) + 1) << 8 | \ 57 | (u_int32_t)*((const u_int8_t *)(p) + 0)) 58 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/external/interface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was copied and adapted from OpenBSD usr.sbin/tcpdump/interface.h 3 | */ 4 | 5 | /* $OpenBSD: interface.h,v 1.69 2016/11/16 13:47:27 reyk Exp $ */ 6 | 7 | /* 8 | * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 9 | * The Regents of the University of California. All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that: (1) source code distributions 13 | * retain the above copyright notice and this paragraph in its entirety, (2) 14 | * distributions including binary code include the above copyright notice and 15 | * this paragraph in its entirety in the documentation or other materials 16 | * provided with the distribution, and (3) all advertising materials mentioning 17 | * features or use of this software display the following acknowledgement: 18 | * ``This product includes software developed by the University of California, 19 | * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of 20 | * the University nor the names of its contributors may be used to endorse 21 | * or promote products derived from this software without specific prior 22 | * written permission. 23 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED 24 | * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 25 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 26 | * 27 | * @(#) $Id: interface.h,v 1.69 2016/11/16 13:47:27 reyk Exp $ (LBL) 28 | */ 29 | 30 | #ifndef INTERFACE_H 31 | #define INTERFACE_H 32 | 33 | extern int snaplen; 34 | /* global pointers to beginning and end of current packet (during printing) */ 35 | extern const u_char *packetp; 36 | extern const u_char *snapend; 37 | 38 | /* 39 | * True if "l" bytes of "var" were captured. 40 | * 41 | * The "snapend - (l) <= snapend" checks to make sure "l" isn't so large 42 | * that "snapend - (l)" underflows. 43 | * 44 | * The check is for <= rather than < because "l" might be 0. 45 | */ 46 | #define TTEST2(var, l) (snapend - (l) <= snapend && \ 47 | (const u_char *)&(var) <= snapend - (l)) 48 | 49 | /* True if "var" was captured */ 50 | #define TTEST(var) TTEST2(var, sizeof(var)) 51 | 52 | /* Bail if "l" bytes of "var" were not captured */ 53 | #define TCHECK2(var, l) if (!TTEST2(var, l)) goto trunc 54 | 55 | /* Bail if "var" was not captured */ 56 | #define TCHECK(var) TCHECK2(var, sizeof(var)) 57 | 58 | #endif /* INTERFACE_H */ 59 | 60 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/fuzzing/FUZZING: -------------------------------------------------------------------------------- 1 | ==> Wireshark randpkt 2 | 3 | Some fuzzing is done using the randpkt utility from Wireshark. 4 | 5 | $ git clone WIRESHARK 6 | $ cd wireshark 7 | $ mkdir _build 8 | $ cd _build 9 | $ cmake .. -DBUILD_randpkt=true 10 | $ make 11 | 12 | $ ./run/randpkt 13 | 14 | See https://wiki.wireshark.org/FuzzTesting 15 | 16 | 17 | ==> Wireshark editcap 18 | 19 | Part of the wireshark-common Debian package. 20 | 21 | $ editcap 22 | $ man editcap 23 | 24 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/fuzzing/afl/README: -------------------------------------------------------------------------------- 1 | Testcases created as follows: 2 | 3 | $ editcap -r iot-lab-6-20180501.pcap ../afl/testcases/dns1.pcap 140 4 | $ editcap -r iot-lab-6-20180501.pcap ../afl/testcases/tcpsyn1.pcap 148 5 | $ editcap -r iot-lab-6-20180501.pcap ../afl/testcases/arp1.pcap 178 6 | $ editcap -r iot-lab-6-20180501.pcap ../afl/testcases/arp2.pcap 179 7 | 8 | And so on. 9 | 10 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/fuzzing/afl/afl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd ../.. 6 | if [ "$(uname)" = "OpenBSD" ] ; then 7 | CC=afl-clang make -f Makefile.OpenBSD 8 | else 9 | CC=afl-clang make -f Makefile.Debian 10 | fi 11 | cd - 12 | 13 | export AFL_SKIP_CPUFREQ=1 14 | 15 | afl-fuzz -i testcases -o findings -- ../../pcap -r @@ 16 | 17 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/ipt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ipt.h" 9 | 10 | void 11 | ipt_from_uint8t(ip_t *ip_t, char *ipstr, size_t ipstr_len, const uint8_t *ip, 12 | uint8_t af) 13 | { 14 | assert(af == AF_INET || af == AF_INET6); 15 | 16 | if (inet_ntop(af, ip, ipstr, ipstr_len) == NULL) { 17 | err(1, "inet_ntop"); 18 | } 19 | 20 | if (spin_pton(ip_t, ipstr) == 0) { 21 | errx(1, "spin_pton"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/ipt.h: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | /* 4 | * Convert IP address to ip_t type. Also provides a string representation 5 | * in the provided string. 6 | */ 7 | void ipt_from_uint8t(ip_t *, char*, size_t, const uint8_t *, uint8_t); 8 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/macstr.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #ifdef HAVE_NET_ETHERNET_H 9 | #include 10 | #endif 11 | #ifdef HAVE_NETINET_IF_ETHER_H 12 | #include 13 | #endif 14 | 15 | #include 16 | #include 17 | 18 | #include "macstr.h" 19 | 20 | int 21 | macstr_from_ea(struct ether_addr *ea, char *s, size_t len) 22 | { 23 | return snprintf(s, len, "%02x:%02x:%02x:%02x:%02x:%02x", 24 | ea->ether_addr_octet[0], ea->ether_addr_octet[1], 25 | ea->ether_addr_octet[2], ea->ether_addr_octet[3], 26 | ea->ether_addr_octet[4], ea->ether_addr_octet[5]); 27 | } 28 | 29 | void 30 | macstr_from_uint8t(const uint8_t *mac, char *s, size_t len) 31 | { 32 | struct ether_addr ea; 33 | 34 | memcpy(&ea.ether_addr_octet, mac, sizeof(ea.ether_addr_octet)); 35 | macstr_from_ea(&ea, s, len); 36 | } 37 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/macstr.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct ether_addr; 4 | 5 | int macstr_from_ea(struct ether_addr *ea, char *s, size_t len); 6 | void macstr_from_uint8t(const uint8_t *mac, char *s, size_t len); 7 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/sleep.c: -------------------------------------------------------------------------------- 1 | #include 2 | #define __USE_GNU /* for TIMESPEC_TO_TIMEVAL */ 3 | #include 4 | #undef __USE_GNU 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "sleep.h" 11 | 12 | static void 13 | monotime(struct timeval *tv) 14 | { 15 | struct timespec ts; 16 | 17 | if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) { 18 | err(1, "clock_gettime"); 19 | } 20 | 21 | TIMESPEC_TO_TIMEVAL(tv, &ts); 22 | } 23 | 24 | void 25 | maybe_sleep(const struct timeval *cur_pcap) 26 | { 27 | static struct timeval last_pcap = { 0, 0 }; 28 | static struct timeval last_wall = { 0, 0 }; 29 | struct timeval cur_wall; 30 | struct timeval diff_pcap; 31 | struct timeval diff_wall; 32 | struct timeval to_sleep; 33 | 34 | monotime(&cur_wall); 35 | 36 | if (timerisset(&last_wall)) { 37 | timersub(cur_pcap, &last_pcap, &diff_pcap); 38 | timersub(&cur_wall, &last_wall, &diff_wall); 39 | 40 | if (timercmp(&diff_wall, &diff_pcap, <)) { 41 | timersub(&diff_pcap, &diff_wall, &to_sleep); 42 | 43 | if (select(0, NULL, NULL, NULL, &to_sleep) == -1 && 44 | errno != EINTR) { 45 | err(1, "select"); 46 | } 47 | } 48 | } 49 | 50 | last_pcap = *cur_pcap; 51 | last_wall = cur_wall; 52 | } 53 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/sleep.h: -------------------------------------------------------------------------------- 1 | void maybe_sleep(const struct timeval *); 2 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/socket.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "extsrc.h" 11 | #include "socket.h" 12 | 13 | /* 14 | * If not using UNIX domain sockets, use TCP for communication between 15 | * an extsrc client and spind if defined; UDP otherwise. 16 | */ 17 | #define EXTSRC_TCP 18 | 19 | static int 20 | socket_open_inet(const char *host) 21 | { 22 | struct addrinfo hints, *res, *res0; 23 | char port[sizeof("65535")]; 24 | int error; 25 | int save_errno; 26 | int fd; 27 | const char *cause = NULL; 28 | 29 | memset(&hints, 0, sizeof(hints)); 30 | hints.ai_family = AF_UNSPEC; 31 | #ifdef EXTSRC_TCP 32 | hints.ai_socktype = SOCK_STREAM; 33 | #else 34 | hints.ai_socktype = SOCK_DGRAM; 35 | #endif 36 | snprintf(port, sizeof(port), "%d", EXTSRC_PORT); 37 | error = getaddrinfo(host, port, &hints, &res0); 38 | if (error) { 39 | errx(1, "getaddrinfo: %s", gai_strerror(error)); 40 | } 41 | fd = -1; 42 | for (res = res0; res; res = res->ai_next) { 43 | fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 44 | if (fd == -1) { 45 | cause = "socket"; 46 | continue; 47 | } 48 | 49 | if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) { 50 | cause = "connect"; 51 | save_errno = errno; 52 | close(fd); 53 | errno = save_errno; 54 | fd = -1; 55 | continue; 56 | } 57 | 58 | break; 59 | } 60 | if (fd == -1) { 61 | err(1, "%s", cause); 62 | } 63 | 64 | freeaddrinfo(res0); 65 | 66 | return fd; 67 | } 68 | 69 | static int 70 | socket_open_unix(const char *path) 71 | { 72 | int fd; 73 | struct sockaddr_un s_un; 74 | 75 | fd = socket(AF_UNIX, SOCK_DGRAM, 0); 76 | if (fd == -1) { 77 | err(1, "socket"); 78 | } 79 | 80 | memset(&s_un, 0, sizeof(s_un)); 81 | s_un.sun_family = AF_UNIX; 82 | if (snprintf(s_un.sun_path, sizeof(s_un.sun_path), "%s", 83 | path) >= (ssize_t)sizeof(s_un.sun_path)) { 84 | errx(1, "%s: socket path too long", path); 85 | } 86 | if (connect(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { 87 | err(1, "connect: %s", path); 88 | } 89 | 90 | return fd; 91 | } 92 | 93 | int 94 | socket_open(const char *path, const char *host) 95 | { 96 | if (host) { 97 | return socket_open_inet(host); 98 | } 99 | return socket_open_unix(path == NULL ? EXTSRC_SOCKET_PATH : path); 100 | } 101 | 102 | void 103 | socket_writemsg(int fd, char *msg, size_t msg_len) 104 | { 105 | static unsigned long ok = 0; 106 | static unsigned long fail = 0; 107 | 108 | if (send(fd, msg, msg_len, 0) == -1) { 109 | if (errno == ENOBUFS) { 110 | /* 111 | * XXX perhaps distinguish between the case where we 112 | * read a PCAP and the case where we're listening to an 113 | * interface? In the former case we could perhaps wait, 114 | * e.g. using poll(). 115 | */ 116 | ++fail; 117 | warnx("send returned ENOBUFS; %lu/%lu fail/ok", fail, 118 | ok); 119 | } else { 120 | err(1, "send"); 121 | } 122 | } else { 123 | ++ok; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/socket.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int socket_open(const char *, const char *); 4 | void socket_writemsg(int, char *, size_t); 5 | 6 | -------------------------------------------------------------------------------- /src/tools/spin-pcap-reader/spinhook.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "spinhook.h" 4 | #include "spin_log.h" 5 | 6 | void 7 | spinhook_nodesmerged(node_cache_t *node_cache, node_t *dest_node, node_t *src_node) 8 | { 9 | /* don't care */ 10 | } 11 | 12 | void 13 | spinhook_nodedeleted(node_cache_t *node_cache, node_t *node) 14 | { 15 | /* don't care */ 16 | } 17 | --------------------------------------------------------------------------------