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-VPNVRF 정보: RD
100:1, Import/Export RT100: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
!
!
!