GNS3 2.2.61 REST API 使用详解

基于 2.2.61 实机验证的 /v2 接口指南:Basic 鉴权、computes 与 capabilities 失真、工程 open/close 与 auto_close、节点与链路创建、qemu_path 必填、系统盘文件上传 204 与无删除接口、WebSocket 心跳与 node.updated 推送,以及 400/401/403/404/405/409 原样报错与端到端脚本。

GNS3GNS3网络仿真Python2026/10/27

GNS3 2.2.61 REST API 使用详解

目标:把 GNS3 2.2.61 的 /v2 REST 接口与 WebSocket 通知讲透——怎么认证、怎么建工程、怎么建节点与链路、怎么上传系统盘文件、怎么订阅状态变化,以及每个错误码背后真实的原因。本文所有请求与响应均来自 2.2.61 实机(Ubuntu 22.04.5,服务端监听 3080),接口对应的服务端版本为 2.2.61;组件构成见 《GNS3 2.2.61 介绍与选型指南》,部署见 《Ubuntu 22.04 安装 GNS3 2.2.61 服务端指南》,参数含义见 《GNS3 2.2.61 配置参数详解》。

一、接口约定与认证

1.1 基础约定

项目值
基础路径http://<host>:3080/v2
数据格式请求与响应均为 JSON(上传系统盘文件除外,走原始字节流)
方法GET / POST / PUT / DELETE
认证配置开启 auth 后使用 HTTP Basic
响应标识响应头带 X-Route、Server: Python/3.10 GNS3/2.2.61

版本接口是最轻量的连通性检查,也是唯一不依赖任何对象状态的接口:

curl -s http://127.0.0.1:3080/v2/version
{
    "local": false,
    "version": "2.2.61"
}

local 字段反映服务端配置里的 local 值,不是"是否本机"的意思。

1.2 认证

配置里开启鉴权后,不带凭据的请求直接 401,不会返回业务数据:

curl -s -o /dev/null -w 'noauth HTTP %{http_code}\n' http://127.0.0.1:3080/v2/version
curl -s -o /dev/null -w 'basic  HTTP %{http_code}\n' -u root:q1w2e3r4 http://127.0.0.1:3080/v2/version
noauth HTTP 401
basic  HTTP 200

401 响应带 WWW-Authenticate: Basic realm="GNS3 server",所有写操作同样需要凭据。脚本里推荐用环境变量传账号,避免密码进 shell 历史:

export GNS3_API=http://127.0.0.1:3080/v2
curl -s -u "$GNS3_USER:$GNS3_PASS" $GNS3_API/projects

1.3 一处易踩的认证豁免

console 的 WebSocket 路径(/projects/{project_id}/nodes/{node_id}/console/ws)不在 Basic 认证范围内。这意味着仅靠 HTTP 鉴权并不能保护 console 通道,服务端暴露在公网时必须在反向代理层再加一层访问控制。

二、计算节点信息

2.1 列出计算节点

curl -s http://127.0.0.1:3080/v2/computes
[
    {
        "capabilities": {
            "node_types": [
                "cloud", "ethernet_hub", "ethernet_switch", "nat", "vpcs",
                "virtualbox", "dynamips", "frame_relay_switch", "atm_switch",
                "qemu", "vmware", "traceng", "iou"
            ],
            "platform": "linux",
            "version": "2.2.61"
        },
        "compute_id": "local",
        "connected": true,
        "cpu_usage_percent": 83.5,
        "host": "127.0.0.1",
        "last_error": null,
        "memory_usage_percent": 63.3,
        "name": "gns3-server",
        "port": 3080,
        "protocol": "http",
        "user": null
    }
]

(node_types 上列为节选)node_types 是服务端实际支持的节点类型清单,脚本里可以据此判断某类节点是否可用;connected 为 false 时先查 last_error,再查 compute 之间的连通性。

2.2 capabilities 与真实可用性不一致(实测)

QEMU 能力探测接口在本机 KVM 完全不可用的情况下,仍然报告支持 KVM:

curl -s http://127.0.0.1:3080/v2/compute/qemu/capabilities
{
    "kvm": [
        "x86_64",
        "i386"
    ]
}

这个结果是"二进制编译时支持的架构",不是"当前机器能不能跑 KVM"。不要用它做健康判断:真正的判据是启动节点后观察状态是否稳定,以及配置里 require_kvm 的行为(见参数篇 6.2)。

2.3 QEMU 二进制与磁盘文件清单

curl -s http://127.0.0.1:3080/v2/compute/qemu/binaries
[
    {"path": "/usr/bin/qemu-system-i386", "version": "6.2.0"},
    {"path": "/usr/bin/qemu-system-x86_64", "version": "6.2.0"},
    {"path": "/bin/qemu-system-x86_64", "version": "6.2.0"}
]

该接口是"扫 PATH 得到的清单",与配置文件里 [Qemu] 的 qemu_path 无关(该键在 2.2.61 不被读取)。真正创建节点时必须把 qemu_path 写在节点属性里,否则报错(见第五章)。

系统盘文件清单:

curl -s http://127.0.0.1:3080/v2/compute/qemu/images
[
    {
        "filename": "empty8G.qcow2",
        "filesize": 196616,
        "md5sum": "9f2c1b0d0b0e4a1e8f0b6d4b3c2a1f00"
    }
]

首启时服务端会自动装入 empty8G.qcow2、empty10G.qcow2 等内置文件,出现在这个列表里是正常现象。

三、工程(project)生命周期

3.1 创建

curl -s -X POST http://127.0.0.1:3080/v2/projects \
     -H 'Content-Type: application/json' \
     -d '{"name":"api-doc-demo"}'
{
    "auto_close": true,
    "auto_open": false,
    "auto_start": false,
    "drawing_grid_size": 25,
    "filename": "api-doc-demo.gns3",
    "grid_size": 75,
    "name": "api-doc-demo",
    "path": "/var/lib/gns3/projects/f1c60cb3-ac6f-4f99-bf30-4d9e61d47911",
    "project_id": "f1c60cb3-ac6f-4f99-bf30-4d9e61d47911",
    "scene_height": 1000,
    "scene_width": 2000,
    "status": "closed"
}

两个关键默认值:

  • status 是 closed,新建工程不会自动打开;
  • auto_close 为 true,工程在一段时间无客户端访问后会自动关闭。实测中一个已打开的工程在操作中断后自动回到 closed,此时查询节点/统计会得到空结果,容易误判为"数据丢了"。

写入位置由 projects_path 决定,path 字段可以直接拿去做备份脚本的输入。

3.2 打开与关闭

curl -s -o /dev/null -w 'open HTTP %{http_code}\n' -X POST $GNS3_API/projects/$PID/open
curl -s -o /dev/null -w 'close HTTP %{http_code}\n' -X POST $GNS3_API/projects/$PID/close
open HTTP 201
close HTTP 204

未打开就操作工程内的对象会被拒:

{"message": "The project is not opened", "status": 403}

所以自动化脚本的第一个动作应该是 open,且每次长时间空闲后重新确认 status:

curl -s $GNS3_API/projects/$PID | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])'
opened

3.3 统计与删除

curl -s $GNS3_API/projects/$PID/stats
{
    "drawings": 0,
    "links": 1,
    "nodes": 2,
    "snapshots": 0
}

统计只在工程处于 opened 时反映真实内容;工程自动关闭后返回全 0。

删除工程返回 204,磁盘目录同步移除:

curl -s -o /dev/null -w 'delete HTTP %{http_code}\n' -X DELETE $GNS3_API/projects/$PID
delete HTTP 204

四、节点与链路

4.1 创建节点

节点的统一入口是 POST /v2/projects/{project_id}/nodes,用 node_type 区分类型;不要去找 /vpcs/nodes、/qemu/nodes 这类路径,它们不存在(会 404)。

curl -s -X POST $GNS3_API/projects/$PID/nodes \
     -H 'Content-Type: application/json' \
     -d '{
           "name": "Q1",
           "node_type": "qemu",
           "compute_id": "local",
           "properties": {
             "ram": 128,
             "hda_disk_image": "empty8G.qcow2",
             "qemu_path": "/usr/bin/qemu-system-x86_64"
           }
         }'

返回体是完整的节点对象,关键字段:

{
    "name": "Q1",
    "node_id": "60c4e99e-8f32-453c-8c79-a15e2c0248e2",
    "node_type": "qemu",
    "status": "stopped",
    "console": null,
    "properties": {
        "ram": 128,
        "hda_disk_image": "empty8G.qcow2",
        "qemu_path": "/usr/bin/qemu-system-x86_64"
    }
}

qemu_path 必须写在节点属性里。省略时服务端会拼出一个 qemu-system-None 的路径并直接报 409:

{
    "message": "QEMU binary path qemu-system-None is not found in the path",
    "status": 409
}

status 恒为 stopped,创建不等于启动。

4.2 创建链路

链路创建要用两端节点的 node_id + 适配器号 + 端口号,缺一不可;字段缺失会得到 schema 校验错误:

curl -s -X POST $GNS3_API/projects/$PID/links \
     -H 'Content-Type: application/json' \
     -d '{
           "nodes": [
             {"node_id": "'$N1'", "adapter_number": 0, "port_number": 0},
             {"node_id": "'$N2'", "adapter_number": 0, "port_number": 0}
           ]
         }'
{
    "link_id": "5ba82033-a2fd-4bf3-9623-8421cd5ee0c6",
    "link_type": "ethernet",
    "nodes": [
        {"node_id": "60c4e99e-8f32-453c-8c79-a15e2c0248e2", "adapter_number": 0, "port_number": 0},
        {"node_id": "279730b5-a952-4419-8542-e2f7bb707987", "adapter_number": 0, "port_number": 0}
    ]
}

node_id 传空字符串时的原样报错(可用于自查拼装脚本):

{
    "message": "JSON schema error with API request '/v2/projects/<uuid>/links' and JSON data '{...}': '' is too short",
    "method": "POST",
    "path": "/v2/projects/{project_id}/links",
    "status": 400
}

4.3 启动与停止

curl -s -o /tmp/q1start.json -w 'start HTTP %{http_code}\n' \
     -X POST $GNS3_API/projects/$PID/nodes/$N1/start
start HTTP 200

启动成功的响应会把分配到的 console 端口与最终命令行回填:

{
    "name": "Q1",
    "status": "started",
    "console": 5000,
    "console_host": "0.0.0.0"
}

命令行(节选,可在排障时直接拿去复现):

/usr/bin/qemu-system-x86_64 -name Q1 -m 128M -smp cpus=1,sockets=1 -boot order=c \
  -drive file=/var/lib/gns3/projects/<project_id>/project-files/qemu/<node_id>/hda_disk.qcow2,if=none,index=0,media=disk,id=drive0 \
  -uuid <node_id> -serial telnet:127.0.0.1:5001,server,nowait -monitor tcp:127.0.0.1:44161,server,nowait \
  -net none -device e1000,mac=0c:c4:e9:9e:00:00,netdev=gns3-0 \
  -netdev socket,id=gns3-0,udp=127.0.0.1:20003,localaddr=127.0.0.1:20002 -display none

几个信息点:

  • QEMU 节点会占用两个连续端口:console(5000)与 serial 端口(5001),端口段由配置里的 console 范围决定;
  • 链路在运行时体现为 -netdev socket 的 UDP 隧道,端口取自配置里的 UDP 范围(实测 20002/20003);
  • 没有 -enable-kvm,说明当前走的是纯软件模拟(配置 enable_kvm = false)。

停止同样简单,返回 200:

curl -s -o /dev/null -w 'stop HTTP %{http_code}\n' \
     -X POST $GNS3_API/projects/$PID/nodes/$N1/stop
stop HTTP 200

启动接口返回 200 不代表节点一直活着。实测在 enable_kvm = true 而 KVM 不可用时,启动先 200、约 3 秒后节点自动变 stopped,接口不会补报错误。自动化脚本应在启动后轮询一次状态:

sleep 4
curl -s $GNS3_API/projects/$PID/nodes/$N1 \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])'
started

五、系统盘文件上传

5.1 上传

上传接口接收原始字节流,不是 multipart 表单,成功只返回 204 无响应体:

qemu-img create -f qcow2 /tmp/demo-disk.qcow2 64M
curl -s -o /dev/null -w 'upload HTTP %{http_code}\n' \
     -X POST --data-binary @/tmp/demo-disk.qcow2 \
     $GNS3_API/compute/qemu/images/demo-disk.qcow2
Formatting '/tmp/demo-disk.qcow2', fmt=qcow2 cluster_size=65536 ... size=67108864 ...
upload HTTP 204

上传后服务端会计算校验和,列表接口可以直接核对文件是否完整:

curl -s $GNS3_API/compute/qemu/images
[
    {
        "filename": "demo-disk.qcow2",
        "filesize": 196616,
        "md5sum": "41ce4d32ce22529a293044bc1eb4e734"
    }
]

本地文件大小与 filesize 一致(196616 字节)即上传完整;qcow2 是稀疏格式,创建 64M 磁盘只占 196KB 属正常。

5.2 没有删除接口(实测)

对同一路径发 DELETE 会得到 405:

curl -s -o /dev/null -w 'delete image HTTP %{http_code}\n' -X DELETE $GNS3_API/compute/qemu/images/demo-disk.qcow2
delete image HTTP 405

2.2.61 的该资源只注册了 GET(下载)与 POST(上传),没有 DELETE。清理只能落到磁盘目录上(images_path/QEMU/<filename>),或者先停止全部引用它的节点再手工删除,否则节点启动会因找不到系统盘文件失败。

下载同理,直接 GET 同名路径即得到文件流。

六、WebSocket 通知

6.1 工程通知通道

ws://<host>:3080/v2/projects/{project_id}/notifications/ws

连接后服务端持续推送两类消息:心跳(带资源占用)与对象变更。实测抓取(启动/停止 Q1 期间的 6 秒窗口):

{"action": "ping", "event": {"cpu_usage_percent": 88.4, "memory_usage_percent": 43.5}}
{"action": "node.updated", "event": {"name": "Q1", "status": "started"}}
{"action": "ping", "event": {"cpu_usage_percent": 90.3, "memory_usage_percent": 43.7}}
{"action": "node.updated", "event": {"name": "Q1", "status": "stopped"}}
{"action": "node.updated", "event": {"name": "Q1", "status": "stopped"}}

要点:

  • ping 是心跳,event 里带 cpu_usage_percent、memory_usage_percent,可直接做监控数据源;
  • node.updated 会在启动、停止各推一次,停止时偶发重复推送,客户端要做幂等处理;
  • 心跳间隔在 5 秒量级,脚本里不要假设"连上就立刻有业务事件",需要配合轮询兜底。

Python 订阅示例(依赖 aiohttp):

import asyncio, json, aiohttp

async def watch(project_id):
    url = f"ws://127.0.0.1:3080/v2/projects/{project_id}/notifications/ws"
    async with aiohttp.ClientSession() as s:
        async with s.ws_connect(url) as ws:
            async for msg in ws:
                if msg.type == aiohttp.WSMsgType.TEXT:
                    data = json.loads(msg.data)
                    if data["action"] in ("node.updated", "link.created", "link.deleted"):
                        print(data["action"], data["event"].get("name"), data["event"].get("status"))

asyncio.run(watch("<project_id>"))

6.2 其它两类通道

通道路径用途
compute 通知/v2/compute/notifications/ws订阅计算节点自身事件(如可用文件变化)
节点 console/v2/projects/{project_id}/nodes/{node_id}/console/ws浏览器终端,二进制/文本帧承载 console 输出

console 通道不在认证范围内(见 1.3),暴露在外网时必须由反代控制访问。

七、错误码与排障

状态码典型原样报错根因与处理
400JSON schema error with API request ...: '' is too short请求体字段缺失/为空,按 schema 补齐;链路必须有 node_id/adapter_number/port_number
401无响应体,带 WWW-Authenticate: Basic realm="GNS3 server"未认证;补 Basic 凭据(且确认 auth 已开启)
403{"message": "The project is not opened", "status": 403}工程未打开;先 POST /projects/{id}/open
404404: Not Found路径拼错(常见:把节点创建写成 /vpcs/nodes、/qemu/nodes)
405Method Not Allowed(Allow: GET,OPTIONS,POST)方法不支持;HEAD /v2/version、DELETE /compute/qemu/images/xxx 都会命中原样 405
409QEMU binary path qemu-system-None is not found in the path节点属性缺 qemu_path
409VPCS executable version must be >= 0.6.1 but not a 0.8VPCS 版本过旧(Ubuntu 22.04 自带 0.5b2),升级到 0.8.4
409uBridge is not available, path doesn't exist...缺 ubridge,或路径不在 PATH

补充两个实测细节:

  1. HEAD /v2/version 返回 405 而不是 200,健康检查请用 GET;
  2. GET /v2/status 不存在(404),服务状态用 /v2/version 或 /v2/computes 判断。

八、端到端脚本

把前面各章串起来,一个可以直接跑的完整流程(成功时最终输出 started):

#!/bin/bash
set -e
API=${GNS3_API:-http://127.0.0.1:3080/v2}
AUTH=${GNS3_AUTH:--u root:q1w2e3r4}

PID=$(curl -s $AUTH -X POST $API/projects -H 'Content-Type: application/json' \
      -d '{"name":"api-doc-demo"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["project_id"])')
curl -s $AUTH -o /dev/null -X POST $API/projects/$PID/open

mknode () {
  curl -s $AUTH -X POST $API/projects/$PID/nodes -H 'Content-Type: application/json' \
    -d "{\"name\":\"$1\",\"node_type\":\"qemu\",\"compute_id\":\"local\",
         \"properties\":{\"ram\":128,\"hda_disk_image\":\"empty8G.qcow2\",
                         \"qemu_path\":\"/usr/bin/qemu-system-x86_64\"}}" \
    | python3 -c 'import sys,json;print(json.load(sys.stdin)["node_id"])'
}

N1=$(mknode Q1)
N2=$(mknode Q2)

curl -s $AUTH -X POST $API/projects/$PID/links -H 'Content-Type: application/json' \
  -d "{\"nodes\":[{\"node_id\":\"$N1\",\"adapter_number\":0,\"port_number\":0},
                  {\"node_id\":\"$N2\",\"adapter_number\":0,\"port_number\":0}]}" >/dev/null

curl -s $AUTH -o /dev/null -X POST $API/projects/$PID/nodes/$N1/start
sleep 4
curl -s $AUTH $API/projects/$PID/nodes/$N1 \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])'

# 清理
curl -s $AUTH -o /dev/null -X POST $API/projects/$PID/close
curl -s $AUTH -o /dev/null -X DELETE $API/projects/$PID

注意脚本里的三处"必须":创建后 open、节点属性带 qemu_path、启动后等 4 秒再查状态。漏掉任何一处,结果要么 403,要么 409,要么误判为成功。

九、参考与延伸阅读