Fix port allocation Groundhog Day loop, expand pool to 128 ports
Some checks are pending
Build Ghidra Plugin / build (push) Waiting to run
Some checks are pending
Build Ghidra Plugin / build (push) Waiting to run
Port allocator would spin on the same Docker-occupied ports because releasing a flock and re-calling allocate() restarts from port 8192. Now holds flocks on occupied ports during the scan so allocate() advances past them. Also expands default pool from 32 to 128 ports (8192-8319), and makes range configurable via MCGHIDRA_PORT_START/MCGHIDRA_PORT_END environment variables.
This commit is contained in:
parent
112c1969c8
commit
f4cf1cef9e
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mcghidra"
|
||||
version = "2026.2.11"
|
||||
version = "2026.3.2"
|
||||
description = "AI-assisted reverse engineering bridge: a multi-instance Ghidra plugin exposed via a HATEOAS REST API plus an MCP Python bridge for decompilation, analysis & binary manipulation"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@ -24,10 +24,10 @@ from fastmcp.contrib.mcp_mixin import mcp_tool
|
||||
from mcghidra.core.logging import logger
|
||||
from mcghidra.mixins.base import MCGhidraMixinBase
|
||||
|
||||
# Port pool configuration (32 ports should handle many concurrent sessions)
|
||||
PORT_POOL_START = 8192
|
||||
PORT_POOL_END = 8223
|
||||
PORT_LOCK_DIR = Path("/tmp/mcghidra-ports")
|
||||
# Port pool configuration — 128 ports by default, configurable via env vars
|
||||
PORT_POOL_START = int(os.environ.get("MCGHIDRA_PORT_START", "8192"))
|
||||
PORT_POOL_END = int(os.environ.get("MCGHIDRA_PORT_END", "8319"))
|
||||
PORT_LOCK_DIR = Path(os.environ.get("MCGHIDRA_PORT_LOCK_DIR", "/tmp/mcghidra-ports"))
|
||||
|
||||
|
||||
class PortPool:
|
||||
@ -216,7 +216,7 @@ class DockerMixin(MCGhidraMixinBase):
|
||||
with the MCGhidra plugin pre-installed.
|
||||
|
||||
Supports multi-process environments with:
|
||||
- Dynamic port allocation from a pool (8192-8223)
|
||||
- Dynamic port allocation from a pool ({PORT_POOL_START}-{PORT_POOL_END})
|
||||
- Session-scoped container naming with UUIDs
|
||||
- Docker label-based tracking for cross-process visibility
|
||||
- Automatic cleanup of orphaned containers
|
||||
@ -561,7 +561,7 @@ class DockerMixin(MCGhidraMixinBase):
|
||||
plugin pre-installed. The binary will be imported and analyzed,
|
||||
then the HTTP API will be available.
|
||||
|
||||
Ports are automatically allocated from the pool (8192-8223) to
|
||||
Ports are automatically allocated from the pool ({PORT_POOL_START}-{PORT_POOL_END}) to
|
||||
prevent conflicts between concurrent sessions. Container names
|
||||
are auto-generated with the session ID to ensure uniqueness.
|
||||
|
||||
@ -588,6 +588,7 @@ class DockerMixin(MCGhidraMixinBase):
|
||||
# Clean up invalid characters in container name
|
||||
name = "".join(c if c.isalnum() or c in "-_" else "-" for c in name)
|
||||
|
||||
port = None
|
||||
try:
|
||||
# Check if container with this name already exists
|
||||
check_result = await self._run_docker_cmd(
|
||||
@ -598,33 +599,39 @@ class DockerMixin(MCGhidraMixinBase):
|
||||
"error": f"Container '{name}' already exists. Stop it first with docker_stop."
|
||||
}
|
||||
|
||||
# Allocate a port that's both lockable AND not in use by Docker
|
||||
# This handles external containers (not managed by MCGhidra) using ports in our range
|
||||
port = None
|
||||
ports_tried = []
|
||||
for _ in range(PORT_POOL_END - PORT_POOL_START + 1):
|
||||
candidate_port = self.port_pool.allocate(self.session_id)
|
||||
if candidate_port is None:
|
||||
break # Pool exhausted
|
||||
# Allocate a port that's both lockable AND not in use by Docker.
|
||||
# We HOLD flocks on Docker-occupied ports while searching, so
|
||||
# allocate() advances past them. Release held ports after.
|
||||
held_ports = [] # Ports we locked but can't use (Docker-occupied)
|
||||
try:
|
||||
for _ in range(PORT_POOL_END - PORT_POOL_START + 1):
|
||||
candidate_port = self.port_pool.allocate(self.session_id)
|
||||
if candidate_port is None:
|
||||
break # Pool exhausted
|
||||
|
||||
# Check if this port is already in use by a Docker container
|
||||
port_check = await self._run_docker_cmd(
|
||||
["ps", "-q", "-f", f"publish={candidate_port}"], check=False
|
||||
)
|
||||
if port_check.stdout.strip():
|
||||
# Port is in use by Docker - release and try next
|
||||
ports_tried.append(candidate_port)
|
||||
self.port_pool.release(candidate_port)
|
||||
continue
|
||||
# Check if this port is already in use by a Docker container
|
||||
port_check = await self._run_docker_cmd(
|
||||
["ps", "-q", "-f", f"publish={candidate_port}"], check=False
|
||||
)
|
||||
if port_check.stdout.strip():
|
||||
# Port is Docker-occupied — hold the flock so allocate()
|
||||
# skips it on the next iteration, then release after loop
|
||||
held_ports.append(candidate_port)
|
||||
continue
|
||||
|
||||
# Found a usable port!
|
||||
port = candidate_port
|
||||
break
|
||||
# Found a usable port!
|
||||
port = candidate_port
|
||||
break
|
||||
finally:
|
||||
# Release all the Docker-occupied ports we held during the scan
|
||||
for held in held_ports:
|
||||
self.port_pool.release(held)
|
||||
|
||||
if port is None:
|
||||
return {
|
||||
"error": "Port pool exhausted (8192-8223). All ports are in use by Docker containers.",
|
||||
"ports_checked": ports_tried if ports_tried else "all ports locked by other MCGhidra sessions",
|
||||
"error": f"Port pool exhausted ({PORT_POOL_START}-{PORT_POOL_END}). All ports are in use.",
|
||||
"docker_occupied": held_ports if held_ports else [],
|
||||
"hint": "Stop some containers with docker_stop or docker_cleanup.",
|
||||
"allocated_ports": self.port_pool.get_allocated_ports(),
|
||||
}
|
||||
|
||||
@ -679,7 +686,8 @@ class DockerMixin(MCGhidraMixinBase):
|
||||
}
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.port_pool.release(port)
|
||||
if port is not None:
|
||||
self.port_pool.release(port)
|
||||
return {"error": f"Failed to start container: {e.stderr or e.stdout}"}
|
||||
|
||||
@mcp_tool(
|
||||
@ -945,7 +953,7 @@ class DockerMixin(MCGhidraMixinBase):
|
||||
2. If not, allocates a port from the pool and starts a new container
|
||||
3. Returns connection info immediately
|
||||
|
||||
Ports are auto-allocated from the pool (8192-8223) to prevent
|
||||
Ports are auto-allocated from the pool ({PORT_POOL_START}-{PORT_POOL_END}) to prevent
|
||||
conflicts between concurrent sessions.
|
||||
|
||||
After starting, poll docker_health(port) in a loop to check readiness.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user