Skip to content

Commit d6c910b

Browse files
Feat add interface vti support (#256)
* Adding interface VIT backend * Adding interface vti frontend
1 parent 60fb0fe commit d6c910b

17 files changed

Lines changed: 2840 additions & 9 deletions

File tree

backend/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
# Import routers
1515
from routers.session import session as session_router
16-
from routers.interfaces import ethernet, dummy, bonding, bridge, geneve, input, l2tpv3, loopback, macsec, openvpn, pppoe, pseudo_ethernet, sstpc, virtual_ethernet, vpp
16+
from routers.interfaces import ethernet, dummy, bonding, bridge, geneve, input, l2tpv3, loopback, macsec, openvpn, pppoe, pseudo_ethernet, sstpc, virtual_ethernet, vpp, vti
1717
from routers.firewall import groups
1818
from routers.firewall import ipv4 as firewall_ipv4
1919
from routers.firewall import ipv6 as firewall_ipv6
@@ -295,6 +295,7 @@ async def get_permissions(request: Request) -> dict:
295295
app.include_router(sstpc.router)
296296
app.include_router(virtual_ethernet.router)
297297
app.include_router(vpp.router)
298+
app.include_router(vti.router)
298299
app.include_router(groups.router)
299300
app.include_router(firewall_ipv4.router)
300301
app.include_router(firewall_ipv6.router)
@@ -372,6 +373,7 @@ async def read_root() -> dict:
372373
"sstpc-interface",
373374
"virtual-ethernet-interface",
374375
"vpp-interface",
376+
"vti-interface",
375377
"firewall-groups",
376378
"firewall-ipv4",
377379
"firewall-ipv6",

backend/routers/interfaces/vti.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
"""
2+
VTI (Virtual Tunnel Interface) Configuration Endpoints
3+
4+
All VTI interface endpoints for VyOS configuration.
5+
VTI (XFRM) interfaces are used as the kernel-side of IPsec tunnels.
6+
Interface naming follows the pattern vtiN (e.g., vti0, vti1).
7+
"""
8+
9+
import inspect
10+
import logging
11+
from typing import Dict, List, Optional, Any
12+
13+
from fastapi import APIRouter, HTTPException, Request
14+
from pydantic import BaseModel, Field, ConfigDict
15+
from starlette.concurrency import run_in_threadpool
16+
17+
from fastapi_permissions import require_read_permission, require_write_permission
18+
from rbac_permissions import FeatureGroup
19+
from session_vyos_service import get_session_vyos_service
20+
21+
logger = logging.getLogger(__name__)
22+
23+
router = APIRouter(prefix="/vyos/vti", tags=["vti-interface"])
24+
25+
26+
# ============================================================================
27+
# Request / Response Models
28+
# ============================================================================
29+
30+
31+
class BatchOperation(BaseModel):
32+
op: str = Field(..., description="Builder method name")
33+
value: Optional[str] = Field(None, description="Operation value (if required)")
34+
35+
36+
class BatchRequest(BaseModel):
37+
interface: str = Field(..., description="Interface name (e.g., vti0)")
38+
operations: List[BatchOperation]
39+
40+
41+
class VyOSResponse(BaseModel):
42+
success: bool
43+
data: Optional[Dict[str, Any]] = None
44+
error: Optional[str] = None
45+
46+
47+
class VtiInterfaceConfig(BaseModel):
48+
name: str
49+
type: str
50+
addresses: List[str] = Field(default_factory=list)
51+
description: Optional[str] = None
52+
mtu: Optional[str] = None
53+
disable: Optional[bool] = None
54+
vrf: Optional[str] = None
55+
redirect: Optional[str] = None
56+
# Mirror
57+
mirror_ingress: Optional[str] = None
58+
mirror_egress: Optional[str] = None
59+
# IP settings
60+
ip_adjust_mss: Optional[str] = None
61+
ip_arp_cache_timeout: Optional[str] = None
62+
ip_disable_arp_filter: Optional[bool] = None
63+
ip_disable_forwarding: Optional[bool] = None
64+
ip_enable_arp_accept: Optional[bool] = None
65+
ip_enable_arp_announce: Optional[bool] = None
66+
ip_enable_arp_ignore: Optional[bool] = None
67+
ip_enable_directed_broadcast: Optional[bool] = None
68+
ip_enable_proxy_arp: Optional[bool] = None
69+
ip_proxy_arp_pvlan: Optional[bool] = None
70+
ip_source_validation: Optional[str] = None
71+
# IPv6 settings
72+
ipv6_accept_dad: Optional[str] = None
73+
ipv6_address_autoconf: Optional[bool] = None
74+
ipv6_address_eui64: List[str] = Field(default_factory=list)
75+
ipv6_address_no_default_link_local: Optional[bool] = None
76+
ipv6_adjust_mss: Optional[str] = None
77+
ipv6_base_reachable_time: Optional[str] = None
78+
ipv6_disable_forwarding: Optional[bool] = None
79+
ipv6_dup_addr_detect_transmits: Optional[str] = None
80+
ipv6_source_validation: Optional[str] = None
81+
82+
model_config = ConfigDict(populate_by_name=True)
83+
84+
85+
class VtiInterfacesConfigResponse(BaseModel):
86+
interfaces: List[VtiInterfaceConfig] = Field(default_factory=list)
87+
total: int = 0
88+
by_type: Dict[str, int] = Field(default_factory=dict)
89+
by_vrf: Dict[str, int] = Field(default_factory=dict)
90+
91+
92+
# ============================================================================
93+
# Endpoints
94+
# ============================================================================
95+
96+
97+
@router.get("/capabilities")
98+
async def get_capabilities(request: Request) -> Dict[str, Any]:
99+
"""Return version-aware feature capabilities for VTI interfaces."""
100+
await require_read_permission(request, FeatureGroup.INTERFACES)
101+
service = get_session_vyos_service(request)
102+
from vyos_builders.interfaces.vti import VtiInterfaceBuilderMixin
103+
builder = VtiInterfaceBuilderMixin(version=service.get_version())
104+
return builder.get_capabilities()
105+
106+
107+
@router.get("/config", response_model=VtiInterfacesConfigResponse)
108+
async def get_config(http_request: Request, refresh: bool = False) -> VtiInterfacesConfigResponse:
109+
"""Get all VTI interface configurations from VyOS."""
110+
await require_read_permission(http_request, FeatureGroup.INTERFACES)
111+
try:
112+
service = get_session_vyos_service(http_request)
113+
full_config = await run_in_threadpool(service.get_full_config, refresh)
114+
raw_config = full_config.get("interfaces", {}).get("vti", {})
115+
116+
from vyos_mappers.interfaces.vti_versions import get_vti_mapper
117+
mapper = get_vti_mapper(service.get_version())
118+
parsed = mapper.parse_interfaces_of_type(raw_config)
119+
return VtiInterfacesConfigResponse(**parsed)
120+
except Exception:
121+
logger.exception("Unhandled error in get_config")
122+
raise HTTPException(status_code=500, detail="Internal server error")
123+
124+
125+
@router.post("/batch", response_model=VyOSResponse)
126+
async def batch_configure(http_request: Request, request: BatchRequest) -> VyOSResponse:
127+
"""
128+
Configure a VTI interface using batch operations.
129+
130+
**Supported operations (all versions):**
131+
| Operation | Value | Description |
132+
|-----------|-------|-------------|
133+
| `delete_interface` | No | Delete entire interface |
134+
| `set_interface_description` | Yes | Set description |
135+
| `delete_interface_description` | No | Remove description |
136+
| `set_interface_address` | Yes | Add IP address (CIDR) |
137+
| `delete_interface_address` | Yes | Remove specific IP address |
138+
| `delete_interface_addresses` | No | Remove all addresses |
139+
| `set_interface_mtu` | Yes | Set MTU (68-16000) |
140+
| `delete_interface_mtu` | No | Reset MTU to default |
141+
| `set_interface_disable` | No | Administratively disable |
142+
| `delete_interface_disable` | No | Re-enable interface |
143+
| `set_interface_vrf` | Yes | Assign to VRF |
144+
| `delete_interface_vrf` | No | Remove VRF assignment |
145+
| `set_redirect` | Yes | Redirect incoming packets |
146+
| `delete_redirect` | No | Remove redirect |
147+
| `set_mirror_ingress` | Yes | Mirror ingress traffic |
148+
| `delete_mirror_ingress` | No | Remove ingress mirror |
149+
| `set_mirror_egress` | Yes | Mirror egress traffic |
150+
| `delete_mirror_egress` | No | Remove egress mirror |
151+
| `set_ip_adjust_mss` | Yes | Adjust TCP MSS (clamp-mss-to-pmtu or 536-65535) |
152+
| `delete_ip_adjust_mss` | No | Remove MSS adjustment |
153+
| `set_ip_arp_cache_timeout` | Yes | ARP cache timeout (1-86400 seconds) |
154+
| `delete_ip_arp_cache_timeout` | No | Reset ARP cache timeout |
155+
| `set_ip_disable_arp_filter` | No | Disable ARP filter |
156+
| `delete_ip_disable_arp_filter` | No | Re-enable ARP filter |
157+
| `set_ip_disable_forwarding` | No | Disable IPv4 forwarding |
158+
| `delete_ip_disable_forwarding` | No | Enable IPv4 forwarding |
159+
| `set_ip_enable_arp_accept` | No | Enable ARP accept |
160+
| `delete_ip_enable_arp_accept` | No | Disable ARP accept |
161+
| `set_ip_enable_arp_announce` | No | Enable ARP announce |
162+
| `delete_ip_enable_arp_announce` | No | Disable ARP announce |
163+
| `set_ip_enable_arp_ignore` | No | Enable ARP ignore |
164+
| `delete_ip_enable_arp_ignore` | No | Disable ARP ignore |
165+
| `set_ip_enable_directed_broadcast` | No | Enable directed broadcast |
166+
| `delete_ip_enable_directed_broadcast` | No | Disable directed broadcast |
167+
| `set_ip_enable_proxy_arp` | No | Enable proxy ARP |
168+
| `delete_ip_enable_proxy_arp` | No | Disable proxy ARP |
169+
| `set_ip_proxy_arp_pvlan` | No | Enable private VLAN proxy ARP |
170+
| `delete_ip_proxy_arp_pvlan` | No | Disable private VLAN proxy ARP |
171+
| `set_ip_source_validation` | Yes | Source validation (strict/loose/disable) |
172+
| `delete_ip_source_validation` | No | Remove source validation |
173+
| `set_ipv6_accept_dad` | Yes | DAD mode (0/1/2) |
174+
| `delete_ipv6_accept_dad` | No | Reset DAD mode |
175+
| `set_ipv6_address_autoconf` | No | Enable SLAAC |
176+
| `delete_ipv6_address_autoconf` | No | Disable SLAAC |
177+
| `set_ipv6_address_eui64` | Yes | Add EUI-64 prefix |
178+
| `delete_ipv6_address_eui64` | Yes | Remove EUI-64 prefix |
179+
| `delete_ipv6_address_eui64_all` | No | Remove all EUI-64 prefixes |
180+
| `set_ipv6_address_no_default_link_local` | No | Remove default link-local |
181+
| `delete_ipv6_address_no_default_link_local` | No | Restore default link-local |
182+
| `set_ipv6_adjust_mss` | Yes | Adjust IPv6 TCP MSS |
183+
| `delete_ipv6_adjust_mss` | No | Remove IPv6 MSS adjustment |
184+
| `set_ipv6_base_reachable_time` | Yes | Base reachable time (1-86400 seconds) |
185+
| `delete_ipv6_base_reachable_time` | No | Reset base reachable time |
186+
| `set_ipv6_disable_forwarding` | No | Disable IPv6 forwarding |
187+
| `delete_ipv6_disable_forwarding` | No | Enable IPv6 forwarding |
188+
| `set_ipv6_dup_addr_detect_transmits` | Yes | DAD transmit count |
189+
| `delete_ipv6_dup_addr_detect_transmits` | No | Reset DAD transmit count |
190+
| `set_ipv6_source_validation` | Yes | IPv6 source validation (strict/loose/disable) |
191+
| `delete_ipv6_source_validation` | No | Remove IPv6 source validation |
192+
"""
193+
await require_write_permission(http_request, FeatureGroup.INTERFACES)
194+
195+
try:
196+
service = get_session_vyos_service(http_request)
197+
batch = service.create_vti_batch()
198+
199+
for op in request.operations:
200+
if op.op in batch._INTERNAL_BUILDER_METHODS:
201+
raise HTTPException(
202+
status_code=400,
203+
detail=f"Operation '{op.op}' is not a valid interface operation",
204+
)
205+
206+
method = getattr(batch, op.op, None)
207+
if method is None:
208+
raise HTTPException(
209+
status_code=400,
210+
detail=f"Unsupported operation: {op.op}",
211+
)
212+
213+
sig = inspect.signature(method)
214+
params = [p for p in sig.parameters.keys() if p != "self"]
215+
216+
if len(params) == 1:
217+
method(request.interface)
218+
elif len(params) == 2:
219+
if op.value is None:
220+
raise HTTPException(
221+
status_code=400,
222+
detail=f"Operation '{op.op}' requires a value",
223+
)
224+
method(request.interface, op.value)
225+
else:
226+
raise HTTPException(
227+
status_code=400,
228+
detail=f"Operation '{op.op}' has unexpected signature",
229+
)
230+
231+
response = service.execute_batch(batch)
232+
return VyOSResponse(
233+
success=response.status == 200,
234+
data=response.result if isinstance(response.result, dict) else None,
235+
error=response.error if response.error else None,
236+
)
237+
except HTTPException:
238+
raise
239+
except Exception:
240+
logger.exception("Unhandled error in batch_configure")
241+
raise HTTPException(status_code=500, detail="Internal server error")

backend/vyos_builders/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Each builder includes all necessary operations for its feature type.
66
"""
77

8-
from .interfaces import EthernetInterfaceBuilderMixin, DummyInterfaceBuilderMixin, BondingInterfaceBuilderMixin, GeneveInterfaceBuilderMixin, InputInterfaceBuilderMixin, L2TPv3InterfaceBuilderMixin, LoopbackInterfaceBuilderMixin, MacsecInterfaceBuilderMixin, OpenvpnInterfaceBuilderMixin, PppoeInterfaceBuilderMixin, PseudoEthernetInterfaceBuilderMixin, SstpcInterfaceBuilderMixin, VirtualEthernetInterfaceBuilderMixin, VppInterfaceBuilderMixin
8+
from .interfaces import EthernetInterfaceBuilderMixin, DummyInterfaceBuilderMixin, BondingInterfaceBuilderMixin, GeneveInterfaceBuilderMixin, InputInterfaceBuilderMixin, L2TPv3InterfaceBuilderMixin, LoopbackInterfaceBuilderMixin, MacsecInterfaceBuilderMixin, OpenvpnInterfaceBuilderMixin, PppoeInterfaceBuilderMixin, PseudoEthernetInterfaceBuilderMixin, SstpcInterfaceBuilderMixin, VirtualEthernetInterfaceBuilderMixin, VppInterfaceBuilderMixin, VtiInterfaceBuilderMixin
99
from .firewall import FirewallGroupsBatchBuilder, FirewallIPv4BatchBuilder, FirewallIPv6BatchBuilder, BridgeFirewallBatchBuilder, FirewallZonesBatchBuilder
1010
from .nat import NATBatchBuilder
1111
from .nat64 import NAT64BatchBuilder
@@ -53,6 +53,7 @@
5353
SstpcBatchBuilder = SstpcInterfaceBuilderMixin
5454
VirtualEthernetBatchBuilder = VirtualEthernetInterfaceBuilderMixin
5555
VppBatchBuilder = VppInterfaceBuilderMixin
56+
VtiBatchBuilder = VtiInterfaceBuilderMixin
5657

5758
__all__ = [
5859
"EthernetBatchBuilder",
@@ -105,4 +106,5 @@
105106
"SstpcBatchBuilder",
106107
"VirtualEthernetBatchBuilder",
107108
"VppBatchBuilder",
109+
"VtiBatchBuilder",
108110
]

backend/vyos_builders/interfaces/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .sstpc import SstpcInterfaceBuilderMixin
2020
from .virtual_ethernet import VirtualEthernetInterfaceBuilderMixin
2121
from .vpp import VppInterfaceBuilderMixin
22+
from .vti import VtiInterfaceBuilderMixin
2223

2324
__all__ = [
2425
"EthernetInterfaceBuilderMixin",
@@ -36,4 +37,5 @@
3637
"SstpcInterfaceBuilderMixin",
3738
"VirtualEthernetInterfaceBuilderMixin",
3839
"VppInterfaceBuilderMixin",
40+
"VtiInterfaceBuilderMixin",
3941
]

0 commit comments

Comments
 (0)