├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── LICENSE-GPLv3 ├── LICENSE-OpenSSL ├── Makefile ├── README.md ├── conf ├── rvrrpd.conf └── rvrrpd.json.conf ├── docs ├── Makefile ├── api │ ├── examples.rst │ ├── intro.rst │ └── reference.rst ├── conf.py ├── config │ ├── install.rst │ ├── intro.rst │ └── reference.rst └── index.rst ├── src ├── api │ ├── client │ │ ├── mod.rs │ │ ├── router │ │ │ ├── handlers │ │ │ │ ├── auth │ │ │ │ │ └── mod.rs │ │ │ │ ├── config │ │ │ │ │ ├── global │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── protocols │ │ │ │ │ │ └── mod.rs │ │ │ │ │ └── vrouter │ │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ └── run │ │ │ │ │ ├── global │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── protocols │ │ │ │ │ └── mod.rs │ │ │ │ │ └── vrrp │ │ │ │ │ └── mod.rs │ │ │ └── mod.rs │ │ └── sessions │ │ │ ├── auth.rs │ │ │ ├── mod.rs │ │ │ └── token.rs │ └── mod.rs ├── auth.rs ├── bin │ └── rvrrpd.rs ├── checksums.rs ├── config.rs ├── constants.rs ├── debug.rs ├── fsm.rs ├── lib.rs ├── os │ ├── drivers.rs │ ├── freebsd │ │ ├── arp.rs │ │ ├── bpf.rs │ │ ├── constants.rs │ │ ├── libc.rs │ │ ├── mod.rs │ │ └── netinet.rs │ ├── linux │ │ ├── arp.rs │ │ ├── filter.rs │ │ ├── libc.rs │ │ ├── libnl.rs │ │ ├── mod.rs │ │ └── netdev.rs │ ├── mod.rs │ └── multi │ │ ├── libc.rs │ │ └── mod.rs ├── packets.rs ├── protocols.rs ├── threads.rs ├── timers.rs └── vrouter.rs └── utils └── rvrrpd-pw ├── .gitignore ├── Cargo.toml ├── Makefile └── src └── main.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | debug-build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Install dependencies 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install -y libnl-3-dev 16 | sudo apt-get install -y libnl-route-3-dev 17 | - name: Build 18 | run: cargo build --verbose 19 | - name: Run tests 20 | run: cargo test --verbose 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # local configuration files 13 | *.local.con 14 | 15 | # editors 16 | .vscode/ 17 | 18 | # documentation 19 | docs/_build 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rVRRPd" 3 | version = "0.1.3" 4 | authors = ["Nicolas Chabbey "] 5 | edition = "2018" 6 | repository = "httsp://github.com/e3prom/rvrrpd" 7 | homepage = "https://github.com/e3prom/rVRRPd" 8 | readme = "README.md" 9 | description = "A fast, multi-platform and highly secure VRRP daemon." 10 | license = "GPL-3.0" 11 | publish = false 12 | 13 | [badges] 14 | github-actions = { repository = "e3prom/rVRRPd", workflow = "Build"} 15 | 16 | [dependencies] 17 | rand = "0.7" 18 | getopts = "0.2" 19 | libc = "0.2.51" 20 | nix = "0.15" 21 | ctrlc = { version = "3.1", features = ["termination"] } 22 | byteorder = "1" 23 | itertools = "0.8.0" 24 | foreign-types = "0.4.0" 25 | futures = "0.1" 26 | tokio = "0.1" 27 | tokio-openssl = "0.3" 28 | serde = "1.0.91" 29 | serde_derive = "1.0.91" 30 | serde_json = "1.0" 31 | toml = "0.5" 32 | daemonize = "0.4.1" 33 | openssl = "0.10" 34 | chrono = "0.4.6" 35 | hmac = "0.7" 36 | sha2 = "0.8" 37 | sha3 = "0.8" 38 | scrypt = "0.2" 39 | crossbeam = "0.7" 40 | gotham = { git = "https://github.com/gotham-rs/gotham", rev= "22e68d5f" } 41 | gotham_derive = "0.4.0" 42 | hyper = "0.12" 43 | mime = "0.3.14" 44 | cookie = "0.12" 45 | regex = "1" 46 | lazy_static = "1.4" 47 | failure = "0.1" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Lightweight, fast, and highly secure VRRP daemon 2 | Copyright (C) 2019-2021 Nicolas Chabbey 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program . 16 | If not, see . 17 | 18 | Additional permission under GNU GPL version 3 section 7 19 | 20 | If you modify this Program, or any covered work, by linking or 21 | combining it with OpenSSL (or a modified version of that library), 22 | containing parts covered by the terms of OpenSSL , 23 | the licensors of this Program grant you additional permission 24 | to convey the resulting work. 25 | -------------------------------------------------------------------------------- /LICENSE-OpenSSL: -------------------------------------------------------------------------------- 1 | 2 | LICENSE ISSUES 3 | ============== 4 | 5 | The OpenSSL toolkit stays under a double license, i.e. both the conditions of 6 | the OpenSSL License and the original SSLeay license apply to the toolkit. 7 | See below for the actual license texts. 8 | 9 | OpenSSL License 10 | --------------- 11 | 12 | /* ==================================================================== 13 | * Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved. 14 | * 15 | * Redistribution and use in source and binary forms, with or without 16 | * modification, are permitted provided that the following conditions 17 | * are met: 18 | * 19 | * 1. Redistributions of source code must retain the above copyright 20 | * notice, this list of conditions and the following disclaimer. 21 | * 22 | * 2. Redistributions in binary form must reproduce the above copyright 23 | * notice, this list of conditions and the following disclaimer in 24 | * the documentation and/or other materials provided with the 25 | * distribution. 26 | * 27 | * 3. All advertising materials mentioning features or use of this 28 | * software must display the following acknowledgment: 29 | * "This product includes software developed by the OpenSSL Project 30 | * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" 31 | * 32 | * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to 33 | * endorse or promote products derived from this software without 34 | * prior written permission. For written permission, please contact 35 | * openssl-core@openssl.org. 36 | * 37 | * 5. Products derived from this software may not be called "OpenSSL" 38 | * nor may "OpenSSL" appear in their names without prior written 39 | * permission of the OpenSSL Project. 40 | * 41 | * 6. Redistributions of any form whatsoever must retain the following 42 | * acknowledgment: 43 | * "This product includes software developed by the OpenSSL Project 44 | * for use in the OpenSSL Toolkit (http://www.openssl.org/)" 45 | * 46 | * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY 47 | * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 48 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 49 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR 50 | * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 51 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 52 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 53 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 54 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 55 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 56 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 57 | * OF THE POSSIBILITY OF SUCH DAMAGE. 58 | * ==================================================================== 59 | * 60 | * This product includes cryptographic software written by Eric Young 61 | * (eay@cryptsoft.com). This product includes software written by Tim 62 | * Hudson (tjh@cryptsoft.com). 63 | * 64 | */ 65 | 66 | Original SSLeay License 67 | ----------------------- 68 | 69 | /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) 70 | * All rights reserved. 71 | * 72 | * This package is an SSL implementation written 73 | * by Eric Young (eay@cryptsoft.com). 74 | * The implementation was written so as to conform with Netscapes SSL. 75 | * 76 | * This library is free for commercial and non-commercial use as long as 77 | * the following conditions are aheared to. The following conditions 78 | * apply to all code found in this distribution, be it the RC4, RSA, 79 | * lhash, DES, etc., code; not just the SSL code. The SSL documentation 80 | * included with this distribution is covered by the same copyright terms 81 | * except that the holder is Tim Hudson (tjh@cryptsoft.com). 82 | * 83 | * Copyright remains Eric Young's, and as such any Copyright notices in 84 | * the code are not to be removed. 85 | * If this package is used in a product, Eric Young should be given attribution 86 | * as the author of the parts of the library used. 87 | * This can be in the form of a textual message at program startup or 88 | * in documentation (online or textual) provided with the package. 89 | * 90 | * Redistribution and use in source and binary forms, with or without 91 | * modification, are permitted provided that the following conditions 92 | * are met: 93 | * 1. Redistributions of source code must retain the copyright 94 | * notice, this list of conditions and the following disclaimer. 95 | * 2. Redistributions in binary form must reproduce the above copyright 96 | * notice, this list of conditions and the following disclaimer in the 97 | * documentation and/or other materials provided with the distribution. 98 | * 3. All advertising materials mentioning features or use of this software 99 | * must display the following acknowledgement: 100 | * "This product includes cryptographic software written by 101 | * Eric Young (eay@cryptsoft.com)" 102 | * The word 'cryptographic' can be left out if the rouines from the library 103 | * being used are not cryptographic related :-). 104 | * 4. If you include any Windows specific code (or a derivative thereof) from 105 | * the apps directory (application code) you must include an acknowledgement: 106 | * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" 107 | * 108 | * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND 109 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 110 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 111 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 112 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 113 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 114 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 115 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 116 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 117 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 118 | * SUCH DAMAGE. 119 | * 120 | * The licence and distribution terms for any publically available version or 121 | * derivative of this code cannot be changed. i.e. this code cannot simply be 122 | * copied and put under another distribution licence 123 | * [including the GNU Public Licence.] 124 | */ 125 | 126 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = target/release 2 | BINARY = rvrrpd 3 | PREFIX = /usr 4 | 5 | main: rvrrpd-pw 6 | @cargo build --release 7 | 8 | test: 9 | @cargo test 10 | 11 | docs: 12 | @cargo doc --no-deps 13 | 14 | check: 15 | @cargo fmt --all -- --check 16 | 17 | clean: rvrrpd-pw-clean 18 | @cargo clean 19 | 20 | install: rvrrpd-pw-install 21 | if [ ! -d $(DESTDIR)$(PREFIX)/sbin ]; then \ 22 | mkdir -p $(DESTDIR)$(PREFIX)/sbin; \ 23 | fi 24 | cp $(TARGET)/${BINARY} $(DESTDIR)$(PREFIX)/sbin/${BINARY} 25 | chmod 755 $(DESTDIR)$(PREFIX)/sbin/${BINARY} 26 | if [ ! -d $(DESTDIR)/etc/rvrrpd ]; then \ 27 | mkdir -p $(DESTDIR)/etc/rvrrpd; \ 28 | fi 29 | 30 | rvrrpd-pw: 31 | $(MAKE) -C utils/rvrrpd-pw 32 | 33 | rvrrpd-pw-install: 34 | $(MAKE) -C utils/rvrrpd-pw install 35 | 36 | rvrrpd-pw-clean: 37 | $(MAKE) -C utils/rvrrpd-pw clean 38 | 39 | .PHONY: main test docs check clean install 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/e3prom/rVRRPd/blob/master/LICENSE) 2 | ![GitHub top language](https://img.shields.io/github/languages/top/e3prom/rvrrpd.svg) 3 | ![GitHub issues](https://img.shields.io/github/issues/e3prom/rvrrpd.svg) 4 | ![GitHub last commit](https://img.shields.io/github/last-commit/e3prom/rvrrpd.svg) 5 | ![Github build status](https://github.com/e3prom/rVRRPd/workflows/Build/badge.svg) 6 | [![Documentation Status](https://readthedocs.org/projects/rvrrpd/badge/?version=latest)](https://rvrrpd.readthedocs.io/en/latest/?badge=latest) 7 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=TWE8MESRMWRG8) 8 | 9 | # Introduction 10 | [rVRRPd](https://github.com/e3prom/rVRRPd) is a fast, multi-platform and standard-compliant VRRPv2 implementation written in [Rust](https://www.rust-lang.org/), a modern programming language known for its portability, memory-safety and speed. 11 | 12 | # Features 13 | * Aimed to be Fast, Portable and **Highly Secure** 14 | * Supports multiple operating systems and processor architectures 15 | * Multi-threaded operation (1 thread per interface and virtual router) 16 | * Easily configurable using [TOML](https://github.com/toml-lang/toml) or [JSON](https://www.json.org/) 17 | * Interoperable with [`RFC3768`](https://tools.ietf.org/html/rfc3768) (VRRPv2) compliant devices 18 | * Fully compatible with Cisco IOS and Cisco IOS-XE devices 19 | * Authentication Support 20 | * Password Authentication (Type-1) based on [`RFC2338`](https://tools.ietf.org/html/rfc2338) 21 | * Proprietary P0 HMAC (SHA256 truncated to 8 bytes) 22 | * Proprietary P1 (SHAKE256 XOF) 23 | * Supports multiple operation modes: 24 | * Sniffer mode (`-m0`) 25 | * Virtual Router in foreground mode (`-m1`) 26 | * Virtual Router in daemon mode (`-m2`) 27 | * Supports MAC-based Virtual LAN interface (`macvlan`) _(Linux)_ 28 | * Uses Berkeley Packet Filters Sockets (`BPF`) _(FreeBSD)_ 29 | * Supports BPF Linux Socket Filters (_Linux_) 30 | * Provides a Client Application Programming Interface (API) 31 | * Runs plain-text HTTP or HTTPS (SSL/TLS) 32 | 33 | # Development 34 | This project is still in **_active development_**, and at this time, only supports the Linux and FreeBSD operating systems. There is no stable API, configuration or even documentation yet. [`rVRRPd`](https://github.com/e3prom/rVRRPd) may not be interoperable with standard-compliant network equipments when using proprietary features (such as P0 or P1 authentication). 35 | 36 | The development roadmap for the upcoming `0.2.0` release can be found [here](https://github.com/e3prom/rVRRPd/projects/2). 37 | 38 | # Documentation 39 | You can access the latest documentation at [https://rvrrpd.readthedocs.io/en/latest/](https://rvrrpd.readthedocs.io/en/latest/). 40 | 41 | # Support 42 | If you are experiencing any stability, security or interoperability issues, feel free to open a [new issue](https://github.com/e3prom/rVRRPd/issues/new). 43 | -------------------------------------------------------------------------------- /conf/rvrrpd.conf: -------------------------------------------------------------------------------- 1 | debug = 5 # debugging level (0=none, 1=low, 2=medium, 4=high, 5=extensive) 2 | #time_format = "disabled" # 'short' for M/D/Y HH:MM:SS timestamps 3 | # 'rfc2822' for RFC2822 time and date format 4 | #time_zone = "local" # 'local' for local time, 'utc' for UTC time 5 | #pid = "/var/run/rvrrpd.pid" # path to PID file 6 | #working_dir = "/var/tmp" # daemon's working directory 7 | #main_log = "/var/log/rvrrpd.log" # main log file 8 | #error_log = "/var/log/rvrrpd-error.log" # error log file 9 | #client_api = "http" # client API transport type 10 | # 'http' for using plain-text HTTP 11 | 12 | # [[vrouter]] 13 | # group = 2 # VRRP group id 14 | # interface = "vmnet8" # interface to listen on 15 | # vip = "10.2.2.254" # virtual ip address 16 | # priority = 254 # priority (default: 100) 17 | # preemption = false # preemption (default: false) 18 | # auth_type = simple # authentication type (default: disabled) 19 | # 'rfc2338-simple' RFC2338 Simple (Type-1) 20 | # 'p0-t8-sha256' Proprietary P0 (8 bytes truncated SHA256-HMAC) 21 | # 'p1-b8-shake256' Proprietary P1 (8 bytes SHAKE256 XOF) 22 | # auth_secret = "changeme" # authentication secret key 23 | # timers = { advert = 1 } # user configurable timers 24 | # 'advert' advertisement interval (default: 1s) 25 | # rfc3768 = true # rfc3768 compatibility flag (default: true) 26 | # 'true' provides strict RFC compliance and interoperability 27 | # 'false' allow for multiple IP addresses and proprietary extensions 28 | # netdrv = "libnl" # network driver (default: libnl) 29 | # 'libnl' for linux netlink support 30 | # 'ioctl' for ioctls 31 | # iftype = "ether" # interface type (default: ether) 32 | # 'ether' for standard ethernet interface 33 | # 'macvlan' for virtual macvlan interface 34 | # vifname = "standby" # virtual interface name (default: standby) 35 | # requires iftype = "macvlan" 36 | # socket_filter = true # use BPF socket filters (default: true) 37 | 38 | # [[vrouter]] 39 | # group = 5 40 | # interface = "docker0" 41 | # vip = "10.0.5.254" 42 | # priority = 200 43 | 44 | # [protocols] 45 | # [[protocols.static]] # static route 46 | # route = "100.100.100.0" # destination network 47 | # mask = "255.255.255.0" # destination network mask 48 | # nh = "172.16.46.1" # next-hop 49 | # metric = 100 # metric (optional) 50 | # mtu = 1500 # maximum transmission unit (optional) 51 | # [[protocols.static]] 52 | # route = "100.100.101.0" 53 | # mask = "255.255.255.0" 54 | # nh = "172.16.46.1" 55 | 56 | # [api] 57 | # host = "0.0.0.0:7080" # "host:port" to listen on (default: 0.0.0.0:7080) 58 | # users = [] # a comma seperated list of users authorized to query the API 59 | # # use utils/rvrrpd-pw to generate password lines 60 | # tls = true # enable SSL/TLS (HTTPS) support (default: false) 61 | # tls_key = "/etc/rvrrpd/ssl/key.pem" # RSA key file (PEM) 62 | # tls_cert = "/etc/rvrrpd/ssl/cert.pem" # X.509 certificate (PEM) 63 | -------------------------------------------------------------------------------- /conf/rvrrpd.json.conf: -------------------------------------------------------------------------------- 1 | { 2 | "debug": 5, 3 | "time_zone": "local", 4 | "time_format": "disabled", 5 | "pid": "/var/run/rvrrpd.pid", 6 | "working_dir": "/var/tmp", 7 | "main_log": "/var/log/rvrrpd.log", 8 | "error_log": "/var/log/rvrrpd-error.log", 9 | "vrouter": [ 10 | { 11 | "group": 2, 12 | "interface": "vmnet8", 13 | "vip": "10.0.2.1", 14 | "priority": 100, 15 | "preemption": false, 16 | "auth_type": null, 17 | "auth_secret": null, 18 | "timers": null, 19 | "rfc3768": null, 20 | "netdrv": "libnl", 21 | "iftype": "macvlan", 22 | "vifname": "vrrp0", 23 | "socket_filter": true, 24 | } 25 | ], 26 | "protocols": { 27 | "static": [ 28 | { 29 | "route": "100.100.100.0", 30 | "mask": "255.255.255.0", 31 | "nh": "10.2.2.1", 32 | "metric": 500, 33 | "mtu": 1500 34 | } 35 | ] 36 | } 37 | } -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/api/examples.rst: -------------------------------------------------------------------------------- 1 | .. _api_examples: 2 | 3 | Client API Queries Examples 4 | =========================== 5 | 6 | Getting Virtual Router States 7 | ----------------------------- 8 | Getting Started 9 | ^^^^^^^^^^^^^^^ 10 | You can get running information directly from an instance of **rVRRPd** using 11 | the HTTP Client API, but first you must authenticate using an HTTP ``POST`` 12 | request to the ``auth/`` path. 13 | 14 | Authenticating 15 | ^^^^^^^^^^^^^^ 16 | The below example shows how to authenticate to the daemon running on 17 | ``10.0.0.1``, using the ``curl`` utility: 18 | 19 | .. code-block:: console 20 | 21 | $ curl -k -c /tmp/rvrrpd-api-cookie -d "user=admin passwd=banana" -X POST https://10.0.0.1:7080/auth 22 | 23 | The above command will send an HTTP ``POST`` request to the API, and if 24 | successful will store the resulting session cookie to 25 | ``/tmp/rvrrpd-api-cookie``. 26 | 27 | Requesting VRRP Information 28 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 29 | Once authenticated, you can query the router for the current VRRP running 30 | information by sending an HTTP ``GET`` request to the ``run/vrrp`` resource 31 | path: 32 | 33 | .. code-block:: console 34 | 35 | $ curl -k -s -b /tmp/rvrrpd-api-cookie -X GET https://10.0.0.1:7080/run/vrrp | jq 36 | 37 | You should get a JSON formatted response like below: 38 | 39 | .. code-block:: json 40 | 41 | [ 42 | { 43 | "virtual_ip": "10.100.100.1", 44 | "group": 1, 45 | "interface": "standby1", 46 | "priority": 254, 47 | "preempt": true, 48 | "state": "Master" 49 | }, 50 | { 51 | "virtual_ip": "10.100.101.1", 52 | "group": 2, 53 | "interface": "standby2", 54 | "priority": 254, 55 | "preempt": true, 56 | "state": "Master" 57 | } 58 | ] 59 | 60 | -------------------------------------------------------------------------------- /docs/api/intro.rst: -------------------------------------------------------------------------------- 1 | .. _api-introduction: 2 | 3 | Introduction to the API 4 | ======================= 5 | **rVRRPd** provides an Application Programming Interface (API) that allow 6 | remote tasks to be performed on the daemon and on the running virtual 7 | routers. 8 | 9 | The Client API can be accessed by various means, but at this time of 10 | writing, only supports the Hypertext Transfer Protocol (HTTP), in 11 | plain-text or securely by using a SSL/TLS channel. The use of the 12 | latter is highly recommended for integrity and confidentiality 13 | purposes. 14 | 15 | RESTful HTTP Interface 16 | ---------------------- 17 | The Client API can be accessed over HTTP using the Representational 18 | State Transfer or REST model, which provides a simple and uniform 19 | access model to the various data coming from the daemon instance, 20 | the VRRP virtual routers, and from the operating system such as 21 | interfaces information and kernel routes. 22 | 23 | The API not only allow to read data and to parse it efficiently, 24 | but also to make modification to the running instance of **rVRRPd**, 25 | such as adding a new virtual router, or changing its priority so it 26 | can take over a Master router. 27 | 28 | .. note:: 29 | 30 | As of version 0.1.3, the Client API is only providing read-only 31 | access. Modifications are not yet supported but will be introduced 32 | in a later release. 33 | 34 | To query **rVRRPd** for information, such as the current role of a 35 | running VRRP virtual router, a simple HTTP GET request can be made 36 | to a specific resource path. If the query can be honored, the API 37 | will return a `JSON `_ formatted 38 | body response with all the attributes and values corresponding to 39 | your query. 40 | 41 | The responses can be easily and efficiently parsed by both a human 42 | and a machine, thus providing a uniform and standardize interface 43 | that can be used as a *console*, as an automation interface for 44 | SDN applications and much more. 45 | -------------------------------------------------------------------------------- /docs/api/reference.rst: -------------------------------------------------------------------------------- 1 | .. _api_ref: 2 | 3 | API Reference 4 | ============= 5 | 6 | .. todo:: 7 | The API is still under **active** development. The reference documentation will 8 | be available when the API will be stable and ready for production use. 9 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = u'rVRRPd' 23 | copyright = u'2020, Nicolas Chabbey' 24 | author = u'Nicolas Chabbey' 25 | 26 | # The short X.Y version 27 | version = u'' 28 | # The full version, including alpha/beta/rc tags 29 | release = u'' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.todo', 43 | 'sphinx.ext.autosectionlabel', 44 | 'sphinx_rtd_theme', 45 | ] 46 | 47 | # add support for cross-referencing documents 48 | autosectionlabel_prefix_document = True 49 | 50 | # Add any paths that contain templates here, relative to this directory. 51 | templates_path = ['_templates'] 52 | 53 | # The suffix(es) of source filenames. 54 | # You can specify multiple suffix as a list of string: 55 | # 56 | # source_suffix = ['.rst', '.md'] 57 | source_suffix = '.rst' 58 | 59 | # The master toctree document. 60 | master_doc = 'index' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This pattern also affects html_static_path and html_extra_path. 72 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = None 76 | 77 | 78 | # -- Options for HTML output ------------------------------------------------- 79 | # github 80 | html_context = { 81 | "display_github": True, # Integrate GitHub 82 | "github_user": "e3prom", # Username 83 | "github_repo": "rVRRPd", # Repo name 84 | "github_version": "master", # Version 85 | "conf_py_path": "/docs/", # Path in the checkout to the docs root 86 | } 87 | 88 | # The theme to use for HTML and HTML Help pages. See the documentation for 89 | # a list of builtin themes. 90 | # 91 | html_theme = 'sphinx_rtd_theme' 92 | 93 | # Theme options are theme-specific and customize the look and feel of a theme 94 | # further. For a list of options available for each theme, see the 95 | # documentation. 96 | # 97 | # html_theme_options = {} 98 | 99 | # Add any paths that contain custom static files (such as style sheets) here, 100 | # relative to this directory. They are copied after the builtin static files, 101 | # so a file named "default.css" will overwrite the builtin "default.css". 102 | html_static_path = ['_static'] 103 | 104 | # Custom sidebar templates, must be a dictionary that maps document names 105 | # to template names. 106 | # 107 | # The default sidebars (for documents that don't match any pattern) are 108 | # defined by theme itself. Builtin themes are using these templates by 109 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 110 | # 'searchbox.html']``. 111 | # 112 | # html_sidebars = {} 113 | 114 | 115 | # -- Options for HTMLHelp output --------------------------------------------- 116 | 117 | # Output file base name for HTML help builder. 118 | htmlhelp_basename = 'rVRRPddoc' 119 | 120 | 121 | # -- Options for LaTeX output ------------------------------------------------ 122 | 123 | latex_elements = { 124 | # The paper size ('letterpaper' or 'a4paper'). 125 | # 126 | # 'papersize': 'letterpaper', 127 | 128 | # The font size ('10pt', '11pt' or '12pt'). 129 | # 130 | # 'pointsize': '10pt', 131 | 132 | # Additional stuff for the LaTeX preamble. 133 | # 134 | # 'preamble': '', 135 | 136 | # Latex figure (float) alignment 137 | # 138 | # 'figure_align': 'htbp', 139 | } 140 | 141 | # Grouping the document tree into LaTeX files. List of tuples 142 | # (source start file, target name, title, 143 | # author, documentclass [howto, manual, or own class]). 144 | latex_documents = [ 145 | (master_doc, 'rVRRPd.tex', u'rVRRPd Documentation', 146 | u'Nicolas Chabbey', 'manual'), 147 | ] 148 | 149 | 150 | # -- Options for manual page output ------------------------------------------ 151 | 152 | # One entry per manual page. List of tuples 153 | # (source start file, name, description, authors, manual section). 154 | man_pages = [ 155 | (master_doc, 'rvrrpd', u'rVRRPd Documentation', 156 | [author], 1) 157 | ] 158 | 159 | 160 | # -- Options for Texinfo output ---------------------------------------------- 161 | 162 | # Grouping the document tree into Texinfo files. List of tuples 163 | # (source start file, target name, title, author, 164 | # dir menu entry, description, category) 165 | texinfo_documents = [ 166 | (master_doc, 'rVRRPd', u'rVRRPd Documentation', 167 | author, 'rVRRPd', 'rVRRPd is a fast, secure and multi-platform VRRP daemon.', 168 | 'Miscellaneous'), 169 | ] 170 | 171 | 172 | # -- Options for Epub output ------------------------------------------------- 173 | 174 | # Bibliographic Dublin Core info. 175 | epub_title = project 176 | 177 | # The unique identifier of the text. This can be a ISBN number 178 | # or the project homepage. 179 | # 180 | # epub_identifier = '' 181 | 182 | # A unique identification for the text. 183 | # 184 | # epub_uid = '' 185 | 186 | # A list of files that should not be packed into the epub file. 187 | epub_exclude_files = ['search.html'] 188 | 189 | 190 | # -- Extension configuration ------------------------------------------------- 191 | 192 | # -- Options for todo extension ---------------------------------------------- 193 | 194 | # If true, `todo` and `todoList` produce output, else they produce nothing. 195 | todo_include_todos = True 196 | -------------------------------------------------------------------------------- /docs/config/install.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | Install rVRRPd 4 | ============== 5 | 6 | **rVRRPd** can be installed from source or by using pre-compiled binaries 7 | packages. The latter is recommended for production uses, as the executables 8 | have been previously tested for stability. 9 | 10 | .. _requirements: 11 | 12 | Software Requirements 13 | --------------------- 14 | * The Linux or `FreeBSD `_ operating system (64 bits) 15 | * The `OpenSSL `_ library 16 | * The `Netlink Protocol Library Suite `_ library *(Linux)* 17 | 18 | Hardware Requirements 19 | --------------------- 20 | * An Intel IA-64 (x86_64) or ARMv8 (aarch64) processor 21 | * At least **one** Ethernet interface 22 | 23 | Source Installation 24 | ------------------- 25 | Getting Started 26 | ^^^^^^^^^^^^^^^ 27 | To install **rVRRPd** from source, first of all, make sure you have all the 28 | required build dependencies (see :ref:`dependencies` section below). 29 | 30 | Then download the source tarball files (tar.gz) from our `release `_ page 31 | or use `git `_ to clone the source repository. 32 | 33 | Below we will describe the step-by-step instructions on how to install 34 | a stable release of the daemon and its utilities: 35 | 36 | .. _dependencies: 37 | 38 | Building Dependencies 39 | ^^^^^^^^^^^^^^^^^^^^^ 40 | To build **rVRRPd** from source you must have several programs and libraries installed on your system (preferably system-wide): 41 | * Rust `Cargo `_ (v1.33.0 or later), \ 42 | to build the project and its related dependencies (crates). 43 | * The `OpenSSL `_ development headers 44 | * The `Netlink Protocol Library Suite `_ development headers *(Linux)* 45 | 46 | On `Debian `_ and derivatives, all three libraries' headers files can be installed with the below command: 47 | 48 | .. code-block:: console 49 | 50 | $ sudo apt-get install libnl-3-dev libnl-route-3-dev libssl-dev 51 | 52 | Cloning Source Repository 53 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 54 | We will now clone the source from our official `github repository `_: 55 | 56 | .. code-block:: console 57 | 58 | $ git clone https://github.com/e3prom/rvrrpd 59 | Cloning into 'rvrrpd'... 60 | remote: Enumerating objects: 16, done. 61 | remote: Counting objects: 100% (16/16), done. 62 | remote: Compressing objects: 100% (12/12), done. 63 | remote: Total 1301 (delta 4), reused 12 (delta 4), pack-reused 1285 64 | Receiving objects: 100% (1301/1301), 347.88 KiB | 0 bytes/s, done. 65 | Resolving deltas: 100% (831/831), done. 66 | 67 | Switching to Stable Release 68 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 69 | We move to the ``rvrrpd`` directory just created by git and we will 70 | switch to the latest stable release (here version ``0.1.3``): 71 | 72 | .. code-block:: console 73 | 74 | $ cd rvrrpd 75 | $ git checkout tags/0.1.3 76 | [...] 77 | 78 | Invoking the Build Process 79 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 80 | Enter the ``make`` command to start the build process. 81 | Rust Cargo will automatically fetch and build all the required dependencies and 82 | will start the build process of the **rVRRPd** daemon and related utilities 83 | such as ``rvrrpd-pw``: 84 | 85 | .. code-block:: console 86 | 87 | $ make 88 | Updating crates.io index 89 | [...] 90 | Compiling rVRRPd v0.1.3 (/var/tmp/rvrrpd) 91 | Finished release [optimized] target(s) in 2m 40s 92 | 93 | 94 | Once the build process is completed, you can find the daemon executable 95 | in ``target/release/rvrrpd``. The latter can be executed as-is or can be 96 | installed system-wide (recommended). 97 | 98 | Installing System-wide 99 | ^^^^^^^^^^^^^^^^^^^^^^ 100 | We will now install ``rvrrpd``, its accompanying configuration file 101 | ``/etc/rvrrpd.conf``, and the ``rvrrpd-pw`` utility in our system 102 | paths by using the ``make install`` command (requires root privileges): 103 | 104 | .. code-block:: console 105 | 106 | $ sudo make install 107 | make -C utils/rvrrpd-pw install 108 | make[1]: Entering directory 'utils/rvrrpd-pw' 109 | if [ ! -d /usr/bin ]; then \ 110 | mkdir -p /usr/bin; \ 111 | fi 112 | cp target/release/rvrrpd-pw /usr/bin/rvrrpd-pw 113 | chmod 755 /usr/bin/rvrrpd-pw 114 | make[1]: Leaving directory 'utils/rvrrpd-pw' 115 | if [ ! -d /usr/sbin ]; then \ 116 | mkdir -p /usr/sbin; \ 117 | fi 118 | cp target/release/rvrrpd /usr/sbin/rvrrpd 119 | chmod 755 /usr/sbin/rvrrpd 120 | if [ ! -d /etc/rvrrpd ]; then \ 121 | mkdir -p /etc/rvrrpd; \ 122 | fi 123 | 124 | Configuring 125 | ^^^^^^^^^^^ 126 | Prior to running the daemon, you must edit the main configuration file 127 | according to your network or high-availability environment. See 128 | :ref:`Configure ` below for a basic sample configuration 129 | example. 130 | 131 | Running 132 | ^^^^^^^ 133 | **rVRRPd** supports multiple operating modes: it can run in ``foreground`` 134 | mode from a terminal or in ``background`` mode as a standard Unix daemon, 135 | using the ``-m1`` and ``-m2`` switches, respectively. 136 | 137 | .. warning:: 138 | 139 | The daemon requires root privileges to run successfully. The daemon must 140 | have access to raw sockets, and to privileged kernel functions to create 141 | virtual interfaces, IP addresses and routes. 142 | 143 | In the below example, we are running the daemon in ``foreground`` mode 144 | using the ``-m1`` switch: 145 | 146 | .. code-block:: console 147 | 148 | $ sudo rvrrpd -m1 149 | 150 | 151 | Binary Package Installation 152 | --------------------------- 153 | **rVRRPd** could also be installed directly from binaries packages. 154 | This is the recommended way of installing the VRRP daemon for production uses 155 | as we are testing every executable for stability prior to shipping the 156 | releases to the public. 157 | 158 | Getting Binary Archives 159 | ^^^^^^^^^^^^^^^^^^^^^^^ 160 | Visit the official `release `_ page on github and download 161 | the latest package in ``tar.xz`` format. 162 | 163 | You can download directly from the command-line using the ``wget`` utility: 164 | 165 | .. code-block:: console 166 | 167 | $ wget "https://github.com/e3prom/rVRRPd/releases/download/0.1.3/rvrrpd-0.1.3-linux-amd64.tar.xz" 168 | 169 | Verifying the Archives Integrity 170 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 171 | Prior to unpacking the archive, we strongly suggest to verify the file checksum 172 | to ensure it has not be tempered by a third party. 173 | 174 | .. code-block:: console 175 | 176 | $ wget "https://github.com/e3prom/rVRRPd/releases/download/0.1.3/SHA256SUMS" 177 | $ sha256sum --check SHA256SUMS 178 | rvrrpd-0.1.3-linux-amd64.tar.xz: OK 179 | 180 | Unpacking Archives 181 | ^^^^^^^^^^^^^^^^^^ 182 | Untar the downloaded archive using ``tar``: 183 | 184 | .. code-block:: console 185 | 186 | $ tar -xvf rvrrpd-0.1.3-linux-amd64.tar.xz 187 | rvrrpd-0.1.3-linux-amd64/ 188 | rvrrpd-0.1.3-linux-amd64/README.md 189 | rvrrpd-0.1.3-linux-amd64/conf/ 190 | rvrrpd-0.1.3-linux-amd64/conf/rvrrpd.conf 191 | rvrrpd-0.1.3-linux-amd64/conf/rvrrpd.json.conf 192 | rvrrpd-0.1.3-linux-amd64/rvrrpd 193 | rvrrpd-0.1.3-linux-amd64/LICENSE 194 | 195 | Configuring 196 | ^^^^^^^^^^^^ 197 | Move into the release ``rvrrpd---/`` directory just 198 | created above: 199 | 200 | .. code-block:: console 201 | 202 | $ cd rvrrpd-0.1.3-linux-amd64/ 203 | 204 | :ref:`Edit ` the sample configuration file in 205 | ``etc/rvrrpd.conf`` and run the daemon from the current directory: 206 | 207 | Running 208 | ^^^^^^^ 209 | .. warning:: 210 | 211 | The daemon requires root privileges to run successfully. The daemon must 212 | have access to raw sockets, and to privileged kernel functions to create 213 | virtual interfaces, IP addresses and routes. 214 | 215 | .. code-block:: console 216 | 217 | $ sudo ./rvrrpd -m1 -c conf/rvrrpd.conf 218 | 219 | See our configuration reference for more information about the available 220 | configuration options. 221 | 222 | 223 | .. _config_example: 224 | 225 | Basic Configuration Example 226 | --------------------------- 227 | rVRRPd read its configuration file from the default ``/etc/rvrrpd.conf``. 228 | The later, must be configured to match your current network and 229 | high-availability configuration. You can also overwrite the config 230 | file path using the ``-c`` or ``--conf`` command-line switches. 231 | 232 | Below a sample TOML configuration file of a basic VRRP first-hop router: 233 | 234 | .. code-block:: toml 235 | :caption: rvrrpd.conf 236 | :linenos: 237 | 238 | debug = 5 239 | pid = "/var/tmp/rvrrpd.pid" 240 | working_dir = "/var/tmp" 241 | main_log = "/var/tmp/rvrrpd.log" 242 | error_log = "/var/tmp/rvrrpd-error.log" 243 | client_api = "http" 244 | 245 | [[vrouter]] 246 | group = 1 247 | interface = "ens192.900" 248 | vip = "10.100.100.1" 249 | priority = 254 250 | preemption = true 251 | rfc3768 = true 252 | netdrv = "libnl" 253 | iftype = "macvlan" 254 | vifname = "vrrp0" 255 | auth_type = "rfc2338-simple" 256 | auth_secret = "thissecretnolongeris" 257 | 258 | [protocols] 259 | [[protocols.static]] 260 | route = "0.0.0.0" 261 | mask = "0.0.0.0" 262 | nh = "10.240.0.254" 263 | 264 | [api] 265 | tls = false 266 | host = "0.0.0.0:7080" 267 | users = [ "{{SHA256}}admin:0:1eb7ac761a1201f9:095820af..." ] 268 | 269 | The above configuration do the following: 270 | * Starts the daemon in foreground mode with a debug level of ``5``\ 271 | (extensive). 272 | * Enable the Client API with the ``http`` listener \ 273 | (listen by default on ``tcp/7080``). 274 | * Runs one virtual-router with group id ``1`` on interface ``ens192.900``, \ 275 | with the below parameters: 276 | 277 | * Uses the virtual IP address ``10.100.100.1``. 278 | * Is configured with the highest priority of ``254``. 279 | * Has preemption enabled. 280 | * Has compatibility with `RFC3768 `_ turned on \ 281 | (may be required to fully interoperate with some equipment vendors). 282 | * Uses the network driver ``libnl`` which leverage the netlink protocol. \ 283 | Alternatively, you can use the ``ioctl`` driver, which is simpler but 284 | will removes the interface's IP addresse(s) for the VIP when in Master \ 285 | state. 286 | * Is configured for a ``macvlan`` type interface, \ 287 | a MAC-based virtual interface. 288 | * Name the child virtual interface ``vrrp0``, the latter will be used to \ 289 | hold the virtual router IP address. 290 | * Set authentication to the `RFC2338 `_ \ 291 | , ``Simple Password`` authentication method. 292 | * Set the secret key (or password) to be shared between the virtual routers. 293 | * When Master, install a static default route with a next-hop of \ 294 | ``10.240.0.254``. 295 | * The Client API only authorizes queries from the users listed in the \ 296 | ``users`` list under the ``[api]`` section. The users must \ 297 | authenticate prior to accessing the virtual router's information. 298 | 299 | * You can generate users passwords hashes using the \ 300 | `rvrrpd-pw `_ utility. 301 | 302 | You can consult our configuration guide to have more details and 303 | explanation about all the available configuration options. 304 | -------------------------------------------------------------------------------- /docs/config/intro.rst: -------------------------------------------------------------------------------- 1 | .. _config-introduction: 2 | 3 | Introduction 4 | ============ 5 | By default, **rVRRPd** reads the ``/etc/rvrrpd.conf`` configuration file. 6 | This file holds all the configuration elements needed for the proper operation 7 | of the daemon, the virtual routers, and their related functions. 8 | 9 | At this time of writing, both `TOML `_ (default) 10 | and the `JSON `_ formats are supported for 11 | the main configuration file. The former is usually simpler to understand and 12 | to write, greatly reducing human errors. JSON based configurations however, 13 | are harder to write and to parse for some people, but may be more practical 14 | when used with automation tools or with an HTTP based Application Programming 15 | Interface (API). 16 | 17 | If you don't know which configuration file format to use, we recommend to 18 | stick with `TOML `_, unless you want to 19 | use the Client API extensively. 20 | 21 | The **rVRRPd** daemon runs one ``virtual-router`` per ``interface, group`` 22 | pair, which means you can configure the same VRRP groups id or 23 | ``virtual-router`` id across several physical interfaces. The daemon can 24 | scale to hundreds if not thousands of active virtual-routers if the CPU 25 | and memory resources permit. 26 | 27 | The initial :ref:`developer ` of **rVRRPd** has chosen to build the daemon 28 | entirely using the `Rust `_ programming language. 29 | Rust is a language, aimed primarily at security and speed. You get all the 30 | benefits of a modern object-oriented programming language such as Java or C++, 31 | without their respective performance penalty and inherent security risks. 32 | 33 | We tried to keep ``unsafe`` blocks as small as possible in order to provides 34 | a clean interface to unsafe functions. However, we cannot removes all of them 35 | as they are necessary to implement functions calls to the standard C library, 36 | and to the various interfaces (such as IOCTLs) to the operating system kernel. 37 | 38 | We hope that you will enjoy running **rVRRPd** and you would be able to solve 39 | your current network and high-availability challenges in less time and thus 40 | without the hassles commonly found in commercial solutions. 41 | 42 | This project wouldn't be live without the dedication of its developers, and 43 | the open source community. Any `contributions `_ 44 | are greatly welcome, and will help us developing new features and a more 45 | stable and secure implementation. 46 | 47 | .. _developers: 48 | 49 | Developpers 50 | ^^^^^^^^^^^ 51 | * Nicolas Chabbey 52 | 53 | * Keybase: `@e3prom `_ 54 | * PGP Public Key Fingerprint: \ 55 | ``DBD4 3BD8 81F3 C3E2 37E1 9E54 D7FF 004E 2E22 CF1C`` 56 | 57 | Sponsorship 58 | ^^^^^^^^^^^ 59 | You can help us directly by donating to the project. 60 | 61 | Every single penny will cover the development cost of **rVRRPd**, which is 62 | comprised of a lot of coffee, and the power bill of the bare-metal servers 63 | running the interoperability and testing labs. 64 | 65 | You can donate by Paypal, or by using a crypto-currency listed below: 66 | 67 | .. image:: https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif 68 | :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=TWE8MESRMWRG8 69 | 70 | +---------------------+--------------------------------------------+ 71 | | Crypto Currency | Wallet Address | 72 | +=====================+============================================+ 73 | | Bitcoin (BTC) | 3Pz7PQk5crAABg2MsR6PVfUxGzq2MmPd2i | 74 | +---------------------+--------------------------------------------+ 75 | | Etherum (ETH) | 0x0686Dd4474dAA1181Fc3391035d22C8e0D2dA058 | 76 | +---------------------+--------------------------------------------+ 77 | 78 | Software License 79 | ^^^^^^^^^^^^^^^^ 80 | .. include:: ../../LICENSE 81 | :literal: 82 | -------------------------------------------------------------------------------- /docs/config/reference.rst: -------------------------------------------------------------------------------- 1 | .. _config_reference: 2 | 3 | Configuration Reference 4 | ======================= 5 | 6 | General Directives 7 | ------------------ 8 | 9 | debug 10 | ^^^^^ 11 | :Description: The verbose or debugging level. 12 | :Value type: Decimal 13 | :Default: 0 14 | 15 | The ``debug`` directive sets the debugging (or verbosity) level 16 | of the daemon. 17 | 18 | Possible values are: 19 | * ``0`` Information 20 | * ``1`` Low 21 | * ``2`` Medium 22 | * ``3`` High 23 | * ``5`` Extensive 24 | 25 | time_zone 26 | ^^^^^^^^^ 27 | :Description: The timestamps reference time zone 28 | :Value type: String 29 | :Default: local 30 | 31 | The ``time_zone`` directive sets the reference time zone for 32 | the various daemon timestamps. 33 | 34 | Possible values are: 35 | * ``local`` for Local Time (LT) 36 | This setting uses the locally configured time zone of the 37 | operating system. 38 | * ``utc`` for Coordinated Universal Time (UTC) 39 | Timestamps will be given in UTC or Zulu time. 40 | 41 | time_format 42 | ^^^^^^^^^^^ 43 | :Description: The timestamps time format 44 | :Value type: String 45 | :Default: disabled 46 | 47 | The ``time_format`` directive sets the reference time format for 48 | the various daemon timestamps. 49 | 50 | Possible values are: 51 | * ``disabled`` for no particular time format (use the default 52 | time format) 53 | * ``short`` for a shortened, more concise time format 54 | * ``rfc2822`` for the standard `RFC2822 `_, Internet Time Format 55 | 56 | pid 57 | ^^^ 58 | :Description: The daemon's PID file path 59 | :Value type: String 60 | :Default: /var/run/rvrrpd.pid 61 | 62 | The ``pid`` directive sets the full or relative path to the daemon's 63 | PID file. 64 | 65 | working_dir 66 | ^^^^^^^^^^^ 67 | :Description: The daemon's working directory 68 | :Value type: String 69 | :Default: /tmp 70 | 71 | The ``working_dir`` directive sets the daemon's working directory. 72 | The daemon's user must have read access to this directory. 73 | 74 | main_log 75 | ^^^^^^^^ 76 | :Description: Path to the daemon's main log file 77 | :Value type: String 78 | :Default: /var/log/rvrrpd.log 79 | 80 | The ``main_log`` directive sets the path to the daemon's main log file. 81 | 82 | error_log 83 | ^^^^^^^^^ 84 | :Description: Path to the daemon's error log file 85 | :Value type: String 86 | :Default: disabled 87 | 88 | The ``error_log`` directive sets the path to the daemon's error log file. 89 | Any errors occuring during the runtime are written to this log file. 90 | 91 | .. _client-api: 92 | 93 | client_api 94 | ^^^^^^^^^^ 95 | :Description: Client API interface type 96 | :Value type: String 97 | :Default: http 98 | 99 | The ``client_api`` directive sets the Client API interface type. 100 | 101 | Possible values are: 102 | * ``http`` for the RESTful HTTP interface 103 | This value enable a plain-text HTTP or HTTPS (SSL/TLS) interface 104 | to the client API. It does include user authentication and 105 | a secure communication channel when SSL/TLS is enabled. 106 | 107 | .. versionadded:: 0.1.3 108 | 109 | Directive added with Client API Support 110 | 111 | 112 | Virtual Routers Directives 113 | -------------------------- 114 | 115 | group 116 | ^^^^^^ 117 | :Description: Virtual Router Group ID (VRID) 118 | :Value type: Integer 119 | :Default: *none* 120 | 121 | The ``group`` directive sets the VRRP group id or virtual-router id (VRID). 122 | 123 | Valid values are: 124 | * ``0-255`` The VRRP group id or virtual-router id. 125 | Usually matches the sub-interface unit number or 126 | interface's vlan id. 127 | 128 | interface 129 | ^^^^^^^^^ 130 | :Description: Interface to run VRRP on 131 | :Value type: String 132 | :Default: *none* 133 | 134 | The ``interface`` directive sets the VRRP virtual-router's interface. 135 | Only Ethernet interfaces are supported. 136 | 137 | .. _if_type: 138 | 139 | iftype 140 | ^^^^^^ 141 | :Description: Interface type 142 | :Value type: String 143 | :Default: *none* 144 | 145 | The ``iftype`` directive sets the VRRP virtual-router's interface type. 146 | By default, the daemon will directly work with the configured running 147 | interface, and therefore may change its IP and/or MAC address(es). 148 | 149 | Valid values are: 150 | * ``macvlan`` Use a MAC-Based Virtual LAN interface. 151 | 152 | .. versionadded:: 0.1.1 153 | 154 | Directive added with MAC-Based Virtual LAN Interface Support 155 | 156 | vip 157 | ^^^ 158 | :Description: Virtual IP Address 159 | :Value type: String 160 | :Default: *none* 161 | 162 | The ``vip`` directive sets the VRRP standby address or virtual-router 163 | address. Only IPv4 addresses are currently supported at this time. 164 | 165 | priority 166 | ^^^^^^^^ 167 | :Description: Virtual Router Priority 168 | :Value type: Integer 169 | :Default: 100 170 | 171 | The ``priority`` directive sets the virtual-router VRRP priority. 172 | 173 | Valid values are: 174 | * ``1-254`` The VRRP virtual router priority. Values 0 and 255 175 | are reserved as per `RFC3768 `_ 176 | and cannot be configured manually. 177 | 178 | preemption 179 | ^^^^^^^^^^ 180 | :Description: Preemption Support 181 | :Value type: Boolean 182 | :Default: false 183 | 184 | The ``preemption`` directive sets if preemption is enabled. By default, 185 | preemption is turned off; a higher-priority virtual router cannot preempt 186 | an active Master. 187 | 188 | Valid values are: 189 | * ``true`` Preemption is turned on, a higher-priority Standby 190 | virtual router can preempt the current Master virtual router. 191 | * ``false`` Preemption is turned off. 192 | 193 | .. _auth_type: 194 | 195 | auth_type 196 | ^^^^^^^^^ 197 | :Description: Authentication Type 198 | :Value type: String 199 | :Default: *none* 200 | 201 | The ``auth_type`` directive sets the VRRP group's authentication type. 202 | Authentication allow to authenticate VRRP messages and with some types 203 | allow to verify their integrity. Authentication can prevent a 204 | misconfigured VRRP virtual router to take over the Master, resulting 205 | in the blackhole or interception of the user network traffic. 206 | 207 | Valid values are: 208 | * ``rfc2338-simple`` for `RFC2338 `_ 209 | Simple Password Authentication. 210 | * ``p0-t8-sha256`` for proprietary P0 Authentication. Uses a 211 | SHA256 HMAC of the VRRP messages. This type provides both messages 212 | authentication and integrity. 213 | * ``p1-t8-shake256`` for proprietary P1 Authentication. Uses the 214 | SHAKE256 Extendable-Output Function (XOF). This type provides both 215 | messages authentication and integrity. 216 | 217 | auth_secret 218 | ^^^^^^^^^^^ 219 | :Description: Authentication Secret 220 | :Value type: String 221 | :Default: *none* 222 | 223 | The ``auth_secret`` directive sets the VRRP group's authentication secret 224 | or password. Ensure all virtual routers among the configured group share 225 | the same secret and that the latter has been transmitted securely. 226 | 227 | .. warning:: 228 | 229 | Keep in mind that the configuration file holds the secret, therefore 230 | only authorized users should be able to read it. 231 | 232 | rfc3768 233 | ^^^^^^^ 234 | :Description: RFC3768 Compatibility Warning Flag 235 | :Value type: Boolean 236 | :Default: true 237 | 238 | The ``rfc3768`` directive allow you to force the compatibility flag. 239 | The meaning of this flag may be confusing, and can be safely ignored 240 | most of the time. When this flag is set to ``true``, it indicates 241 | the virtual router may **NOT** operates entirely according to the 242 | applicable VRRP RFCs. In particular regarding to the authentication 243 | and to the length of some VRRP PDUs header fields. When this flag is 244 | ``true``, the virtual router may not be interoperable with 245 | third-party, standard-compliant devices or softwares. 246 | 247 | .. note:: 248 | 249 | Enabling proprietary features such as the proprietary authentication 250 | types, will automatically turn this flag on. 251 | 252 | Valid values are: 253 | * ``true`` to forcibly enable non-standard operations. 254 | * ``false`` to forcibly disable non-standard operations whenever 255 | possible. 256 | 257 | netdrv 258 | ^^^^^^ 259 | :Description: Network Driver 260 | :Value type: String 261 | :Default: ioctl 262 | 263 | The ``netdrv`` directive specify which network driver to uses for the 264 | virtual-router. The available drivers depend on the operating system 265 | and slight differences do exists between them. The driver is used 266 | partially or entirely to; add the virtual IP addresses, create the 267 | virtual interface, change the interface's MAC address, or to update 268 | the kernel routes. 269 | 270 | Valid values are: 271 | * ``ioctl`` for using IOCTLs. This option should be supported in 272 | all Linux based operating systems, even with the presence of an 273 | old kernel. 274 | * ``libnl`` for using the `Netlink Protocol Library `_ 275 | which is an intermediate API to communicate with the Linux 276 | Netlink protocol. The latter is a modern and robust way 277 | of configuring and interrogating the kernel. 278 | 279 | .. note:: 280 | 281 | We strongly suggest to keep using this driver whenever possible. 282 | When using ``macvlan`` interfaces, this driver is automatically 283 | enabled. 284 | 285 | vifname 286 | ^^^^^^^^ 287 | :Description: Virtual Interface Name 288 | :Value type: String 289 | :Default: standby\<*group-id*\> 290 | 291 | The ``vifname`` directive sets the virtual-router's virtual interface name. 292 | By default, the virtual interface is named using the ``standby`` 293 | format, where ``group-id`` correspond to the virtual-router's VRRP group 294 | id or VRID. 295 | 296 | .. note:: 297 | 298 | This directive is only used when virtual interface support is activated. 299 | (e,g. by having the :ref:`iftype ` directive set to ``macvlan``). 300 | 301 | .. versionadded:: 0.1.1 302 | 303 | Directive added with MAC-Based Virtual LAN Interface Support 304 | 305 | socket_filter 306 | ^^^^^^^^^^^^^ 307 | :Description: Socket Filter Support 308 | :Value type: String 309 | :Default: true 310 | 311 | The ``socket_filter`` directive allow you to enable or disable the 312 | use of Socket Filters. On Linux, eBPF based Socket Filters allow 313 | every virtual-router raw sockets to only receives VRRP traffic 314 | matching their interface and VRRP group, thus greatly improving 315 | performance. 316 | 317 | Valid values are: 318 | * ``true`` for enabling support for socket filters. Drastically 319 | improves the listener threads performance by allowing the 320 | kernel to filter out unwanted traffic not to be processed by 321 | the listening thread. 322 | * ``false`` for disabling support for socket filters. 323 | 324 | .. versionadded:: 0.1.2 325 | 326 | Directive added with Linux Socket Filters Support 327 | 328 | 329 | API Directives 330 | -------------- 331 | 332 | users 333 | ^^^^^ 334 | :Description: API Users 335 | :Value type: List of Strings 336 | :Default: *none* 337 | 338 | The ``users`` directive lists the user accounts authorized for the 339 | Client API. Every string in the list must adhere to strict formatting 340 | rules and can be easily generated using the ``rvrrpd-pw`` utility. 341 | 342 | secret 343 | ^^^^^^ 344 | :Description: API Secret 345 | :Value type: String 346 | :Default: 128-bits random number 347 | 348 | The ``secret`` directive sets the API secret. This secret is used for 349 | a number of cryptogrphic functions and must be kept secret. 350 | 351 | By default, at every start of the daemon, a random 128 bits unsigned 352 | integer is generated from a secure PRNG. This number is large enough 353 | and *SHOULD* have sufficient entropy to provides good security. 354 | 355 | You can overwrite this secret by specifiy your own. The secret will 356 | be maintained across restart of the *rVRRPd* daemon. 357 | 358 | .. warning:: 359 | Improper setting of the secret string can open up vulnerabilities 360 | or security holes, such as authentication bypass. 361 | 362 | .. note:: 363 | If setting the secret manually, please ensure your string is long 364 | and random enough to provides *sufficient* security. We strongly 365 | recommend to use a random number generator to generate it. 366 | 367 | host 368 | ^^^^ 369 | :Description: Listening Host 370 | :Value type: String 371 | :Default: 0.0.0.0:7080 372 | 373 | The ``host`` directive sets the IP address(es) and port for the 374 | API interface to listen on. By default it listens on all interfaces 375 | on port ``7080``. 376 | 377 | When setting the Client API Interface to ``http`` this directive will 378 | specify which interfaces and port the HTTP or HTTPS service will 379 | listen on. 380 | 381 | tls 382 | ^^^ 383 | :Description: Transport Layer Security (TLS) Support 384 | :Value type: Boolean 385 | :Default: false 386 | 387 | The ``tls`` directive allow you to enable or disable support for 388 | SSL/TLS. When using the ``http`` :ref:`Client API Interface `, 389 | it will allow you to enable secure HTTPS communication with the 390 | API clients. 391 | 392 | Valid values are: 393 | * ``true`` for activating Transport Layer Security (TLS) on 394 | the API interface. 395 | * ``false`` for disabling the TLS support. 396 | 397 | tls_key 398 | ^^^^^^^ 399 | :Description: SSL/TLS Key File 400 | :Value type: String 401 | :Default: /etc/rvrrpd/ssl/key.pem 402 | 403 | The ``tls_key`` directive allow you to set the full or relative path 404 | to the TLS key file. 405 | 406 | tls_cert 407 | ^^^^^^^^ 408 | :Description: SSL/TLS Certificate File 409 | :Value type: String 410 | :Default: /etc/rvrrpd/ssl/cert.pem 411 | 412 | The ``tls_key`` directive allow you to set the full or relative path 413 | to the certificate chain file. At this time of writting, only a 414 | valid X.509 server's certificate is necessary. 415 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. rVRRPd documentation master file, created by 2 | sphinx-quickstart on Fri Jan 3 13:40:25 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to rVRRPd's documentation! 7 | ================================== 8 | **rVRRPd** is a fast, secure and standard compliant implementation 9 | of the high-availability VRRP protocol. It is very scalable, and can 10 | run on multiple platforms and operating systems. 11 | 12 | As its name implies, **rVRRPd** can run as a Unix daemon or 13 | as a standalone program. It can also exposes a RESTful API for 14 | monitoring and configuration purposes, enabling Software Defined 15 | Networking (SDN) applications. 16 | 17 | Features 18 | -------- 19 | rVRRPd supports a number of innovative features: 20 | - Secure software architecture leveraging the `Rust `_ programming language 21 | - Highly scalable; up to several hundreds of concurrent VRRP groups 22 | - Supports standard `RFC3768 `_ and \ 23 | `RFC2338 `_, ``Simple Password`` authentication 24 | - Supports additional :ref:`proprietary authentication 25 | ` methods 26 | - Supports multiple operating systems and processors architectures 27 | - Provides a RESTful Client Application Programming Interface (API) 28 | - Provides a plain-text HTTP or SSL/TLS HTTPS interface to the Client API 29 | - Leverages additional features such as ``macvlan`` and \ 30 | ``Linux Socket Filters`` 31 | 32 | Features Support Matrix 33 | ^^^^^^^^^^^^^^^^^^^^^^^ 34 | +-----------------------------------------------+-------+---------+ 35 | | Supported Features | Linux | FreeBSD | 36 | +===============================================+=======+=========+ 37 | | Multiple Listeners Threads | Yes | Yes | 38 | +-----------------------------------------------+-------+---------+ 39 | | RESTful Client API | Yes | Yes | 40 | +-----------------------------------------------+-------+---------+ 41 | | Socket Filters (eBPF) | Yes | No | 42 | +-----------------------------------------------+-------+---------+ 43 | | MAC-Based Virtual LAN Interface (``macvlan``) | Yes | No | 44 | +-----------------------------------------------+-------+---------+ 45 | | Static Routing | Yes | No | 46 | +-----------------------------------------------+-------+---------+ 47 | 48 | Configuration Guide 49 | ------------------- 50 | This part of the documentation focuses on the step-by-step installation 51 | instructions of the daemon and on how to configure the latter for various 52 | network and high-availability scenarios. 53 | 54 | .. toctree:: 55 | :maxdepth: 2 56 | 57 | config/intro 58 | config/install 59 | config/reference 60 | 61 | Client API Guide 62 | ---------------- 63 | This guide covers the Client Application Programming Interface (API), how to 64 | configure it, how to make requests and interprets their various 65 | responses. 66 | 67 | .. toctree:: 68 | :maxdepth: 2 69 | 70 | api/intro 71 | api/reference 72 | api/examples 73 | 74 | Additional resources 75 | -------------------- 76 | * `Github Repository `_ 77 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/auth/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - authentication handlers 2 | use super::*; 3 | 4 | // futures 5 | use futures::{future, Future, Stream}; 6 | 7 | // gotham 8 | use gotham::handler::{HandlerFuture, IntoHandlerError}; 9 | 10 | // regex 11 | extern crate regex; 12 | use regex::Regex; 13 | 14 | /// client() handler function 15 | pub fn client(mut state: State) -> Box { 16 | // extract authentication information from HTTP POST 17 | let f = Body::take_from(&mut state) 18 | .concat2() 19 | .then(|body| match body { 20 | Ok(valid_body) => { 21 | // extract body content 22 | let content = String::from_utf8(valid_body.to_vec()).unwrap(); 23 | // authenticate the user to the Client API 24 | let down = DownstreamAPI::borrow_from(&state); 25 | let q = match regex_captures_authav(&content) { 26 | // if the regexp captured the user/passwd attributes 27 | Some(c) => ClientAPIQuery::AuthRequest( 28 | c.get(1).unwrap().as_str().to_string(), 29 | c.get(2).unwrap().as_str().to_string(), 30 | ), 31 | // if not, return an error 32 | None => { 33 | let resp = create_empty_response(&state, StatusCode::BAD_REQUEST); 34 | return future::ok((state, resp)); 35 | } 36 | }; 37 | // send authentication request 38 | down.query(q); 39 | 40 | // read downstream API channel 41 | match down.read() { 42 | // read authentication response 43 | ClientAPIResponse::AuthResponse(sess) => { 44 | match sess { 45 | // authentication succeeded 46 | Some(st) => { 47 | // build cookies 48 | let mut cookie_user = 49 | Cookie::build(COOKIE_USER, st.user()).http_only(true); 50 | let mut cookie_ts = 51 | Cookie::build(COOKIE_TIMESTAMP, st.ts_since()).http_only(true); 52 | let mut cookie_nonce = 53 | Cookie::build(COOKIE_NONCE, st.nonce()).http_only(true); 54 | let mut cookie_token = 55 | Cookie::build(COOKIE_TOKEN, st.token()).http_only(true); 56 | // add 'secure' attribute if TLS is enabled 57 | if st.secure() { 58 | cookie_user = cookie_user.secure(true); 59 | cookie_ts = cookie_ts.secure(true); 60 | cookie_nonce = cookie_nonce.secure(true); 61 | cookie_token = cookie_token.secure(true); 62 | } 63 | // add 'SameSite' attribute and finish the cookies 64 | let cookie_user = cookie_user.same_site(SameSite::Strict).finish(); 65 | let cookie_ts = cookie_ts.same_site(SameSite::Strict).finish(); 66 | let cookie_nonce = 67 | cookie_nonce.same_site(SameSite::Strict).finish(); 68 | let cookie_token = 69 | cookie_token.same_site(SameSite::Strict).finish(); 70 | // create an empty response 71 | let mut resp = create_empty_response(&state, StatusCode::OK); 72 | // append cookies to response 73 | resp.headers_mut() 74 | .append(SET_COOKIE, cookie_user.to_string().parse().unwrap()); 75 | resp.headers_mut() 76 | .append(SET_COOKIE, cookie_ts.to_string().parse().unwrap()); 77 | resp.headers_mut() 78 | .append(SET_COOKIE, cookie_nonce.to_string().parse().unwrap()); 79 | resp.headers_mut() 80 | .append(SET_COOKIE, cookie_token.to_string().parse().unwrap()); 81 | // return future 82 | future::ok((state, resp)) 83 | } 84 | // no token has been issued 85 | None => { 86 | let resp = create_empty_response(&state, StatusCode::UNAUTHORIZED); 87 | return future::ok((state, resp)); 88 | } 89 | } 90 | } 91 | // other response types are considered invalid 92 | _ => { 93 | let resp = create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR); 94 | return future::ok((state, resp)); 95 | } 96 | } 97 | } 98 | Err(e) => future::err((state, e.into_handler_error())), 99 | }); 100 | 101 | Box::new(f) 102 | } 103 | 104 | /// regex_captures_authav() function 105 | /// creates a globally accessible and static compiled regular expression 106 | fn regex_captures_authav(content: &String) -> Option { 107 | // only allow alphanumeric user strings with at least one character, up to 64 108 | // allow passwords with less than 256 printable characters (is not reflected in Cookies) 109 | lazy_static! { 110 | static ref REGEX_HTBODY_AUTH_AV: Regex = 111 | Regex::new(r"^user=(?P[[:alnum:]]{1,64}) passwd=(?P

[[:print:]]{1,256})$") 112 | .unwrap(); 113 | } 114 | // capture content using the pre-compiled regular expression 115 | REGEX_HTBODY_AUTH_AV.captures(content) 116 | } 117 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/config/global/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - global configuration handlers 2 | use super::*; 3 | 4 | /// all() handler function 5 | pub fn all(state: State) -> (State, Response) { 6 | // borrow references to the Downstream API 7 | let down = DownstreamAPI::borrow_from(&state); 8 | 9 | // retrieve session cookie 10 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 11 | 12 | // create SessionToken 13 | let mut sess = SessionToken::new(); 14 | sess.set_user(user); 15 | sess.set_tssince(ts_since); 16 | sess.set_nonce(nonce); 17 | sess.set_token(token); 18 | 19 | // send a query downstream 20 | let q = ClientAPIQuery::CfgGlobalAll(sess); 21 | down.query(q); 22 | 23 | // read answer and set HTTP body (blocking) 24 | let htbody = { 25 | match down.read() { 26 | // if a response is returned 27 | ClientAPIResponse::CfgGlobalAll(ans) => serialize_answer(&state, ans), 28 | ClientAPIResponse::Unauthorized => { 29 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 30 | } 31 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 32 | } 33 | }; 34 | return (state, htbody); 35 | } 36 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/config/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - global router handlers 2 | use super::*; 3 | 4 | // global configuration handlers 5 | pub mod global; 6 | 7 | // virtual routers configuration handlers 8 | pub mod vrouter; 9 | 10 | // protocols configuration handlers 11 | pub mod protocols; 12 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/config/protocols/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - protocols configuration handlers 2 | use super::*; 3 | 4 | /// all() handler function 5 | pub fn all(state: State) -> (State, Response) { 6 | // borrow references to the Downstream API 7 | let down = DownstreamAPI::borrow_from(&state); 8 | 9 | // retrieve session cookie 10 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 11 | 12 | // create SessionToken 13 | let mut sess = SessionToken::new(); 14 | sess.set_user(user); 15 | sess.set_tssince(ts_since); 16 | sess.set_nonce(nonce); 17 | sess.set_token(token); 18 | 19 | // send a query downstream 20 | let q = ClientAPIQuery::CfgProtoAll(sess); 21 | down.query(q); 22 | 23 | // read answer (blocking) 24 | let htbody = { 25 | match down.read() { 26 | // if a response is returned 27 | ClientAPIResponse::CfgProtoAll(ans) => serialize_answer(&state, ans), 28 | ClientAPIResponse::Unauthorized => { 29 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 30 | } 31 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 32 | } 33 | }; 34 | return (state, htbody); 35 | } 36 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/config/vrouter/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - virtual routers configuration handlers 2 | use super::*; 3 | 4 | /// all() handler function 5 | pub fn all(state: State) -> (State, Response) { 6 | // borrow references to the Downstream API 7 | let down = DownstreamAPI::borrow_from(&state); 8 | 9 | // retrieve session cookie 10 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 11 | 12 | // create SessionToken 13 | let mut sess = SessionToken::new(); 14 | sess.set_user(user); 15 | sess.set_tssince(ts_since); 16 | sess.set_nonce(nonce); 17 | sess.set_token(token); 18 | 19 | // send a query downstream 20 | let q = ClientAPIQuery::CfgVrrpAll(sess); 21 | down.query(q); 22 | 23 | // read answer (blocking) 24 | let htbody = { 25 | match down.read() { 26 | // if a response is returned 27 | ClientAPIResponse::CfgVrrpAll(ans) => serialize_answer(&state, ans), 28 | ClientAPIResponse::Unauthorized => { 29 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 30 | } 31 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 32 | } 33 | }; 34 | return (state, htbody); 35 | } 36 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API router handlers module 2 | use super::*; 3 | 4 | // gotham 5 | use gotham::state::State; 6 | 7 | // authentication scope handlers 8 | pub mod auth; 9 | 10 | // configuration scope handlers 11 | pub mod config; 12 | 13 | // running config scope handlers 14 | pub mod run; 15 | 16 | // index() function 17 | pub fn index(state: State) -> (State, Response) { 18 | let body = r#" 19 | 20 | 21 | 22 | 23 | rVRRPd Client API 24 | 25 | 26 |

Welcome to the rVRRPd Client API HTTP Interface

27 |

If you see this page, it means the API processes requests from you.

28 |

However, you must first authenticate by sending a POST request 29 | with a valid user and password key = value pair.

30 |

Once authenticated, you will receive a cookie to be used for your further 31 | resources requests.

32 |

33 | 34 | 35 | "#; 36 | // HTTP body 37 | let htbody = { create_response(&state, StatusCode::OK, mime::TEXT_HTML, body) }; 38 | return (state, htbody); 39 | } 40 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/run/global/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - running global configuration handlers 2 | use super::*; 3 | 4 | /// all() handler function 5 | pub fn all(state: State) -> (State, Response) { 6 | // borrow references to the Downstream API 7 | let down = DownstreamAPI::borrow_from(&state); 8 | 9 | // retrieve session cookie 10 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 11 | 12 | // create SessionToken 13 | let mut sess = SessionToken::new(); 14 | sess.set_user(user); 15 | sess.set_tssince(ts_since); 16 | sess.set_nonce(nonce); 17 | sess.set_token(token); 18 | 19 | // send a query downstream 20 | let q = ClientAPIQuery::RunGlobalAll(sess); 21 | down.query(q); 22 | 23 | // read answer and set HTTP body (blocking) 24 | let htbody = { 25 | match down.read() { 26 | // if a response is returned 27 | ClientAPIResponse::RunGlobalAll(ans) => serialize_answer(&state, ans), 28 | ClientAPIResponse::Unauthorized => { 29 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 30 | } 31 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 32 | } 33 | }; 34 | return (state, htbody); 35 | } 36 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/run/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API router handlers module 2 | use super::*; 3 | 4 | // global running config 5 | pub mod global; 6 | 7 | // vrrp running config 8 | pub mod vrrp; 9 | 10 | // protocols running config 11 | pub mod protocols; 12 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/run/protocols/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - protocols running configuration handlers 2 | use super::*; 3 | 4 | /// all() handler function 5 | pub fn all(state: State) -> (State, Response) { 6 | // borrow references to the Downstream API 7 | let down = DownstreamAPI::borrow_from(&state); 8 | 9 | // retrieve session cookie 10 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 11 | 12 | // create SessionToken 13 | let mut sess = SessionToken::new(); 14 | sess.set_user(user); 15 | sess.set_tssince(ts_since); 16 | sess.set_nonce(nonce); 17 | sess.set_token(token); 18 | 19 | // send a query downstream 20 | let q = ClientAPIQuery::RunProtoAll(sess); 21 | down.query(q); 22 | 23 | // read answer and set HTTP body (blocking) 24 | let htbody = { 25 | match down.read() { 26 | // if a response is returned 27 | ClientAPIResponse::RunProtoAll(ans) => serialize_answer(&state, ans), 28 | ClientAPIResponse::Unauthorized => { 29 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 30 | } 31 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 32 | } 33 | }; 34 | return (state, htbody); 35 | } 36 | 37 | // pstatic() handler function 38 | pub fn pstatic(state: State) -> (State, Response) { 39 | // borrow references to the Downstream API 40 | let down = DownstreamAPI::borrow_from(&state); 41 | 42 | // retrieve session cookie 43 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 44 | 45 | // create SessionToken 46 | let mut sess = SessionToken::new(); 47 | sess.set_user(user); 48 | sess.set_tssince(ts_since); 49 | sess.set_nonce(nonce); 50 | sess.set_token(token); 51 | 52 | // send a query downstream 53 | let sess = SessionToken::new(); 54 | // send a query downstream 55 | let q = ClientAPIQuery::RunProtoStatic(sess); 56 | down.query(q); 57 | 58 | // read answer and set HTTP body (blocking) 59 | let htbody = { 60 | match down.read() { 61 | // if a response is returned 62 | ClientAPIResponse::RunProtoStatic(ans) => serialize_answer(&state, ans), 63 | ClientAPIResponse::Unauthorized => { 64 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 65 | } 66 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 67 | } 68 | }; 69 | return (state, htbody); 70 | } 71 | -------------------------------------------------------------------------------- /src/api/client/router/handlers/run/vrrp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - VRRP running configuration handlers 2 | use super::*; 3 | 4 | // all() handler function 5 | pub fn all(state: State) -> (State, Response) { 6 | // borrow references to the Downstream API 7 | let down = DownstreamAPI::borrow_from(&state); 8 | 9 | // retrieve session cookie 10 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 11 | 12 | // create SessionToken 13 | let mut sess = SessionToken::new(); 14 | sess.set_user(user); 15 | sess.set_tssince(ts_since); 16 | sess.set_nonce(nonce); 17 | sess.set_token(token); 18 | 19 | // send a query downstream 20 | let q = ClientAPIQuery::RunVRRPAll(sess); 21 | down.query(q); 22 | 23 | // read answer and set HTTP body (blocking) 24 | let htbody = { 25 | match down.read() { 26 | // if a response is returned 27 | ClientAPIResponse::RunVRRPAll(ans) => serialize_answer(&state, ans), 28 | ClientAPIResponse::Unauthorized => { 29 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 30 | } 31 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 32 | } 33 | }; 34 | return (state, htbody); 35 | } 36 | 37 | // group() handler function 38 | pub fn group(state: State) -> (State, Response) { 39 | // borrow references to the Downstream API 40 | let down = DownstreamAPI::borrow_from(&state); 41 | 42 | // retrieve session cookie 43 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 44 | 45 | // create SessionToken 46 | let mut sess = SessionToken::new(); 47 | sess.set_user(user); 48 | sess.set_tssince(ts_since); 49 | sess.set_nonce(nonce); 50 | sess.set_token(token); 51 | 52 | // extract group_id from GET path 53 | let path = GroupIdExtractor::borrow_from(&state); 54 | let gid = path.group_id; 55 | 56 | // send a query downstream 57 | let q = ClientAPIQuery::RunVRRPGrp(sess, gid); 58 | down.query(q); 59 | 60 | // read answer and set HTTP body (blocking) 61 | let htbody = { 62 | match down.read() { 63 | ClientAPIResponse::RunVRRPGrp(ans) => serialize_answer(&state, ans), 64 | ClientAPIResponse::Unauthorized => { 65 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 66 | } 67 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 68 | } 69 | }; 70 | return (state, htbody); 71 | } 72 | 73 | /// group_interface() handler function 74 | pub fn group_interface(state: State) -> (State, Response) { 75 | // borrow references to the Downstream API 76 | let down = DownstreamAPI::borrow_from(&state); 77 | 78 | // retrieve session cookie 79 | let (user, ts_since, nonce, token) = read_session_cookies(&state); 80 | 81 | // create SessionToken 82 | let mut sess = SessionToken::new(); 83 | sess.set_user(user); 84 | sess.set_tssince(ts_since); 85 | sess.set_nonce(nonce); 86 | sess.set_token(token); 87 | 88 | // extract group_id and interface from GET path 89 | let path = GroupIdInterfaceExtractor::borrow_from(&state); 90 | let gid = path.group_id; 91 | let intf = path.interface.clone(); 92 | 93 | // send a query downstream 94 | let q = ClientAPIQuery::RunVRRPGrpIntf(sess, gid, intf); 95 | down.query(q); 96 | 97 | // read answer and set HTTP body (blocking) 98 | let htbody = { 99 | match down.read() { 100 | // if a response is returned 101 | ClientAPIResponse::RunVRRPGrpIntf(ans) => serialize_answer(&state, ans), 102 | ClientAPIResponse::Unauthorized => { 103 | create_empty_response(&state, StatusCode::UNAUTHORIZED) 104 | } 105 | _ => create_empty_response(&state, StatusCode::INTERNAL_SERVER_ERROR), 106 | } 107 | }; 108 | return (state, htbody); 109 | } 110 | -------------------------------------------------------------------------------- /src/api/client/router/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API router 2 | use super::*; 3 | 4 | // std 5 | use std::net::ToSocketAddrs; 6 | 7 | // future 8 | use futures::future::Future; 9 | 10 | // gotham 11 | extern crate gotham; 12 | use gotham::bind_server; 13 | use gotham::helpers::http::response::{create_empty_response, create_response}; 14 | use gotham::middleware::cookie::CookieParser; 15 | use gotham::middleware::state::StateMiddleware; 16 | use gotham::pipeline::new_pipeline; 17 | use gotham::pipeline::single::single_pipeline; 18 | use gotham::router::builder::*; 19 | use gotham::router::Router; 20 | use gotham::state::{FromState, State}; 21 | 22 | // hyper 23 | extern crate hyper; 24 | use hyper::header::SET_COOKIE; 25 | use hyper::{Body, Response, StatusCode}; 26 | 27 | // failure 28 | use failure::{err_msg, Error}; 29 | 30 | // openssl 31 | extern crate openssl; 32 | use openssl::{ 33 | pkey::PKey, 34 | ssl::{SslAcceptor, SslMethod}, 35 | x509::X509, 36 | }; 37 | 38 | // tokio 39 | use tokio::{net::TcpListener, runtime::Runtime}; 40 | 41 | // tokio_openssl 42 | extern crate tokio_openssl; 43 | use tokio_openssl::SslAcceptorExt; 44 | 45 | // mime 46 | extern crate mime; 47 | 48 | // cookie 49 | extern crate cookie; 50 | use cookie::{Cookie, CookieJar, SameSite}; 51 | 52 | // serde 53 | use serde::Serialize; 54 | 55 | // handlers 56 | mod handlers; 57 | 58 | // handlers constants 59 | const COOKIE_USER: &str = "user"; 60 | const COOKIE_TIMESTAMP: &str = "ts"; 61 | const COOKIE_NONCE: &str = "nonce"; 62 | const COOKIE_TOKEN: &str = "token"; 63 | 64 | // Client API routing 65 | // ------------------ 66 | // c-api/ 67 | // |_ v1/ 68 | // |_ / GET, HEAD index 69 | // |_ auth/ client API authentication 70 | // |_ config/ static configuration objects 71 | // | |_ global/ global configuration 72 | // | | |_ / GET retrieve all global configuration 73 | // | | |_ / PUT modify global configuration 74 | // | |_ vrouter/ virtual router(s) configuration 75 | // | | |_ / GET retrieve all virtual router configuration 76 | // | | |_ / PUT modify virtual router configuration 77 | // | |_ protocols/ protocols configuration 78 | // | |_ / GET retrieve all protocols configuration 79 | // | |_ / PUT modify protocols configuration 80 | // |_ run/ running configurations objects 81 | // |_ global/ running global configuration 82 | // | |_ / GET retrieve running global configuration 83 | // |_ vrrp/ 84 | // | |_ / GET retrieve all VRRP information 85 | // | |_ / PUT modify a specific virtual router (spec. grp/intf) 86 | // | |_ / POST add a new VRRP virtual router (spec. grp/intf) 87 | // | |_ / DELETE remove a specific virtual router (spec. grp/intf) 88 | // | |_ / 89 | // | |_ / GET retrieve group specific information 90 | // | |_ / PUT modify specific virtual router (spec. intf) 91 | // | |_ / POST add a new VRRP virtual router (spec. intf) 92 | // | |_ / DELETE remove a specific virtual router (spec. intf) 93 | // | |_ // 94 | // | |_ / GET get specific virtual router information 95 | // | |_ / PUT modify specific virtual router 96 | // | |_ / POST add a new VRRP virtual router 97 | // | |_ / DELETE remove a specific virtual router 98 | // |_ protocols/ 99 | // |_ / GET retrieve all protocols information 100 | // |_ static/ 101 | // |_ / GET retrieve all static routes 102 | // |_ / POST add a new static route (specify route) 103 | // |_ / PUT modify a static route (specify route) 104 | // |_ / DELETE remove a static route (specifc route) 105 | // 106 | 107 | // router() function 108 | fn router(down_api: &DownstreamAPI) -> Router { 109 | // create new pipeline 110 | let pipeline = new_pipeline(); 111 | 112 | // add CookieParser middleware 113 | let pipeline = pipeline.add(CookieParser); 114 | 115 | // create the state middleware to share the downstream API 116 | let stm = StateMiddleware::new(down_api.clone()); // verify 117 | 118 | // add state middleware to existing pipeline 119 | let pipeline = pipeline.add(stm); 120 | 121 | // construct a basic chain from the pipeline 122 | let (chain, pipelines) = single_pipeline(pipeline.build()); 123 | 124 | // build the router with chain and pipelines 125 | build_router(chain, pipelines, |route| { 126 | // index 127 | route.get_or_head("/").to(handlers::index); 128 | 129 | // auth/ scope 130 | route.scope("/auth", |route| { 131 | // / (POST) 132 | route.post("/").to(handlers::auth::client); 133 | }); 134 | 135 | // config/ scope 136 | route.scope("/config", |route| { 137 | // global/ 138 | route.scope("/global", |route| { 139 | route.get("/").to(handlers::config::global::all); 140 | }); 141 | // vrouter/ 142 | route.scope("/vrouter", |route| { 143 | route.get("/").to(handlers::config::vrouter::all); 144 | }); 145 | // protocols/ 146 | route.scope("/protocols", |route| { 147 | route.get("/").to(handlers::config::protocols::all); 148 | }) 149 | }); 150 | 151 | // run/ scope 152 | route.scope("/run", |route| { 153 | // global/ scope 154 | route.scope("/global", |route| { 155 | route.get("/").to(handlers::run::global::all); 156 | }); 157 | // vrrp/ scope 158 | route.scope("/vrrp", |route| { 159 | // / 160 | route.get("/").to(handlers::run::vrrp::all); 161 | // / 162 | route 163 | .get("/:group_id") 164 | .with_path_extractor::() 165 | .to(handlers::run::vrrp::group); 166 | // // 167 | route 168 | .get("/:group_id/:interface") 169 | .with_path_extractor::() 170 | .to(handlers::run::vrrp::group_interface); 171 | }); 172 | // protocols/ scope 173 | route.scope("/protocols", |route| { 174 | // / 175 | route.get("/").to(handlers::run::protocols::all); 176 | // static/ 177 | route.get("/static").to(handlers::run::protocols::pstatic); 178 | }); 179 | }); 180 | }) 181 | } 182 | 183 | // start() function 184 | pub fn start(down_api: DownstreamAPI, host: String, tls: bool, tls_key: String, tls_cert: String) { 185 | println!("Client API Server listening on http://{}", host); 186 | 187 | // if TLS is enabled 188 | if tls { 189 | let acceptor = build_tls_acceptor(tls_key, tls_cert).unwrap(); 190 | let sockaddr = host 191 | .to_socket_addrs() 192 | .unwrap() 193 | .next() 194 | .ok_or_else(|| err_msg("Invalid Socket Address")) 195 | .unwrap(); 196 | let listener = TcpListener::bind(&sockaddr).unwrap(); 197 | let server = bind_server( 198 | listener, 199 | move || Ok(router(&down_api)), 200 | move |socket| { 201 | acceptor 202 | .accept_async(socket) 203 | .map_err(|e| println!("OpenSSL error: {}", e)) 204 | }, 205 | ); 206 | let mut runtime = Runtime::new().unwrap(); 207 | runtime 208 | .block_on(server) 209 | .map_err(|()| err_msg("Server failed")) 210 | .unwrap(); 211 | } else { 212 | gotham::start(host, router(&down_api)) 213 | } 214 | } 215 | 216 | // build_tls_acceptor() function 217 | fn build_tls_acceptor(keyfile: String, certfile: String) -> Result { 218 | // openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout key.pem -days 365 -out cert.pem 219 | let key = std::fs::read(keyfile).expect("Cannot read RSA key file"); 220 | let cert = std::fs::read(certfile).expect("Cannot read X.509 certificate file"); 221 | let cert = X509::from_pem(&cert).expect("Malformed X.509 certificate"); 222 | let key = PKey::private_key_from_pem(&key).expect("Malformed PEM key"); 223 | 224 | let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; 225 | builder.set_certificate(&cert)?; 226 | builder.set_private_key(&key)?; 227 | 228 | let acceptor = builder.build(); 229 | Ok(acceptor) 230 | } 231 | 232 | // serialize_answer() function 233 | fn serialize_answer(state: &State, ans: T) -> Response { 234 | create_response( 235 | &state, 236 | StatusCode::OK, 237 | mime::APPLICATION_JSON, 238 | serde_json::to_vec(&ans).expect("serialized response"), 239 | ) 240 | } 241 | 242 | // GroupIdExtractor structure 243 | #[derive(Deserialize, StateData, StaticResponseExtender)] 244 | struct GroupIdExtractor { 245 | group_id: u8, 246 | } 247 | 248 | // GroupIdInterfaceExtractor structure 249 | #[derive(Deserialize, StateData, StaticResponseExtender)] 250 | struct GroupIdInterfaceExtractor { 251 | group_id: u8, 252 | interface: String, 253 | } 254 | 255 | // read_session_cookies() function 256 | pub fn read_session_cookies(state: &State) -> (String, u64, u64, String) { 257 | // retrieve session cookies 258 | let c = CookieJar::borrow_from(&state); 259 | let user = { 260 | c.get(COOKIE_USER) 261 | .map(|c| c.value().to_owned()) 262 | .unwrap_or_else(|| "null".to_string()) 263 | }; 264 | let ts_since: u64 = { 265 | c.get(COOKIE_TIMESTAMP) 266 | .map(|c| c.value().parse::().unwrap()) 267 | .unwrap_or_else(|| 0) 268 | }; 269 | let nonce: u64 = { 270 | c.get(COOKIE_NONCE) 271 | .map(|c| c.value().parse::().unwrap()) 272 | .unwrap_or_else(|| 0) 273 | }; 274 | let token = { 275 | c.get(COOKIE_TOKEN) 276 | .map(|c| c.value().to_owned()) 277 | .unwrap_or_else(|| "null".to_string()) 278 | }; 279 | (user, ts_since, nonce, token) 280 | } 281 | 282 | #[cfg(test)] 283 | mod tests { 284 | use super::*; 285 | use gotham::test::TestServer; 286 | use hyper::StatusCode; 287 | 288 | #[test] 289 | fn receive_hello_response() { 290 | let down_api = DownstreamAPI::new(); 291 | 292 | let server = TestServer::new(router(&down_api)).unwrap(); 293 | let response = server.client().get("http://localhost").perform().unwrap(); 294 | 295 | assert_eq!(response.status(), StatusCode::OK); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/api/client/sessions/auth.rs: -------------------------------------------------------------------------------- 1 | //! Client API - session authentication module 2 | use super::*; 3 | 4 | // std 5 | use std::thread; 6 | use std::time; 7 | 8 | // session token 9 | use token::SessionToken; 10 | 11 | // config 12 | use crate::config; 13 | 14 | // regex 15 | extern crate regex; 16 | use regex::Regex; 17 | 18 | // rand 19 | use rand::Rng; 20 | 21 | // sha256 22 | use sha2::{Digest, Sha256}; 23 | 24 | // scrypt 25 | extern crate scrypt; 26 | use scrypt::scrypt_check; 27 | 28 | /// auth_api_client() function 29 | pub fn auth_api_client( 30 | cfg: &config::CConfig, 31 | user: String, 32 | passwd: String, 33 | ) -> Option { 34 | // authenticate the API user 35 | let sess = match auth_user_from_cfg(cfg, user, passwd) { 36 | Some(usr) => { 37 | // if authentication is succesful, create a new SessionToken 38 | let mut token = SessionToken::new(); 39 | // set authenticated user 40 | token.set_user(usr); 41 | // generate the token 42 | match token.gen_token(cfg) { 43 | // if succesfully generated 44 | Ok(()) => { 45 | // set session duration 46 | let timeout = time::Duration::new(60, 0); 47 | token.set_validfor(timeout.as_secs()); 48 | // return SessionToken 49 | Some(token) 50 | } 51 | // if token generation failed 52 | _ => None, 53 | } 54 | } 55 | // if authentication failed 56 | None => None, 57 | }; 58 | 59 | sess 60 | } 61 | 62 | /// auth_user_from_cfg() function 63 | /// read the 'users' vector in the api configuration section 64 | /// and for every user, compare the hashed passwords according 65 | /// to the configured hash function. 66 | /// 67 | /// return the user name String if sucessfully authenticated 68 | fn auth_user_from_cfg(cfg: &config::CConfig, user: String, passwd: String) -> Option { 69 | // initialize response 70 | let res: Option = None; 71 | 72 | // access configuation api users 73 | if let Some(a) = &cfg.api { 74 | for acc in a.users() { 75 | match regex_captures_apiuser(&acc) { 76 | Some(c) => { 77 | let alg = c.get(1).unwrap().as_str().to_string(); 78 | let username = c.get(2).unwrap().as_str().to_string(); 79 | let _access = c.get(3).unwrap().as_str().to_string(); 80 | let salt = c.get(4).unwrap().as_str().to_string(); 81 | let hash = c.get(5).unwrap().as_str().to_string(); 82 | // if the username matches 83 | if username == user { 84 | // perform password hashing 85 | match &alg[..] { 86 | "SHA256" => { 87 | // create new SHA256 hasher 88 | let mut hasher = Sha256::new(); 89 | // feed the hasher with the password 90 | hasher.input(&passwd); 91 | // feed the hasher with the salt 92 | hasher.input(&salt); 93 | // convert the result to an hex formatted String 94 | let h2 = format!("{:x}", hasher.result()); 95 | // reduce likelihood of timing attacks by introducing a random delay 96 | // prior to the hashes comparison. 97 | let mut rng = rand::thread_rng(); 98 | let rdelay = rng.gen::() as u64; 99 | let time = time::Duration::from_millis(rdelay); 100 | thread::sleep(time); 101 | // compare hashed values 102 | if hash == h2 { 103 | return Some(username); 104 | } 105 | } 106 | "SCRYPT" => { 107 | // check if password is matching the stored hash 108 | if scrypt_check(&passwd, &hash).is_ok() { 109 | return Some(username); 110 | } 111 | } 112 | // if alg doesn't match, continue 113 | &_ => (), 114 | } 115 | } 116 | } 117 | // if regex caputre failed, proceed to next account entry 118 | None => (), 119 | } 120 | } 121 | } 122 | 123 | // return authentication response 124 | res 125 | } 126 | 127 | /// regex_captures_apiuser function 128 | fn regex_captures_apiuser(acc: &String) -> Option { 129 | // the API user account information is formatted as follow: 130 | // {{}}:: 131 | // 'user-name' must be alphanumeric between 1 and 256 characters 132 | // 'salt' must be between 0 and 64 hex digits 133 | // 'passowrd-hash' must be betwween 16 and 256 printable digits 134 | lazy_static! { 135 | static ref REGEX_HTBODY_AUTH_AV: Regex = 136 | Regex::new(r"^\{\{(?P[[:alnum:]]{1,16})\}\}(?P[[:alnum:]]{1,256}):(?P\d):(?P[[:xdigit:]]{0,64}):(?P

[[:print:]]{16,256})$") 137 | .unwrap(); 138 | } 139 | // capture content using the pre-compiled regular expression 140 | REGEX_HTBODY_AUTH_AV.captures(acc) 141 | } 142 | -------------------------------------------------------------------------------- /src/api/client/sessions/mod.rs: -------------------------------------------------------------------------------- 1 | //! Client API - sessions support module 2 | 3 | // session authentication 4 | pub mod auth; 5 | 6 | // session token 7 | pub mod token; 8 | -------------------------------------------------------------------------------- /src/api/client/sessions/token.rs: -------------------------------------------------------------------------------- 1 | //! Client API - session token module 2 | 3 | // std 4 | use std::thread; 5 | use std::time::SystemTime; 6 | 7 | // rand 8 | use rand::Rng; 9 | 10 | // hmac 11 | use hmac::{Hmac, Mac}; 12 | 13 | // sha3 14 | use sha3::Sha3_256; 15 | 16 | // time 17 | use std::time; 18 | 19 | // config 20 | use crate::config; 21 | 22 | /// SessionTroken structure 23 | pub struct SessionToken { 24 | user: String, 25 | ts_since: u64, 26 | ts_valid: u64, 27 | nonce: u64, 28 | token: String, 29 | secure: bool, 30 | } 31 | 32 | /// SessionToken implementation 33 | impl SessionToken { 34 | // new() method 35 | pub fn new() -> SessionToken { 36 | SessionToken { 37 | user: "null".to_string(), 38 | ts_since: 0, 39 | ts_valid: 0, 40 | nonce: 0, 41 | token: "null".to_string(), 42 | secure: false, 43 | } 44 | } 45 | // set_user() setter 46 | pub fn set_user(&mut self, user: String) { 47 | self.user = user; 48 | } 49 | // set_tssince() setter 50 | pub fn set_tssince(&mut self, ts: u64) { 51 | self.ts_since = ts; 52 | } 53 | // set_validfor() method 54 | pub fn set_validfor(&mut self, duration: u64) -> u64 { 55 | self.ts_valid = self.ts_since + duration; 56 | self.ts_valid 57 | } 58 | // set_nonce() setter 59 | pub fn set_nonce(&mut self, nonce: u64) { 60 | self.nonce = nonce; 61 | } 62 | // set_token() setter 63 | pub fn set_token(&mut self, token: String) { 64 | self.token = token; 65 | } 66 | // gen_token() method 67 | pub fn gen_token(&mut self, cfg: &config::CConfig) -> std::io::Result<()> { 68 | // get current system time (in seconds since the Unix Epoch) 69 | let now = SystemTime::now(); 70 | let time = now 71 | .duration_since(SystemTime::UNIX_EPOCH) 72 | .unwrap() 73 | .as_secs(); 74 | // set current time to 'since' timestamp 75 | self.ts_since = time; 76 | // generate a random number of 64 bits 77 | let mut rng = rand::thread_rng(); 78 | let nonce: u64 = rng.gen(); 79 | // set the nonce 80 | self.nonce = nonce; 81 | // concatenate the user and time with the nonce 82 | let utn = format!("{}{}{}", self.user, self.ts_since, self.nonce); 83 | // hash the above elements 84 | let secret = cfg.api.as_ref().unwrap().secret(); 85 | let token = gen_hmac_string(&utn, secret); 86 | // set the hashed token 87 | self.token = token; 88 | // set the token's 'secure' flag if tls is enabled 89 | if cfg.api.as_ref().unwrap().tls() { 90 | self.secure = true; 91 | } 92 | // confirm completion 93 | Ok(()) 94 | } 95 | // user() method 96 | pub fn user(&self) -> String { 97 | self.user.clone() 98 | } 99 | // ts_since() method 100 | pub fn ts_since(&self) -> String { 101 | format!("{}", self.ts_since) 102 | } 103 | // nonce() method 104 | pub fn nonce(&self) -> String { 105 | format!("{}", self.nonce) 106 | } 107 | // token() method 108 | pub fn token(&self) -> String { 109 | self.token.clone() 110 | } 111 | // secure() method 112 | pub fn secure(&self) -> bool { 113 | self.secure 114 | } 115 | // validate() method 116 | // check the integrity of the token 117 | pub fn validate(&self, cfg: &config::CConfig) -> Option { 118 | // concatenate the user and time with the nonce 119 | let utn = format!("{}{}{}", self.user, self.ts_since, self.nonce); 120 | // hash the above elements 121 | let secret = cfg.api.as_ref().unwrap().secret(); 122 | let token = gen_hmac_string(&utn, secret); 123 | // compare the stored (or passed) hash with the recomputed hash/token above 124 | // make sure the comparison time is randomized or constant to avoid timing attacks 125 | let mut rng = rand::thread_rng(); 126 | let rdelay = rng.gen_range(10, 40); 127 | let time = time::Duration::from_millis(rdelay); 128 | thread::sleep(time); 129 | // return the token if they match 130 | if self.token == token { 131 | Some(token) 132 | } else { 133 | None 134 | } 135 | } 136 | } 137 | 138 | // type alias 139 | type HmacSha3_256 = Hmac; 140 | 141 | // gen_hmac_string() function 142 | fn gen_hmac_string(input: &String, secret: String) -> String { 143 | let mut mac = HmacSha3_256::new_varkey(secret.as_bytes()).expect("invalid key length"); 144 | mac.input(input.as_bytes()); 145 | let res = mac.result(); 146 | let out = res.code(); 147 | 148 | format!("{:x}", out) 149 | } 150 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | //! Application Programming Interface (API) module 2 | 3 | // client API 4 | pub mod client; 5 | -------------------------------------------------------------------------------- /src/auth.rs: -------------------------------------------------------------------------------- 1 | //! authentication module 2 | use super::*; 3 | 4 | // hmac 5 | use hmac::digest::{ExtendableOutput, XofReader}; 6 | use hmac::{digest::Input, Hmac, Mac}; 7 | 8 | // sha2 9 | extern crate sha2; 10 | use sha2::Sha256; 11 | 12 | // sha3 13 | extern crate sha3; 14 | use sha3::Shake256; 15 | 16 | // type aliases 17 | type HmacSha256 = Hmac; 18 | 19 | // gen_auth_data() function 20 | pub fn gen_auth_data(autht: u8, secret: &Option, msg: Option<&[u8]>) -> Vec { 21 | match autht { 22 | // AUTH_TYPE_SIMPLE (RFC2338 Type-1 Plain) 23 | AUTH_TYPE_SIMPLE => match secret { 24 | Some(s) => { 25 | let data = format!("{:\0<8}", s); 26 | return data.into_bytes(); 27 | } 28 | None => { 29 | let mut data: Vec = Vec::new(); 30 | for _ in 0..8 { 31 | data.push(0); 32 | } 33 | return data; 34 | } 35 | }, 36 | // AUTH_TYPE_P0 (PROPRIETARY-TRUNCATED-8B-SHA256) 37 | // This is a proprietary type for a HMAC producing fixed outputs of only 8 38 | // bytes, which should be resistant enough to thwart most protocol attacks. 39 | AUTH_TYPE_P0 => { 40 | let mut data: Vec = Vec::new(); 41 | // hmac secret key 42 | let key = match secret { 43 | Some(s) => s, 44 | None => "", 45 | }; 46 | // create HMAC-SHA256 instance 47 | let mut mac = HmacSha256::new_varkey(key.as_bytes()).expect("invalid key size"); 48 | // input message (if any) 49 | match msg { 50 | Some(m) => mac.input(m), 51 | None => panic!("cannot perform authentication without message"), 52 | } 53 | // get computation result 54 | for b in mac.result().code().iter() { 55 | data.push(*b); 56 | } 57 | // truncat data to 8 bytes 58 | data.truncate(8); 59 | // return data 60 | return data; 61 | } 62 | // AUTH_TYPE_P1 (PROPRIETARY-XOF-8B-SHAKE256) 63 | // This is an internal, proprietary type using the SHAKE256 XOF 64 | AUTH_TYPE_P1 => { 65 | // secret key 66 | let key = match secret { 67 | Some(s) => s.as_bytes(), 68 | None => "".as_bytes(), 69 | }; 70 | // create SHAKE256 instance 71 | let mut hasher = Shake256::default(); 72 | // feed the hasher 73 | match msg { 74 | Some(m) => { 75 | let km = [key, m].concat(); 76 | hasher.input(km); 77 | } 78 | None => panic!("cannot perform authentication without message"), 79 | } 80 | // read result 81 | let mut reader = hasher.xof_result(); 82 | let mut data = [0u8; 8]; 83 | reader.read(&mut data); 84 | return data.to_vec(); 85 | } 86 | // no authentication 87 | _ => { 88 | let mut data: Vec = Vec::new(); 89 | for _ in 0..8 { 90 | data.push(0); 91 | } 92 | return data; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/bin/rvrrpd.rs: -------------------------------------------------------------------------------- 1 | //! # rVRRPd 2 | //! 3 | //! `rVRRPd` is aimed to be a fast, secure and multi-platform VRRPv2 implementation. 4 | extern crate rVRRPd; 5 | use rVRRPd::{listen_ip_pkts, Config}; 6 | 7 | // getopts 8 | use getopts::Options; 9 | 10 | // std 11 | use std::env; 12 | use std::error::Error; 13 | 14 | // ctrlc (linux signal handling) 15 | extern crate ctrlc; 16 | 17 | /// MyError Type 18 | #[derive(Debug)] 19 | struct MyError(String); 20 | 21 | impl std::fmt::Display for MyError { 22 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 23 | write!(f, "{}", self.0) 24 | } 25 | } 26 | impl Error for MyError {} 27 | 28 | // print_usage() function 29 | fn print_usage(program: &str, opts: Options) { 30 | let modes = format!( 31 | "\ 32 | Modes: 33 | 0 = VRRPv2 Sniffer 34 | 1 = VRRPv2 Virtual Router (foreground) 35 | 2 = VRRPv2 Virtual Router (daemon)\ 36 | " 37 | ); 38 | let usage = format!("Usage: {} -m0|1|2 [options]\n\n{}", program, modes); 39 | print!("{}", opts.usage(&usage)); 40 | } 41 | 42 | // parse_cl_opts() function 43 | fn parse_cli_opts(args: &[String]) -> Result> { 44 | let program = args[0].clone(); 45 | let mut opts = Options::new(); 46 | 47 | opts.optflag("h", "help", "display help information"); 48 | opts.optopt( 49 | "i", 50 | "iface", 51 | "ethernet interface to listen on (sniffer mode)", 52 | "INTERFACE", 53 | ); 54 | opts.optopt( 55 | "m", 56 | "mode", 57 | "operation modes (see Modes):\n 0(sniffer), 1(foreground), 2(daemon)", 58 | "MODE", 59 | ); 60 | opts.optopt( 61 | "c", 62 | "conf", 63 | "path to configuration file:\n (default to /etc/rvrrpd/rvrrpd.conf)", 64 | "FILE", 65 | ); 66 | opts.optopt( 67 | "d", 68 | "debug", 69 | "debugging level:\n0(none), 1(low), 2(medium), 3(high), 5(extensive)", 70 | "LEVEL", 71 | ); 72 | opts.optopt( 73 | "g", 74 | "cfg-format", 75 | "configuration format: toml(default), json", 76 | "FORMAT", 77 | ); 78 | 79 | let matches = match opts.parse(&args[1..]) { 80 | Ok(m) => m, 81 | Err(f) => return Result::Err(Box::new(MyError(f.to_string().into()))), 82 | }; 83 | 84 | // help command-line option 85 | if matches.opt_present("help") || args[1..].is_empty() { 86 | print_usage(&program, opts); 87 | std::process::exit(1); 88 | } 89 | 90 | // mode command-line option 91 | let mode = matches.opt_str("mode"); 92 | let mode = match mode { 93 | Some(x) => x.parse::().unwrap(), 94 | None => { 95 | return Result::Err(Box::new(MyError("No operation mode specified (-m)".into()))); 96 | } 97 | }; 98 | 99 | // iface command-line option 100 | let iface = matches.opt_str("iface"); 101 | let iface = match iface { 102 | Some(x) => Option::Some(x.parse::().unwrap()), 103 | None => { 104 | if mode == 0 { 105 | return Result::Err(Box::new(MyError("No interface specified (-i)".into()))); 106 | } 107 | Option::None 108 | } 109 | }; 110 | 111 | // config command-line option 112 | let conf = matches.opt_str("conf"); 113 | let conf = match conf { 114 | Some(x) => Option::Some(x.parse::().unwrap()), 115 | None => Option::None, 116 | }; 117 | 118 | // debug level command-line option 119 | let debug = matches.opt_str("debug"); 120 | let debug = match debug { 121 | Some(x) => Option::Some(x.parse::().unwrap()), 122 | None => None, 123 | }; 124 | 125 | // configuration file format command-line option 126 | let cfg_format = matches.opt_str("cfg-format"); 127 | let cfg_format = match cfg_format { 128 | Some(x) => Option::Some(x.parse::().unwrap()), 129 | None => Option::None, 130 | }; 131 | 132 | Ok(Config::new(iface, mode, conf, debug, cfg_format)) 133 | } 134 | 135 | // run() function 136 | fn run(cfg: Config) -> Result<(), Box> { 137 | // print information 138 | println!("Starting rVRRPd"); 139 | 140 | // Listen to IP packets 141 | match listen_ip_pkts(&cfg) { 142 | Ok(_) => Ok(()), 143 | Err(e) => { 144 | return Result::Err(Box::new(MyError( 145 | format!("A runtime error occured: {}", e).into(), 146 | ))); 147 | } 148 | } 149 | } 150 | 151 | // main() function 152 | fn main() { 153 | let args: Vec = env::args().collect(); 154 | 155 | match parse_cli_opts(&args) { 156 | // error while parsing cli options 157 | Err(e) => { 158 | eprintln!("{}", e); 159 | std::process::exit(1); 160 | } 161 | // if a configuration is returned from the parser 162 | Ok(c) => match run(c) { 163 | Err(e) => { 164 | eprintln!("{}", e); 165 | std::process::exit(1); 166 | } 167 | Ok(_) => { 168 | std::process::exit(0); 169 | } 170 | }, 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/checksums.rs: -------------------------------------------------------------------------------- 1 | //! checksums related functions module 2 | //! This module is dedicated to internet checksums functions. 3 | //! 4 | //! credit to ref. impl. https://github.com/m-labs/smoltcp/blob/master/src/wire/ip.rs 5 | use byteorder::{ByteOrder, NetworkEndian}; 6 | 7 | const RFC1071_CHUNK_SIZE: usize = 32; 8 | 9 | // rfc1071() function 10 | /// compute rfc1071 internet checksum 11 | /// returns all-ones if carried checksum is valid 12 | pub fn rfc1071(mut data: &[u8]) -> u16 { 13 | let mut acc = 0; 14 | 15 | // for each 32 bytes chunk 16 | while data.len() >= RFC1071_CHUNK_SIZE { 17 | let mut d = &data[..RFC1071_CHUNK_SIZE]; 18 | while d.len() >= 2 { 19 | // sum adjacent pairs converted to 16 bits integer 20 | acc += NetworkEndian::read_u16(d) as u32; 21 | // take the next 2 bytes for the next iteration 22 | d = &d[2..]; 23 | } 24 | data = &data[RFC1071_CHUNK_SIZE..]; 25 | } 26 | 27 | // if it does not fit a 32 bytes chunk 28 | while data.len() >= 2 { 29 | acc += NetworkEndian::read_u16(data) as u32; 30 | data = &data[2..]; 31 | } 32 | 33 | // add odd byte is present 34 | if let Some(&v) = data.first() { 35 | acc += (v as u32) << 8; 36 | } 37 | 38 | propagate_carries(acc) 39 | } 40 | 41 | // propagate final complement? 42 | pub fn propagate_carries(word: u32) -> u16 { 43 | let sum = (word >> 16) + (word & 0xffff); 44 | ((sum >> 16) as u16) + (sum as u16) 45 | } 46 | 47 | // one_complement_sum() function 48 | /// returns all-zeros if checksum is valid 49 | pub fn one_complement_sum(data: &[u8], pos: Option) -> u16 { 50 | let mut sum = 0u32; 51 | let mut idx = 0; 52 | 53 | while idx < data.len() { 54 | match pos { 55 | // if a position is given: 56 | Some(p) => { 57 | if idx == p { 58 | idx = p + 2; // skip 2 bytes 59 | } 60 | // if we reach the end of slice, we are done 61 | if idx == data.len() { 62 | break; 63 | } 64 | } 65 | None => (), 66 | } 67 | let word = (data[idx] as u32) << 8 | data[idx + 1] as u32; 68 | sum = sum + word; 69 | idx = idx + 2; 70 | } 71 | 72 | while sum >> 16 != 0 { 73 | sum = (sum >> 16) + (sum & 0xFFFF); 74 | } 75 | 76 | !sum as u16 77 | } 78 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! configuration file handling module 2 | //! This module provides structure and methods related to configuration file handling. 3 | use super::*; 4 | 5 | // std 6 | use std::net::IpAddr; 7 | 8 | // rand 9 | use rand::Rng; 10 | 11 | /// CfgType Enumerator 12 | pub enum CfgType { 13 | Toml, // TOML 14 | Json, // JSON 15 | } 16 | 17 | /// Main Configuration Structure 18 | #[derive(Debug, Deserialize, Serialize, Clone)] 19 | pub struct CConfig { 20 | pub debug: Option, 21 | pub time_zone: Option, 22 | pub time_format: Option, 23 | pub pid: Option, 24 | pub working_dir: Option, 25 | pub main_log: Option, 26 | pub error_log: Option, 27 | pub vrouter: Option>, 28 | pub protocols: Option, 29 | pub client_api: Option, 30 | pub api: Option, 31 | } 32 | 33 | impl CConfig { 34 | // debug() getter 35 | pub fn debug(&self) -> u8 { 36 | match self.debug { 37 | Some(v) => v, 38 | None => DEBUG_LEVEL_INFO, 39 | } 40 | } 41 | // time_zone() getter 42 | pub fn time_zone(&self) -> u8 { 43 | match &self.time_zone { 44 | Some(s) => match &s[..] { 45 | "local" => 0, 46 | "utc" => 1, 47 | _ => 0, 48 | }, 49 | None => 0, 50 | } 51 | } 52 | // time_format() getter 53 | pub fn time_format(&self) -> u8 { 54 | match &self.time_format { 55 | Some(s) => match &s[..] { 56 | "disabled" => 0, 57 | "short" => 1, 58 | "rfc2822" => 2, 59 | _ => 0, 60 | }, 61 | None => 0, 62 | } 63 | } 64 | // pid() getter 65 | pub fn pid(&self) -> String { 66 | match &self.pid { 67 | Some(v) => v.clone(), 68 | None => RVRRPD_DFLT_PIDFILE.to_string(), 69 | } 70 | } 71 | // working_dir() getter 72 | pub fn working_dir(&self) -> String { 73 | match &self.working_dir { 74 | Some(v) => v.clone(), 75 | None => RVRRPD_DFLT_WORKDIR.to_string(), 76 | } 77 | } 78 | // main_log() getter 79 | pub fn main_log(&self) -> String { 80 | match &self.main_log { 81 | Some(v) => v.clone(), 82 | None => RVRRPD_DFLT_LOGFILE.to_string(), 83 | } 84 | } 85 | // error_log() getter 86 | pub fn error_log(&self) -> String { 87 | match &self.error_log { 88 | Some(v) => v.clone(), 89 | None => RVRRPD_DFLT_ELOGFILE.to_string(), 90 | } 91 | } 92 | // client_api() method 93 | pub fn client_api(&self) -> bool { 94 | match &self.client_api { 95 | Some(s) => match &s[..] { 96 | "http" => true, 97 | _ => true, 98 | }, 99 | None => false, 100 | } 101 | } 102 | } 103 | 104 | /// Virtual-Routers Configuration Structure 105 | #[derive(Debug, Deserialize, Serialize, Clone)] 106 | pub struct VRConfig { 107 | group: u8, 108 | interface: String, 109 | vip: Option, 110 | priority: Option, 111 | preemption: Option, 112 | auth_type: Option, 113 | auth_secret: Option, 114 | timers: Option, 115 | rfc3768: Option, 116 | netdrv: Option, 117 | iftype: Option, 118 | vifname: Option, 119 | socket_filter: Option, 120 | } 121 | impl VRConfig { 122 | // group() getter 123 | pub fn group(&self) -> u8 { 124 | if self.group < 1 { 125 | panic!("error(config): Please configure a group id between 1 and 255") 126 | } 127 | self.group 128 | } 129 | // interface() getter 130 | pub fn interface(&self) -> &String { 131 | &self.interface 132 | } 133 | // vip() getter 134 | pub fn vip(&self) -> [u8; 4] { 135 | match &self.vip { 136 | Some(ip) => match ip.parse::().unwrap() { 137 | IpAddr::V4(ip) => ip.octets(), 138 | IpAddr::V6(_ipv6) => panic!("error(config): Only IPv4 addresses are supported"), 139 | }, 140 | None => panic!("error(config): No virtual IP specified"), 141 | } 142 | } 143 | // timer_advert() getter 144 | pub fn timer_advert(&self) -> u8 { 145 | match &self.timers { 146 | Some(t) => t.advert, 147 | None => 1, 148 | } 149 | } 150 | // priority() getter 151 | pub fn priority(&self) -> u8 { 152 | match self.priority { 153 | Some(v) => { 154 | if v < 1 || v > 254 { 155 | panic!("error(config): Please configure a priority between 1 and 254"); 156 | } 157 | v 158 | } 159 | None => VRRP_V2_DEFAULT_PRIORITY, 160 | } 161 | } 162 | // preemption() getter 163 | pub fn preemption(&self) -> bool { 164 | match self.preemption { 165 | Some(b) => b, 166 | None => false, 167 | } 168 | } 169 | // auth_type() method 170 | pub fn auth_type(&self) -> u8 { 171 | match &self.auth_type { 172 | Some(s) => match &s[..] { 173 | "rfc2338-simple" => AUTH_TYPE_SIMPLE, 174 | "p0-t8-sha256" => AUTH_TYPE_P0, 175 | "p1-b8-shake256" => AUTH_TYPE_P1, 176 | _ => panic!("error(config): authentication type {} is not supported", s), 177 | }, 178 | None => 0, 179 | } 180 | } 181 | // auth_secret() method 182 | pub fn auth_secret(&self) -> Option { 183 | match &self.auth_secret { 184 | Some(cs) => match self.auth_type() { 185 | // if type-1, then truncate to 8 bytes 186 | 1 => { 187 | let mut s = cs.clone(); 188 | s.truncate(8); 189 | Option::Some(s) 190 | } 191 | _ => { 192 | let s = cs.clone(); 193 | Option::Some(s) 194 | } 195 | }, 196 | None => Option::None, 197 | } 198 | } 199 | // rfc3768() getter 200 | pub fn rfc3768(&self) -> bool { 201 | // if auth_type is 'p0-t8-sha256', or 'p1-b8-shake256', 202 | // overwrite rfc3768 compatibility flag 203 | match &self.auth_type { 204 | Some(t) => match &t[..] { 205 | "p0-t8-sha256" | "p1-b8-shake256" => { 206 | println!( 207 | "warning(config): authentication type {} is enabled, forcing rfc3768 compatibility.", 208 | t 209 | ); 210 | return true; 211 | } 212 | _ => {} 213 | }, 214 | None => {} 215 | } 216 | match self.rfc3768 { 217 | Some(b) => b, 218 | None => true, 219 | } 220 | } 221 | // netdrv() method 222 | pub fn netdrv(&self) -> NetDrivers { 223 | // if os is Linux 224 | if cfg!(target_os = "linux") { 225 | // if macvlan is configured, return libnl 226 | match self.iftype() { 227 | IfTypes::macvlan => return NetDrivers::libnl, 228 | _ => {} 229 | } 230 | match &self.netdrv { 231 | Some(s) => match &s[..] { 232 | "ioctl" => NetDrivers::ioctl, 233 | _ => NetDrivers::libnl, 234 | }, 235 | None => NetDrivers::libnl, 236 | } 237 | } 238 | // unspecified OSes 239 | else { 240 | NetDrivers::ioctl 241 | } 242 | } 243 | // iftype() method 244 | pub fn iftype(&self) -> IfTypes { 245 | // if os is Linux 246 | if cfg!(target_os = "linux") { 247 | match &self.iftype { 248 | Some(s) => match &s[..] { 249 | "macvlan" => IfTypes::macvlan, 250 | _ => IfTypes::ether, 251 | }, 252 | None => IfTypes::ether, 253 | } 254 | } else { 255 | IfTypes::ether 256 | } 257 | } 258 | // vifname() method 259 | pub fn vifname(&self) -> String { 260 | match &self.vifname { 261 | Some(s) => s.clone(), 262 | None => format!("{}{}", RVRRPD_DFLT_MACVLAN_NAME, self.group), 263 | } 264 | } 265 | // socket_filter() method 266 | pub fn socket_filter(&self) -> bool { 267 | match self.socket_filter { 268 | Some(b) => b, 269 | None => true, 270 | } 271 | } 272 | } 273 | 274 | /// Timers Option Type 275 | #[derive(Debug, Deserialize, Serialize, Clone)] 276 | struct Timers { 277 | advert: u8, 278 | } 279 | impl Default for Timers { 280 | fn default() -> Self { 281 | Timers { advert: 1 } 282 | } 283 | } 284 | 285 | /// Protocols Option Type 286 | #[derive(Debug, Deserialize, Serialize, Clone)] 287 | pub struct Protocols { 288 | pub r#static: Option>, 289 | } 290 | 291 | /// Static Option Type 292 | #[derive(Debug, Deserialize, Serialize, Clone)] 293 | pub struct Static { 294 | route: String, 295 | mask: String, 296 | nh: String, 297 | metric: Option, 298 | mtu: Option, 299 | } 300 | 301 | // Static Option Implementation 302 | impl Static { 303 | // route() getter 304 | // convert IPv4 String to array of four 8-bits unsigned integers 305 | pub fn route(&self) -> [u8; 4] { 306 | match self.route.parse::().unwrap() { 307 | IpAddr::V4(ip) => ip.octets(), 308 | IpAddr::V6(_ipv6) => panic!("error(config-static): Only IPv4 routes are supported"), 309 | } 310 | } 311 | // mask() getter 312 | pub fn mask(&self) -> [u8; 4] { 313 | match self.mask.parse::().unwrap() { 314 | IpAddr::V4(ip) => ip.octets(), 315 | IpAddr::V6(_ipv6) => panic!("error(config-static): Only IPv4 masks are supported"), 316 | } 317 | } 318 | // nh() getter 319 | pub fn nh(&self) -> [u8; 4] { 320 | match self.nh.parse::().unwrap() { 321 | IpAddr::V4(ip) => ip.octets(), 322 | IpAddr::V6(_ipv6) => panic!("error(config-static): Only IPv4 next-hops are supported"), 323 | } 324 | } 325 | // metric() getter 326 | pub fn metric(&self) -> i16 { 327 | match self.metric { 328 | Some(v) => v as i16, 329 | None => 0, 330 | } 331 | } 332 | // mtu() getter 333 | pub fn mtu(&self) -> u64 { 334 | match self.mtu { 335 | Some(v) => v as u64, 336 | None => 0, 337 | } 338 | } 339 | } 340 | 341 | // decode_config() function 342 | /// read and decode configuration file 343 | pub fn decode_config(filename: String, cfgtype: CfgType) -> CConfig { 344 | let file: std::string::String = match std::fs::read_to_string(filename) { 345 | Ok(s) => s, 346 | Err(e) => { 347 | eprintln!( 348 | "error(config): Cannot read rVRRPd configuration file: {}", 349 | e 350 | ); 351 | std::process::exit(1); 352 | } 353 | }; 354 | match cfgtype { 355 | // TOML 356 | CfgType::Toml => { 357 | let config: CConfig = match toml::from_str(&file) { 358 | Ok(c) => c, 359 | Err(e) => { 360 | eprintln!("error(config): Cannot parse TOML configuration file: {}", e); 361 | std::process::exit(1); 362 | } 363 | }; 364 | return config; 365 | } 366 | // JSON 367 | CfgType::Json => { 368 | let config: CConfig = match serde_json::from_str(&file) { 369 | Ok(c) => c, 370 | Err(e) => { 371 | eprintln!("error(config): Cannot parse JSON configuration file: {}", e); 372 | std::process::exit(1); 373 | } 374 | }; 375 | return config; 376 | } 377 | } 378 | } 379 | 380 | /// API structure 381 | #[derive(Debug, Deserialize, Serialize, Clone)] 382 | pub struct API { 383 | users: Vec, 384 | secret: Option, 385 | host: Option, 386 | tls: Option, 387 | tls_key: Option, 388 | tls_cert: Option, 389 | } 390 | 391 | // API structure implementation 392 | impl API { 393 | // users() method 394 | pub fn users(&self) -> Vec { 395 | self.users.clone() 396 | } 397 | // secret() method 398 | pub fn secret(&self) -> String { 399 | let secret = match &self.secret { 400 | Some(s) => s.clone(), 401 | None => gen_runtime_secret(), 402 | }; 403 | 404 | secret 405 | } 406 | // host() method 407 | pub fn host(&self) -> String { 408 | match &self.host { 409 | Some(s) => s.clone(), 410 | None => "0.0.0.0:7080".to_string(), 411 | } 412 | } 413 | // tls() method 414 | pub fn tls(&self) -> bool { 415 | match self.tls { 416 | Some(b) => b, 417 | None => false, 418 | } 419 | } 420 | // tls_key() method 421 | pub fn tls_key(&self) -> String { 422 | match &self.tls_key { 423 | Some(s) => s.clone(), 424 | None => RVRRPD_CFG_DFLT_TLSKEY.to_string(), 425 | } 426 | } 427 | // tls_cert() method 428 | pub fn tls_cert(&self) -> String { 429 | match &self.tls_cert { 430 | Some(s) => s.clone(), 431 | None => RVRRPD_CFG_DFLT_TLSCERT.to_string(), 432 | } 433 | } 434 | } 435 | 436 | // gen_runtime_secret() function 437 | fn gen_runtime_secret() -> String { 438 | // create a static secret key string 439 | lazy_static! { 440 | static ref SECRET: String = { 441 | // generate a unique runtime secret 442 | let mut rng = rand::thread_rng(); 443 | let r = rng.gen::(); 444 | format!("{:x}", r) 445 | }; 446 | } 447 | SECRET.to_string() 448 | } 449 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | //! Constants module 2 | //! This module regroups all the program's and protocols constants. 3 | 4 | // Program Constants 5 | pub const RVRRPD_DFLT_CFG_FILE: &str = "/etc/rvrrpd/rvrrpd.conf"; 6 | pub const RVRRPD_DFLT_PIDFILE: &str = "/var/run/rvrrpd.pid"; 7 | pub const RVRRPD_DFLT_WORKDIR: &str = "/tmp"; 8 | pub const RVRRPD_DFLT_LOGFILE: &str = "/var/log/rvrrpd.log"; 9 | pub const RVRRPD_DFLT_ELOGFILE: &str = "/var/log/rvrrpd-error.log"; 10 | pub const RVRRPD_DFLT_DATE_FORMAT: &str = "%b %e %Y %T"; 11 | pub const RVRRPD_DFLT_MACVLAN_NAME: &str = "standby"; 12 | pub const RVRRPD_DFLT_CLIENT_API: &str = "disabled"; 13 | pub const RVRRPD_VERSION_STRING: &str = "0.1.3"; 14 | 15 | // Config Constants 16 | pub const RVRRPD_CFG_DFLT_TLSKEY: &str = "/etc/rvrrpd/ssl/key.pem"; 17 | pub const RVRRPD_CFG_DFLT_TLSCERT: &str = "/etc/rvrrpd/ssl/cert.pem"; 18 | 19 | // Debug Constants 20 | pub const DEBUG_LEVEL_INFO: u8 = 0; 21 | pub const DEBUG_LEVEL_LOW: u8 = 1; 22 | pub const DEBUG_LEVEL_MEDIUM: u8 = 2; 23 | pub const DEBUG_LEVEL_HIGH: u8 = 3; 24 | pub const DEBUG_LEVEL_EXTENSIVE: u8 = 5; 25 | pub const DEBUG_SRC_INFO: &str = "info"; 26 | pub const DEBUG_SRC_PROTO: &str = "protocols"; 27 | pub const DEBUG_SRC_VR: &str = "vr"; 28 | pub const DEBUG_SRC_MAIN: &str = "main"; 29 | pub const DEBUG_SRC_MAC: &str = "mac"; 30 | pub const DEBUG_SRC_ROUTE: &str = "route"; 31 | pub const DEBUG_SRC_PACKET: &str = "packet"; 32 | pub const DEBUG_SRC_ARP: &str = "arp"; 33 | pub const DEBUG_SRC_THREAD: &str = "thread"; 34 | pub const DEBUG_SRC_THREADP: &str = "thread-pool"; 35 | pub const DEBUG_SRC_FSM: &str = "fsm"; 36 | pub const DEBUG_SRC_WORKER: &str = "worker"; 37 | pub const DEBUG_SRC_WORKERG: &str = "worker-reg"; 38 | pub const DEBUG_SRC_TIMER: &str = "timer"; 39 | pub const DEBUG_SRC_IP: &str = "ip"; 40 | pub const DEBUG_SRC_AUTH: &str = "auth"; 41 | pub const DEBUG_SRC_MACVLAN: &str = "macvlan"; 42 | pub const DEBUG_SRC_BPF: &str = "bpf"; 43 | 44 | // Ethernet Constants 45 | pub const ETHER_P_IP: u16 = 0x0800; // IPv4 (/usr/include/linux/if_ether.h) 46 | pub const ETHER_P_ARP: u16 = 0x0806; 47 | pub const ETHER_VRRP_IPADDR_POS: usize = 42; // Position of the IP addresses variable-length field 48 | pub const ETHER_VRRP_V2_SRC_MAC: [u8; 6] = [0x00, 0x00, 0x5e, 0x00, 0x01, 0x00]; 49 | pub const ETHER_VRRP_V2_DST_MAC: [u8; 6] = [0x01, 0x00, 0x5e, 0x00, 0x00, 0x12]; 50 | pub const ETHER_ARP_DST_MAC: [u8; 6] = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; 51 | pub const ETHER_FRAME_SIZE: usize = 14; 52 | 53 | // ARP Constants 54 | pub const ARP_HW_TYPE: u16 = 1; // ethernet 55 | pub const ARP_OP_REQUEST: u16 = 1; // request 56 | 57 | // IP Constants 58 | pub const IP_FRAME_OFFSET: usize = 14; 59 | pub const IP_V4_VERSION: u8 = 0x45; 60 | pub const IP_UPPER_PROTO_VRRP: u8 = 112; 61 | pub const IP_TTL_VRRP_MINTTL: u8 = 255; 62 | pub const IP_DSCP_CS6: u8 = 0xc0; 63 | 64 | // VRRP Constants 65 | pub const VRRP_V2_FRAME_OFFSET: usize = 34; 66 | pub const VRRP_V2_CHECKSUM_POS: usize = 6; 67 | pub const VRRP_V2_VER_TYPE_AUTHMSG: u8 = 0x21; 68 | pub const VRRP_V2_IP_MCAST_DST: [u8; 4] = [224, 0, 0, 18]; 69 | pub const VRRP_V2_ADVERT_VERSION_TYPE: u8 = 0x21; 70 | pub const VRRP_V2_DEFAULT_PRIORITY: u8 = 100; 71 | 72 | // Authentication Constants 73 | pub const AUTH_TYPE_SIMPLE: u8 = 1; 74 | pub const AUTH_TYPE_P0: u8 = 250; 75 | pub const AUTH_TYPE_P1: u8 = 251; 76 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | //! debugging module 2 | //! This module provides debugging related functions. 3 | use super::*; 4 | 5 | // chrono 6 | use chrono::{DateTime, Local, Utc}; 7 | 8 | // Verbose Structure 9 | #[derive(Clone, Copy)] 10 | pub struct Verbose { 11 | level: u8, 12 | time_zone: u8, 13 | time_format: u8, 14 | } 15 | 16 | // Debug type implementation 17 | impl Verbose { 18 | // new() method 19 | pub fn new(level: u8, time_zone: u8, time_format: u8) -> Verbose { 20 | Verbose { 21 | level, 22 | time_zone, 23 | time_format, 24 | } 25 | } 26 | } 27 | 28 | // print_debug() function 29 | /// This function simply print debugging information according to the specified level 30 | pub fn print_debug(debug: &Verbose, msg_level: u8, src: &str, msg: String) { 31 | // set debug header 32 | let mut hdr = format!(""); 33 | if src != DEBUG_SRC_INFO { 34 | hdr = format!("debug({}): ", src); 35 | } 36 | 37 | // print debugging information with date and time 38 | if debug.level >= msg_level { 39 | match debug.time_zone { 40 | 1 => { 41 | // UTC 42 | let now: DateTime = Utc::now(); 43 | match debug.time_format { 44 | 1 => println!("[{}] {}{}", now.format(RVRRPD_DFLT_DATE_FORMAT), hdr, msg), 45 | 2 => println!("[{}] {}{}", now.to_rfc2822(), hdr, msg), 46 | _ => println!("{}{}", hdr, msg), 47 | } 48 | } // local time 49 | _ => { 50 | let now: DateTime = Local::now(); 51 | match debug.time_format { 52 | 1 => println!("[{}] {}{}", now.format(RVRRPD_DFLT_DATE_FORMAT), hdr, msg), 53 | 2 => println!("[{}] {}{}", now.to_rfc2822(), hdr, msg), 54 | _ => println!("{}{}", hdr, msg), 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/os/drivers.rs: -------------------------------------------------------------------------------- 1 | //! generic drivers module 2 | 3 | // network drivers enumerator 4 | #[allow(non_camel_case_types)] 5 | #[derive(Debug)] 6 | pub enum NetDrivers { 7 | ioctl, // ioctl 8 | libnl, // netlink (libnl-3) 9 | } 10 | 11 | // network interfaces type enumerator 12 | #[allow(non_camel_case_types)] 13 | #[derive(Debug)] 14 | pub enum IfTypes { 15 | ether, // default ethernet 16 | macvlan, // macvlan 17 | } 18 | 19 | // pflag operation Enumerator 20 | #[cfg(target_os = "linux")] 21 | pub enum PflagOp { 22 | Set, 23 | Unset, 24 | } 25 | 26 | // Operation enumerator 27 | #[derive(Debug)] 28 | pub enum Operation { 29 | Add, // Add IP address 30 | Rem, // Remove IP Address 31 | } 32 | -------------------------------------------------------------------------------- /src/os/freebsd/arp.rs: -------------------------------------------------------------------------------- 1 | //! FreeBSD Address Resolution Protocol (ARP) module 2 | //! This module provides ARP related functions. 3 | 4 | /// Address Resolution Protocol (ARP) Structure 5 | #[repr(C)] 6 | pub struct ARPframe { 7 | // Ethernet Header 8 | pub dst_mac: [u8; 6], // destination MAC address 9 | pub src_mac: [u8; 6], // source MAC address 10 | pub ethertype: u16, // ether type 11 | 12 | // ARP 13 | pub hardware_type: u16, // network link type (0x1=ethernet) 14 | pub protocol_type: u16, // upper-layer protocol for resolution 15 | pub hw_addr_len: u8, // length of hardware address (bytes) 16 | pub proto_addr_len: u8, // upper-layer protocol address length 17 | pub opcode: u16, // operation (0x1=request, 0x2=reply) 18 | pub sender_hw_addr: [u8; 6], // sender hardware address 19 | pub sender_proto_addr: [u8; 4], // internetwork address of sender 20 | pub target_hw_addr: [u8; 6], // hardware address of target 21 | pub target_proto_addr: [u8; 4], // internetwork address of target 22 | } 23 | -------------------------------------------------------------------------------- /src/os/freebsd/bpf.rs: -------------------------------------------------------------------------------- 1 | //! FreeBSD Berkeley Packet Filter (BPF) module 2 | use crate::*; 3 | 4 | // std 5 | use std::io; 6 | use std::convert::TryInto; 7 | 8 | // libc 9 | use libc::{IF_NAMESIZE}; 10 | 11 | // ffi 12 | use std::ffi::{CString}; 13 | 14 | // FreeBSD constants 15 | use crate::os::freebsd::constants::*; 16 | 17 | // Ifreq redifinition (stripped) 18 | #[repr(C)] 19 | struct IfreqS { 20 | ifr_name: [u8; IF_NAMESIZE], 21 | } 22 | 23 | // BPF system structures 24 | // bpf_ts structure 25 | // https://github.com/freebsd/freebsd/blob/master/sys/net/bpf.h:202 26 | #[repr(C)] 27 | pub struct bpf_ts { 28 | pub bt_sec: i64, 29 | pub bt_frac: u64, 30 | } 31 | 32 | // bpf_xhdr structure 33 | #[repr(C)] 34 | pub struct bpf_xhdr { 35 | pub bh_tstamp: bpf_ts, // timestamp 36 | pub bh_caplen: u32, // length of captured pattern 37 | pub bh_datalen: u32, // length of packet 38 | pub bh_hdrlen: u16, // length of this structure + alignment padding 39 | } 40 | 41 | // bpf_open_device() function 42 | // 43 | /// Open a BPF device and return the file descriptor if successful 44 | pub fn bpf_open_device(debug: &Verbose) -> io::Result<(i32)> { 45 | // try /dev/bpf 46 | let bpf_dev = CString::new("/dev/bpf").unwrap(); 47 | let bpf_dev_slice = &mut [0i8; 10]; 48 | for (i, b) in bpf_dev.as_bytes_with_nul().iter().enumerate() { 49 | bpf_dev_slice[i] = (*b).try_into().unwrap(); 50 | } 51 | let mut buf = [0i8; 10]; 52 | buf.clone_from_slice(bpf_dev_slice); 53 | 54 | // open /dev/bpf device 55 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, format!("opening /dev/bpf device")); 56 | let res = unsafe {libc::open(&buf as *const i8, libc::O_RDWR)}; 57 | if res >= 0 { 58 | return Ok(res); 59 | } 60 | 61 | // if above failed, try /dev/bpfX devices 62 | for i in 0..99 { 63 | // create bpf device name slice 64 | let bpf_fmtstr = format!("/dev/bpf{}", i); 65 | let bpf_dev = CString::new(bpf_fmtstr).unwrap(); 66 | let bpf_dev_slice = &mut [0i8; 11]; 67 | for (i, b) in bpf_dev.as_bytes_with_nul().iter().enumerate() { 68 | bpf_dev_slice[i] = (*b).try_into().unwrap(); 69 | } 70 | // create bpf device name buffer 71 | let mut buf = [0i8; 11]; 72 | buf.clone_from_slice(bpf_dev_slice); 73 | 74 | // open bpf device 75 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, format!("opening /dev/bpf{} device", i)); 76 | let res = unsafe {libc::open(&buf as *const i8, libc::O_RDWR)}; 77 | 78 | // check returned value 79 | // if negative, an error occured, continue 80 | // if positive, return the file descriptor 81 | if res >= 0 { 82 | return Ok(res); 83 | } 84 | } 85 | 86 | // if all BPF devices are exhausted 87 | eprintln!("error, cannot find an available BPF device"); 88 | return Err(io::Error::last_os_error()); 89 | } 90 | 91 | // bpf_bind_device() function 92 | // 93 | /// Bind BPF device to a physical interface 94 | pub fn bpf_bind_device(bpf_fd: i32, interface: &CString, debug: &Verbose) -> io::Result<()> { 95 | let ifname_slice = &mut [0u8; IF_NAMESIZE]; 96 | for (i, b) in interface.as_bytes_with_nul().iter().enumerate() { 97 | ifname_slice[i] = *b; 98 | } 99 | 100 | // create Ifreq structure 101 | let ifbound = IfreqS { 102 | ifr_name: { 103 | let mut buf = [0u8; IF_NAMESIZE]; 104 | buf.clone_from_slice(ifname_slice); 105 | buf 106 | } 107 | }; 108 | 109 | // ioctl 110 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, 111 | format!("binding BPF device with fd {} to interface {:?}", bpf_fd, interface) 112 | ); 113 | match unsafe { libc::ioctl(bpf_fd, BIOCSETIF, &ifbound) } { 114 | r if r >= 0 => Ok(()), 115 | e => { 116 | eprintln!("error while binding BPF device, fd {}, error no: {}", bpf_fd, e); 117 | return Err(io::Error::last_os_error()); 118 | } 119 | } 120 | } 121 | 122 | // bpf_setup_buf() function 123 | // 124 | /// Setup BPF device buffer and features 125 | /// Return size of BPF buffer after setup 126 | pub fn bpf_setup_buf(bpf_fd: i32, pkt_buf: &mut [u8], debug: &Verbose) -> io::Result<(usize)> { 127 | // initialize local buf_len with current buffer size 128 | let buf_len = pkt_buf.len(); 129 | 130 | if buf_len == 0 { 131 | // get buffer length (ioctl) 132 | // actually ignoring returned value 133 | match unsafe { libc::ioctl(bpf_fd, BIOCGBLEN, &buf_len)} { 134 | e if e < 0 => { 135 | eprintln!("error while getting buffer length on BPF device, fd {}, error no: {}", bpf_fd, e); 136 | return Err(io::Error::last_os_error()); 137 | } 138 | s => { 139 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, 140 | format!("required buffer length for BPF device, fd {}, is: {} bytes", bpf_fd, s) 141 | ); 142 | 143 | } 144 | }; 145 | } else { 146 | // set buffer length (ioctl) 147 | match unsafe { libc::ioctl(bpf_fd, BIOCSBLEN, &buf_len)} { 148 | e if e < 0 => { 149 | eprintln!("error while setting buffer length on BPF device, fd {}, error no: {}", bpf_fd, e); 150 | return Err(io::Error::last_os_error()); 151 | } 152 | _ => { 153 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, 154 | format!("buffer length for BPF device, fd {} set", bpf_fd) 155 | ); 156 | } 157 | }; 158 | } 159 | 160 | // activate immediate mode (ioctl) 161 | match unsafe { libc::ioctl(bpf_fd, BIOCIMMEDIATE, &buf_len) } { 162 | e if e < 0 => { 163 | eprintln!("error while setting immediate mode on BPF device, fd {}, error no: {}", bpf_fd, e); 164 | return Err(io::Error::last_os_error()); 165 | } 166 | _ => { 167 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, 168 | format!("immediate mode set on BPF device, fd {}", bpf_fd) 169 | ); 170 | } 171 | }; 172 | 173 | // set the header complete flag to one 174 | let flag = 1; 175 | match unsafe { libc::ioctl(bpf_fd, BIOCSHDRCMPLT, &flag) } { 176 | e if e < 0 => { 177 | eprintln!("error while setting ({}) header complete flag on BPF device, fd {}, error no: {}", flag, bpf_fd, e); 178 | return Err(io::Error::last_os_error()); 179 | } 180 | _ => { 181 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, 182 | format!("header complete flag set to {} on BPF device, fd {}", flag, bpf_fd) 183 | ); 184 | } 185 | }; 186 | 187 | // return Ok(buf_len) if everything went successful 188 | Ok(buf_len as usize) 189 | } 190 | 191 | // bpf_set_promisc() function 192 | // 193 | /// Set interface bound to the BPF's fd in promiscuous mode 194 | pub fn bpf_set_promisc(bpf_fd: i32, debug: &Verbose) -> io::Result<()> { 195 | // set interface in promiscuous mode 196 | match unsafe { libc::ioctl(bpf_fd, BIOCPROMISC.into(), 0) } { 197 | e if e < 0 => { 198 | eprintln!("error while setting promiscuous mode on BPF device, fd {}, error no: {}", bpf_fd, e); 199 | return Err(io::Error::last_os_error()); 200 | } 201 | _ => { 202 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, 203 | format!("promiscuous mode set on BPF device, fd {}", bpf_fd) 204 | ); 205 | Ok(()) 206 | } 207 | } 208 | } 209 | 210 | // bpf_wordalign() function 211 | // 212 | /// Align the BPF buffer to the next frame given capured size 213 | /// Reference: pnet's source/src/bindings/bpf.rs 214 | pub fn bpf_wordalign(s: isize) -> isize { 215 | let bpf_alignement = BPF_ALIGNMENT as isize; 216 | let one = 1; 217 | 218 | (s + (bpf_alignement - one)) & !(bpf_alignement - one) 219 | } 220 | -------------------------------------------------------------------------------- /src/os/freebsd/constants.rs: -------------------------------------------------------------------------------- 1 | //! FreeBSD specific constants 2 | 3 | // libc 4 | use libc::{c_ulong, c_uint, c_int}; 5 | 6 | // sockios, ioctls 7 | pub const BIOCGBLEN: c_ulong = 0x40044266; 8 | pub const BIOCSBLEN: c_ulong = 0xc0044266; 9 | pub const BIOCFLUSH: c_uint = 0x20004268; 10 | pub const BIOCPROMISC: c_uint = 0x20004269; 11 | pub const BIOCGDLT: c_ulong = 0x4004426a; 12 | pub const BIOCGETIF: c_ulong = 0x4020426b; 13 | pub const BIOCSETIF: c_ulong = 0x8020426c; 14 | pub const BIOCGSTATS: c_ulong = 0x4008426f; 15 | pub const BIOCIMMEDIATE: c_ulong = 0x80044270; 16 | pub const BIOCVERSION: c_ulong = 0x40044271; 17 | pub const BIOCGRSIG: c_ulong = 0x40044272; 18 | pub const BIOCSRSIG: c_ulong = 0x80044273; 19 | pub const BIOCGHDRCMPLT: c_ulong = 0x40044274; 20 | pub const BIOCSHDRCMPLT: c_ulong = 0x80044275; 21 | pub const BIOCGSEESENT: c_ulong = 0x40044276; 22 | pub const BIOCSSEESENT: c_ulong = 0x80044277; 23 | pub const BIOCSDLT: c_ulong = 0x80044278; 24 | pub const SIOCGIFADDR: c_ulong = 0xc0206921; 25 | pub const SIOCAIFADDR: c_ulong = 0x8040691a; 26 | pub const SIOCDIFADDR: c_ulong = 0x80206919; 27 | pub const SIOCGIFCONF: c_ulong = 0xc0106924; 28 | pub const SIOCGIFFLAGS: c_ulong = 0xc0206911; 29 | pub const SIOCSIFFLAGS: c_ulong = 0x80206910; 30 | pub const SIOCGIFDSTADDR: c_ulong = 0xc0206922; 31 | pub const SIOCSIFDSTADDR: c_ulong = 0x8020690e; 32 | pub const SIOCGIFBRDADDR: c_ulong = 0xc0206923; 33 | pub const SIOCSIFBRDADDR: c_ulong = 0x80206913; 34 | pub const SIOCGIFNETMASK: c_ulong = 0xc0206925; 35 | pub const SIOCSIFNETMASK: c_ulong = 0x80206916; 36 | pub const SIOCGIFMETRIC: c_ulong = 0xc0206917; 37 | pub const SIOCSIFMETRIC: c_ulong = 0x80206918; 38 | pub const SIOCGIFMTU: c_ulong = 0xc0206933; 39 | pub const SIOCSIFMTU: c_ulong = 0x80206934; 40 | pub const SIOCADDMULTI: c_ulong = 0x80206931; 41 | pub const SIOCDELMULTI: c_ulong = 0x80206932; 42 | 43 | // Berkeley Packet Filter 44 | pub const BPF_ALIGNMENT: c_int = 8; 45 | -------------------------------------------------------------------------------- /src/os/freebsd/libc.rs: -------------------------------------------------------------------------------- 1 | //! FreeBSD standard C library support 2 | use crate::*; 3 | 4 | // std, libc 5 | use libc::{read, write, c_void}; 6 | use std::io; 7 | use std::mem; 8 | 9 | // read_bpf_buf() function 10 | // 11 | /// Receive IP Packet 12 | pub fn read_bpf_buf(bpf_fd: i32, buf: &mut [u8], buf_size: usize) -> io::Result { 13 | // declare len 14 | let len: isize; 15 | 16 | // read from BPF device (unsafe) 17 | unsafe { 18 | len = match read(bpf_fd, buf.as_ptr() as *mut c_void, buf_size) { 19 | -1 => { 20 | eprintln!("error while reading BPF buffer on fd {}, buffer length {}", bpf_fd, buf.len()); 21 | return Err(io::Error::last_os_error()); 22 | } 23 | len => len, 24 | } 25 | } 26 | 27 | // return the length of read buffer 28 | Ok(len) 29 | } 30 | 31 | // raw_sendto() function 32 | /// Send RAW frame/packet 33 | pub fn raw_sendto( 34 | fd: i32, 35 | _ifindex: i32, 36 | frame: &mut Vec, 37 | debug: &Verbose 38 | ) -> io::Result<()> { 39 | unsafe { 40 | // unsafe call to write() 41 | match write(fd, &mut frame[..] as *mut _ as *const c_void, mem::size_of_val(&frame[..])) { 42 | -1 => Err(io::Error::last_os_error()), 43 | _ => { 44 | print_debug(debug, DEBUG_LEVEL_MEDIUM, DEBUG_SRC_BPF, 45 | format!("VRRPv2 frame successfully sent on BPF device, fd {}", fd) 46 | ); 47 | Ok(()) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/os/freebsd/mod.rs: -------------------------------------------------------------------------------- 1 | //! FreeBSD Operating System support 2 | 3 | // FreeBSD constants 4 | #[allow(dead_code)] 5 | pub mod constants; 6 | 7 | // FreeBSD standard C library support 8 | pub mod libc; 9 | 10 | // FreeBSD ARP support 11 | pub mod arp; 12 | 13 | // FreeBSD BPF support 14 | pub mod bpf; 15 | 16 | // FreeBSD network support 17 | pub mod netinet; 18 | -------------------------------------------------------------------------------- /src/os/freebsd/netinet.rs: -------------------------------------------------------------------------------- 1 | //! FreeBSD network support 2 | 3 | // libc 4 | use libc::{IF_NAMESIZE, c_int, c_short, c_uchar, ioctl, AF_INET}; 5 | 6 | // std 7 | use std::io; 8 | use std::ffi::CString; 9 | 10 | // FreeBSD constants 11 | use crate::os::freebsd::constants::*; 12 | 13 | // operating systems drivers 14 | use crate::os::drivers::Operation; 15 | 16 | // IfAliasReq Structure 17 | #[repr(C)] 18 | struct IfAliasReq { 19 | ifr_name: [u8; IF_NAMESIZE], // interface name 20 | ifra_addr: int_sockaddr_in, // IPv4 address 21 | ifra_broadaddr: int_sockaddr_in, // destination address 22 | ifra_mask: int_sockaddr_in, // netmask 23 | ifra_vhid: c_int, 24 | } 25 | 26 | // int_sockaddr alias 27 | #[repr(C)] 28 | struct int_sockaddr_in { 29 | sin_len: c_uchar, 30 | sin_family: c_uchar, 31 | sin_port: c_short, 32 | sin_addr: [c_uchar; 12], 33 | } 34 | 35 | // set_ip_address() function 36 | /// Set IPv4 Address on given interface 37 | pub fn set_ip_address(_fd: i32, ifname: &CString, ip: [u8; 4], netmask: [u8; 4], op: Operation) -> io::Result<()> { 38 | // create a slice of mutable reference to array of 16 u8 39 | let ifname_slice = &mut [0u8; 16]; 40 | // for every bytes/character in name of type Cstring, insert it into the above slice. 41 | for (i, b) in ifname.as_bytes_with_nul().iter().enumerate() { 42 | ifname_slice[i] = *b; 43 | } 44 | // check interface name size 45 | if ifname_slice.len() > IF_NAMESIZE { 46 | panic!("Interface name is longer than {}", IF_NAMESIZE - 1); 47 | } 48 | 49 | // create IP address slice 50 | let ip_addr_slice = &mut [0u8; 12]; 51 | for (i, b) in ip.iter().enumerate() { 52 | ip_addr_slice[i] = *b; 53 | } 54 | 55 | // create IP netmask slice 56 | let ip_netmask_slice = &mut [0u8; 12]; 57 | for (i, b) in netmask.iter().enumerate() { 58 | ip_netmask_slice[i] = *b; 59 | } 60 | 61 | let mut ifaddr = IfAliasReq { 62 | ifr_name: { 63 | let mut buf = [0u8; IF_NAMESIZE]; 64 | buf.clone_from_slice(ifname_slice); 65 | buf 66 | }, 67 | ifra_addr: int_sockaddr_in { 68 | sin_len: 16, 69 | sin_family: AF_INET as u8, 70 | sin_port: 0, 71 | sin_addr: { 72 | let mut data = [0u8; 12]; 73 | data.clone_from_slice(ip_addr_slice); 74 | data 75 | } 76 | }, 77 | ifra_broadaddr: int_sockaddr_in { 78 | sin_len: 0, 79 | sin_family: 0, 80 | sin_port: 0, 81 | sin_addr: [0u8; 12], 82 | }, 83 | ifra_mask: int_sockaddr_in { 84 | sin_len: 16, 85 | sin_family: AF_INET as u8, 86 | sin_port: 0, 87 | sin_addr: { 88 | let mut data = [0u8; 12]; 89 | data.clone_from_slice(ip_netmask_slice); 90 | data 91 | } 92 | }, 93 | ifra_vhid: 0, 94 | }; 95 | 96 | // open new socket for below ioctl 97 | let fd = match unsafe { libc::socket(libc::PF_INET, libc::SOCK_DGRAM, 0) } { 98 | -1 => return Err(io::Error::last_os_error()), 99 | fd => fd, 100 | }; 101 | 102 | // match given operation on IP address 103 | // see man 4 netintro 104 | match op { 105 | Operation::Add => { 106 | // ioctl - set interface's IP address 107 | let res = unsafe { ioctl(fd, SIOCAIFADDR, &mut ifaddr) }; 108 | if res < 0 { 109 | return Err(io::Error::last_os_error()); 110 | } 111 | }, 112 | Operation::Rem => { 113 | // ioctl - remove interface's IP address 114 | let res = unsafe { ioctl(fd, SIOCDIFADDR,&mut ifaddr) }; 115 | if res < 0 { 116 | return Err(io::Error::last_os_error()); 117 | } 118 | } 119 | } 120 | 121 | Ok(()) 122 | } 123 | -------------------------------------------------------------------------------- /src/os/linux/arp.rs: -------------------------------------------------------------------------------- 1 | //! Linux Address Resolution Protocol (ARP) module 2 | //! This module provides ARP related functions. 3 | 4 | // std 5 | use std::io; 6 | 7 | // libc 8 | use libc::{socket, AF_PACKET, ETH_P_ARP, SOCK_RAW}; 9 | 10 | /// Address Resolution Protocol (ARP) Structure 11 | #[repr(C)] 12 | pub struct ARPframe { 13 | // Ethernet Header 14 | pub dst_mac: [u8; 6], // destination MAC address 15 | pub src_mac: [u8; 6], // source MAC address 16 | pub ethertype: u16, // ether type 17 | 18 | // ARP 19 | pub hardware_type: u16, // network link type (0x1=ethernet) 20 | pub protocol_type: u16, // upper-layer protocol for resolution 21 | pub hw_addr_len: u8, // length of hardware address (bytes) 22 | pub proto_addr_len: u8, // upper-layer protocol address length 23 | pub opcode: u16, // operation (0x1=request, 0x2=reply) 24 | pub sender_hw_addr: [u8; 6], // sender hardware address 25 | pub sender_proto_addr: [u8; 4], // internetwork address of sender 26 | pub target_hw_addr: [u8; 6], // hardware address of target 27 | pub target_proto_addr: [u8; 4], // internetwork address of target 28 | } 29 | 30 | // open_raw_socket_arp() function 31 | /// Open raw socket 32 | pub fn open_raw_socket_arp() -> io::Result { 33 | unsafe { 34 | // man 2 socket 35 | // returns a file descriptor or -1 if error. 36 | match socket(AF_PACKET, SOCK_RAW, ETH_P_ARP.to_be() as i32) { 37 | -1 => Err(io::Error::last_os_error()), 38 | fd => Ok(fd), 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/os/linux/filter.rs: -------------------------------------------------------------------------------- 1 | //! Linux Socket Filter module 2 | 3 | // libc 4 | use libc::c_void; 5 | 6 | // SockFilter structure 7 | #[repr(C)] 8 | pub struct SockFilter { 9 | code: u16, // Filter code 10 | jt: u8, // Jump true 11 | jf: u8, // Jump false 12 | k: u32, // Generic multiuse field 13 | } 14 | 15 | // SockFilter implementation 16 | impl SockFilter { 17 | // new_vrrpv2_gid() method 18 | // 19 | // BPF Filter - VRRPv2 Advertisement Packets: 20 | // ldh [12] 21 | // jne #0x800, drop 22 | // ldb [23] 23 | // jneq #0x70, drop 24 | // ldb [34] 25 | // jneq #0x21, drop 26 | // ldb [35] 27 | // jneq #0x1, drop 28 | // ret #-1 29 | // drop: ret #0 30 | // 31 | // BPF Bytecode: 32 | // { 0x28, 0, 0, 0x0000000c }, 33 | // { 0x15, 0, 7, 0x00000800 }, 34 | // { 0x30, 0, 0, 0x00000017 }, 35 | // { 0x15, 0, 5, 0x00000070 }, 36 | // { 0x30, 0, 0, 0x00000022 }, 37 | // { 0x15, 0, 3, 0x00000021 }, 38 | // { 0x30, 0, 0, 0x00000023 }, 39 | // { 0x15, 0, 1, 0x00000001 }, 40 | // { 0x06, 0, 0, 0xffffffff }, 41 | // { 0x06, 0, 0, 0000000000 }, 42 | // 43 | pub fn new_vrrpv2_gid(gid: u8) -> [SockFilter; 10] { 44 | let filter: [SockFilter; 10] = [ 45 | SockFilter { 46 | // 001 47 | code: 0x28, 48 | jt: 0x0, 49 | jf: 0x0, 50 | k: 0x0000000c, 51 | }, 52 | SockFilter { 53 | // 002 54 | code: 0x15, 55 | jt: 0x0, 56 | jf: 0x7, 57 | k: 0x00000800, 58 | }, 59 | SockFilter { 60 | // 003 61 | code: 0x30, 62 | jt: 0x0, 63 | jf: 0x0, 64 | k: 0x00000017, 65 | }, 66 | SockFilter { 67 | // 004 68 | code: 0x15, 69 | jt: 0x0, 70 | jf: 0x5, 71 | k: 0x00000070, 72 | }, 73 | SockFilter { 74 | // 005 75 | code: 0x30, 76 | jt: 0x0, 77 | jf: 0x0, 78 | k: 0x00000022, 79 | }, 80 | SockFilter { 81 | // 006 82 | code: 0x15, 83 | jt: 0x0, 84 | jf: 0x0, 85 | k: 0x00000021, 86 | }, 87 | SockFilter { 88 | // 007 89 | code: 0x30, 90 | jt: 0x0, 91 | jf: 0x0, 92 | k: 0x00000023, 93 | }, 94 | SockFilter { 95 | // 008 96 | code: 0x15, 97 | jt: 0x0, 98 | jf: 0x1, 99 | k: gid as u32, // replace by the group id 100 | }, 101 | SockFilter { 102 | // 009 103 | code: 0x06, 104 | jt: 0x0, 105 | jf: 0x0, 106 | k: 0xffffffff, 107 | }, 108 | SockFilter { 109 | // 010 110 | code: 0x06, 111 | jt: 0x0, 112 | jf: 0x0, 113 | k: 0000000000, 114 | }, 115 | ]; 116 | filter 117 | } 118 | } 119 | 120 | // SockFprog structure 121 | #[repr(C)] 122 | pub struct SockFprog { 123 | len: u16, // Number of filter blocks (size of array) 124 | filter: *const c_void, // Pointer to an array of SockFilter 125 | } 126 | 127 | // SockFprog implementation 128 | impl SockFprog { 129 | // build_fprog_vrrpv2_gid() method 130 | pub fn build_fprog_vrrpv2_gid(filter: &[SockFilter; 10]) -> SockFprog { 131 | let fprog = SockFprog { 132 | filter: filter.as_ptr() as *const c_void, 133 | len: filter.len() as u16, 134 | }; 135 | fprog 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/os/linux/libc.rs: -------------------------------------------------------------------------------- 1 | //! Linux standard C library compatibility 2 | use crate::*; 3 | 4 | // std, libc, ffi 5 | use libc::{setsockopt, socket, AF_PACKET, SOCK_RAW, SOL_SOCKET, SO_ATTACH_FILTER}; 6 | use std::ffi::CString; 7 | use std::io; 8 | use std::mem; 9 | 10 | // Linux socket filter 11 | use crate::os::linux::filter::SockFprog; 12 | 13 | // open_raw_socket_fd() function 14 | /// Open a raw AF_PACKET socket for IPv4 15 | pub fn open_raw_socket_fd() -> io::Result { 16 | unsafe { 17 | // man 2 socket 18 | // returns a file descriptor or -1 if error. 19 | match socket(AF_PACKET, SOCK_RAW, ETHER_P_IP.to_be() as i32) { 20 | -1 => Err(io::Error::last_os_error()), 21 | fd => Ok(fd), 22 | } 23 | } 24 | } 25 | 26 | // set_sock_filter function 27 | /// Set a BPF filter on a socket 28 | pub fn set_sock_filter(sockfd: i32, bpf: &SockFprog) -> io::Result { 29 | unsafe { 30 | // man 2 setsockopt 31 | match setsockopt( 32 | sockfd, 33 | SOL_SOCKET, 34 | SO_ATTACH_FILTER, 35 | bpf as *const _ as *mut c_void, 36 | mem::size_of::() as u32, 37 | ) { 38 | -1 => Err(io::Error::last_os_error()), 39 | ret => Ok(ret), 40 | } 41 | } 42 | } 43 | 44 | // recv_ip_pkts() function 45 | /// Receive IP packets 46 | pub fn recv_ip_pkts(sockfd: i32, sockaddr: &mut sockaddr_ll, buf: &mut [u8]) -> io::Result { 47 | // stack variables 48 | let len: isize; 49 | let mut addr_buf_len: socklen_t = mem::size_of::() as socklen_t; 50 | 51 | unsafe { 52 | // unsafe transmut of sockaddr_ll to a sockaddr type 53 | let addr_ptr: *mut sockaddr = mem::transmute::<*mut sockaddr_ll, *mut sockaddr>(sockaddr); 54 | // unsafe call to libc's recvfrom (man 2 recvfrom) 55 | // returns length of message, -1 if error 56 | len = match recvfrom( 57 | sockfd, // socket file descriptor 58 | buf.as_mut_ptr() as *mut c_void, // pointer to buffer 59 | buf.len(), // buffer length 60 | 0, // flags 61 | addr_ptr as *mut sockaddr, // pointer to source address 62 | &mut addr_buf_len, // address buffer length 63 | ) { 64 | -1 => { 65 | return Err(io::Error::last_os_error()); 66 | } 67 | len => len, 68 | } 69 | } 70 | 71 | Ok(len as usize) 72 | } 73 | 74 | // c_ifnametoindex() function 75 | /// see 'man 3 if_nametoindex' 76 | pub fn c_ifnametoindex(ifname: &String) -> io::Result { 77 | unsafe { 78 | let c_ifname = CString::new(ifname.clone()).unwrap(); 79 | let r = libc::if_nametoindex(c_ifname.as_ptr()); 80 | if r == 0 { 81 | Err(io::Error::last_os_error()) 82 | } else { 83 | Ok(r) 84 | } 85 | } 86 | } 87 | 88 | // raw_sendto() function 89 | /// Send RAW frame/packet 90 | pub fn raw_sendto( 91 | sockfd: i32, 92 | ifindex: i32, 93 | frame: &mut Vec, 94 | _debug: &Verbose, 95 | ) -> io::Result<()> { 96 | // sockaddr_ll (man 7 packet) 97 | let mut sa = libc::sockaddr_ll { 98 | sll_family: libc::AF_PACKET as u16, 99 | sll_protocol: ETHER_P_IP.to_be(), 100 | sll_ifindex: ifindex, 101 | sll_hatype: 0, 102 | sll_pkttype: 0, 103 | sll_halen: 0, 104 | sll_addr: [0; 8], 105 | }; 106 | 107 | unsafe { 108 | // unsafe call to sendto() 109 | let ptr_sockaddr = mem::transmute::<*mut libc::sockaddr_ll, *mut libc::sockaddr>(&mut sa); 110 | match libc::sendto( 111 | sockfd, 112 | &mut frame[..] as *mut _ as *const c_void, 113 | mem::size_of_val(&frame[..]), 114 | 0, 115 | ptr_sockaddr, 116 | mem::size_of_val(&sa) as u32, 117 | ) { 118 | -1 => Err(io::Error::last_os_error()), 119 | _ => Ok(()), 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/os/linux/mod.rs: -------------------------------------------------------------------------------- 1 | //! Linux Operating System support 2 | 3 | // standard C library compatibility 4 | pub mod libc; 5 | // netdev support 6 | pub mod netdev; 7 | // libnl netlink support 8 | pub mod libnl; 9 | // Linux ARP support 10 | pub mod arp; 11 | // Linux Socket Filter support 12 | pub mod filter; 13 | -------------------------------------------------------------------------------- /src/os/linux/netdev.rs: -------------------------------------------------------------------------------- 1 | //! Linux specific network device functions module 2 | //! This module interfaces with the linux netdevice kernel API and related networking functions of the standard C library. 3 | use crate::*; 4 | 5 | // std, libc, ffi 6 | use libc::{ 7 | c_short, c_uchar, c_ulong, c_ushort, ioctl, AF_INET, ARPHRD_ETHER, ETH_ALEN, IFF_PROMISC, 8 | IFF_RUNNING, IFF_UP, IF_NAMESIZE, RTF_UP, 9 | }; 10 | use std::ffi::CString; 11 | use std::io; 12 | 13 | // operating system drivers 14 | use crate::os::drivers::Operation; 15 | 16 | /// ioctl_flags Structure 17 | #[repr(C)] 18 | struct ioctl_flags { 19 | ifr_name: [u8; IF_NAMESIZE], 20 | ifr_flags: c_short, 21 | } 22 | 23 | /// ioctl_4_addr Structure 24 | #[repr(C)] 25 | struct ioctl_v4_addr { 26 | ifr_name: [u8; IF_NAMESIZE], 27 | ifr_addr: int_sockaddr_pad, 28 | } 29 | 30 | /// ioctl_v4_netmask Structure 31 | #[repr(C)] 32 | struct ioctl_v4_netmask { 33 | ifr_name: [u8; IF_NAMESIZE], 34 | ifr_netmask: int_sockaddr_pad, 35 | } 36 | 37 | /// ioctl_v4_route Structure 38 | #[derive(Debug)] 39 | #[repr(C)] 40 | struct ioctl_v4_route { 41 | rt_hash: c_ulong, 42 | rt_dst: int_sockaddr, 43 | rt_gateway: int_sockaddr, 44 | rt_genmask: int_sockaddr, 45 | rt_flags: c_ushort, 46 | rt_pad1: c_short, 47 | rt_pad2: c_ulong, 48 | rt_tos: c_uchar, 49 | rt_class: c_uchar, 50 | rt_pad3: [c_short; 3], // c_short or x3 on 64-bits 51 | rt_metric: c_short, 52 | rt_dev: *const u8, 53 | rt_mtu: c_ulong, 54 | rt_window: c_ulong, 55 | rt_irtt: c_ushort, 56 | } 57 | 58 | /// ioctl_ether_mac Structure 59 | #[repr(C)] 60 | #[derive(Debug)] 61 | struct ioctl_ether_mac { 62 | ifr_name: [u8; IF_NAMESIZE], 63 | ifr_hwaddr: int_sockaddr_ether, 64 | } 65 | 66 | /// internal int_sockaddr_pad Structure 67 | #[repr(C)] 68 | #[derive(Debug)] 69 | struct int_sockaddr_pad { 70 | sa_family: u16, 71 | sa_void: u16, // ensure proper alignement 72 | sa_data: [u8; 14], 73 | } 74 | 75 | /// internal int_sockaddr Structure 76 | #[derive(Debug)] 77 | #[repr(C)] 78 | struct int_sockaddr { 79 | sa_family: u16, 80 | sa_data: [u8; 14], 81 | } 82 | 83 | /// internal int_sockaddr_ether Structure 84 | #[derive(Debug)] 85 | #[repr(C)] 86 | struct int_sockaddr_ether { 87 | sa_family: u16, 88 | sa_data: [u8; ETH_ALEN as usize], 89 | } 90 | 91 | // set_if_promiscuous() function 92 | /// Set (or Unset) interface in promiscuous mode 93 | pub fn set_if_promiscuous(sockfd: i32, ifname: &CString, op: PflagOp) -> io::Result<()> { 94 | // create a slice of mutable reference to array of 16 u8 95 | let ifname_slice = &mut [0u8; 16]; 96 | 97 | // for every bytes/character in name of type Cstring, insert it into the above slice. 98 | for (i, b) in ifname.as_bytes_with_nul().iter().enumerate() { 99 | ifname_slice[i] = *b; 100 | } 101 | 102 | // check interface name size 103 | if ifname_slice.len() > IF_NAMESIZE { 104 | panic!("Interface name is longer than {}", IF_NAMESIZE - 1); 105 | } 106 | 107 | // construct ioctl_flags structure 108 | let mut ifopts = ioctl_flags { 109 | ifr_name: { 110 | let mut buf = [0u8; IF_NAMESIZE]; 111 | // the src and dst must be of the same size. 112 | buf.clone_from_slice(ifname_slice); 113 | buf 114 | }, 115 | ifr_flags: 0, 116 | }; 117 | 118 | // operation to perform on promiscuous flag 119 | match op { 120 | PflagOp::Set => { 121 | // set the flags to UP,RUNNING,PROMISC using bitwise OR operation. 122 | ifopts.ifr_flags |= IFF_UP as c_short | IFF_RUNNING as c_short | IFF_PROMISC as c_short; 123 | let res = unsafe { ioctl(sockfd, libc::SIOCSIFFLAGS, &mut ifopts) }; 124 | if res < 0 { 125 | return Err(io::Error::last_os_error()); 126 | } 127 | } 128 | PflagOp::Unset => { 129 | // unset PROMISC flag 130 | ifopts.ifr_flags |= IFF_UP as c_short | IFF_RUNNING as c_short; 131 | let res = unsafe { ioctl(sockfd, libc::SIOCSIFFLAGS, &mut ifopts) }; 132 | if res < 0 { 133 | return Err(io::Error::last_os_error()); 134 | } 135 | } 136 | } 137 | 138 | Ok(()) 139 | } 140 | 141 | // set_ip_address() function 142 | /// Set an IP address on an interface 143 | pub fn set_ip_address( 144 | sockfd: i32, 145 | ifname: &CString, 146 | ip: [u8; 4], 147 | netmask: [u8; 4], 148 | ) -> io::Result<()> { 149 | // create a slice of mutable reference to array of 16 u8 150 | let ifname_slice = &mut [0u8; 16]; 151 | // for every bytes/character in name of type Cstring, insert it into the above slice. 152 | for (i, b) in ifname.as_bytes_with_nul().iter().enumerate() { 153 | ifname_slice[i] = *b; 154 | } 155 | // check interface name size 156 | if ifname_slice.len() > IF_NAMESIZE { 157 | panic!("Interface name is longer than {}", IF_NAMESIZE - 1); 158 | } 159 | 160 | // create IP address slice 161 | let ip_addr_slice = &mut [0u8; 14]; 162 | for (i, b) in ip.iter().enumerate() { 163 | ip_addr_slice[i] = *b; 164 | } 165 | 166 | // create IP netmask slice 167 | let ip_netmask_slice = &mut [0u8; 14]; 168 | for (i, b) in netmask.iter().enumerate() { 169 | ip_netmask_slice[i] = *b; 170 | } 171 | 172 | // construct ifaddr structure 173 | let mut ifaddr = ioctl_v4_addr { 174 | ifr_name: { 175 | let mut buf = [0u8; IF_NAMESIZE]; 176 | // the src and dst must be of the same size. 177 | buf.clone_from_slice(ifname_slice); 178 | buf 179 | }, 180 | ifr_addr: { 181 | let mut ip_buf = [0u8; 14]; 182 | ip_buf.clone_from_slice(ip_addr_slice); 183 | let addr = int_sockaddr_pad { 184 | sa_family: AF_INET as u16, 185 | sa_void: 0, 186 | sa_data: ip_buf, 187 | }; 188 | addr 189 | }, 190 | }; 191 | 192 | // construct ifnetmask structure 193 | let mut ifnetmask = ioctl_v4_netmask { 194 | ifr_name: { 195 | let mut buf = [0u8; IF_NAMESIZE]; 196 | // the src and dst must be of the same size. 197 | buf.clone_from_slice(ifname_slice); 198 | buf 199 | }, 200 | ifr_netmask: { 201 | let mut netmask_buf = [0u8; 14]; 202 | netmask_buf.clone_from_slice(ip_netmask_slice); 203 | let netmask = int_sockaddr_pad { 204 | sa_family: AF_INET as u16, 205 | sa_void: 0, 206 | sa_data: netmask_buf, 207 | }; 208 | netmask 209 | }, 210 | }; 211 | 212 | // ioctl - set interface's IP address 213 | let res = unsafe { ioctl(sockfd, libc::SIOCSIFADDR, &mut ifaddr) }; 214 | if res < 0 { 215 | return Err(io::Error::last_os_error()); 216 | } 217 | 218 | // ioctl - set interface's netmask 219 | let res = unsafe { ioctl(sockfd, libc::SIOCSIFNETMASK, &mut ifnetmask) }; 220 | if res < 0 { 221 | return Err(io::Error::last_os_error()); 222 | } 223 | 224 | Ok(()) 225 | } 226 | 227 | // get_mac_addr() function 228 | /// Get the MAC address of an interface 229 | /// this function return the interface's MAC address if read sucessfully 230 | pub fn get_mac_addr(sockfd: i32, ifname: &CString, debug: &Verbose) -> io::Result<[u8; 6]> { 231 | // convert interface name to CString type 232 | let ifname = CString::new(ifname.as_bytes() as &[u8]).unwrap(); 233 | 234 | // create a slice of mutable reference to array of 16 u8 235 | let ifname_slice = &mut [0u8; 16]; 236 | 237 | // for every bytes/character in name of type Cstring, insert it into the above slice. 238 | for (i, b) in ifname.as_bytes_with_nul().iter().enumerate() { 239 | ifname_slice[i] = *b; 240 | } 241 | 242 | // check interface name size 243 | if ifname_slice.len() > IF_NAMESIZE { 244 | panic!("Interface name is longer than {}", IF_NAMESIZE - 1); 245 | } 246 | 247 | // constuct ifmac structure 248 | let mut ifmac = ioctl_ether_mac { 249 | ifr_name: { 250 | let mut buf = [0u8; IF_NAMESIZE]; 251 | // the src and dst must be of the same size. 252 | buf.clone_from_slice(ifname_slice); 253 | buf 254 | }, 255 | ifr_hwaddr: { 256 | let mac_buf = [0u8; ETH_ALEN as usize]; 257 | let mac = int_sockaddr_ether { 258 | sa_family: 0, 259 | sa_data: mac_buf, 260 | }; 261 | mac 262 | }, 263 | }; 264 | 265 | // ioctl - set/reset MAC address 266 | print_debug( 267 | debug, 268 | DEBUG_LEVEL_HIGH, 269 | DEBUG_SRC_MAC, 270 | format!("getting mac address on interface {:?}", ifname), 271 | ); 272 | let result = unsafe { ioctl(sockfd, libc::SIOCGIFHWADDR, &mut ifmac) }; 273 | if result < 0 { 274 | return Err(io::Error::last_os_error()); 275 | } 276 | print_debug( 277 | debug, 278 | DEBUG_LEVEL_HIGH, 279 | DEBUG_SRC_MAC, 280 | format!("got interface {:?} mac address {:?}", ifname, ifmac), 281 | ); 282 | 283 | // return the mac address 284 | Ok(ifmac.ifr_hwaddr.sa_data) 285 | } 286 | 287 | // set_mac_addr() function 288 | /// Set the specified MAC address on interface 289 | pub fn set_mac_addr( 290 | sockfd: i32, 291 | ifname: &CString, 292 | mac: [u8; 6], 293 | debug: &Verbose, 294 | ) -> io::Result<()> { 295 | // convert interface name to CString type 296 | let ifname = CString::new(ifname.as_bytes() as &[u8]).unwrap(); 297 | 298 | // create a slice of mutable reference to array of 16 u8 299 | let ifname_slice = &mut [0u8; 16]; 300 | 301 | // for every bytes/character in name of type Cstring, insert it into the above slice. 302 | for (i, b) in ifname.as_bytes_with_nul().iter().enumerate() { 303 | ifname_slice[i] = *b; 304 | } 305 | 306 | // check interface name size 307 | if ifname_slice.len() > IF_NAMESIZE { 308 | panic!("Interface name is longer than {}", IF_NAMESIZE - 1); 309 | } 310 | 311 | // create ethernet mac slice 312 | let ether_mac_slice = &mut [0u8; ETH_ALEN as usize]; 313 | for (i, b) in mac.iter().enumerate() { 314 | ether_mac_slice[i] = *b; 315 | } 316 | 317 | // constuct ifmac structure 318 | let mut ifmac = ioctl_ether_mac { 319 | ifr_name: { 320 | let mut buf = [0u8; IF_NAMESIZE]; 321 | // the src and dst must be of the same size. 322 | buf.clone_from_slice(ifname_slice); 323 | buf 324 | }, 325 | ifr_hwaddr: { 326 | let mut mac_buf = [0u8; ETH_ALEN as usize]; 327 | mac_buf.clone_from_slice(ether_mac_slice); 328 | let mac = int_sockaddr_ether { 329 | sa_family: ARPHRD_ETHER as u16, 330 | sa_data: mac_buf, 331 | }; 332 | mac 333 | }, 334 | }; 335 | 336 | // ioctl - set/reset MAC address 337 | print_debug( 338 | debug, 339 | DEBUG_LEVEL_HIGH, 340 | DEBUG_SRC_MAC, 341 | format!("setting mac address on {:?}, {:?}", ifname, ifmac), 342 | ); 343 | let result = unsafe { ioctl(sockfd, libc::SIOCSIFHWADDR, &mut ifmac) }; 344 | if result < 0 { 345 | return Err(io::Error::last_os_error()); 346 | } 347 | Ok(()) 348 | } 349 | 350 | // set_ip_routes() function 351 | /// Set IP routes into the routing table 352 | /// if boolean 'set_flag' is true then add the route, otherwise remove 353 | pub fn set_ip_route( 354 | sockfd: i32, 355 | ifname: &String, 356 | route: [u8; 4], 357 | rtmask: [u8; 4], 358 | gw: [u8; 4], 359 | metric: i16, 360 | mtu: u64, 361 | op: &Operation, 362 | debug: &Verbose, 363 | ) -> io::Result<()> { 364 | // convert interface name to CString type 365 | let ifname = CString::new(ifname.as_bytes() as &[u8]).unwrap(); 366 | 367 | // create a slice of mutable reference to array of 16 u8 368 | let ifname_slice = &mut [0u8; 16]; 369 | 370 | // for every bytes/character in name of type Cstring, insert it into the above slice. 371 | for (i, b) in ifname.as_bytes_with_nul().iter().enumerate() { 372 | ifname_slice[i] = *b; 373 | } 374 | 375 | // check interface name size 376 | if ifname_slice.len() > IF_NAMESIZE { 377 | panic!("Interface name is longer than {}", IF_NAMESIZE - 1); 378 | } 379 | 380 | // create route slice 381 | let route_slice = &mut [0u8; 14]; 382 | for (i, b) in route.iter().enumerate() { 383 | route_slice[i + 2] = *b; 384 | } 385 | 386 | // create rtmask slice 387 | let rtmask_slice = &mut [0u8; 14]; 388 | for (i, b) in rtmask.iter().enumerate() { 389 | rtmask_slice[i + 2] = *b; 390 | } 391 | 392 | // create gateway slice 393 | let gateway_slice = &mut [0u8; 14]; 394 | for (i, b) in gw.iter().enumerate() { 395 | gateway_slice[i + 2] = *b; 396 | } 397 | 398 | // construct route 399 | let dst_route = int_sockaddr { 400 | sa_family: AF_INET as u16, 401 | //sa_void: 0, 402 | sa_data: { 403 | let mut route_buf = [0u8; 14]; 404 | route_buf.clone_from_slice(route_slice); 405 | route_buf 406 | }, 407 | }; 408 | 409 | // construct route mask 410 | let route_mask = int_sockaddr { 411 | sa_family: AF_INET as u16, 412 | //sa_void: 0, 413 | sa_data: { 414 | let mut mask_buf = [0u8; 14]; 415 | mask_buf.clone_from_slice(rtmask_slice); 416 | mask_buf 417 | }, 418 | }; 419 | 420 | // construct gateway 421 | let gateway = int_sockaddr { 422 | sa_family: AF_INET as u16, 423 | //sa_void: 0, 424 | sa_data: { 425 | let mut gateway_buf = [0u8; 14]; 426 | gateway_buf.clone_from_slice(gateway_slice); 427 | gateway_buf 428 | }, 429 | }; 430 | 431 | // set device name 432 | let mut dev = [0u8; IF_NAMESIZE]; 433 | dev.clone_from_slice(ifname_slice); 434 | 435 | // construct ifroute structure 436 | let mut ifroute = ioctl_v4_route { 437 | rt_hash: 0, 438 | rt_dst: dst_route, // set route 439 | rt_gateway: gateway, // set gateway 440 | rt_genmask: route_mask, // set route's mask 441 | rt_flags: 0, // initialize flags to zero 442 | rt_pad1: 0, 443 | rt_pad2: 0, 444 | rt_tos: 0, 445 | rt_class: 0, 446 | rt_pad3: [0, 0, 0], 447 | rt_metric: metric, // set metric 448 | rt_dev: &dev as *const u8, // set dev 449 | rt_mtu: mtu, 450 | rt_window: 0, 451 | rt_irtt: 0, 452 | }; 453 | // set route flags 454 | ifroute.rt_flags |= RTF_UP | libc::RTF_GATEWAY; 455 | 456 | // ioctl - set/delete route 457 | let res: i32; 458 | match op { 459 | Operation::Add => { 460 | print_debug( 461 | debug, 462 | DEBUG_LEVEL_HIGH, 463 | DEBUG_SRC_ROUTE, 464 | format!("adding route {:?}", ifroute), 465 | ); 466 | res = unsafe { ioctl(sockfd, libc::SIOCADDRT, &mut ifroute) }; 467 | } 468 | Operation::Rem => { 469 | print_debug( 470 | debug, 471 | DEBUG_LEVEL_HIGH, 472 | DEBUG_SRC_ROUTE, 473 | format!("removing route {:?}", ifroute), 474 | ); 475 | res = unsafe { ioctl(sockfd, libc::SIOCDELRT, &mut ifroute) }; 476 | } 477 | } 478 | // check result of ioctls 479 | if res < 0 { 480 | return Err(io::Error::last_os_error()); 481 | } 482 | 483 | Ok(()) 484 | } 485 | -------------------------------------------------------------------------------- /src/os/mod.rs: -------------------------------------------------------------------------------- 1 | //! operating systems support module 2 | 3 | // drivers 4 | pub mod drivers; 5 | 6 | // Linux Operating System support 7 | #[cfg(target_os = "linux")] 8 | pub mod linux; 9 | 10 | // FreeBSD Operating System support 11 | #[cfg(target_os = "freebsd")] 12 | pub mod freebsd; 13 | 14 | // Multi-operating System Support 15 | pub mod multi; 16 | -------------------------------------------------------------------------------- /src/os/multi/libc.rs: -------------------------------------------------------------------------------- 1 | //! Standard C Library Support (multi-os) 2 | 3 | // std 4 | use std::ffi::CStr; 5 | use std::io; 6 | use std::net::{IpAddr, IpAddr::V4, Ipv4Addr, Ipv6Addr}; 7 | use std::ptr; 8 | 9 | // foreign_types 10 | use foreign_types::{ForeignType, ForeignTypeRef}; 11 | 12 | // libc-like getifaddrs() function implementation 13 | // Credit sfackler: https://gist.github.com/sfackler/d614e6c130f3462f443e6c0c6255383a 14 | foreign_type! { 15 | #[derive(Debug)] 16 | pub type IfAddrs: Sync + Send { 17 | type CType = libc::ifaddrs; 18 | fn drop = libc::freeifaddrs; 19 | } 20 | } 21 | 22 | impl IfAddrs { 23 | pub fn get() -> io::Result { 24 | unsafe { 25 | let mut ifaddrs = ptr::null_mut(); 26 | let r = libc::getifaddrs(&mut ifaddrs); 27 | if r == 0 { 28 | Ok(IfAddrs::from_ptr(ifaddrs)) 29 | } else { 30 | Err(io::Error::last_os_error()) 31 | } 32 | } 33 | } 34 | } 35 | 36 | impl IfAddrsRef { 37 | // next() method 38 | pub fn next(&self) -> Option<&IfAddrsRef> { 39 | unsafe { 40 | let next = (*self.as_ptr()).ifa_next; 41 | if next.is_null() { 42 | None 43 | } else { 44 | Some(IfAddrsRef::from_ptr(next)) 45 | } 46 | } 47 | } 48 | 49 | // name() method 50 | pub fn name(&self) -> &str { 51 | unsafe { 52 | let s = CStr::from_ptr((*self.as_ptr()).ifa_name); 53 | s.to_str().unwrap() 54 | } 55 | } 56 | 57 | // addr() method 58 | pub fn addr(&self) -> Option { 59 | unsafe { 60 | let addr = (*self.as_ptr()).ifa_addr; 61 | if addr.is_null() { 62 | return None; 63 | } 64 | 65 | match (*addr).sa_family as _ { 66 | libc::AF_INET => { 67 | let addr = addr as *mut libc::sockaddr_in; 68 | let addr = Ipv4Addr::from((*addr).sin_addr.s_addr.to_be()); 69 | Some(IpAddr::V4(addr)) 70 | } 71 | libc::AF_INET6 => { 72 | let addr = addr as *mut libc::sockaddr_in6; 73 | let addr = Ipv6Addr::from((*addr).sin6_addr.s6_addr); 74 | Some(IpAddr::V6(addr)) 75 | } 76 | _ => None, 77 | } 78 | } 79 | } 80 | 81 | // netmask() method 82 | pub fn netmask(&self) -> Option { 83 | unsafe { 84 | let netmask = (*self.as_ptr()).ifa_netmask; 85 | if netmask.is_null() { 86 | return None; 87 | } 88 | 89 | match (*netmask).sa_family as _ { 90 | libc::AF_INET => { 91 | let netmask = netmask as *mut libc::sockaddr_in; 92 | let netmask = Ipv4Addr::from((*netmask).sin_addr.s_addr.to_be()); 93 | Some(IpAddr::V4(netmask)) 94 | } 95 | libc::AF_INET6 => { 96 | let netmask = netmask as *mut libc::sockaddr_in6; 97 | let netmask = Ipv6Addr::from((*netmask).sin6_addr.s6_addr); 98 | Some(IpAddr::V6(netmask)) 99 | } 100 | _ => None, 101 | } 102 | } 103 | } 104 | 105 | pub fn iter<'a>(&'a self) -> Iter<'a> { 106 | Iter(Some(self)) 107 | } 108 | } 109 | 110 | impl<'a> IntoIterator for &'a IfAddrs { 111 | type Item = &'a IfAddrsRef; 112 | type IntoIter = Iter<'a>; 113 | 114 | fn into_iter(self) -> Iter<'a> { 115 | self.iter() 116 | } 117 | } 118 | 119 | impl<'a> IntoIterator for &'a IfAddrsRef { 120 | type Item = &'a IfAddrsRef; 121 | type IntoIter = Iter<'a>; 122 | 123 | fn into_iter(self) -> Iter<'a> { 124 | self.iter() 125 | } 126 | } 127 | 128 | pub struct Iter<'a>(Option<&'a IfAddrsRef>); 129 | 130 | impl<'a> Iterator for Iter<'a> { 131 | type Item = &'a IfAddrsRef; 132 | 133 | fn next(&mut self) -> Option<&'a IfAddrsRef> { 134 | let cur = match self.0 { 135 | Some(cur) => cur, 136 | None => return None, 137 | }; 138 | 139 | self.0 = cur.next(); 140 | Some(cur) 141 | } 142 | } 143 | 144 | // get_addrlist() function 145 | /// get list of IP address(es) and store them into vectors 146 | pub fn get_addrlist( 147 | ifname: &String, 148 | v4addrs: &mut Vec<[u8; 4]>, 149 | v4masks: &mut Vec<[u8; 4]>, 150 | ) -> io::Result<()> { 151 | // get list of all ip address per interfaces 152 | let addrlist = IfAddrs::get().unwrap(); 153 | // create a vector of tuples (ifname: &str, ipaddr: IpAddr, netmask: IpAddr) 154 | let addrlist = addrlist 155 | .iter() 156 | .map(|a| (a.name(), a.addr(), a.netmask())) 157 | .collect::>(); 158 | 159 | // for every tuples in addrlist: 160 | // if the key matches the vr's interface, push the converted IPv4 address 161 | // into the v4addrs vector. 162 | for t in addrlist { 163 | // take the address and netmask of the matching vr's interface 164 | if t.0.to_lowercase() == *ifname { 165 | if let Some(V4(ip)) = t.1 { 166 | v4addrs.push(ip.octets()); 167 | if let Some(V4(netmask)) = t.2 { 168 | v4masks.push(netmask.octets()) 169 | } 170 | } 171 | } 172 | } 173 | 174 | Ok(()) 175 | } 176 | -------------------------------------------------------------------------------- /src/os/multi/mod.rs: -------------------------------------------------------------------------------- 1 | //! Multi-Operating System Support 2 | // This modules includes codes compatible over multiple operating systems 3 | 4 | // Standard C library 5 | pub mod libc; 6 | -------------------------------------------------------------------------------- /src/packets.rs: -------------------------------------------------------------------------------- 1 | //! packets handling module 2 | //! This module includes the various packets formats and related functions such as internet checksums calculation function. 3 | 4 | // constants 5 | use crate::constants::*; 6 | 7 | // virtual router 8 | use crate::VirtualRouter; 9 | 10 | /// Raw VRRPv2 Packet Format Structure 11 | /// This is the fixed size portion of a possibly VRRPv2 packet 12 | #[repr(C)] 13 | #[derive(Debug, Clone, Copy)] 14 | pub struct VRRPpkt { 15 | // Ethernet frame headers 16 | dst_mac: [u8; 6], // destination MAC address 17 | src_mac: [u8; 6], // source MAC address 18 | ethertype: u16, // ether type 19 | 20 | // IPv4 packet headers 21 | ipver: u8, // IP version and header length 22 | ipdscp: u8, // DSCP 23 | iplength: u16, // length 24 | ipident: u16, // identifier 25 | ipflags: u16, // flags 26 | ipttl: u8, // TTL 27 | ipproto: u8, // IP Protocol 28 | ipchecksum: u16, // Header Checksum 29 | ipsrc: [u8; 4], // source IP address 30 | ipdst: [u8; 4], // destinatin IP address 31 | 32 | // VRRPv2 packet format (RFC3768) 33 | version: u8, // version/type - 4/4 bits 34 | vrid: u8, // virtual router id - 8 bits 35 | prio: u8, // priority - 8 bits 36 | addrcount: u8, // count ip addr - 8 bits 37 | authtype: u8, // auth type - 8 bits 38 | adverint: u8, // advertisement interval - 8 bits 39 | checksum: u16, // checksum - 16 bits 40 | } 41 | 42 | // VRRPpkt methods 43 | impl VRRPpkt { 44 | // getters 45 | pub fn _dst_mac(&self) -> &[u8; 6] { 46 | &self.dst_mac 47 | } 48 | pub fn _src_mac(&self) -> &[u8; 6] { 49 | &self.src_mac 50 | } 51 | pub fn _ethertype(&self) -> &u16 { 52 | &self.ethertype 53 | } 54 | pub fn ipsrc(&self) -> &[u8; 4] { 55 | &self.ipsrc 56 | } 57 | pub fn ipdst(&self) -> &[u8; 4] { 58 | &self.ipdst 59 | } 60 | pub fn ipttl(&self) -> &u8 { 61 | &self.ipttl 62 | } 63 | pub fn ipproto(&self) -> &u8 { 64 | &self.ipproto 65 | } 66 | pub fn version(&self) -> &u8 { 67 | &self.version 68 | } 69 | pub fn vrid(&self) -> &u8 { 70 | &self.vrid 71 | } 72 | pub fn prio(&self) -> &u8 { 73 | &self.prio 74 | } 75 | pub fn addrcount(&self) -> &u8 { 76 | &self.addrcount 77 | } 78 | // safer getter for addrcount, with checks for valid frame size 79 | pub fn s_addrcount(&self, framesize: usize) -> u8 { 80 | // make sure the address count matches the frame size, 81 | // a valid packet with one address should equal 60 bytes 82 | if framesize != 56 + (self.addrcount as usize * 4) as usize { 83 | return 0u8; 84 | } 85 | self.addrcount 86 | } 87 | pub fn authtype(&self) -> &u8 { 88 | &self.authtype 89 | } 90 | pub fn adverint(&self) -> &u8 { 91 | &self.adverint 92 | } 93 | pub fn checksum(&self) -> &u16 { 94 | &self.checksum 95 | } 96 | // gen_advert() method 97 | // generate a VRRPv2 ADVERTISEMENT packet 98 | pub fn gen_advert(vr: &VirtualRouter) -> VRRPpkt { 99 | // Ethernet frame headers: 100 | // dst multicast MAC address for 224.0.0.18 101 | let dst_mac = ETHER_VRRP_V2_DST_MAC; 102 | // generate source MAC address from VID 103 | let mut src_mac = ETHER_VRRP_V2_SRC_MAC; 104 | src_mac[5] = vr.parameters.vrid(); 105 | // ipv4 ethertype 106 | let ethertype = ETHER_P_IP.to_be(); 107 | 108 | // IPv4 headers: 109 | let ipver = IP_V4_VERSION; 110 | // dscp (CS6) 111 | let ipdscp = IP_DSCP_CS6; 112 | // lowest total packet length (header+data) 113 | let iplength = 40u16.to_be(); 114 | // identification and flags fields to zeros 115 | let ipident = 0x0000; 116 | let ipflags = 0x0000; 117 | // TTL must be set to 255 118 | let ipttl = IP_TTL_VRRP_MINTTL; 119 | // VRRPv2 is IP Proto 112 120 | let ipproto = IP_UPPER_PROTO_VRRP; 121 | // internet checksum (set to all zeros) 122 | let ipchecksum = 0x0000; 123 | // source packet from interface 'primary' ip address 124 | let ipsrc = vr.parameters.primary_ip(); 125 | // VRRPv2 multicast group 126 | let ipdst = VRRP_V2_IP_MCAST_DST; 127 | 128 | // VRRPv2 ADVERTISEMENT: 129 | // version = 0x2 130 | // type = 0x1 (ADVERTISEMENT) 131 | let version = VRRP_V2_ADVERT_VERSION_TYPE; 132 | // virtual router id 133 | let vrid = vr.parameters.vrid(); 134 | let prio = vr.parameters.prio(); 135 | let addrcount = vr.parameters.addrcount(); 136 | let authtype = vr.parameters.authtype(); 137 | let adverint = vr.parameters.adverint(); 138 | // generate checksum on VRRP message 139 | let checksum = 0; 140 | 141 | // return the built VRRP ADVERTISEMENT packet 142 | VRRPpkt { 143 | dst_mac, 144 | src_mac, 145 | ethertype, 146 | ipver, 147 | ipdscp, 148 | iplength, 149 | ipident, 150 | ipflags, 151 | ipttl, 152 | ipproto, 153 | ipchecksum, 154 | ipsrc, 155 | ipdst, 156 | version, 157 | vrid, 158 | prio, 159 | addrcount, 160 | authtype, 161 | adverint, 162 | checksum, 163 | } 164 | } 165 | } 166 | 167 | // as_u8_slice() unsafe function 168 | /// transform type T as slice of u8 169 | pub unsafe fn as_u8_slice(p: &T) -> &[u8] { 170 | ::std::slice::from_raw_parts((p as *const T) as *const u8, ::std::mem::size_of::()) 171 | } 172 | -------------------------------------------------------------------------------- /src/protocols.rs: -------------------------------------------------------------------------------- 1 | //! protocols module 2 | //! This module includes networking protocols data structures and related functions. 3 | 4 | /// Protocols Structure 5 | #[derive(Debug)] 6 | pub struct Protocols { 7 | pub r#static: Option>, 8 | } 9 | 10 | // Protocols Type Imlementation 11 | impl Protocols { 12 | // new() method 13 | pub fn _new(r#static: Option>) -> Protocols { 14 | Protocols { r#static } 15 | } 16 | } 17 | 18 | /// Static Protocol Structure 19 | #[derive(Debug)] 20 | pub struct Static { 21 | route: [u8; 4], 22 | mask: [u8; 4], 23 | nh: [u8; 4], 24 | metric: i16, 25 | mtu: u64, 26 | } 27 | 28 | // Static Protocol Type Implementation 29 | impl Static { 30 | // new() method 31 | pub fn new(route: [u8; 4], mask: [u8; 4], nh: [u8; 4], metric: i16, mtu: u64) -> Static { 32 | Static { 33 | route, 34 | mask, 35 | nh, 36 | metric, 37 | mtu, 38 | } 39 | } 40 | // route() getter 41 | pub fn route(&self) -> [u8; 4] { 42 | self.route 43 | } 44 | // mask() getter 45 | pub fn mask(&self) -> [u8; 4] { 46 | self.mask 47 | } 48 | // nh() getter 49 | pub fn nh(&self) -> [u8; 4] { 50 | self.nh 51 | } 52 | // metric() getter 53 | pub fn metric(&self) -> i16 { 54 | self.metric 55 | } 56 | // mtu() getter 57 | pub fn mtu(&self) -> u64 { 58 | self.mtu 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/threads.rs: -------------------------------------------------------------------------------- 1 | //! threads pool module 2 | //! This module implement the thread pool. 3 | use super::*; 4 | 5 | // concurrency 6 | use std::sync::Arc; 7 | use std::sync::RwLock; 8 | use std::thread; 9 | 10 | // channels 11 | use std::sync::mpsc; 12 | 13 | // debugging 14 | use crate::debug::Verbose; 15 | 16 | // finite state machine 17 | use fsm::{fsm_run, Event}; 18 | 19 | /// ThreadPool Structure 20 | pub struct ThreadPool { 21 | workers: Vec, 22 | } 23 | 24 | // ThreadPool Implementation 25 | impl ThreadPool { 26 | // new() method 27 | // Create a new Thread Pool 28 | pub fn new(vrouters: &Vec>>, debug: &Verbose) -> ThreadPool { 29 | // verify the vector is not empty and doesn't exceed 1024 virtual routers 30 | assert!(vrouters.len() > 0 && vrouters.len() < 1024); 31 | 32 | // built a fixed-size vector of workers 33 | let mut workers = Vec::with_capacity(vrouters.len()); 34 | 35 | // creating individual workers for every virtual routers 36 | for (id, vr) in vrouters.iter().enumerate() { 37 | // acquire read lock on vr 38 | let vro = vr.read().unwrap(); 39 | // create new worker 40 | workers.push(Worker::new(id, Arc::clone(&vr), vro.parameters.fd(), debug)); 41 | } 42 | 43 | ThreadPool { workers } 44 | } 45 | // startup() method 46 | // Send startup event to every worker threads 47 | pub fn startup(&self, vrouters: &Vec>>, debug: &Verbose) { 48 | for (id, vr) in vrouters.iter().enumerate() { 49 | // acquire read lock on vr 50 | let vr = vr.read().unwrap(); 51 | // print debugging information 52 | print_debug( 53 | debug, 54 | DEBUG_LEVEL_EXTENSIVE, 55 | DEBUG_SRC_THREAD, 56 | format!("sending Startup event to worker threads"), 57 | ); 58 | match vr.parameters.notification() { 59 | Some(tx) => tx.lock().unwrap().send(Event::Startup).unwrap(), 60 | None => eprintln!("error(thread): cannot send Startup event for thread {}, channel does not exist", id), 61 | } 62 | } 63 | } 64 | // drop() method 65 | // Custom destructor function for the Thread pool 66 | pub fn drop(&mut self, vrouters: &Vec>>, debug: &Verbose) { 67 | print_debug( 68 | debug, 69 | DEBUG_LEVEL_LOW, 70 | DEBUG_SRC_THREAD, 71 | format!("signaling workers to shut down"), 72 | ); 73 | // send Shutdown/Terminate events to all workers 74 | for (id, vr) in vrouters.iter().enumerate() { 75 | // acquire read lock on vr 76 | let vr = vr.read().unwrap(); 77 | match vr.parameters.notification() { 78 | Some(tx) => { 79 | // send Shutdown event 80 | tx.lock().unwrap().send(Event::Shutdown).unwrap(); 81 | // send Terminate event 82 | tx.lock().unwrap().send(Event::Terminate).unwrap(); 83 | } 84 | None => eprintln!( 85 | "error(thread): cannot send Terminate event for {}, channel does not exist", 86 | id 87 | ), 88 | } 89 | } 90 | 91 | // waiting for threads 92 | for worker in &mut self.workers { 93 | // print debugging information 94 | print_debug( 95 | debug, 96 | DEBUG_LEVEL_HIGH, 97 | DEBUG_SRC_THREAD, 98 | format!("waiting for thread {} to exit...", worker.id), 99 | ); 100 | // take the thread out of the worker stucture and leave a None 101 | if let Some(thread) = worker.thread.take() { 102 | // Wait for the thread to finish 103 | thread.join().unwrap(); 104 | } 105 | } 106 | } 107 | } 108 | 109 | // new Trait FnBox to take ownership of a Self stored in a Box 110 | pub trait FnBox { 111 | fn call_box(self: Box); 112 | } 113 | 114 | // new FnBox trait for any type F having the Trait FnOnce() 115 | impl FnBox for F { 116 | // every FnOnce() closures can now call the .call_box() method 117 | fn call_box(self: Box) { 118 | (*self)() 119 | } 120 | } 121 | 122 | /// Worker Structure 123 | pub struct Worker { 124 | id: usize, 125 | // we wrap thread::JoinHandle in a Option so we can 126 | // consume the thread later when calling .join(). 127 | thread: Option>, 128 | } 129 | 130 | // Worker Implementation 131 | impl Worker { 132 | // new() method 133 | fn new(id: usize, vr: Arc>, sockfd: i32, debug: &Verbose) -> Worker { 134 | // creating a pair of sender and receiver channels 135 | let (sender, receiver) = mpsc::channel(); 136 | let receiver = Arc::new(Mutex::new(receiver)); 137 | let sender = Arc::new(Mutex::new(sender)); 138 | 139 | // clone VR for worker thread 140 | let worker_vr = Arc::clone(&vr); 141 | 142 | // clone event channels 143 | let worker_tx = Arc::clone(&sender); 144 | let worker_rx = Arc::clone(&receiver); 145 | 146 | // clone debug 147 | let debug = debug.clone(); 148 | 149 | // create worker thread 150 | let worker_thread = thread::spawn(move || { 151 | // print debugging information 152 | print_debug( 153 | &debug, 154 | DEBUG_LEVEL_EXTENSIVE, 155 | DEBUG_SRC_THREADP, 156 | format!("spawning worker thread {}", id), 157 | ); 158 | fsm_run(id, &worker_tx, &worker_rx, &worker_vr, sockfd, &debug); 159 | }); 160 | 161 | Worker { 162 | id, 163 | thread: Some(worker_thread), 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/timers.rs: -------------------------------------------------------------------------------- 1 | //! Timers related functions module 2 | //! This module implements the various timers threads using tokio. 3 | use super::*; 4 | 5 | // tokio 6 | use tokio::prelude::*; 7 | use tokio::timer::Interval; 8 | 9 | // futures 10 | use futures::Future; 11 | 12 | // channels 13 | use std::sync::mpsc; 14 | 15 | // debugging 16 | use crate::debug::{print_debug, Verbose}; 17 | 18 | // fsm 19 | use crate::fsm::Event; 20 | 21 | // std 22 | use std::sync::RwLock; 23 | use std::time::{Duration, Instant}; 24 | 25 | // start_timers() function 26 | /// starts the various timers needed for the protocol and internal operations 27 | pub fn start_timers( 28 | tx: Arc>>, 29 | vr: Arc>, 30 | debug: &Verbose, 31 | ) { 32 | // clone vr's Arc 33 | let vr0 = Arc::clone(&vr); 34 | // acquire read lock on virtual router 35 | let vr0 = vr0.read().unwrap(); 36 | 37 | // clone debug 38 | let debug = debug.clone(); 39 | 40 | // set duration from vr's timer 41 | let master_down = Duration::from_secs(vr0.timers.master_down() as u64); 42 | let advert = Duration::from_secs(vr0.timers.advert() as u64); 43 | 44 | // drop the lock as we don't need read access to vr anymore 45 | drop(vr0); 46 | 47 | // clone tx channel 48 | let tx1 = Arc::clone(&tx); 49 | // clone the vr's Arc 50 | let vr1 = Arc::clone(&vr); 51 | let vr2 = Arc::clone(&vr); 52 | 53 | // new instance of 'master_down' interval 54 | // this is a countdown-type timer which verify that at least one ADVERTISEMEMNT 55 | // packet has been received since the last call of the timer. If the flag equal 56 | // 0x1, no ADVERTISEMENT has been received (since) and the master is signaled 57 | // down to the approriate vr's thread, this timer share the 'tx' channel with. 58 | let master_down_int = Interval::new_interval(master_down) 59 | .take_while(move |_| future::ok(is_master_down_disabled(&vr1, &debug))) 60 | .for_each(move |_| { 61 | print_debug( 62 | &debug, 63 | DEBUG_LEVEL_HIGH, 64 | DEBUG_SRC_TIMER, 65 | format!("master_down interval has expired"), 66 | ); 67 | 68 | // first acquire read lock on vr 69 | let vr2 = vr2.read().unwrap(); 70 | 71 | // if flag is already set, then signal master down 72 | if vr2.flags.get_down_flag() == 0x1 { 73 | print_debug( 74 | &debug, 75 | DEBUG_LEVEL_EXTENSIVE, 76 | DEBUG_SRC_TIMER, 77 | format!("signaling Master down"), 78 | ); 79 | // acquire transmit channel lock 80 | let tx1 = tx1.lock().unwrap(); 81 | // send MasterDown Event down the channel 82 | match tx1.send(Event::MasterDown) { 83 | Ok(_) => (), 84 | Err(_) => (), // ignore errors from now 85 | } 86 | // return Ok(()) 87 | Ok(()) 88 | } 89 | // check if down flag is set 90 | else if vr2.flags.get_down_flag() == 0x0 { 91 | print_debug( 92 | &debug, 93 | DEBUG_LEVEL_EXTENSIVE, 94 | DEBUG_SRC_TIMER, 95 | format!("signaling master_down timer expiry"), 96 | ); 97 | // acquire transmit channel lock 98 | let tx1 = tx1.lock().unwrap(); 99 | // send MasterDown Event down the channel 100 | tx1.send(Event::MasterDownExpiry).unwrap(); 101 | // return Ok(()) 102 | Ok(()) 103 | } else { 104 | Ok(()) 105 | } 106 | }) 107 | .map_err(|_| ()); 108 | 109 | // clone transmit channel 110 | let tx2 = Arc::clone(&tx); 111 | // clone the vr's Arc 112 | let vr3 = Arc::clone(&vr); 113 | 114 | // new advertisement interval-type timer 115 | // when this future executes, a GenAdvert notification is sent to the worker thread 116 | // which then trigger an ADVERTISEMENT message in some finite state machine states. 117 | let advert_int = Interval::new(Instant::now() + advert, advert) 118 | // must return true to activate the interval timer 119 | .take_while(move |_| future::ok(is_advert_disabled(&vr3, &debug))) 120 | .for_each(move |_| { 121 | // print debugging information 122 | print_debug( 123 | &debug, 124 | DEBUG_LEVEL_HIGH, 125 | DEBUG_SRC_TIMER, 126 | format!("advertisement interval has expired"), 127 | ); 128 | // acquire lock on transmit channel 129 | let tx2 = tx2.lock().unwrap(); 130 | // print debugging information 131 | print_debug( 132 | &debug, 133 | DEBUG_LEVEL_EXTENSIVE, 134 | DEBUG_SRC_TIMER, 135 | format!("signaling advertisement interval expiry"), 136 | ); 137 | // send GenAdvert event to worker thread 138 | tx2.send(Event::GenAdvert).unwrap(); 139 | // return Ok(()) 140 | Ok(()) 141 | }) 142 | .map_err(|_| ()); 143 | 144 | // start the tokio runtime 145 | tokio::run(future::lazy(|| { 146 | tokio::spawn(master_down_int); 147 | tokio::spawn(advert_int); 148 | Ok(()) 149 | })); 150 | } 151 | 152 | // is_master_down_disabled() function 153 | /// return boolean false is the master_down interval is zero or lower 154 | fn is_master_down_disabled(vr: &Arc>, debug: &Verbose) -> bool { 155 | let vr = vr.read().unwrap(); 156 | if vr.timers.master_down() > 0.0 { 157 | true 158 | } else { 159 | // print debugging information 160 | print_debug( 161 | debug, 162 | DEBUG_LEVEL_MEDIUM, 163 | DEBUG_SRC_TIMER, 164 | format!("master_down timer is disabled"), 165 | ); 166 | false 167 | } 168 | } 169 | 170 | // is_advert_disabled() function 171 | /// return boolean true is the advertisement interval vr's timer 172 | /// is higher than zero 173 | fn is_advert_disabled(vr: &Arc>, debug: &Verbose) -> bool { 174 | let vr = vr.read().unwrap(); 175 | if vr.timers.advert() > 0 { 176 | true 177 | } else if vr.timers.advert() == 255 { 178 | // special non-zero value to disable timer 179 | true 180 | } else { 181 | // print debugging information 182 | print_debug( 183 | debug, 184 | DEBUG_LEVEL_MEDIUM, 185 | DEBUG_SRC_TIMER, 186 | format!("the advertisement timer is disabled"), 187 | ); 188 | false 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /utils/rvrrpd-pw/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # local configuration files 13 | *.local.conf 14 | -------------------------------------------------------------------------------- /utils/rvrrpd-pw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rvrrpd-pw" 3 | version = "0.1.0" 4 | authors = ["Nicolas Chabbey "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | clap = "2.33" 9 | rand = "0.7" 10 | sha2 = "0.8" 11 | scrypt = "0.2" 12 | -------------------------------------------------------------------------------- /utils/rvrrpd-pw/Makefile: -------------------------------------------------------------------------------- 1 | TARGET = target/release 2 | BINARY = rvrrpd-pw 3 | PREFIX = /usr 4 | 5 | main: 6 | @cargo build --release 7 | 8 | test: 9 | @cargo test 10 | 11 | check: 12 | @cargo fmt --all -- --check 13 | 14 | clean: 15 | @cargo clean 16 | 17 | install: 18 | if [ ! -d $(DESTDIR)$(PREFIX)/bin ]; then \ 19 | mkdir -p $(DESTDIR)$(PREFIX)/bin; \ 20 | fi 21 | cp $(TARGET)/${BINARY} $(DESTDIR)$(PREFIX)/bin/${BINARY} 22 | chmod 755 $(DESTDIR)$(PREFIX)/bin/${BINARY} 23 | 24 | .PHONY: main test check clean install 25 | -------------------------------------------------------------------------------- /utils/rvrrpd-pw/src/main.rs: -------------------------------------------------------------------------------- 1 | //! rVRRPd-pw - rVRRPd password utility 2 | //! This program provides a fast and easy way to generate hashed passwords for rVRRPd, 3 | //! a fast and highly-secure VRRP daemon. 4 | 5 | // std 6 | use std::error::Error; 7 | use std::fmt; 8 | 9 | // clap 10 | extern crate clap; 11 | use clap::{App, Arg}; 12 | 13 | // rand 14 | extern crate rand; 15 | use rand::prelude::Rng; 16 | 17 | // sha256 18 | extern crate sha2; 19 | use sha2::{Digest, Sha256}; 20 | 21 | // scrypt 22 | extern crate scrypt; 23 | use scrypt::{scrypt_simple, ScryptParams}; 24 | 25 | // MyError type 26 | #[derive(Debug)] 27 | struct MyError { 28 | msg: String, 29 | } 30 | 31 | impl MyError { 32 | fn new(msg: &str) -> MyError { 33 | MyError { 34 | msg: msg.to_string(), 35 | } 36 | } 37 | } 38 | 39 | impl fmt::Display for MyError { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | write!(f, "Error: {}", self.msg) 42 | } 43 | } 44 | 45 | impl Error for MyError { 46 | fn description(&self) -> &str { 47 | &self.msg 48 | } 49 | } 50 | 51 | // main() function 52 | fn main() { 53 | let matches = App::new("rVRRPd password utility") 54 | .version("v0.1.0") 55 | .author("Nicolas Chabbey ") 56 | .about("Quick and easy password generation for rVRRPd") 57 | .arg( 58 | Arg::with_name("user") 59 | .short("u") 60 | .long("user") 61 | .takes_value(true) 62 | .help("user name") 63 | .required(true) 64 | .index(1), 65 | ) 66 | .arg( 67 | Arg::with_name("password") 68 | .short("p") 69 | .long("password") 70 | .takes_value(true) 71 | .help("user's password") 72 | .required(true) 73 | .index(2), 74 | ) 75 | .arg( 76 | Arg::with_name("alg") 77 | .short("a") 78 | .long("hash-alg") 79 | .takes_value(true) 80 | .help("hashing algorithm (default: sha256)") 81 | .index(3), 82 | ) 83 | .after_help("HASHING ALGS:\n\ 84 | sha256\t\tSHA2 (256 bits)\n\ 85 | scrypt\t\tscrypt (interactive)\n") 86 | .get_matches(); 87 | 88 | let user = matches.value_of("user").unwrap(); 89 | let passwd = matches.value_of("password").unwrap(); 90 | let alg = matches.value_of("alg").unwrap_or("sha256"); 91 | 92 | match gen_hashed_pw(user, passwd, alg) { 93 | Err(e) => { 94 | eprintln!("{}", e); 95 | std::process::exit(1); 96 | } 97 | Ok(_) => std::process::exit(0), 98 | } 99 | } 100 | 101 | /// gen_hashed_pw() function 102 | /// Print new user account information 103 | fn gen_hashed_pw(user: &str, passwd: &str, alg: &str) -> Result<(), MyError> { 104 | match alg { 105 | "sha256" => { 106 | let mut rng = rand::thread_rng(); 107 | let r = rng.gen::(); 108 | let salt = format!("{:x}", r); 109 | match gen_sha256_hash(&passwd, &salt) { 110 | Some(h) => { 111 | display_userpw_line(alg, user, Some(salt), h); 112 | } 113 | None => { 114 | let err = format!("the {} hashing function failed", alg); 115 | return Err(MyError::new(&err)); 116 | } 117 | } 118 | } 119 | "scrypt" => match gen_scrypt_hash(passwd) { 120 | Some(h) => { 121 | display_userpw_line(alg, user, None, h); 122 | } 123 | None => { 124 | let err = format!("the {} hashing function failed", alg); 125 | return Err(MyError::new(&err)); 126 | } 127 | }, 128 | _ => { 129 | let err = format!("unknown hashing algorithm {}", alg); 130 | return Err(MyError::new(&err)); 131 | } 132 | } 133 | 134 | Ok(()) 135 | } 136 | 137 | /// gen_sha256_hash() function 138 | /// Generate a SHA256 hashed password with random salt 139 | fn gen_sha256_hash(passwd: &str, salt: &String) -> Option { 140 | // create new sha256 hasher 141 | let mut hasher = Sha256::new(); 142 | // feed the hasher with the user supplied password 143 | hasher.input(&passwd); 144 | // feed the hasher with the supplied salt 145 | hasher.input(salt); 146 | // get the result 147 | let h = hasher.result(); 148 | // format the result in a hexadecimal string 149 | let hash = format!("{:x}", h); 150 | // return the hash 151 | Some(hash) 152 | } 153 | 154 | /// gen_scrypt_hash() function 155 | /// Generate a scrypt based password 156 | fn gen_scrypt_hash(passwd: &str) -> Option { 157 | // create scrypt parameters with sound values 158 | let params = ScryptParams::new(4, 8, 1).unwrap(); 159 | // hash the given password 160 | let hash = scrypt_simple(&passwd, ¶ms).expect("PNRG failure"); 161 | // return the hash 162 | Some(hash) 163 | } 164 | 165 | /// display_userpw_line() function 166 | /// Display the user password line for inclusion in rVRRPd configuration 167 | fn display_userpw_line(alg: &str, user: &str, salt: Option, hash: String) { 168 | // not implemented yet 169 | let level = 0; 170 | println!( 171 | "{{{{{}}}}}{}:{}:{}:{}", 172 | alg.to_uppercase(), 173 | user, 174 | level, 175 | salt.unwrap_or("".to_string()), 176 | hash 177 | ); 178 | } 179 | --------------------------------------------------------------------------------