MATCH_MAC_SOURCE doesn't work as expected

Hello there,

I have the following set of rules :

drop not ethertype ipv4 and not ethertype arp and not ethertype ipv6;
accept macsrc ce:da:80:e0:29:67;
drop not chr ipauth;
accept;

With a member (let’s call it A) that does have ce:da:80:e0:29:67 indicated in the controller’s UI.

Messages from member A are dropped somewhere.

Here is the interface of member A:

2: ztREDACTED: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 2800 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether ce:da:80:e0:29:67 brd ff:ff:ff:ff:ff:ff
    inet 10.147.19.1/24 scope global ztREDACTED
       valid_lft forever preferred_lft forever

And here is the interface of member B:

6: ztREDACTED: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 2800 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether ce:3e:87:ba:ed:c3 brd ff:ff:ff:ff:ff:ff
    inet 10.147.19.253/24 brd 10.147.19.255 scope global ztREDACTED
       valid_lft forever preferred_lft forever

Here is a tcpdump made at member B:

15:21:30.588681 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
15:21:31.600179 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
15:21:32.624251 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
15:21:33.648421 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
15:21:34.676176 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28

And here is a tcpdump made at member A:

13:21:30.632453 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
13:21:30.632465 ce:da:80:e0:29:67 > ce:3e:87:ba:ed:c3, ethertype ARP (0x0806), length 42: Reply 10.147.19.1 is-at ce:da:80:e0:29:67, length 28
13:21:31.627069 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
13:21:31.627079 ce:da:80:e0:29:67 > ce:3e:87:ba:ed:c3, ethertype ARP (0x0806), length 42: Reply 10.147.19.1 is-at ce:da:80:e0:29:67, length 28
13:21:32.645644 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
13:21:32.645655 ce:da:80:e0:29:67 > ce:3e:87:ba:ed:c3, ethertype ARP (0x0806), length 42: Reply 10.147.19.1 is-at ce:da:80:e0:29:67, length 28
13:21:33.659106 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
13:21:33.659119 ce:da:80:e0:29:67 > ce:3e:87:ba:ed:c3, ethertype ARP (0x0806), length 42: Reply 10.147.19.1 is-at ce:da:80:e0:29:67, length 28
13:21:34.688282 ce:3e:87:ba:ed:c3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.147.19.1 tell 10.147.19.253, length 28
13:21:34.688295 ce:da:80:e0:29:67 > ce:3e:87:ba:ed:c3, ethertype ARP (0x0806), length 42: Reply 10.147.19.1 is-at ce:da:80:e0:29:67, length 28

There are no nftables rules at both members.

I am still looking for an answer. I’m expecting the rule to allow messages from any host that have the ce:da:80:e0:29:67 MAC address, but this is not what’s happening for me.

What are you expecting to happen and what is not happening?

Do you only want communication to happen if ce:da:80:e0:29:67 is involved, and no more? So hosts can talk to ce:da:80:e0:29:67 but not between eachother?

What I expect is that messages from A being received by B.
What I observe, as explained in the first message of this thread, is that messages from A are not reaching B.

I want communication to happen when ce:da:80:e0:29:67 is emitting messages (regardless of IP addresses used). The accept macsrc ce:da:80:e0:29:67; rule seems to be ignored and drop not chr ipauth; seems to be honored.

EDIT #1: The source-code for this part of the rules engine seems to do what the documentation says. So I don’t really understand why it’s not honored.

EDIT #2: To clarify, the rules doesn’t mean anything in themselves, they are just a minimal test to see whether an host with a given MAC address is allowed to send anything.

I labbed it and I’m seeing the same as you. Also tried bridging and filtered on those MACs in case that was the intent. Seems to just be broken.

If you need this to work, you can just do the VL1 address instead of the MAC. That seems to work just fine.

Thanks a lot for testing this :slight_smile:

I’ll raise an issue at GitHub and try to hunt the bug. But again, at a first glance the source-code seems to do what the doc says it should do.

I have found the solution by looking at the source-code. The string expected is an hexadecimal representation of the address, without separators, in big-endian. This can’t be done with the GUI of the official controller, see the spoilers below. I’ll file an issue on Github to improve documentation and fix the behavior with the :.

Explanation of the issue

Here is the code responsible for parsing JSON rules:

/* In the parser */

if (t == "MATCH_MAC_SOURCE") {
    rule.t |= ZT_NETWORK_RULE_MATCH_MAC_SOURCE;
    const std::string mac(OSUtils::jsonString(r["mac"],"0"));
    Utils::unhex(mac.c_str(),(unsigned int)mac.length(),rule.v.mac,6);
    return true;
}

/* The utils */

static inline unsigned int unhex(const char *h,unsigned int hlen,void *buf,unsigned int buflen)
{
    unsigned int l = 0;
    const char *hend = h + hlen;
    while (l < buflen) {
        if (h == hend) {
            break;
        }
        uint8_t hc = *(reinterpret_cast<const uint8_t *>(h++));
        if (!hc) {
            break;
        }

        uint8_t c = 0;
        if ((hc >= 48)&&(hc <= 57)) {
            c = hc - 48;
        } else if ((hc >= 97)&&(hc <= 102)) {
            c = hc - 87;
        } else if ((hc >= 65)&&(hc <= 70)) {
            c = hc - 55;
        }

        if (h == hend) {
            break;
        }
        hc = *(reinterpret_cast<const uint8_t *>(h++));
        if (!hc) {
            break;
        }

        c <<= 4;
        if ((hc >= 48)&&(hc <= 57)) {
            c |= hc - 48;
        } else if ((hc >= 97)&&(hc <= 102)) {
            c |= hc - 87;
        } else if ((hc >= 65)&&(hc <= 70)) {
            c |= hc - 55;
        }

        reinterpret_cast<uint8_t *>(buf)[l++] = c;
    }
    return l;
}

We can see that unhex doesn’t ignore the colon (:) character.

This conflicts with the official compiler, that automatically reformat addresses and inserts : in them.

The human-format to JSON compiler:

switch(match) {
/* snip */
    case 'macsrc':
    case 'macdest': {
        let mac = _cleanMac(args[0][0]);
        if (mac.length !== 17)
            return [ args[0][1],args[0][2],'Invalid MAC address.' ];
        rules.push({
            'type': KEYWORD_TO_API_MAP[match],
            'not': not,
            'or': or,
            'mac': mac
        });
    }	break;
/* snip */
}

The _cleanMac function:

function _cleanMac(m)
{
    m = m.toLowerCase();
    var m2 = '';
    let charcount = 0;
    for(let i=0;((i<m.length)&&(m2.length<17));++i) {
        let c = m.charAt(i);
        if ("0123456789abcdef".indexOf(c) >= 0) {
            m2 += c;
            charcount++;
            if ((m2.length > 0)&&(m2.length !== 17)&&(charcount >= 2) ) {
                m2 += ':';
                charcount=0;
            }
        }
    }
    return m2;
}

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.