@@ -962,6 +962,54 @@ def host_cfg_side_effect(**kwargs):
962962 hc2 = mock_client .api .create_host_config .call_args .kwargs
963963 assert hc2 ["sysctls" ]["net.ipv6.conf.all.disable_ipv6" ] == 1
964964
965+
966+ @patch ("opensandbox_server.services.docker.docker" )
967+ def test_egress_sidecar_normalizes_windows_port_bindings (mock_docker ):
968+ mock_client = MagicMock ()
969+ mock_client .containers .list .return_value = []
970+
971+ def host_cfg_side_effect (** kwargs ):
972+ return kwargs
973+
974+ sidecar_container = MagicMock ()
975+ mock_client .api .create_host_config .side_effect = host_cfg_side_effect
976+ mock_client .api .create_container .return_value = {"Id" : "sidecar-id" }
977+ mock_client .containers .get .return_value = sidecar_container
978+ mock_docker .from_env .return_value = mock_client
979+
980+ cfg = _app_config ()
981+ cfg .docker .network_mode = "bridge"
982+ cfg .egress = EgressConfig (image = "egress:latest" , disable_ipv6 = False )
983+ service = DockerSandboxService (config = cfg )
984+
985+ with (
986+ patch .object (service , "_ensure_image_available" ),
987+ patch .object (service , "_docker_operation" ) as mock_op ,
988+ ):
989+ mock_op .return_value .__enter__ .return_value = None
990+ mock_op .return_value .__exit__ .return_value = None
991+ service ._start_egress_sidecar (
992+ "sandbox-id" ,
993+ NetworkPolicy (defaultAction = "deny" , egress = []),
994+ egress_token = "egress-token" ,
995+ host_execd_port = 44772 ,
996+ host_http_port = 8080 ,
997+ extra_port_bindings = {
998+ "3389/tcp" : ("0.0.0.0" , 53389 ),
999+ "3389/udp" : ("0.0.0.0" , 53390 ),
1000+ "8006/tcp" : ("0.0.0.0" , 58006 ),
1001+ },
1002+ )
1003+
1004+ hc_kwargs = mock_client .api .create_host_config .call_args .kwargs
1005+ assert "3389" in hc_kwargs ["port_bindings" ]
1006+ assert "3389/udp" in hc_kwargs ["port_bindings" ]
1007+ assert "8006" in hc_kwargs ["port_bindings" ]
1008+ sidecar_kwargs = mock_client .api .create_container .call_args .kwargs
1009+ assert "3389" in sidecar_kwargs ["ports" ]
1010+ assert "3389/udp" in sidecar_kwargs ["ports" ]
1011+ assert "8006" in sidecar_kwargs ["ports" ]
1012+
9651013def test_expire_cleans_sidecar ():
9661014 service = DockerSandboxService (config = _app_config ())
9671015 mock_container = MagicMock ()
@@ -1327,9 +1375,9 @@ async def test_create_sandbox_windows_profile_injects_runtime_defaults(mock_dock
13271375 port_bindings = host_config_kwargs ["port_bindings" ]
13281376 assert "44772" in port_bindings
13291377 assert "8080" in port_bindings
1330- assert "3389/tcp " in port_bindings
1378+ assert "3389" in port_bindings
13311379 assert "3389/udp" in port_bindings
1332- assert "8006/tcp " in port_bindings
1380+ assert "8006" in port_bindings
13331381
13341382
13351383@pytest .mark .asyncio
@@ -1496,6 +1544,86 @@ async def test_create_sandbox_windows_profile_accepts_dockur_demo_like_request(m
14961544 assert response .platform .os == "windows"
14971545 assert response .platform .arch == "amd64"
14981546
1547+
1548+ @pytest .mark .asyncio
1549+ @patch ("opensandbox_server.services.docker.docker" )
1550+ async def test_create_sandbox_windows_profile_with_network_policy_maps_windows_ports (mock_docker ):
1551+ mock_client = MagicMock ()
1552+ mock_client .containers .list .return_value = []
1553+ mock_docker .from_env .return_value = mock_client
1554+
1555+ cfg = _app_config ()
1556+ cfg .runtime .execd_image = "ghcr.io/opensandbox/execd:v1.0.11"
1557+ cfg .docker .network_mode = "bridge"
1558+ cfg .egress = EgressConfig (image = "opensandbox/egress:latest" )
1559+ service = DockerSandboxService (config = cfg )
1560+ request = CreateSandboxRequest (
1561+ image = ImageSpec (uri = "dockurr/windows:latest" ),
1562+ resourceLimits = ResourceLimits (
1563+ root = {
1564+ "cpu" : "4" ,
1565+ "memory" : "8G" ,
1566+ "disk" : "64G" ,
1567+ }
1568+ ),
1569+ env = {"VERSION" : "11" },
1570+ entrypoint = ["cmd" , "/c" , "echo ready" ],
1571+ platform = PlatformSpec (os = "windows" , arch = "amd64" ),
1572+ networkPolicy = NetworkPolicy (default_action = "deny" , egress = []),
1573+ )
1574+ created_container = MagicMock ()
1575+ created_container .image .attrs = {"Os" : "windows" , "Architecture" : "amd64" }
1576+ sidecar = MagicMock ()
1577+ sidecar .id = "sidecar-123"
1578+
1579+ with (
1580+ patch (
1581+ "opensandbox_server.services.docker.validate_windows_runtime_prerequisites" ,
1582+ return_value = None ,
1583+ ),
1584+ patch ("opensandbox_server.services.docker.generate_egress_token" , return_value = "egress-token" ),
1585+ patch (
1586+ "opensandbox_server.services.docker.allocate_port_bindings" ,
1587+ return_value = {
1588+ "44772" : ("0.0.0.0" , 51664 ),
1589+ "8080" : ("0.0.0.0" , 48891 ),
1590+ "3389/tcp" : ("0.0.0.0" , 53389 ),
1591+ "3389/udp" : ("0.0.0.0" , 53390 ),
1592+ "8006/tcp" : ("0.0.0.0" , 58006 ),
1593+ },
1594+ ),
1595+ patch .object (service , "_start_egress_sidecar" , return_value = sidecar ) as mock_start_sidecar ,
1596+ patch .object (
1597+ service ,
1598+ "_create_and_start_container" ,
1599+ return_value = created_container ,
1600+ ) as mock_create ,
1601+ ):
1602+ await service .create_sandbox (request )
1603+
1604+ _ , start_kwargs = mock_start_sidecar .call_args
1605+ assert start_kwargs ["host_execd_port" ] == 51664
1606+ assert start_kwargs ["host_http_port" ] == 48891
1607+ assert start_kwargs ["extra_port_bindings" ] == {
1608+ "3389/tcp" : ("0.0.0.0" , 53389 ),
1609+ "3389/udp" : ("0.0.0.0" , 53390 ),
1610+ "8006/tcp" : ("0.0.0.0" , 58006 ),
1611+ }
1612+
1613+ forwarded_env = mock_create .call_args .args [4 ]
1614+ host_config_kwargs = mock_create .call_args .args [5 ]
1615+ forwarded_ports = mock_create .call_args .args [6 ]
1616+ labels = mock_create .call_args .args [3 ]
1617+
1618+ assert "USER_PORTS=44772,8080,3389,8006" in forwarded_env
1619+ assert host_config_kwargs ["network_mode" ] == "container:sidecar-123"
1620+ assert "NET_ADMIN" in set (host_config_kwargs .get ("cap_add" ) or [])
1621+ assert "NET_ADMIN" not in set (host_config_kwargs .get ("cap_drop" ) or [])
1622+ assert forwarded_ports is None
1623+ assert labels ["opensandbox.io/embedding-proxy-port" ] == "51664"
1624+ assert labels ["opensandbox.io/http-port" ] == "48891"
1625+
1626+
14991627def test_restore_existing_sandboxes_ignores_manual_cleanup_without_warning ():
15001628 service = DockerSandboxService (config = _app_config ())
15011629 manual_container = MagicMock ()
0 commit comments