Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import { NextRequest, NextResponse } from "next/server";
import { Ollama } from "ollama";
import { z } from "zod";

// Initialize Ollama client
const ollama = new Ollama({
host: "http://localhost:11434",
});

// Define response schemas for structured output
const CommandResponseSchema = {
type: "object",
properties: {
type: {
type: "string",
enum: ["command", "explanation", "error"],
},
content: {
type: "string",
},
commands: {
type: "array",
items: {
type: "object",
properties: {
action: { type: "string" },
target: { type: "string" },
parameters: {
type: "object",
additionalProperties: true,
},
description: { type: "string" },
},
required: ["action", "description"],
},
},
networkChanges: {
type: "object",
properties: {
nodes: {
type: "array",
items: { type: "object" },
},
connections: {
type: "array",
items: { type: "object" },
},
rules: {
type: "array",
items: { type: "string" },
},
},
},
},
required: ["type", "content"],
};

const TextResponseSchema = {
type: "object",
properties: {
type: { type: "string", enum: ["text"] },
content: { type: "string" },
},
required: ["type", "content"],
};

export async function POST(request: NextRequest) {
try {
const { message, context } = await request.json();

// Determine if this is a command request or general query
const commandKeywords = [
"command",
"execute",
"run",
"apply",
"block",
"allow",
"rule",
"iptables",
"add",
"remove",
"delete",
"create",
"configure",
"set",
"enable",
"disable",
"drop",
"accept",
"reject",
"forward",
"input",
"output",
"chain",
"table",
"flush",
"policy",
"insert",
"append",
"firewall",
"route",
"redirect",
"nat",
"masquerade",
"port",
"protocol",
"tcp",
"udp",
"icmp",
"ssh",
"http",
"https",
"connect",
"disconnect",
"link",
"unlink",
"bridge",
"subnet",
"vlan",
"interface",
];

const isCommandRequest = commandKeywords.some((keyword) =>
message.toLowerCase().includes(keyword)
);

if (isCommandRequest) {
// Generate structured response for commands
const prompt = `You are an expert network security assistant for LLMGuard.

Current network context: ${JSON.stringify(context)}

Analyze the request and provide a structured response with specific commands that can be executed on the network topology. Focus on iptables rules, network configuration, or topology changes.
After generating the request, generate a quick summary of the changes being made.

For iptables commands, provide the exact command syntax.
For network changes, specify node IDs and connection modifications.
Always include a clear description of what each command does.

Respond in JSON format with:
- type: "command"
- content: A brief explanation of what you're doing
- commands: Array of command objects with action, target, parameters, and description

Example response format:
{
"type": "command",
"content": "I'll create iptables rules to block traffic between the specified nodes.",
"commands": [
{
"action": "iptables",
"target": "fw-1",
"parameters": {
"rule": "iptables -A FORWARD -s 172.20.3.10 -d 172.20.3.11 -j DROP"
},
"description": "Block traffic from user-1 to user-2"
}
]
}`;

const response = await ollama.generate({
model: "qwen3:4b",
prompt: prompt,
system: message,
format: CommandResponseSchema,
options: {
temperature: 0.1,
},
});

try {
const parsedResponse = JSON.parse(response.response);

return NextResponse.json({
success: true,
data: parsedResponse,
type: "structured",
});
} catch (parseError) {
// Fallback to text response if JSON parsing fails
return NextResponse.json({
success: true,
data: {
type: "explanation",
content: response.response,
},
type: "text",
});
}
} else {
// Use regular text generation for explanations
const prompt = `You are an expert network security assistant for LLMGuard.

Current network context: ${JSON.stringify(context)}

Provide a helpful, detailed explanation about network security, firewall rules, or topology analysis. Be specific and technical when appropriate. Keep responses concise but informative.
Be friendly but still professional.
`;

const response = await ollama.chat({
model: "qwen3:4b",
messages: [
{ role: "system", content: prompt },
{ role: "user", content: message },
],
options: {
temperature: 0.3,
},
format: TextResponseSchema,
});

return NextResponse.json({
success: true,
data: {
type: "explanation",
content: JSON.parse(response.message.content).content,
},
type: "text",
});
}
} catch (error) {
console.error("LLM API Error:", error);
return NextResponse.json(
{
success: false,
error:
error instanceof Error
? error.message
: "Failed to process request",
},
{ status: 500 }
);
}
}
129 changes: 129 additions & 0 deletions app/api/execute-command/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { NextRequest, NextResponse } from "next/server";
import { exec } from "child_process";
import { promisify } from "util";

const execAsync = promisify(exec);

export async function POST(request: NextRequest) {
try {
const { commands, nodeId } = await request.json();

if (!commands || !Array.isArray(commands)) {
return NextResponse.json(
{
success: false,
error: "Invalid commands format",
},
{ status: 400 }
);
}

const results = [];

for (const command of commands) {
try {
let dockerCommand = "";

if (command.action === "iptables") {
// Execute iptables command in specific container
const targetNode = nodeId || command.target;
if (!targetNode) {
results.push({
command: command.action,
success: false,
error: "No target node specified for iptables command",
});
continue;
}

dockerCommand = `docker exec ${targetNode} ${command.parameters.rule}`;
} else if (command.action === "network_config") {
// Execute network configuration
const targetNode = nodeId || command.target;
if (!targetNode) {
results.push({
command: command.action,
success: false,
error: "No target node specified for network config",
});
continue;
}

dockerCommand = `docker exec ${targetNode} ${command.parameters.command}`;
} else if (command.action === "ping_test") {
// Execute ping test
const targetNode = nodeId || command.target;
if (!targetNode) {
results.push({
command: command.action,
success: false,
error: "No target node specified for ping test",
});
continue;
}

dockerCommand = `docker exec ${targetNode} ping -c 3 ${command.parameters.destination}`;
} else if (command.action === "topology_change") {
// Handle topology changes (would require updating state.json and reinitializing)
results.push({
command: command.action,
success: false,
message:
"Topology changes require manual approval and system restart",
});
continue;
} else {
// Generic docker exec command
const targetNode = nodeId || command.target;
if (!targetNode) {
results.push({
command: command.action,
success: false,
error: "No target node specified",
});
continue;
}

dockerCommand = `docker exec ${targetNode} ${
command.parameters?.command || command.action
}`;
}

if (dockerCommand) {
const { stdout, stderr } = await execAsync(dockerCommand);
results.push({
command: command.action,
description: command.description,
target: command.target,
success: true,
output: stdout.trim(),
error: stderr.trim() || null,
});
}
} catch (error: any) {
console.error(`Command execution failed:`, error);
results.push({
command: command.action,
description: command.description,
target: command.target,
success: false,
error: error.message || "Unknown error occurred",
});
}
}

return NextResponse.json({
success: true,
results,
});
} catch (error) {
console.error("Execute command API error:", error);
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "Unknown error",
},
{ status: 500 }
);
}
}
Loading
Loading