Merge pull request #6079 from mheon/add_expose

Rework port parsing to support --expose and -P
This commit is contained in:
OpenShift Merge Robot
2020-05-05 15:21:37 +02:00
committed by GitHub
10 changed files with 704 additions and 55 deletions

View File

@ -192,7 +192,6 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []strin
func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error {
var (
err error
// namespaces map[string]string
)
// validate flags as needed
@ -234,9 +233,15 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// We are not handling the Expose flag yet.
// s.PortsExpose = c.Expose
s.PortMappings = c.Net.PublishPorts
s.PublishImagePorts = c.PublishAll
s.PublishExposedPorts = c.PublishAll
s.Pod = c.Pod
expose, err := createExpose(c.Expose)
if err != nil {
return err
}
s.Expose = expose
for k, v := range map[string]*specgen.Namespace{
c.IPC: &s.IpcNS,
c.PID: &s.PidNS,

View File

@ -1,43 +1,201 @@
package common
import (
"net"
"strconv"
"strings"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// createPortBindings iterates ports mappings and exposed ports into a format CNI understands
func createPortBindings(ports []string) ([]ocicni.PortMapping, error) {
// TODO wants someone to rewrite this code in the future
var portBindings []ocicni.PortMapping
// The conversion from []string to natBindings is temporary while mheon reworks the port
// deduplication code. Eventually that step will not be required.
_, natBindings, err := nat.ParsePortSpecs(ports)
if err != nil {
return nil, err
}
for containerPb, hostPb := range natBindings {
var pm ocicni.PortMapping
pm.ContainerPort = int32(containerPb.Int())
for _, i := range hostPb {
var hostPort int
var err error
pm.HostIP = i.HostIP
if i.HostPort == "" {
hostPort = containerPb.Int()
} else {
hostPort, err = strconv.Atoi(i.HostPort)
if err != nil {
return nil, errors.Wrapf(err, "unable to convert host port to integer")
}
}
// createExpose parses user-provided exposed port definitions and converts them
// into SpecGen format.
// TODO: The SpecGen format should really handle ranges more sanely - we could
// be massively inflating what is sent over the wire with a large range.
func createExpose(expose []string) (map[uint16]string, error) {
toReturn := make(map[uint16]string)
pm.HostPort = int32(hostPort)
pm.Protocol = containerPb.Proto()
portBindings = append(portBindings, pm)
for _, e := range expose {
// Check for protocol
proto := "tcp"
splitProto := strings.Split(e, "/")
if len(splitProto) > 2 {
return nil, errors.Errorf("invalid expose format - protocol can only be specified once")
} else if len(splitProto) == 2 {
proto = splitProto[1]
}
// Check for a range
start, len, err := parseAndValidateRange(splitProto[0])
if err != nil {
return nil, err
}
var index uint16
for index = 0; index < len; index++ {
portNum := start + index
protocols, ok := toReturn[portNum]
if !ok {
toReturn[portNum] = proto
} else {
newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",")
toReturn[portNum] = newProto
}
}
}
return portBindings, nil
return toReturn, nil
}
// createPortBindings iterates ports mappings into SpecGen format.
func createPortBindings(ports []string) ([]specgen.PortMapping, error) {
// --publish is formatted as follows:
// [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol]
toReturn := make([]specgen.PortMapping, 0, len(ports))
for _, p := range ports {
var (
ctrPort string
proto, hostIP, hostPort *string
)
splitProto := strings.Split(p, "/")
switch len(splitProto) {
case 1:
// No protocol was provided
case 2:
proto = &(splitProto[1])
default:
return nil, errors.Errorf("invalid port format - protocol can only be specified once")
}
splitPort := strings.Split(splitProto[0], ":")
switch len(splitPort) {
case 1:
ctrPort = splitPort[0]
case 2:
hostPort = &(splitPort[0])
ctrPort = splitPort[1]
case 3:
hostIP = &(splitPort[0])
hostPort = &(splitPort[1])
ctrPort = splitPort[2]
default:
return nil, errors.Errorf("invalid port format - format is [[hostIP:]hostPort:]containerPort")
}
newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto)
if err != nil {
return nil, err
}
toReturn = append(toReturn, newPort)
}
return toReturn, nil
}
// parseSplitPort parses individual components of the --publish flag to produce
// a single port mapping in SpecGen format.
func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (specgen.PortMapping, error) {
newPort := specgen.PortMapping{}
if ctrPort == "" {
return newPort, errors.Errorf("must provide a non-empty container port to publish")
}
ctrStart, ctrLen, err := parseAndValidateRange(ctrPort)
if err != nil {
return newPort, errors.Wrapf(err, "error parsing container port")
}
newPort.ContainerPort = ctrStart
newPort.Range = ctrLen
if protocol != nil {
if *protocol == "" {
return newPort, errors.Errorf("must provide a non-empty protocol to publish")
}
newPort.Protocol = *protocol
}
if hostIP != nil {
if *hostIP == "" {
return newPort, errors.Errorf("must provide a non-empty container host IP to publish")
}
testIP := net.ParseIP(*hostIP)
if testIP == nil {
return newPort, errors.Errorf("cannot parse %q as an IP address", *hostIP)
}
newPort.HostIP = testIP.String()
}
if hostPort != nil {
if *hostPort == "" {
return newPort, errors.Errorf("must provide a non-empty container host port to publish")
}
hostStart, hostLen, err := parseAndValidateRange(*hostPort)
if err != nil {
return newPort, errors.Wrapf(err, "error parsing host port")
}
if hostLen != ctrLen {
return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen)
}
newPort.HostPort = hostStart
}
hport := newPort.HostPort
if hport == 0 {
hport = newPort.ContainerPort
}
logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol)
return newPort, nil
}
// Parse and validate a port range.
// Returns start port, length of range, error.
func parseAndValidateRange(portRange string) (uint16, uint16, error) {
splitRange := strings.Split(portRange, "-")
if len(splitRange) > 2 {
return 0, 0, errors.Errorf("invalid port format - port ranges are formatted as startPort-stopPort")
}
if splitRange[0] == "" {
return 0, 0, errors.Errorf("port numbers cannot be negative")
}
startPort, err := parseAndValidatePort(splitRange[0])
if err != nil {
return 0, 0, err
}
var rangeLen uint16 = 1
if len(splitRange) == 2 {
if splitRange[1] == "" {
return 0, 0, errors.Errorf("must provide ending number for port range")
}
endPort, err := parseAndValidatePort(splitRange[1])
if err != nil {
return 0, 0, err
}
if endPort <= startPort {
return 0, 0, errors.Errorf("the end port of a range must be higher than the start port - %d is not higher than %d", endPort, startPort)
}
// Our range is the total number of ports
// involved, so we need to add 1 (8080:8081 is
// 2 ports, for example, not 1)
rangeLen = endPort - startPort + 1
}
return startPort, rangeLen, nil
}
// Turn a single string into a valid U16 port.
func parseAndValidatePort(port string) (uint16, error) {
num, err := strconv.Atoi(port)
if err != nil {
return 0, errors.Wrapf(err, "cannot parse %q as a port number", port)
}
if num < 1 || num > 65535 {
return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num)
}
return uint16(num), nil
}

View File

@ -8,7 +8,6 @@ import (
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/storage/pkg/archive"
"github.com/cri-o/ocicni/pkg/ocicni"
)
type Container struct {
@ -40,7 +39,7 @@ type NetOptions struct {
DNSServers []net.IP
Network specgen.Namespace
NoHosts bool
PublishPorts []ocicni.PortMapping
PublishPorts []specgen.PortMapping
StaticIP *net.IP
StaticMAC *net.HardwareAddr
}

View File

@ -96,7 +96,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, err
}
opts, err := createContainerOptions(rt, s, pod, finalVolumes)
opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage)
if err != nil {
return nil, err
}
@ -115,7 +115,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return rt.NewContainer(ctx, runtimeSpec, options...)
}
func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume) ([]libpod.CtrCreateOption, error) {
func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@ -134,7 +134,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, rt.WithPod(pod))
}
destinations := []string{}
// // Take all mount and named volume destinations.
// Take all mount and named volume destinations.
for _, mount := range s.Mounts {
destinations = append(destinations, mount.Destination)
}
@ -188,7 +188,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
namespaceOptions, err := GenerateNamespaceOptions(s, rt, pod)
namespaceOptions, err := GenerateNamespaceOptions(ctx, s, rt, pod, img)
if err != nil {
return nil, err
}

View File

@ -1,12 +1,14 @@
package generate
import (
"context"
"os"
"strings"
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/util"
@ -76,7 +78,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod)
// joining a pod.
// TODO: Consider grouping options that are not directly attached to a namespace
// elsewhere.
func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) {
func GenerateNamespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, img *image.Image) ([]libpod.CtrCreateOption, error) {
toReturn := []libpod.CtrCreateOption{}
// If pod is not nil, get infra container.
@ -204,7 +206,6 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod
}
// Net
// TODO image ports
// TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we
// are in bridge mode.
postConfigureNetNS := !s.UserNS.IsHost()
@ -221,9 +222,17 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod
}
toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr))
case specgen.Slirp:
toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "slirp4netns", nil))
portMappings, err := createPortMappings(ctx, s, img)
if err != nil {
return nil, err
}
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "slirp4netns", nil))
case specgen.Bridge:
toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "bridge", s.CNINetworks))
portMappings, err := createPortMappings(ctx, s, img)
if err != nil {
return nil, err
}
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "bridge", s.CNINetworks))
}
if s.UseImageHosts {
@ -428,7 +437,7 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt
if g.Config.Annotations == nil {
g.Config.Annotations = make(map[string]string)
}
if s.PublishImagePorts {
if s.PublishExposedPorts {
g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
} else {
g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse

View File

@ -83,7 +83,11 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er
options = append(options, libpod.WithPodUseImageHosts())
}
if len(p.PortMappings) > 0 {
options = append(options, libpod.WithInfraContainerPorts(p.PortMappings))
ports, _, _, err := parsePortMapping(p.PortMappings)
if err != nil {
return nil, err
}
options = append(options, libpod.WithInfraContainerPorts(ports))
}
options = append(options, libpod.WithPodCgroups())
return options, nil

View File

@ -0,0 +1,333 @@
package generate
import (
"context"
"net"
"strconv"
"strings"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/specgen"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const (
protoTCP = "tcp"
protoUDP = "udp"
protoSCTP = "sctp"
)
// Parse port maps to OCICNI port mappings.
// Returns a set of OCICNI port mappings, and maps of utilized container and
// host ports.
func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) {
// First, we need to validate the ports passed in the specgen, and then
// convert them into CNI port mappings.
finalMappings := []ocicni.PortMapping{}
// To validate, we need two maps: one for host ports, one for container
// ports.
// Each is a map of protocol to map of IP address to map of port to
// port (for hostPortValidate, it's host port to container port;
// for containerPortValidate, container port to host port.
// These will ensure no collisions.
hostPortValidate := make(map[string]map[string]map[uint16]uint16)
containerPortValidate := make(map[string]map[string]map[uint16]uint16)
// Initialize the first level of maps (we can't really guess keys for
// the rest).
for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
hostPortValidate[proto] = make(map[string]map[uint16]uint16)
containerPortValidate[proto] = make(map[string]map[uint16]uint16)
}
// Iterate through all port mappings, generating OCICNI PortMapping
// structs and validating there is no overlap.
for _, port := range portMappings {
// First, check proto
protocols, err := checkProtocol(port.Protocol, true)
if err != nil {
return nil, nil, nil, err
}
// Validate host IP
hostIP := port.HostIP
if hostIP == "" {
hostIP = "0.0.0.0"
}
if ip := net.ParseIP(hostIP); ip == nil {
return nil, nil, nil, errors.Errorf("invalid IP address %s in port mapping", port.HostIP)
}
// Validate port numbers and range.
len := port.Range
if len == 0 {
len = 1
}
containerPort := port.ContainerPort
if containerPort == 0 {
return nil, nil, nil, errors.Errorf("container port number must be non-0")
}
hostPort := port.HostPort
if hostPort == 0 {
hostPort = containerPort
}
if uint32(len-1)+uint32(containerPort) > 65535 {
return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number")
}
if uint32(len-1)+uint32(hostPort) > 65536 {
return nil, nil, nil, errors.Errorf("host port range exceeds maximum allowable port number")
}
// Iterate through ports, populating maps to check for conflicts
// and generating CNI port mappings.
for _, p := range protocols {
hostIPMap := hostPortValidate[p]
ctrIPMap := containerPortValidate[p]
hostPortMap, ok := hostIPMap[hostIP]
if !ok {
hostPortMap = make(map[uint16]uint16)
hostIPMap[hostIP] = hostPortMap
}
ctrPortMap, ok := ctrIPMap[hostIP]
if !ok {
ctrPortMap = make(map[uint16]uint16)
ctrIPMap[hostIP] = ctrPortMap
}
// Iterate through all port numbers in the requested
// range.
var index uint16
for index = 0; index < len; index++ {
cPort := containerPort + index
hPort := hostPort + index
if cPort == 0 || hPort == 0 {
return nil, nil, nil, errors.Errorf("host and container ports cannot be 0")
}
testCPort := ctrPortMap[cPort]
if testCPort != 0 && testCPort != hPort {
// This is an attempt to redefine a port
return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p)
}
ctrPortMap[cPort] = hPort
testHPort := hostPortMap[hPort]
if testHPort != 0 && testHPort != cPort {
return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p)
}
hostPortMap[hPort] = cPort
// If we have an exact duplicate, just continue
if testCPort == hPort && testHPort == cPort {
continue
}
// We appear to be clear. Make an OCICNI port
// struct.
// Don't use hostIP - we want to preserve the
// empty string hostIP by default for compat.
cniPort := ocicni.PortMapping{
HostPort: int32(hPort),
ContainerPort: int32(cPort),
Protocol: p,
HostIP: port.HostIP,
}
finalMappings = append(finalMappings, cniPort)
}
}
}
return finalMappings, containerPortValidate, hostPortValidate, nil
}
// Make final port mappings for the container
func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *image.Image) ([]ocicni.PortMapping, error) {
finalMappings, containerPortValidate, hostPortValidate, err := parsePortMapping(s.PortMappings)
if err != nil {
return nil, err
}
// If not publishing exposed ports, or if we are publishing and there is
// nothing to publish - then just return the port mappings we've made so
// far.
if !s.PublishExposedPorts || (len(s.Expose) == 0 && img == nil) {
return finalMappings, nil
}
logrus.Debugf("Adding exposed ports")
// We need to merge s.Expose into image exposed ports
expose := make(map[uint16]string)
for k, v := range s.Expose {
expose[k] = v
}
if img != nil {
inspect, err := img.InspectNoSize(ctx)
if err != nil {
return nil, errors.Wrapf(err, "error inspecting image to get exposed ports")
}
for imgExpose := range inspect.Config.ExposedPorts {
// Expose format is portNumber[/protocol]
splitExpose := strings.SplitN(imgExpose, "/", 2)
num, err := strconv.Atoi(splitExpose[0])
if err != nil {
return nil, errors.Wrapf(err, "unable to convert image EXPOSE statement %q to port number", imgExpose)
}
if num > 65535 || num < 1 {
return nil, errors.Errorf("%d from image EXPOSE statement %q is not a valid port number", num, imgExpose)
}
// No need to validate protocol, we'll do it below.
if len(splitExpose) == 1 {
expose[uint16(num)] = "tcp"
} else {
expose[uint16(num)] = splitExpose[1]
}
}
}
// There's been a request to expose some ports. Let's do that.
// Start by figuring out what needs to be exposed.
// This is a map of container port number to protocols to expose.
toExpose := make(map[uint16][]string)
for port, proto := range expose {
// Validate protocol first
protocols, err := checkProtocol(proto, false)
if err != nil {
return nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port)
}
if port == 0 {
return nil, errors.Errorf("cannot expose 0 as it is not a valid port number")
}
// Check to see if the port is already present in existing
// mappings.
for _, p := range protocols {
ctrPortMap, ok := containerPortValidate[p]["0.0.0.0"]
if !ok {
ctrPortMap = make(map[uint16]uint16)
containerPortValidate[p]["0.0.0.0"] = ctrPortMap
}
if portNum := ctrPortMap[port]; portNum == 0 {
// We want to expose this port for this protocol
exposeProto, ok := toExpose[port]
if !ok {
exposeProto = []string{}
}
exposeProto = append(exposeProto, p)
toExpose[port] = exposeProto
}
}
}
// We now have a final list of ports that we want exposed.
// Let's find empty, unallocated host ports for them.
for port, protocols := range toExpose {
for _, p := range protocols {
// Find an open port on the host.
// I see a faint possibility that this will infinite
// loop trying to find a valid open port, so I've
// included a max-tries counter.
hostPort := 0
tries := 15
for hostPort == 0 && tries > 0 {
// We can't select a specific protocol, which is
// unfortunate for the UDP case.
candidate, err := getRandomPort()
if err != nil {
return nil, err
}
// Check if the host port is already bound
hostPortMap, ok := hostPortValidate[p]["0.0.0.0"]
if !ok {
hostPortMap = make(map[uint16]uint16)
hostPortValidate[p]["0.0.0.0"] = hostPortMap
}
if checkPort := hostPortMap[uint16(candidate)]; checkPort != 0 {
// Host port is already allocated, try again
tries--
continue
}
hostPortMap[uint16(candidate)] = port
hostPort = candidate
logrus.Debugf("Mapping exposed port %d/%s to host port %d", port, p, hostPort)
// Make a CNI port mapping
cniPort := ocicni.PortMapping{
HostPort: int32(candidate),
ContainerPort: int32(port),
Protocol: p,
HostIP: "",
}
finalMappings = append(finalMappings, cniPort)
}
if tries == 0 && hostPort == 0 {
// We failed to find an open port.
return nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port)
}
}
}
return finalMappings, nil
}
// Check a string to ensure it is a comma-separated set of valid protocols
func checkProtocol(protocol string, allowSCTP bool) ([]string, error) {
protocols := make(map[string]struct{})
splitProto := strings.Split(protocol, ",")
// Don't error on duplicates - just deduplicate
for _, p := range splitProto {
switch p {
case protoTCP, "":
protocols[protoTCP] = struct{}{}
case protoUDP:
protocols[protoUDP] = struct{}{}
case protoSCTP:
if !allowSCTP {
return nil, errors.Errorf("protocol SCTP is not allowed for exposed ports")
}
protocols[protoSCTP] = struct{}{}
default:
return nil, errors.Errorf("unrecognized protocol %q in port mapping", p)
}
}
finalProto := []string{}
for p := range protocols {
finalProto = append(finalProto, p)
}
// This shouldn't be possible, but check anyways
if len(finalProto) == 0 {
return nil, errors.Errorf("no valid protocols specified for port mapping")
}
return finalProto, nil
}
// Find a random, open port on the host
func getRandomPort() (int, error) {
l, err := net.Listen("tcp", ":0")
if err != nil {
return 0, errors.Wrapf(err, "unable to get free TCP port")
}
defer l.Close()
_, randomPort, err := net.SplitHostPort(l.Addr().String())
if err != nil {
return 0, errors.Wrapf(err, "unable to determine free port")
}
rp, err := strconv.Atoi(randomPort)
if err != nil {
return 0, errors.Wrapf(err, "unable to convert random port to int")
}
return rp, nil
}

View File

@ -2,8 +2,6 @@ package specgen
import (
"net"
"github.com/cri-o/ocicni/pkg/ocicni"
)
// PodBasicConfig contains basic configuration options for pods.
@ -79,7 +77,7 @@ type PodNetworkConfig struct {
// container, this will forward the ports to the entire pod.
// Only available if NetNS is set to Bridge or Slirp.
// Optional.
PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"`
PortMappings []PortMapping `json:"portmappings,omitempty"`
// CNINetworks is a list of CNI networks that the infra container will
// join. As, by default, containers share their network with the infra
// container, these networks will effectively be joined by the

View File

@ -6,7 +6,6 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
@ -306,11 +305,23 @@ type ContainerNetworkConfig struct {
// PortBindings is a set of ports to map into the container.
// Only available if NetNS is set to bridge or slirp.
// Optional.
PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"`
// PublishImagePorts will publish ports specified in the image to random
// ports outside.
// Requires Image to be set.
PublishImagePorts bool `json:"publish_image_ports,omitempty"`
PortMappings []PortMapping `json:"portmappings,omitempty"`
// PublishExposedPorts will publish ports specified in the image to
// random unused ports (guaranteed to be above 1024) on the host.
// This is based on ports set in Expose below, and any ports specified
// by the Image (if one is given).
// Only available if NetNS is set to Bridge or Slirp.
PublishExposedPorts bool `json:"publish_image_ports,omitempty"`
// Expose is a number of ports that will be forwarded to the container
// if PublishExposedPorts is set.
// Expose is a map of uint16 (port number) to a string representing
// protocol. Allowed protocols are "tcp", "udp", and "sctp", or some
// combination of the three separated by commas.
// If protocol is set to "" we will assume TCP.
// Only available if NetNS is set to Bridge or Slirp, and
// PublishExposedPorts is set.
// Optional.
Expose map[uint16]string `json:"expose,omitempty"`
// CNINetworks is a list of CNI networks to join the container to.
// If this list is empty, the default CNI network will be joined
// instead. If at least one entry is present, we will not join the
@ -410,6 +421,35 @@ type NamedVolume struct {
Options []string
}
// PortMapping is one or more ports that will be mapped into the container.
type PortMapping struct {
// HostIP is the IP that we will bind to on the host.
// If unset, assumed to be 0.0.0.0 (all interfaces).
HostIP string `json:"host_ip,omitempty"`
// ContainerPort is the port number that will be exposed from the
// container.
// Mandatory.
ContainerPort uint16 `json:"container_port"`
// HostPort is the port number that will be forwarded from the host into
// the container.
// If omitted, will be assumed to be identical to
HostPort uint16 `json:"host_port,omitempty"`
// Range is the number of ports that will be forwarded, starting at
// HostPort and ContainerPort and counting up.
// This is 1-indexed, so 1 is assumed to be a single port (only the
// Hostport:Containerport mapping will be added), 2 is two ports (both
// Hostport:Containerport and Hostport+1:Containerport+1), etc.
// If unset, assumed to be 1 (a single port).
// Both hostport + range and containerport + range must be less than
// 65536.
Range uint16 `json:"range,omitempty"`
// Protocol is the protocol forward.
// Must be either "tcp", "udp", and "sctp", or some combination of these
// separated by commas.
// If unset, assumed to be TCP.
Protocol string `json:"protocol,omitempty"`
}
// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator {
csc := ContainerStorageConfig{}

View File

@ -19,7 +19,6 @@ var _ = Describe("Podman run networking", func() {
)
BeforeEach(func() {
Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
@ -65,6 +64,110 @@ var _ = Describe("Podman run networking", func() {
Expect(results.OutputToString()).To(ContainSubstring("223"))
})
It("podman run -p 80", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "80", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
})
It("podman run -p 8080:80", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "8080:80", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
})
It("podman run -p 80/udp", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "80/udp", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
})
It("podman run -p 127.0.0.1:8080:80", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1:8080:80", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1"))
})
It("podman run -p 127.0.0.1:8080:80/udp", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1:8080:80/udp", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1"))
})
It("podman run --expose 80 -P", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "--expose", "80", "-P", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Not(Equal(int32(0))))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
})
It("podman run --expose 80/udp -P", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "--expose", "80/udp", "-P", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Not(Equal(int32(0))))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
})
It("podman run --expose 80 -p 80", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "--expose", "80", "-p", "80", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
})
It("podman run network expose host port 80 to container port 8000", func() {
SkipIfRootless()
session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"})