diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go index f580ce1134ea..9842734889b3 100644 --- a/cmd/podman/networks/create.go +++ b/cmd/podman/networks/create.go @@ -80,6 +80,9 @@ func networkCreateFlags(cmd *cobra.Command) { flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin") flags.BoolVar(&networkCreateOptions.IgnoreIfExists, "ignore", false, "Don't fail if network already exists") + dnsserverFlagName := "dns" + flags.StringArrayVar(&networkCreateOptions.NetworkDNSServers, dnsserverFlagName, nil, "DNS servers this network will use") + _ = cmd.RegisterFlagCompletionFunc(dnsserverFlagName, completion.AutocompleteNone) } func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ @@ -107,13 +110,14 @@ func networkCreate(cmd *cobra.Command, args []string) error { } network := types.Network{ - Name: name, - Driver: networkCreateOptions.Driver, - Options: networkCreateOptions.Options, - Labels: networkCreateOptions.Labels, - IPv6Enabled: networkCreateOptions.IPv6, - DNSEnabled: !networkCreateOptions.DisableDNS, - Internal: networkCreateOptions.Internal, + Name: name, + Driver: networkCreateOptions.Driver, + Options: networkCreateOptions.Options, + Labels: networkCreateOptions.Labels, + IPv6Enabled: networkCreateOptions.IPv6, + DNSEnabled: !networkCreateOptions.DisableDNS, + NetworkDNSServers: networkCreateOptions.NetworkDNSServers, + Internal: networkCreateOptions.Internal, } if cmd.Flags().Changed(ipamDriverFlagName) { diff --git a/cmd/podman/networks/update.go b/cmd/podman/networks/update.go new file mode 100644 index 000000000000..3a83be6c504e --- /dev/null +++ b/cmd/podman/networks/update.go @@ -0,0 +1,57 @@ +package network + +import ( + "fmt" + + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + networkUpdateDescription = `Update an existing podman network` + networkUpdateCommand = &cobra.Command{ + Use: "update [options] NETWORK", + Short: "update an existing podman network", + Long: networkUpdateDescription, + RunE: networkUpdate, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteNetworks, + Example: `podman network update podman1`, + } +) + +var ( + networkUpdateOptions entities.NetworkUpdateOptions +) + +func networkUpdateFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + addDNSServerFlagName := "dns-add" + flags.StringArrayVar(&networkUpdateOptions.AddDNSServers, addDNSServerFlagName, nil, "add network level nameservers") + removeDNSServerFlagName := "dns-drop" + flags.StringArrayVar(&networkUpdateOptions.RemoveDNSServers, removeDNSServerFlagName, nil, "remove network level nameservers") + _ = cmd.RegisterFlagCompletionFunc(addDNSServerFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(removeDNSServerFlagName, completion.AutocompleteNone) +} +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: networkUpdateCommand, + Parent: networkCmd, + }) + networkUpdateFlags(networkUpdateCommand) +} + +func networkUpdate(cmd *cobra.Command, args []string) error { + name := args[0] + + err := registry.ContainerEngine().NetworkUpdate(registry.Context(), name, networkUpdateOptions) + if err != nil { + return err + } + fmt.Printf("%s", name) + return nil +} diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md index 6fbea02f0240..c8a36f82fc50 100644 --- a/docs/source/markdown/podman-network-create.1.md +++ b/docs/source/markdown/podman-network-create.1.md @@ -24,6 +24,10 @@ release because it is used as a special network mode in **podman run/create --ne Disables the DNS plugin for this network which if enabled, can perform container to container name resolution. +#### **--dns**=*ip* + +Set network-scoped DNS resolver/nameserver for containers in this network. If not set, the host servers from `/etc/resolv.conf` will be used. It can be overwritten on the container level with the `podman run/create --dns` option. This option can be specified multiple times to set more than one IP. + #### **--driver**, **-d** Driver to manage the network. Currently `bridge`, `macvlan` and `ipvlan` are supported. Defaults to `bridge`. diff --git a/docs/source/markdown/podman-network-update.1.md b/docs/source/markdown/podman-network-update.1.md new file mode 100644 index 000000000000..5b26cfca9525 --- /dev/null +++ b/docs/source/markdown/podman-network-update.1.md @@ -0,0 +1,37 @@ +% podman-network-update 1 + +## NAME +podman\-network\-update - Update an existing podman network + +## SYNOPSIS +**podman network update** [*options*] *network* + +## DESCRIPTION +Allow changes to existing container networks. At present, only changes to the DNS servers in use by a network is supported. + +NOTE: Only supported with the netavark network backend. + + +## OPTIONS +#### **--dns-add** + +Accepts array of DNS resolvers and add it to the existing list of resolvers configured for a network. + +#### **--dns-drop** + +Accepts array of DNS resolvers and removes them from the existing list of resolvers configured for a network. + +## EXAMPLE + +Update a network +``` +$ podman network update network1 --dns-add 8.8.8.8,1.1.1.1 +podman2 +``` + +Update a network and add/remove dns servers +``` +$ podman network update network1 --dns-drop 8.8.8.8 --dns-add 3.3.3.3 +``` +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-network(1)](podman-network.1.md)**, **[podman-network-inspect(1)](podman-network-inspect.1.md)**, **[podman-network-ls(1)](podman-network-ls.1.md)** diff --git a/docs/source/markdown/podman-network.1.md b/docs/source/markdown/podman-network.1.md index 6ab7013e1d13..b85d84bd13e1 100644 --- a/docs/source/markdown/podman-network.1.md +++ b/docs/source/markdown/podman-network.1.md @@ -32,6 +32,7 @@ so networks have to be created again after a backend change. | prune | [podman-network-prune(1)](podman-network-prune.1.md) | Remove all unused networks | | reload | [podman-network-reload(1)](podman-network-reload.1.md) | Reload network configuration for containers | | rm | [podman-network-rm(1)](podman-network-rm.1.md) | Remove one or more networks | +| update | [podman-network-upate(1)](podman-network-update.1.md) | Update an existing Podman network | ## SEE ALSO **[podman(1)](podman.1.md)**, **[containers.conf(5)](https://github.com/containers/common/blob/main/docs/containers.conf.5.md)** diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index f52006c38103..20ab695e16f0 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -53,6 +53,26 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, report) } +func UpdateNetwork(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + ic := abi.ContainerEngine{Libpod: runtime} + + networkUpdateOptions := entities.NetworkUpdateOptions{} + if err := json.NewDecoder(r.Body).Decode(&networkUpdateOptions); err != nil { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to decode request JSON payload: %w", err)) + return + } + + name := utils.GetName(r) + + err := ic.NetworkUpdate(r.Context(), name, networkUpdateOptions) + if err != nil { + utils.Error(w, http.StatusInternalServerError, err) + } + + utils.WriteResponse(w, http.StatusNoContent, nil) +} + func ListNetworks(w http.ResponseWriter, r *http.Request) { if v, err := utils.SupportedVersion(r, ">=4.0.0"); err != nil { utils.BadRequest(w, "version", v.String(), err) diff --git a/pkg/api/handlers/swagger/models.go b/pkg/api/handlers/swagger/models.go index 2422b08b60c4..57f35c869c7d 100644 --- a/pkg/api/handlers/swagger/models.go +++ b/pkg/api/handlers/swagger/models.go @@ -44,3 +44,7 @@ type networkDisconnectRequest types.NetworkDisconnect // Network connect // swagger:model type networkConnectRequestLibpod entities.NetworkConnectOptions + +// Network update +// swagger:model +type networkUpdateRequestLibpod entities.NetworkUpdateOptions diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index 8f1b6a96a6bf..96b51b696294 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -234,6 +234,33 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/internalError" r.HandleFunc(VersionedPath("/libpod/networks/{name}"), s.APIHandler(libpod.RemoveNetwork)).Methods(http.MethodDelete) + // swagger:operation POST /libpod/networks/{name}/update libpod NetworkUpdateLibpod + // --- + // tags: + // - networks + // summary: Update exisiting podman network + // description: Update exisiting podman network + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the network + // - in: body + // name: update + // description: attributes for updating a netavark network + // schema: + // $ref: "#/definitions/networkUpdateRequestLibpod" + // responses: + // 200: + // description: OK + // 400: + // $ref: "#/responses/badParamError" + // 500: + // $ref: "#/responses/internalError" + r.HandleFunc(VersionedPath("/libpod/networks/{name}/update"), s.APIHandler(libpod.UpdateNetwork)).Methods(http.MethodPost) // swagger:operation GET /libpod/networks/{name}/exists libpod NetworkExistsLibpod // --- // tags: diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index ec4940b6dc32..79d26cbfa83b 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -50,6 +50,25 @@ func CreateWithOptions(ctx context.Context, network *types.Network, extraCreateO return report, response.Process(&report) } +// Updates an existing netavark network config +func Update(ctx context.Context, netNameOrID string, options *UpdateOptions) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + networkConfig, err := jsoniter.MarshalToString(options) + if err != nil { + return err + } + reader := strings.NewReader(networkConfig) + response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/networks/%s/update", nil, nil, netNameOrID) + if err != nil { + return err + } + defer response.Body.Close() + return response.Process(nil) +} + // Inspect returns information about a network configuration func Inspect(ctx context.Context, nameOrID string, _ *InspectOptions) (types.Network, error) { var net types.Network diff --git a/pkg/bindings/network/types.go b/pkg/bindings/network/types.go index c978a0d3dd26..d99f784177b8 100644 --- a/pkg/bindings/network/types.go +++ b/pkg/bindings/network/types.go @@ -58,6 +58,14 @@ type ListOptions struct { Filters map[string][]string } +// NetworkUpdateOptions describes options to update a network +// +//go:generate go run ../generator/generator.go UpdateOptions +type UpdateOptions struct { + AddDNSServers []string `json:"adddnsservers"` + RemoveDNSServers []string `json:"removednsservers"` +} + // DisconnectOptions are optional options for disconnecting // containers from a network // diff --git a/pkg/bindings/network/types_update_options.go b/pkg/bindings/network/types_update_options.go new file mode 100644 index 000000000000..2cc9d50dccf4 --- /dev/null +++ b/pkg/bindings/network/types_update_options.go @@ -0,0 +1,48 @@ +// Code generated by go generate; DO NOT EDIT. +package network + +import ( + "net/url" + + "github.com/containers/podman/v4/pkg/bindings/internal/util" +) + +// Changed returns true if named field has been set +func (o *UpdateOptions) Changed(fieldName string) bool { + return util.Changed(o, fieldName) +} + +// ToParams formats struct fields to be passed to API service +func (o *UpdateOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} + +// WithAddDNSServers set field AddDNSServers to given value +func (o *UpdateOptions) WithAddDNSServers(value []string) *UpdateOptions { + o.AddDNSServers = value + return o +} + +// GetAddDNSServers returns value of field AddDNSServers +func (o *UpdateOptions) GetAddDNSServers() []string { + if o.AddDNSServers == nil { + var z []string + return z + } + return o.AddDNSServers +} + +// WithRemoveDNSServers set field RemoveDNSServers to given value +func (o *UpdateOptions) WithRemoveDNSServers(value []string) *UpdateOptions { + o.RemoveDNSServers = value + return o +} + +// GetRemoveDNSServers returns value of field RemoveDNSServers +func (o *UpdateOptions) GetRemoveDNSServers() []string { + if o.RemoveDNSServers == nil { + var z []string + return z + } + return o.RemoveDNSServers +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index be1f85e2b94c..06a6372fbfdc 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -64,6 +64,7 @@ type ContainerEngine interface { //nolint:interfacebloat KubeApply(ctx context.Context, body io.Reader, opts ApplyOptions) error NetworkConnect(ctx context.Context, networkname string, options NetworkConnectOptions) error NetworkCreate(ctx context.Context, network types.Network, createOptions *types.NetworkCreateOptions) (*types.Network, error) + NetworkUpdate(ctx context.Context, networkname string, options NetworkUpdateOptions) error NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error NetworkExists(ctx context.Context, networkname string) (*BoolReport, error) NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]types.Network, []error, error) diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go index 03bbc950d822..0ac3d5bfa7b0 100644 --- a/pkg/domain/entities/network.go +++ b/pkg/domain/entities/network.go @@ -41,21 +41,28 @@ type NetworkRmReport struct { // NetworkCreateOptions describes options to create a network type NetworkCreateOptions struct { - DisableDNS bool - Driver string - Gateways []net.IP - Internal bool - Labels map[string]string - MacVLAN string - Ranges []string - Subnets []string - IPv6 bool + DisableDNS bool + Driver string + Gateways []net.IP + Internal bool + Labels map[string]string + MacVLAN string + NetworkDNSServers []string + Ranges []string + Subnets []string + IPv6 bool // Mapping of driver options and values. Options map[string]string // IgnoreIfExists if true, do not fail if the network already exists IgnoreIfExists bool } +// NetworkUpdateOptions describes options to update a network +type NetworkUpdateOptions struct { + AddDNSServers []string `json:"adddnsservers"` + RemoveDNSServers []string `json:"removednsservers"` +} + // NetworkCreateReport describes a created network for the cli type NetworkCreateReport struct { Name string diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index c221b3ce9322..01e6dea5388c 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -13,6 +13,17 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" ) +func (ic *ContainerEngine) NetworkUpdate(ctx context.Context, netName string, options entities.NetworkUpdateOptions) error { + var networkUpdateOptions types.NetworkUpdateOptions + networkUpdateOptions.AddDNSServers = options.AddDNSServers + networkUpdateOptions.RemoveDNSServers = options.RemoveDNSServers + err := ic.Libpod.Network().NetworkUpdate(netName, networkUpdateOptions) + if err != nil { + return err + } + return nil +} + func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]types.Network, error) { // dangling filter is not provided by netutil var wantDangling bool diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go index 2c3ce57213ce..36d8ddce4103 100644 --- a/pkg/domain/infra/tunnel/network.go +++ b/pkg/domain/infra/tunnel/network.go @@ -12,6 +12,11 @@ import ( "github.com/containers/podman/v4/pkg/errorhandling" ) +func (ic *ContainerEngine) NetworkUpdate(ctx context.Context, netName string, opts entities.NetworkUpdateOptions) error { + options := new(network.UpdateOptions).WithAddDNSServers(opts.AddDNSServers).WithRemoveDNSServers(opts.RemoveDNSServers) + return network.Update(ic.ClientCtx, netName, options) +} + func (ic *ContainerEngine) NetworkList(ctx context.Context, opts entities.NetworkListOptions) ([]types.Network, error) { options := new(network.ListOptions).WithFilters(opts.Filters) return network.List(ic.ClientCtx, options) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 56b09fc73744..377f229aa6bb 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -1,6 +1,7 @@ package integration import ( + "encoding/json" "fmt" "net" "os" @@ -8,6 +9,7 @@ import ( "syscall" "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/common/libnetwork/types" . "github.com/containers/podman/v4/test/utils" "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" @@ -41,6 +43,56 @@ var _ = Describe("Podman run networking", func() { }) + It("podman verify network scoped DNS server and also verify updating network dns server", func() { + // TODO: Unskip after https://github.com/containers/podman/pull/16525 + Skip("TODO: unskip after https://github.com/containers/podman/pull/16525") + // Following test is only functional with netavark and aardvark + SkipIfCNI(podmanTest) + net := createNetworkName("IntTest") + session := podmanTest.Podman([]string{"network", "create", net, "--dns", "1.1.1.1"}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(net) + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"network", "inspect", net}) + session.WaitWithDefaultTimeout() + defer podmanTest.removeNetwork(net) + var results []types.Network + err := json.Unmarshal([]byte(session.OutputToString()), &results) + Expect(err).ToNot(HaveOccurred()) + Expect(results).To(HaveLen(1)) + result := results[0] + Expect(result.Subnets).To(HaveLen(1)) + aardvarkDNSGateway := result.Subnets[0].Gateway.String() + Expect(session.OutputToString()).To(ContainSubstring("1.1.1.1")) + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "-d", "--name", "con1", "--network", net, "busybox", "top"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"exec", "-i", "con1", "nslookup", "google.com", aardvarkDNSGateway}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("Non-authoritative answer: Name: google.com Address:")) + + // Update to a bad DNS Server + session = podmanTest.Podman([]string{"network", "update", net, "--dns-add", "7.7.7.7"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + // Remove good DNS server + session = podmanTest.Podman([]string{"network", "update", net, "--dns-drop=1.1.1.1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"exec", "-i", "con1", "nslookup", "google.com", aardvarkDNSGateway}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(1)) + Expect(session.OutputToString()).To(ContainSubstring(";; connection timed out; no servers could be reached")) + + }) + It("podman run network connection with default bridge", func() { session := podmanTest.RunContainerWithNetworkTest("") session.WaitWithDefaultTimeout()