Last Part Of The WiFi-AP Bridge Setup (DHCP Relay/Forwarder) in Py

I was having troubles getting dnsmasq to be a simple DHCP relay/forwarder/proxy and I didn’t want to add this into the ARP relay C code to keep that as simple as possible so I wrote this little Python script that will basically bridge 2 interfaces together (one that has DHCP clients on it and one that is connected to the DHCP server [1 main server running for the whole network]).

I stored this in the ARP-relay GIT repo I created prior since it is related to the same bridged setup:
https://github.com/stoops/arprb/blob/master/dhcprb.py?ts=4

An example usage & output from running it so far:

root@OpenWRT:~# python dhcprb.py br-wan wlan0
('00:be:ee:ca:fe:00', '192.168.17.51', '<-->', '00:be:ee:ca:fe:ff', '192.168.16.51')
...
request-> ('0.0.0.0', 68) [304] {wlan0}
<-reply ('192.168.16.1', 67) [300] {br-wan:192.168.16.131}
...
request-> ('0.0.0.0', 68) [300] {wlan0}
<-reply ('192.168.16.1', 67) [300] {br-wan:192.168.16.153}
...

Last Part Of The WiFi-AP Bridge Setup (DHCP Relay/Forwarder) in Py

802.11n AP <-> Client Kick-Off Script (Py)

So I have been running multiple APs with the same SSID on separate channels and frequencies and noticed that the clients are really good at switching from a weak 802.11ac signal strength to the stronger but lower speed 802.11n AP station. This is good, however, they don’t seem to be as aggressive in switching back to 802.11ac once they get closer again (unless they turn off or shutdown or restart their network stack since the 802.11n just gets stronger the closer you get). I found an OpenWRT compatible shell script which kicks clients off a given radio depending on their signal strength to the router. I adjusted it to disconnect a client if they start to get too close to the N router as they are likely going to get a good signal strength from the AC AP instead. You can set the AP deauth time (ex 19 seconds), the time between kicking the same client on/off again (ex 31 mins), and it checks for the signal-to-noise ratio to get above a certain amount (ex 45 SNR) before activating on a client!

python /root/apc.py n 19 31
import os,sys,time

radios = []
mode = sys.argv[1]
secs = (int(sys.argv[2]) * 1000)
macs = {}
ban = (int(sys.argv[3]) * 60)

if (mode == "ac"):
	radios = ["wlan0"]

if (mode == "n"):
	radios = ["wlan1"]

while True:
	sec = int(time.time())

	for intf in radios:
		os.system("iwinfo '%s' assoclist | grep 'SNR' | tr '(/)' ' ' | tr -s ' ' > /tmp/apc.log" % (intf))
		f = open("/tmp/apc.log", "r")
		lines = f.readlines()
		f.close()

		for line in lines:
			info = line.strip().split(" ")
			mac = info[0] ; sig = int(info[1]) ; snr = abs(int(info[6]))
			print("> %d %d [%s][%s]" % (snr, sig, intf, mac))

			if (secs >= 1000):
				delc = 0
				opts = ("'addr':'%s', 'reason':5, 'deauth':false, 'ban_time':%s" % (mac, secs))

				if ((mode == "ac") and (sig <= -83)):
					delc = 1

				#if ((mode == "n") and (sig >= -51)):
				#	delc = 1

				if ((mode == "n") and (snr >= 45)):
					delc = 1

				if (delc == 1):
					if (not mac in macs.keys()):
						macs[mac] = 0
					if (sec >= (macs[mac] + ban)):
						macs[mac] = sec
						print("* %d %d [%s][%s][%d][%d]" % (macs[mac], sec, mac, intf, sig, snr))
						os.system('ubus call "hostapd.%s" del_client "{%s}"' % (intf, opts))

	for mac in macs.keys():
		if (sec >= (macs[mac] + ban)):
			print("x", mac, macs[mac])
			del macs[mac]

	print("")
	time.sleep(9)

802.11n AP <-> Client Kick-Off Script (Py)

Creating a simple relayd replacement in C (compiled for an ARMv7 WRT router)

I’ve been running into different issues with relayd that I tried to manually fix in the framework, for example: not locking host route entries onto a specific interface, better detection for when a host changes interfaces, better arp table & ip routing management, etc.. However, I’ve still been experiencing some flakiness in it’s operation so I made a simple alternative that I can run which duplicates its most essential features needed for layer 2 bridging:

  • Layer 2 server socket to listen for ARP request packets
  • Layer 2 client sockets to send ARP packets with the routers MAC address inserted into the ARP replies for each host and for each bridged interface
  • Layer 3 UDP socket-based pinging for unknown host detection & discovery (auto IP-to-MAC ARP table entry resolution for each bridged interface)
  • Layer 3 IP routing-table host-entry updates related to which bridged interface the client is currently on

The code is just under 300 lines but it seems to be working so far and will keep testing it out to see if it works better than what I was experiencing with relayd!

https://github.com/stoops/arprb/blob/master/arprb.c?ts=4

--
arp req [42] [1544][256] [192.168.17.175][192.168.16.1]
arp reply [wlan0] [00:be:ee:ca:fe:ff][192.168.17.175] <-> [192.168.16.1]
--
arp req [42] [1544][256] [192.168.16.1][192.168.17.175]
arp reply [br-wan] [00:be:ee:ca:fe:00][192.168.16.1] <-> [192.168.17.175]
--

... snippet ...

void send_arp(struct intf relay, char *who_adr, uchar *dst_mac, char *dst_adr) {
	printf("arp reply [%s] [%s][%s] <-> [%s]\n", relay.ifn, relay.smac, who_adr, dst_adr);

	int sock = relay.sock;
	unsigned char *src_mac = relay.mac;
	struct sockaddr ssa = relay.ssa;

	struct in_addr src_adr;
	struct arp_packet pkt;

	pkt.frame_type = htons(FRAME_HW_TYPE);
	pkt.hw_type    = htons(ETHER_HW_TYPE);
	pkt.proto_type = htons(IP_PROTO_TYPE);
	pkt.hw_size    = HW_ADDR_LEN;
	pkt.proto_size = IP_ADDR_LEN;
	pkt.op         = htons(OP_ARP_REPLY);

	bcopy(src_mac, pkt.sorc_hw_addr, HW_ADDR_LEN);
	bcopy(dst_mac, pkt.targ_hw_addr, HW_ADDR_LEN);

	bcopy(src_mac, pkt.sndr_hw_addr, HW_ADDR_LEN); /* target mac */
	src_adr.s_addr = inet_addr(who_adr);
	bcopy(&src_adr, pkt.sndr_ip_addr, IP_ADDR_LEN); /* target ip */

	bcopy(dst_mac, pkt.rcpt_hw_addr, HW_ADDR_LEN); /* requester mac */
	src_adr.s_addr = inet_addr(dst_adr);
	bcopy(&src_adr, pkt.rcpt_ip_addr, IP_ADDR_LEN); /* requester ip */

	sendto(sock, &pkt, sizeof(pkt), 0, &ssa, sizeof(ssa));
}

Creating a simple relayd replacement in C (compiled for an ARMv7 WRT router)

Following along with the OpenWRT subreddit users

There was a post on a subreddit that I follow (OpenWRT) that was asking for peoples router/network setups and how they are using OpenWRT to accomplish that so I decided to try and make a network diagram of the setup I worked on here at my parents home while I’ve been working remotely (needing stable Internet/WiFi!). There are 2 levels that are covered and the Internet comes into the lower level which then gets sent up via a dedicated 802.11ac radio (separate backchannel). That is then rebroadcasted to the rest of the home with another dedicated, separate 802.11ac AP via the middle relay bridge in between. The relay bridge runs the customized app below that I compiled and it all shares one flat network throughout the home (one /20)! There is also 802.11n sent out for better reach and distance just in case as well 🙂

Edit: You can also add guest networks on each of the radios but with different SSIDs and wlan interfaces which will let them act like wifi-vlans that you can section off with dnsmasq & iptables…

Edit-edit (Updated diagram/layout):

I made this network graph with this site: https://www.lucidchart.com/documents#/dashboard

Historical documentation purposes,
Previous setup with semi-ok results:

Following along with the OpenWRT subreddit users

Turning a small stream cipher (ARC4) into a hash function (ARCH)!

$ python hash.py b b
('fd5f1b4640e0f5cdda0368b67601b9077cbe4a4931a94b52fdf46bf1be0c2bf5', '3a4627878dca5c4daeb243a661b56ebbaa03c268f659b6001cd84911051c103b', 'bb', 2)

$ python hash.py b c
('1181834e637bf3519010de1902889807341cd57a1d2e68a281524d91f2f86cf9', '7deadb485c3d329026d04cefadb4b43a9af19536aaf450052a2edce404f6065f', 'bc', 2)

$ python hash.py c b
('2f4802585ed79a61d25c92a3d7af8fa2a16e2e269e9c6fbd21891e6a7bae15ec', '6a25a3b18a93d966d7c878f9263f053d653618782a9f7918db05b06e0fa27787', 'cb', 2)

$ python hash.py c c
('6683682f501fdaa1c4a95e1ec6b7fb5ff49206af34e1729a17ded27fda355ea4', '7f12dc4dd32e3c9b31d03382b9c9af1efc0ea3ba620ba8e27fa9b9d8ebc1a88b', 'cc', 2)
import os,sys

def swap(s, a, b):
	t = s[a] ; s[a] = s[b] ; s[b] = t
	return s

def init(mesg, leng):
	j = 0 ; skey = []
	for i in range(0, 256):
		skey.append(i)
	for i in range(0, 256+leng):
		k = (i % 256) ; m = (i % leng)
		l = (((i + 1 + ord(mesg[m])) * leng) % 256)
		j = ((j + skey[k] + skey[l]) % 256)
		swap(skey, k, j)
	return skey

def arch(skey):
	i = 0 ; j = 0
	l = 0 ; rnds = 4 ; outp = ""
	for z in range(0, 256*rnds):
		i = ((i + 1) % 256)
		j = ((j + skey[i]) % 256)
		swap(skey, i, j)
	for z in range(0, 32):
		i = ((i + 1) % 256)
		j = ((j + skey[i]) % 256)
		swap(skey, i, j)
		k = ((skey[i] + skey[j] + skey[l]) % 256)
		outp += chr(skey[k])
		l = (l ^ ord(outp[z]))
	return outp

def hmac(mesg, mlen, skey, klen):
	inner_pad = 0x36 ; outer_pad = 0x5C
	block_size = 64 ; ikey = "" ; okey = ""
	tkey = skey ; tlen = klen
	if (klen > block_size):
		zkey = init(skey, klen)
		tkey = arch(zkey)
		tlen = len(tkey)
	for x in range(0, block_size):
		c = 0
		if (x < tlen):
			c = ord(tkey[x])
		ikey += chr(inner_pad ^ c)
		okey += chr(outer_pad ^ c)
	zkey = init(ikey+mesg, block_size+mlen)
	ihsh = arch(zkey)
	ilen = len(ihsh)
	zkey = init(okey+ihsh, block_size+ilen)
	ohsh = arch(zkey)
	return ohsh

def stoh(inpt):
	o = ""
	for c in inpt:
		s = hex(ord(c))[2:]
		if len(s) != 2:
			s = "0" + s
		o += s
	return o

m = sys.argv[1] ; l = len(m)
k = sys.argv[2] ; n = len(k)
z = init(m+k, l+n)
print(stoh(hmac(m, l, k, n)), stoh(arch(z)), m+k, l+n)

Turning a small stream cipher (ARC4) into a hash function (ARCH)!

A simpler improvement to relayd (checking the arp table before expiring)

So instead of running a process heavy static arp broadcaster client/server service, I realized that I could just make relayd a bit smarter in order to properly detect disconnected hosts faster (simply by double checking the arp table). I was also able to compile this software in an Ubuntu VM running in a VirtualBox on OSX for mips & armv7 (archer c7 tplink & wrt1900acs wifi extended setup):

https://github.com/stoops/relayd/compare/master…stoops:moddel

./relayd -I wlan0 -I wlan1 -t 2 -p -1 -B -D -P

With a couple of arp table clean up helper scripts the setup is much simpler with this and less involved in managing the arp table entries!

mode:  ap  |  ssid:  Peppi Place      bmac: 00:be:ee:ca:fe:01  key: psk2+ccmp  |  freq: 2.4GHz  chan:   1  mode:  HT40  |  timeouts: 300 300
mode:  ap  |  ssid:  Peppi Place      bmac: 00:be:ee:ca:fe:11  key: psk2+ccmp  |  freq: 2.4GHz  chan:  11  mode:  HT40  |  timeouts: 300 300
mode:  ap  |  ssid:  Peppi Place      bmac: 00:be:ee:ca:fe:61  key: psk2+ccmp  |  freq: 5.0GHz  chan: 161  mode: VHT80  |  timeouts: 300 300
mode:  ap  |  ssid:  Peppi Place AC   bmac: 00:be:ee:ca:fe:63  key: psk2+ccmp  |  freq: 5.0GHz  chan: 161  mode: VHT80  |  timeouts: 300 300

A simpler improvement to relayd (checking the arp table before expiring)

GIT Rebase with HEAD Conflicts (Auto Accept & Merge the Changes)

I have a bad habit of using git amends (vs commit & squash)
git commit -a --amend --no-edit
but this can cause some future issues when force pulling in the new changes (for example to another branch possibly). I learnt a new way when rebasing to auto accept all of the head changes listed as a conflict:

git checkout --conflict=merge . ; git checkout --ours . ; git add .

GIT Rebase with HEAD Conflicts (Auto Accept & Merge the Changes)