Skip to content

Commit 8a211c1

Browse files
committed
Add MCP Tools for OVS layer
This commit introduces new MCP tools for inspecting and debugging Open vSwitch (OVS) in ovn-kubernetes clusters: - ovs-list-br: List all OVS bridges on a pod - ovs-list-ports: List all ports attached to an OVS bridge - ovs-list-ifaces: List all interfaces attached to an OVS bridge - ovs-vsctl-show: Display comprehensive OVS configuration overview - ovs-ofctl-dump-flows: Dump and analyze OpenFlow rules from OVS bridges with optional filtering by pattern - ovs-appctl-dump-conntrack: Inspect connection tracking (conntrack) entries from the OVS datapath to debug stateful firewall rules and NAT - ovs-appctl-ofproto-trace: Simulate packet processing through the OpenFlow pipeline to troubleshoot flow rules and forwarding decisions Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Periyasamy Palanisamy <pepalani@redhat.com>
1 parent 7a97731 commit 8a211c1

File tree

5 files changed

+1101
-0
lines changed

5 files changed

+1101
-0
lines changed

cmd/ovnk-mcp-server/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
mcp "github.com/modelcontextprotocol/go-sdk/mcp"
1414
kubernetesmcp "github.com/ovn-kubernetes/ovn-kubernetes-mcp/pkg/kubernetes/mcp"
15+
ovsmcp "github.com/ovn-kubernetes/ovn-kubernetes-mcp/pkg/ovs/mcp"
1516
sosreportmcp "github.com/ovn-kubernetes/ovn-kubernetes-mcp/pkg/sosreport/mcp"
1617
)
1718

@@ -37,6 +38,9 @@ func main() {
3738
}
3839
log.Println("Adding Kubernetes tools to OVN-K MCP server")
3940
k8sMcpServer.AddTools(ovnkMcpServer)
41+
ovsServer := ovsmcp.NewMCPServer(k8sMcpServer)
42+
log.Println("Adding OVS tools to OVN-K MCP server")
43+
ovsServer.AddTools(ovnkMcpServer)
4044
}
4145
if serverCfg.Mode == "offline" {
4246
sosreportServer := sosreportmcp.NewMCPServer()

pkg/ovs/mcp/commands.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package mcp
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"regexp"
7+
"strings"
8+
9+
"github.com/modelcontextprotocol/go-sdk/mcp"
10+
k8stypes "github.com/ovn-kubernetes/ovn-kubernetes-mcp/pkg/kubernetes/types"
11+
)
12+
13+
const defaultMaxLines = 100
14+
15+
func (s *MCPServer) runCommand(ctx context.Context, req *mcp.CallToolRequest, namespacedName k8stypes.NamespacedNameParams,
16+
commands []string) ([]string, error) {
17+
_, result, err := s.k8sMcpServer.ExecPod(ctx, req, k8stypes.ExecPodParams{NamespacedNameParams: namespacedName, Command: commands})
18+
if err != nil {
19+
return nil, err
20+
}
21+
if result.Stderr != "" {
22+
return nil, fmt.Errorf("error occurred while running command %v on pod %s/%s: %s", commands, namespacedName.Namespace,
23+
namespacedName.Name, result.Stderr)
24+
}
25+
output := []string{} // Initialize with empty slice to ensure valid JSON when there's no output
26+
for _, line := range strings.Split(result.Stdout, "\n") {
27+
line = strings.TrimSpace(line)
28+
if line != "" {
29+
output = append(output, line)
30+
}
31+
}
32+
return output, nil
33+
}
34+
35+
// filterLines filters lines using a regex pattern.
36+
func filterLines(lines []string, pattern string) ([]string, error) {
37+
if pattern == "" {
38+
return lines, nil
39+
}
40+
41+
filterPattern, err := regexp.Compile(pattern)
42+
if err != nil {
43+
return nil, fmt.Errorf("invalid filter pattern %s: %w", pattern, err)
44+
}
45+
46+
filtered := []string{} // Initialize with empty slice to ensure valid JSON when there's no output
47+
for _, line := range lines {
48+
if filterPattern.MatchString(line) {
49+
filtered = append(filtered, line)
50+
}
51+
}
52+
return filtered, nil
53+
}
54+
55+
// limitLines limits the number of lines returned.
56+
func limitLines(lines []string, maxLines int) []string {
57+
if maxLines <= 0 {
58+
maxLines = defaultMaxLines
59+
}
60+
if len(lines) > maxLines {
61+
return lines[:maxLines]
62+
}
63+
return lines
64+
}
65+
66+
// validateBridgeName validates that a bridge name is safe and non-empty.
67+
// Bridge names should only contain alphanumeric characters, hyphens, and underscores.
68+
func validateBridgeName(bridge string) error {
69+
if bridge == "" {
70+
return fmt.Errorf("bridge name cannot be empty")
71+
}
72+
73+
// OVS bridge names typically follow naming conventions: alphanumeric, hyphens, underscores
74+
validBridgeName := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
75+
if !validBridgeName.MatchString(bridge) {
76+
return fmt.Errorf("invalid bridge name %q: must contain only alphanumeric characters, hyphens, and underscores", bridge)
77+
}
78+
79+
return nil
80+
}
81+
82+
// validateFlowSpec validates that a flow specification is safe and non-empty.
83+
func validateFlowSpec(flow string) error {
84+
if flow == "" {
85+
return fmt.Errorf("flow specification cannot be empty")
86+
}
87+
88+
// Check for potentially dangerous characters that shouldn't appear in flow specs
89+
// Flow specs should contain: alphanumeric, commas, equals, colons, periods, slashes, parentheses, brackets
90+
// We explicitly block: semicolons, pipes, backticks, dollar signs, and other shell metacharacters
91+
dangerousChars := regexp.MustCompile(`[;&|$` + "`" + `<>\\]`)
92+
if dangerousChars.MatchString(flow) {
93+
return fmt.Errorf("invalid flow specification: contains potentially dangerous characters")
94+
}
95+
96+
return nil
97+
}
98+
99+
// validateConntrackParams validates that conntrack additional parameters are safe.
100+
// Valid parameters for dpctl/dump-conntrack include: zone=N, mark=0xN, labels=0xN, -m, -s, etc.
101+
func validateConntrackParams(params []string) error {
102+
for _, param := range params {
103+
if param == "" {
104+
return fmt.Errorf("conntrack parameter cannot be empty")
105+
}
106+
107+
// Check for potentially dangerous characters
108+
// Valid conntrack params should contain: alphanumeric, equals, hyphens, underscores, periods, colons, commas, forward slashes
109+
// We explicitly block: semicolons, pipes, backticks, dollar signs, ampersands, and other shell metacharacters
110+
dangerousChars := regexp.MustCompile(`[;&|$` + "`" + `<>\\()]`)
111+
if dangerousChars.MatchString(param) {
112+
return fmt.Errorf("invalid conntrack parameter %q: contains potentially dangerous characters", param)
113+
}
114+
115+
// Additional validation for common parameter patterns
116+
// Valid patterns include:
117+
// - Single-char flags: -m, -s (single hyphen followed by single letter)
118+
// - Key=value pairs: zone=5, mark=0x1, src=10.0.0.1 (key must contain only alphanumeric, underscore, hyphen)
119+
validParam := regexp.MustCompile(`^(-[a-zA-Z]|[a-zA-Z0-9_-]+=[a-zA-Z0-9x.:,/_-]+)$`)
120+
if !validParam.MatchString(param) {
121+
return fmt.Errorf("invalid conntrack parameter format %q: must be a flag (e.g., '-m') or key=value pair (e.g., 'zone=5')", param)
122+
}
123+
}
124+
125+
return nil
126+
}

0 commit comments

Comments
 (0)