diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 1399dac6..8456ec06 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -17,12 +17,12 @@ jobs:
strategy:
matrix:
- python-version: ["3.7", "3.8", "3.9", "3.10"]
+ python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Display Python version
diff --git a/CHANGELOG b/CHANGELOG
index 84b1d639..83d2e083 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,46 @@
# Change Log
+## 3.0.0a3 27/12/2022
+
+* Add web-ui v3.0.0a3
+* Add config option to change the server name. Ref #2149
+* Option to disable image discovery and do not scan parent directory
+* Allow raw images by default. Fixes https://github.com/GNS3/gns3-server/issues/2097
+* Fix bug when creating Dynamips router with chassis setting
+* Stricter checks to create/update an Ethernet switch and add tests
+* Fix schema for removing WICs from Cisco routers. Fixes #3392
+* Fix issues with VMnet interface on macOS >= 11.0. Ref #3381
+* Use importlib_resources instead of pkg_resources and install built-in appliances in config dir.
+* Fix console vnc don't use configured ports in some case. Fixes #2111
+* Add missing VMware settings in gns3_server.conf
+* Make version PEP 440 compliant
+* Support for Python 3.11
+* Allow for more dependency versions at patch level
+* Replace deprecated distro.linux_distribution() call
+* Update gns3.service.systemd
+* Fix some issues with HTTP notification streams
+* gns3.service.openrc: make openrc script posix compliant
+* fix: use exact match to find interface in windows to avoid get wrong interface
+
+## 2.2.35.1 10/11/2022
+
+* Re-release Web-Ui v2.2.35
+
+## 2.2.35 08/11/2022
+
+* Release web-ui v2.2.35
+* Fix issues with VMnet interface on macOS >= 11.0. Ref #3381
+* Use importlib_resources instead of pkg_resources and install built-in appliances in config dir.
+* Fix console vnc don't use configured ports in some case. Fixes #2111
+* Add missing VMware settings in gns3_server.conf
+* Make version PEP 440 compliant
+* Support for Python 3.11
+* Allow for more dependency versions at patch level
+* Replace deprecated distro.linux_distribution() call
+* Update gns3.service.systemd
+* gns3.service.openrc: make openrc script posix compliant
+* fix: use exact match to find interface in windows to avoid get wrong interface
+
## 3.0.0a2 06/09/2022
* Add web-ui v3.0.0a2
diff --git a/conf/gns3_server.conf b/conf/gns3_server.conf
index d1143eb4..fe15c335 100644
--- a/conf/gns3_server.conf
+++ b/conf/gns3_server.conf
@@ -15,6 +15,9 @@ default_admin_password = admin
[Server]
+; Server name, default is what is returned by socket.gethostname()
+name = GNS3_Server
+
; What protocol the server uses (http or https)
protocol = http
@@ -54,6 +57,12 @@ configs_path = /home/gns3/GNS3/configs
; "Affinity-square-gray", "Affinity-circle-blue", "Affinity-circle-red" and "Affinity-circle-gray"
default_symbol_theme = Affinity-square-blue
+; Option to enable or disable raw images to be uploaded to the server
+allow_raw_images = True
+
+; Option to automatically discover images in the images directory
+auto_discover_images = True
+
; Option to automatically send crash reports to the GNS3 team
report_errors = True
@@ -133,3 +142,9 @@ monitor_host = 127.0.0.1
enable_hardware_acceleration = True
; Require hardware acceleration in order to start VMs
require_hardware_acceleration = False
+
+[VMware]
+; First vmnet interface of the range that can be managed by the GNS3 server
+vmnet_start_range = 2
+; Last vmnet interface of the range that can be managed by the GNS3 server. It must be maximum 19 on Windows.
+vmnet_end_range = 255
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 6f964e26..faefc4dd 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,8 +1,8 @@
-r requirements.txt
-pytest==7.1.2
-flake8==5.0.4
+pytest==7.2.0
+flake8==5.0.4 # v5.0.4 is the last to support Python 3.7
pytest-timeout==2.1.0
-pytest-asyncio==0.19.0
+pytest-asyncio==0.20.3
requests==2.28.1
-httpx==0.23.0
+httpx==0.23.1
diff --git a/gns3server/api/routes/compute/dynamips_nodes.py b/gns3server/api/routes/compute/dynamips_nodes.py
index ea74ad25..5f34f066 100644
--- a/gns3server/api/routes/compute/dynamips_nodes.py
+++ b/gns3server/api/routes/compute/dynamips_nodes.py
@@ -61,9 +61,11 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) ->
dynamips_manager = Dynamips.instance()
platform = node_data.platform
- chassis = None
+ print(node_data.chassis, platform in DEFAULT_CHASSIS)
if not node_data.chassis and platform in DEFAULT_CHASSIS:
chassis = DEFAULT_CHASSIS[platform]
+ else:
+ chassis = node_data.chassis
node_data = jsonable_encoder(node_data, exclude_unset=True)
vm = await dynamips_manager.create_node(
node_data.pop("name"),
diff --git a/gns3server/api/routes/compute/ethernet_switch_nodes.py b/gns3server/api/routes/compute/ethernet_switch_nodes.py
index 8b30c476..e672954b 100644
--- a/gns3server/api/routes/compute/ethernet_switch_nodes.py
+++ b/gns3server/api/routes/compute/ethernet_switch_nodes.py
@@ -150,12 +150,22 @@ def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
pass
+@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
+def reload_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
+ """
+ Reload an Ethernet switch.
+ This endpoint results in no action since Ethernet switch nodes are always on.
+ """
+
+ pass
+
+
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
)
-async def create_nio(
+async def create_ethernet_switch_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
@@ -169,7 +179,7 @@ async def create_nio(
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
-async def delete_nio(
+async def delete_ethernet_switch_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
@@ -185,7 +195,7 @@ async def delete_nio(
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
-async def start_capture(
+async def start_ethernet_switch_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
@@ -205,7 +215,7 @@ async def start_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
-async def stop_capture(
+async def stop_ethernet_switch_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
diff --git a/gns3server/api/routes/controller/images.py b/gns3server/api/routes/controller/images.py
index e61444ce..a37566fa 100644
--- a/gns3server/api/routes/controller/images.py
+++ b/gns3server/api/routes/controller/images.py
@@ -70,7 +70,6 @@ async def upload_image(
current_user: schemas.User = Depends(get_current_active_user),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
install_appliances: Optional[bool] = False,
- allow_raw_image: Optional[bool] = False
) -> schemas.Image:
"""
Upload an image.
@@ -91,6 +90,7 @@ async def upload_image(
raise ControllerBadRequestError(f"Image '{image_path}' already exists")
try:
+ allow_raw_image = Config.instance().settings.Server.allow_raw_images
image = await write_image(image_path, full_path, request.stream(), images_repo, allow_raw_image=allow_raw_image)
except (OSError, InvalidImageError, ClientDisconnect) as e:
raise ControllerError(f"Could not save image '{image_path}': {e}")
diff --git a/gns3server/api/routes/controller/notifications.py b/gns3server/api/routes/controller/notifications.py
index 2618efae..52d29e74 100644
--- a/gns3server/api/routes/controller/notifications.py
+++ b/gns3server/api/routes/controller/notifications.py
@@ -18,7 +18,7 @@
API routes for controller notifications.
"""
-from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
+from fastapi import APIRouter, Request, Depends, WebSocket, WebSocketDisconnect
from fastapi.responses import StreamingResponse
from websockets.exceptions import ConnectionClosed, WebSocketException
@@ -35,17 +35,24 @@ router = APIRouter()
@router.get("", dependencies=[Depends(get_current_active_user)])
-async def controller_http_notifications() -> StreamingResponse:
+async def controller_http_notifications(request: Request) -> StreamingResponse:
"""
Receive controller notifications about the controller from HTTP stream.
"""
- async def event_stream():
- with Controller.instance().notification.controller_queue() as queue:
- while True:
- msg = await queue.get_json(5)
- yield f"{msg}\n".encode("utf-8")
+ from gns3server.api.server import app
+ log.info(f"New client {request.client.host}:{request.client.port} has connected to controller HTTP "
+ f"notification stream")
+ async def event_stream():
+ try:
+ with Controller.instance().notification.controller_queue() as queue:
+ while not app.state.exiting:
+ msg = await queue.get_json(5)
+ yield f"{msg}\n".encode("utf-8")
+ finally:
+ log.info(f"Client {request.client.host}:{request.client.port} has disconnected from controller HTTP "
+ f"notification stream")
return StreamingResponse(event_stream(), media_type="application/json")
diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py
index 91021f64..302173d0 100644
--- a/gns3server/api/routes/controller/projects.py
+++ b/gns3server/api/routes/controller/projects.py
@@ -210,18 +210,19 @@ async def project_http_notifications(project_id: UUID) -> StreamingResponse:
Receive project notifications about the controller from HTTP stream.
"""
+ from gns3server.api.server import app
controller = Controller.instance()
project = controller.get_project(str(project_id))
- log.info(f"New client has connected to the notification stream for project ID '{project.id}' (HTTP steam method)")
+ log.info(f"New client has connected to the notification stream for project ID '{project.id}' (HTTP stream method)")
async def event_stream():
try:
with controller.notification.project_queue(project.id) as queue:
- while True:
+ while not app.state.exiting:
msg = await queue.get_json(5)
- yield (f"{msg}\n").encode("utf-8")
+ yield f"{msg}\n".encode("utf-8")
finally:
log.info(f"Client has disconnected from notification for project ID '{project.id}' (HTTP stream method)")
if project.auto_close:
diff --git a/gns3server/api/routes/index.py b/gns3server/api/routes/index.py
index 571ee2fd..0d64828b 100644
--- a/gns3server/api/routes/index.py
+++ b/gns3server/api/routes/index.py
@@ -55,6 +55,9 @@ async def web_ui(file_path: str):
if static is None or not os.path.exists(static):
static = get_resource(os.path.join("static", "web-ui", "index.html"))
+ if static is None:
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
+
# guesstype prefers to have text/html type than application/javascript
# which results with warnings in Firefox 66 on Windows
# Ref. gns3-server#1559
diff --git a/gns3server/appliances/alpine-linux-virt.gns3a b/gns3server/appliances/alpine-linux-virt.gns3a
new file mode 100644
index 00000000..d7c2883d
--- /dev/null
+++ b/gns3server/appliances/alpine-linux-virt.gns3a
@@ -0,0 +1,45 @@
+{
+ "appliance_id": "3da5c614-772c-4963-af86-f24e058c9216",
+ "name": "Alpine Linux Virt",
+ "category": "guest",
+ "description": "Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.\n\nThis is the qemu version of Alpine Linux, stripped down to the maximum, only the default packages are installed without an SSH server.",
+ "vendor_name": "Alpine Linux Development Team",
+ "vendor_url": "http://alpinelinux.org",
+ "documentation_url": "http://wiki.alpinelinux.org",
+ "product_name": "Alpine Linux Virt",
+ "registry_version": 4,
+ "status": "stable",
+ "availability": "free",
+ "maintainer": "Adnan RIHAN",
+ "maintainer_email": "adnan@rihan.fr",
+ "usage": "Autologin is enabled as \"root\" with no password.\n\nThe network interfaces aren't configured, you can do either of the following:\n- Use alpine's DHCP client: `udhcpc`\n- Configure them manually (ip address add …, ip route add …)\n- Modify interfaces file in /etc/network/interfaces\n- Use alpine's wizard: `setup-interfaces`",
+ "symbol": "alpine-virt-qemu.svg",
+ "port_name_format": "eth{0}",
+ "qemu": {
+ "adapter_type": "virtio-net-pci",
+ "adapters": 1,
+ "ram": 128,
+ "hda_disk_interface": "virtio",
+ "arch": "x86_64",
+ "console_type": "telnet",
+ "kvm": "allow"
+ },
+ "images": [
+ {
+ "filename": "alpine-virt-3.16.img",
+ "version": "3.16",
+ "md5sum": "ce90ff64b8f8e5860c49ea4a038e54cc",
+ "filesize": 96468992,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/alpine-virt-3.16.img/download"
+ }
+ ],
+ "versions": [
+ {
+ "name": "3.16",
+ "images": {
+ "hda_disk_image": "alpine-virt-3.16.img"
+ }
+ }
+ ]
+}
diff --git a/gns3server/appliances/aruba-arubaoscx.gns3a b/gns3server/appliances/aruba-arubaoscx.gns3a
index db45527b..39afe6a6 100644
--- a/gns3server/appliances/aruba-arubaoscx.gns3a
+++ b/gns3server/appliances/aruba-arubaoscx.gns3a
@@ -30,6 +30,13 @@
"process_priority": "normal"
},
"images": [
+ {
+ "filename": "arubaoscx-disk-image-genericx86-p4-20220815162137.vmdk",
+ "version": "10.10.1000",
+ "md5sum": "40f9ddf1e12640376af443b5d982f2f6",
+ "filesize": 356162560,
+ "download_url": "https://asp.arubanetworks.com/"
+ },
{
"filename": "arubaoscx-disk-image-genericx86-p4-20220616193419.vmdk",
"version": "10.10.0002",
@@ -95,6 +102,12 @@
}
],
"versions": [
+ {
+ "name": "10.10.1000",
+ "images": {
+ "hda_disk_image": "arubaoscx-disk-image-genericx86-p4-20220815162137.vmdk"
+ }
+ },
{
"name": "10.10.0002",
"images": {
diff --git a/gns3server/appliances/cisco-3725.gns3a b/gns3server/appliances/cisco-3725.gns3a
index 2e258082..6eeafb06 100644
--- a/gns3server/appliances/cisco-3725.gns3a
+++ b/gns3server/appliances/cisco-3725.gns3a
@@ -21,14 +21,14 @@
"images": [
{
"filename": "c3725-adventerprisek9-mz.124-15.T14.image",
- "version": "124-25.T14",
+ "version": "124-15.T14",
"md5sum": "64f8c427ed48fd21bd02cf1ff254c4eb",
"filesize": 97859480
}
],
"versions": [
{
- "name": "124-25.T14",
+ "name": "124-15.T14",
"idlepc": "0x60c09aa0",
"images": {
"image": "c3725-adventerprisek9-mz.124-15.T14.image"
diff --git a/gns3server/appliances/cisco-fmcv.gns3a b/gns3server/appliances/cisco-fmcv.gns3a
index 1fe7269d..a1dbfbcc 100644
--- a/gns3server/appliances/cisco-fmcv.gns3a
+++ b/gns3server/appliances/cisco-fmcv.gns3a
@@ -77,6 +77,13 @@
"md5sum": "4cf5b7fd68075b6f7ee0dd41a4029ca0",
"filesize": 2150017536,
"download_url": "https://software.cisco.com/download/"
+ },
+ {
+ "filename": "Cisco_Firepower_Management_Center_Virtual-6.2.2-81.qcow2",
+ "version": "6.2.2 (81)",
+ "md5sum": "2f75c9c6c18a6fbb5516f6f451aef3a4",
+ "filesize": 2112356352,
+ "download_url": "https://software.cisco.com/download/"
}
],
"versions": [
@@ -121,6 +128,12 @@
"images": {
"hda_disk_image": "Cisco_Firepower_Management_Center_Virtual_VMware-6.2.1-342-disk1.vmdk"
}
+ },
+ {
+ "name": "6.2.2 (81)",
+ "images": {
+ "hda_disk_image": "Cisco_Firepower_Management_Center_Virtual-6.2.2-81.qcow2"
+ }
}
]
}
diff --git a/gns3server/appliances/cumulus-vx.gns3a b/gns3server/appliances/cumulus-vx.gns3a
index c21971a4..cdf27052 100644
--- a/gns3server/appliances/cumulus-vx.gns3a
+++ b/gns3server/appliances/cumulus-vx.gns3a
@@ -25,6 +25,14 @@
"kvm": "require"
},
"images": [
+ {
+ "filename": "cumulus-linux-5.3.1-vx-amd64-qemu.qcow2",
+ "version": "5.3.1",
+ "md5sum": "366b4e5afbfb638244fac4dd6cd092fd",
+ "filesize": 2147479552,
+ "download_url": "https://www.nvidia.com/en-us/networking/ethernet-switching/cumulus-vx/download/",
+ "direct_download_url": "https://d2cd9e7ca6hntp.cloudfront.net/public/CumulusLinux-5.3.1/cumulus-linux-5.3.1-vx-amd64-qemu.qcow2"
+ },
{
"filename": "cumulus-linux-5.1.0-vx-amd64-qemu.qcow2",
"version": "5.1.0",
diff --git a/gns3server/appliances/debian.gns3a b/gns3server/appliances/debian.gns3a
index 4b98cc20..0ab950ca 100644
--- a/gns3server/appliances/debian.gns3a
+++ b/gns3server/appliances/debian.gns3a
@@ -24,20 +24,20 @@
},
"images": [
{
- "filename": "debian-11-genericcloud-amd64-20220711-1073.qcow2",
- "version": "11.4",
- "md5sum": "e8fadf4bbf7324a2e2875a5ba00588e7",
- "filesize": 253231104,
+ "filename": "debian-11-genericcloud-amd64-20221219-1234.qcow2",
+ "version": "11.6",
+ "md5sum": "bd6ddbccc89e40deb7716b812958238d",
+ "filesize": 258801664,
"download_url": "https://cloud.debian.org/images/cloud/bullseye/",
- "direct_download_url": "https://cloud.debian.org/images/cloud/bullseye/20220711-1073/debian-11-genericcloud-amd64-20220711-1073.qcow2"
+ "direct_download_url": "https://cloud.debian.org/images/cloud/bullseye/20221219-1234/debian-11-genericcloud-amd64-20221219-1234.qcow2"
},
{
- "filename": "debian-10-genericcloud-amd64-20220328-962.qcow2",
- "version": "10.12",
- "md5sum": "e92dfa1fc779fff807856f6ea6876e42",
- "filesize": 232980480,
+ "filename": "debian-10-genericcloud-amd64-20220911-1135.qcow2",
+ "version": "10.13",
+ "md5sum": "9d4d1175bef974caba79dd6ca33d500c",
+ "filesize": 234749952,
"download_url": "https://cloud.debian.org/images/cloud/buster/",
- "direct_download_url": "https://cloud.debian.org/images/cloud/buster/20220328-962/debian-10-genericcloud-amd64-20220328-962.qcow2"
+ "direct_download_url": "https://cloud.debian.org/images/cloud/buster/20220911-1135/debian-10-genericcloud-amd64-20220911-1135.qcow2"
},
{
"filename": "debian-cloud-init-data.iso",
@@ -49,16 +49,16 @@
],
"versions": [
{
- "name": "11.4",
+ "name": "11.6",
"images": {
- "hda_disk_image": "debian-11-genericcloud-amd64-20220711-1073.qcow2",
+ "hda_disk_image": "debian-11-genericcloud-amd64-20221219-1234.qcow2",
"cdrom_image": "debian-cloud-init-data.iso"
}
},
{
- "name": "10.12",
+ "name": "10.13",
"images": {
- "hda_disk_image": "debian-10-genericcloud-amd64-20220328-962.qcow2",
+ "hda_disk_image": "debian-10-genericcloud-amd64-20220911-1135.qcow2",
"cdrom_image": "debian-cloud-init-data.iso"
}
}
diff --git a/gns3server/appliances/dns.gns3a b/gns3server/appliances/dns.gns3a
index e41026f3..fb9cbcc2 100644
--- a/gns3server/appliances/dns.gns3a
+++ b/gns3server/appliances/dns.gns3a
@@ -10,7 +10,7 @@
"status": "stable",
"maintainer": "Andras Dosztal",
"maintainer_email": "developers@gns3.net",
- "usage": "You can add records by adding entries to the /etc/hosts file in the following format:\n%IP_ADDRESS% %HOSTNAME%.lab %HOSTNAME%\n\nExample:\n192.168.123.10 router1.lab router1",
+ "usage": "You can add records by adding entries to the /etc/hosts file in the following format:\n%IP_ADDRESS% %HOSTNAME%.lab %HOSTNAME%\n\nExample:\n192.168.123.10 router1.lab router1\n\nIf you require DNS requests to be serviced from a different subnet than the one that the DNS server resides on then do the following:\n\n1. Edit (nano or vim) /ect/init.d/dnsmasq\n2. Find the line DNSMASQ_OPTS=\"$DNSMASQ_OPTS --local-service\"\n3. Remove the --local-service or comment that line out and add DNSMASQ_OPTS=\"\"\n4. Restart dnsmasq - service dnsmaq restart",
"symbol": "linux_guest.svg",
"docker": {
"adapters": 1,
diff --git a/gns3server/appliances/extreme-networks-voss.gns3a b/gns3server/appliances/extreme-networks-voss.gns3a
index 1435bcd6..0246472e 100644
--- a/gns3server/appliances/extreme-networks-voss.gns3a
+++ b/gns3server/appliances/extreme-networks-voss.gns3a
@@ -27,6 +27,13 @@
"options": "-nographic"
},
"images": [
+ {
+ "filename": "VOSSGNS3.8.8.0.0.qcow2",
+ "version": "v8.8.0.0",
+ "md5sum": "caa01094bad8ea5750261924b82ca746",
+ "filesize": 348389376,
+ "direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_VOSS/VOSSGNS3.8.8.0.0.qcow2"
+ },
{
"filename": "VOSSGNS3.8.4.0.0.qcow2",
"version": "v8.4.0.0",
@@ -78,6 +85,13 @@
}
],
"versions": [
+ {
+ "name": "v8.8.0.0",
+ "images":
+ {
+ "hda_disk_image": "VOSSGNS3.8.8.0.0.qcow2"
+ }
+ },
{
"name": "v8.4.0.0",
"images": {
diff --git a/gns3server/appliances/fortianalyzer.gns3a b/gns3server/appliances/fortianalyzer.gns3a
index e82a10f4..4ff054c5 100644
--- a/gns3server/appliances/fortianalyzer.gns3a
+++ b/gns3server/appliances/fortianalyzer.gns3a
@@ -34,6 +34,13 @@
"filesize": 340631552,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FAZ_VM64_KVM-v7.0.5-build0365-FORTINET.out.kvm.qcow2",
+ "version": "7.0.5",
+ "md5sum": "6cbc1f865ed285bb3a73323e222f03b8",
+ "filesize": 334184448,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FAZ_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
"version": "6.4.5",
@@ -191,6 +198,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.5",
+ "images": {
+ "hda_disk_image": "FAZ_VM64_KVM-v7.0.5-build0365-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.5",
"images": {
diff --git a/gns3server/appliances/fortigate.gns3a b/gns3server/appliances/fortigate.gns3a
index 73a6e997..9e48f232 100644
--- a/gns3server/appliances/fortigate.gns3a
+++ b/gns3server/appliances/fortigate.gns3a
@@ -27,6 +27,13 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "FGT_VM64_KVM-v7.2.3.F-build1262-FORTINET.out.kvm.qcow2",
+ "version": "7.2.3",
+ "md5sum": "e8f3c5879f0d6fe238dc2665a3508694",
+ "filesize": 87490560,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v7.2.1.F-build1254-FORTINET.out.kvm.qcow2",
"version": "7.2.1",
@@ -34,6 +41,20 @@
"filesize": 86704128,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FGT_VM64_KVM-v7.0.9.M-build0444-FORTINET.out.kvm.qcow2",
+ "version": "7.0.9",
+ "md5sum": "0aee912ab11bf9a4b0e3fc1a62dd0e40",
+ "filesize": 77135872,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
+ {
+ "filename": "FGT_VM64_KVM-v6.M-build2030-FORTINET.out.kvm.qcow2",
+ "version": "6.4.11",
+ "md5sum": "bcd7491ddfa31fec4f618b73792456e4",
+ "filesize": 69861376,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FGT_VM64_KVM-v6-build1828-FORTINET.out.kvm.qcow2",
"version": "6.4.5",
@@ -261,6 +282,13 @@
}
],
"versions": [
+ {
+ "name": "7.2.3",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v7.2.3.F-build1262-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "7.2.1",
"images": {
@@ -268,6 +296,20 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.9",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v7.0.9.M-build0444-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
+ {
+ "name": "6.4.11",
+ "images": {
+ "hda_disk_image": "FGT_VM64_KVM-v6.M-build2030-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.5",
"images": {
diff --git a/gns3server/appliances/fortimanager.gns3a b/gns3server/appliances/fortimanager.gns3a
index 2f920388..faf00d78 100644
--- a/gns3server/appliances/fortimanager.gns3a
+++ b/gns3server/appliances/fortimanager.gns3a
@@ -34,6 +34,13 @@
"filesize": 242814976,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
+ {
+ "filename": "FMG_VM64_KVM-v7.0.5-build0365-FORTINET.out.kvm.qcow2",
+ "version": "7.0.5",
+ "md5sum": "e8b9c992784cea766b52a427a5fe0279",
+ "filesize": 237535232,
+ "download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
+ },
{
"filename": "FMG_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
"version": "6.4.5",
@@ -191,6 +198,13 @@
"hdb_disk_image": "empty30G.qcow2"
}
},
+ {
+ "name": "7.0.5",
+ "images": {
+ "hda_disk_image": "FMG_VM64_KVM-v7.0.5-build0365-FORTINET.out.kvm.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
{
"name": "6.4.5",
"images": {
diff --git a/gns3server/appliances/frr.gns3a b/gns3server/appliances/frr.gns3a
index a909c7a6..fc2ab658 100644
--- a/gns3server/appliances/frr.gns3a
+++ b/gns3server/appliances/frr.gns3a
@@ -22,6 +22,14 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "frr-8.2.2.qcow2",
+ "version": "8.2.2",
+ "md5sum": "45cda6b991a1b9e8205a3a0ecc953640",
+ "filesize": 56609280,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
+ "direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/frr-8.2.2.qcow2"
+ },
{
"filename": "frr-8.1.0.qcow2",
"version": "8.1.0",
@@ -48,6 +56,12 @@
}
],
"versions": [
+ {
+ "name": "8.2.2",
+ "images": {
+ "hda_disk_image": "frr-8.2.2.qcow2"
+ }
+ },
{
"name": "8.1.0",
"images": {
diff --git a/gns3server/appliances/ntopng.gns3a b/gns3server/appliances/ntopng.gns3a
index 2f33c3ef..8804d7cb 100644
--- a/gns3server/appliances/ntopng.gns3a
+++ b/gns3server/appliances/ntopng.gns3a
@@ -14,7 +14,7 @@
"usage": "In the web interface login as admin/admin\n\nPersistent configuration:\n- Add \"/var/lib/redis\" as an additional persistent directory.\n- Use \"redis-cli save\" in an auxiliary console to save the configuration.",
"docker": {
"adapters": 1,
- "image": "ntop/ntopng:stable",
+ "image": "ntop/ntopng:latest",
"start_command": "--dns-mode 2 --interface eth0",
"console_type": "http",
"console_http_port": 3000,
diff --git a/gns3server/appliances/openwrt.gns3a b/gns3server/appliances/openwrt.gns3a
index 2a293c62..a8bc8bf7 100644
--- a/gns3server/appliances/openwrt.gns3a
+++ b/gns3server/appliances/openwrt.gns3a
@@ -23,6 +23,24 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "openwrt-22.03.0-x86-64-generic-ext4-combined.img",
+ "version": "22.03.0",
+ "md5sum": "0f9a266bd8a6cdfcaf0b59f7ba103a0e",
+ "filesize": 126353408,
+ "download_url": "https://downloads.openwrt.org/releases/22.03.0/targets/x86/64/",
+ "direct_download_url": "https://downloads.openwrt.org/releases/22.03.0/targets/x86/64/openwrt-22.03.0-x86-64-generic-ext4-combined.img.gz",
+ "compression": "gzip"
+ },
+ {
+ "filename": "openwrt-21.02.3-x86-64-generic-ext4-combined.img",
+ "version": "21.02.3",
+ "md5sum": "652c432e758420cb8d749139e8bef14b",
+ "filesize": 126353408,
+ "download_url": "https://downloads.openwrt.org/releases/21.02.3/targets/x86/64/",
+ "direct_download_url": "https://downloads.openwrt.org/releases/21.02.3/targets/x86/64/openwrt-21.02.3-x86-64-generic-ext4-combined.img.gz",
+ "compression": "gzip"
+ },
{
"filename": "openwrt-21.02.1-x86-64-generic-ext4-combined.img",
"version": "21.02.1",
@@ -196,6 +214,18 @@
}
],
"versions": [
+ {
+ "name": "22.03.0",
+ "images": {
+ "hda_disk_image": "openwrt-22.03.0-x86-64-generic-ext4-combined.img"
+ }
+ },
+ {
+ "name": "21.02.3",
+ "images": {
+ "hda_disk_image": "openwrt-21.02.3-x86-64-generic-ext4-combined.img"
+ }
+ },
{
"name": "21.02.1",
"images": {
diff --git a/gns3server/appliances/reactos.gns3a b/gns3server/appliances/reactos.gns3a
new file mode 100644
index 00000000..391abe95
--- /dev/null
+++ b/gns3server/appliances/reactos.gns3a
@@ -0,0 +1,67 @@
+{
+ "appliance_id": "c811e588-39ef-41e9-9f60-6e8e08618c3d",
+ "name": "ReactOS",
+ "category": "guest",
+ "description": "Imagine running your favorite Windows applications and drivers in an open-source environment you can trust.\nThat's the mission of ReactOS! ",
+ "vendor_name": "ReactOS Project",
+ "vendor_url": "https://reactos.org/",
+ "documentation_url": "https://reactos.org/what-is-reactos/",
+ "product_name": "ReactOS",
+ "product_url": "https://reactos.org/",
+ "registry_version": 3,
+ "status": "stable",
+ "maintainer": "Savio D'souza",
+ "maintainer_email": "savio2002@yahoo.co.in",
+ "usage": "Passwords are set during installation.",
+ "qemu": {
+ "adapter_type": "e1000",
+ "adapters": 1,
+ "ram": 1024,
+ "hda_disk_interface": "ide",
+ "arch": "x86_64",
+ "console_type": "vnc",
+ "kvm": "require"
+ },
+ "images": [
+ {
+ "filename": "ReactOS-0.4.14-release-15-gb6088a6.iso",
+ "version": "Installer-0.4.14-release-15",
+ "md5sum": "af4be6b27463446905f155f14232d2b4",
+ "filesize": 140509184,
+ "download_url": "https://reactos.org/download",
+ "direct_download_url": "https://sourceforge.net/projects/reactos/files/ReactOS/0.4.14/ReactOS-0.4.14-release-21-g1302c1b-iso.zip/download"
+ },
+ {
+ "filename": "ReactOS-0.4.14-release-15-gb6088a6-Live.iso",
+ "version": "Live-0.4.14-release-15",
+ "md5sum": "73c1a0169a9a3b8a4feb91f4d00f5e97",
+ "filesize": 267386880,
+ "download_url": "https://reactos.org/download",
+ "direct_download_url": "https://sourceforge.net/projects/reactos/files/ReactOS/0.4.14/ReactOS-0.4.14-release-21-g1302c1b-live.zip/download"
+ },
+ {
+ "filename": "empty30G.qcow2",
+ "version": "1.0",
+ "md5sum": "3411a599e822f2ac6be560a26405821a",
+ "filesize": 197120,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%30disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty30G.qcow2/download"
+ }
+ ],
+ "versions": [
+ {
+ "name": "Installer-0.4.14-release-15",
+ "images": {
+ "hda_disk_image": "empty30G.qcow2",
+ "cdrom_image": "ReactOS-0.4.14-release-15-gb6088a6.iso"
+ }
+ },
+ {
+ "name": "Live-0.4.14-release-15",
+ "images": {
+ "hda_disk_image": "empty30G.qcow2",
+ "cdrom_image": "ReactOS-0.4.14-release-15-gb6088a6-Live.iso"
+ }
+ }
+ ]
+}
diff --git a/gns3server/appliances/vyos.gns3a b/gns3server/appliances/vyos.gns3a
index 4004a1bc..7fbe68ca 100644
--- a/gns3server/appliances/vyos.gns3a
+++ b/gns3server/appliances/vyos.gns3a
@@ -2,7 +2,7 @@
"appliance_id": "f82b74c4-0f30-456f-a582-63daca528502",
"name": "VyOS",
"category": "router",
- "description": "VyOS is a community fork of Vyatta, a Linux-based network operating system that provides software-based network routing, firewall, and VPN functionality. VyOS has a subscription LTS version and a community rolling release. The latest version in this appliance is the monthly snapshot of the rolling release track.",
+ "description": "VyOS is a community fork of Vyatta, a Linux-based network operating system that provides software-based network routing, firewall, and VPN functionality.",
"vendor_name": "Linux",
"vendor_url": "https://vyos.net/",
"documentation_url": "https://docs.vyos.io/",
@@ -26,6 +26,20 @@
"kvm": "allow"
},
"images": [
+ {
+ "filename": "vyos-1.3.2-amd64.iso",
+ "version": "1.3.2",
+ "md5sum": "070743faac800f9e5197058a8b6b3ba1",
+ "filesize": 334495744,
+ "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-2-generic-iso-image"
+ },
+ {
+ "filename": "vyos-1.3.1-S1-amd64.iso",
+ "version": "1.3.1-S1",
+ "md5sum": "781f345e8a4ab9eb9e075ce5c87c8817",
+ "filesize": 351272960,
+ "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-1-s1-generic-iso-image"
+ },
{
"filename": "vyos-1.3.1-amd64.iso",
"version": "1.3.1",
@@ -41,12 +55,11 @@
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-0-generic-iso-image"
},
{
- "filename": "vyos-1.3.0-epa3-amd64.iso",
- "version": "1.3.0-epa3",
- "md5sum": "1b5de684d8995844e35fa5cec3171811",
- "filesize": 331350016,
- "download_url": "https://vyos.net/get/snapshots/",
- "direct_download_url": "https://s3.amazonaws.com/s3-us.vyos.io/snapshot/vyos-1.3.0-epa3/vyos-1.3.0-epa3-amd64.iso"
+ "filename": "vyos-1.2.9-amd64.iso",
+ "version": "1.2.9",
+ "md5sum": "586be23b6256173e174c82d8f1f699a1",
+ "filesize": 430964736,
+ "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-generic-iso-image"
},
{
"filename": "vyos-1.2.8-amd64.iso",
@@ -80,6 +93,20 @@
}
],
"versions": [
+ {
+ "name": "1.3.2",
+ "images": {
+ "hda_disk_image": "empty8G.qcow2",
+ "cdrom_image": "vyos-1.3.2-amd64.iso"
+ }
+ },
+ {
+ "name": "1.3.1-S1",
+ "images": {
+ "hda_disk_image": "empty8G.qcow2",
+ "cdrom_image": "vyos-1.3.1-S1-amd64.iso"
+ }
+ },
{
"name": "1.3.1",
"images": {
@@ -95,10 +122,10 @@
}
},
{
- "name": "1.3.0-epa3",
+ "name": "1.2.9",
"images": {
"hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.3.0-epa3-amd64.iso"
+ "cdrom_image": "vyos-1.2.9-amd64.iso"
}
},
{
diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py
index fcc31893..1ae0bb57 100644
--- a/gns3server/compute/base_node.py
+++ b/gns3server/compute/base_node.py
@@ -638,8 +638,12 @@ class BaseNode:
# no need to allocate a port when the console type is none
self._console = None
elif console_type == "vnc":
- # VNC is a special case and the range must be 5900-6000
- self._console = self._manager.port_manager.get_free_tcp_port(self._project, 5900, 6000)
+ vnc_console_start_port_range, vnc_console_end_port_range = self._get_vnc_console_port_range()
+ self._console = self._manager.port_manager.get_free_tcp_port(
+ self._project,
+ vnc_console_start_port_range,
+ vnc_console_end_port_range
+ )
else:
self._console = self._manager.port_manager.get_free_tcp_port(self._project)
diff --git a/gns3server/compute/dynamips/nodes/ethernet_switch.py b/gns3server/compute/dynamips/nodes/ethernet_switch.py
index 1ca67f79..01632214 100644
--- a/gns3server/compute/dynamips/nodes/ethernet_switch.py
+++ b/gns3server/compute/dynamips/nodes/ethernet_switch.py
@@ -166,7 +166,7 @@ class EthernetSwitch(Device):
"""
if ports != self._ports:
if len(self._nios) > 0 and len(ports) != len(self._ports):
- raise NodeError("Can't modify a switch already connected.")
+ raise NodeError("Cannot change ports on a switch that is already connected.")
port_number = 0
for port in ports:
@@ -356,7 +356,7 @@ class EthernetSwitch(Device):
elif settings["type"] == "dot1q":
await self.set_dot1q_port(port_number, settings["vlan"])
elif settings["type"] == "qinq":
- await self.set_qinq_port(port_number, settings["vlan"], settings.get("ethertype"))
+ await self.set_qinq_port(port_number, settings["vlan"], settings.get("ethertype", "0x8100"))
async def set_access_port(self, port_number, vlan_id):
"""
@@ -427,7 +427,7 @@ class EthernetSwitch(Device):
await self._hypervisor.send(
'ethsw set_qinq_port "{name}" {nio} {outer_vlan} {ethertype}'.format(
name=self._name, nio=nio, outer_vlan=outer_vlan, ethertype=ethertype if ethertype != "0x8100" else ""
- )
+ ).strip()
)
log.info(
diff --git a/gns3server/compute/vmware/__init__.py b/gns3server/compute/vmware/__init__.py
index 606e8a03..f19e3661 100644
--- a/gns3server/compute/vmware/__init__.py
+++ b/gns3server/compute/vmware/__init__.py
@@ -306,6 +306,8 @@ class VMware(BaseManager):
def refresh_vmnet_list(self, ubridge=True):
+ log.debug("Refreshing VMnet list with uBridge={}".format(ubridge))
+
if ubridge:
# VMnet host adapters must be present when uBridge is used
vmnet_interfaces = self._get_vmnet_interfaces_ubridge()
@@ -314,6 +316,7 @@ class VMware(BaseManager):
self._vmnets_info = vmnet_interfaces.copy()
vmnet_interfaces = list(vmnet_interfaces.keys())
+ log.debug("Found {} VMnet interfaces".format(len(vmnet_interfaces)))
# remove vmnets already in use
for vmware_vm in self._nodes.values():
for used_vmnet in vmware_vm.vmnets:
@@ -324,6 +327,7 @@ class VMware(BaseManager):
# remove vmnets that are not managed
for vmnet in vmnet_interfaces.copy():
if vmnet in vmnet_interfaces and self.is_managed_vmnet(vmnet) is False:
+ log.debug("{} is not managed by GNS3".format(vmnet))
vmnet_interfaces.remove(vmnet)
self._vmnets = vmnet_interfaces
diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py
index 1c7c3861..62a03aa9 100644
--- a/gns3server/compute/vmware/vmware_vm.py
+++ b/gns3server/compute/vmware/vmware_vm.py
@@ -481,6 +481,10 @@ class VMwareVM(BaseNode):
try:
if self._ubridge_hypervisor:
+ if parse_version(platform.mac_ver()[0]) >= parse_version("11.0.0"):
+ # give VMware some time to create the bridge interfaces, so they can be found
+ # by psutil and used by uBridge
+ await asyncio.sleep(1)
for adapter_number in range(0, self._adapters):
nio = self._ethernet_adapters[adapter_number].get_nio(0)
if nio:
diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py
index cb5d874e..21c05b9d 100644
--- a/gns3server/controller/__init__.py
+++ b/gns3server/controller/__init__.py
@@ -22,6 +22,7 @@ import socket
import shutil
import asyncio
import random
+import importlib_resources
from ..config import Config
from .project import Project
@@ -32,7 +33,6 @@ from .notification import Notification
from .symbols import Symbols
from .topology import load_topology
from .gns3vm import GNS3VM
-from ..utils.get_resource import get_resource
from .gns3vm.gns3_vm_error import GNS3VMError
from .controller_error import ControllerError, ControllerNotFoundError
@@ -62,9 +62,10 @@ class Controller:
async def start(self, computes=None):
log.info("Controller is starting")
- self.load_base_files()
+ self._load_base_files()
server_config = Config.instance().settings.Server
Config.instance().listen_for_config_changes(self._update_config)
+ name = server_config.name
host = server_config.host
port = server_config.port
@@ -86,7 +87,7 @@ class Controller:
try:
self._local_server = await self.add_compute(
compute_id="local",
- name=f"{socket.gethostname()} (controller)",
+ name=name,
protocol=protocol,
host=host,
console_host=console_host,
@@ -281,6 +282,7 @@ class Controller:
except OSError as e:
log.error(f"Cannot read Etag appliance file '{etag_appliances_path}': {e}")
+ self._appliance_manager.install_builtin_appliances()
self._appliance_manager.load_appliances()
self._config_loaded = True
@@ -305,20 +307,27 @@ class Controller:
except OSError as e:
log.error(str(e))
- def load_base_files(self):
+ def _load_base_files(self):
"""
At startup we copy base file to the user location to allow
them to customize it
"""
dst_path = self.configs_path()
- src_path = get_resource("configs")
try:
- for file in os.listdir(src_path):
- if not os.path.exists(os.path.join(dst_path, file)):
- shutil.copy(os.path.join(src_path, file), os.path.join(dst_path, file))
- except OSError:
- pass
+ if hasattr(sys, "frozen") and sys.platform.startswith("win"):
+ resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "configs"))
+ for filename in os.listdir(resource_path):
+ if not os.path.exists(os.path.join(dst_path, filename)):
+ shutil.copy(os.path.join(resource_path, filename), os.path.join(dst_path, filename))
+ else:
+ for entry in importlib_resources.files('gns3server.configs').iterdir():
+ full_path = os.path.join(dst_path, entry.name)
+ if entry.is_file() and not os.path.exists(full_path):
+ log.debug(f"Installing base config file {entry.name} to {full_path}")
+ shutil.copy(str(entry), os.path.join(dst_path, entry.name))
+ except OSError as e:
+ log.error(f"Could not install base config files to {dst_path}: {e}")
def images_path(self):
"""
diff --git a/gns3server/controller/appliance_manager.py b/gns3server/controller/appliance_manager.py
index 85533106..68554942 100644
--- a/gns3server/controller/appliance_manager.py
+++ b/gns3server/controller/appliance_manager.py
@@ -15,10 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import sys
import os
import json
import asyncio
import aiofiles
+import importlib_resources
+import shutil
from typing import Tuple, List
from aiohttp.client_exceptions import ClientError
@@ -29,7 +32,6 @@ from pydantic import ValidationError
from .appliance import Appliance
from ..config import Config
from ..utils.asyncio import locking
-from ..utils.get_resource import get_resource
from ..utils.http_client import HTTPClient
from .controller_error import ControllerBadRequestError, ControllerNotFoundError, ControllerError
from .appliance_to_template import ApplianceToTemplate
@@ -82,9 +84,9 @@ class ApplianceManager:
return self._appliances
- def appliances_path(self) -> str:
+ def _custom_appliances_path(self) -> str:
"""
- Get the image storage directory
+ Get the custom appliance storage directory
"""
server_config = Config.instance().settings.Server
@@ -92,6 +94,37 @@ class ApplianceManager:
os.makedirs(appliances_path, exist_ok=True)
return appliances_path
+ def _builtin_appliances_path(self):
+ """
+ Get the built-in appliance storage directory
+ """
+
+ config = Config.instance()
+ appliances_dir = os.path.join(config.config_dir, "appliances")
+ os.makedirs(appliances_dir, exist_ok=True)
+ return appliances_dir
+
+ def install_builtin_appliances(self):
+ """
+ At startup we copy the built-in appliances files.
+ """
+
+ dst_path = self._builtin_appliances_path()
+ try:
+ if hasattr(sys, "frozen") and sys.platform.startswith("win"):
+ resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "appliances"))
+ for filename in os.listdir(resource_path):
+ if not os.path.exists(os.path.join(dst_path, filename)):
+ shutil.copy(os.path.join(resource_path, filename), os.path.join(dst_path, filename))
+ else:
+ for entry in importlib_resources.files('gns3server.appliances').iterdir():
+ full_path = os.path.join(dst_path, entry.name)
+ if entry.is_file() and not os.path.exists(full_path):
+ log.debug(f"Installing built-in appliance file {entry.name} to {full_path}")
+ shutil.copy(str(entry), os.path.join(dst_path, entry.name))
+ except OSError as e:
+ log.error(f"Could not install built-in appliance files to {dst_path}: {e}")
+
def _find_appliances_from_image_checksum(self, image_checksum: str) -> List[Tuple[Appliance, str]]:
"""
Find appliances that matches an image checksum.
@@ -289,11 +322,11 @@ class ApplianceManager:
self._appliances = {}
for directory, builtin in (
(
- get_resource("appliances"),
+ self._builtin_appliances_path(),
True,
),
(
- self.appliances_path(),
+ self._custom_appliances_path(),
False,
),
):
@@ -407,7 +440,7 @@ class ApplianceManager:
Controller.instance().save()
json_data = await response.json()
- appliances_dir = get_resource("appliances")
+ appliances_dir = self._builtin_appliances_path()
downloaded_appliance_files = []
for appliance in json_data:
if appliance["type"] == "file":
diff --git a/gns3server/core/tasks.py b/gns3server/core/tasks.py
index 6d363baa..24de5940 100644
--- a/gns3server/core/tasks.py
+++ b/gns3server/core/tasks.py
@@ -21,6 +21,7 @@ from typing import Callable
from fastapi import FastAPI
from gns3server.controller import Controller
+from gns3server.config import Config
from gns3server.compute import MODULES
from gns3server.compute.port_manager import PortManager
from gns3server.utils.http_client import HTTPClient
@@ -60,9 +61,10 @@ def create_startup_handler(app: FastAPI) -> Callable:
# computing with server start
from gns3server.compute.qemu import Qemu
- # Start the discovering new images on file system 5 seconds after the server has started
- # to give it a chance to process API requests
- loop.call_later(5, asyncio.create_task, discover_images_on_filesystem(app))
+ if Config.instance().settings.Server.auto_discover_images is True:
+ # Start the discovering new images on file system 5 seconds after the server has started
+ # to give it a chance to process API requests
+ loop.call_later(5, asyncio.create_task, discover_images_on_filesystem(app))
for module in MODULES:
log.debug(f"Loading module {module.__name__}")
diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py
index 53dc218e..cde05b5b 100644
--- a/gns3server/crash_report.py
+++ b/gns3server/crash_report.py
@@ -59,7 +59,7 @@ class CrashReport:
Report crash to a third party service
"""
- DSN = "https://bad2bd9ef9f14c8d9239d6f815ed453f@o19455.ingest.sentry.io/38482"
+ DSN = "https://45f39fa6ea64493b8966a263049e844c@o19455.ingest.sentry.io/38482"
_instance = None
def __init__(self):
@@ -95,8 +95,9 @@ class CrashReport:
"os:name": platform.system(),
"os:release": platform.release(),
"os:win_32": " ".join(platform.win32_ver()),
- "os:mac": f"{platform.mac_ver()[0]} {platform.mac_ver()[2]}",
- "os:linux": " ".join(distro.linux_distribution()),
+ "os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
+ "os:linux": distro.name(pretty=True),
+
}
with sentry_sdk.configure_scope() as scope:
diff --git a/gns3server/db/tasks.py b/gns3server/db/tasks.py
index a31210a9..3b270b0d 100644
--- a/gns3server/db/tasks.py
+++ b/gns3server/db/tasks.py
@@ -30,7 +30,7 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from gns3server.db.repositories.computes import ComputesRepository
from gns3server.db.repositories.images import ImagesRepository
-from gns3server.utils.images import discover_images, check_valid_image_header, read_image_info, InvalidImageError
+from gns3server.utils.images import discover_images, check_valid_image_header, read_image_info, default_images_directory, InvalidImageError
from gns3server import schemas
from .models import Base
@@ -117,12 +117,16 @@ def image_filter(change: Change, path: str) -> bool:
async def monitor_images_on_filesystem(app: FastAPI):
- server_config = Config.instance().settings.Server
- images_dir = os.path.expanduser(server_config.images_path)
+ directories_to_monitor = []
+ for image_type in ("qemu", "ios", "iou"):
+ image_dir = default_images_directory(image_type)
+ if os.path.isdir(image_dir):
+ log.debug(f"Monitoring for new images in '{image_dir}'")
+ directories_to_monitor.append(image_dir)
try:
async for changes in awatch(
- images_dir,
+ *directories_to_monitor,
watch_filter=image_filter,
raise_interrupt=True
):
diff --git a/gns3server/schemas/compute/dynamips_nodes.py b/gns3server/schemas/compute/dynamips_nodes.py
index 585973fd..2ef6c86c 100644
--- a/gns3server/schemas/compute/dynamips_nodes.py
+++ b/gns3server/schemas/compute/dynamips_nodes.py
@@ -78,6 +78,7 @@ class DynamipsWics(str, Enum):
wic_1enet = "WIC-1ENET"
wic_1t = "WIC-1T"
wic_2t = "WIC-2T"
+ _ = ""
class DynamipsConsoleType(str, Enum):
@@ -113,7 +114,7 @@ class DynamipsMidplane(str, Enum):
vxr = "vxr"
-# TODO: improve schema for Dynamips (match platform specific options, e.g. NPE allowd only for c7200)
+# TODO: improve schema for Dynamips (match platform specific options, e.g. NPE allowed only for c7200)
class DynamipsBase(BaseModel):
"""
Common Dynamips node properties.
diff --git a/gns3server/schemas/compute/ethernet_switch_nodes.py b/gns3server/schemas/compute/ethernet_switch_nodes.py
index db0e8cd3..5265aa4c 100644
--- a/gns3server/schemas/compute/ethernet_switch_nodes.py
+++ b/gns3server/schemas/compute/ethernet_switch_nodes.py
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from pydantic import BaseModel, Field
+from pydantic import BaseModel, Field, validator
from typing import Optional, List
from uuid import UUID
from enum import Enum
@@ -42,9 +42,17 @@ class EthernetSwitchPort(BaseModel):
name: str
port_number: int
type: EthernetSwitchPortType = Field(..., description="Port type")
- vlan: Optional[int] = Field(None, ge=1, description="VLAN number")
+ vlan: int = Field(..., ge=1, le=4094, description="VLAN number")
ethertype: Optional[EthernetSwitchEtherType] = Field(None, description="QinQ Ethertype")
+ @validator("ethertype")
+ def validate_ethertype(cls, v, values):
+
+ if v is not None:
+ if "type" not in values or values["type"] != EthernetSwitchPortType.qinq:
+ raise ValueError("Ethertype is only for QinQ port type")
+ return v
+
class TelnetConsoleType(str, Enum):
"""
diff --git a/gns3server/schemas/config.py b/gns3server/schemas/config.py
index d2efdcc0..99202c48 100644
--- a/gns3server/schemas/config.py
+++ b/gns3server/schemas/config.py
@@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import socket
+
from enum import Enum
from pydantic import BaseModel, Field, SecretStr, FilePath, DirectoryPath, validator
from typing import List
@@ -123,6 +125,7 @@ class BuiltinSymbolTheme(str, Enum):
class ServerSettings(BaseModel):
local: bool = False
+ name: str = f"{socket.gethostname()} (controller)"
protocol: ServerProtocol = ServerProtocol.http
host: str = "0.0.0.0"
port: int = Field(3080, gt=0, le=65535)
@@ -136,6 +139,8 @@ class ServerSettings(BaseModel):
symbols_path: str = "~/GNS3/symbols"
configs_path: str = "~/GNS3/configs"
default_symbol_theme: BuiltinSymbolTheme = BuiltinSymbolTheme.affinity_square_blue
+ allow_raw_images: bool = True
+ auto_discover_images: bool = True
report_errors: bool = True
additional_images_paths: List[str] = Field(default_factory=list)
console_start_port_range: int = Field(5000, gt=0, le=65535)
diff --git a/gns3server/static/web-ui/index.html b/gns3server/static/web-ui/index.html
index 70846673..98fc94b4 100644
--- a/gns3server/static/web-ui/index.html
+++ b/gns3server/static/web-ui/index.html
@@ -46,6 +46,6 @@
gtag('config', 'G-5D6FZL9923');
-
+