L2VPN EVPL 테스트 Version 2

1. 시나리오 개요

본 문서는 NSO(Network Services Orchestrator)를 사용하여 두 개의 사이트를 L2VPN으로 연결하고, 각 사이트의 VLAN 10, 20, 30을 서로 통신시키는 것을 목표

  • 물리적 구성

    • PE 라우터: R1, R3

    • 액세스 스위치: Switch1 (R1 측), Switch2 (R3 측)

    • 엔드포인트: VPC37/38/39 (R1 측), VPC40/41/42 (R3 측)

  • 논리적 연결 목표

    • VLAN 10 (VPC37) <==> VLAN 10 (VPC40)

    • VLAN 20 (VPC38) <==> VLAN 20 (VPC41)

    • VLAN 30 (VPC39) <==> VLAN 30 (VPC42)

  • 토폴로지


2. NSO 서비스 패키지 최종 코드

2.1. l2vpn.yang (서비스 모델)

module l2vpn {
  namespace "http://example.com/l2vpn";
  prefix l2vpn;

  import ietf-inet-types {
    prefix inet;
  }
  import tailf-ncs {
    prefix ncs;
  }

  description
    "L2VPN service using direct interface rewrite for EVPL.";

  revision 2025-07-29 {
    description "Final version using direct rewrite for EVPL+dot1q scenario.";
  }

  list l2vpn {
    key name;
    description "List of L2VPN service instances.";

    uses ncs:service-data;
    ncs:servicepoint "l2vpn-servicepoint";

    leaf name {
      type string;
      description "Unique name for this L2VPN service instance.";
    }

    leaf device {
      type leafref {
        path "/ncs:devices/ncs:device/ncs:name";
      }
      mandatory true;
      description "The target device on which to configure the L2VPN.";
    }

    container local-l2transport-interface {
      description "Configuration for the local L2 transport interface.";
      
      leaf interface-type {
        type enumeration {
          enum "GigabitEthernet";
          enum "TenGigE";
          enum "HundredGigE";
        }
        mandatory true;
        description "Type of the L2 transport interface.";
      }

      leaf interface-main-id {
        type string;
        mandatory true;
        description "Main part of the interface ID (e.g., '0/0/0/0').";
      }

      leaf subinterface-id {
        type uint32;
        mandatory true;
        description "Subinterface ID, which will be appended after a dot (e.g., 100).";
      }
      
      leaf mtu {
        type uint16;
        default 1522;
        description "MTU for the L2 transport interface.";
      }

      leaf encapsulation-type {
        type enumeration {
          enum "dot1q";
          enum "untagged";
          enum "default";
        }
        mandatory true;
        description "Encapsulation type for the L2 transport interface.";
      }

      leaf vlan-id {
        when "../encapsulation-type = 'dot1q'";
        type uint16 {
          range "1..4094";
        }
        mandatory true;
        description "VLAN ID for dot1q encapsulation.";
      }

      leaf service-type {
        type enumeration {
          enum "epl";
          enum "evpl";
        }
        mandatory true;
        description "Select the L2VPN service type.";
      }

      container rewrite-policy {
        when "../service-type = 'evpl' and ../encapsulation-type = 'dot1q'";
        description "Rewrite policy settings for EVPL.";
        leaf rewrite-type {
          type enumeration {
            enum "none";
            enum "pop1";
            enum "translate1to1";
          }
          mandatory true;
          description "Type of ingress rewrite operation.";
        }
        leaf rewrite-vlan-id {
          when "../rewrite-type = 'translate1to1'";
          type uint16 {
            range "1..4094";
          }
          mandatory true;
          description "VLAN ID for 1-to-1 translate rewrite.";
        }
      }
    }

    container pw-class {
      description "Pseudowire class configuration for the L2VPN.";
      leaf pw-class-name {
        type string {
          pattern "[a-zA-Z0-9_-]+";
        }
        mandatory true;
        description "Name of the pseudowire class.";
      }
      leaf control-word {
        type boolean;
        default "false";
        description "Enable or disable control-word for the pseudowire.";
      }
    }

    container xconnect {
      description "Xconnect group and point-to-point connection configuration.";
      leaf group-name {
        type string {
          pattern "[a-zA-Z0-9_-]+";
        }
        mandatory true;
        description "Name of the xconnect group (e.g., Provisioned).";
      }
      leaf p2p-name {
        type string {
          pattern "[a-zA-Z0-9_-]+";
        }
        mandatory true;
        description "Name of the point-to-point connection (e.g., epl1).";
      }
      leaf neighbor-ip {
        type inet:ipv4-address;
        mandatory true;
        description "Neighbor IPv4 address for the pseudowire (e.g., 10.10.10.10).";
      }
      leaf pw-id {
        type uint32;
        mandatory true;
        description "Pseudowire ID (e.g., 13).";
      }
      leaf pw-class-ref {
        type leafref {
          path "../../pw-class/pw-class-name";
        }
        mandatory true;
        description "Reference to the pseudowire class to be used for this xconnect.";
      }
    }
  }
}

2.2. main.py 

import ncs
from ncs.application import Service

class ServiceCallbacks(Service):

    @Service.create
    def cb_create(self, tctx, root, service, proplist):
        self.log.info(f"Service create for '{service.name}' using direct rewrite logic.")
        
        tvars = ncs.template.Variables()
        iface = service.local_l2transport_interface
        
        # rewrite 관련 변수 초기화
        l2_serv_ingress_rewrite_type = 'none'
        l2_serv_ingress_rewrite_vlan = 0

        # rewrite_policy 컨테이너가 있는지 확인
        if hasattr(iface, 'rewrite_policy'):
            rewrite_policy = iface.rewrite_policy
            l2_serv_ingress_rewrite_type = rewrite_policy.rewrite_type
            if l2_serv_ingress_rewrite_type == 'translate1to1' and hasattr(rewrite_policy, 'rewrite_vlan_id'):
                l2_serv_ingress_rewrite_vlan = rewrite_policy.rewrite_vlan_id
        
        # 템플릿에 rewrite 변수 전달
        tvars.add('L2_SERV_INGRESS_REWRITE_TYPE', l2_serv_ingress_rewrite_type)
        tvars.add('L2_SERV_INGRESS_REWRITE_VLAN', l2_serv_ingress_rewrite_vlan)

        # 나머지 모든 변수 전달
        interface_id_combined = f"{iface.interface_main_id}.{iface.subinterface_id}"
        full_interface_name = f"{iface.interface_type}{interface_id_combined}"
        
        tvars.add('DEVICE', service.device)
        tvars.add('INTERFACE_TYPE', iface.interface_type)
        tvars.add('INTERFACE_ID', interface_id_combined)
        tvars.add('FULL_INTERFACE_NAME', full_interface_name)
        tvars.add('MTU', iface.mtu)
        tvars.add('L2_SERV_ENCAP', iface.encapsulation_type)
        tvars.add('L2_SERV_VLAN_ID', iface.vlan_id if hasattr(iface, 'vlan_id') else 0)
        
        pw = service.pw_class
        tvars.add('PW_CLASS_NAME', pw.pw_class_name)
        tvars.add('CONTROL_WORD_ENABLED', 'true' if pw.control_word else 'false')
        
        xc = service.xconnect
        tvars.add('XCONNECT_GROUP_NAME', xc.group_name)
        tvars.add('P2P_NAME', xc.p2p_name)
        tvars.add('NEIGHBOR_IP', xc.neighbor_ip)
        tvars.add('PW_ID', xc.pw_id)
        tvars.add('PW_CLASS_REF', xc.pw_class_ref)

        template = ncs.template.Template(service)
        template.apply('l2vpn-template', tvars)

class L2vpn(ncs.application.Application):
    def setup(self):
        self.log.info('L2vpn RUNNING')
        self.register_service('l2vpn-servicepoint', ServiceCallbacks)

    def teardown(self):
        self.log.info('L2vpn FINISHED')

2.3. l2vpn-template.xml (디바이스 템플릿)

<config xmlns="http://tail-f.com/ns/config/1.0"
                 xmlns:ncs="http://tail-f.com/ns/ncs">
  <devices xmlns="http://tail-f.com/ns/ncs">
    <device>
      <name>{$DEVICE}</name>
      <config>
        
        <interface xmlns="http://tail-f.com/ned/cisco-ios-xr">
          <GigabitEthernet-subinterface ncs:when="{$INTERFACE_TYPE = 'GigabitEthernet'}">
            <GigabitEthernet>
              <id>{$INTERFACE_ID}</id>
              <mode>l2transport</mode>
              <mtu>{$MTU}</mtu>
              <encapsulation>
                <default ncs:when="{$L2_SERV_ENCAP = 'default'}"/>
                <dot1q ncs:when="{$L2_SERV_ENCAP = 'dot1q'}">
                  <vlan-id ncs:when="{$L2_SERV_VLAN_ID != 0}">{$L2_SERV_VLAN_ID}</vlan-id>
                </dot1q>
                <untagged ncs:when="{$L2_SERV_ENCAP = 'untagged'}"/>
              </encapsulation>
              <rewrite ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE != 'none'}">
                <ingress>
                  <tag>
                    <pop ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'pop1'}">1</pop>
                    <translate ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">1-to-1</translate>
                    <dot1q ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1' and $L2_SERV_INGRESS_REWRITE_VLAN != 0}">{$L2_SERV_INGRESS_REWRITE_VLAN}</dot1q>
                    <mode ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">symmetric</mode>
                  </tag>
                </ingress>
              </rewrite>
            </GigabitEthernet>
          </GigabitEthernet-subinterface>

          <TenGigE-subinterface ncs:when="{$INTERFACE_TYPE = 'TenGigE'}">
            <TenGigE>
              <id>{$INTERFACE_ID}</id>
              <mode>l2transport</mode>
              <mtu>{$MTU}</mtu>
              <encapsulation>
                <default ncs:when="{$L2_SERV_ENCAP = 'default'}"/>
                <dot1q ncs:when="{$L2_SERV_ENCAP = 'dot1q'}">
                  <vlan-id ncs:when="{$L2_SERV_VLAN_ID != 0}">{$L2_SERV_VLAN_ID}</vlan-id>
                </dot1q>
                <untagged ncs:when="{$L2_SERV_ENCAP = 'untagged'}"/>
              </encapsulation>
              <rewrite ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE != 'none'}">
                <ingress>
                  <tag>
                    <pop ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'pop1'}">1</pop>
                    <translate ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">1-to-1</translate>
                    <dot1q ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1' and $L2_SERV_INGRESS_REWRITE_VLAN != 0}">{$L2_SERV_INGRESS_REWRITE_VLAN}</dot1q>
                    <mode ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">symmetric</mode>
                  </tag>
                </ingress>
              </rewrite>
            </TenGigE>
          </TenGigE-subinterface>
          
          <HundredGigE-subinterface ncs:when="{$INTERFACE_TYPE = 'HundredGigE'}">
            <HundredGigE>
              <id>{$INTERFACE_ID}</id>
              <mode>l2transport</mode>
              <mtu>{$MTU}</mtu>
              <encapsulation>
                <default ncs:when="{$L2_SERV_ENCAP = 'default'}"/>
                <dot1q ncs:when="{$L2_SERV_ENCAP = 'dot1q'}">
                  <vlan-id ncs:when="{$L2_SERV_VLAN_ID != 0}">{$L2_SERV_VLAN_ID}</vlan-id>
                </dot1q>
                <untagged ncs:when="{$L2_SERV_ENCAP = 'untagged'}"/>
              </encapsulation>
              <rewrite ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE != 'none'}">
                <ingress>
                  <tag>
                    <pop ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'pop1'}">1</pop>
                    <translate ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">1-to-1</translate>
                    <dot1q ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1' and $L2_SERV_INGRESS_REWRITE_VLAN != 0}">{$L2_SERV_INGRESS_REWRITE_VLAN}</dot1q>
                    <mode ncs:when="{$L2_SERV_INGRESS_REWRITE_TYPE = 'translate1to1'}">symmetric</mode>
                  </tag>
                </ingress>
              </rewrite>
            </HundredGigE>
          </HundredGigE-subinterface>
        </interface>
        
        <l2vpn xmlns="http://tail-f.com/ned/cisco-ios-xr">
          <pw-class>
            <name>{$PW_CLASS_NAME}</name>
            <encapsulation>
              <mpls>
                <control-word ncs:when="{$CONTROL_WORD_ENABLED = 'true'}"/>
              </mpls>
            </encapsulation>
          </pw-class>
          <xconnect>
            <group>
              <name>{$XCONNECT_GROUP_NAME}</name>
              <p2p>
                <name>{$P2P_NAME}</name>
                <interface>
                  <name>{$FULL_INTERFACE_NAME}</name>
                </interface>
                <neighbor>
                  <address>{$NEIGHBOR_IP}</address>
                  <pw-id>{$PW_ID}</pw-id>
                  <ip-version>ipv4</ip-version>
                  <pw-class>{$PW_CLASS_REF}</pw-class>
                </neighbor>
              </p2p>
            </group>
          </xconnect>
        </l2vpn>
      </config>
    </device>
  </devices>
</config>




3. 전체 구성 단계

3.1. 1단계: VPC IP 주소 설정

L2VPN으로 연결된 VPC들은 같은 IP 서브넷에 있어야 하며, 게이트웨이가 필요 없다.

  • R1 측 VPC:

    • VPC37 (VLAN 10): ip 192.168.10.4/24

    • VPC38 (VLAN 20): ip 192.168.20.5/24

    • VPC39 (VLAN 30): ip 192.168.30.6/24

  • R3 측 VPC:

    • VPC40 (VLAN 10): ip 192.168.10.10/24

    • VPC41 (VLAN 20): ip 192.168.20.11/24

    • VPC42 (VLAN 30): ip 192.168.30.12/24


3.2. 2단계: L2 스위치 설정

양쪽 스위치는 VPC로부터 받은 트래픽에 올바른 VLAN 태그를 붙여 PE 라우터로 전달하는 역할을 합니다.

  • Switch1 설정 (R1 측)

    configure terminal
    vlan 10,20,30
    !
    interface Ethernet0/1
     description To_VPC37
     switchport mode access
     switchport access vlan 10
    !
    interface Ethernet0/2
     description To_VPC38
     switchport mode access
     switchport access vlan 20
    !
    interface Ethernet0/3
     description To_VPC39
     switchport mode access
     switchport access vlan 30
    !
    interface Ethernet0/0
     description To_R1
     switchport trunk encapsulation dot1q
     switchport mode trunk
    !
    end

  • Switch2 설정 (R3 측)

    configure terminal
    vlan 10,20,30
    !
    interface Ethernet0/1
     description To_VPC40
     switchport mode access
     switchport access vlan 10
    !
    interface Ethernet0/2
     description To_VPC41
     switchport mode access
     switchport access vlan 20
    !
    interface Ethernet0/3
     description To_VPC42
     switchport mode access
     switchport access vlan 30
    !
    interface Ethernet0/0
     description To_R3
     switchport trunk encapsulation dot1q
     switchport mode trunk
    !
    end


3.3. 3단계: NSO L2VPN 서비스 생성

! ===================================================
!  연결 1: VLAN 10
! ===================================================
l2vpn R1_VLAN10
  device R1
  local-l2transport-interface interface-type GigabitEthernet
  local-l2transport-interface interface-main-id 0/0/0/9
  local-l2transport-interface subinterface-id 10
  local-l2transport-interface encapsulation-type dot1q
  local-l2transport-interface vlan-id 10
  local-l2transport-interface service-type evpl
  xconnect group-name L2VPN_Group
  xconnect p2p-name P2P_VLAN10
  xconnect neighbor-ip 30.30.30.30
  xconnect pw-id 1310
  xconnect pw-class-ref L2VPN-CLASS
exit

l2vpn R3_VLAN10
  device R3
  local-l2transport-interface interface-type GigabitEthernet
  local-l2transport-interface interface-main-id 0/0/0/9
  local-l2transport-interface subinterface-id 10
  local-l2transport-interface encapsulation-type dot1q
  local-l2transport-interface vlan-id 10
  local-l2transport-interface service-type evpl
  xconnect group-name L2VPN_Group
  xconnect p2p-name P2P_VLAN10
  xconnect neighbor-ip 10.10.10.10
  xconnect pw-id 1310
  xconnect pw-class-ref L2VPN-CLASS
exit

! ===================================================
!  연결 2: VLAN 20
! ===================================================
l2vpn R1_VLAN20
  device R1
  local-l2transport-interface interface-type GigabitEthernet
  local-l2transport-interface interface-main-id 0/0/0/9
  local-l2transport-interface subinterface-id 20
  local-l2transport-interface encapsulation-type dot1q
  local-l2transport-interface vlan-id 20
  local-l2transport-interface service-type evpl
  xconnect group-name L2VPN_Group
  xconnect p2p-name P2P_VLAN20
  xconnect neighbor-ip 30.30.30.30
  xconnect pw-id 1320
  xconnect pw-class-ref L2VPN-CLASS
exit

l2vpn R3_VLAN20
  device R3
  local-l2transport-interface interface-type GigabitEthernet
  local-l2transport-interface interface-main-id 0/0/0/9
  local-l2transport-interface subinterface-id 20
  local-l2transport-interface encapsulation-type dot1q
  local-l2transport-interface vlan-id 20
  local-l2transport-interface service-type evpl
  xconnect group-name L2VPN_Group
  xconnect p2p-name P2P_VLAN20
  xconnect neighbor-ip 10.10.10.10
  xconnect pw-id 1320
  xconnect pw-class-ref L2VPN-CLASS
exit

! ===================================================
!  연결 3: VLAN 30
! ===================================================
l2vpn R1_VLAN30
  device R1
  local-l2transport-interface interface-type GigabitEthernet
  local-l2transport-interface interface-main-id 0/0/0/9
  local-l2transport-interface subinterface-id 30
  local-l2transport-interface encapsulation-type dot1q
  local-l2transport-interface vlan-id 30
  local-l2transport-interface service-type evpl
  xconnect group-name L2VPN_Group
  xconnect p2p-name P2P_VLAN30
  xconnect neighbor-ip 30.30.30.30
  xconnect pw-id 1330
  xconnect pw-class-ref L2VPN-CLASS
exit

l2vpn R3_VLAN30
  device R3
  local-l2transport-interface interface-type GigabitEthernet
  local-l2transport-interface interface-main-id 0/0/0/9
  local-l2transport-interface subinterface-id 30
  local-l2transport-interface encapsulation-type dot1q
  local-l2transport-interface vlan-id 30
  local-l2transport-interface service-type evpl
  xconnect group-name L2VPN_Group
  xconnect p2p-name P2P_VLAN30
  xconnect neighbor-ip 10.10.10.10
  xconnect pw-id 1330
  xconnect pw-class-ref L2VPN-CLASS
exit

commit




4. 검증 및 확인

모든 설정이 적용된 후, 아래 방법으로 통신을 확인합니다.

  • PE 라우터에서 L2VPN 상태 확인

    R1
    
    
    RP/0/RP0/CPU0:ios#show l2vpn xconnect 
    Fri Aug  1 00:38:03.986 UTC
    Legend: ST = State, UP = Up, DN = Down, AD = Admin Down, UR = Unresolved,
            SB = Standby, SR = Standby Ready, (PP) = Partially Programmed
    
    XConnect                   Segment 1                       Segment 2                
    Group      Name       ST   Description            ST       Description            ST    
    ------------------------   -----------------------------   -----------------------------
    L2VPN_Group
               P2P_R1_R3  UP   Gi0/0/0/9.10           UP       30.30.30.30     1310   UP    
    ----------------------------------------------------------------------------------------
    L2VPN_Group
               P2P_R1_R3_1
                          UP   Gi0/0/0/9.20           UP       30.30.30.30     1320   UP    
    ----------------------------------------------------------------------------------------
    L2VPN_Group
               P2P_R1_R3_2
                          UP   Gi0/0/0/9.30           UP       30.30.30.30     1330   UP    
    ----------------------------------------------------------------------------------------
    
    
    R3
    RP/0/RP0/CPU0:ios#show l2vpn xconnect 
    Fri Aug  1 00:38:57.996 UTC
    Legend: ST = State, UP = Up, DN = Down, AD = Admin Down, UR = Unresolved,
            SB = Standby, SR = Standby Ready, (PP) = Partially Programmed
    
    XConnect                   Segment 1                       Segment 2                
    Group      Name       ST   Description            ST       Description            ST    
    ------------------------   -----------------------------   -----------------------------
    L2VPN_Group
               P2P_R3_R1  UP   Gi0/0/0/9.10           UP       10.10.10.10     1310   UP    
    ----------------------------------------------------------------------------------------
    L2VPN_Group
               P2P_R3_R1_1
                          UP   Gi0/0/0/9.20           UP       10.10.10.10     1320   UP    
    ----------------------------------------------------------------------------------------
    L2VPN_Group
               P2P_R3_R1_2
                          UP   Gi0/0/0/9.30           UP       10.10.10.10     1330   UP    
    ----------------------------------------------------------------------------------------


  • VPC 간 Ping 테스트:

    • VPC37에서 ping 192.168.10.10 (VPC40)

      VPCS> ping 192.168.10.10
      
      84 bytes from 192.168.10.10 icmp_seq=1 ttl=64 time=1.541 ms
      84 bytes from 192.168.10.10 icmp_seq=2 ttl=64 time=1.875 ms
      84 bytes from 192.168.10.10 icmp_seq=3 ttl=64 time=1.774 ms
      84 bytes from 192.168.10.10 icmp_seq=4 ttl=64 time=1.796 ms
      84 bytes from 192.168.10.10 icmp_seq=5 ttl=64 time=1.867 ms


    • VPC38에서 ping 192.168.20.11 (VPC41)

      VPCS> ping 192.168.20.11
      
      84 bytes from 192.168.20.11 icmp_seq=1 ttl=64 time=1.572 ms
      84 bytes from 192.168.20.11 icmp_seq=2 ttl=64 time=1.950 ms
      84 bytes from 192.168.20.11 icmp_seq=3 ttl=64 time=1.812 ms
      84 bytes from 192.168.20.11 icmp_seq=4 ttl=64 time=1.932 ms
      84 bytes from 192.168.20.11 icmp_seq=5 ttl=64 time=1.791 ms


    • VPC39에서 ping 192.168.30.12 (VPC42)

      VPCS> ping 192.168.30.12 
      
      84 bytes from 192.168.30.12 icmp_seq=1 ttl=64 time=1.620 ms
      84 bytes from 192.168.30.12 icmp_seq=2 ttl=64 time=1.789 ms
      84 bytes from 192.168.30.12 icmp_seq=3 ttl=64 time=1.875 ms
      84 bytes from 192.168.30.12 icmp_seq=4 ttl=64 time=1.867 ms
      84 bytes from 192.168.30.12 icmp_seq=5 ttl=64 time=1.989 ms