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 <bbaude@redhat.com>

Closes: #214
Approved by: baude
This commit is contained in:
baude
2018-01-04 12:59:33 -06:00
committed by Atomic Bot
parent 67f06cf1cf
commit 946b4ced54
10 changed files with 153 additions and 25 deletions

View File

@ -16,6 +16,11 @@ tests:
packages:
- containernetworking-cni
extra-repos:
- name: updatestesting
baseurl: http://download.fedoraproject.org/pub/fedora/linux/updates/testing/27/x86_64/
---
inherit: true

View File

@ -68,7 +68,7 @@ RUN set -x \
&& rm -rf "$GOPATH"
# Install CNI plugins
ENV CNI_COMMIT dcf7368eeab15e2affc6256f0bb1e84dd46a34de
ENV CNI_COMMIT 7480240de9749f9a0a5c8614b17f1f03e0c06ab9
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/containernetworking/plugins.git "$GOPATH/src/github.com/containernetworking/plugins" \

View File

@ -157,7 +157,7 @@ install.completions:
install ${SELINUXOPT} -m 644 -D completions/bash/podman ${BASHINSTALLDIR}
install.cni:
install ${SELINUXOPT} -D -m 644 cni/97-podman-bridge.conf ${ETCDIR}/cni/net.d/97-podman-bridge.conf
install ${SELINUXOPT} -D -m 644 cni/87-podman-bridge.conflist ${ETCDIR}/cni/net.d/87-podman-bridge.conflist
install.docker: docker-docs
install ${SELINUXOPT} -D -m 755 docker $(BINDIR)/docker

View File

@ -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}
}

View File

@ -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
}

View File

@ -0,0 +1,25 @@
{
"cniVersion": "0.3.0",
"name": "podman",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}

View File

@ -1,15 +0,0 @@
{
"cniVersion": "0.3.0",
"name": "podman",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}

View File

@ -35,7 +35,6 @@ func (r *Runtime) createNetNS(ctr *Container) (err error) {
}()
logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID())
podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.PortMappings)
_, err = r.netPlugin.SetUpPod(podNetwork)

View File

@ -584,7 +584,7 @@ func WithNetNS(portMappings []ocicni.PortMapping) CtrCreateOption {
}
ctr.config.CreateNetNS = true
copy(ctr.config.PortMappings, portMappings)
ctr.config.PortMappings = portMappings
return nil
}

View File

@ -27,3 +27,24 @@ function setup() {
echo "$output"
[ "$status" -eq 0 ]
}
@test "expose port 222" {
run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt --expose 222-223 ${ALPINE} /bin/sh
echo "$output"
[ "$status" -eq 0 ]
run bash -c "iptables -t nat -L"
echo "$output"
[ "$status" -eq 0 ]
run bash -c "iptables -t nat -L | grep 223"
echo "$output"
[ "$status" -eq 0 ]
}
@test "expose host port 80 to container port 8000" {
run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt -p 80:8000 ${ALPINE} /bin/sh
echo "$output"
[ "$status" -eq 0 ]
run bash -c "iptables -t nat -L | grep 8000"
echo "$output"
[ "$status" -eq 0 ]
}