Import wmediumd

Test: n/a
Bug: 191918323
Change-Id: I906c45cb04c2aa8cb556adff5a233e27b8114d8b
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..f74f9cf
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,13 @@
+name: "wmediumd"
+description:
+    "This is a wireless medium simulation tool for Linux."
+
+third_party {
+  url {
+    type: GIT
+    value: "https://github.com/bcopeland/wmediumd"
+  }
+  version: "717e5d7fcc23eecbc8e32bd897a8fd4b1e3ba640"
+  last_upgrade_date { year: 2021 month: 6 day: 24 }
+  license_type: RESTRICTED
+}
diff --git a/MODULE_LICENSE_GPL b/MODULE_LICENSE_GPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_GPL
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ff67257
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+SHELL=/bin/sh
+MAKE = make
+SUBDIRS ?= wmediumd
+BIN = wmediumd/wmediumd
+PREFIX ?= /usr
+BINDIR ?= /bin
+
+all:
+
+	@for i in $(SUBDIRS); do \
+	echo "make all in $$i..."; \
+	(cd $$i; $(MAKE) all); done
+
+clean:
+
+	@for i in $(SUBDIRS); do \
+	echo "Clearing in $$i..."; \
+	(cd $$i; $(MAKE) clean); done
+
+install: all
+	install -Dm 0755 $(BIN) $(DESTDIR)$(PREFIX)$(BINDIR)
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..b9f5251
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,4 @@
+jeongik@google.com
+jaeman@google.com
+sundongahn@google.com
+jooyung@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..354aa1e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,230 @@
+# Introduction
+
+This is a wireless medium simulation tool for Linux, based on the netlink API
+implemented in the `mac80211_hwsim` kernel driver.  Unlike the default in-kernel
+forwarding mode of `mac80211_hwsim`, wmediumd allows simulating frame loss and
+delay.
+
+This version is forked from an earlier version, hosted here:
+
+    https://github.com/cozybit/wmediumd
+
+# Prerequisites
+
+First, you need a recent Linux kernel with the `mac80211_hwsim` module
+available.  If you do not have this module, you may be able to build it using
+the [backports project](https://backports.wiki.kernel.org/index.php/Main_Page).
+
+Wmediumd requires libnl3.0.
+
+# Building
+```
+cd wmediumd && make
+```
+
+# Using Wmediumd
+
+Starting wmediumd with an appropriate config file is enough to make frames
+pass through wmediumd:
+```
+sudo modprobe mac80211_hwsim radios=2
+sudo ./wmediumd/wmediumd -c tests/2node.cfg &
+# run some hwsim test
+```
+However, please see the next section on some potential pitfalls.
+
+A complete example using network namespaces is given at the end of
+this document.
+
+# Configuration
+
+Wmediumd supports multiple ways of configuring the wireless medium.
+
+## Perfect medium
+
+With this configuration, all traffic flows between the configured interfaces, identified by their mac address:
+
+```
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+};
+```
+
+## Per-link loss probability model
+
+You can simulate a slightly more realistic channel by assigning fixed error
+probabilities to each link.
+
+```
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+};
+
+model:
+{
+	type = "prob";
+
+	default_prob = 1.0;
+	links = (
+		(0, 2, 0.000000),
+		(2, 3, 0.000000)
+	);
+};
+```
+
+The above configuration would assign 0% loss probability (perfect medium) to
+all frames flowing between nodes 0 and 2, and 100% loss probability to all
+other links.  Unless both directions of a link are configured, the loss
+probability will be symmetric.
+
+This is a very simplistic model that does not take into account that losses
+depend on transmission rates and signal-to-noise ratio.  For that, keep reading.
+
+## Per-link signal-to-noise ratio (SNR) model
+
+You can model different signal-to-noise ratios for each link by including a
+list of link tuples in the form of (sta1, sta2, snr).
+
+```
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+
+	links = (
+		(0, 1, 0),
+		(0, 2, 0),
+		(2, 0, 10),
+		(0, 3, 0),
+		(1, 2, 30),
+		(1, 3, 10),
+		(2, 3, 20)
+	);
+};
+```
+The snr will affect the maximum data rates that are successfully transmitted
+over the link.
+
+If only one direction of a link is configured, then the link will be
+symmetric.  For asymmetric links, configure both directions, as in the
+above example where the path between 0 and 2 is usable in only one
+direction.
+
+The packet loss error probabilities are derived from this snr.  See function
+`get_error_prob_from_snr()`.  Or you can provide a packet-error-rate table like
+the one in `tests/signal_table_ieee80211ax`
+
+## Path loss model
+
+The path loss model derives signal-to-noise and probabilities from the
+coordinates of each node.  This is an example configuration file for it.
+
+```
+ifaces : {...};
+model :
+{
+	type = "path_loss";
+	positions = (
+		(-50.0,   0.0),
+		(  0.0,  40.0),
+		(  0.0, -70.0),
+		( 50.0,   0.0)
+	);
+	directions = (
+		(  0.0,   0.0),
+		(  0.0,  10.0),
+		(  0.0,  10.0),
+		(  0.0,   0.0)
+	);
+	tx_powers = (15.0, 15.0, 15.0, 15.0);
+
+	model_name = "log_distance";
+	path_loss_exp = 3.5;
+	xg = 0.0;
+};
+```
+
+## Gotchas
+
+### Allowable MAC addresses
+
+The kernel only allows wmediumd to work on the second available hardware
+address, which has bit 6 set in the most significant octet
+(i.e. 42:00:00:xx:xx:xx, not 02:00:00:xx:xx:xx).  Set this appropriately
+using 'ip link set address'.
+
+This issue was fixed in commit cd37a90b2a417e5882414e19954eeed174aa4d29
+in Linux, released in kernel 4.1.0.
+
+### Rates
+
+wmediumd's rate table is currently hardcoded to 802.11a OFDM rates.
+Therefore, either operate wmediumd networks in 5 GHz channels, or supply
+a rateset for the BSS with no CCK rates.
+
+### Send-to-self
+
+By default, traffic between local devices in Linux will not go over
+the wire / wireless medium.  This is true of vanilla hwsim as well.
+In order to make this happen, you need to either run the hwsim interfaces
+in separate network namespaces, or you need to set up routing rules with
+the hwsim devices at a higher priority than local forwarding.
+
+`tests/test-001.sh` contains an example of the latter setup.
+
+# Example session
+
+The following sequence of commands establishes a two-node mesh using network
+namespaces.
+```
+sudo modprobe -r mac80211_hwsim
+sudo modprobe mac80211_hwsim
+sudo ./wmediumd/wmediumd -c ./tests/2node.cfg
+
+# in window 2
+sudo lxc-unshare -s NETWORK bash
+ps | grep bash  # note pid
+
+# in window 1
+sudo iw phy phy2 set netns $pid
+
+sudo ip link set wlan1 down
+sudo iw dev wlan1 set type mp
+sudo ip link set addr 42:00:00:00:00:00 dev wlan1
+sudo ip link set wlan1 up
+sudo ip addr add 10.10.10.1/24 dev wlan1
+sudo iw dev wlan1 set channel 149
+sudo iw dev wlan1 mesh join meshabc
+
+# in window 2
+ip link set lo
+
+sudo ip link set wlan2 down
+sudo iw dev wlan2 set type mp
+sudo ip link set addr 42:00:00:00:01:00 dev wlan2
+sudo ip link set wlan2 up
+sudo ip addr add 10.10.10.2/24 dev wlan2
+sudo iw dev wlan2 set channel 149
+sudo iw dev wlan2 mesh join meshabc
+
+iperf -u -s -i 10 -B 10.10.10.2
+
+# in window 1
+iperf -u -c 10.10.10.2 -b 100M -i 10 -t 120
+```
diff --git a/tests/2node.cfg b/tests/2node.cfg
new file mode 100644
index 0000000..3ce3bea
--- /dev/null
+++ b/tests/2node.cfg
@@ -0,0 +1,5 @@
+ifaces :
+{
+	count = 2;
+	ids = ["42:00:00:00:00:00", "42:00:00:00:01:00" ];
+};
diff --git a/tests/diamond.sh b/tests/diamond.sh
new file mode 100755
index 0000000..b34d512
--- /dev/null
+++ b/tests/diamond.sh
@@ -0,0 +1,117 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+if [[ $# -eq 0 ]]; then
+	freq=2412
+else
+	freq=$1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+iw reg set US
+
+for i in `seq 0 $((num_nodes-1))`; do
+	addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+
+	links = (
+		(0, 1, 10),
+		(0, 2, 20),
+		(0, 3, 0),
+		(1, 2, 30),
+		(1, 3, 10),
+		(2, 3, 20)
+	);
+};
+__EOM
+
+tmux new -s $session -d
+# find out the index of the first window as we can't assume zero-indexing
+first_idx=`tmux list-windows -t $session | head -n1 | cut -d: -f1`
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+	phy=`addr2phy $addr`
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	phys[$i]=$phy
+	devs[$i]=$dev
+
+	ip=${subnet}.$((10 + i))
+
+	# put this phy in own netns and tmux window, and start a mesh node
+	tmux new-window -t $session
+
+	# start netns
+	pidfile=/tmp/netns.pid.$i
+	win=$session:$((first_idx+i+1))
+	tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+	tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+	# wait for netns to exist
+	while [[ ! -e $pidfile ]]; do
+		echo "Waiting for netns $i -- $pidfile"
+		sleep 0.5
+	done
+
+	tmux send-keys -t $session:$first_idx \
+        'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+	# wait for phy to exist in netns
+	while [[ -e /sys/class/ieee80211/$phy ]]; do
+		echo "Waiting for $phy to move to netns..."
+		sleep 0.5
+	done
+
+	# start mesh node
+	tmux send-keys -t $win '. func' C-m
+	tmux send-keys -t $win 'meshup-iw '$dev' diamond '$freq' '$ip C-m
+
+	i=$((i+1))
+done
+
+# start wmediumd
+tmux send-keys -t $session:$first_idx '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.13
+node_idx=$((first_idx + 4))
+tmux send-keys -t $session:$node_idx 'iperf -s' C-m
+
+# enable monitor
+tmux new-window -t $session
+cap_idx=$((first_idx + 5))
+tmux send-keys -t $session:$cap_idx 'ip link set hwsim0 up' C-m
+# capture traffic as normal user (if possible) or root 
+CAP_USER=${SUDO_USER:-root}
+tmux send-keys -t $session:$cap_idx "sudo -u $CAP_USER dumpcap -i hwsim0" C-m
+
+node_idx=$((first_idx + 1))
+tmux select-window -t $session:$node_idx
+tmux send-keys -t $session:$node_idx 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:$node_idx 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/diamond_direction.sh b/tests/diamond_direction.sh
new file mode 100755
index 0000000..969d879
--- /dev/null
+++ b/tests/diamond_direction.sh
@@ -0,0 +1,115 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# node 1 and 2 moves along with y axis
+# ping will be lost until switching node 1 to node 2
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+	addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+};
+
+model :
+{
+	type = "path_loss";
+	positions = (
+		(-50.0,   0.0),
+		(  0.0,  40.0),
+		(  0.0, -70.0),
+		( 50.0,   0.0)
+	);
+	directions = (
+		(  0.0,   0.0),
+		(  0.0,  10.0),
+		(  0.0,  10.0),
+		(  0.0,   0.0)
+	);
+	tx_powers = (15.0, 15.0, 15.0, 15.0);
+
+	model_name = "log_distance";
+	path_loss_exp = 3.5;
+	xg = 0.0;
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+	phy=`addr2phy $addr`
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	phys[$i]=$phy
+	devs[$i]=$dev
+
+	ip=${subnet}.$((10 + i))
+
+	# put this phy in own netns and tmux window, and start a mesh node
+	win=$session:$((i+1)).0
+	tmux new-window -t $session -n $ip
+
+	# start netns
+	pidfile=/tmp/netns.pid.$i
+	tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+	tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+	# wait for netns to exist
+	while [[ ! -e $pidfile ]]; do
+		echo "Waiting for netns $i -- $pidfile"
+		sleep 0.5
+	done
+
+	tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+	# wait for phy to exist in netns
+	while [[ -e /sys/class/ieee80211/$phy ]]; do
+		echo "Waiting for $phy to move to netns..."
+		sleep 0.5
+	done
+
+	# start mesh node
+	tmux send-keys -t $win '. func' C-m
+	tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+	i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 15 10.10.10.13' C-m
+
+tmux attach
diff --git a/tests/diamond_error_prob.sh b/tests/diamond_error_prob.sh
new file mode 100755
index 0000000..9ff6480
--- /dev/null
+++ b/tests/diamond_error_prob.sh
@@ -0,0 +1,106 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+	addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+};
+
+model:
+{
+	type = "prob";
+
+	default_prob = 1.0;
+	links = (
+		(0, 2, 0.000000),
+		(2, 3, 0.000000)
+	);
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+	phy=`addr2phy $addr`
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	phys[$i]=$phy
+	devs[$i]=$dev
+
+	ip=${subnet}.$((10 + i))
+
+	# put this phy in own netns and tmux window, and start a mesh node
+	win=$session:$((i+1)).0
+	tmux new-window -t $session -n $ip
+
+	# start netns
+	pidfile=/tmp/netns.pid.$i
+	tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+	tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+	# wait for netns to exist
+	while [[ ! -e $pidfile ]]; do
+		echo "Waiting for netns $i -- $pidfile"
+		sleep 0.5
+	done
+
+	tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+	# wait for phy to exist in netns
+	while [[ -e /sys/class/ieee80211/$phy ]]; do
+		echo "Waiting for $phy to move to netns..."
+		sleep 0.5
+	done
+
+	# start mesh node
+	tmux send-keys -t $win '. func' C-m
+	tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+	i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.13
+tmux send-keys -t $session:4 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:1 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/diamond_fading.sh b/tests/diamond_fading.sh
new file mode 100755
index 0000000..7f0fca1
--- /dev/null
+++ b/tests/diamond_fading.sh
@@ -0,0 +1,109 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+	addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+};
+
+model:
+{
+	type = "snr"
+	links = (
+		(0, 1, 10),
+		(0, 2, 20),
+		(0, 3, 0),
+		(1, 2, 30),
+		(1, 3, 10),
+		(2, 3, 20)
+	);
+	fading_coefficient = 1;
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+	phy=`addr2phy $addr`
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	phys[$i]=$phy
+	devs[$i]=$dev
+
+	ip=${subnet}.$((10 + i))
+
+	# put this phy in own netns and tmux window, and start a mesh node
+	win=$session:$((i+1)).0
+	tmux new-window -t $session -n $ip
+
+	# start netns
+	pidfile=/tmp/netns.pid.$i
+	tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+	tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+	# wait for netns to exist
+	while [[ ! -e $pidfile ]]; do
+		echo "Waiting for netns $i -- $pidfile"
+		sleep 0.5
+	done
+
+	tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+	# wait for phy to exist in netns
+	while [[ -e /sys/class/ieee80211/$phy ]]; do
+		echo "Waiting for $phy to move to netns..."
+		sleep 0.5
+	done
+
+	# start mesh node
+	tmux send-keys -t $win '. func' C-m
+	tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+	i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.13
+tmux send-keys -t $session:4 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:1 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/diamond_log_distance.sh b/tests/diamond_log_distance.sh
new file mode 100755
index 0000000..fdd78cb
--- /dev/null
+++ b/tests/diamond_log_distance.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+	addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+};
+
+model :
+{
+	type = "path_loss";
+	positions = (
+		(-50.0,   0.0),
+		(  0.0,  10.0),
+		(  0.0,   0.0),
+		( 50.0,   0.0)
+	);
+	tx_powers = (15.0, 15.0, 15.0, 15.0);
+
+	model_name = "log_distance";
+	path_loss_exp = 3.5;
+	xg = 0.0;
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+	phy=`addr2phy $addr`
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	phys[$i]=$phy
+	devs[$i]=$dev
+
+	ip=${subnet}.$((10 + i))
+
+	# put this phy in own netns and tmux window, and start a mesh node
+	win=$session:$((i+1)).0
+	tmux new-window -t $session -n $ip
+
+	# start netns
+	pidfile=/tmp/netns.pid.$i
+	tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+	tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+	# wait for netns to exist
+	while [[ ! -e $pidfile ]]; do
+		echo "Waiting for netns $i -- $pidfile"
+		sleep 0.5
+	done
+
+	tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+	# wait for phy to exist in netns
+	while [[ -e /sys/class/ieee80211/$phy ]]; do
+		echo "Waiting for $phy to move to netns..."
+		sleep 0.5
+	done
+
+	# start mesh node
+	tmux send-keys -t $win '. func' C-m
+	tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+	i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.13
+tmux send-keys -t $session:4 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:1 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/diamond_per_matrix.sh b/tests/diamond_per_matrix.sh
new file mode 100755
index 0000000..bf6f3d5
--- /dev/null
+++ b/tests/diamond_per_matrix.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+# 4 mesh nodes in a diamond topology
+# paths must go through one of two intermediate nodes.
+
+num_nodes=4
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+	addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00"
+	];
+
+	links = (
+		(0, 1, 10),
+		(0, 2, 20),
+		(0, 3, 0),
+		(1, 2, 30),
+		(1, 3, 10),
+		(2, 3, 20)
+	);
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+	phy=`addr2phy $addr`
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	phys[$i]=$phy
+	devs[$i]=$dev
+
+	ip=${subnet}.$((10 + i))
+
+	# put this phy in own netns and tmux window, and start a mesh node
+	win=$session:$((i+1)).0
+	tmux new-window -t $session -n $ip
+
+	# start netns
+	pidfile=/tmp/netns.pid.$i
+	tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+	tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+	# wait for netns to exist
+	while [[ ! -e $pidfile ]]; do
+		echo "Waiting for netns $i -- $pidfile"
+		sleep 0.5
+	done
+
+	tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+	# wait for phy to exist in netns
+	while [[ -e /sys/class/ieee80211/$phy ]]; do
+		echo "Waiting for $phy to move to netns..."
+		sleep 0.5
+	done
+
+	# start mesh node
+	tmux send-keys -t $win '. func' C-m
+	tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+	i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg -x signal_table_ieee80211ax' C-m
+
+# start iperf server on 10.10.10.13
+tmux send-keys -t $session:4 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'ping -c 5 10.10.10.13' C-m
+tmux send-keys -t $session:1 'iperf -c 10.10.10.13 -i 5 -t 120'
+
+tmux attach
diff --git a/tests/func b/tests/func
new file mode 100644
index 0000000..85ddf00
--- /dev/null
+++ b/tests/func
@@ -0,0 +1,119 @@
+function freq_to_chan {
+	local freq=$1
+
+	if [[ $freq -ge 2412 && $freq -le 2472 ]]; then
+		band="11g"
+		chan=$(( ($freq - 2412) / 5 + 1 ))
+	else
+		band="11a"
+		chan=$(( ($freq - 5000) / 5 ))
+	fi
+	echo "$chan $band"
+}
+
+
+function meshup-iw {
+	local if=$1
+	local meshid=$2
+	local freq=$3
+	local ip=$4
+
+	ip link set $if down
+	iw dev $if set type mp
+	ip link set $if up
+	iw dev $if mesh join $meshid freq $freq
+	ip addr add $ip/24 dev $if 2>/dev/null
+}
+
+function meshup-wpas-open {
+	local if=$1
+	local meshid=$2
+	local freq=$3
+	local ip=$4
+
+	ip link set $if down
+	iw dev $if set type mp
+	ip link set $if up
+
+	cat<<EOM > /tmp/wpas-$if.conf
+network={
+	ssid="wmediumd-mesh"
+	mode=5
+	frequency=$freq
+	key_mgmt=NONE
+}
+EOM
+	wpa_supplicant -i $if -c /tmp/wpas-$if.conf &
+	ip addr add $ip/24 dev $if 2>/dev/null
+}
+
+function meshup-wpas {
+	local if=$1;
+	local meshid=$2;
+	local freq=$3;
+	local ip=$4;
+
+	ip link set $if down
+	iw dev $if set type mp
+	ip link set $if up
+
+	cat<<EOM > /tmp/wpas-$if.conf
+network={
+	ssid="wmediumd-mesh-sec"
+	mode=5
+	frequency=$freq
+	key_mgmt=SAE
+	psk="some passphrase"
+}
+EOM
+	wpa_supplicant -i $if -c /tmp/wpas-$if.conf &
+	ip addr add $ip/24 dev $if 2>/dev/null
+}
+
+function meshup-authsae {
+	local if=$1;
+	local meshid=$2;
+	local freq=$3;
+	local ip=$4;
+
+	ip link set $if down
+	iw dev $if set type mp
+	ip link set $if up
+
+	chan_params=$(freq_to_chan $freq)
+	read -ra ch <<< "$chan_params"
+
+	cat<<EOM > /tmp/authsae-$if.conf
+authsae:
+{
+ sae:
+  {
+    debug = 480;
+    password = "some passphrase";
+    group = [19, 26, 21, 25, 20];
+    blacklist = 5;
+    thresh = 5;
+    lifetime = 3600;
+  };
+ meshd:
+  {
+    meshid = "wmediumd-mesh-sec";
+    interface = "wlan0";
+    passive = 0;
+    secured = 1;
+    debug = 1;
+    mediaopt = 1;
+    band = "${ch[1]}";
+    channel = ${ch[0]};
+  };
+};
+EOM
+	meshd-nl80211 -i $if -c /tmp/authsae-$if.conf &
+	ip addr add $ip/24 dev $if 2>/dev/null
+}
+
+function addr2phy {
+	local addr=$1;
+	grep -l $addr /sys/class/ieee80211/phy*/macaddress | \
+		awk -F '/' '{print $(NF-1)}'
+}
diff --git a/tests/interference.sh b/tests/interference.sh
new file mode 100755
index 0000000..9be3c7e
--- /dev/null
+++ b/tests/interference.sh
@@ -0,0 +1,126 @@
+#!/bin/bash
+# 3 mesh nodes in a linear topology
+# 4 additional mesh nodes exists to prevent transmission
+# When enable_interference=true, ping always fails.
+# (This test is not perfect because of random values)
+
+num_nodes=7
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+iw reg set US
+
+for i in `seq 0 $((num_nodes-1))`; do
+	addrs[$i]=`printf $macfmt $i`
+done
+
+cat <<__EOM > diamond.cfg
+ifaces :
+{
+	ids = [
+		"02:00:00:00:00:00",
+		"02:00:00:00:01:00",
+		"02:00:00:00:02:00",
+		"02:00:00:00:03:00",
+		"02:00:00:00:04:00",
+		"02:00:00:00:05:00",
+		"02:00:00:00:06:00"
+	];
+	enable_interference = true;
+};
+
+model :
+{
+	band = 2;
+	type = "path_loss";
+	positions = (
+		(-69.0,   0.0),
+		(  0.0,   0.0),
+		( 69.0,   0.0),
+		(130.0,  -2.0),
+		(130.0,  -1.0),
+		(130.0,   2.0),
+		(130.0,   1.0)
+	);
+	tx_powers = (15.0, 15.0, 15.0, 11.0, 11.0, 11.0, 11.0);
+
+	model_name = "log_distance";
+	path_loss_exp = 3.5;
+	xg = 0.0;
+};
+__EOM
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+	phy=`addr2phy $addr`
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	phys[$i]=$phy
+	devs[$i]=$dev
+
+	ip=${subnet}.$((10 + i))
+
+	# put this phy in own netns and tmux window, and start a mesh node
+	win=$session:$((i+1)).0
+	tmux new-window -t $session -n $ip
+
+	# start netns
+	pidfile=/tmp/netns.pid.$i
+	tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+	tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+	# wait for netns to exist
+	while [[ ! -e $pidfile ]]; do
+		echo "Waiting for netns $i -- $pidfile"
+		sleep 0.5
+	done
+
+	tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+	# wait for phy to exist in netns
+	while [[ -e /sys/class/ieee80211/$phy ]]; do
+		echo "Waiting for $phy to move to netns..."
+		sleep 0.5
+	done
+
+	# start mesh node
+	tmux send-keys -t $win '. func' C-m
+	tmux send-keys -t $win 'meshup-iw '$dev' diamond 2412 '$ip C-m
+
+	i=$((i+1))
+done
+winct=$i
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -a -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c diamond.cfg' C-m
+
+# start iperf server on 10.10.10.14
+tmux send-keys -t $session:5 'iperf -s' C-m
+# start iperf server on 10.10.10.16
+tmux send-keys -t $session:7 'iperf -s' C-m
+
+# enable monitor
+tmux send-keys -t $session:0 'ip link set hwsim0 up' C-m
+
+tmux send-keys -t $session:4 'iperf -u -b 100M -c 10.10.10.14 -t 10' C-m
+tmux send-keys -t $session:6 'iperf -u -b 100M -c 10.10.10.16 -t 10' C-m
+
+tmux select-window -t $session:1
+tmux send-keys -t $session:1 'sleep 2; ping -c 5 -W 1 10.10.10.12' C-m
+
+tmux attach
diff --git a/tests/n-linear-mesh.sh b/tests/n-linear-mesh.sh
new file mode 100755
index 0000000..736d229
--- /dev/null
+++ b/tests/n-linear-mesh.sh
@@ -0,0 +1,102 @@
+#!/bin/bash
+# run multiple mesh nodes in a linear topology
+# each node is in the same coverage area, so
+# total throughput is divided by n.
+
+num_nodes=${1:-4}
+daemon=${2:-iw}
+
+session=wmediumd
+subnet=10.10.10
+macfmt='02:00:00:00:%02x:00'
+
+. func
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$num_nodes
+
+for i in `seq 0 $((num_nodes-1))`; do
+	addrs[$i]=`printf $macfmt $i`
+done
+
+echo "ifaces: { count = $num_nodes; ids = [" > linear.cfg
+for addr in "${addrs[@]}"; do
+	echo -n '"'$addr'"' >> linear.cfg
+	if [[ $addr != ${addrs[$((num_nodes-1))]} ]]; then
+		echo ", " >> linear.cfg
+	fi
+done
+echo "]; }" >> linear.cfg
+
+tmux new -s $session -d
+
+rm /tmp/netns.pid.* 2>/dev/null
+i=0
+for addr in ${addrs[@]}; do
+	phy=`addr2phy $addr`
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	phys[$i]=$phy
+	devs[$i]=$dev
+
+	ip=${subnet}.$((10 + i))
+
+	# put this phy in own netns and tmux window, and start a mesh node
+	win=$session:$((i+1)).0
+	tmux new-window -t $session -n $ip
+
+	# start netns
+	pidfile=/tmp/netns.pid.$i
+	tmux send-keys -t $win 'lxc-unshare -s NETWORK /bin/bash' C-m
+	tmux send-keys -t $win 'echo $$ > '$pidfile C-m
+
+	# wait for netns to exist
+	while [[ ! -e $pidfile ]]; do
+		echo "Waiting for netns $i -- $pidfile"
+		sleep 0.5
+	done
+
+	tmux send-keys -t $session:0.0 'iw phy '$phy' set netns `cat '$pidfile'`' C-m
+
+	# wait for phy to exist in netns
+	while [[ -e /sys/class/ieee80211/$phy ]]; do
+		echo "Waiting for $phy to move to netns..."
+		sleep 0.5
+	done
+
+	# start mesh node
+	tmux send-keys -t $win '. func' C-m
+	tmux send-keys -t $win 'meshup-'$daemon ' ' $dev' linear 2412 '$ip C-m
+
+	i=$((i+1))
+done
+winct=$i
+
+# wait a few beacon periods for everyone to discover each other
+sleep 3
+
+# force a linear topology
+for i in `seq 0 $((${#addrs[@]} - 1))`; do
+	win=$session:$((i+1)).0
+	addr=${addrs[$i]}
+	dev=${devs[$i]}
+
+	for j in `seq 0 $((${#addrs[@]} - 1))`; do
+		oaddr=${addrs[$j]}
+		if [[ $j -lt $((i-1)) || $j -gt $((i+1)) ]]; then
+			tmux send-keys -t $win 'iw dev '$dev' station set '$oaddr' plink_action block' C-m
+		fi
+	done
+done
+
+# start wmediumd
+win=$session:$((winct+1)).0
+winct=$((winct+1))
+tmux new-window -t $session -n wmediumd
+tmux send-keys -t $win '../wmediumd/wmediumd -c linear.cfg' C-m
+
+tmux attach
diff --git a/tests/signal_table_ieee80211ax b/tests/signal_table_ieee80211ax
new file mode 100644
index 0000000..8c107ad
--- /dev/null
+++ b/tests/signal_table_ieee80211ax
@@ -0,0 +1,44 @@
+# RSSI vs MCS vs PER table, based on 11-14-0571-12-00ax-evaluation-methodology.docx assuming No is -91dBm.
+# bitrate	1Mbps	2Mbps	5.5Mbps	11Mbps	6Mbps	9Mbps	12Mbps	18Mbps	24Mbps	36Mbps	48Mbps	54Mbps
+# RSSI [dBm]	0	1	2	3	4	5	6	7	8	9	10	11
+-100	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-99	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-98	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-97	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-96	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-95	0.9995	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-94	0.529	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-93	0.0427	0.9194	0.9995	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-92	0.0014	0.1765	0.529	1.00E+00	0.9995	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-91	0.00E+00	0.0086	0.0427	1.00E+00	0.529	0.9995	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-90	0.00E+00	0.0001	0.0014	0.9995	0.0427	0.529	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-89	0.00E+00	0.00E+00	0.00E+00	0.529	0.0014	0.0427	0.9997	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-88	0.00E+00	0.00E+00	0.00E+00	0.0427	0.00E+00	0.0014	0.5462	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-87	0.00E+00	0.00E+00	0.00E+00	0.0014	0.00E+00	0.00E+00	0.0439	1.00E+00	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-86	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.0016	0.9597	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-85	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.2239	1.00E+00	1.00E+00	1.00E+00	1.00E+00
+-84	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.0117	0.8908	1.00E+00	1.00E+00	1.00E+00
+-83	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.2343	1.00E+00	1.00E+00	1.00E+00
+-82	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.024	1.00E+00	1.00E+00	1.00E+00
+-81	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	1.00E+00	1.00E+00	1.00E+00
+-80	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.979	1.00E+00	1.00E+00
+-79	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.3536	1.00E+00	1.00E+00
+-78	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.0356	1.00E+00	1.00E+00
+-77	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.0018	1.00E+00	1.00E+00
+-76	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.9496	1.00E+00
+-75	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.379	0.9981
+-74	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.061	0.6465
+-73	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.0057	0.1343
+-72	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.0004	0.0145
+-71	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.0007
+-70	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-69	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-68	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-67	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-66	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-65	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-64	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-63	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-62	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-61	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
+-60	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00	0.00E+00
diff --git a/tests/test-001.sh b/tests/test-001.sh
new file mode 100755
index 0000000..619eb99
--- /dev/null
+++ b/tests/test-001.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+SUBNET=10.10.10
+NUM_PHYS=2
+
+if [[ $UID -ne 0 ]]; then
+	echo "Sorry, run me as root."
+	exit 1
+fi
+
+function cleanup() {
+	echo "Cleaning up!"
+	# restore default routing rules
+	echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore
+	for i in `seq 0 $NUM_PHYS`; do
+		prio=$((i+10))
+		prio2=$((256+prio))
+		tbl=$prio2
+
+		ip rule del priority $prio2 2> /dev/null
+		ip rule del priority $prio 2> /dev/null
+		ip route flush table $tbl 2> /dev/null
+	done
+	ip rule del priority 1000
+	ip rule add priority 0 table local
+
+	# kill whatever we started
+	killall wmediumd
+}
+
+trap 'cleanup' INT TERM EXIT
+
+modprobe -r mac80211_hwsim
+modprobe mac80211_hwsim radios=$NUM_PHYS
+
+# routing-based send-to-self (Patrick McHardy)
+# lower priority of kernel local table
+ip rule add priority 1000 lookup local
+ip rule del priority 0 &>/dev/null
+
+# only arp reply for self
+echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
+
+i=0
+# Assume most recently modified phys are hwsim phys (hence the ls -t)
+for phy in `ls -t /sys/class/ieee80211 | head -$NUM_PHYS`; do
+	# The usual stuff
+	dev=`ls /sys/class/ieee80211/$phy/device/net`
+	ip=${SUBNET}.$((10 + i))
+
+	ip link set $dev down
+	ip link set address 42:00:00:00:0${i}:00 dev $dev
+	iw dev $dev set type mesh
+	iw dev $dev set channel 36
+	ip link set $dev up
+	iw dev $dev mesh join meshtest
+
+	ip addr flush dev $dev
+	ip addr add $ip/24 dev $dev
+
+	# set up local delivery
+	prio=$((i+10))
+	prio2=$((256+prio))
+	tbl=$prio2
+
+	# incoming traffic to us delivered via local table
+	echo 1 > /proc/sys/net/ipv4/conf/$dev/accept_local
+	ip rule del priority $prio 2> /dev/null
+	ip rule add priority $prio iif $dev lookup local
+
+	# outgoing frames with our ip will be generated on our interface
+	# and go over the wire.
+	ip rule del priority $prio2 2> /dev/null
+	ip rule add priority $prio2 from $ip table $tbl
+	ip route flush table $tbl 2> /dev/null
+	ip route add default dev $dev table $tbl
+
+	i=$((i+1))
+done
+
+# enable wmediumd
+../wmediumd/wmediumd -c 2node.cfg > wmediumd.log &
+
+# see if we can establish a mesh path
+ping -i 1 -c 5 -I ${SUBNET}.10 ${SUBNET}.11 || { echo FAIL; exit 1; }
+
+echo PASS
diff --git a/wmediumd/Makefile b/wmediumd/Makefile
new file mode 100644
index 0000000..fa94eb0
--- /dev/null
+++ b/wmediumd/Makefile
@@ -0,0 +1,71 @@
+VERSION_STR="\"0.3.1\""
+
+# Look for libnl libraries
+PKG_CONFIG ?= pkg-config
+NL2FOUND := $(shell $(PKG_CONFIG) --atleast-version=2 libnl-2.0 && echo Y)
+NL3FOUND := $(shell $(PKG_CONFIG) --atleast-version=3 libnl-3.0 && echo Y)
+NL31FOUND := $(shell $(PKG_CONFIG) --exact-version=3.1 libnl-3.1 && echo Y)
+NL3xFOUND := $(shell $(PKG_CONFIG) --atleast-version=3.2 libnl-3.0 && echo Y)
+
+CFLAGS += -g -Wall -Wextra -Wno-unused-parameter -O2 -Iinc
+CFLAGS += -MMD -MP
+CFLAGS += -Wno-format-zero-length
+LDFLAGS += -lm
+
+ifeq ($(NL2FOUND),Y)
+CFLAGS += -DCONFIG_LIBNL20
+LDFLAGS += -lnl-genl
+NLLIBNAME = libnl-2.0
+endif
+
+ifeq ($(NL3xFOUND),Y)
+# libnl 3.2 might be found as 3.2 and 3.0
+NL3FOUND = N
+CFLAGS += -DCONFIG_LIBNL30
+LDFLAGS += -lnl-genl-3
+NLLIBNAME = libnl-3.0
+endif
+
+ifeq ($(NL3FOUND),Y)
+CFLAGS += -DCONFIG_LIBNL30
+LDFLAGS += -lnl-genl
+NLLIBNAME = libnl-3.0
+endif
+
+# nl-3.1 has a broken libnl-gnl-3.1.pc file
+# as show by pkg-config --debug --libs --cflags --exact-version=3.1 libnl-genl-3.1;echo $?
+ifeq ($(NL31FOUND),Y)
+CFLAGS += -DCONFIG_LIBNL30
+LDFLAGS += -lnl-genl
+NLLIBNAME = libnl-3.1
+endif
+
+ifeq ($(NLLIBNAME),)
+$(error Cannot find development files for any supported version of libnl)
+endif
+
+LDFLAGS += $(shell $(PKG_CONFIG) --libs $(NLLIBNAME))
+CFLAGS += $(shell $(PKG_CONFIG) --cflags $(NLLIBNAME))
+
+CFLAGS+=-DVERSION_STR=$(VERSION_STR)
+LDFLAGS+=-lconfig 
+OBJECTS=wmediumd.o config.o per.o
+OBJECTS += lib/loop.o lib/sched.o lib/schedctrl.o
+OBJECTS += lib/uds.o lib/vhost.o lib/wallclock.o
+
+ifeq ($(SANITIZE),1)
+CFLAGS += -fsanitize=undefined,address
+# apparently these have to come first for some reason
+override LDFLAGS := -lasan -lubsan -lstdc++ $(LDFLAGS)
+endif
+
+all: wmediumd 
+
+wmediumd: $(OBJECTS) 
+	$(CC) -o $@ $(OBJECTS) $(LDFLAGS) 
+
+DEPS := $(patsubst %.o,%.d,$(OBJECTS))
+clean: 
+	rm -f $(OBJECTS) $(DEPS) wmediumd
+
+-include *.d lib/*.d
diff --git a/wmediumd/api.h b/wmediumd/api.h
new file mode 100644
index 0000000..24e9548
--- /dev/null
+++ b/wmediumd/api.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _WMEDIUMD_API_H
+#define _WMEDIUMD_API_H
+#include <stdint.h>
+
+enum wmediumd_message {
+	/* invalid message */
+	WMEDIUMD_MSG_INVALID,
+
+	/* ACK, returned for each message for synchronisation */
+	WMEDIUMD_MSG_ACK,
+
+	/*
+	 * Register/unregister for frames, this may be a pure control
+	 * socket which doesn't want to see frames.
+	 */
+	WMEDIUMD_MSG_REGISTER,
+	WMEDIUMD_MSG_UNREGISTER,
+
+	/*
+	 * netlink message, the data is the entire netlink message,
+	 * this is used to communicate frame TX/RX in the familiar
+	 * netlink format, to avoid having a special format
+	 */
+	WMEDIUMD_MSG_NETLINK,
+
+	/* control message, see struct wmediumd_message_control */
+	WMEDIUMD_MSG_SET_CONTROL,
+
+	/*
+	 * Indicates TX start if WMEDIUMD_RX_CTL_NOTIFY_TX_START is set,
+	 * with struct wmediumd_tx_start as the payload.
+	 */
+	WMEDIUMD_MSG_TX_START,
+};
+
+struct wmediumd_message_header {
+	/* type of message - see enum wmediumd_message */
+	uint32_t type;
+	/* data length */
+	uint32_t data_len;
+
+	/* variable-length data according to the message type */
+	uint8_t data[];
+};
+
+enum wmediumd_control_flags {
+	WMEDIUMD_CTL_NOTIFY_TX_START		= 1 << 0,
+	WMEDIUMD_CTL_RX_ALL_FRAMES		= 1 << 1,
+};
+
+struct wmediumd_message_control {
+	uint32_t flags;
+
+	/*
+	 * For compatibility, wmediumd is meant to understand shorter
+	 * (and ignore unknown parts of longer) control messages than
+	 * what's sent to it, so always take care to have defaults as
+	 * zero since that's what it assumes.
+	 */
+};
+
+struct wmediumd_tx_start {
+	/*
+	 * The cookie is set only when telling the sender, otherwise
+	 * it's set to 0.
+	 */
+	uint64_t cookie;
+	uint32_t freq;
+	uint32_t reserved[3];
+};
+
+#endif /* _WMEDIUMD_API_H */
diff --git a/wmediumd/config.c b/wmediumd/config.c
new file mode 100644
index 0000000..c9a6f5c
--- /dev/null
+++ b/wmediumd/config.c
@@ -0,0 +1,657 @@
+/*
+ *	wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ *	Copyright (c) 2011 cozybit Inc.
+ *	Copyright (C) 2020 Intel Corporation
+ *
+ *	Author: Javier Lopez    <jlopex@cozybit.com>
+ *		Javier Cardona  <javier@cozybit.com>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version 2
+ *	of the License, or (at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *	02110-1301, USA.
+ */
+
+#include <sys/timerfd.h>
+#include <libconfig.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
+
+#include "wmediumd.h"
+
+static void string_to_mac_address(const char *str, u8 *addr)
+{
+	int a[ETH_ALEN];
+
+	sscanf(str, "%x:%x:%x:%x:%x:%x",
+	       &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]);
+
+	addr[0] = (u8) a[0];
+	addr[1] = (u8) a[1];
+	addr[2] = (u8) a[2];
+	addr[3] = (u8) a[3];
+	addr[4] = (u8) a[4];
+	addr[5] = (u8) a[5];
+}
+
+static int get_link_snr_default(struct wmediumd *ctx, struct station *sender,
+				 struct station *receiver)
+{
+	return SNR_DEFAULT;
+}
+
+static int get_link_snr_from_snr_matrix(struct wmediumd *ctx,
+					struct station *sender,
+					struct station *receiver)
+{
+	return ctx->snr_matrix[sender->index * ctx->num_stas + receiver->index];
+}
+
+static double _get_error_prob_from_snr(struct wmediumd *ctx, double snr,
+				       unsigned int rate_idx, u32 freq,
+				       int frame_len,
+				       struct station *src, struct station *dst)
+{
+	return get_error_prob_from_snr(snr, rate_idx, freq, frame_len);
+}
+
+static double get_error_prob_from_matrix(struct wmediumd *ctx, double snr,
+					 unsigned int rate_idx, u32 freq,
+					 int frame_len, struct station *src,
+					 struct station *dst)
+{
+	if (dst == NULL) // dst is multicast. returned value will not be used.
+		return 0.0;
+
+	return ctx->error_prob_matrix[ctx->num_stas * src->index + dst->index];
+}
+
+int use_fixed_random_value(struct wmediumd *ctx)
+{
+	return ctx->error_prob_matrix != NULL;
+}
+
+#define FREQ_1CH (2.412e9)		// [Hz]
+#define SPEED_LIGHT (2.99792458e8)	// [meter/sec]
+/*
+ * Calculate path loss based on a free-space path loss
+ *
+ * This function returns path loss [dBm].
+ */
+static int calc_path_loss_free_space(void *model_param,
+			  struct station *dst, struct station *src)
+{
+	double PL, d;
+
+	d = sqrt((src->x - dst->x) * (src->x - dst->x) +
+		 (src->y - dst->y) * (src->y - dst->y));
+
+	/*
+	 * Calculate PL0 with Free-space path loss in decibels
+	 *
+	 * 20 * log10 * (4 * M_PI * d * f / c)
+	 *   d: distance [meter]
+	 *   f: frequency [Hz]
+	 *   c: speed of light in a vacuum [meter/second]
+	 *
+	 * https://en.wikipedia.org/wiki/Free-space_path_loss
+	 */
+	PL = 20.0 * log10(4.0 * M_PI * d * FREQ_1CH / SPEED_LIGHT);
+	return PL;
+}
+/*
+ * Calculate path loss based on a log distance model
+ *
+ * This function returns path loss [dBm].
+ */
+static int calc_path_loss_log_distance(void *model_param,
+			  struct station *dst, struct station *src)
+{
+	struct log_distance_model_param *param;
+	double PL, PL0, d;
+
+	param = model_param;
+
+	d = sqrt((src->x - dst->x) * (src->x - dst->x) +
+		 (src->y - dst->y) * (src->y - dst->y));
+
+	/*
+	 * Calculate PL0 with Free-space path loss in decibels
+	 *
+	 * 20 * log10 * (4 * M_PI * d * f / c)
+	 *   d: distance [meter]
+	 *   f: frequency [Hz]
+	 *   c: speed of light in a vacuum [meter/second]
+	 *
+	 * https://en.wikipedia.org/wiki/Free-space_path_loss
+	 */
+	PL0 = 20.0 * log10(4.0 * M_PI * 1.0 * FREQ_1CH / SPEED_LIGHT);
+
+	/*
+	 * Calculate signal strength with Log-distance path loss model
+	 * https://en.wikipedia.org/wiki/Log-distance_path_loss_model
+	 */
+	PL = PL0 + 10.0 * param->path_loss_exponent * log10(d) + param->Xg;
+
+	return PL;
+}
+/*
+ * Calculate path loss based on a itu model
+ *
+ * This function returns path loss [dBm].
+ */
+static int calc_path_loss_itu(void *model_param,
+			  struct station *dst, struct station *src)
+{
+	struct itu_model_param *param;
+	double PL, d;
+	int N=28;
+
+	param = model_param;
+
+	d = sqrt((src->x - dst->x) * (src->x - dst->x) +
+		 (src->y - dst->y) * (src->y - dst->y));
+
+	if (d>16)
+		N=38;
+	/*
+	 * Calculate signal strength with ITU path loss model
+	 * Power Loss Coefficient Based on the Paper
+         * Site-Specific Validation of ITU Indoor Path Loss Model at 2.4 GHz
+         * from Theofilos Chrysikos, Giannis Georgopoulos and Stavros Kotsopoulos
+	 * LF: floor penetration loss factor
+	 * nFLOORS: number of floors
+	 */
+	PL = 20.0 * log10(FREQ_1CH) + N * log10(d) + param->LF * param->nFLOORS - 28;
+	return PL;
+}
+
+static void recalc_path_loss(struct wmediumd *ctx)
+{
+	int start, end, path_loss;
+
+	for (start = 0; start < ctx->num_stas; start++) {
+		for (end = 0; end < ctx->num_stas; end++) {
+			if (start == end)
+				continue;
+
+			path_loss = ctx->calc_path_loss(ctx->path_loss_param,
+				ctx->sta_array[end], ctx->sta_array[start]);
+			ctx->snr_matrix[ctx->num_stas * start + end] =
+				ctx->sta_array[start]->tx_power - path_loss -
+				NOISE_LEVEL;
+		}
+	}
+}
+
+static void move_stations_to_direction(struct usfstl_job *job)
+{
+	struct wmediumd *ctx = job->data;
+	struct station *station;
+
+	list_for_each_entry(station, &ctx->stations, list) {
+		station->x += station->dir_x;
+		station->y += station->dir_y;
+	}
+	recalc_path_loss(ctx);
+
+	job->start += MOVE_INTERVAL * 1000000;
+	usfstl_sched_add_job(&scheduler, job);
+}
+
+static int parse_path_loss(struct wmediumd *ctx, config_t *cf)
+{
+	struct station *station;
+	const config_setting_t *positions, *position;
+	const config_setting_t *directions, *direction;
+	const config_setting_t *tx_powers, *model;
+	const char *path_loss_model_name;
+
+	positions = config_lookup(cf, "model.positions");
+	if (!positions) {
+		w_flogf(ctx, LOG_ERR, stderr,
+			"No positions found in model\n");
+		return -EINVAL;
+	}
+	if (config_setting_length(positions) != ctx->num_stas) {
+		w_flogf(ctx, LOG_ERR, stderr,
+			"Specify %d positions\n", ctx->num_stas);
+		return -EINVAL;
+	}
+
+	directions = config_lookup(cf, "model.directions");
+	if (directions) {
+		if (config_setting_length(directions) != ctx->num_stas) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"Specify %d directions\n", ctx->num_stas);
+			return -EINVAL;
+		}
+		ctx->move_job.start = MOVE_INTERVAL * 1000000;
+		ctx->move_job.name = "move";
+		ctx->move_job.data = ctx;
+		ctx->move_job.callback = move_stations_to_direction;
+		usfstl_sched_add_job(&scheduler, &ctx->move_job);
+	}
+
+	tx_powers = config_lookup(cf, "model.tx_powers");
+	if (!tx_powers) {
+		w_flogf(ctx, LOG_ERR, stderr,
+			"No tx_powers found in model\n");
+		return -EINVAL;
+	}
+	if (config_setting_length(tx_powers) != ctx->num_stas) {
+		w_flogf(ctx, LOG_ERR, stderr,
+			"Specify %d tx_powers\n", ctx->num_stas);
+		return -EINVAL;
+	}
+
+	model = config_lookup(cf, "model");
+	if (config_setting_lookup_string(model, "model_name",
+		&path_loss_model_name) != CONFIG_TRUE) {
+		w_flogf(ctx, LOG_ERR, stderr, "Specify model_name\n");
+		return -EINVAL;
+	}
+	if (strncmp(path_loss_model_name, "log_distance",
+		    sizeof("log_distance")) == 0) {
+		struct log_distance_model_param *param;
+
+		ctx->calc_path_loss = calc_path_loss_log_distance;
+
+		param = malloc(sizeof(*param));
+		if (!param) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"Out of memory(path_loss_param)\n");
+			return -EINVAL;
+		}
+
+		if (config_setting_lookup_float(model, "path_loss_exp",
+			&param->path_loss_exponent) != CONFIG_TRUE) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"path_loss_exponent not found\n");
+			return -EINVAL;
+		}
+
+		if (config_setting_lookup_float(model, "xg",
+			&param->Xg) != CONFIG_TRUE) {
+			w_flogf(ctx, LOG_ERR, stderr, "xg not found\n");
+			return -EINVAL;
+		}
+		ctx->path_loss_param = param;
+	}
+	else if (strncmp(path_loss_model_name, "free_space",
+			sizeof("free_space")) == 0) {
+		struct log_distance_model_param *param;
+		ctx->calc_path_loss = calc_path_loss_free_space;
+		param = malloc(sizeof(*param));
+		if (!param) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"Out of memory(path_loss_param)\n");
+			return -EINVAL;
+		}
+	}
+	else if (strncmp(path_loss_model_name, "itu",
+			sizeof("itu")) == 0) {
+		struct itu_model_param *param;
+		ctx->calc_path_loss = calc_path_loss_itu;
+		param = malloc(sizeof(*param));
+		if (!param) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"Out of memory(path_loss_param)\n");
+			return -EINVAL;
+		}
+
+		if (config_setting_lookup_int(model, "nFLOORS",
+			&param->nFLOORS) != CONFIG_TRUE) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"nFLOORS not found\n");
+			return -EINVAL;
+		}
+
+		if (config_setting_lookup_int(model, "LF",
+			&param->LF) != CONFIG_TRUE) {
+			w_flogf(ctx, LOG_ERR, stderr, "LF not found\n");
+			return -EINVAL;
+		}
+		ctx->path_loss_param = param;
+	}
+	else {
+		w_flogf(ctx, LOG_ERR, stderr, "No path loss model found\n");
+		return -EINVAL;
+	}
+
+	list_for_each_entry(station, &ctx->stations, list) {
+		position = config_setting_get_elem(positions, station->index);
+		if (config_setting_length(position) != 2) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"Invalid position: expected (double,double)\n");
+			return -EINVAL;
+		}
+		station->x = config_setting_get_float_elem(position, 0);
+		station->y = config_setting_get_float_elem(position, 1);
+
+		if (directions) {
+			direction = config_setting_get_elem(directions,
+				station->index);
+			if (config_setting_length(direction) != 2) {
+				w_flogf(ctx, LOG_ERR, stderr,
+					"Invalid direction: expected (double,double)\n");
+				return -EINVAL;
+			}
+			station->dir_x = config_setting_get_float_elem(
+				direction, 0);
+			station->dir_y = config_setting_get_float_elem(
+				direction, 1);
+		}
+
+		station->tx_power = config_setting_get_float_elem(
+			tx_powers, station->index);
+	}
+
+	recalc_path_loss(ctx);
+
+	return 0;
+}
+
+static double pseudo_normal_distribution(void)
+{
+	int i;
+	double normal = -6.0;
+
+	for (i = 0; i < 12; i++)
+		normal += drand48();
+
+	return normal;
+}
+
+static int _get_fading_signal(struct wmediumd *ctx)
+{
+	return ctx->fading_coefficient * pseudo_normal_distribution();
+}
+
+static int get_no_fading_signal(struct wmediumd *ctx)
+{
+	return 0;
+}
+
+/* Existing link is from from -> to; copy to other dir */
+static void mirror_link(struct wmediumd *ctx, int from, int to)
+{
+	ctx->snr_matrix[ctx->num_stas * to + from] =
+		ctx->snr_matrix[ctx->num_stas * from + to];
+
+	if (ctx->error_prob_matrix) {
+		ctx->error_prob_matrix[ctx->num_stas * to + from] =
+			ctx->error_prob_matrix[ctx->num_stas * from + to];
+	}
+}
+
+/*
+ *	Loads a config file into memory
+ */
+int load_config(struct wmediumd *ctx, const char *file, const char *per_file)
+{
+	config_t cfg, *cf;
+	const config_setting_t *ids, *links, *model_type;
+	const config_setting_t *error_probs = NULL, *error_prob;
+	const config_setting_t *enable_interference;
+	const config_setting_t *fading_coefficient, *default_prob;
+	int count_ids, i, j;
+	int start, end, snr;
+	struct station *station;
+	const char *model_type_str;
+	float default_prob_value = 0.0;
+	bool *link_map = NULL;
+
+	/*initialize the config file*/
+	cf = &cfg;
+	config_init(cf);
+
+	/*read the file*/
+	if (!config_read_file(cf, file)) {
+		w_logf(ctx, LOG_ERR, "Error loading file %s at line:%d, reason: %s\n",
+				file,
+				config_error_line(cf),
+				config_error_text(cf));
+		config_destroy(cf);
+		return -EIO;
+	}
+
+	ids = config_lookup(cf, "ifaces.ids");
+	if (!ids) {
+		w_logf(ctx, LOG_ERR, "ids not found in config file\n");
+		return -EIO;
+	}
+	count_ids = config_setting_length(ids);
+
+	w_logf(ctx, LOG_NOTICE, "#_if = %d\n", count_ids);
+
+	/* Fill the mac_addr */
+	ctx->sta_array = calloc(count_ids, sizeof(struct station *));
+	if (!ctx->sta_array) {
+		w_flogf(ctx, LOG_ERR, stderr, "Out of memory(sta_array)!\n");
+		return -ENOMEM;
+	}
+	for (i = 0; i < count_ids; i++) {
+		u8 addr[ETH_ALEN];
+		const char *str =  config_setting_get_string_elem(ids, i);
+		string_to_mac_address(str, addr);
+
+		station = calloc(1, sizeof(*station));
+		if (!station) {
+			w_flogf(ctx, LOG_ERR, stderr, "Out of memory!\n");
+			return -ENOMEM;
+		}
+		station->index = i;
+		memcpy(station->addr, addr, ETH_ALEN);
+		memcpy(station->hwaddr, addr, ETH_ALEN);
+		station->tx_power = SNR_DEFAULT;
+		station_init_queues(station);
+		list_add_tail(&station->list, &ctx->stations);
+		ctx->sta_array[i] = station;
+
+		w_logf(ctx, LOG_NOTICE, "Added station %d: " MAC_FMT "\n", i, MAC_ARGS(addr));
+	}
+	ctx->num_stas = count_ids;
+
+	enable_interference = config_lookup(cf, "ifaces.enable_interference");
+	if (enable_interference &&
+	    config_setting_get_bool(enable_interference)) {
+		ctx->intf = calloc(ctx->num_stas * ctx->num_stas,
+				   sizeof(struct intf_info));
+		if (!ctx->intf) {
+			w_flogf(ctx, LOG_ERR, stderr, "Out of memory(intf)\n");
+			return -ENOMEM;
+		}
+		for (i = 0; i < ctx->num_stas; i++)
+			for (j = 0; j < ctx->num_stas; j++)
+				ctx->intf[i * ctx->num_stas + j].signal = -200;
+	} else {
+		ctx->intf = NULL;
+	}
+
+	fading_coefficient =
+		config_lookup(cf, "model.fading_coefficient");
+	if (fading_coefficient &&
+	    config_setting_get_int(fading_coefficient) > 0) {
+		ctx->get_fading_signal = _get_fading_signal;
+		ctx->fading_coefficient =
+			config_setting_get_int(fading_coefficient);
+	} else {
+		ctx->get_fading_signal = get_no_fading_signal;
+		ctx->fading_coefficient = 0;
+	}
+
+	/* create link quality matrix */
+	ctx->snr_matrix = calloc(sizeof(int), count_ids * count_ids);
+	if (!ctx->snr_matrix) {
+		w_flogf(ctx, LOG_ERR, stderr, "Out of memory!\n");
+		return -ENOMEM;
+	}
+	/* set default snrs */
+	for (i = 0; i < count_ids * count_ids; i++)
+		ctx->snr_matrix[i] = SNR_DEFAULT;
+
+	links = config_lookup(cf, "ifaces.links");
+	if (!links) {
+		model_type = config_lookup(cf, "model.type");
+		if (model_type) {
+			model_type_str = config_setting_get_string(model_type);
+			if (memcmp("snr", model_type_str, strlen("snr")) == 0) {
+				links = config_lookup(cf, "model.links");
+			} else if (memcmp("prob", model_type_str,
+				strlen("prob")) == 0) {
+				error_probs = config_lookup(cf, "model.links");
+			} else if (memcmp("path_loss", model_type_str,
+				strlen("path_loss")) == 0) {
+				/* calculate signal from positions */
+				if (parse_path_loss(ctx, cf))
+					goto fail;
+			}
+		}
+	}
+
+	if (per_file && error_probs) {
+		w_flogf(ctx, LOG_ERR, stderr,
+			"per_file and error_probs could not be used at the same time\n");
+		goto fail;
+	}
+
+	ctx->get_link_snr = get_link_snr_from_snr_matrix;
+	ctx->get_error_prob = _get_error_prob_from_snr;
+
+	ctx->per_matrix = NULL;
+	ctx->per_matrix_row_num = 0;
+	if (per_file && read_per_file(ctx, per_file))
+		goto fail;
+
+	ctx->error_prob_matrix = NULL;
+	if (error_probs) {
+		ctx->error_prob_matrix = calloc(sizeof(double),
+						count_ids * count_ids);
+		if (!ctx->error_prob_matrix) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"Out of memory(error_prob_matrix)\n");
+			goto fail;
+		}
+
+		ctx->get_link_snr = get_link_snr_default;
+		ctx->get_error_prob = get_error_prob_from_matrix;
+
+		default_prob = config_lookup(cf, "model.default_prob");
+		if (default_prob) {
+			default_prob_value = config_setting_get_float(
+				default_prob);
+			if (default_prob_value < 0.0 ||
+			    default_prob_value > 1.0) {
+				w_flogf(ctx, LOG_ERR, stderr,
+					"model.default_prob should be in [0.0, 1.0]\n");
+				goto fail;
+			}
+		}
+	}
+
+	link_map = calloc(sizeof(bool), count_ids * count_ids);
+	if (!link_map) {
+		w_flogf(ctx, LOG_ERR, stderr, "Out of memory\n");
+		goto fail;
+	}
+
+	/* read snr values */
+	for (i = 0; links && i < config_setting_length(links); i++) {
+		config_setting_t *link;
+
+		link = config_setting_get_elem(links, i);
+		if (config_setting_length(link) != 3) {
+			w_flogf(ctx, LOG_ERR, stderr, "Invalid link: expected (int,int,int)\n");
+			goto fail;
+		}
+		start = config_setting_get_int_elem(link, 0);
+		end = config_setting_get_int_elem(link, 1);
+		snr = config_setting_get_int_elem(link, 2);
+
+		if (start < 0 || start >= ctx->num_stas ||
+		    end < 0 || end >= ctx->num_stas) {
+			w_flogf(ctx, LOG_ERR, stderr, "Invalid link [%d,%d,%d]: index out of range\n",
+					start, end, snr);
+			goto fail;
+		}
+		ctx->snr_matrix[ctx->num_stas * start + end] = snr;
+		link_map[ctx->num_stas * start + end] = true;
+	}
+
+	/* initialize with default_prob */
+	for (start = 0; error_probs && start < ctx->num_stas; start++)
+		for (end = start + 1; end < ctx->num_stas; end++) {
+			ctx->error_prob_matrix[ctx->num_stas *
+				start + end] =
+			ctx->error_prob_matrix[ctx->num_stas *
+				end + start] = default_prob_value;
+		}
+
+	/* read error probabilities */
+	for (i = 0; error_probs &&
+	     i < config_setting_length(error_probs); i++) {
+		float error_prob_value;
+
+		error_prob = config_setting_get_elem(error_probs, i);
+		if (config_setting_length(error_prob) != 3) {
+			w_flogf(ctx, LOG_ERR, stderr, "Invalid error probability: expected (int,int,float)\n");
+			goto fail;
+		}
+
+		start = config_setting_get_int_elem(error_prob, 0);
+		end = config_setting_get_int_elem(error_prob, 1);
+		error_prob_value = config_setting_get_float_elem(error_prob, 2);
+
+		if (start < 0 || start >= ctx->num_stas ||
+		    end < 0 || end >= ctx->num_stas ||
+		    error_prob_value < 0.0 || error_prob_value > 1.0) {
+			w_flogf(ctx, LOG_ERR, stderr, "Invalid error probability [%d,%d,%f]\n",
+				start, end, error_prob_value);
+			goto fail;
+		}
+
+		ctx->error_prob_matrix[ctx->num_stas * start + end] =
+			error_prob_value;
+		link_map[ctx->num_stas * start + end] = true;
+	}
+
+	/*
+	 * If any links are specified in only one direction, mirror them,
+	 * making them symmetric.  If specified in both directions they
+	 * can be asymmetric.
+	 */
+	for (i = 0; i < ctx->num_stas; i++) {
+		for (j = 0; j < ctx->num_stas; j++) {
+			if (i == j)
+				continue;
+
+			if (link_map[ctx->num_stas * i + j] &&
+			    !link_map[ctx->num_stas * j + i]) {
+				mirror_link(ctx, i, j);
+			}
+		}
+	}
+
+	free(link_map);
+	config_destroy(cf);
+	return 0;
+
+fail:
+	free(ctx->snr_matrix);
+	free(ctx->error_prob_matrix);
+	config_destroy(cf);
+	return -EINVAL;
+}
diff --git a/wmediumd/config.h b/wmediumd/config.h
new file mode 100644
index 0000000..f9bcdf4
--- /dev/null
+++ b/wmediumd/config.h
@@ -0,0 +1,30 @@
+/*
+ *	wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ *	Copyright (c) 2011 cozybit Inc.
+ *
+ *	Author:	Javier Lopez	<jlopex@cozybit.com>
+ *		Javier Cardona	<javier@cozybit.com>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version 2
+ *	of the License, or (at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *	02110-1301, USA.
+ */
+
+#ifndef CONFIG_H_
+#define CONFIG_H_
+
+int load_config(struct wmediumd *ctx, const char *file, const char *per_file);
+int use_fixed_random_value(struct wmediumd *ctx);
+
+#endif /* CONFIG_H_ */
diff --git a/wmediumd/ieee80211.h b/wmediumd/ieee80211.h
new file mode 100644
index 0000000..f3deb4b
--- /dev/null
+++ b/wmediumd/ieee80211.h
@@ -0,0 +1,74 @@
+/*
+ *	wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ *	Copyright (c) 2011 cozybit Inc.
+ *
+ *	Author:	Javier Lopez	<jlopex@cozybit.com>
+ *		Javier Cardona	<javier@cozybit.com>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version 2
+ *	of the License, or (at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *	02110-1301, USA.
+ */
+
+#ifndef IEEE80211_H_
+#define IEEE80211_H_
+
+#define IEEE80211_AVAILABLE_RATES 12
+#define IEEE80211_TX_MAX_RATES 4
+#define IEEE80211_NUM_ACS 4
+
+#ifndef ETH_ALEN
+#define ETH_ALEN 6
+#endif
+
+#define FCTL_FTYPE		0x0c
+#define FCTL_TODS		0x01
+#define FCTL_FROMDS		0x02
+
+#define FTYPE_MGMT		0x00
+#define FTYPE_DATA		0x08
+
+#define STYPE_QOS_DATA		0x80
+
+#define QOS_CTL_TAG1D_MASK	0x07
+
+enum ieee80211_ac_number {
+	IEEE80211_AC_VO		= 0,
+	IEEE80211_AC_VI		= 1,
+	IEEE80211_AC_BE		= 2,
+	IEEE80211_AC_BK		= 3,
+};
+
+static const enum ieee80211_ac_number ieee802_1d_to_ac[8] = {
+	IEEE80211_AC_BE,
+	IEEE80211_AC_BK,
+	IEEE80211_AC_BK,
+	IEEE80211_AC_BE,
+	IEEE80211_AC_VI,
+	IEEE80211_AC_VI,
+	IEEE80211_AC_VO,
+	IEEE80211_AC_VO
+};
+
+struct ieee80211_hdr {
+	unsigned char frame_control[2];
+	unsigned char duration_id[2];
+	unsigned char addr1[ETH_ALEN];
+	unsigned char addr2[ETH_ALEN];
+	unsigned char addr3[ETH_ALEN];
+	unsigned char seq_ctrl[2];
+	unsigned char addr4[ETH_ALEN];
+};
+
+#endif /* IEEE80211_H_ */
diff --git a/wmediumd/inc/linux/um_timetravel.h b/wmediumd/inc/linux/um_timetravel.h
new file mode 100644
index 0000000..174057d
--- /dev/null
+++ b/wmediumd/inc/linux/um_timetravel.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _UAPI_LINUX_UM_TIMETRAVEL_H
+#define _UAPI_LINUX_UM_TIMETRAVEL_H
+#include <linux/types.h>
+
+/**
+ * struct um_timetravel_msg - UM time travel message
+ *
+ * This is the basic message type, going in both directions.
+ *
+ * This is the message passed between the host (user-mode Linux instance)
+ * and the calendar (the application on the other side of the socket) in
+ * order to implement common scheduling.
+ *
+ * Whenever UML has an event it will request runtime for it from the
+ * calendar, and then wait for its turn until it can run, etc. Note
+ * that it will only ever request the single next runtime, i.e. multiple
+ * REQUEST messages override each other.
+ */
+struct um_timetravel_msg {
+	/**
+	 * @op: operation value from &enum um_timetravel_ops
+	 */
+	__u32 op;
+
+	/**
+	 * @seq: sequence number for the message - shall be reflected in
+	 *	the ACK response, and should be checked while processing
+	 *	the response to see if it matches
+	 */
+	__u32 seq;
+
+	/**
+	 * @time: time in nanoseconds
+	 */
+	__u64 time;
+};
+
+/**
+ * enum um_timetravel_ops - Operation codes
+ */
+enum um_timetravel_ops {
+	/**
+	 * @UM_TIMETRAVEL_ACK: response (ACK) to any previous message,
+	 *	this usually doesn't carry any data in the 'time' field
+	 *	unless otherwise specified below
+	 */
+	UM_TIMETRAVEL_ACK		= 0,
+
+	/**
+	 * @UM_TIMETRAVEL_START: initialize the connection, the time
+	 *	field contains an (arbitrary) ID to possibly be able
+	 *	to distinguish the connections.
+	 */
+	UM_TIMETRAVEL_START		= 1,
+
+	/**
+	 * @UM_TIMETRAVEL_REQUEST: request to run at the given time
+	 *	(host -> calendar)
+	 */
+	UM_TIMETRAVEL_REQUEST		= 2,
+
+	/**
+	 * @UM_TIMETRAVEL_WAIT: Indicate waiting for the previously requested
+	 *	runtime, new requests may be made while waiting (e.g. due to
+	 *	interrupts); the time field is ignored. The calendar must process
+	 *	this message and later	send a %UM_TIMETRAVEL_RUN message when
+	 *	the host can run again.
+	 *	(host -> calendar)
+	 */
+	UM_TIMETRAVEL_WAIT		= 3,
+
+	/**
+	 * @UM_TIMETRAVEL_GET: return the current time from the calendar in the
+	 *	ACK message, the time in the request message is ignored
+	 *	(host -> calendar)
+	 */
+	UM_TIMETRAVEL_GET		= 4,
+
+	/**
+	 * @UM_TIMETRAVEL_UPDATE: time update to the calendar, must be sent e.g.
+	 *	before kicking an interrupt to another calendar
+	 *	(host -> calendar)
+	 */
+	UM_TIMETRAVEL_UPDATE		= 5,
+
+	/**
+	 * @UM_TIMETRAVEL_RUN: run time request granted, current time is in
+	 *	the time field
+	 *	(calendar -> host)
+	 */
+	UM_TIMETRAVEL_RUN		= 6,
+
+	/**
+	 * @UM_TIMETRAVEL_FREE_UNTIL: Enable free-running until the given time,
+	 *	this is a message from the calendar telling the host that it can
+	 *	freely do its own scheduling for anything before the indicated
+	 *	time.
+	 *	Note that if a calendar sends this message once, the host may
+	 *	assume that it will also do so in the future, if it implements
+	 *	wraparound semantics for the time field.
+	 *	(calendar -> host)
+	 */
+	UM_TIMETRAVEL_FREE_UNTIL	= 7,
+
+	/**
+	 * @UM_TIMETRAVEL_GET_TOD: Return time of day, typically used once at
+	 *	boot by the virtual machines to get a synchronized time from
+	 *	the simulation.
+	 */
+	UM_TIMETRAVEL_GET_TOD		= 8,
+};
+
+#endif /* _UAPI_LINUX_UM_TIMETRAVEL_H */
diff --git a/wmediumd/inc/usfstl/assert.h b/wmediumd/inc/usfstl/assert.h
new file mode 100644
index 0000000..bc46cd0
--- /dev/null
+++ b/wmediumd/inc/usfstl/assert.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_ASSERT_H_
+#define _USFSTL_ASSERT_H_
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+
+/*
+ * usfstl_abort - abort a test and print the given message(s)
+ */
+static inline void __attribute__((noreturn, format(printf, 4, 5)))
+usfstl_abort(const char *fn, unsigned int line, const char *cond, const char *msg, ...)
+{
+	va_list va;
+
+	printf("assertion failure in %s (line %d)\n", fn, line);
+	printf("'%s' failed\n", cond);
+	va_start(va, msg);
+	vprintf(msg, va);
+	va_end(va);
+	abort();
+}
+
+#define USFSTL_BUILD_BUG_ON(expr)	extern void __bbo_##__LINE__(char[1 - 2*!!(expr)]);
+
+#define _USFSTL_2STR(x)			#x
+#define USFSTL_2STR(x)			_USFSTL_2STR(x)
+
+#define __USFSTL_COUNTDOWN		10,9,8,7,6,5,4,3,2,1,0
+#define __USFSTL_COUNT(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...)	N
+#define _USFSTL_COUNT(...)		__USFSTL_COUNT(__VA_ARGS__)
+#define USFSTL_COUNT(...)		_USFSTL_COUNT(, ##__VA_ARGS__, __USFSTL_COUNTDOWN)
+#define __USFSTL_ASSERT_HASARGS_LIST	1,1,1,1,1,1,1,1,1,1,0
+#define USFSTL_HASARGS(...)		_USFSTL_COUNT(, ##__VA_ARGS__, __USFSTL_ASSERT_HASARGS_LIST)
+
+#define __usfstl_dispatch(func, numargs) \
+        func ## numargs
+#define _usfstl_dispatch(func, numargs) \
+        __usfstl_dispatch(func, numargs)
+#define usfstl_dispatch(func, ...) \
+	_usfstl_dispatch(func, USFSTL_HASARGS(__VA_ARGS__))
+
+#define _USFSTL_ASSERT_0(cstr, cond) do {				\
+	if (!(cond))							\
+		usfstl_abort(__FILE__, __LINE__, cstr, "");		\
+} while (0)
+
+#define _USFSTL_ASSERT_1(cstr, cond, msg, ...) do {			\
+	if (!(cond))							\
+		usfstl_abort(__FILE__, __LINE__, cstr,			\
+			     msg, ##__VA_ARGS__);			\
+} while (0)
+
+/*
+ * assert, with or without message
+ *
+ * USFSTL_ASSERT(cond)
+ * USFSTL_ASSERT(cond, msg, format)
+ */
+#define USFSTL_ASSERT(cond, ...)	\
+	usfstl_dispatch(_USFSTL_ASSERT_, __VA_ARGS__)(#cond, cond, ##__VA_ARGS__)
+
+#define _USFSTL_ASSERT_CMP_0(as, a, op, bs, b, fmt) do {		\
+	typeof(a) _a = a;						\
+	typeof(b) _b = b;						\
+	if (!((_a) op (_b)))						\
+		usfstl_abort(__FILE__, __LINE__, as " " #op " " bs,	\
+			     "  " as " = " fmt "\n  " bs " = " fmt "\n",\
+			     _a, _b);					\
+} while (0)
+
+#define _USFSTL_ASSERT_CMP_1(as, a, op, bs, b, fmt, prfn) do {		\
+	typeof(a) _a = a;						\
+	typeof(b) _b = b;						\
+	if (!((_a) op (_b)))						\
+		usfstl_abort(__FILE__, __LINE__, as " " #op " " bs,	\
+			     "  " as " = " fmt "\n  " bs " = " fmt "\n",\
+			     prfn(_a), prfn(_b));			\
+} while (0)
+
+/*
+ * Assert that two values are equal.
+ *
+ * Note that this is a special case of USFSTL_ASSERT_CMP() below, so the
+ * documentation for that applies.
+ */
+#define USFSTL_ASSERT_EQ(a, b, fmt, ...)				\
+	usfstl_dispatch(_USFSTL_ASSERT_CMP_, __VA_ARGS__)(#a, a, ==,	\
+							  #b, b, fmt,	\
+							  ##__VA_ARGS__)
+
+/*
+ * Assert a comparison is true.
+ *
+ * Given a value, comparison operator and another value it checks that
+ * the comparison is true, and aborts the test (or program, if used
+ * outside a test) otherwise.
+ *
+ * You must pass a format string suitable for printing the values.
+ *
+ * You may additionally pass a formatting macro that evaluates the
+ * data for the format string, e.g.
+ *
+ * #define val_and_addr(x) (x), &(x)
+ * ...
+ * int x = 1, y = 2;
+ * USFSTL_ASSERT_CMP(x, ==, y, "%d (at %p)", val_and_addr)
+ *
+ * Will result in the printout
+ * [...]
+ *   x = 1 (at 0xffff1110)
+ *   y = 2 (at 0xffff1114)
+ */
+#define USFSTL_ASSERT_CMP(a, op, b, fmt, ...)				\
+	usfstl_dispatch(_USFSTL_ASSERT_CMP_, __VA_ARGS__)(#a, a, op,	\
+							  #b, b, fmt,	\
+							  ##__VA_ARGS__)
+
+#endif // _USFSTL_ASSERT_H_
diff --git a/wmediumd/inc/usfstl/list.h b/wmediumd/inc/usfstl/list.h
new file mode 100644
index 0000000..a07c9e9
--- /dev/null
+++ b/wmediumd/inc/usfstl/list.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_LIST_H_
+#define _USFSTL_LIST_H_
+#include <stddef.h>
+#include <stdbool.h>
+
+#ifndef offsetof
+#define offsetof __builtin_offsetof
+#endif
+
+#ifndef container_of
+#define container_of(ptr, type, member) ((type *)(void *)((char *)ptr - offsetof(type, member)))
+#endif
+
+struct usfstl_list_entry {
+	struct usfstl_list_entry *next, *prev;
+};
+
+struct usfstl_list {
+	struct usfstl_list_entry list;
+};
+
+#define USFSTL_LIST_INIT(name) {	\
+	.list.next = &(name).list,	\
+	.list.prev = &(name).list,	\
+}
+#define USFSTL_LIST(name) struct usfstl_list name = USFSTL_LIST_INIT(name)
+
+static inline void usfstl_list_init(struct usfstl_list *list)
+{
+	list->list.next = &list->list;
+	list->list.prev = &list->list;
+}
+
+static inline void usfstl_list_insert_before(struct usfstl_list_entry *existing,
+					     struct usfstl_list_entry *new)
+{
+	new->prev = existing->prev;
+	existing->prev->next = new;
+	existing->prev = new;
+	new->next = existing;
+}
+
+static inline void usfstl_list_append(struct usfstl_list *list,
+				      struct usfstl_list_entry *new)
+{
+	usfstl_list_insert_before(&list->list, new);
+}
+
+#define usfstl_list_item(element, type, member) \
+	((type *)container_of(element, type, member))
+
+#define usfstl_next_item(_list, entry, type, member) \
+	((entry)->member.next != &(_list)->list ? \
+		usfstl_list_item((entry)->member.next, type, member) :\
+		NULL)
+
+#define usfstl_for_each_list_item(item, _list, member) \
+	for (item = usfstl_list_first_item(_list, typeof(*item), member); \
+	     item; \
+	     item = usfstl_next_item(_list, item, typeof(*item), member))
+
+#define usfstl_for_each_list_item_safe(item, next, _list, member) \
+	for (item = usfstl_list_first_item(_list, typeof(*item), member), \
+	     next = item ? usfstl_next_item(_list, item, typeof(*next), member) : NULL; \
+	     item; \
+	     item = next, \
+	     next = item ? usfstl_next_item(_list, next, typeof(*next), member) : NULL)
+
+#define usfstl_for_each_list_item_continue_safe(item, next, _list, member) \
+	for (item = item ? usfstl_next_item(_list, item, typeof(*item), member) : \
+			   usfstl_list_first_item(_list, typeof(*item), member), \
+	     next = item ? usfstl_next_item(_list, item, typeof(*item), member) : NULL; \
+	     item; \
+	     item = next, next = item ? usfstl_next_item(_list, next, typeof(*item), member) : NULL)
+
+static inline bool usfstl_list_empty(const struct usfstl_list *list)
+{
+	return list->list.next == &list->list;
+}
+
+#define usfstl_list_first_item(_list, type, member) \
+	(usfstl_list_empty(_list) ? NULL : usfstl_list_item((_list)->list.next, type, member))
+
+static inline void usfstl_list_item_remove(struct usfstl_list_entry *entry)
+{
+	entry->next->prev = entry->prev;
+	entry->prev->next = entry->next;
+	entry->next = NULL;
+	entry->prev = NULL;
+}
+
+#endif // _USFSTL_LIST_H_
diff --git a/wmediumd/inc/usfstl/loop.h b/wmediumd/inc/usfstl/loop.h
new file mode 100644
index 0000000..6d6204d
--- /dev/null
+++ b/wmediumd/inc/usfstl/loop.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+/*
+ * This defines a simple mainloop for reading from multiple sockets and handling
+ * the one that becomes readable. Note that on Windows, it can currently only
+ * handle sockets, not arbitrary descriptors, since we only need it for RPC.
+ */
+#ifndef _USFSTL_LOOP_H_
+#define _USFSTL_LOOP_H_
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include "list.h"
+
+#ifdef _WIN32
+typedef uintptr_t usfstl_fd_t;
+#else
+typedef int usfstl_fd_t;
+#endif
+
+/**
+ * struct usfstl_loop_entry - main loop entry
+ * @list: private
+ * @handler: handler to call when fd is readable
+ * @fd: file descriptor
+ * @priority: priority, higher is handled earlier;
+ *	must not change while the entry is registered
+ * @data: user data
+ */
+struct usfstl_loop_entry {
+	struct usfstl_list_entry list;
+	void (*handler)(struct usfstl_loop_entry *);
+	usfstl_fd_t fd;
+	int priority;
+	void *data;
+};
+
+extern struct usfstl_list g_usfstl_loop_entries;
+
+/**
+ * g_usfstl_loop_pre_handler_fn - pre-handler function
+ *
+ * If assigned (defaults to %NULL) this handler will be called
+ * before every loop handler's handling function, e.g. in order
+ * to synchronize time with the wallclock scheduler.
+ */
+extern void (*g_usfstl_loop_pre_handler_fn)(void *data);
+extern void *g_usfstl_loop_pre_handler_fn_data;
+
+/**
+ * usfstl_loop_register - add an entry to the mainloop
+ * @entry: the entry to add, must be fully set up including
+ *	the priority
+ */
+void usfstl_loop_register(struct usfstl_loop_entry *entry);
+
+/**
+ * usfstl_loop_unregister - remove an entry from the mainloop
+ * @entry: the entry to remove
+ */
+void usfstl_loop_unregister(struct usfstl_loop_entry *entry);
+
+/**
+ * usfstl_loop_wait_and_handle - wait and handle a single event
+ *
+ * Wait for, and handle, a single event, then return.
+ */
+void usfstl_loop_wait_and_handle(void);
+
+/**
+ * usfstl_loop_for_each_entry - iterate main loop entries
+ */
+#define usfstl_loop_for_each_entry(entry) \
+	usfstl_for_each_list_item(entry, &g_usfstl_loop_entries, list)
+
+/**
+ * usfstl_loop_for_each_entry_safe - iterate main loop entries safely
+ *
+ * Where "safely" means safe to concurrent modification.
+ */
+#define usfstl_loop_for_each_entry_safe(entry, tmp) \
+	usfstl_for_each_list_item_safe(entry, tmp, &g_usfstl_loop_entries, list)
+
+#endif // _USFSTL_LOOP_H_
diff --git a/wmediumd/inc/usfstl/sched.h b/wmediumd/inc/usfstl/sched.h
new file mode 100644
index 0000000..48e3826
--- /dev/null
+++ b/wmediumd/inc/usfstl/sched.h
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_SCHED_H_
+#define _USFSTL_SCHED_H_
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include "assert.h"
+#include "list.h"
+#include "loop.h"
+
+/*
+ * usfstl's simple scheduler
+ *
+ * usfstl's concept of time is just a free-running counter. You can use
+ * any units you like, but we recommend micro or nanoseconds, even
+ * with nanoseconds you can simulate ~580 years in a uint64_t time.
+ *
+ * The scheduler is just a really basic concept, you enter "jobs"
+ * and then call usfstl_sched_next() to run the next job. Usually,
+ * this job would schedule another job and call usfstl_sched_next()
+ * again, etc.
+ *
+ * The scheduler supports grouping jobs (currently into up to 32
+ * groups) and then blocking certain groups from being executed in
+ * the next iteration. This can be used, for example, to separate
+ * in-SIM and out-of-SIM jobs, or threads from IRQs, etc. Note
+ * that groups and priorities are two entirely separate concepts.
+ *
+ * In many cases, you'll probably be looking for usfstltask.h instead
+ * as that allows you to simulate a cooperative multithreading system.
+ * However, raw jobs may in that case be useful for things like e.g.
+ * interrupts that come into the simulated system (if they're always
+ * run-to-completion i.e. cannot yield to other jobs.)
+ *
+ * Additionally, the scheduler has APIs to integrate with another,
+ * external, scheduler to synchronize multiple components.
+ */
+
+/**
+ * struct usfstl_job - usfstl scheduler job
+ * @time: time this job fires
+ * @priority: priority of the job, in case of multiple happening
+ *	at the same time; higher value means higher priority
+ * @group: group value, in range 0-31
+ * @name: job name
+ * @data: job data
+ * @callback: called when the job occurs
+ */
+struct usfstl_job {
+	uint64_t start;
+	uint32_t priority;
+	uint8_t group;
+	const char *name;
+
+	void *data;
+	void (*callback)(struct usfstl_job *job);
+
+	/* private: */
+	struct usfstl_list_entry entry;
+};
+
+/**
+ * struct usfstl_scheduler - usfstl scheduler structure
+ * @external_request: If external scheduler integration is required,
+ *	set this function pointer appropriately to request the next
+ *	run time from the external scheduler.
+ * @external_wait: For external scheduler integration, this must wait
+ *	for the previously requested runtime being granted, and you
+ *	must call usfstl_sched_set_time() before returning from this
+ *	function.
+ * @external_sync_from: For external scheduler integration, return current
+ *	time based on external time info.
+ * @time_advanced: Set this to have logging (or similar) when time
+ *	advances. Note that the argument is relative to the previous
+ *	time, if you need the current absolute time use
+ *	usfstl_sched_current_time(), subtract @delta from that to
+ *	obtain the time prior to the current advance.
+ *
+ * Use USFSTL_SCHEDULER() to declare (and initialize) a scheduler.
+ */
+struct usfstl_scheduler {
+	void (*external_request)(struct usfstl_scheduler *, uint64_t);
+	void (*external_wait)(struct usfstl_scheduler *);
+	uint64_t (*external_sync_from)(struct usfstl_scheduler *sched);
+	void (*time_advanced)(struct usfstl_scheduler *, uint64_t delta);
+
+/* private: */
+	void (*next_time_changed)(struct usfstl_scheduler *);
+	uint64_t current_time;
+	uint64_t prev_external_sync, next_external_sync;
+
+	struct usfstl_list joblist;
+	struct usfstl_list pending_jobs;
+	struct usfstl_job *allowed_job;
+
+	uint32_t blocked_groups;
+	uint8_t next_external_sync_set:1,
+		prev_external_sync_set:1,
+		waiting:1;
+
+	struct {
+		struct usfstl_loop_entry entry;
+		uint64_t start;
+		uint32_t nsec_per_tick;
+		uint8_t timer_triggered:1,
+			initialized:1;
+	} wallclock;
+
+	struct {
+		struct usfstl_scheduler *parent;
+		int64_t offset;
+		uint32_t tick_ratio;
+		struct usfstl_job job;
+		bool waiting;
+	} link;
+
+	struct {
+		void *ctrl;
+	} ext;
+};
+
+#define USFSTL_SCHEDULER(name)						\
+	struct usfstl_scheduler name = {				\
+		.joblist = USFSTL_LIST_INIT(name.joblist),		\
+		.pending_jobs = USFSTL_LIST_INIT(name.pending_jobs),	\
+	}
+
+#define usfstl_time_check(x) \
+	({ uint64_t __t; typeof(x) __x; (void)(&__t == &__x); 1; })
+
+/**
+ * usfstl_time_cmp - compare time, wrap-around safe
+ * @a: first time
+ * @op: comparison operator (<, >, <=, >=)
+ * @b: second time
+ *
+ * Returns: (a op b), e.g. usfstl_time_cmp(a, >=, b) returns (a >= b)
+ *	while accounting for wrap-around
+ */
+#define usfstl_time_cmp(a, op, b)	\
+	(usfstl_time_check(a) && usfstl_time_check(b) && \
+	 (0 op (int64_t)((b) - (a))))
+
+/**
+ * USFSTL_ASSERT_TIME_CMP - assert that the time comparison holds
+ * @a: first time
+ * @op: comparison operator
+ * @b: second time
+ */
+#define USFSTL_ASSERT_TIME_CMP(a, op, b) do {				\
+	uint64_t _a = a;						\
+	uint64_t _b = b;						\
+	if (!usfstl_time_cmp(_a, op, _b))				\
+		usfstl_abort(__FILE__, __LINE__,			\
+			     "usfstl_time_cmp(" #a ", " #op ", " #b ")",\
+			     "  " #a " = %" PRIu64 "\n"			\
+			     "  " #b " = %" PRIu64 "\n",		\
+			     _a, _b);					\
+} while (0)
+
+/**
+ * usfstl_sched_current_time - return current time
+ * @sched: the scheduler to operate with
+ */
+uint64_t usfstl_sched_current_time(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_sched_add_job - add job execution
+ * @sched: the scheduler to operate with
+ * @job: job to add
+ *
+ * Add an job to the execution queue, at the time noted
+ * inside the job.
+ */
+void usfstl_sched_add_job(struct usfstl_scheduler *sched,
+			  struct usfstl_job *job);
+
+/**
+ * @usfstl_sched_del_job - remove an job
+ * @sched: the scheduler to operate with
+ * @job: job to remove
+ *
+ * Remove an job from the execution queue, if present.
+ */
+void usfstl_sched_del_job(struct usfstl_job *job);
+
+/**
+ * usfstl_sched_start - start the scheduler
+ * @sched: the scheduler to operate with
+ *
+ * Start the scheduler, which initializes the scheduler data
+ * and syncs with the external scheduler if necessary.
+ */
+void usfstl_sched_start(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_sched_next - call next job
+ * @sched: the scheduler to operate with
+ *
+ * Go into the scheduler, forward time to the next job,
+ * and call its callback.
+ *
+ * Returns the job that was run.
+ */
+struct usfstl_job *usfstl_sched_next(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_job_scheduled - check if an job is scheduled
+ * @job: the job to check
+ *
+ * Returns: %true if the job is on the schedule list, %false otherwise.
+ */
+bool usfstl_job_scheduled(struct usfstl_job *job);
+
+/**
+ * usfstl_sched_next_pending - get first/next pending job
+ * @sched: the scheduler to operate with
+ * @job: %NULL or previously returned job
+ *
+ * This is used to implement usfstl_sched_for_each_pending()
+ * and usfstl_sched_for_each_pending_safe().
+ */
+struct usfstl_job *usfstl_sched_next_pending(struct usfstl_scheduler *sched,
+					 struct usfstl_job *job);
+
+#define usfstl_sched_for_each_pending(sched, job) \
+	for (job = usfstl_sched_next_pending(sched, NULL); job; \
+	     job = usfstl_sched_next_pending(sched, job))
+
+#define usfstl_sched_for_each_pending_safe(sched, job, tmp) \
+	for (job = usfstl_sched_next_pending(sched, NULL), \
+	     tmp = usfstl_sched_next_pending(sched, job); \
+	     job; \
+	     job = tmp, tmp = usfstl_sched_next_pending(sched, tmp))
+
+struct usfstl_sched_block_data {
+	uint32_t groups;
+	struct usfstl_job *job;
+};
+
+/**
+ * usfstl_sched_block_groups - block groups from executing
+ * @sched: the scheduler to operate with
+ * @groups: groups to block, ORed with the currently blocked groups
+ * @job: single job that's allowed anyway, e.g. if the caller is
+ *	part of the blocked group and must be allowed to continue
+ * @save: save data, use with usfstl_sched_restore_groups()
+ */
+void usfstl_sched_block_groups(struct usfstl_scheduler *sched, uint32_t groups,
+			       struct usfstl_job *job,
+			       struct usfstl_sched_block_data *save);
+
+/**
+ * usfstl_sched_restore_groups - restore blocked groups
+ * @sched: the scheduler to operate with
+ * @restore: data saved during usfstl_sched_block_groups()
+ */
+void usfstl_sched_restore_groups(struct usfstl_scheduler *sched,
+				 struct usfstl_sched_block_data *restore);
+
+/**
+ * usfstl_sched_set_time - set time from external source
+ * @sched: the scheduler to operate with
+ * @time: time
+ *
+ * Set the scheduler time from the external source, use this
+ * before returning from the sched_external_wait() method but also when
+ * injecting any other kind of job like an interrupt from an
+ * external source (only applicable when running with external
+ * scheduling and other external interfaces.)
+ */
+void usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time);
+
+/**
+ * usfstl_sched_set_sync_time - set next external sync time
+ * @sched: the scheduler to operate with
+ * @time: time
+ *
+ * When cooperating with an external scheduler, it may be good to
+ * avoid ping-pong all the time, if there's nothing to do. This
+ * function facilitates that.
+ *
+ * Call it before returning from the sched_external_wait() method and
+ * the scheduler will not sync for each internal job again, but
+ * only when the next internal job would be at or later than the
+ * time given as the argument here.
+ *
+ * Note that then you also have to coordinate with the external
+ * scheduler every time there's any interaction with any other
+ * component also driven by the external scheduler.
+ *
+ * To understand this, consider the following timeline, where the
+ * letters indicate scheduler jobs:
+ *  - component 1: A      C D E F
+ *  - component 2:   B             G
+ * Without calling this function, component 1 will always sync
+ * with the external scheduler in component 2, for every job
+ * it has. However, after the sync for job/time C, component
+ * 2 knows that it will not have any job until G, so if it
+ * tells component 1 (whatever RPC there is calls this function)
+ * then component 1 will sync again only after F.
+ *
+ * However, this also necessitates that whenever the components
+ * interact, this function could be called again. If you imagine
+ * jobs C-F to just be empty ticks that do nothing then this
+ * might not happen. However, if one of them causes interaction
+ * with component 2 (say a network packet in the simulation) the
+ * interaction may cause a new job to be inserted into the
+ * scheduler timeline of component 2. Let's say, for example,
+ * the job D was a transmission from component 1 to 2, and in
+ * component 2 that causes an interrupt and a rescheduling. The
+ * above timeline will thus change to:
+ *  - component 1: A      C D E F
+ *  - component 2:   B       N     G
+ * inserting the job N. Thus, this very interaction needs to
+ * call this function again with the time of N which will be at
+ * or shortly after the time of D, rather than at G.
+ *
+ * This optimises things if jobs C-F don't cause interaction
+ * with other components, and if considered properly in the RPC
+ * protocol will not cause any degradation.
+ *
+ * If not supported, just never call this function and each and
+ * every job will require external synchronisation.
+ */
+void usfstl_sched_set_sync_time(struct usfstl_scheduler *sched, uint64_t time);
+
+/**
+ * g_usfstl_top_scheduler - top level scheduler
+ *
+ * There can be multiple schedulers in an usfstl binary, in particular
+ * when the multi-binary support code is used. In any case, this will
+ * will point to the top-level scheduler to facilitate another level
+ * of integration if needed.
+ */
+extern struct usfstl_scheduler *g_usfstl_top_scheduler;
+
+/**
+ * usfstl_sched_wallclock_init - initialize wall-clock integration
+ * @sched: the scheduler to initialize, it must not have external
+ *	integration set up yet
+ * @ns_per_tick: nanoseconds per scheduler tick
+ *
+ * You can use this function to set up a scheduler to run at roughly
+ * wall clock speed (per the @ns_per_tick setting).
+ *
+ * This is compatible with the usfstlloop abstraction, so you can also
+ * add other things to the event loop and they'll be handled while
+ * the scheduler is waiting for time to pass.
+ *
+ * NOTE: This is currently Linux-only.
+ */
+void usfstl_sched_wallclock_init(struct usfstl_scheduler *sched,
+				 unsigned int ns_per_tick);
+
+/**
+ * usfstl_sched_wallclock_exit - remove wall-clock integration
+ * @sched: scheduler to remove wall-clock integration from
+ *
+ * This releases any resources used for the wall-clock integration.
+ */
+void usfstl_sched_wallclock_exit(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_sched_wallclock_wait_and_handle - wait for external events
+ * @sched: scheduler that's integrated with the wallclock
+ *
+ * If no scheduler events are pending, this will wait for external
+ * events using usfstl_wait_and_handle() and synchronize the time it
+ * took for such an event to arrive into the given scheduler.
+ */
+void usfstl_sched_wallclock_wait_and_handle(struct usfstl_scheduler *sched);
+
+/**
+ * usfstl_sched_link - link a scheduler to another one
+ * @sched: the scheduler to link, must not already use the external
+ *	request methods, of course. Should also not be running.
+ * @parent: the parent scheduler to link to
+ * @tick_ratio: "tick_ratio" parent ticks == 1 of our ticks;
+ *	e.g. 1000 for if @sched should have microseconds, while @parent
+ *	uses nanoseconds.
+ *
+ * This links two schedulers together, and requesting any runtime in the
+ * inner scheduler (@sched) depends on the parent scheduler (@parent)
+ * granting it.
+ *
+ * Time in the inner scheduler is adjusted in two ways:
+ * 1) there's a "tick_ratio" as described above
+ * 2) at the time of linking, neither scheduler changes its current
+ *    time, instead an offset between the two is maintained, so the
+ *    inner scheduler can be at e.g. zero and be linked to a parent
+ *    that has already been running for a while.
+ */
+void usfstl_sched_link(struct usfstl_scheduler *sched,
+		       struct usfstl_scheduler *parent,
+		       uint32_t tick_ratio);
+
+/**
+ * usfstl_sched_unlink - unlink a scheduler again
+ * @sched: the scheduler to unlink, must be linked
+ */
+void usfstl_sched_unlink(struct usfstl_scheduler *sched);
+
+#endif // _USFSTL_SCHED_H_
diff --git a/wmediumd/inc/usfstl/schedctrl.h b/wmediumd/inc/usfstl/schedctrl.h
new file mode 100644
index 0000000..c8b1cb6
--- /dev/null
+++ b/wmediumd/inc/usfstl/schedctrl.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_SCHEDCTRL_H_
+#define _USFSTL_SCHEDCTRL_H_
+#include <stdint.h>
+#include <inttypes.h>
+#include "loop.h"
+#include "sched.h"
+
+struct usfstl_sched_ctrl {
+	struct usfstl_scheduler *sched;
+	uint64_t ack_time;
+	int64_t offset;
+	uint32_t nsec_per_tick;
+	int fd;
+	unsigned int waiting:1, acked:1, frozen:1, started:1;
+	uint32_t expected_ack_seq;
+};
+
+void usfstl_sched_ctrl_start(struct usfstl_sched_ctrl *ctrl,
+			     const char *socket,
+			     uint32_t nsec_per_tick,
+			     uint64_t client_id,
+			     struct usfstl_scheduler *sched);
+void usfstl_sched_ctrl_sync_to(struct usfstl_sched_ctrl *ctrl);
+void usfstl_sched_ctrl_sync_from(struct usfstl_sched_ctrl *ctrl);
+void usfstl_sched_ctrl_stop(struct usfstl_sched_ctrl *ctrl);
+
+/**
+ * usfstl_sched_ctrl_set_frozen - freeze/thaw scheduler interaction
+ * @ctrl: scheduler control
+ * @frozen: whether or not the scheduler interaction is frozen
+ *
+ * When the scheduler control connection is frozen, then any remote
+ * updates will not reflect in the local scheduler, but instead will
+ * just modify the offset vs. the remote.
+ *
+ * This allows scheduling repeatedly at "time zero" while the remote
+ * side is actually running, e.g. to ensure pre-firmware boot hardware
+ * simulation doesn't affect the firmware simulation time.
+ */
+void usfstl_sched_ctrl_set_frozen(struct usfstl_sched_ctrl *ctrl, bool frozen);
+
+#endif // _USFSTL_SCHEDCTRL_H_
diff --git a/wmediumd/inc/usfstl/uds.h b/wmediumd/inc/usfstl/uds.h
new file mode 100644
index 0000000..622e122
--- /dev/null
+++ b/wmediumd/inc/usfstl/uds.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+/*
+ * This defines a simple unix domain socket listen abstraction.
+ */
+#ifndef _USFSTL_UDS_H_
+#define _USFSTL_UDS_H_
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include "list.h"
+
+void usfstl_uds_create(const char *path, void (*connected)(int, void *),
+		       void *data);
+void usfstl_uds_remove(const char *path);
+
+int usfstl_uds_connect(const char *path, void (*readable)(int, void *),
+		       void *data);
+void usfstl_uds_disconnect(int fd);
+
+#endif // _USFSTL_UDS_H_
diff --git a/wmediumd/inc/usfstl/vhost.h b/wmediumd/inc/usfstl/vhost.h
new file mode 100644
index 0000000..f07a33d
--- /dev/null
+++ b/wmediumd/inc/usfstl/vhost.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_VHOST_H_
+#define _USFSTL_VHOST_H_
+
+#include "list.h"
+#include "sched.h"
+#include "schedctrl.h"
+#include "vhostproto.h"
+
+struct usfstl_vhost_user_buf {
+	unsigned int n_in_sg, n_out_sg;
+	struct iovec *in_sg, *out_sg;
+	size_t written;
+	unsigned int idx;
+	bool allocated;
+};
+
+struct usfstl_vhost_user_dev {
+	uint64_t features, protocol_features;
+	struct usfstl_vhost_user_server *server;
+	void *data;
+};
+
+struct usfstl_vhost_user_ops {
+	void (*connected)(struct usfstl_vhost_user_dev *dev);
+	void (*handle)(struct usfstl_vhost_user_dev *dev,
+		       struct usfstl_vhost_user_buf *buf,
+		       unsigned int vring);
+	void (*disconnected)(struct usfstl_vhost_user_dev *dev);
+};
+
+/**
+ * struct usfstl_vhost_user_server: vhost-user device server
+ */
+struct usfstl_vhost_user_server {
+	/**
+	 * @ops: various ops for the server/devices
+	 */
+	const struct usfstl_vhost_user_ops *ops;
+
+	/**
+	 * @name: socket name to use
+	 */
+	char *socket;
+
+	/**
+	 * @interrupt_latency: interrupt latency to model, in
+	 *	scheduler ticks (actual time then depends on
+	 *	the scheduler unit)
+	 */
+	unsigned int interrupt_latency;
+
+	/**
+	 * @max_queues: max number of virt queues supported
+	 */
+	unsigned int max_queues;
+
+	/**
+	 * @input_queues: bitmap of input queues (where to handle interrupts)
+	 */
+	uint64_t input_queues;
+
+	/**
+	 * @scheduler: the scheduler to integrate with,
+	 *	may be %NULL
+	 */
+	struct usfstl_scheduler *scheduler;
+
+	/**
+	 * @ctrl: external scheduler control to integrate with,
+	 *	may be %NULL
+	 */
+	struct usfstl_sched_ctrl *ctrl;
+
+	/**
+	 * @features: user features
+	 */
+	uint64_t features;
+
+	/**
+	 * @protocol_features: protocol features
+	 */
+	uint64_t protocol_features;
+
+	/**
+	 * @config: config data, if supported
+	 */
+	const void *config;
+
+	/**
+	 * @config_len: length of config data
+	 */
+	size_t config_len;
+
+	/**
+	 * @data: arbitrary user data
+	 */
+	void *data;
+};
+
+/**
+ * usfstl_vhost_user_server_start - start the server
+ */
+void usfstl_vhost_user_server_start(struct usfstl_vhost_user_server *server);
+
+/**
+ * usfstl_vhost_user_server_stop - stop the server
+ *
+ * Note that this doesn't stop the existing devices, and it thus
+ * may be used from the connected callback, if only one connection
+ * is allowed.
+ */
+void usfstl_vhost_user_server_stop(struct usfstl_vhost_user_server *server);
+
+/**
+ * usfstl_vhost_user_dev_notify - send a message on a vring
+ * @dev: device to send to
+ * @vring: vring index to send on
+ * @buf: buffer to send
+ * @buflen: length of the buffer
+ */
+void usfstl_vhost_user_dev_notify(struct usfstl_vhost_user_dev *dev,
+				  unsigned int vring,
+				  const uint8_t *buf, size_t buflen);
+
+/**
+ * usfstl_vhost_user_config_changed - notify host of a config change event
+ * @dev: device to send to
+ */
+void usfstl_vhost_user_config_changed(struct usfstl_vhost_user_dev *dev);
+
+/**
+ * usfstl_vhost_user_to_va - translate address
+ * @dev: device to translate address for
+ * @addr: guest-side virtual addr
+ */
+void *usfstl_vhost_user_to_va(struct usfstl_vhost_user_dev *dev, uint64_t addr);
+
+/* also some IOV helpers */
+size_t iov_len(struct iovec *sg, unsigned int nsg);
+size_t iov_fill(struct iovec *sg, unsigned int nsg,
+		const void *buf, size_t buflen);
+size_t iov_read(void *buf, size_t buflen,
+		struct iovec *sg, unsigned int nsg);
+
+#endif // _USFSTL_VHOST_H_
diff --git a/wmediumd/inc/usfstl/vhostproto.h b/wmediumd/inc/usfstl/vhostproto.h
new file mode 100644
index 0000000..8e7a76e
--- /dev/null
+++ b/wmediumd/inc/usfstl/vhostproto.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_VHOST_PROTO_H_
+#define _USFSTL_VHOST_PROTO_H_
+
+#define MAX_REGIONS 2
+
+/* these are from the vhost-user spec */
+
+struct vhost_user_msg_hdr {
+	uint32_t request;
+
+#define VHOST_USER_MSG_FLAGS_VERSION	0x3
+#define VHOST_USER_VERSION		  1
+#define VHOST_USER_MSG_FLAGS_REPLY	0x4
+#define VHOST_USER_MSG_FLAGS_NEED_REPLY	0x8
+	uint32_t flags;
+
+	uint32_t size;
+};
+
+struct vhost_user_region {
+	uint64_t guest_phys_addr;
+	uint64_t size;
+	uint64_t user_addr;
+	uint64_t mmap_offset;
+};
+
+struct vhost_user_msg {
+	struct vhost_user_msg_hdr hdr;
+	union {
+#define VHOST_USER_U64_VRING_IDX_MSK	0x7f
+#define VHOST_USER_U64_NO_FD		0x80
+		uint64_t u64;
+		struct {
+			uint32_t idx, num;
+		} vring_state;
+		struct {
+			uint32_t idx, flags;
+			uint64_t descriptor;
+			uint64_t used;
+			uint64_t avail;
+			uint64_t log;
+		} vring_addr;
+		struct {
+			uint32_t n_regions;
+			uint32_t reserved;
+			struct vhost_user_region regions[MAX_REGIONS];
+		} mem_regions;
+		struct {
+			uint32_t offset;
+			uint32_t size;
+#define VHOST_USER_CFG_SPACE_WRITABLE	0x1
+#define VHOST_USER_CFG_SPACE_MIGRATION	0x2
+			uint32_t flags;
+			uint8_t payload[0];
+		} cfg_space;
+		struct {
+			uint64_t idx_flags;
+			uint64_t size;
+			uint64_t offset;
+		} vring_area;
+	} __attribute__((packed)) payload;
+};
+
+#define VHOST_USER_GET_FEATURES			 1
+#define VHOST_USER_SET_FEATURES			 2
+#define VHOST_USER_SET_OWNER			 3
+#define VHOST_USER_SET_MEM_TABLE		 5
+#define VHOST_USER_SET_VRING_NUM		 8
+#define VHOST_USER_SET_VRING_ADDR		 9
+#define VHOST_USER_SET_VRING_BASE		10
+#define VHOST_USER_SET_VRING_KICK		12
+#define VHOST_USER_SET_VRING_CALL		13
+#define VHOST_USER_GET_PROTOCOL_FEATURES	15
+#define VHOST_USER_SET_VRING_ENABLE		18
+#define VHOST_USER_SET_PROTOCOL_FEATURES	16
+#define VHOST_USER_SET_SLAVE_REQ_FD		21
+#define VHOST_USER_GET_CONFIG			24
+#define VHOST_USER_VRING_KICK			35
+
+#define VHOST_USER_SLAVE_CONFIG_CHANGE_MSG	 2
+#define VHOST_USER_SLAVE_VRING_CALL		 4
+
+#define VHOST_USER_F_PROTOCOL_FEATURES 30
+
+#define VHOST_USER_PROTOCOL_F_MQ                    0
+#define VHOST_USER_PROTOCOL_F_LOG_SHMFD             1
+#define VHOST_USER_PROTOCOL_F_RARP                  2
+#define VHOST_USER_PROTOCOL_F_REPLY_ACK             3
+#define VHOST_USER_PROTOCOL_F_MTU                   4
+#define VHOST_USER_PROTOCOL_F_SLAVE_REQ             5
+#define VHOST_USER_PROTOCOL_F_CROSS_ENDIAN          6
+#define VHOST_USER_PROTOCOL_F_CRYPTO_SESSION        7
+#define VHOST_USER_PROTOCOL_F_PAGEFAULT             8
+#define VHOST_USER_PROTOCOL_F_CONFIG                9
+#define VHOST_USER_PROTOCOL_F_SLAVE_SEND_FD        10
+#define VHOST_USER_PROTOCOL_F_H_OST_NOTIFIER        11
+#define VHOST_USER_PROTOCOL_F_INFLIGHT_SHMFD       12
+#define VHOST_USER_PROTOCOL_F_RESET_DEVICE         13
+#define VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS 14
+
+#endif // _USFSTL_VHOST_PROTO_H_
diff --git a/wmediumd/lib/internal.h b/wmediumd/lib/internal.h
new file mode 100644
index 0000000..709a1c4
--- /dev/null
+++ b/wmediumd/lib/internal.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#ifndef _USFSTL_INTERNAL_H_
+#define _USFSTL_INTERNAL_H_
+#include <stdarg.h>
+#include <stdbool.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <usfstl/sched.h>
+
+/* byteswap helper */
+#define __swap32(v)			\
+	((((v) & 0xff000000) >> 24) |	\
+	 (((v) & 0x00ff0000) >>  8) |	\
+	 (((v) & 0x0000ff00) <<  8) |	\
+	 (((v) & 0x000000ff) << 24))
+
+static inline uint32_t swap32(uint32_t v)
+{
+	return __swap32(v);
+}
+
+#define DIV_ROUND_UP(a, b) ({	\
+	typeof(a) _a = a;	\
+	typeof(b) _b = b;	\
+	(_a + _b - 1) / _b;	\
+})
+
+/* scheduler */
+void _usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time);
+
+/* main loop */
+extern struct usfstl_list g_usfstl_loop_entries;
+
+#endif // _USFSTL_INTERNAL_H_
diff --git a/wmediumd/lib/loop.c b/wmediumd/lib/loop.c
new file mode 100644
index 0000000..e2b28ef
--- /dev/null
+++ b/wmediumd/lib/loop.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <usfstl/loop.h>
+#include <usfstl/list.h>
+#include <assert.h>
+#ifdef _WIN32
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600
+#include <winsock2.h>
+#include <windows.h>
+#else
+#include <sys/select.h>
+#endif
+
+struct usfstl_list g_usfstl_loop_entries =
+	USFSTL_LIST_INIT(g_usfstl_loop_entries);
+void (*g_usfstl_loop_pre_handler_fn)(void *);
+void *g_usfstl_loop_pre_handler_fn_data;
+
+
+void usfstl_loop_register(struct usfstl_loop_entry *entry)
+{
+	struct usfstl_loop_entry *tmp;
+
+	usfstl_loop_for_each_entry(tmp) {
+		if (entry->priority >= tmp->priority) {
+			usfstl_list_insert_before(&tmp->list, &entry->list);
+			return;
+		}
+	}
+
+	usfstl_list_append(&g_usfstl_loop_entries, &entry->list);
+}
+
+void usfstl_loop_unregister(struct usfstl_loop_entry *entry)
+{
+	usfstl_list_item_remove(&entry->list);
+}
+
+void usfstl_loop_wait_and_handle(void)
+{
+	while (true) {
+		struct usfstl_loop_entry *tmp;
+		fd_set rd_set, exc_set;
+		unsigned int max = 0, num;
+
+		FD_ZERO(&rd_set);
+		FD_ZERO(&exc_set);
+
+		usfstl_loop_for_each_entry(tmp) {
+			FD_SET(tmp->fd, &rd_set);
+			FD_SET(tmp->fd, &exc_set);
+			if ((unsigned int)tmp->fd > max)
+				max = tmp->fd;
+		}
+
+		num = select(max + 1, &rd_set, NULL, &exc_set, NULL);
+		assert(num > 0);
+
+		usfstl_loop_for_each_entry(tmp) {
+			void *data = g_usfstl_loop_pre_handler_fn_data;
+
+			if (!FD_ISSET(tmp->fd, &rd_set) &&
+			    !FD_ISSET(tmp->fd, &exc_set))
+				continue;
+
+			if (g_usfstl_loop_pre_handler_fn)
+				g_usfstl_loop_pre_handler_fn(data);
+			tmp->handler(tmp);
+			return;
+		}
+	}
+}
diff --git a/wmediumd/lib/sched.c b/wmediumd/lib/sched.c
new file mode 100644
index 0000000..7d0618b
--- /dev/null
+++ b/wmediumd/lib/sched.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <usfstl/assert.h>
+#include <usfstl/sched.h>
+#include <usfstl/list.h>
+#include "internal.h"
+
+uint64_t usfstl_sched_current_time(struct usfstl_scheduler *sched)
+{
+	uint64_t current_time;
+
+	if (!sched->external_sync_from || !sched->waiting)
+		return sched->current_time;
+
+	current_time = sched->external_sync_from(sched);
+
+	/* update current time after sync */
+	usfstl_sched_set_time(sched, current_time);
+
+	return current_time;
+}
+
+static bool usfstl_sched_external_request(struct usfstl_scheduler *sched,
+					  uint64_t time)
+{
+	if (!sched->external_request)
+		return false;
+
+	/*
+	 * If we received a next_external_sync point, we don't need to ask for
+	 * runtime for anything earlier than that point, we're allowed to run.
+	 * However, note that this only applies if we're not currently waiting,
+	 * if we are in fact waiting for permission, then of course we need to
+	 * ask for any earlier time, regardless of the next_external_sync, as
+	 * we won't schedule until we get called to run, and that usually won't
+	 * happen if we don't ask for it.
+	 */
+	if (!sched->waiting && sched->next_external_sync_set &&
+	    usfstl_time_cmp(time, <, sched->next_external_sync))
+		return false;
+
+	/* If we asked for this time slot already, don't ask again but wait. */
+	if (sched->prev_external_sync_set && time == sched->prev_external_sync)
+		return true;
+
+	sched->prev_external_sync = time;
+	sched->prev_external_sync_set = 1;
+	sched->external_request(sched, time);
+
+	return true;
+}
+
+static void usfstl_sched_external_wait(struct usfstl_scheduler *sched)
+{
+	/*
+	 * Once we wait for the external scheduler, we have to ask again
+	 * even if for some reason we end up asking for the same time.
+	 */
+	sched->prev_external_sync_set = 0;
+	sched->waiting = 1;
+	sched->external_wait(sched);
+	sched->waiting = 0;
+}
+
+void usfstl_sched_add_job(struct usfstl_scheduler *sched, struct usfstl_job *job)
+{
+	struct usfstl_job *tmp;
+
+	USFSTL_ASSERT_TIME_CMP(job->start, >=, sched->current_time);
+	USFSTL_ASSERT(!usfstl_job_scheduled(job),
+		      "cannot add a job that's already scheduled");
+	USFSTL_ASSERT_CMP(job->group, <, 32, "%u");
+
+	if ((1 << job->group) & sched->blocked_groups &&
+	    job != sched->allowed_job) {
+		job->start = 0;
+		usfstl_list_append(&sched->pending_jobs, &job->entry);
+		return;
+	}
+
+	usfstl_for_each_list_item(tmp, &sched->joblist, entry) {
+		if (usfstl_time_cmp(tmp->start, >, job->start))
+			break;
+		if (tmp->start == job->start &&
+		    tmp->priority < job->priority)
+			break;
+	}
+
+	/* insert after previous entry */
+	if (!tmp)
+		usfstl_list_append(&sched->joblist, &job->entry);
+	else
+		usfstl_list_insert_before(&tmp->entry, &job->entry);
+
+	/*
+	 * Request the new job's runtime from the external scheduler
+	 * (if configured); if this job doesn't request any earlier
+	 * runtime than previously requested, this does nothing. It
+	 * may, however, request earlier runtime if this is due to
+	 * an interrupt we got from outside while waiting for the
+	 * external scheduler.
+	 */
+	usfstl_sched_external_request(sched, job->start);
+
+	if (sched->next_time_changed)
+		sched->next_time_changed(sched);
+}
+
+bool usfstl_job_scheduled(struct usfstl_job *job)
+{
+	return job->entry.next != NULL;
+}
+
+void usfstl_sched_del_job(struct usfstl_job *job)
+{
+	if (!usfstl_job_scheduled(job))
+		return;
+
+	usfstl_list_item_remove(&job->entry);
+}
+
+void _usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time)
+{
+	uint64_t delta;
+
+	if (sched->current_time == time)
+		return;
+
+	// check that we at least don't move backwards
+	USFSTL_ASSERT_TIME_CMP(time, >=, sched->current_time);
+
+	delta = time - sched->current_time;
+	sched->current_time = time;
+
+	if (sched->time_advanced)
+		sched->time_advanced(sched, delta);
+}
+
+void usfstl_sched_set_time(struct usfstl_scheduler *sched, uint64_t time)
+{
+	/*
+	 * also check that we're not getting set to something later than what
+	 * we requested, that'd be a bug since we want to run something at an
+	 * earlier time than what we just got set to; unless we have nothing
+	 * to do and thus don't care at all.
+	 */
+	USFSTL_ASSERT(usfstl_list_empty(&sched->joblist) ||
+		      usfstl_time_cmp(time, <=, sched->prev_external_sync),
+		      "scheduler time moves further (to %" PRIu64 ") than requested (%" PRIu64 ")",
+		      time, sched->prev_external_sync);
+
+	_usfstl_sched_set_time(sched, time);
+}
+
+static void usfstl_sched_forward(struct usfstl_scheduler *sched, uint64_t until)
+{
+	USFSTL_ASSERT_TIME_CMP(until, >=, sched->current_time);
+
+	if (usfstl_sched_external_request(sched, until)) {
+		usfstl_sched_external_wait(sched);
+		/*
+		 * The external_wait() method must call
+		 * usfstl_sched_set_time() before returning,
+		 * so we don't in this case.
+		 */
+		return;
+	}
+
+	_usfstl_sched_set_time(sched, until);
+}
+
+void usfstl_sched_start(struct usfstl_scheduler *sched)
+{
+	if (usfstl_sched_external_request(sched, sched->current_time))
+		usfstl_sched_external_wait(sched);
+}
+
+struct usfstl_job *usfstl_sched_next(struct usfstl_scheduler *sched)
+{
+	while (true) {
+		struct usfstl_job *job = usfstl_sched_next_pending(sched, NULL);
+
+		if (!job) {
+			/*
+			 * If external scheduler is active, we might get here
+			 * with nothing to do, so we just need to wait for an
+			 * external input/job which will add a job to our
+			 * scheduler.
+			 *
+			 * Due to the fact that we don't have any API for
+			 * cancelling external time requests, we might have
+			 * requested time from the external scheduler for a
+			 * job that subsequently got removed, ending up here
+			 * without a job, or one further in the future which
+			 * would cause usfstl_sched_forward() to wait again.
+			 *
+			 * Additionally, we might only remove the job we just
+			 * found during the usfstl_sched_forward() below, if
+			 * that causes the main loop to run and we detect an
+			 * event that causes a job removal (such as a client
+			 * disconnecting from a server), so the job pointer we
+			 * have might go stale. Hence, all of this needs to be
+			 * checked in the overall loop.
+			 */
+			if (sched->external_request) {
+				usfstl_sched_external_wait(sched);
+				continue;
+			}
+			break;
+		}
+
+		/*
+		 * Forward, but only if job isn't in the past - this
+		 * can happen if some job was inserted while we
+		 * were in fact waiting for the external scheduler, i.e.
+		 * some sort of external job happened while we thought
+		 * there was nothing to do.
+		 */
+		if (usfstl_time_cmp(job->start, >, sched->current_time))
+			usfstl_sched_forward(sched, job->start);
+
+		/*
+		 * Some sort of external job might have come to us (while
+		 * we were stuck waiting for the external scheduler), and
+		 * might have inserted an earlier job into the timeline.
+		 * If it's not this job's turn yet, reinsert it and check
+		 * what's up next in the next loop iteration.
+		 *
+		 * Also, 'job' might now have been removed, see above.
+		 */
+		if (usfstl_sched_next_pending(sched, NULL) != job)
+			continue;
+
+		/*
+		 * Otherwise we've actually reached this job, so remove
+		 * and call it.
+		 */
+		usfstl_sched_del_job(job);
+		job->callback(job);
+		return job;
+	}
+
+	/*
+	 * We must not get here, if there's no job whatsoever the
+	 * simulation has basically ended in an undefined state, even
+	 * the main thread can no longer make progress.
+	 */
+	USFSTL_ASSERT(0, "scheduling while there's nothing to do");
+}
+
+void usfstl_sched_set_sync_time(struct usfstl_scheduler *sched, uint64_t time)
+{
+	USFSTL_ASSERT_TIME_CMP(time, >=, sched->current_time);
+	sched->next_external_sync = time;
+	sched->next_external_sync_set = 1;
+}
+
+static void usfstl_sched_block_job(struct usfstl_scheduler *sched,
+				 struct usfstl_job *job)
+{
+	usfstl_sched_del_job(job);
+	usfstl_list_append(&sched->pending_jobs, &job->entry);
+}
+
+struct usfstl_job *usfstl_sched_next_pending(struct usfstl_scheduler *sched,
+					     struct usfstl_job *job)
+{
+	return job ? usfstl_next_item(&sched->joblist, job, struct usfstl_job, entry) :
+		     usfstl_list_first_item(&sched->joblist, struct usfstl_job, entry);
+}
+
+static void usfstl_sched_remove_blocked_jobs(struct usfstl_scheduler *sched)
+{
+	struct usfstl_job *job = NULL, *next;
+
+	usfstl_for_each_list_item_continue_safe(job, next, &sched->joblist,
+					      entry) {
+		if (job == sched->allowed_job)
+			continue;
+		if ((1 << job->group) & sched->blocked_groups)
+			usfstl_sched_block_job(sched, job);
+	}
+}
+
+static void usfstl_sched_restore_job(struct usfstl_scheduler *sched,
+				     struct usfstl_job *job)
+{
+	usfstl_sched_del_job(job);
+	if (usfstl_time_cmp(job->start, <, sched->current_time))
+		job->start = sched->current_time;
+	usfstl_sched_add_job(sched, job);
+}
+
+static void usfstl_sched_restore_blocked_jobs(struct usfstl_scheduler *sched)
+{
+	struct usfstl_job *job = NULL, *next;
+
+	usfstl_for_each_list_item_continue_safe(job, next, &sched->pending_jobs,
+					      entry) {
+		if (job == sched->allowed_job ||
+		    !((1 << job->group) & sched->blocked_groups))
+			usfstl_sched_restore_job(sched, job);
+	}
+}
+
+void usfstl_sched_block_groups(struct usfstl_scheduler *sched, uint32_t groups,
+			       struct usfstl_job *job,
+			       struct usfstl_sched_block_data *save)
+{
+	save->groups = sched->blocked_groups;
+	save->job = sched->allowed_job;
+
+	// it makes no sense to allow a job unless its group is blocked
+	USFSTL_ASSERT(!job || (1 << job->group) & groups,
+		    "allowed job group %d must be part of blocked groups (0x%x\n)",
+		    job->group, groups);
+
+	sched->blocked_groups |= groups;
+	sched->allowed_job = job;
+
+	usfstl_sched_remove_blocked_jobs(sched);
+}
+
+void usfstl_sched_restore_groups(struct usfstl_scheduler *sched,
+				 struct usfstl_sched_block_data *restore)
+{
+	sched->blocked_groups = restore->groups;
+	sched->allowed_job = restore->job;
+
+	usfstl_sched_restore_blocked_jobs(sched);
+	usfstl_sched_remove_blocked_jobs(sched);
+}
+
+static void usfstl_sched_link_job_callback(struct usfstl_job *job)
+{
+	struct usfstl_scheduler *sched = job->data;
+
+	sched->link.waiting = false;
+}
+
+static uint64_t usfstl_sched_link_external_sync_from(struct usfstl_scheduler *sched)
+{
+	uint64_t parent_time;
+
+	parent_time = usfstl_sched_current_time(sched->link.parent);
+
+	return DIV_ROUND_UP(parent_time - sched->link.offset,
+			    sched->link.tick_ratio);
+}
+
+static void usfstl_sched_link_external_wait(struct usfstl_scheduler *sched)
+{
+	sched->link.waiting = true;
+
+	while (sched->link.waiting)
+		usfstl_sched_next(sched->link.parent);
+
+	usfstl_sched_set_time(sched, usfstl_sched_current_time(sched));
+}
+
+static void usfstl_sched_link_external_request(struct usfstl_scheduler *sched,
+					       uint64_t time)
+{
+	uint64_t parent_time;
+	struct usfstl_job *job = &sched->link.job;
+
+	parent_time = sched->link.tick_ratio * time + sched->link.offset;
+
+	usfstl_sched_del_job(job);
+	job->start = parent_time;
+	usfstl_sched_add_job(sched->link.parent, job);
+}
+
+void usfstl_sched_link(struct usfstl_scheduler *sched,
+		       struct usfstl_scheduler *parent,
+		       uint32_t tick_ratio)
+{
+	struct usfstl_job *job;
+
+	USFSTL_ASSERT(tick_ratio, "a ratio must be set");
+	USFSTL_ASSERT(!sched->link.parent, "must not be linked");
+
+	USFSTL_ASSERT_EQ(sched->external_request, NULL, "%p");
+	sched->external_request = usfstl_sched_link_external_request;
+
+	USFSTL_ASSERT_EQ(sched->external_wait, NULL, "%p");
+	sched->external_wait = usfstl_sched_link_external_wait;
+
+	USFSTL_ASSERT_EQ(sched->external_sync_from, NULL, "%p");
+	sched->external_sync_from = usfstl_sched_link_external_sync_from;
+
+	sched->link.tick_ratio = tick_ratio;
+	sched->link.parent = parent;
+
+	sched->link.job.callback = usfstl_sched_link_job_callback;
+	sched->link.job.data = sched;
+
+	/* current_time = (parent_time - offset) / tick_ratio */
+	sched->link.offset = sched->link.parent->current_time -
+		sched->current_time * sched->link.tick_ratio;
+
+	/* if we have a job already, request to run it */
+	job = usfstl_sched_next_pending(sched, NULL);
+	if (job)
+		usfstl_sched_external_request(sched, job->start);
+}
+
+void usfstl_sched_unlink(struct usfstl_scheduler *sched)
+{
+	USFSTL_ASSERT(sched->link.parent, "must be linked");
+
+	sched->external_sync_from = NULL;
+	sched->external_wait = NULL;
+	sched->external_request = NULL;
+
+	usfstl_sched_del_job(&sched->link.job);
+	memset(&sched->link, 0, sizeof(sched->link));
+}
diff --git a/wmediumd/lib/schedctrl.c b/wmediumd/lib/schedctrl.c
new file mode 100644
index 0000000..bcd5f89
--- /dev/null
+++ b/wmediumd/lib/schedctrl.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <usfstl/uds.h>
+#include <usfstl/schedctrl.h>
+#include <linux/um_timetravel.h>
+#include "internal.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+static void _usfstl_sched_ctrl_send_msg(struct usfstl_sched_ctrl *ctrl,
+					enum um_timetravel_ops op,
+					uint64_t time, uint32_t seq)
+{
+	struct um_timetravel_msg msg = {
+		.op = op,
+		.seq = seq,
+		.time = time,
+	};
+
+	USFSTL_ASSERT_EQ((int)write(ctrl->fd, &msg, sizeof(msg)),
+			 (int)sizeof(msg), "%d");
+}
+
+static void usfstl_sched_ctrl_sock_read(int fd, void *data)
+{
+	struct usfstl_sched_ctrl *ctrl = data;
+	struct um_timetravel_msg msg;
+	int sz = read(fd, &msg, sizeof(msg));
+	uint64_t time;
+
+	USFSTL_ASSERT_EQ(sz, (int)sizeof(msg), "%d");
+
+	switch (msg.op) {
+	case UM_TIMETRAVEL_ACK:
+		if (msg.seq == ctrl->expected_ack_seq) {
+			ctrl->acked = 1;
+			ctrl->ack_time = msg.time;
+		}
+		return;
+	case UM_TIMETRAVEL_RUN:
+		time = DIV_ROUND_UP(msg.time - ctrl->offset,
+				    ctrl->nsec_per_tick);
+		usfstl_sched_set_time(ctrl->sched, time);
+		ctrl->waiting = 0;
+		break;
+	case UM_TIMETRAVEL_FREE_UNTIL:
+		/* round down here, so we don't overshoot */
+		time = (msg.time - ctrl->offset) / ctrl->nsec_per_tick;
+		usfstl_sched_set_sync_time(ctrl->sched, time);
+		break;
+	case UM_TIMETRAVEL_START:
+	case UM_TIMETRAVEL_REQUEST:
+	case UM_TIMETRAVEL_WAIT:
+	case UM_TIMETRAVEL_GET:
+	case UM_TIMETRAVEL_UPDATE:
+	case UM_TIMETRAVEL_GET_TOD:
+		USFSTL_ASSERT(0);
+		return;
+	}
+
+	_usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_ACK, 0, msg.seq);
+}
+
+static void usfstl_sched_ctrl_send_msg(struct usfstl_sched_ctrl *ctrl,
+				       enum um_timetravel_ops op,
+				       uint64_t time)
+{
+	static uint32_t seq, old_expected;
+
+	do {
+		seq++;
+	} while (seq == 0);
+
+	_usfstl_sched_ctrl_send_msg(ctrl, op, time, seq);
+	old_expected = ctrl->expected_ack_seq;
+	ctrl->expected_ack_seq = seq;
+
+	USFSTL_ASSERT_EQ((int)ctrl->acked, 0, "%d");
+
+	/*
+	 * Race alert!
+	 *
+	 * UM_TIMETRAVEL_WAIT basically passes the run "token" to the
+	 * controller, which passes it to another participant of the
+	 * simulation. This other participant might immediately send
+	 * us another message on a different channel, e.g. if this
+	 * code is used in a vhost-user device.
+	 *
+	 * If here we were to use use usfstl_loop_wait_and_handle(),
+	 * we could actually get and process the vhost-user message
+	 * before the ACK for the WAIT message here, depending on the
+	 * (host) kernel's message ordering and select() handling etc.
+	 *
+	 * To avoid this, directly read the ACK message for the WAIT,
+	 * without handling any other sockets (first).
+	 */
+	if (op == UM_TIMETRAVEL_WAIT) {
+		usfstl_sched_ctrl_sock_read(ctrl->fd, ctrl);
+		USFSTL_ASSERT(ctrl->acked);
+	}
+
+	while (!ctrl->acked)
+		usfstl_loop_wait_and_handle();
+	ctrl->acked = 0;
+	ctrl->expected_ack_seq = old_expected;
+
+	if (op == UM_TIMETRAVEL_GET) {
+		if (ctrl->frozen) {
+			uint64_t local;
+
+			local = ctrl->sched->current_time * ctrl->nsec_per_tick;
+			ctrl->offset = ctrl->ack_time - local;
+		} else {
+			uint64_t time;
+
+			time = DIV_ROUND_UP(ctrl->ack_time - ctrl->offset,
+					    ctrl->nsec_per_tick);
+			usfstl_sched_set_time(ctrl->sched, time);
+		}
+	}
+}
+
+static void usfstl_sched_ctrl_request(struct usfstl_scheduler *sched, uint64_t time)
+{
+	struct usfstl_sched_ctrl *ctrl = sched->ext.ctrl;
+
+	if (!ctrl->started)
+		return;
+
+	usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_REQUEST,
+				   time * ctrl->nsec_per_tick + ctrl->offset);
+}
+
+static void usfstl_sched_ctrl_wait(struct usfstl_scheduler *sched)
+{
+	struct usfstl_sched_ctrl *ctrl = sched->ext.ctrl;
+
+	ctrl->waiting = 1;
+	usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_WAIT, -1);
+
+	while (ctrl->waiting)
+		usfstl_loop_wait_and_handle();
+}
+
+#define JOB_ASSERT_VAL(j) (j) ? (j)->name : "<NULL>"
+
+void usfstl_sched_ctrl_start(struct usfstl_sched_ctrl *ctrl,
+			     const char *socket,
+			     uint32_t nsec_per_tick,
+			     uint64_t client_id,
+			     struct usfstl_scheduler *sched)
+{
+	struct usfstl_job *job;
+
+	USFSTL_ASSERT_EQ(ctrl->sched, NULL, "%p");
+	USFSTL_ASSERT_EQ(sched->ext.ctrl, NULL, "%p");
+
+	memset(ctrl, 0, sizeof(*ctrl));
+
+	/*
+	 * The remote side assumes we start at 0, so if we don't have 0 right
+	 * now keep the difference in our own offset (in nsec).
+	 */
+	ctrl->offset = -sched->current_time * nsec_per_tick;
+
+	ctrl->nsec_per_tick = nsec_per_tick;
+	ctrl->sched = sched;
+	sched->ext.ctrl = ctrl;
+
+	USFSTL_ASSERT_EQ(usfstl_sched_next_pending(sched, NULL),
+			 (struct usfstl_job *)NULL, "%s", JOB_ASSERT_VAL);
+	USFSTL_ASSERT_EQ(sched->external_request, NULL, "%p");
+	USFSTL_ASSERT_EQ(sched->external_wait, NULL, "%p");
+
+	sched->external_request = usfstl_sched_ctrl_request;
+	sched->external_wait = usfstl_sched_ctrl_wait;
+
+	ctrl->fd = usfstl_uds_connect(socket, usfstl_sched_ctrl_sock_read,
+				      ctrl);
+
+	/* tell the other side we're starting  */
+	usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_START, client_id);
+	ctrl->started = 1;
+
+	/* if we have a job already, request it */
+	job = usfstl_sched_next_pending(sched, NULL);
+	if (job)
+		usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_REQUEST,
+					   job->start * nsec_per_tick);
+
+	/*
+	 * At this point, we're allowed to do further setup work and can
+	 * request schedule time etc. but must eventually start scheduling
+	 * the linked scheduler - the remote side is blocked until we do.
+	 */
+}
+
+void usfstl_sched_ctrl_sync_to(struct usfstl_sched_ctrl *ctrl)
+{
+	uint64_t time;
+
+	USFSTL_ASSERT(ctrl->started, "cannot sync to scheduler until started");
+
+	time = usfstl_sched_current_time(ctrl->sched) * ctrl->nsec_per_tick;
+	time += ctrl->offset;
+
+	usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_UPDATE, time);
+}
+
+void usfstl_sched_ctrl_sync_from(struct usfstl_sched_ctrl *ctrl)
+{
+	if (!ctrl->started)
+		return;
+	usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_GET, -1);
+}
+
+void usfstl_sched_ctrl_stop(struct usfstl_sched_ctrl *ctrl)
+{
+	USFSTL_ASSERT_EQ(ctrl, ctrl->sched->ext.ctrl, "%p");
+	usfstl_sched_ctrl_send_msg(ctrl, UM_TIMETRAVEL_WAIT, -1);
+	usfstl_uds_disconnect(ctrl->fd);
+	ctrl->sched->ext.ctrl = NULL;
+	ctrl->sched->external_request = NULL;
+	ctrl->sched->external_wait = NULL;
+	ctrl->sched = NULL;
+}
+
+void usfstl_sched_ctrl_set_frozen(struct usfstl_sched_ctrl *ctrl, bool frozen)
+{
+	ctrl->frozen = frozen;
+}
diff --git a/wmediumd/lib/uds.c b/wmediumd/lib/uds.c
new file mode 100644
index 0000000..fc0fe2a
--- /dev/null
+++ b/wmediumd/lib/uds.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <usfstl/assert.h>
+#include <usfstl/loop.h>
+#include <usfstl/list.h>
+#include "internal.h"
+
+struct usfstl_uds_server {
+	struct usfstl_loop_entry entry;
+	void (*connected)(int fd, void *data);
+	void *data;
+	char name[];
+};
+
+void usfstl_uds_accept_handler(struct usfstl_loop_entry *entry)
+{
+	struct usfstl_uds_server *uds;
+	int fd;
+
+	uds = container_of(entry, struct usfstl_uds_server, entry);
+	fd = accept(uds->entry.fd, NULL, NULL);
+
+	uds->connected(fd, uds->data);
+}
+
+void usfstl_uds_create(const char *path, void (*connected)(int, void *),
+		       void *data)
+{
+	struct usfstl_uds_server *uds = malloc(sizeof(*uds) + strlen(path) + 1);
+	struct stat buf;
+	int ret = stat(path, &buf), fd;
+	struct sockaddr_un un = {
+		.sun_family = AF_UNIX,
+	};
+
+	USFSTL_ASSERT(uds);
+	strcpy(uds->name, path);
+	uds->data = data;
+	uds->connected = connected;
+
+	if (ret == 0) {
+		USFSTL_ASSERT_EQ((int)(buf.st_mode & S_IFMT), S_IFSOCK, "%d");
+		USFSTL_ASSERT_EQ(unlink(path), 0, "%d");
+	} else {
+		USFSTL_ASSERT_EQ(errno, ENOENT, "%d");
+	}
+
+	fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	uds->entry.fd = fd;
+
+	strcpy(un.sun_path, path);
+	USFSTL_ASSERT_EQ(bind(fd, (void *)&un, sizeof(un)), 0, "%d");
+
+	USFSTL_ASSERT_EQ(listen(fd, 1000), 0, "%d");
+
+	uds->entry.handler = usfstl_uds_accept_handler;
+
+	usfstl_loop_register(&uds->entry);
+}
+
+void usfstl_uds_remove(const char *path)
+{
+	struct usfstl_loop_entry *tmp;
+	struct usfstl_uds_server *uds, *found = NULL;
+
+	usfstl_loop_for_each_entry(tmp) {
+		if (tmp->handler != usfstl_uds_accept_handler)
+			continue;
+
+		uds = container_of(tmp, struct usfstl_uds_server, entry);
+		if (strcmp(uds->name, path) == 0) {
+			found = uds;
+			break;
+		}
+	}
+
+	USFSTL_ASSERT(found);
+
+	close(found->entry.fd);
+	usfstl_loop_unregister(&found->entry);
+	unlink(path);
+	free(found);
+}
+
+struct usfstl_uds_client {
+	struct usfstl_loop_entry entry;
+	void (*readable)(int fd, void *data);
+	void *data;
+};
+
+void usfstl_uds_readable_handler(struct usfstl_loop_entry *entry)
+{
+	struct usfstl_uds_client *uds;
+
+	uds = container_of(entry, struct usfstl_uds_client, entry);
+
+	uds->readable(uds->entry.fd, uds->data);
+}
+
+int usfstl_uds_connect(const char *path, void (*readable)(int, void *),
+		       void *data)
+{
+	struct usfstl_uds_client *client;
+	struct sockaddr_un sock;
+	int fd, err;
+
+	client = calloc(1, sizeof(*client));
+	USFSTL_ASSERT(client);
+	client->entry.handler = usfstl_uds_readable_handler;
+	client->readable = readable;
+	client->data = data;
+
+	sock.sun_family = AF_UNIX;
+	strcpy(sock.sun_path, path);
+
+	fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	USFSTL_ASSERT(fd >= 0);
+
+	err = connect(fd, (struct sockaddr *) &sock, sizeof(sock));
+	USFSTL_ASSERT(err == 0);
+
+	client->entry.fd = fd;
+	usfstl_loop_register(&client->entry);
+
+	return fd;
+}
+
+void usfstl_uds_disconnect(int fd)
+{
+	struct usfstl_loop_entry *tmp;
+	struct usfstl_uds_client *uds, *found = NULL;
+
+	usfstl_loop_for_each_entry(tmp) {
+		if (tmp->handler != usfstl_uds_readable_handler)
+			continue;
+
+		uds = container_of(tmp, struct usfstl_uds_client, entry);
+		if (uds->entry.fd == fd) {
+			found = uds;
+			break;
+		}
+	}
+
+	USFSTL_ASSERT(found);
+
+	close(fd);
+	usfstl_loop_unregister(&found->entry);
+	free(found);
+}
diff --git a/wmediumd/lib/vhost.c b/wmediumd/lib/vhost.c
new file mode 100644
index 0000000..9d46441
--- /dev/null
+++ b/wmediumd/lib/vhost.c
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stdlib.h>
+#include <usfstl/list.h>
+#include <usfstl/loop.h>
+#include <usfstl/uds.h>
+#include <sys/socket.h>
+#include <sys/mman.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <usfstl/vhost.h>
+#include <linux/virtio_ring.h>
+#include <linux/virtio_config.h>
+#include <endian.h>
+
+#define MAX_REGIONS 2
+#define SG_STACK_PREALLOC 5
+
+struct usfstl_vhost_user_dev_int {
+	struct usfstl_list fds;
+	struct usfstl_job irq_job;
+
+	struct usfstl_loop_entry entry;
+
+	struct usfstl_vhost_user_dev ext;
+
+	unsigned int n_regions;
+	struct vhost_user_region regions[MAX_REGIONS];
+	int region_fds[MAX_REGIONS];
+	void *region_vaddr[MAX_REGIONS];
+
+	int req_fd;
+
+	struct {
+		struct usfstl_loop_entry entry;
+		bool enabled;
+		bool triggered;
+		struct vring virtq;
+		int call_fd;
+		uint16_t last_avail_idx;
+	} virtqs[];
+};
+
+#define CONV(bits)							\
+static inline uint##bits##_t __attribute__((used))			\
+cpu_to_virtio##bits(struct usfstl_vhost_user_dev_int *dev,		\
+		    uint##bits##_t v)					\
+{									\
+	if (dev->ext.features & (1ULL << VIRTIO_F_VERSION_1))		\
+		return htole##bits(v);					\
+	return v;							\
+}									\
+static inline uint##bits##_t __attribute__((used))			\
+virtio_to_cpu##bits(struct usfstl_vhost_user_dev_int *dev,		\
+		    uint##bits##_t v)					\
+{									\
+	if (dev->ext.features & (1ULL << VIRTIO_F_VERSION_1))		\
+		return le##bits##toh(v);				\
+	return v;							\
+}
+
+CONV(16)
+CONV(32)
+CONV(64)
+
+static struct usfstl_vhost_user_buf *
+usfstl_vhost_user_get_virtq_buf(struct usfstl_vhost_user_dev_int *dev,
+				unsigned int virtq_idx,
+				struct usfstl_vhost_user_buf *fixed)
+{
+	struct usfstl_vhost_user_buf *buf = fixed;
+	struct vring *virtq = &dev->virtqs[virtq_idx].virtq;
+	uint16_t avail_idx = virtio_to_cpu16(dev, virtq->avail->idx);
+	uint16_t idx, desc_idx;
+	struct vring_desc *desc;
+	unsigned int n_in = 0, n_out = 0;
+	bool more;
+
+	if (avail_idx == dev->virtqs[virtq_idx].last_avail_idx)
+		return NULL;
+
+	/* ensure we read the descriptor after checking the index */
+	__sync_synchronize();
+
+	idx = dev->virtqs[virtq_idx].last_avail_idx++;
+	idx %= virtq->num;
+	desc_idx = virtio_to_cpu16(dev, virtq->avail->ring[idx]);
+	USFSTL_ASSERT(desc_idx < virtq->num);
+
+	desc = &virtq->desc[desc_idx];
+	do {
+		more = virtio_to_cpu16(dev, desc->flags) & VRING_DESC_F_NEXT;
+
+		if (virtio_to_cpu16(dev, desc->flags) & VRING_DESC_F_WRITE)
+			n_in++;
+		else
+			n_out++;
+		desc = &virtq->desc[virtio_to_cpu16(dev, desc->next)];
+	} while (more);
+
+	if (n_in > fixed->n_in_sg || n_out > fixed->n_out_sg) {
+		size_t sz = sizeof(*buf);
+		struct iovec *vec;
+
+		sz += (n_in + n_out) * sizeof(*vec);
+
+		buf = calloc(1, sz);
+		if (!buf)
+			return NULL;
+
+		vec = (void *)(buf + 1);
+		buf->in_sg = vec;
+		buf->out_sg = vec + n_in;
+		buf->allocated = true;
+	}
+
+	buf->n_in_sg = 0;
+	buf->n_out_sg = 0;
+	buf->idx = desc_idx;
+
+	desc = &virtq->desc[desc_idx];
+	do {
+		struct iovec *vec;
+		uint64_t addr;
+
+		more = virtio_to_cpu16(dev, desc->flags) & VRING_DESC_F_NEXT;
+
+		if (virtio_to_cpu16(dev, desc->flags) & VRING_DESC_F_WRITE) {
+			vec = &buf->in_sg[buf->n_in_sg];
+			buf->n_in_sg++;
+		} else {
+			vec = &buf->out_sg[buf->n_out_sg];
+			buf->n_out_sg++;
+		}
+
+		addr = virtio_to_cpu64(dev, desc->addr);
+		vec->iov_base = usfstl_vhost_user_to_va(&dev->ext, addr);
+		vec->iov_len = virtio_to_cpu32(dev, desc->len);
+
+		desc = &virtq->desc[virtio_to_cpu16(dev, desc->next)];
+	} while (more);
+
+	return buf;
+}
+
+static void usfstl_vhost_user_free_buf(struct usfstl_vhost_user_buf *buf)
+{
+	if (buf->allocated)
+		free(buf);
+}
+
+static void usfstl_vhost_user_readable_handler(struct usfstl_loop_entry *entry)
+{
+	usfstl_loop_unregister(entry);
+	entry->fd = -1;
+}
+
+static int usfstl_vhost_user_read_msg(int fd, struct msghdr *msghdr)
+{
+	struct iovec msg_iov;
+	struct msghdr hdr2 = {
+		.msg_iov = &msg_iov,
+		.msg_iovlen = 1,
+		.msg_control = msghdr->msg_control,
+		.msg_controllen = msghdr->msg_controllen,
+	};
+	struct vhost_user_msg_hdr *hdr;
+	size_t i;
+	size_t maxlen = 0;
+	ssize_t len;
+
+	USFSTL_ASSERT(msghdr->msg_iovlen >= 1);
+	USFSTL_ASSERT(msghdr->msg_iov[0].iov_len >= sizeof(*hdr));
+
+	hdr = msghdr->msg_iov[0].iov_base;
+	msg_iov.iov_base = hdr;
+	msg_iov.iov_len = sizeof(*hdr);
+
+	len = recvmsg(fd, &hdr2, 0);
+	if (len < 0)
+		return -errno;
+	if (len == 0)
+		return -ENOTCONN;
+
+	for (i = 0; i < msghdr->msg_iovlen; i++)
+		maxlen += msghdr->msg_iov[i].iov_len;
+	maxlen -= sizeof(*hdr);
+
+	USFSTL_ASSERT_EQ((int)len, (int)sizeof(*hdr), "%d");
+	USFSTL_ASSERT(hdr->size <= maxlen);
+
+	if (!hdr->size)
+		return 0;
+
+	msghdr->msg_control = NULL;
+	msghdr->msg_controllen = 0;
+	msghdr->msg_iov[0].iov_base += sizeof(*hdr);
+	msghdr->msg_iov[0].iov_len -= sizeof(*hdr);
+	len = recvmsg(fd, msghdr, 0);
+
+	/* restore just in case the user needs it */
+	msghdr->msg_iov[0].iov_base -= sizeof(*hdr);
+	msghdr->msg_iov[0].iov_len += sizeof(*hdr);
+	msghdr->msg_control = hdr2.msg_control;
+	msghdr->msg_controllen = hdr2.msg_controllen;
+
+	if (len < 0)
+		return -errno;
+	if (len == 0)
+		return -ENOTCONN;
+
+	USFSTL_ASSERT_EQ(hdr->size, (uint32_t)len, "%u");
+
+	return 0;
+}
+
+static void usfstl_vhost_user_send_msg(struct usfstl_vhost_user_dev_int *dev,
+				       struct vhost_user_msg *msg)
+{
+	size_t msgsz = sizeof(msg->hdr) + msg->hdr.size;
+	bool ack = dev->ext.protocol_features &
+		   (1ULL << VHOST_USER_PROTOCOL_F_REPLY_ACK);
+	ssize_t written;
+
+	if (ack)
+		msg->hdr.flags |= VHOST_USER_MSG_FLAGS_NEED_REPLY;
+
+	written = write(dev->req_fd, msg, msgsz);
+	USFSTL_ASSERT_EQ(written, (ssize_t)msgsz, "%zd");
+
+	if (ack) {
+		struct usfstl_loop_entry entry = {
+			.fd = dev->req_fd,
+			.priority = 0x7fffffff, // max
+			.handler = usfstl_vhost_user_readable_handler,
+		};
+		struct iovec msg_iov = {
+			.iov_base = msg,
+			.iov_len = sizeof(*msg),
+		};
+		struct msghdr msghdr = {
+			.msg_iovlen = 1,
+			.msg_iov = &msg_iov,
+		};
+
+		/*
+		 * Wait for the fd to be readable - we may have to
+		 * handle other simulation (time) messages while
+		 * waiting ...
+		 */
+		usfstl_loop_register(&entry);
+		while (entry.fd != -1)
+			usfstl_loop_wait_and_handle();
+		USFSTL_ASSERT_EQ(usfstl_vhost_user_read_msg(dev->req_fd,
+							    &msghdr),
+				 0, "%d");
+	}
+}
+
+static void usfstl_vhost_user_send_virtq_buf(struct usfstl_vhost_user_dev_int *dev,
+					     struct usfstl_vhost_user_buf *buf,
+					     int virtq_idx)
+{
+	struct vring *virtq = &dev->virtqs[virtq_idx].virtq;
+	unsigned int idx, widx;
+	int call_fd = dev->virtqs[virtq_idx].call_fd;
+	ssize_t written;
+	uint64_t e = 1;
+
+	if (dev->ext.server->ctrl)
+		usfstl_sched_ctrl_sync_to(dev->ext.server->ctrl);
+
+	idx = virtio_to_cpu16(dev, virtq->used->idx);
+	widx = idx + 1;
+
+	idx %= virtq->num;
+	virtq->used->ring[idx].id = cpu_to_virtio32(dev, buf->idx);
+	virtq->used->ring[idx].len = cpu_to_virtio32(dev, buf->written);
+
+	/* write buffers / used table before flush */
+	__sync_synchronize();
+
+	virtq->used->idx = cpu_to_virtio16(dev, widx);
+
+	if (call_fd < 0 &&
+	    dev->ext.protocol_features &
+			(1ULL << VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS) &&
+	    dev->ext.protocol_features &
+			(1ULL << VHOST_USER_PROTOCOL_F_SLAVE_REQ)) {
+		struct vhost_user_msg msg = {
+			.hdr.request = VHOST_USER_SLAVE_VRING_CALL,
+			.hdr.flags = VHOST_USER_VERSION,
+			.hdr.size = sizeof(msg.payload.vring_state),
+			.payload.vring_state = {
+				.idx = virtq_idx,
+			},
+		};
+
+		usfstl_vhost_user_send_msg(dev, &msg);
+		return;
+	}
+
+	written = write(dev->virtqs[virtq_idx].call_fd, &e, sizeof(e));
+	USFSTL_ASSERT_EQ(written, (ssize_t)sizeof(e), "%zd");
+}
+
+static void usfstl_vhost_user_handle_queue(struct usfstl_vhost_user_dev_int *dev,
+					   unsigned int virtq_idx)
+{
+	/* preallocate on the stack for most cases */
+	struct iovec in_sg[SG_STACK_PREALLOC] = { };
+	struct iovec out_sg[SG_STACK_PREALLOC] = { };
+	struct usfstl_vhost_user_buf _buf = {
+		.in_sg = in_sg,
+		.n_in_sg = SG_STACK_PREALLOC,
+		.out_sg = out_sg,
+		.n_out_sg = SG_STACK_PREALLOC,
+	};
+	struct usfstl_vhost_user_buf *buf;
+
+	while ((buf = usfstl_vhost_user_get_virtq_buf(dev, virtq_idx, &_buf))) {
+		dev->ext.server->ops->handle(&dev->ext, buf, virtq_idx);
+
+		usfstl_vhost_user_send_virtq_buf(dev, buf, virtq_idx);
+		usfstl_vhost_user_free_buf(buf);
+	}
+}
+
+static void usfstl_vhost_user_job_callback(struct usfstl_job *job)
+{
+	struct usfstl_vhost_user_dev_int *dev = job->data;
+	unsigned int virtq;
+
+	for (virtq = 0; virtq < dev->ext.server->max_queues; virtq++) {
+		if (!dev->virtqs[virtq].triggered)
+			continue;
+		dev->virtqs[virtq].triggered = false;
+
+		usfstl_vhost_user_handle_queue(dev, virtq);
+	}
+}
+
+static void usfstl_vhost_user_virtq_kick(struct usfstl_vhost_user_dev_int *dev,
+					 unsigned int virtq)
+{
+	if (!(dev->ext.server->input_queues & (1ULL << virtq)))
+		return;
+
+	dev->virtqs[virtq].triggered = true;
+
+	if (usfstl_job_scheduled(&dev->irq_job))
+		return;
+
+	if (!dev->ext.server->scheduler) {
+		usfstl_vhost_user_job_callback(&dev->irq_job);
+		return;
+	}
+
+	if (dev->ext.server->ctrl)
+		usfstl_sched_ctrl_sync_from(dev->ext.server->ctrl);
+
+	dev->irq_job.start = usfstl_sched_current_time(dev->ext.server->scheduler) +
+			     dev->ext.server->interrupt_latency;
+	usfstl_sched_add_job(dev->ext.server->scheduler, &dev->irq_job);
+}
+
+static void usfstl_vhost_user_virtq_fdkick(struct usfstl_loop_entry *entry)
+{
+	struct usfstl_vhost_user_dev_int *dev = entry->data;
+	unsigned int virtq;
+	uint64_t v;
+
+	for (virtq = 0; virtq < dev->ext.server->max_queues; virtq++) {
+		if (entry == &dev->virtqs[virtq].entry)
+			break;
+	}
+
+	USFSTL_ASSERT(virtq < dev->ext.server->max_queues);
+
+	USFSTL_ASSERT_EQ((int)read(entry->fd, &v, sizeof(v)),
+		       (int)sizeof(v), "%d");
+
+	usfstl_vhost_user_virtq_kick(dev, virtq);
+}
+
+static void usfstl_vhost_user_clear_mappings(struct usfstl_vhost_user_dev_int *dev)
+{
+	unsigned int idx;
+	for (idx = 0; idx < MAX_REGIONS; idx++) {
+		if (dev->region_vaddr[idx]) {
+			munmap(dev->region_vaddr[idx],
+			       dev->regions[idx].size + dev->regions[idx].mmap_offset);
+			dev->region_vaddr[idx] = NULL;
+		}
+
+		if (dev->region_fds[idx] != -1) {
+			close(dev->region_fds[idx]);
+			dev->region_fds[idx] = -1;
+		}
+	}
+}
+
+static void usfstl_vhost_user_setup_mappings(struct usfstl_vhost_user_dev_int *dev)
+{
+	unsigned int idx;
+
+	for (idx = 0; idx < dev->n_regions; idx++) {
+		USFSTL_ASSERT(!dev->region_vaddr[idx]);
+
+		/*
+		 * Cannot rely on the offset being page-aligned, I think ...
+		 * adjust for it later when we translate addresses instead.
+		 */
+		dev->region_vaddr[idx] = mmap(NULL,
+					      dev->regions[idx].size +
+					      dev->regions[idx].mmap_offset,
+					      PROT_READ | PROT_WRITE, MAP_SHARED,
+					      dev->region_fds[idx], 0);
+		USFSTL_ASSERT(dev->region_vaddr[idx] != (void *)-1,
+			      "mmap() failed (%d) for fd %d", errno, dev->region_fds[idx]);
+	}
+}
+
+static void
+usfstl_vhost_user_update_virtq_kick(struct usfstl_vhost_user_dev_int *dev,
+				  unsigned int virtq, int fd)
+{
+	if (dev->virtqs[virtq].entry.fd != -1) {
+		usfstl_loop_unregister(&dev->virtqs[virtq].entry);
+		close(dev->virtqs[virtq].entry.fd);
+	}
+
+	if (fd != -1) {
+		dev->virtqs[virtq].entry.fd = fd;
+		usfstl_loop_register(&dev->virtqs[virtq].entry);
+	}
+}
+
+static void usfstl_vhost_user_dev_free(struct usfstl_vhost_user_dev_int *dev)
+{
+	unsigned int virtq;
+
+	usfstl_loop_unregister(&dev->entry);
+	usfstl_sched_del_job(&dev->irq_job);
+
+	for (virtq = 0; virtq < dev->ext.server->max_queues; virtq++) {
+		usfstl_vhost_user_update_virtq_kick(dev, virtq, -1);
+		if (dev->virtqs[virtq].call_fd != -1)
+			close(dev->virtqs[virtq].call_fd);
+	}
+
+	usfstl_vhost_user_clear_mappings(dev);
+
+	if (dev->req_fd != -1)
+		close(dev->req_fd);
+
+	if (dev->ext.server->ops->disconnected)
+		dev->ext.server->ops->disconnected(&dev->ext);
+
+	if (dev->entry.fd != -1)
+		close(dev->entry.fd);
+
+	free(dev);
+}
+
+static void usfstl_vhost_user_get_msg_fds(struct msghdr *msghdr,
+					  int *outfds, int max_fds)
+{
+	struct cmsghdr *msg;
+	int fds;
+
+	for (msg = CMSG_FIRSTHDR(msghdr); msg; msg = CMSG_NXTHDR(msghdr, msg)) {
+		if (msg->cmsg_level != SOL_SOCKET)
+			continue;
+		if (msg->cmsg_type != SCM_RIGHTS)
+			continue;
+
+		fds = (msg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+		USFSTL_ASSERT(fds <= max_fds);
+		memcpy(outfds, CMSG_DATA(msg), fds * sizeof(int));
+		break;
+	}
+}
+
+static void usfstl_vhost_user_handle_msg(struct usfstl_loop_entry *entry)
+{
+	struct usfstl_vhost_user_dev_int *dev;
+	struct vhost_user_msg msg;
+	uint8_t data[256]; // limits the config space size ...
+	struct iovec msg_iov[3] = {
+		[0] = {
+			.iov_base = &msg.hdr,
+			.iov_len = sizeof(msg.hdr),
+		},
+		[1] = {
+			.iov_base = &msg.payload,
+			.iov_len = sizeof(msg.payload),
+		},
+		[2] = {
+			.iov_base = data,
+			.iov_len = sizeof(data),
+		},
+	};
+	uint8_t msg_control[CMSG_SPACE(sizeof(int) * MAX_REGIONS)] = { 0 };
+	struct msghdr msghdr = {
+		.msg_iov = msg_iov,
+		.msg_iovlen = 3,
+		.msg_control = msg_control,
+		.msg_controllen = sizeof(msg_control),
+	};
+	ssize_t len;
+	size_t reply_len = 0;
+	unsigned int virtq;
+	int fd;
+
+	dev = container_of(entry, struct usfstl_vhost_user_dev_int, entry);
+
+	if (usfstl_vhost_user_read_msg(entry->fd, &msghdr)) {
+		usfstl_vhost_user_dev_free(dev);
+		return;
+	}
+	len = msg.hdr.size;
+
+	USFSTL_ASSERT((msg.hdr.flags & VHOST_USER_MSG_FLAGS_VERSION) ==
+		    VHOST_USER_VERSION);
+
+	switch (msg.hdr.request) {
+	case VHOST_USER_GET_FEATURES:
+		USFSTL_ASSERT_EQ(len, (ssize_t)0, "%zd");
+		reply_len = sizeof(uint64_t);
+		msg.payload.u64 = dev->ext.server->features;
+		msg.payload.u64 |= 1ULL << VHOST_USER_F_PROTOCOL_FEATURES;
+		break;
+	case VHOST_USER_SET_FEATURES:
+		USFSTL_ASSERT_EQ(len, (ssize_t)sizeof(msg.payload.u64), "%zd");
+		dev->ext.features = msg.payload.u64;
+		break;
+	case VHOST_USER_SET_OWNER:
+		USFSTL_ASSERT_EQ(len, (ssize_t)0, "%zd");
+		/* nothing to be done */
+		break;
+	case VHOST_USER_SET_MEM_TABLE:
+		USFSTL_ASSERT(len >= (int)sizeof(msg.payload.mem_regions));
+		USFSTL_ASSERT(msg.payload.mem_regions.n_regions <= MAX_REGIONS);
+		usfstl_vhost_user_clear_mappings(dev);
+		memcpy(dev->regions, msg.payload.mem_regions.regions,
+		       msg.payload.mem_regions.n_regions *
+		       sizeof(dev->regions[0]));
+		dev->n_regions = msg.payload.mem_regions.n_regions;
+		usfstl_vhost_user_get_msg_fds(&msghdr, dev->region_fds, MAX_REGIONS);
+		usfstl_vhost_user_setup_mappings(dev);
+		break;
+	case VHOST_USER_SET_VRING_NUM:
+		USFSTL_ASSERT(len == (int)sizeof(msg.payload.vring_state));
+		USFSTL_ASSERT(msg.payload.vring_state.idx <
+			      dev->ext.server->max_queues);
+		dev->virtqs[msg.payload.vring_state.idx].virtq.num =
+			msg.payload.vring_state.num;
+		break;
+	case VHOST_USER_SET_VRING_ADDR:
+		USFSTL_ASSERT(len == (int)sizeof(msg.payload.vring_addr));
+		USFSTL_ASSERT(msg.payload.vring_addr.idx <=
+			      dev->ext.server->max_queues);
+		USFSTL_ASSERT_EQ(msg.payload.vring_addr.flags, (uint32_t)0, "0x%x");
+		USFSTL_ASSERT(!dev->virtqs[msg.payload.vring_addr.idx].enabled);
+		dev->virtqs[msg.payload.vring_addr.idx].last_avail_idx = 0;
+		dev->virtqs[msg.payload.vring_addr.idx].virtq.desc =
+			usfstl_vhost_user_to_va(&dev->ext,
+					      msg.payload.vring_addr.descriptor);
+		dev->virtqs[msg.payload.vring_addr.idx].virtq.used =
+			usfstl_vhost_user_to_va(&dev->ext,
+					      msg.payload.vring_addr.used);
+		dev->virtqs[msg.payload.vring_addr.idx].virtq.avail =
+			usfstl_vhost_user_to_va(&dev->ext,
+					      msg.payload.vring_addr.avail);
+		USFSTL_ASSERT(dev->virtqs[msg.payload.vring_addr.idx].virtq.avail &&
+			    dev->virtqs[msg.payload.vring_addr.idx].virtq.desc &&
+			    dev->virtqs[msg.payload.vring_addr.idx].virtq.used);
+		break;
+	case VHOST_USER_SET_VRING_BASE:
+		/* ignored - logging not supported */
+		/*
+		 * FIXME: our Linux UML virtio implementation
+		 *        shouldn't send this
+		 */
+		break;
+	case VHOST_USER_SET_VRING_KICK:
+		USFSTL_ASSERT(len == (int)sizeof(msg.payload.u64));
+		virtq = msg.payload.u64 & VHOST_USER_U64_VRING_IDX_MSK;
+		USFSTL_ASSERT(virtq <= dev->ext.server->max_queues);
+		if (msg.payload.u64 & VHOST_USER_U64_NO_FD)
+			fd = -1;
+		else
+			usfstl_vhost_user_get_msg_fds(&msghdr, &fd, 1);
+		usfstl_vhost_user_update_virtq_kick(dev, virtq, fd);
+		break;
+	case VHOST_USER_SET_VRING_CALL:
+		USFSTL_ASSERT(len == (int)sizeof(msg.payload.u64));
+		virtq = msg.payload.u64 & VHOST_USER_U64_VRING_IDX_MSK;
+		USFSTL_ASSERT(virtq <= dev->ext.server->max_queues);
+		if (dev->virtqs[virtq].call_fd != -1)
+			close(dev->virtqs[virtq].call_fd);
+		if (msg.payload.u64 & VHOST_USER_U64_NO_FD)
+			dev->virtqs[virtq].call_fd = -1;
+		else
+			usfstl_vhost_user_get_msg_fds(&msghdr,
+						    &dev->virtqs[virtq].call_fd,
+						    1);
+		break;
+	case VHOST_USER_GET_PROTOCOL_FEATURES:
+		USFSTL_ASSERT_EQ(len, (ssize_t)0, "%zd");
+		reply_len = sizeof(uint64_t);
+		msg.payload.u64 = dev->ext.server->protocol_features;
+		if (dev->ext.server->config && dev->ext.server->config_len)
+			msg.payload.u64 |= 1ULL << VHOST_USER_PROTOCOL_F_CONFIG;
+		msg.payload.u64 |= 1ULL << VHOST_USER_PROTOCOL_F_SLAVE_REQ;
+		msg.payload.u64 |= 1ULL << VHOST_USER_PROTOCOL_F_SLAVE_SEND_FD;
+		msg.payload.u64 |= 1ULL << VHOST_USER_PROTOCOL_F_REPLY_ACK;
+		break;
+	case VHOST_USER_SET_VRING_ENABLE:
+		USFSTL_ASSERT(len == (int)sizeof(msg.payload.vring_state));
+		USFSTL_ASSERT(msg.payload.vring_state.idx <
+			      dev->ext.server->max_queues);
+		dev->virtqs[msg.payload.vring_state.idx].enabled =
+			msg.payload.vring_state.num;
+		break;
+	case VHOST_USER_SET_PROTOCOL_FEATURES:
+		USFSTL_ASSERT(len == (int)sizeof(msg.payload.u64));
+		dev->ext.protocol_features = msg.payload.u64;
+		break;
+	case VHOST_USER_SET_SLAVE_REQ_FD:
+		USFSTL_ASSERT_EQ(len, (ssize_t)0, "%zd");
+		if (dev->req_fd != -1)
+			close(dev->req_fd);
+		usfstl_vhost_user_get_msg_fds(&msghdr, &dev->req_fd, 1);
+		USFSTL_ASSERT(dev->req_fd != -1);
+		break;
+	case VHOST_USER_GET_CONFIG:
+		USFSTL_ASSERT(len == (int)(sizeof(msg.payload.cfg_space) +
+					msg.payload.cfg_space.size));
+		USFSTL_ASSERT(dev->ext.server->config && dev->ext.server->config_len);
+		USFSTL_ASSERT(msg.payload.cfg_space.offset == 0);
+		USFSTL_ASSERT(msg.payload.cfg_space.size <= dev->ext.server->config_len);
+		msg.payload.cfg_space.flags = 0;
+		msg_iov[1].iov_len = sizeof(msg.payload.cfg_space);
+		msg_iov[2].iov_base = (void *)dev->ext.server->config;
+		reply_len = len;
+		break;
+	case VHOST_USER_VRING_KICK:
+		USFSTL_ASSERT(len == (int)sizeof(msg.payload.vring_state));
+		USFSTL_ASSERT(msg.payload.vring_state.idx <
+			      dev->ext.server->max_queues);
+		USFSTL_ASSERT(msg.payload.vring_state.num == 0);
+		usfstl_vhost_user_virtq_kick(dev, msg.payload.vring_state.idx);
+		break;
+	default:
+		USFSTL_ASSERT(0, "Unsupported message: %d\n", msg.hdr.request);
+	}
+
+	if (reply_len || (msg.hdr.flags & VHOST_USER_MSG_FLAGS_NEED_REPLY)) {
+		size_t i, tmp;
+
+		if (!reply_len) {
+			msg.payload.u64 = 0;
+			reply_len = sizeof(uint64_t);
+		}
+
+		msg.hdr.size = reply_len;
+		msg.hdr.flags &= ~VHOST_USER_MSG_FLAGS_NEED_REPLY;
+		msg.hdr.flags |= VHOST_USER_MSG_FLAGS_REPLY;
+
+		msghdr.msg_control = NULL;
+		msghdr.msg_controllen = 0;
+
+		reply_len += sizeof(msg.hdr);
+
+		tmp = reply_len;
+		for (i = 0; tmp && i < msghdr.msg_iovlen; i++) {
+			if (tmp <= msg_iov[i].iov_len)
+				msg_iov[i].iov_len = tmp;
+			tmp -= msg_iov[i].iov_len;
+		}
+		msghdr.msg_iovlen = i;
+
+		while (reply_len) {
+			len = sendmsg(entry->fd, &msghdr, 0);
+			if (len < 0) {
+				usfstl_vhost_user_dev_free(dev);
+				return;
+			}
+			USFSTL_ASSERT(len != 0);
+			reply_len -= len;
+
+			for (i = 0; len && i < msghdr.msg_iovlen; i++) {
+				unsigned int rm = len;
+
+				if (msg_iov[i].iov_len <= (size_t)len)
+					rm = msg_iov[i].iov_len;
+				len -= rm;
+				msg_iov[i].iov_len -= rm;
+				msg_iov[i].iov_base += rm;
+			}
+		}
+	}
+}
+
+static void usfstl_vhost_user_connected(int fd, void *data)
+{
+	struct usfstl_vhost_user_server *server = data;
+	struct usfstl_vhost_user_dev_int *dev;
+	unsigned int i;
+
+	dev = calloc(1, sizeof(*dev) +
+			sizeof(dev->virtqs[0]) * server->max_queues);
+
+	USFSTL_ASSERT(dev);
+
+	for (i = 0; i < server->max_queues; i++) {
+		dev->virtqs[i].call_fd = -1;
+		dev->virtqs[i].entry.fd = -1;
+		dev->virtqs[i].entry.data = dev;
+		dev->virtqs[i].entry.handler = usfstl_vhost_user_virtq_fdkick;
+	}
+
+	for (i = 0; i < MAX_REGIONS; i++)
+		dev->region_fds[i] = -1;
+	dev->req_fd = -1;
+
+	dev->ext.server = server;
+	dev->irq_job.data = dev;
+	dev->irq_job.name = "vhost-user-irq";
+	dev->irq_job.priority = 0x10000000;
+	dev->irq_job.callback = usfstl_vhost_user_job_callback;
+	usfstl_list_init(&dev->fds);
+
+	if (server->ops->connected)
+		server->ops->connected(&dev->ext);
+
+	dev->entry.fd = fd;
+	dev->entry.handler = usfstl_vhost_user_handle_msg;
+
+	usfstl_loop_register(&dev->entry);
+}
+
+void usfstl_vhost_user_server_start(struct usfstl_vhost_user_server *server)
+{
+	USFSTL_ASSERT(server->ops);
+	USFSTL_ASSERT(server->socket);
+
+	usfstl_uds_create(server->socket, usfstl_vhost_user_connected, server);
+}
+
+void usfstl_vhost_user_server_stop(struct usfstl_vhost_user_server *server)
+{
+	usfstl_uds_remove(server->socket);
+}
+
+void usfstl_vhost_user_dev_notify(struct usfstl_vhost_user_dev *extdev,
+				  unsigned int virtq_idx,
+				  const uint8_t *data, size_t datalen)
+{
+	struct usfstl_vhost_user_dev_int *dev;
+	/* preallocate on the stack for most cases */
+	struct iovec in_sg[SG_STACK_PREALLOC] = { };
+	struct iovec out_sg[SG_STACK_PREALLOC] = { };
+	struct usfstl_vhost_user_buf _buf = {
+		.in_sg = in_sg,
+		.n_in_sg = SG_STACK_PREALLOC,
+		.out_sg = out_sg,
+		.n_out_sg = SG_STACK_PREALLOC,
+	};
+	struct usfstl_vhost_user_buf *buf;
+
+	dev = container_of(extdev, struct usfstl_vhost_user_dev_int, ext);
+
+	USFSTL_ASSERT(virtq_idx <= dev->ext.server->max_queues);
+
+	if (!dev->virtqs[virtq_idx].enabled)
+		return;
+
+	buf = usfstl_vhost_user_get_virtq_buf(dev, virtq_idx, &_buf);
+	if (!buf)
+		return;
+
+	USFSTL_ASSERT(buf->n_in_sg && !buf->n_out_sg);
+	iov_fill(buf->in_sg, buf->n_in_sg, data, datalen);
+	buf->written = datalen;
+
+	usfstl_vhost_user_send_virtq_buf(dev, buf, virtq_idx);
+	usfstl_vhost_user_free_buf(buf);
+}
+
+void usfstl_vhost_user_config_changed(struct usfstl_vhost_user_dev *dev)
+{
+	struct usfstl_vhost_user_dev_int *idev;
+	struct vhost_user_msg msg = {
+		.hdr.request = VHOST_USER_SLAVE_CONFIG_CHANGE_MSG,
+		.hdr.flags = VHOST_USER_VERSION,
+	};
+
+	idev = container_of(dev, struct usfstl_vhost_user_dev_int, ext);
+
+	if (!(idev->ext.protocol_features &
+			(1ULL << VHOST_USER_PROTOCOL_F_CONFIG)))
+		return;
+
+	usfstl_vhost_user_send_msg(idev, &msg);
+}
+
+void *usfstl_vhost_user_to_va(struct usfstl_vhost_user_dev *extdev, uint64_t addr)
+{
+	struct usfstl_vhost_user_dev_int *dev;
+	unsigned int region;
+
+	dev = container_of(extdev, struct usfstl_vhost_user_dev_int, ext);
+
+	for (region = 0; region < dev->n_regions; region++) {
+		if (addr >= dev->regions[region].user_addr &&
+		    addr < dev->regions[region].user_addr +
+			   dev->regions[region].size)
+			return (uint8_t *)dev->region_vaddr[region] +
+			       (addr -
+				dev->regions[region].user_addr +
+				dev->regions[region].mmap_offset);
+	}
+
+	USFSTL_ASSERT(0, "cannot translate address %"PRIx64"\n", addr);
+	return NULL;
+}
+
+size_t iov_len(struct iovec *sg, unsigned int nsg)
+{
+	size_t len = 0;
+	unsigned int i;
+
+	for (i = 0; i < nsg; i++)
+		len += sg[i].iov_len;
+
+	return len;
+}
+
+size_t iov_fill(struct iovec *sg, unsigned int nsg,
+		const void *_buf, size_t buflen)
+{
+	const char *buf = _buf;
+	unsigned int i;
+	size_t copied = 0;
+
+#define min(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; })
+	for (i = 0; buflen && i < nsg; i++) {
+		size_t cpy = min(buflen, sg[i].iov_len);
+
+		memcpy(sg[i].iov_base, buf, cpy);
+		buflen -= cpy;
+		copied += cpy;
+		buf += cpy;
+	}
+
+	return copied;
+}
+
+size_t iov_read(void *_buf, size_t buflen,
+		struct iovec *sg, unsigned int nsg)
+{
+	char *buf = _buf;
+	unsigned int i;
+	size_t copied = 0;
+
+#define min(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; })
+	for (i = 0; buflen && i < nsg; i++) {
+		size_t cpy = min(buflen, sg[i].iov_len);
+
+		memcpy(buf, sg[i].iov_base, cpy);
+		buflen -= cpy;
+		copied += cpy;
+		buf += cpy;
+	}
+
+	return copied;
+}
diff --git a/wmediumd/lib/wallclock.c b/wmediumd/lib/wallclock.c
new file mode 100644
index 0000000..3fb09ac
--- /dev/null
+++ b/wmediumd/lib/wallclock.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2019 - 2020 Intel Corporation
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+#include <sys/timerfd.h>
+#include <usfstl/sched.h>
+#include <usfstl/loop.h>
+
+void usfstl_sched_wallclock_handle_fd(struct usfstl_loop_entry *entry)
+{
+	struct usfstl_scheduler *sched;
+	uint64_t v;
+
+	sched = container_of(entry, struct usfstl_scheduler, wallclock.entry);
+
+	USFSTL_ASSERT_EQ((int)read(entry->fd, &v, sizeof(v)), (int)sizeof(v), "%d");
+	sched->wallclock.timer_triggered = 1;
+}
+
+static void usfstl_sched_wallclock_initialize(struct usfstl_scheduler *sched)
+{
+	struct timespec now = {};
+
+	USFSTL_ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &now), 0, "%d");
+
+	sched->wallclock.start = (uint64_t)now.tv_sec * 1000000000 + now.tv_nsec;
+	sched->wallclock.initialized = 1;
+}
+
+void usfstl_sched_wallclock_request(struct usfstl_scheduler *sched, uint64_t time)
+{
+	struct itimerspec itimer = {};
+	unsigned int nsec_per_tick = sched->wallclock.nsec_per_tick;
+	uint64_t waketime;
+
+	USFSTL_ASSERT(nsec_per_tick != 0);
+
+	if (!sched->wallclock.initialized)
+		usfstl_sched_wallclock_initialize(sched);
+
+	waketime = sched->wallclock.start + nsec_per_tick * time;
+
+	itimer.it_value.tv_sec = waketime / 1000000000;
+	itimer.it_value.tv_nsec = waketime % 1000000000;
+
+	USFSTL_ASSERT_EQ(timerfd_settime(sched->wallclock.entry.fd,
+				       TFD_TIMER_ABSTIME, &itimer, NULL),
+		       0, "%d");
+}
+
+void usfstl_sched_wallclock_wait(struct usfstl_scheduler *sched)
+{
+	sched->wallclock.timer_triggered = 0;
+
+	usfstl_loop_register(&sched->wallclock.entry);
+
+	while (!sched->wallclock.timer_triggered)
+		usfstl_loop_wait_and_handle();
+
+	usfstl_loop_unregister(&sched->wallclock.entry);
+
+	usfstl_sched_set_time(sched, sched->prev_external_sync);
+}
+
+void usfstl_sched_wallclock_init(struct usfstl_scheduler *sched,
+				 unsigned int ns_per_tick)
+{
+	USFSTL_ASSERT(!sched->external_request && !sched->external_wait);
+
+	sched->external_request = usfstl_sched_wallclock_request;
+	sched->external_wait = usfstl_sched_wallclock_wait;
+
+	sched->wallclock.entry.fd = timerfd_create(CLOCK_MONOTONIC, 0);
+	USFSTL_ASSERT(sched->wallclock.entry.fd >= 0);
+
+	sched->wallclock.entry.handler = usfstl_sched_wallclock_handle_fd;
+
+	sched->wallclock.nsec_per_tick = ns_per_tick;
+}
+
+void usfstl_sched_wallclock_exit(struct usfstl_scheduler *sched)
+{
+	USFSTL_ASSERT(sched->external_request == usfstl_sched_wallclock_request &&
+		    sched->external_wait == usfstl_sched_wallclock_wait);
+
+	sched->external_request = NULL;
+	sched->external_wait = NULL;
+	close(sched->wallclock.entry.fd);
+}
+
+static void _usfstl_sched_wallclock_sync_real(struct usfstl_scheduler *sched)
+{
+	struct timespec now = {};
+	uint64_t nowns;
+
+	USFSTL_ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &now), 0, "%d");
+
+	nowns = (uint64_t)now.tv_sec * 1000000000 + now.tv_nsec;
+	nowns -= sched->wallclock.start;
+	usfstl_sched_set_time(sched, nowns / sched->wallclock.nsec_per_tick);
+}
+
+static void usfstl_sched_wallclock_sync_real(void *data)
+{
+	_usfstl_sched_wallclock_sync_real(data);
+}
+
+void usfstl_sched_wallclock_wait_and_handle(struct usfstl_scheduler *sched)
+{
+	void (*old_fn)(void *);
+	void *old_data;
+
+	if (usfstl_sched_next_pending(sched, NULL))
+		return;
+
+	if (!sched->wallclock.initialized) {
+		/*
+		 * At least once at the beginning we should schedule
+		 * and initialize from an initial request, not here.
+		 */
+		usfstl_loop_wait_and_handle();
+		return;
+	}
+
+	old_fn = g_usfstl_loop_pre_handler_fn;
+	old_data = g_usfstl_loop_pre_handler_fn_data;
+	g_usfstl_loop_pre_handler_fn = usfstl_sched_wallclock_sync_real;
+	g_usfstl_loop_pre_handler_fn_data = sched;
+
+	usfstl_loop_wait_and_handle();
+
+	g_usfstl_loop_pre_handler_fn = old_fn;
+	g_usfstl_loop_pre_handler_fn_data = old_data;
+}
diff --git a/wmediumd/list.h b/wmediumd/list.h
new file mode 100644
index 0000000..a6212af
--- /dev/null
+++ b/wmediumd/list.h
@@ -0,0 +1,774 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#include <linux/types.h>
+#include <stddef.h>
+
+/* Stripped down from Linux ~5.5 */
+
+#define POISON_POINTER_DELTA 0
+#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
+#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
+#define WRITE_ONCE(p, v) do { p = v; } while (0)
+#define READ_ONCE(p) (p)
+#ifndef container_of
+#define container_of(ptr, type, member) ((type *)(void*)( (char*)ptr - offsetof(type, member)))
+#endif
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+	struct list_head name = LIST_HEAD_INIT(name)
+
+/**
+ * INIT_LIST_HEAD - Initialize a list_head structure
+ * @list: list_head structure to be initialized.
+ *
+ * Initializes the list_head to point to itself.  If it is a list header,
+ * the result is an empty list.
+ */
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+	WRITE_ONCE(list->next, list);
+	list->prev = list;
+}
+
+static inline bool __list_add_valid(struct list_head *new,
+				struct list_head *prev,
+				struct list_head *next)
+{
+	return true;
+}
+static inline bool __list_del_entry_valid(struct list_head *entry)
+{
+	return true;
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	if (!__list_add_valid(new, prev, next))
+		return;
+
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	WRITE_ONCE(prev->next, new);
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+	next->prev = prev;
+	WRITE_ONCE(prev->next, next);
+}
+
+/*
+ * Delete a list entry and clear the 'prev' pointer.
+ *
+ * This is a special-purpose list clearing method used in the networking code
+ * for lists allocated as per-cpu, where we don't want to incur the extra
+ * WRITE_ONCE() overhead of a regular list_del_init(). The code that uses this
+ * needs to check the node 'prev' pointer instead of calling list_empty().
+ */
+static inline void __list_del_clearprev(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	entry->prev = NULL;
+}
+
+static inline void __list_del_entry(struct list_head *entry)
+{
+	if (!__list_del_entry_valid(entry))
+		return;
+
+	__list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty() on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+	__list_del_entry(entry);
+	entry->next = LIST_POISON1;
+	entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_replace - replace old entry by new one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace(struct list_head *old,
+				struct list_head *new)
+{
+	new->next = old->next;
+	new->next->prev = new;
+	new->prev = old->prev;
+	new->prev->next = new;
+}
+
+/**
+ * list_replace_init - replace old entry by new one and initialize the old one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace_init(struct list_head *old,
+				     struct list_head *new)
+{
+	list_replace(old, new);
+	INIT_LIST_HEAD(old);
+}
+
+/**
+ * list_swap - replace entry1 with entry2 and re-add entry1 at entry2's position
+ * @entry1: the location to place entry2
+ * @entry2: the location to place entry1
+ */
+static inline void list_swap(struct list_head *entry1,
+			     struct list_head *entry2)
+{
+	struct list_head *pos = entry2->prev;
+
+	list_del(entry2);
+	list_replace(entry1, entry2);
+	if (pos == entry1)
+		pos = entry2;
+	list_add(entry1, pos);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+	__list_del_entry(entry);
+	INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+	__list_del_entry(list);
+	list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+				  struct list_head *head)
+{
+	__list_del_entry(list);
+	list_add_tail(list, head);
+}
+
+/**
+ * list_bulk_move_tail - move a subsection of a list to its tail
+ * @head: the head that will follow our entry
+ * @first: first entry to move
+ * @last: last entry to move, can be the same as first
+ *
+ * Move all entries between @first and including @last before @head.
+ * All three entries must belong to the same linked list.
+ */
+static inline void list_bulk_move_tail(struct list_head *head,
+				       struct list_head *first,
+				       struct list_head *last)
+{
+	first->prev->next = last->next;
+	last->next->prev = first->prev;
+
+	head->prev->next = first;
+	first->prev = head->prev;
+
+	last->next = head;
+	head->prev = last;
+}
+
+/**
+ * list_is_first -- tests whether @list is the first entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_first(const struct list_head *list,
+					const struct list_head *head)
+{
+	return list->prev == head;
+}
+
+/**
+ * list_is_last - tests whether @list is the last entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_last(const struct list_head *list,
+				const struct list_head *head)
+{
+	return list->next == head;
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+	return READ_ONCE(head->next) == head;
+}
+
+/**
+ * list_empty_careful - tests whether a list is empty and not being modified
+ * @head: the list to test
+ *
+ * Description:
+ * tests whether a list is empty _and_ checks that no other CPU might be
+ * in the process of modifying either member (next or prev)
+ *
+ * NOTE: using list_empty_careful() without synchronization
+ * can only be safe if the only activity that can happen
+ * to the list entry is list_del_init(). Eg. it cannot be used
+ * if another CPU could re-list_add() it.
+ */
+static inline int list_empty_careful(const struct list_head *head)
+{
+	struct list_head *next = head->next;
+	return (next == head) && (next == head->prev);
+}
+
+/**
+ * list_rotate_left - rotate the list to the left
+ * @head: the head of the list
+ */
+static inline void list_rotate_left(struct list_head *head)
+{
+	struct list_head *first;
+
+	if (!list_empty(head)) {
+		first = head->next;
+		list_move_tail(first, head);
+	}
+}
+
+/**
+ * list_rotate_to_front() - Rotate list to specific item.
+ * @list: The desired new front of the list.
+ * @head: The head of the list.
+ *
+ * Rotates list so that @list becomes the new front of the list.
+ */
+static inline void list_rotate_to_front(struct list_head *list,
+					struct list_head *head)
+{
+	/*
+	 * Deletes the list head from the list denoted by @head and
+	 * places it as the tail of @list, this effectively rotates the
+	 * list so that @list is at the front.
+	 */
+	list_move_tail(head, list);
+}
+
+/**
+ * list_is_singular - tests whether a list has just one entry.
+ * @head: the list to test.
+ */
+static inline int list_is_singular(const struct list_head *head)
+{
+	return !list_empty(head) && (head->next == head->prev);
+}
+
+static inline void __list_cut_position(struct list_head *list,
+		struct list_head *head, struct list_head *entry)
+{
+	struct list_head *new_first = entry->next;
+	list->next = head->next;
+	list->next->prev = list;
+	list->prev = entry;
+	entry->next = list;
+	head->next = new_first;
+	new_first->prev = head;
+}
+
+/**
+ * list_cut_position - cut a list into two
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ *	and if so we won't cut the list
+ *
+ * This helper moves the initial part of @head, up to and
+ * including @entry, from @head to @list. You should
+ * pass on @entry an element you know is on @head. @list
+ * should be an empty list or a list you do not care about
+ * losing its data.
+ *
+ */
+static inline void list_cut_position(struct list_head *list,
+		struct list_head *head, struct list_head *entry)
+{
+	if (list_empty(head))
+		return;
+	if (list_is_singular(head) &&
+		(head->next != entry && head != entry))
+		return;
+	if (entry == head)
+		INIT_LIST_HEAD(list);
+	else
+		__list_cut_position(list, head, entry);
+}
+
+/**
+ * list_cut_before - cut a list into two, before given entry
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ *
+ * This helper moves the initial part of @head, up to but
+ * excluding @entry, from @head to @list.  You should pass
+ * in @entry an element you know is on @head.  @list should
+ * be an empty list or a list you do not care about losing
+ * its data.
+ * If @entry == @head, all entries on @head are moved to
+ * @list.
+ */
+static inline void list_cut_before(struct list_head *list,
+				   struct list_head *head,
+				   struct list_head *entry)
+{
+	if (head->next == entry) {
+		INIT_LIST_HEAD(list);
+		return;
+	}
+	list->next = head->next;
+	list->next->prev = list;
+	list->prev = entry->prev;
+	list->prev->next = list;
+	head->next = entry;
+	entry->prev = head;
+}
+
+static inline void __list_splice(const struct list_head *list,
+				 struct list_head *prev,
+				 struct list_head *next)
+{
+	struct list_head *first = list->next;
+	struct list_head *last = list->prev;
+
+	first->prev = prev;
+	prev->next = first;
+
+	last->next = next;
+	next->prev = last;
+}
+
+/**
+ * list_splice - join two lists, this is designed for stacks
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(const struct list_head *list,
+				struct list_head *head)
+{
+	if (!list_empty(list))
+		__list_splice(list, head, head->next);
+}
+
+/**
+ * list_splice_tail - join two lists, each list being a queue
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice_tail(struct list_head *list,
+				struct list_head *head)
+{
+	if (!list_empty(list))
+		__list_splice(list, head->prev, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+				    struct list_head *head)
+{
+	if (!list_empty(list)) {
+		__list_splice(list, head, head->next);
+		INIT_LIST_HEAD(list);
+	}
+}
+
+/**
+ * list_splice_tail_init - join two lists and reinitialise the emptied list
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * Each of the lists is a queue.
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_tail_init(struct list_head *list,
+					 struct list_head *head)
+{
+	if (!list_empty(list)) {
+		__list_splice(list, head->prev, head);
+		INIT_LIST_HEAD(list);
+	}
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_entry(ptr, type, member) \
+	container_of(ptr, type, member)
+
+/**
+ * list_first_entry - get the first element from a list
+ * @ptr:	the list head to take the element from.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+	list_entry((ptr)->next, type, member)
+
+/**
+ * list_last_entry - get the last element from a list
+ * @ptr:	the list head to take the element from.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_last_entry(ptr, type, member) \
+	list_entry((ptr)->prev, type, member)
+
+/**
+ * list_first_entry_or_null - get the first element from a list
+ * @ptr:	the list head to take the element from.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define list_first_entry_or_null(ptr, type, member) ({ \
+	struct list_head *head__ = (ptr); \
+	struct list_head *pos__ = READ_ONCE(head__->next); \
+	pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
+})
+
+/**
+ * list_last_entry_or_null - get the last element from a list
+ * @ptr:	the list head to take the element from.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Note that if the list is empty, it returns NULL.
+ */
+#define list_last_entry_or_null(ptr, type, member) ({ \
+	struct list_head *head__ = (ptr); \
+	struct list_head *pos__ = READ_ONCE(head__->prev); \
+	pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
+})
+
+/**
+ * list_next_entry - get the next element in list
+ * @pos:	the type * to cursor
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_next_entry(pos, member) \
+	list_entry((pos)->member.next, typeof(*(pos)), member)
+
+/**
+ * list_prev_entry - get the prev element in list
+ * @pos:	the type * to cursor
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_prev_entry(pos, member) \
+	list_entry((pos)->member.prev, typeof(*(pos)), member)
+
+/**
+ * list_for_each	-	iterate over a list
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @head:	the head for your list.
+ */
+#define list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_continue - continue iteration over a list
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @head:	the head for your list.
+ *
+ * Continue to iterate over a list, continuing after the current position.
+ */
+#define list_for_each_continue(pos, head) \
+	for (pos = pos->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev	-	iterate over a list backwards
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @head:	the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+	for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @n:		another &struct list_head to use as temporary storage
+ * @head:	the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
+ * @pos:	the &struct list_head to use as a loop cursor.
+ * @n:		another &struct list_head to use as temporary storage
+ * @head:	the head for your list.
+ */
+#define list_for_each_prev_safe(pos, n, head) \
+	for (pos = (head)->prev, n = pos->prev; \
+	     pos != (head); \
+	     pos = n, n = pos->prev)
+
+/**
+ * list_for_each_entry	-	iterate over list of given type
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_first_entry(head, typeof(*pos), member);	\
+	     &pos->member != (head);					\
+	     pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member)			\
+	for (pos = list_last_entry(head, typeof(*pos), member);		\
+	     &pos->member != (head); 					\
+	     pos = list_prev_entry(pos, member))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
+ * @pos:	the type * to use as a start point
+ * @head:	the head of the list
+ * @member:	the name of the list_head within the struct.
+ *
+ * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
+ */
+#define list_prepare_entry(pos, head, member) \
+	((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - continue iteration over list of given type
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Continue to iterate over list of given type, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue(pos, head, member) 		\
+	for (pos = list_next_entry(pos, member);			\
+	     &pos->member != (head);					\
+	     pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_continue_reverse - iterate backwards from the given point
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Start to iterate over list of given type backwards, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue_reverse(pos, head, member)		\
+	for (pos = list_prev_entry(pos, member);			\
+	     &pos->member != (head);					\
+	     pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_from - iterate over list of given type from the current point
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from(pos, head, member) 			\
+	for (; &pos->member != (head);					\
+	     pos = list_next_entry(pos, member))
+
+/**
+ * list_for_each_entry_from_reverse - iterate backwards over list of given type
+ *                                    from the current point
+ * @pos:	the type * to use as a loop cursor.
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from_reverse(pos, head, member)		\
+	for (; &pos->member != (head);					\
+	     pos = list_prev_entry(pos, member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos:	the type * to use as a loop cursor.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = list_first_entry(head, typeof(*pos), member),	\
+		n = list_next_entry(pos, member);			\
+	     &pos->member != (head); 					\
+	     pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_continue - continue list iteration safe against removal
+ * @pos:	the type * to use as a loop cursor.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate over list of given type, continuing after current point,
+ * safe against removal of list entry.
+ */
+#define list_for_each_entry_safe_continue(pos, n, head, member) 		\
+	for (pos = list_next_entry(pos, member), 				\
+		n = list_next_entry(pos, member);				\
+	     &pos->member != (head);						\
+	     pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_from - iterate over list from current point safe against removal
+ * @pos:	the type * to use as a loop cursor.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate over list of given type from current point, safe against
+ * removal of list entry.
+ */
+#define list_for_each_entry_safe_from(pos, n, head, member) 			\
+	for (n = list_next_entry(pos, member);					\
+	     &pos->member != (head);						\
+	     pos = n, n = list_next_entry(n, member))
+
+/**
+ * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
+ * @pos:	the type * to use as a loop cursor.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Iterate backwards over list of given type, safe against removal
+ * of list entry.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member)		\
+	for (pos = list_last_entry(head, typeof(*pos), member),		\
+		n = list_prev_entry(pos, member);			\
+	     &pos->member != (head); 					\
+	     pos = n, n = list_prev_entry(n, member))
+
+/**
+ * list_safe_reset_next - reset a stale list_for_each_entry_safe loop
+ * @pos:	the loop cursor used in the list_for_each_entry_safe loop
+ * @n:		temporary storage used in list_for_each_entry_safe
+ * @member:	the name of the list_head within the struct.
+ *
+ * list_safe_reset_next is not safe to use in general if the list may be
+ * modified concurrently (eg. the lock is dropped in the loop body). An
+ * exception to this is if the cursor element (pos) is pinned in the list,
+ * and list_safe_reset_next is called after re-taking the lock and before
+ * completing the current iteration of the loop body.
+ */
+#define list_safe_reset_next(pos, n, member)				\
+	n = list_next_entry(pos, member)
+
+#endif
diff --git a/wmediumd/per.c b/wmediumd/per.c
new file mode 100644
index 0000000..1f89a77
--- /dev/null
+++ b/wmediumd/per.c
@@ -0,0 +1,283 @@
+/*
+ * Generate packet error rates for OFDM rates given signal level and
+ * packet length.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "wmediumd.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+/* Code rates for convolutional codes */
+enum fec_rate {
+	FEC_RATE_1_2,
+	FEC_RATE_2_3,
+	FEC_RATE_3_4,
+};
+
+struct rate {
+	int mbps;
+	int mqam;
+	enum fec_rate fec;
+};
+
+/*
+ * rate sets are defined in drivers/net/wireless/mac80211_hwsim.c#hwsim_rates.
+ */
+static struct rate rateset[] = {
+	/*
+	 * XXX:
+	 * For rate = 1, 2, 5.5, 11 Mbps, we will use mqam and fec of closest
+	 * rate. Because these rates are not OFDM rate.
+	 */
+	{ .mbps = 10, .mqam = 2, .fec = FEC_RATE_1_2 },
+	{ .mbps = 20, .mqam = 2, .fec = FEC_RATE_1_2 },
+	{ .mbps = 55, .mqam = 2, .fec = FEC_RATE_1_2 },
+	{ .mbps = 110, .mqam = 4, .fec = FEC_RATE_1_2 },
+	{ .mbps = 60, .mqam = 2, .fec = FEC_RATE_1_2 },
+	{ .mbps = 90, .mqam = 2, .fec = FEC_RATE_3_4 },
+	{ .mbps = 120, .mqam = 4, .fec = FEC_RATE_1_2 },
+	{ .mbps = 180, .mqam = 4, .fec = FEC_RATE_3_4 },
+	{ .mbps = 240, .mqam = 16, .fec = FEC_RATE_1_2 },
+	{ .mbps = 360, .mqam = 16, .fec = FEC_RATE_3_4 },
+	{ .mbps = 480, .mqam = 64, .fec = FEC_RATE_2_3 },
+	{ .mbps = 540, .mqam = 64, .fec = FEC_RATE_3_4 },
+};
+static size_t rate_len = ARRAY_SIZE(rateset);
+
+static double n_choose_k(double n, double k)
+{
+	int i;
+	double c = 1;
+
+	if (n < k || !k)
+		return 0;
+
+	if (k > n - k)
+		k = n - k;
+
+	for (i = 1; i <= k; i++)
+		c *= (n - (k - i)) / i;
+
+	return c;
+}
+
+static double dot(double *v1, double *v2, int len)
+{
+	int i;
+	double val = 0;
+
+	for (i = 0; i < len; i++)
+		val += v1[i] * v2[i];
+
+	return val;
+}
+
+/*
+ * Compute bit error rate for BPSK at a given SNR.
+ * See http://en.wikipedia.org/wiki/Phase-shift_keying
+ */
+static double bpsk_ber(double snr_db)
+{
+	double snr = pow(10, (snr_db / 10.));
+
+	return .5 * erfc(sqrt(snr));
+}
+
+/*
+ * Compute bit error rate for M-QAM at a given SNR.
+ * See http://www.dsplog.com/2012/01/01/symbol-error-rate-16qam-64qam-256qam/
+ */
+static double mqam_ber(int m, double snr_db)
+{
+	double k = sqrt(1. / ((2./3) * (m - 1)));
+	double snr = pow(10, (snr_db / 10.));
+	double e = erfc(k * sqrt(snr));
+	double sqrtm = sqrt(m);
+
+	double b = 2 * (1 - 1./sqrtm) * e;
+	double c = (1 - 2./sqrtm + 1./m) * pow(e, 2);
+	double ser = b - c;
+
+	return ser / log2(m);
+}
+
+/*
+ * Compute packet (frame) error rate given a length
+ */
+static double per(double ber, enum fec_rate rate, int frame_len)
+{
+	/* free distances for each fec_rate */
+	int d_free[] = { 10, 6, 5 };
+
+	/* initial rate code coefficients */
+	double a_d[3][10] = {
+		/* FEC_RATE_1_2 */
+		{ 11, 0, 38, 0, 193, 0, 1331, 0, 7275, 0 },
+		/* FEC_RATE_2_3 */
+		{ 1, 16, 48, 158, 642, 2435, 9174, 34701, 131533, 499312 },
+		/* FEC_RATE_3_4 */
+		{ 8, 31, 160, 892, 4512, 23297, 120976, 624304, 3229885, 16721329 }
+	};
+
+	double p_d[ARRAY_SIZE(a_d[0])] = {};
+	double rho = ber;
+	double prob_uncorrected;
+	int k;
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(p_d); i++) {
+		double sum_prob = 0;
+		int d = d_free[rate] + i;
+
+		if (d & 1) {
+			for (k = (d + 1)/2; k <= d; k++)
+				sum_prob += n_choose_k(d, k) * pow(rho, k) *
+					    pow(1 - rho, d - k);
+		} else {
+			for (k = d/2 + 1; k <= d; k++)
+				sum_prob += n_choose_k(d, k) * pow(rho, k) *
+					    pow(1 - rho, d - k);
+
+			sum_prob += .5 * n_choose_k(d, d/2) * pow(rho, d/2) *
+				    pow(1 - rho, d/2);
+		}
+
+		p_d[i] = sum_prob;
+	}
+
+	prob_uncorrected = dot(p_d, a_d[rate], ARRAY_SIZE(a_d[rate]));
+	if (prob_uncorrected > 1)
+		prob_uncorrected = 1;
+
+	return 1.0 - pow(1 - prob_uncorrected, 8 * frame_len);
+}
+
+double get_error_prob_from_snr(double snr, unsigned int rate_idx, u32 freq,
+			       int frame_len)
+{
+	int m;
+	enum fec_rate fec;
+	double ber;
+
+	if (snr <= 0.0)
+		return 1.0;
+
+	if (freq > 5000)
+		rate_idx += 4;
+
+	if (rate_idx >= rate_len)
+		return 1.0;
+
+	m = rateset[rate_idx].mqam;
+	fec = rateset[rate_idx].fec;
+
+	if (m == 2)
+		ber = bpsk_ber(snr);
+	else
+		ber = mqam_ber(m, snr);
+
+	return per(ber, fec, frame_len);
+}
+
+static double get_error_prob_from_per_matrix(struct wmediumd *ctx, double snr,
+					     unsigned int rate_idx, u32 freq,
+					     int frame_len, struct station *src,
+					     struct station *dst)
+{
+	int signal_idx;
+
+	signal_idx = snr + NOISE_LEVEL - ctx->per_matrix_signal_min;
+
+	if (signal_idx < 0)
+		return 1.0;
+
+	if (signal_idx >= ctx->per_matrix_row_num)
+		return 0.0;
+
+	if (freq > 5000)
+		rate_idx += 4;
+
+	if (rate_idx >= rate_len)
+		return 1.0;
+
+	return ctx->per_matrix[signal_idx * rate_len + rate_idx];
+}
+
+int read_per_file(struct wmediumd *ctx, const char *file_name)
+{
+	FILE *fp;
+	char line[256];
+	int signal;
+	size_t i;
+	float *temp;
+
+	fp = fopen(file_name, "r");
+	if (fp == NULL) {
+		w_flogf(ctx, LOG_ERR, stderr,
+			"fopen failed %s\n", strerror(errno));
+		return EXIT_FAILURE;
+	}
+
+	ctx->per_matrix_signal_min = 1000;
+	while (fscanf(fp, "%s", line) != EOF){
+		if (line[0] == '#') {
+			if (fgets(line, sizeof(line), fp) == NULL) {
+				w_flogf(ctx, LOG_ERR, stderr,
+					"Failed to read comment line\n");
+				return EXIT_FAILURE;
+			}
+			continue;
+		}
+
+		signal = atoi(line);
+		if (ctx->per_matrix_signal_min > signal)
+			ctx->per_matrix_signal_min = signal;
+
+		if (signal - ctx->per_matrix_signal_min < 0) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"%s: invalid signal=%d\n", __func__, signal);
+			return EXIT_FAILURE;
+		}
+
+		temp = realloc(ctx->per_matrix, sizeof(float) * rate_len *
+				++ctx->per_matrix_row_num);
+		if (temp == NULL) {
+			w_flogf(ctx, LOG_ERR, stderr,
+				"Out of memory(PER file)\n");
+			return EXIT_FAILURE;
+		}
+		ctx->per_matrix = temp;
+
+		for (i = 0; i < rate_len; i++) {
+			if (fscanf(fp, "%f", &ctx->per_matrix[
+				(signal - ctx->per_matrix_signal_min) *
+				rate_len + i]) == EOF) {
+				w_flogf(ctx, LOG_ERR, stderr,
+					"Not enough rate found\n");
+				return EXIT_FAILURE;
+			}
+		}
+	}
+
+	ctx->get_error_prob = get_error_prob_from_per_matrix;
+
+	return EXIT_SUCCESS;
+}
+
+int index_to_rate(size_t index, u32 freq)
+{
+	if (freq > 5000)
+		index += 4;
+
+	if (index >= rate_len)
+		index = rate_len - 1;
+
+	return rateset[index].mbps;
+}
diff --git a/wmediumd/wmediumd.c b/wmediumd/wmediumd.c
new file mode 100644
index 0000000..3b4aed3
--- /dev/null
+++ b/wmediumd/wmediumd.c
@@ -0,0 +1,1438 @@
+/*
+ *	wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ *	Copyright (c) 2011 cozybit Inc.
+ *	Copyright (C) 2020 Intel Corporation
+ *
+ *	Author:	Javier Lopez	<jlopex@cozybit.com>
+ *		Javier Cardona	<javier@cozybit.com>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version 2
+ *	of the License, or (at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *	02110-1301, USA.
+ */
+
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
+#include <assert.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <signal.h>
+#include <math.h>
+#include <sys/timerfd.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <endian.h>
+#include <usfstl/loop.h>
+#include <usfstl/sched.h>
+#include <usfstl/schedctrl.h>
+#include <usfstl/vhost.h>
+#include <usfstl/uds.h>
+
+#include "wmediumd.h"
+#include "ieee80211.h"
+#include "config.h"
+#include "api.h"
+
+USFSTL_SCHEDULER(scheduler);
+
+static void wmediumd_deliver_frame(struct usfstl_job *job);
+
+enum {
+	HWSIM_VQ_TX,
+	HWSIM_VQ_RX,
+	HWSIM_NUM_VQS,
+};
+
+static inline int div_round(int a, int b)
+{
+	return (a + b - 1) / b;
+}
+
+static inline int pkt_duration(int len, int rate)
+{
+	/* preamble + signal + t_sym * n_sym, rate in 100 kbps */
+	return 16 + 4 + 4 * div_round((16 + 8 * len + 6) * 10, 4 * rate);
+}
+
+int w_logf(struct wmediumd *ctx, u8 level, const char *format, ...)
+{
+	va_list(args);
+	va_start(args, format);
+	if (ctx->log_lvl >= level) {
+		return vprintf(format, args);
+	}
+	return -1;
+}
+
+int w_flogf(struct wmediumd *ctx, u8 level, FILE *stream, const char *format, ...)
+{
+	va_list(args);
+	va_start(args, format);
+	if (ctx->log_lvl >= level) {
+		return vfprintf(stream, format, args);
+	}
+	return -1;
+}
+
+static void wqueue_init(struct wqueue *wqueue, int cw_min, int cw_max)
+{
+	INIT_LIST_HEAD(&wqueue->frames);
+	wqueue->cw_min = cw_min;
+	wqueue->cw_max = cw_max;
+}
+
+void station_init_queues(struct station *station)
+{
+	wqueue_init(&station->queues[IEEE80211_AC_BK], 15, 1023);
+	wqueue_init(&station->queues[IEEE80211_AC_BE], 15, 1023);
+	wqueue_init(&station->queues[IEEE80211_AC_VI], 7, 15);
+	wqueue_init(&station->queues[IEEE80211_AC_VO], 3, 7);
+}
+
+static inline bool frame_has_a4(struct frame *frame)
+{
+	struct ieee80211_hdr *hdr = (void *)frame->data;
+
+	return (hdr->frame_control[1] & (FCTL_TODS | FCTL_FROMDS)) ==
+		(FCTL_TODS | FCTL_FROMDS);
+}
+
+static inline bool frame_is_mgmt(struct frame *frame)
+{
+	struct ieee80211_hdr *hdr = (void *)frame->data;
+
+	return (hdr->frame_control[0] & FCTL_FTYPE) == FTYPE_MGMT;
+}
+
+static inline bool frame_is_data(struct frame *frame)
+{
+	struct ieee80211_hdr *hdr = (void *)frame->data;
+
+	return (hdr->frame_control[0] & FCTL_FTYPE) == FTYPE_DATA;
+}
+
+static inline bool frame_is_data_qos(struct frame *frame)
+{
+	struct ieee80211_hdr *hdr = (void *)frame->data;
+
+	return (hdr->frame_control[0] & (FCTL_FTYPE | STYPE_QOS_DATA)) ==
+		(FTYPE_DATA | STYPE_QOS_DATA);
+}
+
+static inline u8 *frame_get_qos_ctl(struct frame *frame)
+{
+	struct ieee80211_hdr *hdr = (void *)frame->data;
+
+	if (frame_has_a4(frame))
+		return (u8 *)hdr + 30;
+	else
+		return (u8 *)hdr + 24;
+}
+
+static enum ieee80211_ac_number frame_select_queue_80211(struct frame *frame)
+{
+	u8 *p;
+	int priority;
+
+	if (!frame_is_data(frame))
+		return IEEE80211_AC_VO;
+
+	if (!frame_is_data_qos(frame))
+		return IEEE80211_AC_BE;
+
+	p = frame_get_qos_ctl(frame);
+	priority = *p & QOS_CTL_TAG1D_MASK;
+
+	return ieee802_1d_to_ac[priority];
+}
+
+static double dBm_to_milliwatt(int decibel_intf)
+{
+#define INTF_LIMIT (31)
+	int intf_diff = NOISE_LEVEL - decibel_intf;
+
+	if (intf_diff >= INTF_LIMIT)
+		return 0.001;
+
+	if (intf_diff <= -INTF_LIMIT)
+		return 1000.0;
+
+	return pow(10.0, -intf_diff / 10.0);
+}
+
+static double milliwatt_to_dBm(double value)
+{
+	return 10.0 * log10(value);
+}
+
+static int set_interference_duration(struct wmediumd *ctx, int src_idx,
+				     int duration, int signal)
+{
+	int i;
+
+	if (!ctx->intf)
+		return 0;
+
+	if (signal >= CCA_THRESHOLD)
+		return 0;
+
+	for (i = 0; i < ctx->num_stas; i++) {
+		ctx->intf[ctx->num_stas * src_idx + i].duration += duration;
+		// use only latest value
+		ctx->intf[ctx->num_stas * src_idx + i].signal = signal;
+	}
+
+	return 1;
+}
+
+static int get_signal_offset_by_interference(struct wmediumd *ctx, int src_idx,
+					     int dst_idx)
+{
+	int i;
+	double intf_power;
+
+	if (!ctx->intf)
+		return 0;
+
+	intf_power = 0.0;
+	for (i = 0; i < ctx->num_stas; i++) {
+		if (i == src_idx || i == dst_idx)
+			continue;
+		if (drand48() < ctx->intf[i * ctx->num_stas + dst_idx].prob_col)
+			intf_power += dBm_to_milliwatt(
+				ctx->intf[i * ctx->num_stas + dst_idx].signal);
+	}
+
+	if (intf_power <= 1.0)
+		return 0;
+
+	return (int)(milliwatt_to_dBm(intf_power) + 0.5);
+}
+
+static bool is_multicast_ether_addr(const u8 *addr)
+{
+	return 0x01 & addr[0];
+}
+
+static struct station *get_station_by_addr(struct wmediumd *ctx, u8 *addr)
+{
+	struct station *station;
+
+	list_for_each_entry(station, &ctx->stations, list) {
+		if (memcmp(station->addr, addr, ETH_ALEN) == 0)
+			return station;
+	}
+	return NULL;
+}
+
+static bool station_has_addr(struct station *station, const u8 *addr)
+{
+	unsigned int i;
+
+	if (memcmp(station->addr, addr, ETH_ALEN) == 0)
+		return true;
+
+	for (i = 0; i < station->n_addrs; i++) {
+		if (memcmp(station->addrs[i].addr, addr, ETH_ALEN) == 0)
+			return true;
+	}
+
+	return false;
+}
+
+static struct station *get_station_by_used_addr(struct wmediumd *ctx, u8 *addr)
+{
+	struct station *station;
+
+	list_for_each_entry(station, &ctx->stations, list) {
+		if (station_has_addr(station, addr))
+			return station;
+	}
+	return NULL;
+}
+
+static void wmediumd_wait_for_client_ack(struct wmediumd *ctx,
+					 struct client *client)
+{
+	client->wait_for_ack = true;
+
+	while (client->wait_for_ack)
+		usfstl_loop_wait_and_handle();
+}
+
+static void wmediumd_notify_frame_start(struct usfstl_job *job)
+{
+	struct frame *frame = container_of(job, struct frame, start_job);
+	struct wmediumd *ctx = job->data;
+	struct client *client, *tmp;
+	struct {
+		struct wmediumd_message_header hdr;
+		struct wmediumd_tx_start start;
+	} __attribute__((packed)) msg = {
+		.hdr.type = WMEDIUMD_MSG_TX_START,
+		.hdr.data_len = sizeof(msg.start),
+		.start.freq = frame->freq,
+	};
+
+	if (ctx->ctrl)
+		usfstl_sched_ctrl_sync_to(ctx->ctrl);
+
+	list_for_each_entry_safe(client, tmp, &ctx->clients, list) {
+		if (!(client->flags & WMEDIUMD_CTL_NOTIFY_TX_START))
+			continue;
+
+		if (client == frame->src)
+			msg.start.cookie = frame->cookie;
+		else
+			msg.start.cookie = 0;
+
+		/* must be API socket since flags cannot otherwise be set */
+		assert(client->type == CLIENT_API_SOCK);
+
+		write(client->loop.fd, &msg, sizeof(msg));
+
+		wmediumd_wait_for_client_ack(ctx, client);
+	}
+}
+
+static void log2pcap(struct wmediumd *ctx, struct frame *frame, uint64_t ts)
+{
+	struct {
+		uint8_t it_version;
+		uint8_t it_pad;
+		uint16_t it_len;
+		uint32_t it_present;
+		struct {
+			uint16_t freq, flags;
+		} channel;
+		uint8_t signal;
+	} __attribute__((packed)) radiotap_hdr = {
+		.it_len = htole16(sizeof(radiotap_hdr)),
+		.it_present = htole32(1 << 3 /* channel */ |
+				      1 << 5 /* signal dBm */),
+		.channel.freq = htole16(frame->freq),
+		.signal = frame->signal,
+	};
+	struct {
+		uint32_t type, blocklen, ifidx, ts_hi, ts_lo, caplen, pktlen;
+	} __attribute__((packed)) blockhdr = {
+		.type = 6,
+		.ts_hi = ts / (1ULL << 32),
+		.ts_lo = ts,
+		.caplen = frame->data_len + sizeof(radiotap_hdr),
+		.pktlen = frame->data_len + sizeof(radiotap_hdr),
+	};
+	static const uint8_t pad[3];
+	uint32_t sz, align;
+
+	sz = blockhdr.caplen + sizeof(blockhdr) + sizeof(uint32_t);
+	blockhdr.blocklen = (sz + 3) & ~3;
+	align = blockhdr.blocklen - sz;
+
+	fwrite(&blockhdr, sizeof(blockhdr), 1, ctx->pcap_file);
+	fwrite(&radiotap_hdr, sizeof(radiotap_hdr), 1, ctx->pcap_file);
+	fwrite(frame->data, frame->data_len, 1, ctx->pcap_file);
+	fwrite(pad, align, 1, ctx->pcap_file);
+	fwrite(&blockhdr.blocklen, sizeof(blockhdr.blocklen), 1, ctx->pcap_file);
+	fflush(ctx->pcap_file);
+}
+
+static void queue_frame(struct wmediumd *ctx, struct station *station,
+			struct frame *frame)
+{
+	struct ieee80211_hdr *hdr = (void *)frame->data;
+	u8 *dest = hdr->addr1;
+	uint64_t target;
+	struct wqueue *queue;
+	struct frame *tail;
+	struct station *tmpsta, *deststa;
+	int send_time;
+	int cw;
+	double error_prob;
+	bool is_acked = false;
+	bool noack = false;
+	int i, j;
+	int rate_idx;
+	int ac;
+
+	/* TODO configure phy parameters */
+	int slot_time = 9;
+	int sifs = 16;
+	int difs = 2 * slot_time + sifs;
+
+	int retries = 0;
+
+	int ack_time_usec = pkt_duration(14, index_to_rate(0, frame->freq)) +
+	                    sifs;
+
+	/*
+	 * To determine a frame's expiration time, we compute the
+	 * number of retries we might have to make due to radio conditions
+	 * or contention, and add backoff time accordingly.  To that, we
+	 * add the expiration time of the previous frame in the queue.
+	 */
+
+	ac = frame_select_queue_80211(frame);
+	queue = &station->queues[ac];
+
+	/* try to "send" this frame at each of the rates in the rateset */
+	send_time = 0;
+	cw = queue->cw_min;
+
+	int snr = SNR_DEFAULT;
+
+	if (is_multicast_ether_addr(dest)) {
+		deststa = NULL;
+	} else {
+		deststa = get_station_by_used_addr(ctx, dest);
+		if (deststa) {
+			snr = ctx->get_link_snr(ctx, station, deststa) -
+				get_signal_offset_by_interference(ctx,
+					station->index, deststa->index);
+			snr += ctx->get_fading_signal(ctx);
+		}
+	}
+	frame->signal = snr + NOISE_LEVEL;
+
+	noack = is_multicast_ether_addr(dest);
+
+	double choice = drand48();
+
+	for (i = 0; i < frame->tx_rates_count && !is_acked; i++) {
+
+		rate_idx = frame->tx_rates[i].idx;
+
+		/* no more rates in MRR */
+		if (rate_idx < 0)
+			break;
+
+		error_prob = ctx->get_error_prob(ctx, snr, rate_idx,
+						 frame->freq, frame->data_len,
+						 station, deststa);
+		for (j = 0; j < frame->tx_rates[i].count; j++) {
+			send_time += difs + pkt_duration(frame->data_len,
+				index_to_rate(rate_idx, frame->freq));
+
+			retries++;
+
+			/* skip ack/backoff/retries for noack frames */
+			if (noack) {
+				is_acked = true;
+				break;
+			}
+
+			/* TODO TXOPs */
+
+			/* backoff */
+			if (j > 0) {
+				send_time += (cw * slot_time) / 2;
+				cw = (cw << 1) + 1;
+				if (cw > queue->cw_max)
+					cw = queue->cw_max;
+			}
+
+			send_time += ack_time_usec;
+
+			if (choice > error_prob) {
+				is_acked = true;
+				break;
+			}
+
+			if (!use_fixed_random_value(ctx))
+				choice = drand48();
+		}
+	}
+
+	if (is_acked) {
+		frame->tx_rates[i-1].count = j + 1;
+		for (; i < frame->tx_rates_count; i++) {
+			frame->tx_rates[i].idx = -1;
+			frame->tx_rates[i].count = -1;
+		}
+		frame->flags |= HWSIM_TX_STAT_ACK;
+	}
+
+	/*
+	 * delivery time starts after any equal or higher prio frame
+	 * (or now, if none).
+	 */
+	target = scheduler.current_time;
+	for (i = 0; i <= ac; i++) {
+		list_for_each_entry(tmpsta, &ctx->stations, list) {
+			tail = list_last_entry_or_null(&tmpsta->queues[i].frames,
+						       struct frame, list);
+			if (tail && target < tail->job.start)
+				target = tail->job.start;
+		}
+	}
+
+	if (ctx->pcap_file) {
+		log2pcap(ctx, frame, target);
+
+		if (is_acked && !noack) {
+			struct {
+				struct frame frame;
+				uint16_t fc;
+				uint16_t dur;
+				uint8_t ra[6];
+			} __attribute__((packed, aligned(8))) ack = {
+				.fc = htole16(0xd4),
+				.dur = htole16(ack_time_usec),
+			};
+
+			memcpy(&ack.frame, frame, sizeof(ack.frame));
+			ack.frame.data_len = 10;
+			memcpy(ack.ra, frame->data + 10, 6);
+
+			log2pcap(ctx, &ack.frame,
+				 target + send_time - ack_time_usec);
+		}
+	}
+
+	target += send_time;
+
+	frame->duration = send_time;
+	frame->src = station->client;
+
+	if (ctx->need_start_notify) {
+		frame->start_job.start = target - send_time;
+		frame->start_job.callback = wmediumd_notify_frame_start;
+		frame->start_job.data = ctx;
+		frame->start_job.name = "frame-start";
+		usfstl_sched_add_job(&scheduler, &frame->start_job);
+	}
+
+	frame->job.start = target;
+	frame->job.callback = wmediumd_deliver_frame;
+	frame->job.data = ctx;
+	frame->job.name = "frame";
+	usfstl_sched_add_job(&scheduler, &frame->job);
+	list_add_tail(&frame->list, &queue->frames);
+}
+
+static void wmediumd_send_to_client(struct wmediumd *ctx,
+				    struct client *client,
+				    struct nl_msg *msg)
+{
+	struct wmediumd_message_header hdr;
+	size_t len;
+	int ret;
+
+	switch (client->type) {
+	case CLIENT_NETLINK:
+		ret = nl_send_auto_complete(ctx->sock, msg);
+		if (ret < 0)
+			w_logf(ctx, LOG_ERR, "%s: nl_send_auto failed\n", __func__);
+		break;
+	case CLIENT_VHOST_USER:
+		len = nlmsg_total_size(nlmsg_datalen(nlmsg_hdr(msg)));
+		usfstl_vhost_user_dev_notify(client->dev, HWSIM_VQ_RX,
+					     (void *)nlmsg_hdr(msg), len);
+		break;
+	case CLIENT_API_SOCK:
+		len = nlmsg_total_size(nlmsg_datalen(nlmsg_hdr(msg)));
+		hdr.type = WMEDIUMD_MSG_NETLINK;
+		hdr.data_len = len;
+		write(client->loop.fd, &hdr, sizeof(hdr));
+		write(client->loop.fd, (void *)nlmsg_hdr(msg), len);
+		wmediumd_wait_for_client_ack(ctx, client);
+		break;
+	}
+}
+
+static void wmediumd_remove_client(struct wmediumd *ctx, struct client *client)
+{
+	struct frame *frame, *tmp;
+	struct wqueue *queue;
+	struct station *station;
+	int ac;
+
+	list_for_each_entry(station, &ctx->stations, list) {
+		if (station->client == client)
+			station->client = NULL;
+	}
+
+	list_for_each_entry(station, &ctx->stations, list) {
+		for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+			queue = &station->queues[ac];
+			list_for_each_entry_safe(frame, tmp, &queue->frames,
+						 list) {
+				if (frame->src == client) {
+					list_del(&frame->list);
+					usfstl_sched_del_job(&frame->job);
+					free(frame);
+				}
+			}
+		}
+	}
+
+	if (!list_empty(&client->list))
+		list_del(&client->list);
+	list_add(&client->list, &ctx->clients_to_free);
+
+	if (client->flags & WMEDIUMD_CTL_NOTIFY_TX_START)
+		ctx->need_start_notify--;
+
+	client->wait_for_ack = false;
+}
+
+/*
+ * Report transmit status to the kernel.
+ */
+static void send_tx_info_frame_nl(struct wmediumd *ctx, struct frame *frame)
+{
+	struct nl_msg *msg;
+
+	msg = nlmsg_alloc();
+	if (!msg) {
+		w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n");
+		return;
+	}
+
+	if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id,
+			0, NLM_F_REQUEST, HWSIM_CMD_TX_INFO_FRAME,
+			VERSION_NR) == NULL) {
+		w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__);
+		goto out;
+	}
+
+	if (nla_put(msg, HWSIM_ATTR_ADDR_TRANSMITTER, ETH_ALEN,
+		    frame->sender->hwaddr) ||
+	    nla_put_u32(msg, HWSIM_ATTR_FLAGS, frame->flags) ||
+	    nla_put_u32(msg, HWSIM_ATTR_SIGNAL, frame->signal) ||
+	    nla_put(msg, HWSIM_ATTR_TX_INFO,
+		    frame->tx_rates_count * sizeof(struct hwsim_tx_rate),
+		    frame->tx_rates) ||
+	    nla_put_u64(msg, HWSIM_ATTR_COOKIE, frame->cookie)) {
+		w_logf(ctx, LOG_ERR, "%s: Failed to fill a payload\n", __func__);
+		goto out;
+	}
+
+	if (ctx->ctrl)
+		usfstl_sched_ctrl_sync_to(ctx->ctrl);
+	wmediumd_send_to_client(ctx, frame->src, msg);
+
+out:
+	nlmsg_free(msg);
+}
+
+/*
+ * Send a data frame to the kernel for reception at a specific radio.
+ */
+static void send_cloned_frame_msg(struct wmediumd *ctx, struct client *src,
+				  struct station *dst, u8 *data, int data_len,
+				  int rate_idx, int signal, int freq,
+				  uint64_t cookie)
+{
+	struct client *client, *tmp;
+	struct nl_msg *msg, *cmsg = NULL;
+
+	msg = nlmsg_alloc();
+	if (!msg) {
+		w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n");
+		return;
+	}
+
+	if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id,
+			0, NLM_F_REQUEST, HWSIM_CMD_FRAME,
+			VERSION_NR) == NULL) {
+		w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__);
+		goto out;
+	}
+
+	if (nla_put(msg, HWSIM_ATTR_ADDR_RECEIVER, ETH_ALEN,
+		    dst->hwaddr) ||
+	    nla_put(msg, HWSIM_ATTR_FRAME, data_len, data) ||
+	    nla_put_u32(msg, HWSIM_ATTR_RX_RATE, 1) ||
+	    nla_put_u32(msg, HWSIM_ATTR_FREQ, freq) ||
+	    nla_put_u32(msg, HWSIM_ATTR_SIGNAL, signal)) {
+		w_logf(ctx, LOG_ERR, "%s: Failed to fill a payload\n", __func__);
+		goto out;
+	}
+
+	w_logf(ctx, LOG_DEBUG, "cloned msg dest " MAC_FMT " (radio: " MAC_FMT ") len %d\n",
+		   MAC_ARGS(dst->addr), MAC_ARGS(dst->hwaddr), data_len);
+
+	if (ctx->ctrl)
+		usfstl_sched_ctrl_sync_to(ctx->ctrl);
+
+	list_for_each_entry_safe(client, tmp, &ctx->clients, list) {
+		if (client->flags & WMEDIUMD_CTL_RX_ALL_FRAMES) {
+			if (src == client && !cmsg) {
+				struct nlmsghdr *nlh = nlmsg_hdr(msg);
+
+				cmsg = nlmsg_inherit(nlh);
+				nlmsg_append(cmsg, nlmsg_data(nlh), nlmsg_datalen(nlh), 0);
+				assert(nla_put_u64(cmsg, HWSIM_ATTR_COOKIE, cookie) == 0);
+			}
+			wmediumd_send_to_client(ctx, client,
+						src == client ? cmsg : msg);
+		} else if (!dst->client || dst->client == client) {
+			wmediumd_send_to_client(ctx, client, msg);
+		}
+	}
+
+out:
+	nlmsg_free(msg);
+	if (cmsg)
+		nlmsg_free(cmsg);
+}
+
+static void wmediumd_deliver_frame(struct usfstl_job *job)
+{
+	struct wmediumd *ctx = job->data;
+	struct frame *frame = container_of(job, struct frame, job);
+	struct ieee80211_hdr *hdr = (void *) frame->data;
+	struct station *station;
+	u8 *dest = hdr->addr1;
+	u8 *src = frame->sender->addr;
+
+	list_del(&frame->list);
+
+	if (frame->flags & HWSIM_TX_STAT_ACK) {
+		/* rx the frame on the dest interface */
+		list_for_each_entry(station, &ctx->stations, list) {
+			if (memcmp(src, station->addr, ETH_ALEN) == 0)
+				continue;
+
+			if (is_multicast_ether_addr(dest)) {
+				int snr, rate_idx, signal;
+				double error_prob;
+
+				/*
+				 * we may or may not receive this based on
+				 * reverse link from sender -- check for
+				 * each receiver.
+				 */
+				snr = ctx->get_link_snr(ctx, frame->sender,
+							station);
+				snr += ctx->get_fading_signal(ctx);
+				signal = snr + NOISE_LEVEL;
+				if (signal < CCA_THRESHOLD)
+					continue;
+
+				if (set_interference_duration(ctx,
+					frame->sender->index, frame->duration,
+					signal))
+					continue;
+
+				snr -= get_signal_offset_by_interference(ctx,
+					frame->sender->index, station->index);
+				rate_idx = frame->tx_rates[0].idx;
+				error_prob = ctx->get_error_prob(ctx,
+					(double)snr, rate_idx, frame->freq,
+					frame->data_len, frame->sender,
+					station);
+
+				if (drand48() <= error_prob) {
+					w_logf(ctx, LOG_INFO, "Dropped mcast from "
+						   MAC_FMT " to " MAC_FMT " at receiver\n",
+						   MAC_ARGS(src), MAC_ARGS(station->addr));
+					continue;
+				}
+
+				send_cloned_frame_msg(ctx, frame->sender->client,
+						      station,
+						      frame->data,
+						      frame->data_len,
+						      1, signal,
+						      frame->freq,
+						      frame->cookie);
+
+			} else if (station_has_addr(station, dest)) {
+				if (set_interference_duration(ctx,
+					frame->sender->index, frame->duration,
+					frame->signal))
+					continue;
+
+				send_cloned_frame_msg(ctx, frame->sender->client,
+						      station,
+						      frame->data,
+						      frame->data_len,
+						      1, frame->signal,
+						      frame->freq,
+						      frame->cookie);
+			}
+		}
+	} else
+		set_interference_duration(ctx, frame->sender->index,
+					  frame->duration, frame->signal);
+
+	send_tx_info_frame_nl(ctx, frame);
+
+	free(frame);
+}
+
+static void wmediumd_intf_update(struct usfstl_job *job)
+{
+	struct wmediumd *ctx = job->data;
+	int i, j;
+
+	for (i = 0; i < ctx->num_stas; i++)
+		for (j = 0; j < ctx->num_stas; j++) {
+			if (i == j)
+				continue;
+			// probability is used for next calc
+			ctx->intf[i * ctx->num_stas + j].prob_col =
+				ctx->intf[i * ctx->num_stas + j].duration /
+				(double)10000;
+			ctx->intf[i * ctx->num_stas + j].duration = 0;
+		}
+
+	job->start += 10000;
+	usfstl_sched_add_job(&scheduler, job);
+}
+
+static
+int nl_err_cb(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg)
+{
+	struct genlmsghdr *gnlh = nlmsg_data(&nlerr->msg);
+	struct wmediumd *ctx = arg;
+
+	w_flogf(ctx, LOG_ERR, stderr, "nl: cmd %d, seq %d: %s\n", gnlh->cmd,
+			nlerr->msg.nlmsg_seq, strerror(abs(nlerr->error)));
+
+	return NL_SKIP;
+}
+
+/*
+ * Handle events from the kernel.  Process CMD_FRAME events and queue them
+ * for later delivery with the scheduler.
+ */
+static void _process_messages(struct nl_msg *msg,
+			      struct wmediumd *ctx,
+			      struct client *client)
+{
+	struct nlattr *attrs[HWSIM_ATTR_MAX+1];
+	/* netlink header */
+	struct nlmsghdr *nlh = nlmsg_hdr(msg);
+	/* generic netlink header*/
+	struct genlmsghdr *gnlh = nlmsg_data(nlh);
+
+	struct station *sender;
+	struct frame *frame;
+	struct ieee80211_hdr *hdr;
+	u8 *src, *hwaddr, *addr;
+	void *new;
+	unsigned int i;
+
+	genlmsg_parse(nlh, 0, attrs, HWSIM_ATTR_MAX, NULL);
+
+	switch (gnlh->cmd) {
+	case HWSIM_CMD_FRAME:
+		if (attrs[HWSIM_ATTR_ADDR_TRANSMITTER]) {
+			hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]);
+
+			unsigned int data_len =
+				nla_len(attrs[HWSIM_ATTR_FRAME]);
+			char *data = (char *)nla_data(attrs[HWSIM_ATTR_FRAME]);
+			unsigned int flags =
+				nla_get_u32(attrs[HWSIM_ATTR_FLAGS]);
+			unsigned int tx_rates_len =
+				nla_len(attrs[HWSIM_ATTR_TX_INFO]);
+			struct hwsim_tx_rate *tx_rates =
+				(struct hwsim_tx_rate *)
+				nla_data(attrs[HWSIM_ATTR_TX_INFO]);
+			u64 cookie = nla_get_u64(attrs[HWSIM_ATTR_COOKIE]);
+			u32 freq;
+
+			freq = attrs[HWSIM_ATTR_FREQ] ?
+				nla_get_u32(attrs[HWSIM_ATTR_FREQ]) : 2412;
+
+			hdr = (struct ieee80211_hdr *)data;
+			src = hdr->addr2;
+
+			if (data_len < 6 + 6 + 4)
+				return;
+
+			sender = get_station_by_addr(ctx, hwaddr);
+			if (!sender) {
+				sender = get_station_by_used_addr(ctx, src);
+				if (!sender) {
+					w_flogf(ctx, LOG_ERR, stderr,
+						"Unable to find sender station by src=" MAC_FMT " nor hwaddr=" MAC_FMT "\n",
+						MAC_ARGS(src), MAC_ARGS(hwaddr));
+					return;
+				}
+				memcpy(sender->hwaddr, hwaddr, ETH_ALEN);
+			}
+
+			if (!sender->client)
+				sender->client = client;
+
+			frame = calloc(1, sizeof(*frame) + data_len);
+			if (!frame)
+				return;
+
+			memcpy(frame->data, data, data_len);
+			frame->data_len = data_len;
+			frame->flags = flags;
+			frame->cookie = cookie;
+			frame->freq = freq;
+			frame->sender = sender;
+			frame->tx_rates_count =
+				tx_rates_len / sizeof(struct hwsim_tx_rate);
+			memcpy(frame->tx_rates, tx_rates,
+			       min(tx_rates_len, sizeof(frame->tx_rates)));
+			queue_frame(ctx, sender, frame);
+		}
+		break;
+	case HWSIM_CMD_ADD_MAC_ADDR:
+		if (!attrs[HWSIM_ATTR_ADDR_TRANSMITTER] ||
+		    !attrs[HWSIM_ATTR_ADDR_RECEIVER])
+			break;
+		hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]);
+		addr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_RECEIVER]);
+		sender = get_station_by_addr(ctx, hwaddr);
+		if (!sender)
+			break;
+		for (i = 0; i < sender->n_addrs; i++) {
+			if (memcmp(sender->addrs[i].addr, addr, ETH_ALEN) == 0)
+				return;
+		}
+		new = realloc(sender->addrs, ETH_ALEN * (sender->n_addrs + 1));
+		if (!new)
+			break;
+		sender->addrs = new;
+		memcpy(sender->addrs[sender->n_addrs].addr, addr, ETH_ALEN);
+		sender->n_addrs += 1;
+		break;
+	case HWSIM_CMD_DEL_MAC_ADDR:
+		if (!attrs[HWSIM_ATTR_ADDR_TRANSMITTER] ||
+		    !attrs[HWSIM_ATTR_ADDR_RECEIVER])
+			break;
+		hwaddr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_TRANSMITTER]);
+		addr = (u8 *)nla_data(attrs[HWSIM_ATTR_ADDR_RECEIVER]);
+		sender = get_station_by_addr(ctx, hwaddr);
+		if (!sender)
+			break;
+		for (i = 0; i < sender->n_addrs; i++) {
+			if (memcmp(sender->addrs[i].addr, addr, ETH_ALEN))
+				continue;
+			sender->n_addrs -= 1;
+			memmove(sender->addrs[i].addr,
+				sender->addrs[sender->n_addrs].addr,
+				ETH_ALEN);
+			break;
+		}
+		break;
+	}
+}
+
+static int process_messages_cb(struct nl_msg *msg, void *arg)
+{
+	struct wmediumd *ctx = arg;
+
+	_process_messages(msg, ctx, &ctx->nl_client);
+	return 0;
+}
+
+static void wmediumd_vu_connected(struct usfstl_vhost_user_dev *dev)
+{
+	struct wmediumd *ctx = dev->server->data;
+	struct client *client;
+
+	client = calloc(1, sizeof(*client));
+	dev->data = client;
+	client->type = CLIENT_VHOST_USER;
+	client->dev = dev;
+	list_add(&client->list, &ctx->clients);
+}
+
+static void wmediumd_vu_handle(struct usfstl_vhost_user_dev *dev,
+			       struct usfstl_vhost_user_buf *buf,
+			       unsigned int vring)
+{
+	struct nl_msg *nlmsg;
+	char data[4096];
+	size_t len;
+
+	len = iov_read(data, sizeof(data), buf->out_sg, buf->n_out_sg);
+
+	if (!nlmsg_ok((const struct nlmsghdr *)data, len))
+		return;
+	nlmsg = nlmsg_convert((struct nlmsghdr *)data);
+	if (!nlmsg)
+		return;
+
+	_process_messages(nlmsg, dev->server->data, dev->data);
+
+	nlmsg_free(nlmsg);
+}
+
+static void wmediumd_vu_disconnected(struct usfstl_vhost_user_dev *dev)
+{
+	struct client *client = dev->data;
+
+	dev->data = NULL;
+	wmediumd_remove_client(dev->server->data, client);
+}
+
+static const struct usfstl_vhost_user_ops wmediumd_vu_ops = {
+	.connected = wmediumd_vu_connected,
+	.handle = wmediumd_vu_handle,
+	.disconnected = wmediumd_vu_disconnected,
+};
+
+static void wmediumd_api_handler(struct usfstl_loop_entry *entry)
+{
+	struct client *client = container_of(entry, struct client, loop);
+	struct wmediumd *ctx = entry->data;
+	struct wmediumd_message_header hdr;
+	enum wmediumd_message response = WMEDIUMD_MSG_ACK;
+	struct wmediumd_message_control control = {};
+	struct nl_msg *nlmsg;
+	unsigned char *data;
+	ssize_t len;
+
+	len = read(entry->fd, &hdr, sizeof(hdr));
+	if (len != sizeof(hdr))
+		goto disconnect;
+
+	/* safety valve */
+	if (hdr.data_len > 1024 * 1024)
+		goto disconnect;
+
+	data = malloc(hdr.data_len);
+	if (!data)
+		goto disconnect;
+
+	len = read(entry->fd, data, hdr.data_len);
+	if (len != hdr.data_len)
+		goto disconnect;
+
+	if (client->wait_for_ack) {
+		assert(hdr.type == WMEDIUMD_MSG_ACK);
+		assert(hdr.data_len == 0);
+		client->wait_for_ack = false;
+		/* don't send a response to a response, of course */
+		return;
+	}
+
+	switch (hdr.type) {
+	case WMEDIUMD_MSG_REGISTER:
+		if (!list_empty(&client->list)) {
+			response = WMEDIUMD_MSG_INVALID;
+			break;
+		}
+		list_add(&client->list, &ctx->clients);
+		break;
+	case WMEDIUMD_MSG_UNREGISTER:
+		if (list_empty(&client->list)) {
+			response = WMEDIUMD_MSG_INVALID;
+			break;
+		}
+		list_del_init(&client->list);
+		break;
+	case WMEDIUMD_MSG_NETLINK:
+		if (ctx->ctrl)
+			usfstl_sched_ctrl_sync_from(ctx->ctrl);
+
+		if (!nlmsg_ok((const struct nlmsghdr *)data, len)) {
+			response = WMEDIUMD_MSG_INVALID;
+			break;
+		}
+
+		nlmsg = nlmsg_convert((struct nlmsghdr *)data);
+		if (!nlmsg)
+			break;
+
+		_process_messages(nlmsg, ctx, client);
+
+		nlmsg_free(nlmsg);
+		break;
+	case WMEDIUMD_MSG_SET_CONTROL:
+		/* copy what we get and understand, leave the rest zeroed */
+		memcpy(&control, data,
+		       min(sizeof(control), hdr.data_len));
+
+		if (client->flags & WMEDIUMD_CTL_NOTIFY_TX_START)
+			ctx->need_start_notify--;
+		if (control.flags & WMEDIUMD_CTL_NOTIFY_TX_START)
+			ctx->need_start_notify++;
+
+		client->flags = control.flags;
+		break;
+	case WMEDIUMD_MSG_ACK:
+		abort();
+	default:
+		response = WMEDIUMD_MSG_INVALID;
+		break;
+	}
+
+	/* return a response */
+	hdr.type = response;
+	hdr.data_len = 0;
+	len = write(entry->fd, &hdr, sizeof(hdr));
+	if (len != sizeof(hdr))
+		goto disconnect;
+
+	return;
+disconnect:
+	usfstl_loop_unregister(&client->loop);
+	wmediumd_remove_client(ctx, client);
+}
+
+static void wmediumd_api_connected(int fd, void *data)
+{
+	struct wmediumd *ctx = data;
+	struct client *client;
+
+	client = calloc(1, sizeof(*client));
+	client->type = CLIENT_API_SOCK;
+	client->loop.fd = fd;
+	client->loop.data = ctx;
+	client->loop.handler = wmediumd_api_handler;
+	usfstl_loop_register(&client->loop);
+	INIT_LIST_HEAD(&client->list);
+}
+
+/*
+ * Register with the kernel to start receiving new frames.
+ */
+static int send_register_msg(struct wmediumd *ctx)
+{
+	struct nl_sock *sock = ctx->sock;
+	struct nl_msg *msg;
+	int ret;
+
+	msg = nlmsg_alloc();
+	if (!msg) {
+		w_logf(ctx, LOG_ERR, "Error allocating new message MSG!\n");
+		return -1;
+	}
+
+	if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, ctx->family_id,
+			0, NLM_F_REQUEST, HWSIM_CMD_REGISTER,
+			VERSION_NR) == NULL) {
+		w_logf(ctx, LOG_ERR, "%s: genlmsg_put failed\n", __func__);
+		ret = -1;
+		goto out;
+	}
+
+	ret = nl_send_auto_complete(sock, msg);
+	if (ret < 0) {
+		w_logf(ctx, LOG_ERR, "%s: nl_send_auto failed\n", __func__);
+		ret = -1;
+		goto out;
+	}
+	ret = 0;
+
+out:
+	nlmsg_free(msg);
+	return ret;
+}
+
+static void sock_event_cb(struct usfstl_loop_entry *entry)
+{
+	struct wmediumd *ctx = entry->data;
+
+	nl_recvmsgs_default(ctx->sock);
+}
+
+/*
+ * Setup netlink socket and callbacks.
+ */
+static int init_netlink(struct wmediumd *ctx)
+{
+	struct nl_sock *sock;
+	int ret;
+
+	ctx->cb = nl_cb_alloc(NL_CB_CUSTOM);
+	if (!ctx->cb) {
+		w_logf(ctx, LOG_ERR, "Error allocating netlink callbacks\n");
+		return -1;
+	}
+
+	sock = nl_socket_alloc_cb(ctx->cb);
+	if (!sock) {
+		w_logf(ctx, LOG_ERR, "Error allocating netlink socket\n");
+		return -1;
+	}
+
+	ctx->sock = sock;
+
+	ret = genl_connect(sock);
+	if (ret < 0) {
+		w_logf(ctx, LOG_ERR, "Error connecting netlink socket ret=%d\n", ret);
+		return -1;
+	}
+
+	ctx->family_id = genl_ctrl_resolve(sock, "MAC80211_HWSIM");
+	if (ctx->family_id < 0) {
+		w_logf(ctx, LOG_ERR, "Family MAC80211_HWSIM not registered\n");
+		return -1;
+	}
+
+	nl_cb_set(ctx->cb, NL_CB_MSG_IN, NL_CB_CUSTOM, process_messages_cb, ctx);
+	nl_cb_err(ctx->cb, NL_CB_CUSTOM, nl_err_cb, ctx);
+
+	return 0;
+}
+
+/*
+ *	Print the CLI help
+ */
+static void print_help(int exval)
+{
+	printf("wmediumd v%s - a wireless medium simulator\n", VERSION_STR);
+	printf("wmediumd [-h] [-V] [-l LOG_LVL] [-x FILE] -c FILE \n\n");
+
+	printf("  -h              print this help and exit\n");
+	printf("  -V              print version and exit\n\n");
+
+	printf("  -l LOG_LVL      set the logging level\n");
+	printf("                  LOG_LVL: RFC 5424 severity, values 0 - 7\n");
+	printf("                  >= 3: errors are logged\n");
+	printf("                  >= 5: startup msgs are logged\n");
+	printf("                  >= 6: dropped packets are logged (default)\n");
+	printf("                  == 7: all packets will be logged\n");
+	printf("  -c FILE         set input config file\n");
+	printf("  -x FILE         set input PER file\n");
+	printf("  -t socket       set the time control socket\n");
+	printf("  -u socket       expose vhost-user socket, don't use netlink\n");
+	printf("  -a socket       expose wmediumd API socket\n");
+	printf("  -n              force netlink use even with vhost-user\n");
+	printf("  -p FILE         log packets to pcapng file FILE\n");
+
+	exit(exval);
+}
+
+static void init_pcapng(struct wmediumd *ctx, const char *filename)
+{
+	struct {
+		uint32_t type, blocklen, byte_order;
+		uint16_t ver_maj, ver_min;
+		uint64_t seclen;
+		uint32_t blocklen2;
+	} __attribute__((packed)) blockhdr = {
+		.type = 0x0A0D0D0A,
+		.blocklen = sizeof(blockhdr),
+		.byte_order = 0x1A2B3C4D,
+		.ver_maj = 1,
+		.ver_min = 0,
+		.seclen = -1,
+		.blocklen2 = sizeof(blockhdr),
+	};
+	struct {
+		uint32_t type, blocklen;
+		uint16_t linktype, reserved;
+		uint32_t snaplen;
+		struct {
+			uint16_t code, len;
+			uint8_t val, pad[3];
+		} opt_if_tsresol;
+		struct {
+			uint16_t code, len;
+		} opt_endofopt;
+		uint32_t blocklen2;
+	} __attribute__((packed)) idb = {
+		.type = 1,
+		.blocklen = sizeof(idb),
+		.linktype = 127, // radiotap
+		.snaplen = -1,
+		.opt_if_tsresol.code = 9,
+		.opt_if_tsresol.len = 1,
+		.opt_if_tsresol.val = 6, // usec
+		.blocklen2 = sizeof(idb),
+	};
+
+	if (!filename)
+		return;
+
+	ctx->pcap_file = fopen(filename, "w+");
+	fwrite(&blockhdr, sizeof(blockhdr), 1, ctx->pcap_file);
+	fwrite(&idb, sizeof(idb), 1, ctx->pcap_file);
+}
+
+int main(int argc, char *argv[])
+{
+	int opt;
+	struct wmediumd ctx = {};
+	char *config_file = NULL;
+	char *per_file = NULL;
+	const char *time_socket = NULL, *api_socket = NULL;
+	struct usfstl_sched_ctrl ctrl = {};
+	struct usfstl_vhost_user_server vusrv = {
+		.ops = &wmediumd_vu_ops,
+		.max_queues = HWSIM_NUM_VQS,
+		.input_queues = 1 << HWSIM_VQ_TX,
+		.protocol_features =
+			1ULL << VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS,
+		.data = &ctx,
+	};
+	bool use_netlink, force_netlink = false;
+
+	setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
+
+	if (argc == 1) {
+		fprintf(stderr, "This program needs arguments....\n\n");
+		print_help(EXIT_FAILURE);
+	}
+
+	ctx.log_lvl = 6;
+	unsigned long int parse_log_lvl;
+	char* parse_end_token;
+
+	while ((opt = getopt(argc, argv, "hVc:l:x:t:u:a:np:")) != -1) {
+		switch (opt) {
+		case 'h':
+			print_help(EXIT_SUCCESS);
+			break;
+		case 'V':
+			printf("wmediumd v%s - a wireless medium simulator "
+			       "for mac80211_hwsim\n", VERSION_STR);
+			exit(EXIT_SUCCESS);
+			break;
+		case 'c':
+			config_file = optarg;
+			break;
+		case 'x':
+			printf("Input packet error rate file: %s\n", optarg);
+			per_file = optarg;
+			break;
+		case ':':
+			printf("wmediumd: Error - Option `%c' "
+			       "needs a value\n\n", optopt);
+			print_help(EXIT_FAILURE);
+			break;
+		case 'l':
+			parse_log_lvl = strtoul(optarg, &parse_end_token, 10);
+			if ((parse_log_lvl == ULONG_MAX && errno == ERANGE) ||
+			     optarg == parse_end_token || parse_log_lvl > 7) {
+				printf("wmediumd: Error - Invalid RFC 5424 severity level: "
+							   "%s\n\n", optarg);
+				print_help(EXIT_FAILURE);
+			}
+			ctx.log_lvl = parse_log_lvl;
+			break;
+		case 't':
+			time_socket = optarg;
+			break;
+		case 'u':
+			vusrv.socket = optarg;
+			break;
+		case 'a':
+			api_socket = optarg;
+			break;
+		case 'n':
+			force_netlink = true;
+			break;
+		case 'p':
+			init_pcapng(&ctx, optarg);
+			break;
+		case '?':
+			printf("wmediumd: Error - No such option: "
+			       "`%c'\n\n", optopt);
+			print_help(EXIT_FAILURE);
+			break;
+		}
+
+	}
+
+	if (optind < argc)
+		print_help(EXIT_FAILURE);
+
+	if (!config_file) {
+		printf("%s: config file must be supplied\n", argv[0]);
+		print_help(EXIT_FAILURE);
+	}
+
+	w_logf(&ctx, LOG_NOTICE, "Input configuration file: %s\n", config_file);
+
+	INIT_LIST_HEAD(&ctx.stations);
+	INIT_LIST_HEAD(&ctx.clients);
+	INIT_LIST_HEAD(&ctx.clients_to_free);
+
+	if (load_config(&ctx, config_file, per_file))
+		return EXIT_FAILURE;
+
+	use_netlink = force_netlink || !vusrv.socket;
+
+	/* init netlink */
+	if (use_netlink && init_netlink(&ctx) < 0)
+		return EXIT_FAILURE;
+
+	if (ctx.intf) {
+		ctx.intf_job.start = 10000; // usec
+		ctx.intf_job.name = "interference update";
+		ctx.intf_job.data = &ctx;
+		ctx.intf_job.callback = wmediumd_intf_update;
+		usfstl_sched_add_job(&scheduler, &ctx.intf_job);
+	}
+
+	if (vusrv.socket)
+		usfstl_vhost_user_server_start(&vusrv);
+
+	if (use_netlink) {
+		ctx.nl_client.type = CLIENT_NETLINK;
+		list_add(&ctx.nl_client.list, &ctx.clients);
+
+		ctx.nl_loop.handler = sock_event_cb;
+		ctx.nl_loop.data = &ctx;
+		ctx.nl_loop.fd = nl_socket_get_fd(ctx.sock);
+		usfstl_loop_register(&ctx.nl_loop);
+
+		/* register for new frames */
+		if (send_register_msg(&ctx) == 0)
+			w_logf(&ctx, LOG_NOTICE, "REGISTER SENT!\n");
+	}
+
+	if (api_socket)
+		usfstl_uds_create(api_socket, wmediumd_api_connected, &ctx);
+
+	if (time_socket) {
+		usfstl_sched_ctrl_start(&ctrl, time_socket,
+				      1000 /* nsec per usec */,
+				      (uint64_t)-1 /* no ID */,
+				      &scheduler);
+		vusrv.scheduler = &scheduler;
+		vusrv.ctrl = &ctrl;
+		ctx.ctrl = &ctrl;
+	} else {
+		usfstl_sched_wallclock_init(&scheduler, 1000);
+	}
+
+	while (1) {
+		if (time_socket) {
+			usfstl_sched_next(&scheduler);
+		} else {
+			usfstl_sched_wallclock_wait_and_handle(&scheduler);
+
+			if (usfstl_sched_next_pending(&scheduler, NULL))
+				usfstl_sched_next(&scheduler);
+		}
+
+		while (!list_empty(&ctx.clients_to_free)) {
+			struct client *client;
+
+			client = list_first_entry(&ctx.clients_to_free,
+						  struct client, list);
+
+			list_del(&client->list);
+			free(client);
+		}
+	}
+
+	free(ctx.sock);
+	free(ctx.cb);
+	free(ctx.intf);
+	free(ctx.per_matrix);
+
+	return EXIT_SUCCESS;
+}
diff --git a/wmediumd/wmediumd.h b/wmediumd/wmediumd.h
new file mode 100644
index 0000000..4b7ca66
--- /dev/null
+++ b/wmediumd/wmediumd.h
@@ -0,0 +1,282 @@
+/*
+ *	wmediumd, wireless medium simulator for mac80211_hwsim kernel module
+ *	Copyright (c) 2011 cozybit Inc.
+ *	Copyright (C) 2020 Intel Corporation
+ *
+ *	Author:	Javier Lopez	<jlopex@cozybit.com>
+ *		Javier Cardona	<javier@cozybit.com>
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version 2
+ *	of the License, or (at your option) any later version.
+ *
+ *	This program is distributed in the hope that it will be useful,
+ *	but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *	GNU General Public License for more details.
+ *
+ *	You should have received a copy of the GNU General Public License
+ *	along with this program; if not, write to the Free Software
+ *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ *	02110-1301, USA.
+ */
+
+#ifndef WMEDIUMD_H_
+#define WMEDIUMD_H_
+
+#define HWSIM_TX_CTL_REQ_TX_STATUS	1
+#define HWSIM_TX_CTL_NO_ACK		(1 << 1)
+#define HWSIM_TX_STAT_ACK		(1 << 2)
+
+enum {
+	HWSIM_CMD_UNSPEC,
+	HWSIM_CMD_REGISTER,
+	HWSIM_CMD_FRAME,
+	HWSIM_CMD_TX_INFO_FRAME,
+	HWSIM_CMD_NEW_RADIO,
+	HWSIM_CMD_DEL_RADIO,
+	HWSIM_CMD_GET_RADIO,
+	HWSIM_CMD_ADD_MAC_ADDR,
+	HWSIM_CMD_DEL_MAC_ADDR,
+	__HWSIM_CMD_MAX,
+};
+#define HWSIM_CMD_MAX (_HWSIM_CMD_MAX - 1)
+
+/**
+ * enum hwsim_attrs - hwsim netlink attributes
+ *
+ * @HWSIM_ATTR_UNSPEC: unspecified attribute to catch errors
+ *
+ * @HWSIM_ATTR_ADDR_RECEIVER: MAC address of the radio device that
+ *	the frame is broadcasted to
+ * @HWSIM_ATTR_ADDR_TRANSMITTER: MAC address of the radio device that
+ *	the frame was broadcasted from
+ * @HWSIM_ATTR_FRAME: Data array
+ * @HWSIM_ATTR_FLAGS: mac80211 transmission flags, used to process
+	properly the frame at user space
+ * @HWSIM_ATTR_RX_RATE: estimated rx rate index for this frame at user
+	space
+ * @HWSIM_ATTR_SIGNAL: estimated RX signal for this frame at user
+	space
+ * @HWSIM_ATTR_TX_INFO: ieee80211_tx_rate array
+ * @HWSIM_ATTR_COOKIE: sk_buff cookie to identify the frame
+ * @HWSIM_ATTR_CHANNELS: u32 attribute used with the %HWSIM_CMD_CREATE_RADIO
+ *	command giving the number of channels supported by the new radio
+ * @HWSIM_ATTR_RADIO_ID: u32 attribute used with %HWSIM_CMD_DESTROY_RADIO
+ *	only to destroy a radio
+ * @HWSIM_ATTR_REG_HINT_ALPHA2: alpha2 for regulatoro driver hint
+ *	(nla string, length 2)
+ * @HWSIM_ATTR_REG_CUSTOM_REG: custom regulatory domain index (u32 attribute)
+ * @HWSIM_ATTR_REG_STRICT_REG: request REGULATORY_STRICT_REG (flag attribute)
+ * @HWSIM_ATTR_SUPPORT_P2P_DEVICE: support P2P Device virtual interface (flag)
+ * @HWSIM_ATTR_USE_CHANCTX: used with the %HWSIM_CMD_CREATE_RADIO
+ *	command to force use of channel contexts even when only a
+ *	single channel is supported
+ * @HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE: used with the %HWSIM_CMD_CREATE_RADIO
+ *	command to force radio removal when process that created the radio dies
+ * @HWSIM_ATTR_RADIO_NAME: Name of radio, e.g. phy666
+ * @HWSIM_ATTR_NO_VIF:  Do not create vif (wlanX) when creating radio.
+ * @HWSIM_ATTR_FREQ: Frequency at which packet is transmitted or received.
+ * @__HWSIM_ATTR_MAX: enum limit
+ */
+
+
+enum {
+	HWSIM_ATTR_UNSPEC,
+	HWSIM_ATTR_ADDR_RECEIVER,
+	HWSIM_ATTR_ADDR_TRANSMITTER,
+	HWSIM_ATTR_FRAME,
+	HWSIM_ATTR_FLAGS,
+	HWSIM_ATTR_RX_RATE,
+	HWSIM_ATTR_SIGNAL,
+	HWSIM_ATTR_TX_INFO,
+	HWSIM_ATTR_COOKIE,
+	HWSIM_ATTR_CHANNELS,
+	HWSIM_ATTR_RADIO_ID,
+	HWSIM_ATTR_REG_HINT_ALPHA2,
+	HWSIM_ATTR_REG_CUSTOM_REG,
+	HWSIM_ATTR_REG_STRICT_REG,
+	HWSIM_ATTR_SUPPORT_P2P_DEVICE,
+	HWSIM_ATTR_USE_CHANCTX,
+	HWSIM_ATTR_DESTROY_RADIO_ON_CLOSE,
+	HWSIM_ATTR_RADIO_NAME,
+	HWSIM_ATTR_NO_VIF,
+	HWSIM_ATTR_FREQ,
+	HWSIM_ATTR_PAD,
+	__HWSIM_ATTR_MAX,
+};
+#define HWSIM_ATTR_MAX (__HWSIM_ATTR_MAX - 1)
+
+#define VERSION_NR 1
+
+#define SNR_DEFAULT 30
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <syslog.h>
+#include <usfstl/sched.h>
+
+#include "list.h"
+#include "ieee80211.h"
+
+typedef uint8_t u8;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+#define TIME_FMT "%lld.%06lld"
+#define TIME_ARGS(a) ((unsigned long long)(a)->tv_sec), ((unsigned long long)(a)->tv_nsec/1000)
+
+#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
+#define MAC_ARGS(a) a[0],a[1],a[2],a[3],a[4],a[5]
+
+#ifndef min
+#define min(x,y) ((x) < (y) ? (x) : (y))
+#endif
+
+#define NOISE_LEVEL	(-91)
+#define CCA_THRESHOLD	(-90)
+
+extern struct usfstl_scheduler scheduler;
+
+struct wqueue {
+	struct list_head frames;
+	int cw_min;
+	int cw_max;
+};
+
+struct addr {
+	u8 addr[ETH_ALEN];
+};
+
+struct station {
+	int index;
+	u8 addr[ETH_ALEN];		/* virtual interface mac address */
+	u8 hwaddr[ETH_ALEN];		/* hardware address of hwsim radio */
+	double x, y;			/* position of the station [m] */
+	double dir_x, dir_y;		/* direction of the station [meter per MOVE_INTERVAL] */
+	int tx_power;			/* transmission power [dBm] */
+	struct wqueue queues[IEEE80211_NUM_ACS];
+	struct list_head list;
+	struct client *client;
+	unsigned int n_addrs;
+	struct addr *addrs;
+};
+
+enum client_type {
+	CLIENT_NETLINK,
+	CLIENT_VHOST_USER,
+	CLIENT_API_SOCK,
+};
+
+struct client {
+	struct list_head list;
+	enum client_type type;
+
+	/*
+	 * There's no additional data for the netlink client, we
+	 * just have it as such for the link from struct station.
+	 */
+
+	/* for vhost-user */
+	struct usfstl_vhost_user_dev *dev;
+
+	/* for API socket */
+	struct usfstl_loop_entry loop;
+	bool wait_for_ack;
+
+	u32 flags;
+};
+
+struct wmediumd {
+	int timerfd;
+
+	struct nl_sock *sock;
+	struct usfstl_loop_entry nl_loop;
+
+	struct usfstl_sched_ctrl *ctrl;
+
+	struct list_head clients, clients_to_free;
+	struct client nl_client;
+
+	int num_stas;
+	struct list_head stations;
+	struct station **sta_array;
+	int *snr_matrix;
+	double *error_prob_matrix;
+	struct intf_info *intf;
+	struct usfstl_job intf_job, move_job;
+#define MOVE_INTERVAL	(3) /* station movement interval [sec] */
+	void *path_loss_param;
+	float *per_matrix;
+	int per_matrix_row_num;
+	int per_matrix_signal_min;
+	int fading_coefficient;
+
+	struct nl_cb *cb;
+	int family_id;
+
+	int (*get_link_snr)(struct wmediumd *, struct station *,
+			    struct station *);
+	double (*get_error_prob)(struct wmediumd *, double, unsigned int, u32,
+				 int, struct station *, struct station *);
+	int (*calc_path_loss)(void *, struct station *,
+			      struct station *);
+	int (*get_fading_signal)(struct wmediumd *);
+
+	u8 log_lvl;
+
+	u32 need_start_notify;
+
+	FILE *pcap_file;
+};
+
+struct hwsim_tx_rate {
+	signed char idx;
+	unsigned char count;
+};
+
+struct frame {
+	struct list_head list;		/* frame queue list */
+	struct usfstl_job job;
+	struct usfstl_job start_job;
+	struct client *src;
+	bool acked;
+	u64 cookie;
+	u32 freq;
+	int flags;
+	int signal;
+	int duration;
+	int tx_rates_count;
+	struct station *sender;
+	struct hwsim_tx_rate tx_rates[IEEE80211_TX_MAX_RATES];
+	size_t data_len;
+	u8 data[0];			/* frame contents */
+};
+
+struct log_distance_model_param {
+	double path_loss_exponent;
+	double Xg;
+};
+
+struct itu_model_param {
+	int nFLOORS;
+	int LF;
+};
+
+struct intf_info {
+	int signal;
+	int duration;
+	double prob_col;
+};
+
+void station_init_queues(struct station *station);
+double get_error_prob_from_snr(double snr, unsigned int rate_idx, u32 freq,
+			       int frame_len);
+int set_default_per(struct wmediumd *ctx);
+int read_per_file(struct wmediumd *ctx, const char *file_name);
+int w_logf(struct wmediumd *ctx, u8 level, const char *format, ...);
+int w_flogf(struct wmediumd *ctx, u8 level, FILE *stream, const char *format, ...);
+int index_to_rate(size_t index, u32 freq);
+
+#endif /* WMEDIUMD_H_ */