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 -> 802.11ac 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 apc.py n 20 40
import os,sys,time

radios = []
mode = sys.argv[1]
secs = (int(sys.argv[2]) * 1000)
macs = {}
band = (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, sig, snr) = (info[0], int(info[1]), abs(int(info[6])))
			print("> %d %d [%s][%s]" % (snr, sig, intf, mac))

			if (secs >= 1000):
				delc = 0 ; kick = "true"
				opts = ("'addr':'%s', 'reason':5, 'deauth':%s, 'ban_time':%s" % (mac, kick, 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] + band)):
						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] + band)):
			print("x", mac, macs[mac])
			del macs[mac]

	print("")
	time.sleep(9)

802.11n -> 802.11ac 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]
--
void send_arps(struct intf *relay, char *who_adr, uchar *dst_mac, char *dst_adr) {
	int sock = relay->sock_arp;
	unsigned char *src_mac = relay->mac;
	struct sockaddr ssa = relay->ssa;

	unsigned int psiz = sizeof(struct arp_pkt);
	struct arp_pkt pkt;

	printf("arp reply !! [%s][%s] -> [%s][%s]\n", relay->smac, who_adr, relay->ifn, dst_adr);

	pkt.frame_type = htons(ETH_P_ARP);
	pkt.hw_type    = htons(ETH_HW_TYPE);
	pkt.proto_type = htons(ETH_P_IP);
	pkt.hw_size    = HW_ADDR_LEN;
	pkt.proto_size = IP_ADDR_LEN;
	pkt.op_code    = htons(ARP_OP_REPLY);

	bcopy(src_mac, pkt.sorc_hw, HW_ADDR_LEN); /* src mac */
	bcopy(dst_mac, pkt.dest_hw, HW_ADDR_LEN); /* dst mac */

	bcopy(src_mac, pkt.sndr_hw, HW_ADDR_LEN); /* who mac */
	ipstr(who_adr, pkt.sndr_ip);              /* who ip  */

	bcopy(dst_mac, pkt.rcpt_hw, HW_ADDR_LEN); /* dst mac */
	ipstr(dst_adr, pkt.rcpt_ip);              /* dst ip  */

	sendto(sock, &pkt, psiz, 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)

Turning the sdbm hash method into an hmac version

Some small hash loop tweaks and I increased the number of hash rounds from 3 to 4 also:

import os,sys

def tt(ll):
	return (ll & 0xffffffff)

def sdbm(inpt, leng):
	hshs = 0
	for x in range(0, leng):
		hshs = tt(ord(inpt[x]) + tt(hshs << 6) + tt(hshs << 16) - hshs)
	return hshs

def sdbm_hash(inpt, leng):
	mixs = [1, 6, 16, 13, 33, 27, 67, 55, 123]
	hshs = [0, 0, 0, 0, 0, 0, 0, 0, 0]
	more = 0
	mlen = len(hshs) ; rnds = (4 * mlen)
	for x in range(0, mlen+leng):
		hshs[0] = tt(mixs[x%mlen] + ord(inpt[x%leng]) + (hshs[0] << 6) + (hshs[0] << 16) - hshs[0])
		more = (more ^ (hshs[0] >> 16))
		i = ((x % (mlen - 1)) + 1)
		less = ((hshs[i] & 0xffff0000) ^ ((hshs[i] & 0xffff) << 16))
		hshs[i] = (less ^ more)
	for z in range(0, rnds):
		hshs[0] = tt(z + more + (hshs[0] << 6) + (hshs[0] << 16) - hshs[0])
		more = (more ^ (hshs[mlen-1] >> 16))
		for y in range(mlen-1, 0, -1):
			hshs[y] = tt((hshs[y] << 16) | (hshs[y-1] >> 16))
			hshs[y-1] = (hshs[y-1] & 0xffff)
	o = ""
	for h in hshs[1:]:
		for x in range(3, -1, -1):
			o += chr((h >> (x * 8)) & 0xff)
	return o

def sdbm_hmac(mesg, mlen, skey, klen):
	inner_pad = 0x36 ; outer_pad = 0x5C
	block_size = 64 ; ikey = "" ; okey = ""
	tkey = skey ; tlen = klen
	if (klen > block_size):
		tkey = sdbm_hash(skey, klen)
		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)
	ihsh = sdbm_hash(ikey+mesg, block_size+mlen)
	ilen = len(ihsh)
	ohsh = sdbm_hash(okey+ihsh, block_size+ilen)
	return ohsh

def stoh(s):
	return "".join([hex(ord(c))[2:].rjust(2, '0') for c in s])

m = sys.argv[1] ; l = len(m)
k = sys.argv[2] ; n = len(k)
#print(stoh(sdbm_hash(m, l)), sdbm(m, l), m, l)
print(stoh(sdbm_hmac(m, l, k, n)), sdbm(m, l), m, l, sdbm(k, n), k, n)

$ for x in b c ; do for y in b c ; do python hash.py "$x" "$y" ; echo ; done ; done
('d805c892902cf6ea0c98d1946110a61334a9a1158b430fb7d1c5ba7afb29147d', 98, 'b', 1, 98, 'b', 1)

('6eb38871cc3679cb533f1daeee4ed87ab4f154f953e5ea96d508bda6bb054abd', 98, 'b', 1, 99, 'c', 1)

('0d034f1f7627492fd57bd92a63a8a4f2d977f02c2fdf29b4e49f4b3b21a9e738', 99, 'c', 1, 98, 'b', 1)

('09b2678c7ce46cd7cd112b4a2b6fd05d50c858fe0460d29264c4ea01595968db', 99, 'c', 1, 99, 'c', 1)


$ ./hash "this is a test" "b"
[6b18600ee98449472dd377755dd67cd436c9c143cfc893c9a2181e0567d16cf1] [1655286693] [this is a test] [14] [98] [b] [1]

$ ./hash "this is a test" "c"
[bd3bc1a983e35311e5ea60db5ae8cd28a821386937dfd480eccff95fab8356b9] [1655286693] [this is a test] [14] [99] [c] [1]

$ ./hash "this is a tesu" "b"
[8cddc8b9f5a1a1adf41c78b035bf59b444431e772b9c39824e2cf67e2837d9b0] [1655286694] [this is a tesu] [14] [98] [b] [1]

$ ./hash "this is a tesu" "c"
[2554d7d9ecb47d36b7f62d5e4432a7983490aca1229a91331c16b81567c4edff] [1655286694] [this is a tesu] [14] [99] [c] [1]
#include <stdio.h>
#include <string.h>

unsigned int sdbm(char *inpt, int leng) {
	unsigned int hshs = 0;
	for (int x = 0; x < leng; ++x) {
		hshs = (inpt[x] + (hshs << 6) + (hshs << 16) - hshs);
	}
	return hshs;
}

void sdbm_hash(unsigned char *outp, unsigned char *inpt, int leng) {
	unsigned int mixs[] = {1, 6, 16, 13, 33, 27, 67, 55, 123};
	unsigned int hshs[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
	unsigned int less, more = 0;
	int i, mlen = 9, rnds = (4 * mlen);
	for (int x = 0; x < (mlen+leng); ++x) {
		hshs[0] = (mixs[x%mlen] + inpt[x%leng] + (hshs[0] << 6) + (hshs[0] << 16) - hshs[0]);
		more = (more ^ (hshs[0] >> 16));
		i = ((x % (mlen - 1)) + 1);
		less = ((hshs[i] & 0xffff0000) ^ ((hshs[i] & 0xffff) << 16));
		hshs[i] = (less ^ more);
	}
	for (int z = 0; z < rnds; ++z) {
		hshs[0] = (z + more + (hshs[0] << 6) + (hshs[0] << 16) - hshs[0]);
		more = (more ^ (hshs[mlen-1] >> 16));
		for (int y = mlen-1; y > 0; --y) {
			hshs[y] = ((hshs[y] << 16) | (hshs[y-1] >> 16));
			hshs[y-1] = (hshs[y-1] & 0xffff);
		}
	}
	for (int x = 1, y = 0; x < mlen; ++x) {
		for (int z = 3; z > -1; --z, ++y) { 
			outp[y] = ((hshs[x] >> (z * 8)) & 0xff);
		}
	}
}

void sdbm_hmac(unsigned char *outp, unsigned char *mesg, int mlen, unsigned char *skey, int klen) {
	int block_size = 64, hash_size = 32;
	unsigned char inner_pad = 0x36, outer_pad = 0x5C;
	unsigned char ikey[block_size], okey[block_size], ihsh[hash_size], thsh[hash_size];
	unsigned char buff[block_size+mlen+hash_size];
	unsigned char *tkey = skey; int tlen = klen;
	if (klen > block_size) {
		sdbm_hash(thsh, skey, klen);
		tkey = thsh; tlen = hash_size;
	}
	for (int x = 0; x < block_size; ++x) {
		unsigned char padc = 0;
		if (x < tlen) { padc = tkey[x]; }
		ikey[x] = (inner_pad ^ padc);
		okey[x] = (outer_pad ^ padc);
	}
	bcopy(ikey, buff, block_size);
	bcopy(mesg, buff+block_size, mlen);
	sdbm_hash(ihsh, buff, block_size+mlen);
	bcopy(okey, buff, block_size);
	bcopy(ihsh, buff+block_size, hash_size);
	sdbm_hash(outp, buff, block_size+hash_size);
}

void stoh(char *outp, unsigned char *inpt) {
	char *hexs = "0123456789abcdef";
	for (int x = 0, y = 0; x < 32; ++x) {
		outp[y] = hexs[(inpt[x] >> 4) & 0xf]; ++y;
		outp[y] = hexs[inpt[x] & 0xf]; ++y;
	}
}

int main(int argc, char *argv[]) {
	char *m = argv[1]; int l = strlen(m);
	char *k = argv[2]; int n = strlen(k);
	unsigned char h[32]; char o[65]; bzero(o, 65);
	sdbm_hmac(h, (unsigned char *)m, l, (unsigned char *)k, n); stoh(o, h);
	printf("[%s] [%u] [%s] [%d] [%u] [%s] [%d]\n", o, sdbm(m, l), m, l, sdbm(k, n), k, n);
	return 0;
}

Turning the sdbm hash method into an hmac version

Experiment – Turning the SDBM mixing algorithm into a hash function

From this page: http://www.cse.yorku.ca/~oz/hash.html

import os,sys

def tt(ll):
	return (ll & 0xffffffff)

def sdbm(inpt, leng):
	hshs = 0
	for x in range(0, leng):
		hshs = tt(ord(inpt[x]) + tt(hshs << 6) + tt(hshs << 16) - hshs)
	return hshs

def sdbm_hash(inpt, leng):
	mixs = [1, 6, 16, 13, 33, 27, 67, 55, 123]
	hshs = [0, 0, 0, 0, 0, 0, 0, 0, 0]
	more = 0
	rnds = len(hshs)
	for z in range(0, rnds*3):
		hshs[0] = tt((hshs[0] + mixs[z%rnds]) * mixs[z%rnds])
		for x in range(0, leng):
			hshs[0] = (hshs[0] & 0xffff)
			hshs[0] = tt(ord(inpt[x]) + (hshs[0] << 6) + (hshs[0] << 16) - hshs[0])
			more = (more ^ (hshs[rnds-1] >> 16))
			for y in range(rnds-1, 0, -1):
				hshs[y] = tt((hshs[y] << 16) | (hshs[y-1] >> 16))
				hshs[y-1] = (hshs[y-1] & 0xffff)
			hshs[0] = (hshs[0] ^ more)
	o = ""
	for h in hshs[1:]:
		for x in range(3, -1, -1):
			o += chr((h>>(x*8))&0xff)
	return o

def stoh(s):
	return "".join([hex(ord(c))[2:].rjust(2, '0') for c in s])

m = sys.argv[1] ; l = len(m)
print(stoh(sdbm_hash(m, l)), sdbm(m, l), m, l)

$ python hash.py "b" 
('197f907989061db579beba252025bfbf7f4848a47da0e5a9bc8eb73c6fdb8904', 98, 'b', 1)

$ python hash.py "c" 
('f39b31f66506067376f8f88f620e731cd4292a6aeb71da43d297fffc3f5f92c4', 99, 'c', 1)

$ python hash.py "bb" 
('d427d6ba1edaabab2ba3c6ddd70ccf3d0ceff23fcd6de3d2c0af7d0ac95dd62d', 6428800, 'bb', 2)

$ python hash.py "bc" 
('4bfdd409d3de82913ba7405894f444d8858a539abe98e312d64474abf6b68e3a', 6428801, 'bc', 2)

$ ./hash "this is a test"
[dc053c4426ce396ace93a2817b388c465bb5188336d5f8816384a1cef0a2039d] [1655286693] [this is a test] [14]

$ ./hash "this is a tesu"
[cfbb3301ae39658cf1a1874dd48d8dbf9da4aee0831f781e30066be5f67eb819] [1655286694] [this is a tesu] [14]
#include <stdio.h>
#include <string.h>

unsigned int sdbm(char *inpt, int leng) {
	unsigned int hshs = 0;
	for (int x = 0; x < leng; ++x) {
		hshs = (inpt[x] + (hshs << 6) + (hshs << 16) - hshs);
	}
	return hshs;
}

void sdbm_hash(unsigned char *outp, unsigned char *inpt, int leng) {
	unsigned int mixs[] = {1, 6, 16, 13, 33, 27, 67, 55, 123};
	unsigned int hshs[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
	unsigned int more = 0;
	int rnds = 9;
	for (int z = 0; z < rnds*3; ++z) {
		hshs[0] = ((hshs[0] + mixs[z%rnds]) * mixs[z%rnds]);
		for (int x = 0; x < leng; ++x) {
			hshs[0] = (hshs[0] & 0xffff);
			hshs[0] = (inpt[x] + (hshs[0] << 6) + (hshs[0] << 16) - hshs[0]);
			more = (more ^ (hshs[rnds-1] >> 16));
			for (int y = rnds-1; y > 0; --y) {
				hshs[y] = ((hshs[y] << 16) | (hshs[y-1] >> 16));
				hshs[y-1] = (hshs[y-1] & 0xffff);
			}
			hshs[0] = (hshs[0] ^ more);
		}
	}
	for (int x = 1, y = 0; x < rnds; ++x) {
		for (int z = 3; z > -1; --z, ++y) { 
			outp[y] = ((hshs[x] >> (z * 8)) & 0xff);
		}
	}
}

void stoh(char *outp, unsigned char *inpt) {
	char *hexs = "0123456789abcdef";
	for (int x = 0, y = 0; x < 32; ++x) {
		outp[y] = hexs[(inpt[x] >> 4) & 0xf]; ++y;
		outp[y] = hexs[inpt[x] & 0xf]; ++y;
	}
}

int main(int argc, char *argv[]) {
	char *m = argv[1]; int l = strlen(m);
	unsigned char h[32]; char o[65]; bzero(o, 65);
	sdbm_hash(h, (unsigned char *)m, l); stoh(o, h);
	printf("[%s] [%u] [%s] [%d]\n", o, sdbm(m, l), m, l);
	return 0;
}

Experiment – Turning the SDBM mixing algorithm into a hash function

Experiment: Running a UDP socket [client/server] ARP broadcaster for each WiFi AP (better client association tracking)

If you are running multiple WiFI APs that are part of the same flat network and bridged together it can be tricky keeping a current list of which client is connected to which AP (sometimes clients can just stop responding on one AP and then wake up and appear on another AP). In addition, the one common question a router needs to know is, is this client connected to me OR are they connected through one of my peer routers and which one is the best one?… I wrote a small service which allows an AP to periodically broadcast out, relay/forward, and receive the current WiFi client associations it has and the best association is chosen to amongst all the APs to be able to keep a common set of ARP entries for each access point! The tables are way cleaner and way more consistent with each other now that I have this thing running. I wrote it in both C and python which are supported on OpenWRT routers and can communicate with each other using a common key/pwd. You simply list which radio interfaces the AP has and the IP address you gave the AP on the flat network and it should be ready to send, forward, & receive ARP related messages. The program will write out a standard file with the following information formatted in it and it can also be used with the relayd service modification post below:

/tmp/arp.txt: [dev] [mac/peer] [ip]
wlan1 00:11:22:dd:ee:ff 192.168.100.123

The code can be found here if you are interested!:

C: https://github.com/stoops/broadarp/blob/init/barp.c
Py2: https://github.com/stoops/broadarp/blob/python/barp.py
relayd-mod: https://github.com/stoops/relayd/compare/master…stoops:merged [blog post]

๐Ÿ™‚
Jon C

Experiment: Running a UDP socket [client/server] ARP broadcaster for each WiFi AP (better client association tracking)

Modifying the OpenWRT relayd package C source code to help set better static ARP entries (ATF_PERM)

If you are bridging 2+ wireless networks together, it can get tricky for the ARP mappings to take place efficiently throughout the network as you move around from AP to AP. I wrote a small shell script that runs on each router which reports in its WiFi-AP associations & DHCP entries that are part of the overall /20 network. This client mapping data is sent around to each router to determine who has the proper client and where/how to find them. I noticed that the relayd bridging app was putting in ARP entries that were becoming stale/incorrect over time as you moved around so I modified it’s source code to outsource this work to a file that has an updated master set of static interface entries based on each DHCP/WIFI association for each router on the network.

(I lost access to my fossjon github account due to 2-factor auth being lost)

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

(the steps to compile this on a armv7l router itself are in the README I added)

For example, a shell script running on the router (with all the relevant network information) can determine and write out a small mapping file that this modified framework can use to keep a cleaner and better ARP tableset (/tmp/arp.txt):

wlan1 00:2b:3d:5a:4a:9e 192.168.18.103

The added capability in relayd will then ensure that for any of the bridged interfaces listed, the only ARP entry that will exist is for the one specified in the file and that it will be set to permanent status instead (deletion of incorrect entries added in).

I am running this as a test on my parents home network to see how long it lasts for, modifying C is tricky for me! ๐Ÿ™‚

Modifying the OpenWRT relayd package C source code to help set better static ARP entries (ATF_PERM)

dnsmasq dhcp mac address hashing algorithm

So during this quarantine break I was setting up a new network layout for my parents place which includes 2 wireless OpenWRT routers connected together via an 802.11ac channel. Each wifi router broadcasts an 802.11n AP which has the same SSID & KEY but on different CHANNELS and BSSIDS. The traffic can be forwarded back and forth between the 2 separate APs via the single private AC connection (using the OWRT relayd package):

/usr/sbin/relayd -I wlan1 -I wlan0 -t -1 -p -1 -B

Each AP has dnsmasq running, however, they are each set to hand out a subset of unique addresses that are shared in a /20 block (so that whatever AP you are on, you can communicate to any other host on any of the APs as if you are on the same network). I noticed that when switching between the 2 APs, my machine was getting the same last octet in the IP range set! I thought this was pretty cool because even though the rest of the IP prefix was different, I could simply memorize the last digits if I wanted to connect to another machine on the network in the future. I then started looking into dnsmasq’s source code to see how it was doing this since the only info DHCP usually has is one’s MAC address:

https://github.com/dnsmasq/dnsmasq/blob/master/src/dhcp.c#L638

…note: they added a fix for the j variable recently: j= instead of j+=…
…note: note: there is a IP loop and epoch counter variable that gets increased in case of any hashing collisions or errors that occur!

So if you compile the code below and want to pre-calculate your likely IP address when asking dnsmasq for one, you can run the following (you need to know the IP range that was given in your configs):

$ a="11:22:33:44:55:66"
$ b=$(echo "$a" | sed -e 's/^/\\x/' -e 's/:/\\x/g')
$ c=$(printf "${b}")
$ ./dd "$c" 6 192.168.17.101 192.168.17.190

[192.168.17.106] [3842235635] [6]

---

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	int i, l=atoi(argv[2]);
	unsigned int j, e=0;
	struct in_addr temp, addr;
	struct sockaddr_in beg, end;
	unsigned char *hwaddr = (unsigned char *)argv[1];

	for (j = 0, i = 0; i < l; i++) {
		j = hwaddr[i] + (j << 6) + (j << 16) - j;
	}

	int pass;
	for (pass = 0; pass <= 1; pass++) {
		inet_pton(AF_INET, argv[3], &(beg.sin_addr));
		inet_pton(AF_INET, argv[4], &(end.sin_addr));
		temp.s_addr = htonl(ntohl(beg.sin_addr.s_addr) + 
					 ((j + e) % (1 + ntohl(end.sin_addr.s_addr) - ntohl(beg.sin_addr.s_addr))));
		addr = temp;
		do {
			addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
			if (addr.s_addr == htonl(ntohl(end.sin_addr.s_addr) + 1))
				addr = beg.sin_addr;
		} while (addr.s_addr != temp.s_addr);
	}

	char str[64];
	bzero(str, 64 * sizeof(char));
	inet_ntop(AF_INET, &(addr.s_addr), str, INET_ADDRSTRLEN);

	printf("[%s] [%u] [%d]\n", str, j, l);
	return 0;
}

dnsmasq dhcp mac address hashing algorithm

Playing around with XOR (my fav operation) during the holidays

# cipher design notes:
## purpose?
### cheap single byte keyed stream cipher
### cheaply obfuscate data based on a simple password
### use a simple language with as few lines & dependencies as possible
## internal key setup:
### produce a random byte for every pw byte (KEY IV)
### generate a secret internal state byte for every pw byte (KEY HASH):
#### loop through the each pw byte & mix together its random byte & a counter value
#### set the final looped result as the state byte for that key byte
#### order the operations used to try & reduce getting a null byte output
#### end the internal state byte with the current counter value mixed in (CBC MODE)
## message cipher steps:
### use the looped key counter above as an index to get a key list item
### XOR the message byte with each byte in key item: random byte, pwd byte, state byte
### mix the cipher byte in with the internal state byte (new secret internal state)
### XOR the prior internal state byte with the cipher byte (cipher output)
## notes:
### this does not prevent or detect bit-flipping (integrity with a hash function)
import sys, random
def h(n):
  sys.stdout.write(hex(n).replace("x","")[-2:])
i=0xff; j=0x00; l=len(sys.argv[1]); s=sys.stdin.read()
if (s[:2]=="ff"):
  r=s[2:2+(l*2)]; t=s[2+(l*2):].strip()
  s="".join([chr(int(t[x:x+2],16)) for x in range(0,len(t),2)])
  k=[[int(r[x*2:(x*2)+2],16), ord(sys.argv[1][x])] for x in range(0,l)]
else:
  k=[[random.randint(0x00, 0xff), ord(d)] for d in sys.argv[1]]
  h(i); z=[h(d[0]) for d in k]; r=""
for e in k:
  for d in k:
    i=(((i+j)*(d[0]^d[1]))%256); j=((j+1)%256)
  e.append(i); i=((i+j)%256); j=((j+1)%256)
j=0
for c in s:
  p=ord(c); m=k[j%l]; o=(((p^m[0])^m[1])^m[2]); j=(j+1); n=(j%256)
  if (r==""):
    t=i; i=((k[o%l][0]+o+n)%256); o=(o^t); h(o)
  else:
    d=(p^i); t=i; i=((k[d%l][0]+d+n)%256); o=(o^t); sys.stdout.write(chr(o))

Cipher testing output (Message: 000000, Key: 000000):

a=$(echo '000000' | python ~/tmp/en.py "000000") ; echo "Cipher-Out: [$a]" ; \
k="000000" ; echo -n "Decrypt-Test[$k]: " ; echo "$a" | tr -d '|' | python ~/tmp/en.py "$k" ; \
for x in 1 2 3 ; do
k="00000$x" ; echo -n "Bad-Key-Test[$k]: "
echo "$a" | tr -d '|' | python ~/tmp/en.py "$k" | xxd | sed -e 's/^[^ ]* //' | sed -e 's/  [ ]*/|/g' | sed -e 's/\(..[ |]\)/ \1/g' | tr -s ' '
done
Cipher-Out: [ff372f4ca01971de08925718521c]
Decrypt-Test[000000]: 000000
Bad-Key-Test[000001]: 70 b0 bf f0 cc 98 0e|p......
Bad-Key-Test[000002]: 6c e0 ac c5 9d 59 54|l....YT
Bad-Key-Test[000003]: 5c 50 e0 c3 da a8 1f|\P.....
Playing around with XOR (my fav operation) during the holidays

Python: Layer-3 tunnel device traffic under client & server TCP sockets

# notes: PoC, single threaded, single client, single interface, single address, ipv4 over tcp
# 
# server: python tun.py -s 0.0.0.0 31337 key
#   ('[conn]:new', [socket._socketobject object at 0xb6b20c00], ('192.168.160.241', 64227))
#   ('[auth]:good', [socket._socketobject object at 0xb6b20c00])
# 
# client: python tun.py -c 1.2.3.4 31337 key
#   client: route add -host 1.2.3.4 [real gateway]
#   client: route add -net 0.0.0.0/0 10.0.0.1
#   client: host yahoo.com 4.2.2.1
#!/usr/bin/python

import os, sys
import ctypes, fcntl, struct
import select, socket, subprocess
import time, random, hashlib

def tclose(sobj):
	try:
		sobj.close()
	except:
		pass

def ksa4(k):
	s = []
	for i in range(0, 256):
		s.append(i)
	j = 0
	l = len(k)
	for i in range(0, 256):
		j = ((j + s[i] + ord(k[i % l])) % 256)
		t = s[i]; s[i] = s[j]; s[j] = t
	return (s, 0, 0)

def arc4(m, k):
	(s, i, j) = k
	l = len(m)
	o = ""
	for x in range(0, l):
		i = ((i + 1) % 256)
		j = ((j + s[i]) % 256)
		t = s[i]; s[i] = s[j]; s[j] = t
		c = s[(s[i] + s[j]) % 256]
		o += chr(ord(m[x]) ^ c)
	k = (s, i, j)
	return (o, k)

def set4(n, k):
	if (n == ""):
		n = (str(time.time()) + ":" + str(random.random()) + "-")
	s = ksa4(n + "s" + k)
	r = ksa4(n + "r" + k)
	(null, s) = arc4("x"*4096, s)
	(null, r) = arc4("x"*4096, r)
	return (n, s, r)

def client(h, p, n, k):
	f = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	f.connect((h, p))
	(enc, k) = arc4(n, k)
	f.send(n + enc)
	return (f, k)

# initialize some common variables
BUFFER_SIZE = 2000
PR_MODE = "client"
PR_HOST = "1.2.3.4"
PR_PORT = 31337
PR_SKEY = "key"
OS_TYPE = sys.platform
IF_CMD = ["/sbin/ifconfig", "tun0", "10.0.0.1", "10.0.0.2", "up"]

# process command line args usage: tun.py mode:[-c,-s] ip:[0.0.0.0] port:[4321] secret:[key]
if (sys.argv[1] == "-s"):
	PR_MODE = "server"
PR_HOST = sys.argv[2]
PR_PORT = int(sys.argv[3])
PR_SKEY = sys.argv[4]

# initialize the clients secret key state
last = 0
(nonce, skey, rkey) = set4("", PR_SKEY)

# define the remote connection socket descriptors - tcp client or tcp server
srv_fd = None
net_fd = None
if (PR_MODE == "client"):
	tmp = IF_CMD[2]
	IF_CMD[2] = IF_CMD[3]
	IF_CMD[3] = tmp
	(net_fd, skey) = client(PR_HOST, PR_PORT, nonce, skey)
else:
	srv_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	srv_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	srv_fd.bind(("0.0.0.0", PR_PORT))
	srv_fd.listen(1)

# determine how to bring up the tun0 interface - mac or linux
if (OS_TYPE == "darwin"):
	tun_fd = os.open("/dev/tun0", os.O_RDWR)
else:
	TUNSETIFF = 0x400454ca
	IFF_TUN = 0x0001
	IFF_TAP = 0x0002
	IFF_NO_PI = 0x1000
	
	tun_fd = os.open("/dev/net/tun", os.O_RDWR)
	ifr = struct.pack("16sH", "tun0", IFF_TUN | IFF_NO_PI)
	fcntl.ioctl(tun_fd, TUNSETIFF, ifr)
	
	IF_CMD.insert(3, "pointopoint")
subprocess.call(IF_CMD)

# main processing loop for network traffic tunneling
conns = []
while (1):
	#print("loop")
	
	# set the list of available socket descriptors to listen for
	fd_list = [tun_fd]
	if (srv_fd):
		fd_list.append(srv_fd)
	if (net_fd):
		fd_list.append(net_fd)
	for conn in conns:
		fd_list.append(conn)
	
	# select the available fds ready for reading
	rd = select.select(fd_list, [], [])
	
	# loop through each socket now to process each action
	for ready_fd in rd[0]:
		# if we are reading from our tun device then encrypt the message and write it to the network socket
		if (ready_fd == tun_fd):
			packet = os.read(tun_fd, BUFFER_SIZE)
			plen = len(packet)
			if ((plen > 0) and (net_fd)):
				(enc, skey) = arc4(packet, skey)
				smac = str(skey)
				hmac = hashlib.sha256(smac + enc + smac).digest()
				net_fd.send(enc + hmac)
		
		# if the server gets a new client connection then add it to a list to be authenticated next
		if (ready_fd == srv_fd):
			(conn, addr) = srv_fd.accept()
			print("[conn]:new",conn,addr)
			conns.append(conn)
		
		# if we get a network message decrypted and authenticated then write it to our tunnel device
		if (ready_fd == net_fd):
			data = net_fd.recv(BUFFER_SIZE)
			dlen = len(data)
			if (dlen > 0):
				try:
					temp = data[:-32]
					(dec, tkey) = arc4(temp, rkey)
					tmac = str(tkey)
					hmac = hashlib.sha256(tmac + temp + tmac).digest()
					if (hmac == data[-32:]):
						rkey = tkey
						os.write(tun_fd, dec)
					else:
						print("[hmac]:fail",hmac,data[-32:])
				except:
					dlen = 0
			if (dlen < 1):
				tclose(net_fd)
				net_fd = None
		
		# loop through any unauthenticated clients who have sent us data
		tmps = []
		conl = (len(conns) - 1)
		while (conl > -1):
			conn = conns[conl]
			
			# if the connection is ready
			if (ready_fd == conn):
				data = conn.recv(BUFFER_SIZE)
				dlen = len(data)
				indx = data.find("-")
				
				# if they sent us some actual data
				if ((dlen > 0) and (indx > -1)):
					# try to decrypt the nonce and verify an increase in the time
					try:
						temp = float(data.split(":")[0])
					except:
						temp = 0
					(tnonce, trkey, tskey) = set4(data[:indx+1], PR_SKEY)
					(dec, trkey) = arc4(data[indx+1:], trkey)
					
					# if the auth succeeds then setup the new key state and set the new client socket
					if ((temp > last) and (tnonce == dec)):
						print("[auth]:good",conn)
						
						nonce = tnonce
						skey = tskey
						rkey = trkey
						
						tclose(net_fd)
						net_fd = conn
						
						last = temp
						conns[conl] = None
					else:
						dlen = 0
				else:
					dlen = 0
				
				# if the auth failed then remove the client now
				if (dlen < 1):
					print("[auth]:fail",conns[conl])
					tclose(conns[conl])
					conns[conl] = None
			
			if (conns[conl]):
				tmps.append(conns[conl])
			conl -= 1
		
		conns = tmps[:]

Python: Layer-3 tunnel device traffic under client & server TCP sockets