Playing with Arista eAPI – Dynamic VLAN Assignment-ish


A note

Before we get started, let me first say that just because we can, doesn’t mean we should.  I got a request to show an example of how dynamic VLAN assingment based on mac-address could work and this is the results of that request. Before anyone takes this code and runs with it, let me first say that you really need to think about whether or not this is a good idea for you and your organization.

If you want dynamic VLAN assignment, there are other ways to accomplish this and I would encourage you to look into them before going down this path.

That being said – It was a fun little project and I wanted to share it with you all.

The Project

For this project, the goal is to write a simple script to help us automatically assign the right VLAN to a host based on the MAC address of that host.

Now we all know there are different ways to do this, like pure mac-authentication using a RADIUS server, or even using a mac-based VLAN configuration, but that wouldn’t be as fun as trying to get this work with Arista’s eAPI now would it?

The Plan

So our basic plan is the following

  • Query the Arista eAPI to request all of the mac addresses.
  • Look through the eAPI response for the target mac address while ignoring the uplink ports that connects the switches
  • Once the interface where the host is connected has been identified, send another Arista eAPI call to assign the appropriate VLAN to the target interface.

*Note: This is a simple project and should NOT be implemented into a production environment without REALLY thinking about the various failure modes.

First we’ll do the imports. For this project, we’ll import the standard json library as well as the Arista pyeapi library.

note: This assumes you’ve actually pip install pyeapi’d this library into your environment. You did do that, right?

In [30]:
import json
import pyeapi

Create the Switches Variable

Now that we’ve imported the libraries we’ll need, let’s start setting up the variables

First we’ll create a variable called switches that will be a python list of strings which in IPv4 address format. One for each switch which we want to search through.

In [23]:
switches = ['10.101.0.249', '10.101.0.250']

As with most things with automation, you have two choices, standardize or write a LOT of code to keep up with the variable conditions. For my purposes, I want to keep this straightforward so I will be deciding that uplinks, or connections/trunks, between the two switches will be using interface Ethernet1 only.

This may sound a bit strange, but remember that in normal operations, an ARP will show up on the link that interconnects two switches as it travels through the network. I’m assuming you don’t want to be changing the VLAN on the interfaces that connect your switches, right?

We will now create a variable called uplinks that will be a python string object with the name of the ethernet interface as it would be displayed in the eAPI response. Basically we’ll be saying if the name is what’s contained in the uplink variable, then don’t do anything.

In [24]:
uplink = 'Ethernet1'

Create the target variable

Our last variable will be to set up the actual target host that we want to find and assign to a specific VLAN. You will need to make sure that the format of the mac-address must be consistent with what’s in the Arista eAPI response.

As we all know, mac-address can be displayed in various different formats including

  • xx:xx:xx:xx:xx:xx
  • xxxx.xxxx.xxxx
  • xxxx:xxxx:xxxx
  • xxxxxxxxxxxx
  • note: If we were doing this right, we would probably want to include some code that would normalize the mac-addresses into a consistent format regardless of the input format. But that’s code for another day. *

The other thing we’ll need to do is to include the dot1q vlan tag.

We will now create a variable called target that will be a python dict object with two key-value pairs. One for the mac-address, and one for the dot1q VLAN ID. This should be very obvious

In [25]:
target = {"mac" : '00:0d:6f:40:1b:e0', "vlan" : "3"}

Gathering the MAC addresses

Now that we’ve staged all variables, we can get down to writing some code.

The first function we’re going to create a python function to gather the mac-addresses from our list of switches and return a list of the mac-addresses.

In [26]:
def gather_macs(devices):
    """
    Function which takes a list of devices as input and runs the show mac address-table command against them
    using the Arista pyeapi library
    :param devices:
    :return: list of mac-address output
    """
    found_macs = []
    for switch in devices:
        node = pyeapi.connect(host=switch, username='admin', password='password')
        output = node.send('show mac address-table')
        output['switch'] = switch
        found_macs.append(output)
    print (len(found_macs))
    return found_macs

Now we can run this and assign the output to a python variable.

In [34]:
mac_list = gather_macs(switches)
mac_list
2
Out[34]:
[{'id': None,
  'jsonrpc': '2.0',
  'result': [{'multicastTable': {'tableEntries': []},
    'unicastTable': {'tableEntries': [{'entryType': 'dynamic',
       'interface': 'Ethernet48',
       'lastMove': 1521485142.203959,
       'macAddress': '00:0d:6f:40:1b:e0',
       'moves': 1,
       'vlanId': 1},
      {'entryType': 'dynamic',
       'interface': 'Ethernet17',
       'lastMove': 1516790007.97397,
       'macAddress': '00:1c:73:8c:92:75',
       'moves': 1,
       'vlanId': 1}]}}],
  'switch': '10.101.0.249'},
 {'id': None,
  'jsonrpc': '2.0',
  'result': [{'multicastTable': {'tableEntries': []},
    'unicastTable': {'tableEntries': [{'entryType': 'dynamic',
       'interface': 'Ethernet1',
       'lastMove': 1521485429.933993,
       'macAddress': '00:0d:6f:40:1b:e0',
       'moves': 1,
       'vlanId': 1},
      {'entryType': 'dynamic',
       'interface': 'Ethernet1',
       'lastMove': 1521421510.48407,
       'macAddress': '00:1c:73:8c:92:75',
       'moves': 1,
       'vlanId': 1}]}}],
  'switch': '10.101.0.250'}]

Now that we’ve got the list of mac-address from the switches that we defined in the switches variable. To do that, we need to write some more python code which will look through the list we created in the last step and look through that mac-list to see if the mac-address is present

In [21]:
def find_mac(target, list_of_macs):
    mac_location = []
    for switch in list_of_macs:
        for entry in switch['result'][0]['unicastTable']['tableEntries']:
            if entry['macAddress'] == target['mac']:
                print ("I found the mac_address " + target['mac'] + " on " + switch['switch'] + " on interface " + entry['interface'])
                mac_location.append({'mac': target['mac'], 'switch': switch['switch'], 'interface' : entry['interface']})
    return mac_location

Now that we’ve created the function, we can test this out using the following bit of code. Remember we created the variable target up above? That’s what we’re using here as the input.

In [28]:
host = find_mac(target, mac_list)
host
I found the mac_address 00:0d:6f:40:1b:e0 on 10.101.0.249 on interface Ethernet48
I found the mac_address 00:0d:6f:40:1b:e0 on 10.101.0.250 on interface Ethernet1
Out[28]:
[{'interface': 'Ethernet48',
  'mac': '00:0d:6f:40:1b:e0',
  'switch': '10.101.0.249'},
 {'interface': 'Ethernet1',
  'mac': '00:0d:6f:40:1b:e0',
  'switch': '10.101.0.250'}]

As you can see, we found two occurrences of the mac-address, the bottom one of two items in this list has the interface of Ethernet1 which means this is the uplink port between the two switches, remember?

So now we have to write some code to exclude the uplink interfaces which you can see below in the if entry[‘interface’] == uplink
phrase below.

If it’s not in the uplink list, then we need to use issue a command to the specific Arista switch to the specific interface where we found that mac-address.

In [35]:
def set_vlan(mac_location, target):
    for entry in mac_location:
        if entry['interface'] == uplink:
            print ("Skip the uplink")
            continue
        else:
            node = pyeapi.connect(host=entry['switch'], username='admin', password='password')
            data = {
  "jsonrpc": "2.0",
  "method": "runCmds",
  "params": {
    "format": "json",
    "timestamps": False,
    "autoComplete": False,
    "expandAliases": False,
    "cmds": [
      {
        "cmd": "enable",
        "input": "my_enable_passw0rd"
      },
      "configure",
      "interface " + entry['interface'],
      "switchport access vlan " + target['vlan']
    ],
    "version": 1
  },
  "id": "EapiExplorer-1"
}
            print (entry['switch'])
            print (data)
            output = node.send(json.dumps(data))
            print (output)

Now we use the output of the find_mac() location that we got above combined with the VLAN information that we stored in the target variable.

In [36]:
output = set_vlan(host, target)
print (output)
10.101.0.249
{'method': 'runCmds', 'params': {'expandAliases': False, 'autoComplete': False, 'timestamps': False, 'version': 1, 'format': 'json', 'cmds': [{'cmd': 'enable', 'input': 'my_enable_passw0rd'}, 'configure', 'interface Ethernet48', 'switchport access vlan 3']}, 'jsonrpc': '2.0', 'id': 'EapiExplorer-1'}
{'result': [{}, {}, {}, {}], 'jsonrpc': '2.0', 'id': 'EapiExplorer-1'}
Skip the uplink
None
Now for the last part, we will write a quick bit of code to check the vlan of the interface to make sure that it’s actually been set in the right VLAN.
In [56]:
def check_int_vlan(switch, interface):
    node = pyeapi.connect(host=switch, username='admin', password='password')
    output = node.send('show mac address-table')
    return output
vlans = check_int_vlan('10.101.0.249', 'Ethernet48')['result'][0]['unicastTable']
for i in vlans['tableEntries']:
    print ("Mac address " + i['macAddress'] + " is located on interface " + i['interface'] + " in VLAN " + str(i['vlanId']))
Mac address 00:1c:73:8c:92:75 is located on interface Ethernet17 in VLAN 1
Mac address 00:0d:6f:40:1b:e0 is located on interface Ethernet48 in VLAN 3

Closing Thoughts

Hopefully you follow the logical process of how you would go about fulfilling the requirements. One thing I do want to leave you thinking about though is whether the goal made sense in the first place? As well, what additional assumptions and operational process needs to be in place to make this a reality. Questions like: Is the mac-address right right identifier? Where did you get the mac-address? Is this the physical mac-address or a virtual mac-address?

Feel free to post in the comments below if you feel inspired.

@netmanchris

Advertisements

2 thoughts on “Playing with Arista eAPI – Dynamic VLAN Assignment-ish

  1. If you have a reason to do this, have you considered doing it from the switch itself? You could grab the EOS SDK and have the switch automatically do this when the MAC is seen on a port.

    1. Thanks for stopping by! I honestly hadn’t considered that approach but that’s totally doable as well. But this also adds in complexity in that you’d have to somehow get the lists to all of the switches and keep them updated. I’m not convinced this is the right way to do this. Haven’t thought it all the way through, but I suspect there might be a better way to do this using hostnames or somehow connect to something other than MAC address on the host. MAC addresses are too easy to spoof. Maybe even something like connecting to an IPAM and then do some conditional logic from there? This is definitely not production grade code. More something to just go through the process as a proof of concept to show it could be done.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s