From 946b4ced544e5988a971da12c7e34a684ab0e39d Mon Sep 17 00:00:00 2001 From: baude Date: Thu, 4 Jan 2018 12:59:33 -0600 Subject: Enable port bindings Set up nbetworking ports for the following use cases: * bind the same port between host and container * bind a specific host port to a different container port * bind a random host port to a specific container port Signed-off-by: baude Closes: #214 Approved by: baude --- cmd/podman/create.go | 48 ++++++++++++++++++++++++++++++++++++++++--- cmd/podman/spec.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 6 deletions(-) (limited to 'cmd/podman') diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 55425638f..28bd0a60e 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "net" "os" "strconv" "strings" @@ -287,15 +288,25 @@ func parseSecurityOpt(config *createConfig, securityOpts []string) error { return err } +// isPortInPortBindings determines if an exposed host port is in user +// provided ports +func isPortInPortBindings(pb map[nat.Port][]nat.PortBinding, port nat.Port) bool { + var hostPorts []string + for _, i := range pb { + hostPorts = append(hostPorts, i[0].HostPort) + } + return libpod.StringInSlice(port.Port(), hostPorts) +} + func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) { // TODO Handle exposed ports from image // Currently ignoring imageExposedPorts - - ports, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish")) + var ports map[nat.Port]struct{} + ports = make(map[nat.Port]struct{}) + _, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish")) if err != nil { return nil, nil, err } - for _, e := range c.StringSlice("expose") { // Merge in exposed ports to the map of published ports if strings.Contains(e, ":") { @@ -314,6 +325,28 @@ func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[na if err != nil { return nil, nil, err } + // check if the port in question is already being used + if isPortInPortBindings(portBindings, p) { + return nil, nil, errors.Errorf("host port %s already used in --publish option", p.Port()) + } + + if c.Bool("publish-all") { + l, err := net.Listen("tcp", ":0") + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to get free port") + } + _, randomPort, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to determine free port") + } + rp, err := strconv.Atoi(randomPort) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to convert random port to int") + } + logrus.Debug(fmt.Sprintf("Using random host port %s with container port %d", randomPort, p.Int())) + portBindings[p] = CreatePortBinding(rp, "") + continue + } if _, exists := ports[p]; !exists { ports[p] = struct{}{} } @@ -669,3 +702,12 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, } return config, nil } + +//CreatePortBinding takes port (int) and IP (string) and creates an array of portbinding structs +func CreatePortBinding(hostPort int, hostIP string) []nat.PortBinding { + pb := nat.PortBinding{ + HostPort: strconv.Itoa(hostPort), + } + pb.HostIP = hostIP + return []nat.PortBinding{pb} +} diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go index 152d1740c..cb9efdcb2 100644 --- a/cmd/podman/spec.go +++ b/cmd/podman/spec.go @@ -2,6 +2,7 @@ package main import ( "io/ioutil" + "strconv" "strings" "github.com/cri-o/ocicni/pkg/ocicni" @@ -543,6 +544,8 @@ func (c *createConfig) GetTmpfsMounts() []spec.Mount { func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption + var portBindings []ocicni.PortMapping + var err error // Uncomment after talking to mheon about unimplemented funcs // options = append(options, libpod.WithLabels(c.labels)) @@ -554,17 +557,25 @@ func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, er logrus.Debugf("appending name %s", c.Name) options = append(options, libpod.WithName(c.Name)) } - // TODO parse ports into libpod format and include + + // TODO deal with ports defined in image metadata + if len(c.PortBindings) > 0 || len(c.ExposedPorts) > 0 { + portBindings, err = c.CreatePortBindings() + if err != nil { + return nil, errors.Wrapf(err, "unable to create port bindings") + } + } + if c.NetMode.IsContainer() { connectedCtr, err := c.Runtime.LookupContainer(c.NetMode.ConnectedContainer()) if err != nil { return nil, errors.Wrapf(err, "container %q not found", c.NetMode.ConnectedContainer()) } - options = append(options, libpod.WithNetNSFrom(connectedCtr)) } else if !c.NetMode.IsHost() { - options = append(options, libpod.WithNetNS([]ocicni.PortMapping{})) + options = append(options, libpod.WithNetNS(portBindings)) } + if c.PidMode.IsContainer() { connectedCtr, err := c.Runtime.LookupContainer(c.PidMode.Container()) if err != nil { @@ -622,3 +633,43 @@ func makeThrottleArray(throttleInput []string) ([]spec.LinuxThrottleDevice, erro } return ltds, nil } + +// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands +func (c *createConfig) CreatePortBindings() ([]ocicni.PortMapping, error) { + var portBindings []ocicni.PortMapping + for containerPb, hostPb := range c.PortBindings { + 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") + } + } + + pm.HostPort = int32(hostPort) + // CNI requires us to make both udp and tcp structs + pm.Protocol = "udp" + portBindings = append(portBindings, pm) + pm.Protocol = "tcp" + portBindings = append(portBindings, pm) + } + } + for j := range c.ExposedPorts { + var expose ocicni.PortMapping + expose.HostPort = int32(j.Int()) + expose.ContainerPort = int32(j.Int()) + // CNI requires us to make both udp and tcp structs + expose.Protocol = "udp" + portBindings = append(portBindings, expose) + expose.Protocol = "tcp" + portBindings = append(portBindings, expose) + } + return portBindings, nil +} -- cgit v1.2.3-54-g00ecf