When a vlan is not a vlan

What is a vlan? What is a vlan-id? Are they the same thing?

Generally yes, but in the ISP world a vlan-id can also be a circuit identifier. While your view of a vlan might be a single broadcast domain, you’ll soon see that multiple vlan IDs can share the same single broadcast domain, or the same vlan-id could be in a completely different broadcast domain.

The Problem

I’ve written about this before. Carriers, at least in the UK, are offering more and more aggregated links to Service Providers. Each circuit to customer sites is aggregated over a single high-bandwidth link to your PE router. This cuts down on ports, cables, and man hours to plug them in.

Old way:

carrier - old

New way:

carrier - new
How are the p2p circuits aggregated over the core high-bandwidth link? Each p2p link is separated by a vlan tag on the PoP side. So we could say that any packet coming out of the core PE with vlan 2000 goes to site 1, while packets with vlan 3000 go to site 2. What happens if site 1 and site 2 are going to the same customer? What if you are providing a VPLS service to them? It’s essential to note that the vlan tag imposed by the carrier is used simply to determine what packet goes to which circuit. As we control the MPLS core, it’s ultimately up to us to decide which packet belongs in which broadcast domain, and that is regardless of the vlan id used by the carrier.

Relevant Initial Core Config

I’ll use the following topology:
vlans-core

R1, R2, and R3 are the core of the network. R1 is a Brocade Netiron running MPLS. R2 is a Cisco me3600x running MPLS. R2 is an me3600x running bridge-groups with no MPLS.

CE1, CE2, and CE3 are all customer routers.

R1 – Brocade XMR

interface ethernet 2/4
 port-name TO-R2
 enable
 route-only
 ip ospf area 0
 ip ospf network point-to-point
 ip address 10.10.10.10/24
!
router mpls
 policy
  traffic-eng ospf area 0

  mpls-interface e2/4

 lsp R1-R2
  to 192.168.224.4
  adaptive
  enable

R2 – Cisco me3600x running MPLS

mpls traffic-eng tunnels
!
router ospf 1
 mpls traffic-eng router-id Loopback0
 mpls traffic-eng area 0
!
interface GigabitEthernet0/1
 description TO-R1
 no switchport
 ip address 10.10.10.11 255.255.255.0
 ip ospf network point-to-point
 ip ospf 1 area 0
 mpls traffic-eng tunnels
!
interface Tunnel0
 ip unnumbered Loopback0
 tunnel mode mpls traffic-eng
 tunnel destination 192.168.224.61
 tunnel mpls traffic-eng autoroute announce
 tunnel mpls traffic-eng path-option 5 dynamic
 tunnel mpls traffic-eng record-route

There is no IP and MPLS configuration on R3 as it’s not running MPLS. I’ll show how the bridge-group is configured when I get to that part.

CPE Config

I’ll be using vlan 3000 to get to CE1, vlan 2000 to get to CE2, and double-tag vlan 3500,2500 to get to CE3. Each CE has their WAN interface in the same subnet as each other running OSPF. I’ll also enable OSPF on their loopbacks and WAN links.

CE1

This is a Juniper EX3200:

[email protected]> show configuration interfaces ge-0/0/0
vlan-tagging;
unit 3000 {
    vlan-id 3000;
    family inet {
        address 1.1.1.1/24;
    }
}

[email protected]> show configuration interfaces lo0.0
family inet {
    address 10.10.10.10/32;
}

[email protected]> show configuration protocols ospf
area 0.0.0.0 {
    interface ge-0/0/0.3000;
    interface lo0.0;
}

CE2

This is a Cisco 3750G:

interface Loopback0
 ip address 20.20.20.20 255.255.255.255
 ip ospf 1 area 0
!
interface Vlan2000
 ip address 1.1.1.2 255.255.255.0
 ip ospf 1 area 0
!
interface GigabitEthernet1/0/1
 switchport trunk encapsulation dot1q
 switchport trunk allowed vlan 2000
 switchport mode trunk

CE3

This is a Cisco 1841:

interface Loopback0
 ip address 30.30.30.30 255.255.255.255
 ip ospf 1 area 0
!
interface FastEthernet0/0.32
 encapsulation dot1Q 3500 second-dot1q 2500
 ip address 1.1.1.3 255.255.255.0
 ip ospf 1 area 0

VPLS Config

As you can see, each CPE will be using a different vlan tag. One site is even sending a double-tagged frame. They all need to be in the same broadcast domain. No problem as we are simply going to use the vlan tag to determine the service.

R2

Gi0/2 will create a LDP-signalled VPLS VC to R1 (aka manual set up). Interface gi0/2 vlan 2000 will be part of VPLS id 501:

ethernet evc TEST-EVC
 uni count 20
!
l2vpn vfi context TEST-VPLS
 vpn id 501
 member 192.168.224.61 encapsulation mpls
!
interface GigabitEthernet0/2
 switchport trunk allowed vlan none
 switchport mode trunk
 mtu 9800
 service instance 1 ethernet TEST-EVC
  encapsulation dot1q 2000
  rewrite ingress tag pop 1 symmetric
  bridge-domain 501
 !
interface Vlan501
 no ip address
 member vfi TEST-VPLS

What’s important to note here is that the me3600x still uses bridge-groups for VPLS, but it’s not exactly the same as just using bridge-groups by itself. You’ll see this soon enough when we configure R3.

R1

R1 will create a VPLS to R2. Vlan 3000 on interface 2/5 will be part of the same VPLS:

router mpls
 vpls TEST-VPLS 501
  vpls-peer 192.168.224.4
  vpls-mtu 1500
  vlan 3000
   tagged ethe 2/5

At this point R1 and R2 have the VPLS set up between them. Each CE is using different vlans on their WAN, but they are in fact on the same broadcast domain:

CE2#sh ip ospf neighbor

Neighbor ID     Pri   State           Dead Time   Address         Interface
1.1.1.1         128   FULL/DR         00:00:39    1.1.1.1         Vlan2000

CE2#ping 1.1.1.1

Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/5/17 ms

CE2#ping 10.10.10.10 so lo0

Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.10.10.10, timeout is 2 seconds:
Packet sent with a source address of 20.20.20.20
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/4/9 ms

The vlan-id used on the CPE, was merely used to push the frame into the correct VPLS. The VPLS itself is the broadcast domain, the vlan tag is irrelevant as its stripped on inbound into the PE router. You CAN however, ensure that the PE router does NOT strip the vlan tag. This has interesting use cases when you purposely want to separate on vlan id with in the VPLS. I wrote more on this over here so please give it a read. Both the Brocade and Cisco default to VC mode 5 when setting up a VPLS.

Bridge Group Config

I’m going to set up R3 so that it only uses bridge-groups. No routing or MPLS involved. Bridge-Groups work very similar to VPLS, though it’s on a single box. Traffic can be pushed from a bridge-group into a VPLS if needed. The bridge-group determines the broadcast domain. I can have multiple different vlans in the same bridge group.

For R3, gi0/2 is the interface pointing towards the core, while gi0/1 is pointing towards the customer. I’ll use different vlan ids on each, but they will be in the same bridge-group:

ethernet evc TEST
!
vlan 501
 name TEST-CE
!
interface GigabitEthernet0/1
 switchport trunk allowed vlan none
 switchport mode trunk
 service instance 1 ethernet TEST
  encapsulation dot1q 501
  rewrite ingress tag pop 1 symmetric
  bridge-domain 501
 !
interface GigabitEthernet0/2
 switchport trunk allowed vlan none
 switchport mode trunk
 service instance 1 ethernet TEST
  encapsulation dot1q 3500 second-dot1q 2500
  rewrite ingress tag pop 2 symmetric
  bridge-domain 501

I’m not going into detail, but I will cover the basics. When gi0/2 receives a double-tagged frame that matches 3500,2500 inbound, the me3600x will pop both tags off and the resulting frame will be part of bridge-group 501. Symmetric means that when a frame leaves gi0/2, it will re-add vlans 3500,2500 on top of the frame. As gi0/1 is also in bridge-group 501, the customer frame will be forwarded out that port, and it will have a single vlan tag of 501 popped on top.

At this point gi0/1 is connected to R1 eth2/3. For this customer I would be expecting a single tag of 501 coming inbound, and so I’ll place that vlan id into the VPLS from above:

 vpls TEST-VPLS 501
  vlan 501
   tagged ethe 2/3

Now all three CE routers should be fully adjacent:

CE3#sh ip ospf neighbor

Neighbor ID     Pri   State           Dead Time   Address         Interface
1.1.1.1         128   FULL/DR         00:00:35    1.1.1.1         FastEthernet0/0.32
1.1.1.2           1   FULL/DROTHER    00:00:37    1.1.1.2         FastEthernet0/0.32

CE3#ping 10.10.10.10 so lo0

Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 10.10.10.10, timeout is 2 seconds:
Packet sent with a source address of 30.30.30.30
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/2/4 ms
CE3#ping 20.20.20.20 so lo0

Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 20.20.20.20, timeout is 2 seconds:
Packet sent with a source address of 30.30.30.30
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/4/12 ms

Conclusions:

vlan tags have multiple uses. In most networks it informs the switches which vlan, and therefore broadcast domain, a frame is part of. They can also be circuit identifiers showing which VPLS/Circuit the frame belongs to. They can also be both at the same time, depending on the VPLS VC type you’re using.

For the above network it’s extremely simplified. Care must be taken when forwarding certain layer2 control frames. Most are sent untagged out tagged interfaces. Cisco’s RSTP+ and STP tag each vlan BPDU with a the same vlan-id. If you’re using vlan 2000 on one side and vlan 3000 on the other, and the BPDU gets through, one side will shut down their WAN link due to receiving a BPDU with a vlan tag that doesn’t match the BPDU data inside the frame.

Function returns – Python

Truth, or not

Defined functions in Python will always return something, even if you don’t specifically return something. If nothing is returned, Python will actually return a value of ‘None’ which is still something. To illustrate this I’ll create a very simple function:

def test(x):
	pass

I’ll create a string and then pass it through the function and do an action based on the return value.

if test(a) is True:
	print(a,"is True")
else:
	print(a,"is not True")

	
string is not True

Python has a few handy shortcuts. Instead of evaluating if test(a) is True, I can simply say if test(a):

if test(a):
	print(a,"is True")
else:
	print(a,"is not True")

	
string is not True

If I return nothing in a function, ‘None’ is actually returned. Any 0 value, False, or None are all returned as non-true values. Let’s test a few return values and see what we get:

def test(x):
	return 1
if test(a):
	print(a,"is True")
else:
	print(a,"is not True")

	
string is True

def test(x):
	return -1
if test(a):
	print(a,"is True")
else:
	print(a,"is not True")

	
string is True

I can also implicitly return False values. The following three all have the same result:

def test(x):
	return 0

def test(x):
	return False

def test(x):
	return None
if test(a):
	print(a,"is True")
else:
	print(a,"is not True")

	
string is not True

Note that True, False, None, etc, are keywords and have their first letter capitalised.

Multiple returns

How are multiple values returned from a defined function? Let's check:

def test():
	a = 1
	b = 2
	c = 3
	return a,b,c
y = test()
type(y)
<class 'tuple'>

I'm simply calling the function. When a defined function returns multiple values, those values are returned in a tuple. As before, if any value is a false value we could use that later:

def test():
	a = 1
	b = 2
	c = 0
	return a,b,c
y = test()
>>> for i in y:
	if i:
	    print(i)
		
1
2

The value returned by c is not printed as its not True.

What goes in...

The parameters of a defined function does not have to match the argument we pass into it. The return value also doesn't have to match it:

def say_hello(x):
	a = "Hello "+x
	return a
print(say_hello("Darren"))
Hello Darren

Here I have passed in the string "Darren" as argument 'x'. The function creates a new variable 'a' which concatenates the string "Hello" with string "Darren" passed in. The value returned is the concatenation.
Another important thing to note here is that 'a' returns a value, 'a' itself is not returned. 'a' does not have scope outside the function:

a
Traceback (most recent call last):
  File "<pyshell#109>", line 1, in <module>
    a
NameError: name 'a' is not defined

I could create a global variable that takes the value of the output of the function:

 b = say_hello("Darren")
b
'Hello Darren'

My own view on the whole ‘CCIE is getting less important’ debate

There have been numerous debates recently about how important the CCIE is going forward. My views seem to differ form a lot of others so allow me to get on my own soapbox.

What I am sick of hearing is how a CCIE is just a ‘cli jockey’ – I have not, and have never, considered myself to be that. I’m also sick of hearing that you’re either this or you’re that. People are far more complicated that being either one thing or another.

Different people have different reasons for pursuing the CCIE. I did it because I wanted to prove to myself I could do it. Some people do it to get more money, others to get a job, others for who knows what reason. The CCIE is not an end into itself, it’s merely something you can do if you want.

Does having a CCIE mean that all I’m good at is punching some commands into a router? No, and I find is extremely insulting for anyone to think such a thing. I took a view of learning how things work and fit together. The CLI is there simply to allow you to get to your end goal. The actual commands for me were the easiest part of the entire lab exam. Learning how things fit together and how protocols operate is the real business side of the CCIE in my opinion. This is perhaps why I was able to do the CCIE SP so quickly after doing my JNCIE-SP. The commands, defaults, and capabilities of each is the only difference. The technology is the same.

The same goes for programming really. You have something you want to do. You could use a load of different languages to get there. The language you use is irrelevant to the underlying problem you are trying to solve.

If you think that the CCIE is merely there to teach you to be a ‘cli jockey’ then I feel pretty sorry for you.

The thought that a programmer is inherently better at designing a network than a network engineer is, is laughable. The very thought that you are either one or the other is also laughable. The thought that a programmer is a better problem solver because they are able to break down big problems into smaller ones is also incorrect. We’ve all been doing this for ages. The very act of troubleshooting and designing large networks is all about this. You don’t just draw one big cloud and say there’s your network. The devils in the smaller details and how each small part operates in the bigger picture to give you your end goal. This could be applied to so many things.

For me, learning itself is one of my favourite processes. Learning anything. Heck if I could somehow spend 6 months every year doing nothing but attending lectures and studying I would do it at the drop of a hat. It’s what I love to do. CCIE before, Python next. Then what? Maybe Physics? Who knows. Will it help me with my career? Probably not, but I’m pretty sure I’ll enjoy it nonetheless!

Passing an argument into Python

Up until now, I’ve been reading a file of a list of devices to log into and do some work on. That’s ideal when I have a load of routers to check, but sometimes I’d like to check one router quickly directly.

Using my OSPF Checker as an example,I currently iterate through a list of lines in devices.txt that the app then logs into. I can instead make it so that if an argument is passed in, that becomes the list and the file is ignored. If no argument is passed, I can read in the original list of devices.

To demonstrate, I’ll set up a really simple script:

#!/usr/bin/python3
import sys
print("You have passed in",sys.argv)

If I run this now form the cli I get the following:

[email protected]:~/git/ospf_checker$ ./arg.py
You have passed in ['./arg.py']

There is only a single argument, as the script itself counts itself as arg location 0. If I pass in an argument:

[email protected]:~/git/ospf_checker$ ./arg.py test
You have passed in ['./arg.py', 'test']

Most of the time I want to do some work on what’s been added after the script name. We can just be sure to look at that:

#!/usr/bin/python3
import sys
print("You have passed in",sys.argv[1])

Now when run:

[email protected]:~/git/ospf_checker$ ./arg.py test
You have passed in test

If I run the script without any arguments, I’ll get a traceback as I’m referencing an index value that doesn’t exist:

[email protected]:~/git/ospf_checker$ ./arg.py
Traceback (most recent call last):
  File "./arg.py", line 3, in 
    print("You have passed in",sys.argv[1])
IndexError: list index out of range

I can use this indexerror to do something else if no argument exists:

#!/usr/bin/python3
import sys
try:
    print("You have passed in",sys.argv[1])
except IndexError:
    print("No argument was passed, I'll do something else")
[email protected]:~/git/ospf_checker$ ./arg.py test
You have passed in test

[email protected]:~/git/ospf_checker$ ./arg.py
No argument was passed, I'll do something else

Very handy.

I’ve just scratched the surface of arguments so I’ll do a more in depth post sometime in the future

Defined Functions – Python

Currently my apps pretty much have a single main body in which all the data is collected, processed, and displayed/saved. A more Pythonic way of doing this is to get functions to do the work in a separate part of the application. In the main body I can then ask these functions to crunch some data I pass to it, and then receive a result back. At first this makes my code look a bit longer, but it should make it easier to debug in future as each defined function can be troubleshooted separately.

Let’s take a look at a very simple defined function:

>>> def square(x):
	x*=x
	return x

A function is defined named square. It accepts one argument. Whatever argument is passed to the function is squared, and the result passed back. I can now call square from the main body of the app and pass it my argument and I should get the square of that argument back:

>>> square(10)
100

If I have a variable with a value, I can pass the variable:

>>> a = 50
>>> square(a)
2500

It’s hard to see the benefits when I have a small app like above, but it makes a lot of sense in a much bigger app. I’ve now updated my OSPF Checker app to use definitions instead. The following is a few examples of the code:

def getip(i):
    ip = re.findall(r'Internet Address (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',i)
    return ip

def getarea(i):
    area = re.findall(r'Area ([\s]{0,3}[0-9]{1,5})',i)
    return area

for i in ospf:
    ip = getip(i)
    area = getarea(i)

Each definition can now be changed as a separate ‘app’ – A great example is when I ran my script on an LNS box with virtual-access interfaces. The getip function didn’t take into account the slightly different output:

Virtual-Access111 is up, line protocol is up
  Interface is unnumbered. Using address of Loopback56146 (10.9.0.250), Area 0, Attached via Network Statement

I can go in and just change the getip function:

def getip(i):
    ip = re.findall(r'Internet Address (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})',i)
    if not ip:
        ip = re.findall(r'Interface is unnumbered. Using address of Loopback[0-9]{0,5}',i)
    return ip

App now picks up those sorts of interfaces just fine:

Int:	Virtual-Access266 
IP:	Interface is unnumbered. Using address of Loopback78437
Area:	0
Type:	POINT_TO_POINT
Cost:	1
Hello:	10
Dead:	40