Configuring E-LAN (VPLS) service over the MPLS cloud using CSR1000v routers

I'm going to build a L2VPN service for Customer 1 sites (purple), and create an "emulated" switching environment within the MPLS core: CE1, CE3, and CE5 routers are going to be placed in the same broadcast domain, the MPLS core will be completely transparent for Customer 1. The whole MPLS core will behave like a huge switch: it learns MAC addresses on the ports of the PE routers, and floods BUM (broadcast/unknown unicast/multicast) traffic to all remote PEs. 
Note: L2 VPN services are not supported on regular IOSv images, so I'll be using IOS-XE with the CSR1000v images, they will be the PEs in this topology (PE1-2-3). Just ignore the Customer 2 sites, the Route Reflector and everything related to iBGP in this lab, they're used for L3VPN services. For L2VPN we'll only need LDP as the control plane within the MPLS core. So let's begin!
Topology
 
First let's enable MPLS globally under the OSPF process and establish the LDP neighborships within the core. I also create a custom label range for each router in the core for troubleshooting purposes, like this:
 
R6_PE2(config)#mpls label range 600 699
R6_PE2(config-router)#mpls ldp autoconfig 
R7_PE2(config)#mpls label range 700 799
R7_PE2(config-router)#mpls ldp autoconfig 
R8_PE3(config)#mpls label range 800 899
R8_PE3(config-router)#mpls ldp autoconfig 
 
Next, we create a service instance on the customer facing interfaces, for PE2 that's G3 for example:
 
R6_PE2(config)#interface g3
R6_PE2(config-if)#no ip address 
R6_PE2(config-if)#no shutdown 
R6_PE2(config-if)#service instance 100 ethernet 
R6_PE2(config-if-srv)#encapsulation default 
R6_PE2(config-if-srv)#bridge-domain 678
 
The service instance number is locally significant here (doesn't have to match with the other PEs), but the bridge domain has to match. We also create the bridge domain globally to be able to learn the MAC addresses, and forward frames based on MAC addresses like a real switch does (in some cases this is not necessary, it'll be automatically created):
 
R6_PE2(config)#bridge-domain 678
R6_PE2(config-bdomain)#
 
Next we create the l2 vfi (Virtual Forwarding Instance) like this:
 
R6_PE2(config)#l2 vfi VPLS manual 
R6_PE2(config-vfi)#vpn id 123
R6_PE2(config-vfi)#bridge-domain 678
R6_PE2(config-vfi)#neighbor 150.7.7.7 encapsulation mpls 
R6_PE2(config-vfi)#neighbor 150.8.8.8 encapsulation mpls 
 
Here the vpn id and the bridge domain has to match with all other PEs. And we need an individual neighbor statement for all other PEs: here we have 3 sites so we need 2 neighbor statement for PE1 (150.7.7.7) and PE3 (150.8.8.8) in this case. [These addresses should already be present in the RIB and learned via OSPF]. So just like with iBGP, we need a full-mesh configuration, if you have a lot of PEs this soulution is obviously not going to scalabe, in that case you'll need BGP. But otherwise we need a targeted LDP session with every other PE router.
The moment we configure the individual neighbor statements a targeted LDP Hello (Discovery) is sent to the remote PE, and a targeted LDP session is established between the loopbacks:
 
R7_PE2(config-vfi)# neighbor 150.6.6.6 encapsulation mpls   
R7_PE2(config-vfi)#
*Jun 17 11:03:06.202: %LDP-5-NBRCHG: LDP Neighbor 150.6.6.6:0 (2) is UP
 
So we use LDP as the control plane, here is an example of an LDP Label Mapping message:
 
Targeted LDP session Label Mapping message
 
Here you can see VPN label (inner label) information for PE1 (708), the MTU for VPLS instance (1500 bytes, more on this later), and the PW ID (123, which is the vpn id under the l2 vfi) which has to match with the other PEs.
 
Next let's configure CE routers: they have to be in the same subnet (in this example 192.168.0.0/24, we use the router number in the last octet). I also enable OSPF on the MPLS core facing interfaces to demonstate that they'll be establishing adjacency with each other:
 
interface GigabitEthernet0/0
 ip address 192.168.0.9 255.255.255.0
 ip ospf 1 area 0
 
R9_CE1(config-if)#do show ip ospf neighbor 
 
Neighbor ID     Pri   State           Dead Time   Address         Interface
1.11.11.11        1   FULL/DROTHER    00:00:36    192.168.0.11    GigabitEthernet0/0
1.13.13.13        1   FULL/DR         00:00:32    192.168.0.13    GigabitEthernet0/0
 
We have a DR, BDR and DROTHER in the subnet, and they've established neighbor relationship with each other, since they are in an emulated broadcast domain.
From CE3's perspective CE1 is a single hop:
 
R11_CE3#traceroute 1.9.9.9 so lo0 numeric 
Type escape sequence to abort.
Tracing the route to 1.9.9.9
VRF info: (vrf in name/id, vrf out name/id)
  1 192.168.0.9 4 msec *  2 msec
 
For verification we can use the following:
 
R6_PE2#show vfi 
Legend: RT=Route-target, S=Split-horizon, Y=Yes, N=No
 
VFI name: VPLS, state: up, type: multipoint, signaling: LDP
  VPN ID: 123
  Bridge-Domain 678 attachment circuits:
  Neighbors connected via pseudowires:
  Peer Address     VC ID        S
  150.8.8.8        123          Y
  150.7.7.7        123          Y
 
 
R6_PE2#show mpls ldp neighbor 
    Peer LDP Ident: 150.3.3.3:0; Local LDP Ident 150.6.6.6:0
        TCP connection: 150.3.3.3.646 - 150.6.6.6.14778
        State: Oper; Msgs sent/rcvd: 21/23; Downstream
        Up time: 00:07:24
        LDP discovery sources:
          GigabitEthernet1, Src IP addr: 10.0.36.3
        Addresses bound to peer LDP Ident:
          10.0.35.3       10.0.13.3       10.0.36.3       10.0.38.3       
          150.3.3.3       
    Peer LDP Ident: 150.7.7.7:0; Local LDP Ident 150.6.6.6:0
        TCP connection: 150.7.7.7.53113 - 150.6.6.6.646
        State: Oper; Msgs sent/rcvd: 23/23; Downstream
        Up time: 00:07:24
        LDP discovery sources:
          Targeted Hello 150.6.6.6 -> 150.7.7.7, active, passive
        Addresses bound to peer LDP Ident:
          10.0.17.7       10.0.27.7       150.7.7.7       
    Peer LDP Ident: 150.8.8.8:0; Local LDP Ident 150.6.6.6:0
        TCP connection: 150.8.8.8.25069 - 150.6.6.6.646
        State: Oper; Msgs sent/rcvd: 23/23; Downstream
        Up time: 00:07:24
        LDP discovery sources:
          Targeted Hello 150.6.6.6 -> 150.8.8.8, active, passive
        Addresses bound to peer LDP Ident:
          10.0.18.8       10.0.38.8       150.8.8.8       
 
Use this for the control plane, targeted LDP sessions must be established between the PEs; make sure the loopbacks are advertised with your IGP in the core. For data plane use this:
 
R6_PE2#show bridge-domain 
Bridge-domain 678 (3 ports in all)
State: UP                    Mac learning: Enabled
Aging-Timer: 300 second(s)
Maximum address limit: 65536
    GigabitEthernet3 service instance 100
    vfi VPLS neighbor 150.7.7.7 123
    vfi VPLS neighbor 150.8.8.8 123
   AED MAC address    Policy  Tag       Age  Pseudoport
   0   5254.0018.1618 forward dynamic   296  VPLS.404012
   0   5254.0002.3C01 forward dynamic   300  GigabitEthernet3.EFP100
   0   5254.0019.4A14 forward dynamic   295  VPLS.404011
 
In this example we can see the local CE and the 2 remote CEs MAC addresses, and also the MAC address aging timer decreasing (300 sec by default). Here is a packet capture between PE2 (csr1000v-6) and R3 (iosv-3):
 
VPLS data plane: VPN + Transport MPLS labels
 
Just like with L3VPN services we have 2 labels: an inner label (VPN label) of 807 which identifies the remote PE (so this frame is destined to csr1000v-8 [PE3]), and an outer label (transport label) of 304, so this frame was sent to R3 in the MPLS core. Just like with L3VPNs R3 is going to perform PHP and remove the transport label before forwarding the frame to the remote PE. The content of the data packet is a control packet for the VPLS servicein this case: it's an OSPF Link State Update which was sent to the DR based on the destination address (224.0.0.6), so CE5 (iosv-13) must be the DR according to this LSU. So we can confirm what we have seen with the show ip ospf neighbor command previously.