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 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 ! ! !