mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
Merge pull request #2679 from yueguobin/wireshark-alias
feat(web-wireshark): add container IP retrieval and Docker API version compatibility fixes
This commit is contained in:
commit
98477371d2
@ -103,7 +103,7 @@ source venv/bin/activate
|
||||
# For China mainland users, use mirror:
|
||||
# pip install -e . -i https://mirrors.aliyun.com/pypi/simple/
|
||||
|
||||
pip install -e . && gns3-wireshark-setup
|
||||
pip install -e . && wireshark
|
||||
pip install -e .[ai-copilot]
|
||||
pip install -e .[dev]
|
||||
```
|
||||
|
||||
@ -27,10 +27,10 @@ Before using Web Wireshark, install the GNS3 server and set up the Docker image:
|
||||
|
||||
```bash
|
||||
# Development install
|
||||
pip install -e . && gns3-wireshark-setup
|
||||
pip install -e . && wireshark
|
||||
|
||||
# Production install
|
||||
pip install gns3-server && gns3-wireshark-setup
|
||||
pip install gns3-server && wireshark
|
||||
```
|
||||
|
||||
This command will:
|
||||
@ -38,7 +38,7 @@ This command will:
|
||||
2. Pull the `gns3/web-wireshark:latest` image from Docker Hub
|
||||
3. If pull fails, build the image locally using the included Dockerfile
|
||||
|
||||
The `gns3-wireshark-setup` command shows the raw output from `docker pull` or `docker build`, allowing you to see the full progress.
|
||||
The `wireshark` command shows the raw output from `docker pull` or `docker build`, allowing you to see the full progress.
|
||||
|
||||
---
|
||||
|
||||
@ -552,6 +552,43 @@ Configured via `WebWiresharkSettings` in `gns3server/schemas/config.py`:
|
||||
|
||||
---
|
||||
|
||||
## Docker API Compatibility
|
||||
|
||||
### Version Negotiation Mechanism
|
||||
|
||||
The Web Wireshark feature implements automatic Docker API version negotiation to ensure compatibility across different Docker versions:
|
||||
|
||||
1. **Primary**: Try API version 1.44 first
|
||||
- Supports Docker 29.3+ (API 1.54+), which requires minimum API 1.44
|
||||
|
||||
2. **Fallback**: If server rejects 1.44 with 400 error, downgrade to API 1.40
|
||||
- Supports Docker 20.10 (API 1.41)
|
||||
|
||||
### Tested Configurations
|
||||
|
||||
| Docker Version | API Version | API 1.40 | API 1.44 | Solution |
|
||||
|----------------|-------------|----------|----------|----------|
|
||||
| 20.10 | 1.41 | ✓ | ✗ | Fallback to 1.40 |
|
||||
| 29.3+ | 1.54+ | ✗ | ✓ | Use 1.44 |
|
||||
|
||||
### Container IP Retrieval
|
||||
|
||||
The `get_container_ip()` method implements a dual-strategy approach for retrieving container IP addresses:
|
||||
|
||||
1. **Primary Method**: Query Docker Container API
|
||||
- Uses `docker inspect` via HTTP API
|
||||
- Safe access to `NetworkSettings.Networks` field using `.get()`
|
||||
- Handles missing fields gracefully (fixes KeyError bug)
|
||||
|
||||
2. **Fallback Method**: Execute `hostname -I` command inside container
|
||||
- Works when Docker API response lacks network information
|
||||
- Compatible with containers without `ip` command
|
||||
- Returns first IP address from `hostname -I` output
|
||||
|
||||
This dual approach ensures compatibility across different Docker API versions that may have varying response formats.
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **JWT Token Visibility**: Token passed via command-line arguments (visible in `/proc/<pid>/cmdline`)
|
||||
|
||||
@ -32,7 +32,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Docker API configuration
|
||||
DOCKER_SOCKET = "/var/run/docker.sock"
|
||||
DOCKER_MINIMUM_API_VERSION = "1.44"
|
||||
DOCKER_MINIMUM_API_VERSION = "1.40"
|
||||
DOCKER_PREFERRED_API_VERSION = "1.44"
|
||||
|
||||
|
||||
@ -46,7 +46,9 @@ class DockerHTTPClient:
|
||||
self._connector = None
|
||||
self._session = None
|
||||
self._connected = False
|
||||
self._api_version = DOCKER_MINIMUM_API_VERSION
|
||||
# Start with preferred API version (1.44) to support newer Docker
|
||||
# Will fallback to minimum version (1.40) if server doesn't support it
|
||||
self._api_version = DOCKER_PREFERRED_API_VERSION
|
||||
|
||||
async def _get_connector(self):
|
||||
"""Get or create Unix socket connector."""
|
||||
@ -142,6 +144,14 @@ class DockerHTTPClient:
|
||||
raise RuntimeError(f"Docker API timeout after {self.REQUEST_TIMEOUT}s for {endpoint}")
|
||||
except aiohttp.ClientError as e:
|
||||
raise RuntimeError(f"Docker connection error: {e}") from e
|
||||
except RuntimeError as e:
|
||||
# Retry with lower API version if Docker daemon doesn't support current version
|
||||
error_msg = str(e)
|
||||
if ("400" in error_msg or "not found" in error_msg.lower()) and self._api_version == DOCKER_PREFERRED_API_VERSION:
|
||||
logger.warning(f"Docker daemon doesn't support API version {self._api_version}, falling back to {DOCKER_MINIMUM_API_VERSION}")
|
||||
self._api_version = DOCKER_MINIMUM_API_VERSION
|
||||
return await self._request(method, endpoint, check_connection=False, **kwargs)
|
||||
raise
|
||||
|
||||
async def create_network(self, name: str, driver: str = "bridge", subnet: str = None):
|
||||
"""Create Docker network."""
|
||||
|
||||
@ -526,6 +526,50 @@ class WebWiresharkManager:
|
||||
|
||||
return None
|
||||
|
||||
async def get_container_ip(self, container_name: str, container_id: str = None) -> Optional[str]:
|
||||
"""Get the container IP address in the wireshark network.
|
||||
|
||||
Args:
|
||||
container_name: Container name (e.g., "gns3-wireshark-project_id")
|
||||
container_id: Container ID (optional, only used for fallback method)
|
||||
|
||||
Returns:
|
||||
Container IP address (e.g., 172.31.0.2) or None if not found
|
||||
"""
|
||||
# Method 1: Get from Docker Container API (fastest)
|
||||
try:
|
||||
container = await self.docker.get_container(container_name)
|
||||
if container:
|
||||
networks = container.get("NetworkSettings", {}).get("Networks", {})
|
||||
# Find gns3-wireshark network
|
||||
for network_name, network_config in networks.items():
|
||||
if "wireshark" in network_name.lower():
|
||||
container_ip = network_config.get("IPAddress")
|
||||
if container_ip:
|
||||
logger.info(f"Got container IP from Docker API: {container_ip}")
|
||||
return container_ip
|
||||
except Exception as e:
|
||||
logger.debug(f"Cannot get container IP from Docker API: {e}")
|
||||
|
||||
# Fallback: Execute command inside container to get IP (slower)
|
||||
if container_id:
|
||||
try:
|
||||
# Use 'hostname -I' to get all IP addresses and take the first one
|
||||
# This works on most Linux systems and is more portable than 'ip' command
|
||||
returncode, stdout, stderr = await self._exec_in_container(
|
||||
container_id,
|
||||
"hostname -I 2>/dev/null | awk '{print $1}'"
|
||||
)
|
||||
if returncode == 0 and stdout.strip():
|
||||
container_ip = stdout.strip()
|
||||
logger.info(f"Got container IP from container command: {container_ip}")
|
||||
return container_ip
|
||||
except Exception as e:
|
||||
logger.debug(f"Cannot get container IP from container command: {e}")
|
||||
|
||||
logger.warning(f"Failed to get container IP for {container_name}")
|
||||
return None
|
||||
|
||||
async def _fix_localhost_url(self, url: str, container_id: str) -> str:
|
||||
"""Fix URL with localhost/127.0.0.1 for container access.
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ from gns3server.utils.http_client import HTTPClient
|
||||
from gns3server.utils.port_allocator import link_id_to_port
|
||||
from gns3server.utils.websocket_to_websocket import websocket_proxy
|
||||
from gns3server import schemas
|
||||
from gns3server.agent.web_wireshark.manager import WebWiresharkManager
|
||||
|
||||
from .dependencies.database import get_repository
|
||||
from .dependencies.rbac import has_privilege, has_privilege_on_websocket
|
||||
@ -385,22 +386,8 @@ async def web_wireshark_websocket(
|
||||
xpra_port = link_id_to_port(link_id)
|
||||
|
||||
# Get container IP
|
||||
from gns3server.compute.docker import Docker
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container_info = await docker_manager.query(
|
||||
"GET",
|
||||
f"containers/{container_name}/json"
|
||||
)
|
||||
|
||||
networks = container_info["NetworkSettings"]["Networks"]
|
||||
container_ip = None
|
||||
|
||||
# Find gns3-wireshark network
|
||||
for network_name, network_config in networks.items():
|
||||
if "wireshark" in network_name.lower():
|
||||
container_ip = network_config["IPAddress"]
|
||||
break
|
||||
manager = WebWiresharkManager()
|
||||
container_ip = await manager.get_container_ip(container_name)
|
||||
|
||||
if not container_ip:
|
||||
log.error(f"Container {container_name} not found in wireshark network")
|
||||
|
||||
@ -51,7 +51,7 @@ ai-copilot = {file = ['ai-requirements.txt']}
|
||||
gns3server = "gns3server.main:main"
|
||||
gns3vmnet = "gns3server.utils.vmnet:main"
|
||||
gns3server-uninstall-ai-copilot = "gns3server.utils.uninstall_ai_copilot:main"
|
||||
gns3-wireshark-setup = "gns3server.agent.web_wireshark.setup_wireshark_image:main"
|
||||
wireshark = "gns3server.agent.web_wireshark.setup_wireshark_image:main"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["gns3server"]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user