2018-07-11 14:31:27 +00:00
package network // import "github.com/docker/docker/integration/network"
import (
"bytes"
2020-11-30 11:34:24 +00:00
"encoding/json"
2022-01-27 20:13:45 +00:00
"fmt"
2018-11-05 13:50:33 +00:00
"net/http"
2018-07-11 14:31:27 +00:00
"os/exec"
"strings"
"testing"
"github.com/docker/docker/api/types"
2023-08-25 18:25:58 +00:00
containertypes "github.com/docker/docker/api/types/container"
2022-01-27 20:13:45 +00:00
ntypes "github.com/docker/docker/api/types/network"
2018-07-11 14:31:27 +00:00
"github.com/docker/docker/integration/internal/container"
2020-03-04 22:44:08 +00:00
"github.com/docker/docker/integration/internal/network"
2023-07-14 18:02:38 +00:00
"github.com/docker/docker/testutil"
2019-08-29 20:52:40 +00:00
"github.com/docker/docker/testutil/daemon"
"github.com/docker/docker/testutil/request"
2020-02-07 13:39:24 +00:00
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
2020-03-04 22:44:08 +00:00
"gotest.tools/v3/icmd"
2020-02-07 13:39:24 +00:00
"gotest.tools/v3/skip"
2018-07-11 14:31:27 +00:00
)
func TestRunContainerWithBridgeNone ( t * testing . T ) {
skip . If ( t , testEnv . IsRemoteDaemon , "cannot start daemon on remote test run" )
skip . If ( t , testEnv . DaemonInfo . OSType != "linux" )
2021-08-12 07:12:04 +00:00
skip . If ( t , testEnv . IsUserNamespace )
2020-03-13 13:37:09 +00:00
skip . If ( t , testEnv . IsRootless , "rootless mode has different view of network" )
2018-07-11 14:31:27 +00:00
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( baseContext , t )
2018-07-11 14:31:27 +00:00
d := daemon . New ( t )
2023-07-14 18:02:38 +00:00
d . StartWithBusybox ( ctx , t , "-b" , "none" )
2018-07-11 14:31:27 +00:00
defer d . Stop ( t )
2018-12-22 14:53:02 +00:00
c := d . NewClientT ( t )
2018-07-11 14:31:27 +00:00
2019-06-06 11:15:31 +00:00
id1 := container . Run ( ctx , t , c )
2023-08-25 18:25:58 +00:00
defer c . ContainerRemove ( ctx , id1 , containertypes . RemoveOptions { Force : true } )
2018-12-22 14:53:02 +00:00
result , err := container . Exec ( ctx , c , id1 , [ ] string { "ip" , "l" } )
2018-07-11 14:31:27 +00:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( false , strings . Contains ( result . Combined ( ) , "eth0" ) ) , "There shouldn't be eth0 in container in default(bridge) mode when bridge network is disabled" )
2019-06-06 11:15:31 +00:00
id2 := container . Run ( ctx , t , c , container . WithNetworkMode ( "bridge" ) )
2023-08-25 18:25:58 +00:00
defer c . ContainerRemove ( ctx , id2 , containertypes . RemoveOptions { Force : true } )
2018-07-11 14:31:27 +00:00
2018-12-22 14:53:02 +00:00
result , err = container . Exec ( ctx , c , id2 , [ ] string { "ip" , "l" } )
2018-07-11 14:31:27 +00:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( false , strings . Contains ( result . Combined ( ) , "eth0" ) ) , "There shouldn't be eth0 in container in bridge mode when bridge network is disabled" )
nsCommand := "ls -l /proc/self/ns/net | awk -F '->' '{print $2}'"
cmd := exec . Command ( "sh" , "-c" , nsCommand )
stdout := bytes . NewBuffer ( nil )
cmd . Stdout = stdout
err = cmd . Run ( )
assert . NilError ( t , err , "Failed to get current process network namespace: %+v" , err )
2019-06-06 11:15:31 +00:00
id3 := container . Run ( ctx , t , c , container . WithNetworkMode ( "host" ) )
2023-08-25 18:25:58 +00:00
defer c . ContainerRemove ( ctx , id3 , containertypes . RemoveOptions { Force : true } )
2018-07-11 14:31:27 +00:00
2018-12-22 14:53:02 +00:00
result , err = container . Exec ( ctx , c , id3 , [ ] string { "sh" , "-c" , nsCommand } )
2018-07-11 14:31:27 +00:00
assert . NilError ( t , err )
2018-11-11 15:07:43 +00:00
assert . Check ( t , is . Equal ( stdout . String ( ) , result . Combined ( ) ) , "The network namespace of container should be the same with host when --net=host and bridge network is disabled" )
2018-07-11 14:31:27 +00:00
}
2018-11-05 13:50:33 +00:00
2022-04-05 09:43:06 +00:00
// TestNetworkInvalidJSON tests that POST endpoints that expect a body return
// the correct error when sending invalid JSON requests.
2018-11-05 13:50:33 +00:00
func TestNetworkInvalidJSON ( t * testing . T ) {
2023-07-14 18:02:38 +00:00
ctx := setupTest ( t )
2018-11-05 13:50:33 +00:00
2022-04-05 09:43:06 +00:00
// POST endpoints that accept / expect a JSON body;
2018-11-05 13:50:33 +00:00
endpoints := [ ] string {
"/networks/create" ,
"/networks/bridge/connect" ,
"/networks/bridge/disconnect" ,
}
for _ , ep := range endpoints {
2021-07-22 20:41:01 +00:00
ep := ep
2022-04-05 09:43:06 +00:00
t . Run ( ep [ 1 : ] , func ( t * testing . T ) {
2018-11-05 13:50:33 +00:00
t . Parallel ( )
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( ctx , t )
2018-11-05 13:50:33 +00:00
2022-04-05 09:43:06 +00:00
t . Run ( "invalid content type" , func ( t * testing . T ) {
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( ctx , t )
res , body , err := request . Post ( ctx , ep , request . RawString ( "{}" ) , request . ContentType ( "text/plain" ) )
2022-04-05 09:43:06 +00:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( res . StatusCode , http . StatusBadRequest ) )
buf , err := request . ReadBody ( body )
assert . NilError ( t , err )
assert . Check ( t , is . Contains ( string ( buf ) , "unsupported Content-Type header (text/plain): must be 'application/json'" ) )
} )
t . Run ( "invalid JSON" , func ( t * testing . T ) {
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( ctx , t )
res , body , err := request . Post ( ctx , ep , request . RawString ( "{invalid json" ) , request . JSON )
2022-04-05 09:43:06 +00:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( res . StatusCode , http . StatusBadRequest ) )
buf , err := request . ReadBody ( body )
assert . NilError ( t , err )
assert . Check ( t , is . Contains ( string ( buf ) , "invalid JSON: invalid character 'i' looking for beginning of object key string" ) )
} )
t . Run ( "extra content after JSON" , func ( t * testing . T ) {
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( ctx , t )
res , body , err := request . Post ( ctx , ep , request . RawString ( ` { } trailing content ` ) , request . JSON )
2022-04-05 09:43:06 +00:00
assert . NilError ( t , err )
assert . Check ( t , is . Equal ( res . StatusCode , http . StatusBadRequest ) )
buf , err := request . ReadBody ( body )
assert . NilError ( t , err )
assert . Check ( t , is . Contains ( string ( buf ) , "unexpected content after JSON" ) )
} )
t . Run ( "empty body" , func ( t * testing . T ) {
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( ctx , t )
2022-04-05 09:43:06 +00:00
// empty body should not produce an 500 internal server error, or
// any 5XX error (this is assuming the request does not produce
// an internal server error for another reason, but it shouldn't)
2023-07-14 18:02:38 +00:00
res , _ , err := request . Post ( ctx , ep , request . RawString ( ` ` ) , request . JSON )
2022-04-05 09:43:06 +00:00
assert . NilError ( t , err )
assert . Check ( t , res . StatusCode < http . StatusInternalServerError )
} )
2018-11-05 13:50:33 +00:00
} )
}
}
2020-03-04 22:44:08 +00:00
2020-11-30 11:34:24 +00:00
// TestNetworkList verifies that /networks returns a list of networks either
// with, or without a trailing slash (/networks/). Regression test for https://github.com/moby/moby/issues/24595
func TestNetworkList ( t * testing . T ) {
2023-07-14 18:02:38 +00:00
ctx := setupTest ( t )
2020-11-30 11:34:24 +00:00
endpoints := [ ] string {
"/networks" ,
"/networks/" ,
}
for _ , ep := range endpoints {
2021-07-22 20:41:01 +00:00
ep := ep
2020-11-30 11:34:24 +00:00
t . Run ( ep , func ( t * testing . T ) {
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( ctx , t )
2020-11-30 11:34:24 +00:00
t . Parallel ( )
2023-07-14 18:02:38 +00:00
res , body , err := request . Get ( ctx , ep , request . JSON )
2020-11-30 11:34:24 +00:00
assert . NilError ( t , err )
assert . Equal ( t , res . StatusCode , http . StatusOK )
buf , err := request . ReadBody ( body )
assert . NilError ( t , err )
var nws [ ] types . NetworkResource
err = json . Unmarshal ( buf , & nws )
assert . NilError ( t , err )
assert . Assert ( t , len ( nws ) > 0 )
} )
}
}
2020-03-04 22:44:08 +00:00
func TestHostIPv4BridgeLabel ( t * testing . T ) {
2023-06-14 09:46:00 +00:00
skip . If ( t , testEnv . DaemonInfo . OSType == "windows" )
2020-03-04 22:44:08 +00:00
skip . If ( t , testEnv . IsRemoteDaemon )
2020-03-13 13:37:09 +00:00
skip . If ( t , testEnv . IsRootless , "rootless mode has different view of network" )
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( baseContext , t )
2020-03-04 22:44:08 +00:00
d := daemon . New ( t )
d . Start ( t )
defer d . Stop ( t )
c := d . NewClientT ( t )
defer c . Close ( )
ipv4SNATAddr := "172.0.0.172"
// Create a bridge network with --opt com.docker.network.host_ipv4=172.0.0.172
bridgeName := "hostIPv4Bridge"
network . CreateNoError ( ctx , t , c , bridgeName ,
network . WithDriver ( "bridge" ) ,
network . WithOption ( "com.docker.network.host_ipv4" , ipv4SNATAddr ) ,
network . WithOption ( "com.docker.network.bridge.name" , bridgeName ) ,
)
out , err := c . NetworkInspect ( ctx , bridgeName , types . NetworkInspectOptions { Verbose : true } )
assert . NilError ( t , err )
assert . Assert ( t , len ( out . IPAM . Config ) > 0 )
// Make sure the SNAT rule exists
2023-07-14 18:02:38 +00:00
testutil . RunCommand ( ctx , "iptables" , "-t" , "nat" , "-C" , "POSTROUTING" , "-s" , out . IPAM . Config [ 0 ] . Subnet , "!" , "-o" , bridgeName , "-j" , "SNAT" , "--to-source" , ipv4SNATAddr ) . Assert ( t , icmd . Success )
2020-03-04 22:44:08 +00:00
}
2022-01-27 20:13:45 +00:00
func TestDefaultNetworkOpts ( t * testing . T ) {
2023-06-14 09:46:00 +00:00
skip . If ( t , testEnv . DaemonInfo . OSType == "windows" )
2022-01-27 20:13:45 +00:00
skip . If ( t , testEnv . IsRemoteDaemon )
skip . If ( t , testEnv . IsRootless , "rootless mode has different view of network" )
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( baseContext , t )
2022-01-27 20:13:45 +00:00
tests := [ ] struct {
name string
mtu int
configFrom bool
args [ ] string
} {
{
name : "default value" ,
mtu : 1500 ,
args : [ ] string { } ,
} ,
{
name : "cmdline value" ,
mtu : 1234 ,
args : [ ] string { "--default-network-opt" , "bridge=com.docker.network.driver.mtu=1234" } ,
} ,
{
name : "config-from value" ,
configFrom : true ,
mtu : 1233 ,
args : [ ] string { "--default-network-opt" , "bridge=com.docker.network.driver.mtu=1234" } ,
} ,
}
for _ , tc := range tests {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
2023-07-14 18:02:38 +00:00
ctx := testutil . StartSpan ( ctx , t )
2022-01-27 20:13:45 +00:00
d := daemon . New ( t )
2023-07-14 18:02:38 +00:00
d . StartWithBusybox ( ctx , t , tc . args ... )
2022-01-27 20:13:45 +00:00
defer d . Stop ( t )
c := d . NewClientT ( t )
defer c . Close ( )
if tc . configFrom {
// Create a new network config
network . CreateNoError ( ctx , t , c , "from-net" , func ( create * types . NetworkCreate ) {
create . ConfigOnly = true
create . Options = map [ string ] string {
"com.docker.network.driver.mtu" : fmt . Sprint ( tc . mtu ) ,
}
} )
defer c . NetworkRemove ( ctx , "from-net" )
}
// Create a new network
networkName := "testnet"
2023-11-24 17:35:01 +00:00
networkId := network . CreateNoError ( ctx , t , c , networkName , func ( create * types . NetworkCreate ) {
2022-01-27 20:13:45 +00:00
if tc . configFrom {
create . ConfigFrom = & ntypes . ConfigReference {
Network : "from-net" ,
}
}
} )
defer c . NetworkRemove ( ctx , networkName )
2023-11-24 17:35:01 +00:00
// Check the MTU of the bridge itself, before any devices are connected. (The
// bridge's MTU will be set to the minimum MTU of anything connected to it, but
// it's set explicitly on the bridge anyway - so it doesn't look like the option
// was ignored.)
cmd := exec . Command ( "ip" , "link" , "show" , "br-" + networkId [ : 12 ] )
output , err := cmd . CombinedOutput ( )
assert . NilError ( t , err )
assert . Check ( t , is . Contains ( string ( output ) , fmt . Sprintf ( " mtu %d " , tc . mtu ) ) , "Bridge MTU should have been set to %d" , tc . mtu )
2022-01-27 20:13:45 +00:00
// Start a container to inspect the MTU of its network interface
id1 := container . Run ( ctx , t , c , container . WithNetworkMode ( networkName ) )
2023-08-25 18:25:58 +00:00
defer c . ContainerRemove ( ctx , id1 , containertypes . RemoveOptions { Force : true } )
2022-01-27 20:13:45 +00:00
result , err := container . Exec ( ctx , c , id1 , [ ] string { "ip" , "l" , "show" , "eth0" } )
assert . NilError ( t , err )
assert . Check ( t , is . Contains ( result . Combined ( ) , fmt . Sprintf ( " mtu %d " , tc . mtu ) ) , "Network MTU should have been set to %d" , tc . mtu )
} )
}
}
libnet: Make sure network names are unique
Fixes #18864, #20648, #33561, #40901.
[This GH comment][1] makes clear network name uniqueness has never been
enforced due to the eventually consistent nature of Classic Swarm
datastores:
> there is no guaranteed way to check for duplicates across a cluster of
> docker hosts.
And this is further confirmed by other comments made by @mrjana in that
same issue, eg. [this one][2]:
> we want to adopt a schema which can pave the way in the future for a
> completely decentralized cluster of docker hosts (if scalability is
> needed).
This decentralized model is what Classic Swarm was trying to be. It's
been superseded since then by Docker Swarm, which has a centralized
control plane.
To circumvent this drawback, the `NetworkCreate` endpoint accepts a
`CheckDuplicate` flag. However it's not perfectly reliable as it won't
catch concurrent requests.
Due to this design decision, API clients like Compose have to implement
workarounds to make sure names are really unique (eg.
docker/compose#9585). And the daemon itself has seen a string of issues
due to that decision, including some that aren't fixed to this day (for
instance moby/moby#40901):
> The problem is, that if you specify a network for a container using
> the ID, it will add that network to the container but it will then
> change it to reference the network by using the name.
To summarize, this "feature" is broken, has no practical use and is a
source of pain for Docker users and API consumers. So let's just remove
it for _all_ API versions.
[1]: https://github.com/moby/moby/issues/18864#issuecomment-167201414
[2]: https://github.com/moby/moby/issues/18864#issuecomment-167202589
Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2023-08-16 18:11:10 +00:00
func TestForbidDuplicateNetworkNames ( t * testing . T ) {
skip . If ( t , testEnv . DaemonInfo . OSType == "windows" )
ctx := testutil . StartSpan ( baseContext , t )
d := daemon . New ( t )
d . StartWithBusybox ( ctx , t )
defer d . Stop ( t )
c := d . NewClientT ( t )
defer c . Close ( )
network . CreateNoError ( ctx , t , c , "testnet" )
_ , err := c . NetworkCreate ( ctx , "testnet" , types . NetworkCreate { } )
assert . Error ( t , err , "Error response from daemon: network with name testnet already exists" , "2nd NetworkCreate call should have failed" )
}