L3VPN 패키지

1. 개요

1.1. 주요 기능

  • End-to-End L3VPN 자동화: 단일 서비스 생성을 통해 여러 PE(Provider Edge) 장비에 걸친 L3VPN 구성을 한 번에 배포

  • 유연한 VRF 관리: 신규 VRF를 RD(Route Distinguisher), RT(Route Target) 값과 함께 동적으로 생성하거나, 기존에 존재하는 VRF를 재사용 가능

  • 동적 Route Policy 생성: 서비스 모델 내에서 직접 Route Policy를 텍스트로 정의하고, 각 PE 장비에 배포 및 BGP Neighbor에 적용 가능

  • 인터페이스 상세 설정: Core 방향 및 CE(Customer Edge) 방향 인터페이스의 종류, VLAN ID, IP 주소 등을 상세하게 지정함

  • PE-CE BGP 연동: 각 VRF 내에서 CE 장비와의 eBGP 네이버 관계를 설정하고, In/Outbound Route Policy를 적용

  • MPLS LDP 자동화: 서비스에 필요한 MPLS LDP를 Core 방향 인터페이스에 활성화


2. NSO 서비스 패키지 상세 설계

2.1. 서비스 데이터 모델 (l3vpn.yang)

  • 서비스의 전체적인 구조와 파라미터를 정의
  • global-route-policy-definition을 통해 공통 정책을 정의하고, pe-site 리스트를 통해 여러 사이트를 동시에 설정하는 강력한 구조를 가진다.
module l3vpn {
  yang-version 1.1;
  namespace "http://example.com/l3vpn";
  prefix l3vpn;
 
  import tailf-ncs {
    prefix ncs;
  }
  import ietf-inet-types {
    prefix inet;
  }
 
  description
    "Final YANG model for the L3VPN service package.";
 
  revision "2025-08-01" {
    description "Adjusted YANG for direct core-interface input and consolidated templates.";
  }
 
  list l3vpn {
    key "name";
    uses ncs:service-data;
    ncs:servicepoint "l3vpn-servicepoint";
 
    must "use-existing-vrf or (vrf-rd and vrf-rt-export and vrf-rt-import)" {
      error-message "Either 'use-existing-vrf' must be specified, or all of 'vrf-rd', 'vrf-rt-export', and 'vrf-rt-import' must be specified.";
    }
 
    leaf name {
      type string;
    }
 
    leaf description {
      type string;
    }
 
    leaf use-existing-vrf {
      type string;
    }
 
    leaf vrf-rd {
      type string;
      mandatory true;
      must "contains(., ':')" {
        error-message "VRF RD must be in the format 'ASN:NN'.";
      }
    }
 
    leaf vrf-rt-export {
      type string;
      mandatory true;
      must "contains(., ':')" {
        error-message "VRF RT Export must be in the format 'ASN:NN'.";
      }
    }
 
    leaf vrf-rt-import {
      type string;
      mandatory true;
      must "contains(., ':')" {
        error-message "VRF RT Import must be in the format 'ASN:NN'.";
      }
    }
 
    list global-route-policy-definition {
      key "name";
       
      leaf name {
        type string;
        mandatory true;
      }
      leaf definition {
        type string;
        mandatory true;
      }
    }
 
    list pe-site {
      key "device-name";
      min-elements 1;
 
      leaf device-name {
        type leafref {
          path "/ncs:devices/ncs:device/ncs:name";
        }
      }
 
      leaf pe-loopback-ip {
        type inet:ip-address;
        mandatory true;
      }
 
      leaf pe-as-number {
        type uint32;
        mandatory true;
      }
       
      leaf core-interface-type {
        type enumeration {
          enum "GigabitEthernet";
          enum "TenGigE";
          enum "HundredGigE";
        }
        mandatory true;
      }
       
      leaf core-interface-name {
        type string;
        mandatory true;
      }
 
      leaf mpls-router-id {
        type inet:ipv4-address;
        mandatory true;
      }
 
      container ce-connection {
         
        leaf pe-interface-type {
          type enumeration {
            enum "GigabitEthernet";
            enum "TenGigE";
            enum "HundredGigE";
          }
          mandatory true;
        }
        leaf interface-name {
          type string;
          mandatory true;
        }
        leaf subinterface-id {
          type uint32;
          mandatory true;
        }
        leaf interface-ip {
          type inet:ip-address;
          mandatory true;
        }
        leaf interface-mask {
          type inet:ipv4-address;
          mandatory true;
        }
        leaf ce-interface-ip {
          type inet:ip-address;
          mandatory true;
        }
        leaf ce-as-number {
          type uint32;
          mandatory true;
        }
        
        leaf apply-route-policy {
          type boolean;
          mandatory true;
        }

        leaf in-route-policy {
          when "../apply-route-policy = 'true'";
          type string;
          mandatory true;
        }
 
        leaf out-route-policy {
          when "../apply-route-policy = 'true'";
          type string;
          mandatory true;
        }
      }
    }
  }
} 



2.2. 디바이스 설정 템플릿 (XML Templates)

  • l3vpn-route-policy-create.xml은 Route Policy를 생성
    <config xmlns="http://tail-f.com/ns/config/1.0">
      <devices xmlns="http://tail-f.com/ns/ncs">
        <device>
          <name>{$device_name}</name>
          <config>
            <route-policy xmlns="http://tail-f.com/ned/cisco-ios-xr">
              <name>{$policy_name}</name>
              <value>{$policy_definition}</value>
            </route-policy>
          </config>
        </device>
      </devices>
    </config>
  • l3vpn-service.xml은 나머지 모든 L3VPN 관련 설정 담당
    <config xmlns="http://tail-f.com/ns/config/1.0"
                     xmlns:ncs="http://tail-f.com/ns/ncs"
                     xmlns:cisco-ios-xr="http://tail-f.com/ned/cisco-ios-xr">
      <devices xmlns="http://tail-f.com/ns/ncs">
        <device>
          <name>{$device_name}</name>
          <config>
            <interface xmlns="http://tail-f.com/ned/cisco-ios-xr">
              <Loopback>
                <id>0</id>
                <ipv4>
                  <address>
                    <ip>{$pe_loopback_ip}</ip>
                    <mask>255.255.255.255</mask>
                  </address>
                </ipv4>
              </Loopback>
    
              <GigabitEthernet ncs:when="{starts-with($core_interface_type,'GigabitEthernet')}">
                <id>{$core_interface_name}</id>
                <description>Core-facing interface</description>
                <!-- <mpls><ip/></mpls> -->
              </GigabitEthernet>
              <HundredGigE ncs:when="{starts-with($core_interface_type,'HundredGigE')}">
                <id>{$core_interface_name}</id>
                <description>Core-facing interface</description>
                <!-- <mpls><ip/></mpls> -->
              </HundredGigE>
              <TenGigE ncs:when="{starts-with($core_interface_type,'TenGigE')}">
                <id>{$core_interface_name}</id>
                <description>Core-facing interface</description>
                <!-- <mpls><ip/></mpls> -->
              </TenGigE>
    
              <GigabitEthernet-subinterface ncs:when="{starts-with($pe_interface_type,'GigabitEthernet')}">
                <GigabitEthernet>
                  <id>{$pe_interface_name}.{$subinterface_id}</id>
                  <description>CE-facing subinterface</description>
                  <vrf>{$l3vpn_name}</vrf>
                  <encapsulation>
                    <dot1q>
                      <vlan-id>{$subinterface_id}</vlan-id>
                    </dot1q>
                  </encapsulation>
                  <ipv4>
                    <address>
                      <ip>{$pe_interface_ip}</ip>
                      <mask>{$pe_interface_mask}</mask>
                    </address>
                  </ipv4>
                </GigabitEthernet>
              </GigabitEthernet-subinterface>
              <HundredGigE-subinterface ncs:when="{starts-with($pe_interface_type,'HundredGigE')}">
                <HundredGigE>
                  <id>{$pe_interface_name}.{$subinterface_id}</id>
                  <description>CE-facing subinterface</description>
                  <vrf>{$l3vpn_name}</vrf>
                  <encapsulation>
                    <dot1q>
                      <vlan-id>{$subinterface_id}</vlan-id>
                    </dot1q>
                  </encapsulation>
                  <ipv4>
                    <address>
                      <ip>{$pe_interface_ip}</ip>
                      <mask>{$pe_interface_mask}</mask>
                    </address>
                  </ipv4>
                </HundredGigE>
              </HundredGigE-subinterface>
              <TenGigE-subinterface ncs:when="{starts-with($pe_interface_type,'TenGigE')}">
                <TenGigE>
                  <id>{$pe_interface_name}.{$subinterface_id}</id>
                  <description>CE-facing subinterface</description>
                  <vrf>{$l3vpn_name}</vrf>
                  <encapsulation>
                    <dot1q>
                      <vlan-id>{$subinterface_id}</vlan-id>
                    </dot1q>
                  </encapsulation>
                  <ipv4>
                    <address>
                      <ip>{$pe_interface_ip}</ip>
                      <mask>{$pe_interface_mask}</mask>
                    </address>
                  </ipv4>
                </TenGigE>
              </TenGigE-subinterface>
            </interface>
    
            <vrf xmlns="http://tail-f.com/ned/cisco-ios-xr">
              <vrf-list>
                <name>{$l3vpn_name}</name>
                <description>{$description}</description>
                <address-family>
                  <ipv4>
                    <unicast>
                      <import>
                        <route-target><address-list><name>{$vrf_rt_import}</name></address-list></route-target>
                      </import>
                      <export>
                        <route-target><address-list><name>{$vrf_rt_export}</name></address-list></route-target>
                      </export>
                    </unicast>
                  </ipv4>
                </address-family>
              </vrf-list>
            </vrf>
    
            <router xmlns="http://tail-f.com/ned/cisco-ios-xr">
              <bgp>
                <bgp-no-instance>
                  <id>{$pe_as_number}</id>
                  <address-family>
                    <vpnv4>
                      <unicast/>
                    </vpnv4>
                  </address-family>
                  <vrf>
                    <name>{$l3vpn_name}</name>
                    <?if {$vrf_rd}?>
                      <rd>{$vrf_rd}</rd>
                    <?end?>
                    <neighbor>
                      <id>{$ce_interface_ip}</id>
                      <remote-as>{$ce_as_number}</remote-as>
                      <address-family>
                        <ipv4>
                        <unicast>
                          <?if {$apply_route_policy}?>
                            <route-policy>
                              <direction>in</direction>
                              <name>{$in_route_policy_name}</name>
                            </route-policy>
                            <route-policy>
                              <direction>out</direction>
                              <name>{$out_route_policy_name}</name>
                            </route-policy>
                          <?end?>
                        </unicast>
                        </ipv4>
                        </address-family>
                    </neighbor>
                    <address-family>
                      <ipv4>
                        <unicast>
                          <redistribute><connected/></redistribute>
                          <redistribute><static/></redistribute>
                        </unicast>
                      </ipv4>
                    </address-family>
                  </vrf>
                  </bgp-no-instance>
              </bgp>
            </router>
    
            <mpls xmlns="http://tail-f.com/ned/cisco-ios-xr">
              <ldp>
                <router-id>{$mpls_ldp_router_id}</router-id>
                <address-family>
                  <ipv4/>
                </address-family>
                <!-- <interface>
                  <name>Loopback0</name>
                </interface> -->
                <!-- 이유는 모르겠지만 Loopback을 입력하면 안됨 -->
                <interface>
                  <name>{$mpls_ldp_interface}</name>
                  <address-family>
                    <ipv4/>
                  </address-family>
                </interface>
              </ldp>
            </mpls>
          </config>
        </device>
      </devices>
    </config>


2.3. 서비스 로직 (main.py)

  • 1단계에서 필요한 모든 Route Policy를 생성
  • 2단계에서 각 PE 장비별로 인터페이스, VRF, BGP 등 핵심 L3VPN 설정 적용
# -*- mode: python; python-indent: 4 -*-
import ncs
from ncs.application import Service
from ncs.application import Application

class ServiceCallbacks(Service):
    @Service.create
    def cb_create(self, tctx, root, service, proplist):
        self.log.info(f"Service create: {service.name}")
        
        main_template_obj = ncs.template.Template(service)

        vrf_name = service.use_existing_vrf if service.use_existing_vrf else service.name
        # 1. 전역 Route-policy 정의 생성 로직 (각 PE 장비에 개별적으로 생성)
        if hasattr(service, 'global_route_policy_definition') and service.global_route_policy_definition:
            self.log.info(f"Phase 1: Processing global_route_policy_definitions for all devices.")
            for pe_site in service.pe_site:
                device_name = pe_site.device_name
                for policy_def in service.global_route_policy_definition:
                    self.log.info(f"Creating route-policy '{policy_def.name}' on device {device_name}")
                    policy_create_vars = ncs.template.Variables()
                    policy_create_vars.add('device_name', device_name)
                    policy_create_vars.add('policy_name', policy_def.name)
                    policy_create_vars.add('policy_definition', policy_def.definition)
                    main_template_obj.apply('l3vpn-route-policy-create', policy_create_vars)
            self.log.info("Finished applying global route-policy definitions.")
        else:
            self.log.info("Phase 1: No global route-policy definitions provided.")

        # 2. 각 PE 장비별 주요 L3VPN 설정 (인터페이스, VRF, BGP, MPLS) 적용
        for pe_site in service.pe_site:
            device_name = pe_site.device_name
            ce_conn = pe_site.ce_connection

            self.log.info(f"Processing PE site: {device_name}")

            # l3vpn-service.xml 템플릿에 전달할 변수 준비
            service_vars = ncs.template.Variables()
            service_vars.add('device_name', device_name)
            service_vars.add('l3vpn_name', vrf_name)
            service_vars.add('pe_loopback_ip', pe_site.pe_loopback_ip)
            service_vars.add('pe_as_number', pe_site.pe_as_number)
            service_vars.add('description', getattr(service, 'description', f"L3VPN service for {service.name}"))

            # VRF 생성 관련 변수 전달
            if not service.use_existing_vrf:
                service_vars.add('vrf_rt_import', service.vrf_rt_import)
                service_vars.add('vrf_rt_export', service.vrf_rt_export)
                if service.vrf_rd:
                    service_vars.add('vrf_rd', service.vrf_rd)

            # Core Interface 변수
            service_vars.add('core_interface_type', str(pe_site.core_interface_type))
            service_vars.add('core_interface_name', pe_site.core_interface_name)

            # CE Connection / Subinterface 변수
            service_vars.add('pe_interface_type', str(ce_conn.pe_interface_type))
            service_vars.add('pe_interface_name', ce_conn.interface_name)
            service_vars.add('subinterface_id', ce_conn.subinterface_id)
            service_vars.add('pe_interface_ip', ce_conn.interface_ip)
            service_vars.add('pe_interface_mask', ce_conn.interface_mask)
            service_vars.add('ce_interface_ip', ce_conn.ce_interface_ip)
            service_vars.add('ce_as_number', ce_conn.ce_as_number)
            
            mpls_ldp_interface_name = str(pe_site.core_interface_type) + pe_site.core_interface_name
            service_vars.add('mpls_ldp_interface', mpls_ldp_interface_name)
            service_vars.add('mpls_ldp_router_id', pe_site.mpls_router_id)
            
            if hasattr(ce_conn, 'apply_route_policy') and bool(ce_conn.apply_route_policy):       
                service_vars.add('in_route_policy_name', ce_conn.in_route_policy)
                service_vars.add('out_route_policy_name', ce_conn.out_route_policy)
                service_vars.add('apply_route_policy', ce_conn.apply_route_policy)
            else:
                service_vars.add('apply_route_policy', '')
                service_vars.add('in_route_policy_name', '')
                service_vars.add('out_route_policy_name', '')
                
            main_template_obj.apply('l3vpn-service', service_vars)
            
            
class L3vpn(Application):
    def setup(self):
        self.log.info('L3vpn RUNNING - Registering final service.')
        self.register_service('l3vpn-servicepoint', ServiceCallbacks)

    def teardown(self):
        self.log.info('L3vpn FINISHED - Unregistering servicepoint.')




3. 서비스 사용 방법

3.1. 사용 시나리오

두 개의 사이트(R1, R3)를 가진 고객사 'CUSTOMER-A'를 위해 신규 L3VPN을 생성하는 시나리오

  • VPN 이름: CUSTOMER-A-VPN

  • VRF 정보: RD 100:1, Import/Export RT 100:1

  • 공통 Route Policy: 모든 경로를 허용하는 PERMIT-ALL 정책 생성

  • R1 사이트: R1(AS 100)과 CE1(AS 200)을 BGP로 연결, Route Policy in, out 모두 적용

  • R3 사이트: R3(AS 100)과 CE2(AS 300)를 BGP로 연결, Route Policy 적용 X

3.2. NSO CLI 생성 명령어

root@ncs(config)# l3vpn CUSTOMER-A-VPN
! 1. 공통으로 사용할 vrf 정의 
Value for 'vrf-rd' (<string>): 100:1
Value for 'vrf-rt-export' (<string>): 100:1
Value for 'vrf-rt-import' (<string>): 100:1


! 2. 공통으로 사용할 Route Policy 정의
root@ncs(config-l3vpn-CUSTOMER-A-VPN)# global-route-policy-definition PERMIT-ALL
Value for 'definition' (<string>): pass
root@ncs(config-global-route-policy-definition-PERMIT-ALL)# exit
root@ncs(config-l3vpn-CUSTOMER-A-VPN)# pe-site 
Possible completions:
  CE01  CE1  CE02  CE2  PE01  PE02  R1  R2  R3  R4  exam  sample7  sample8  sample9  sample10


! 3. R1 사이트 설정
root@ncs(config-l3vpn-CUSTOMER-A-VPN)# pe-site R1
Value for 'pe-loopback-ip' (<IP address>): 10.10.10.10
Value for 'pe-as-number' (<unsignedInt>): 100
Value for 'core-interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
Value for 'core-interface-name' (<string>): 0/0/0/0
Value for 'mpls-router-id' (<IPv4 address>): 10.10.10.10
Value for 'ce-connection pe-interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
Value for 'ce-connection interface-name' (<string>): 0/0/0/5
Value for 'ce-connection subinterface-id' (<unsignedInt>): 101
Value for 'ce-connection interface-ip' (<IP address>): 192.168.101.1
Value for 'ce-connection interface-mask' (<IPv4 address>): 255.255.255.0
Value for 'ce-connection ce-interface-ip' (<IP address>): 192.168.101.2
Value for 'ce-connection ce-as-number' (<unsignedInt>): 200
Value for 'ce-connection apply-route-policy' [false,true]: true
Value for 'ce-connection in-route-policy' (<string>): PERMIT-ALL
Value for 'ce-connection out-route-policy' (<string>): PERMIT-ALL
root@ncs(config-pe-site-R1)# exit


! 4. R3 사이트 설정
root@ncs(config-l3vpn-CUSTOMER-A-VPN)# pe-site R3
Value for 'pe-loopback-ip' (<IP address>): 30.30.30.30
Value for 'pe-as-number' (<unsignedInt>): 100
Value for 'core-interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
Value for 'core-interface-name' (<string>): 0/0/0/2
Value for 'mpls-router-id' (<IPv4 address>): 30.30.30.30
Value for 'ce-connection pe-interface-type' [GigabitEthernet,HundredGigE,TenGigE]: GigabitEthernet
Value for 'ce-connection interface-name' (<string>): 0/0/0/5
Value for 'ce-connection subinterface-id' (<unsignedInt>): 102
Value for 'ce-connection interface-ip' (<IP address>): 192.168.102.1
Value for 'ce-connection interface-mask' (<IPv4 address>): 255.255.255.0
Value for 'ce-connection ce-interface-ip' (<IP address>): 192.168.102.2
Value for 'ce-connection ce-as-number' (<unsignedInt>): 300
Value for 'ce-connection apply-route-policy' [false,true]: false
root@ncs(config-pe-site-R3)# commit

3.3. 반영내역

! 1. R1 설정
RP/0/RP0/CPU0:ios#show running-config 
...
vrf CUSTOMER-A-VPN
 description None
 address-family ipv4 unicast
  import route-target
   100:1
  !
  export route-target
   100:1
  !
 !
!
...
interface Loopback0
 ipv4 address 10.10.10.10 255.255.255.255
!         
...        
interface preconfigure GigabitEthernet0/0/0/5.101
 description CE-facing subinterface
 vrf CUSTOMER-A-VPN
 ipv4 address 192.168.101.1 255.255.255.0
 encapsulation dot1q 101
!         
route-policy PERMIT-ALL
  pass    
end-policy
!                 
...        
router bgp 100
 address-family vpnv4 unicast
 !        
 vrf CUSTOMER-A-VPN
  rd 100:1
  address-family ipv4 unicast
   redistribute connected
   redistribute static
  !       
  neighbor 192.168.101.2
   remote-as 200
   address-family ipv4 unicast
    route-policy PERMIT-ALL in
    route-policy PERMIT-ALL out
   !      
  !       
 !        
!            
mpls ldp  
 router-id 10.10.10.10
 address-family ipv4
 !        
 interface GigabitEthernet0/0/0/0
  address-family ipv4
  !       
 !        
!               



! 2. R3 설정
RP/0/RP0/CPU0:ios#show running-config 
...
vrf CUSTOMER-A-VPN
 description None
 address-family ipv4 unicast
  import route-target
   100:1
  !
  export route-target
   100:1
  !
 !
!
...      
interface Loopback0
 ipv4 address 30.30.30.30 255.255.255.255
!         
...      
interface preconfigure GigabitEthernet0/0/0/0/5.102
 description CE-facing subinterface
 vrf CUSTOMER-A-VPN
 ipv4 address 192.168.102.1 255.255.255.0
 encapsulation dot1q 102
!         
route-policy PERMIT-ALL
  pass    
end-policy
!         
...     
router bgp 100
 address-family vpnv4 unicast
 !        
 vrf CUSTOMER-A-VPN
  rd 100:1
  address-family ipv4 unicast
   redistribute connected
   redistribute static
  !       
  neighbor 192.168.102.2
   remote-as 300
   address-family ipv4 unicast
   !      
  !       
 !        
!                
mpls ldp  
 router-id 30.30.30.30
 address-family ipv4
 !        
 interface GigabitEthernet0/0/0/0
 !        
 interface GigabitEthernet0/0/0/2
  address-family ipv4
  !       
 !        
!