Skip to content

Commit

Permalink
CRI: Allow the pod sandbox to accept port forwards
Browse files Browse the repository at this point in the history
Fixes: rkt#3156
  • Loading branch information
squeed authored and Sergiusz Urbaniak committed Oct 25, 2016
1 parent 3d1c6e1 commit c37e6b8
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 50 deletions.
17 changes: 17 additions & 0 deletions common/networking/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2016 The rkt Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// networking is the package that implements small functionality shared
// between state0 and stage1.
package networking
119 changes: 119 additions & 0 deletions common/networking/ports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2016 The rkt Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package networking

import (
"fmt"
"net"

"github.com/appc/spec/schema"
"github.com/appc/spec/schema/types"
)

// ForwardedPort describes a port that will be
// forwarded (mapped) from the host to the pod
type ForwardedPort struct {
PodPort types.Port
HostPort types.ExposedPort
}

// findAppPort looks through the manifest to find a port with a given name.
// If multiple apps expose the same port name, it will fail
func findAppPort(manifest *schema.PodManifest, portName types.ACName) (*types.Port, error) {
var foundPort *types.Port

for _, app := range manifest.Apps {
for _, port := range app.App.Ports {
if portName == port.Name {
if foundPort != nil { // error: ambiguous
return nil, fmt.Errorf("port name %q defined multiple apps", portName)
}
p := port // duplicate b/c port gets overwritten
foundPort = &p
}
}
}
return foundPort, nil
}

// ForwardedPorts matches up ExposedPorts (host ports) with Ports on the app side.
// By default, it tries to match up by name - apps expose ports, and the podspec
// maps them. The podspec can also map from host to pod, without a corresponding app
// (which is needed for CRI)
// This will error if:
// - a name is ambiguous
// - the same port:proto combination is forwarded
func ForwardedPorts(manifest *schema.PodManifest) ([]ForwardedPort, error) {
var fps []ForwardedPort
var err error

// For every ExposedPort, find its corresponding PodPort
for _, ep := range manifest.Ports {
podPort := ep.PodPort

// If there is no direct mapping, search for the port by name
if podPort == nil {
podPort, err = findAppPort(manifest, ep.Name)
if err != nil {
return nil, err
}
if podPort == nil {
return nil, fmt.Errorf("port name %q could not be found in any apps", ep.Name)
}
}
fp := ForwardedPort{
HostPort: ep,
PodPort: *podPort,
}
fp.HostPort.PodPort = &fp.PodPort
if fp.HostPort.HostIP == nil {
fp.HostPort.HostIP = net.IPv4(0, 0, 0, 0)
}

// Check all already-existing ports for conflicts
for idx := range fps {
if fp.conflicts(&fps[idx]) {
return nil, fmt.Errorf("port %s-%s:%d already mapped to pod port %d",
fp.PodPort.Protocol, fp.HostPort.HostIP.String(), fp.HostPort.HostPort, fps[idx].PodPort.Port)
}
}

fps = append(fps, fp)
}
return fps, nil
}

// conflicts checks if two ports conflict with each other
func (fp *ForwardedPort) conflicts(fp1 *ForwardedPort) bool {
if fp.PodPort.Protocol != fp1.PodPort.Protocol {
return false
}

if fp.HostPort.HostPort != fp1.HostPort.HostPort {
return false
}

// If either port has the 0.0.0.0 address, they conflict
zeroAddr := net.IPv4(0, 0, 0, 0)
if fp.HostPort.HostIP.Equal(zeroAddr) || fp1.HostPort.HostIP.Equal(zeroAddr) {
return true
}

if fp.HostPort.HostIP.Equal(fp1.HostPort.HostIP) {
return true
}

return false
}
10 changes: 8 additions & 2 deletions networking/kvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/vishvananda/netlink"

"github.com/coreos/rkt/common"
commonnet "github.com/coreos/rkt/common/networking"
"github.com/coreos/rkt/networking/tuntap"
)

Expand Down Expand Up @@ -433,7 +434,7 @@ func kvmTransformFlannelNetwork(net *activeNet) error {

// kvmSetup prepare new Networking to be used in kvm environment based on tuntap pair interfaces
// to allow communication with virtual machine created by lkvm tool
func kvmSetup(podRoot string, podID types.UUID, fps []ForwardedPort, netList common.NetList, localConfig string, noDNS bool) (*Networking, error) {
func kvmSetup(podRoot string, podID types.UUID, fps []commonnet.ForwardedPort, netList common.NetList, localConfig string, noDNS bool) (*Networking, error) {
network := Networking{
podEnv: podEnv{
podRoot: podRoot,
Expand Down Expand Up @@ -628,7 +629,12 @@ func kvmSetup(podRoot string, podID types.UUID, fps []ForwardedPort, netList com
if err != nil {
return nil, err
}
if err := network.setupForwarding(); err != nil {
network.teardownForwarding()
return nil, err
}
if err := network.forwardPorts(fps, podIP); err != nil {
network.teardownForwarding()
return nil, err
}

Expand Down Expand Up @@ -698,7 +704,7 @@ func (n *Networking) teardownKvmNets() {
// similar to Networking.Teardown but without host namespaces
func (n *Networking) kvmTeardown() {

if err := n.unforwardPorts(); err != nil {
if err := n.teardownForwarding(); err != nil {
stderr.PrintE("error removing forwarded ports (kvm)", err)
}
n.teardownKvmNets()
Expand Down
11 changes: 8 additions & 3 deletions networking/networking.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/vishvananda/netlink"

"github.com/coreos/rkt/common"
commonnet "github.com/coreos/rkt/common/networking"
"github.com/coreos/rkt/networking/netinfo"
"github.com/coreos/rkt/pkg/log"

Expand Down Expand Up @@ -62,7 +63,7 @@ var stderr *log.Logger

// Setup creates a new networking namespace and executes network plugins to
// set up networking. It returns in the new pod namespace
func Setup(podRoot string, podID types.UUID, fps []ForwardedPort, netList common.NetList, localConfig, flavor string, noDNS, debug bool) (*Networking, error) {
func Setup(podRoot string, podID types.UUID, fps []commonnet.ForwardedPort, netList common.NetList, localConfig, flavor string, noDNS, debug bool) (*Networking, error) {

stderr = log.New(os.Stderr, "networking", debug)

Expand Down Expand Up @@ -105,8 +106,12 @@ func Setup(podRoot string, podID types.UUID, fps []ForwardedPort, netList common
if err != nil {
return nil, err
}
if err := n.setupForwarding(); err != nil {
n.teardownForwarding()
return nil, err
}
if err := n.forwardPorts(fps, podIP); err != nil {
n.unforwardPorts()
n.teardownForwarding()
return nil, err
}
}
Expand Down Expand Up @@ -269,7 +274,7 @@ func (n *Networking) Teardown(flavor string, debug bool) {
return
}

if err := n.unforwardPorts(); err != nil {
if err := n.teardownForwarding(); err != nil {
stderr.PrintE("error removing forwarded ports", err)
}

Expand Down
129 changes: 84 additions & 45 deletions networking/portfwd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ import (
"strconv"

"github.com/coreos/go-iptables/iptables"

commonnet "github.com/coreos/rkt/common/networking"
)

// ForwardedPort describes a port that will be
// forwarded (mapped) from the host to the pod
type ForwardedPort struct {
Protocol string
HostPort uint
PodPort uint
type iptablesRule struct {
Chain string
Rule []string
}

// GetForwardableNet iterates through all loaded networks and returns either
Expand Down Expand Up @@ -67,11 +66,8 @@ func (n *Networking) GetForwardableNetHostIP() (net.IP, error) {
return net.runtime.HostIP, nil
}

func (e *podEnv) forwardPorts(fps []ForwardedPort, podIP net.IP) error {
if len(fps) == 0 {
return nil
}

// setupForwarding creates the iptables chains
func (e *podEnv) setupForwarding() error {
ipt, err := iptables.New()
if err != nil {
return err
Expand All @@ -97,9 +93,9 @@ func (e *podEnv) forwardPorts(fps []ForwardedPort, podIP net.IP) error {
chain string
customChainRule []string
}{
{"POSTROUTING", chainRuleSNAT}, // traffic originating from this host
{"POSTROUTING", chainRuleSNAT}, // traffic originating from this host from loopback
{"PREROUTING", chainRuleDNAT}, // outside traffic hitting this host
{"OUTPUT", chainRuleDNAT}, // traffic originating from this host
{"OUTPUT", chainRuleDNAT}, // traffic originating from this host on non-loopback
} {
exists, err := ipt.Exists("nat", entry.chain, entry.customChainRule...)
if err != nil {
Expand All @@ -112,46 +108,87 @@ func (e *podEnv) forwardPorts(fps []ForwardedPort, podIP net.IP) error {
}
}
}
return nil
}

for _, p := range fps {

socketPod := fmt.Sprintf("%v:%v", podIP, p.PodPort)
dstPortHost := strconv.Itoa(int(p.HostPort))
dstPortPod := strconv.Itoa(int(p.PodPort))

for _, r := range []struct {
chain string
rule []string
}{
{ // Rewrite the destination
chainDNAT,
[]string{
"-p", p.Protocol,
"--dport", dstPortHost,
"-j", "DNAT",
"--to-destination", socketPod,
},
},
{ // Rewrite the source for connections to localhost on the host
chainSNAT,
[]string{
"-p", p.Protocol,
"-s", "127.0.0.1",
"-d", podIP.String(),
"--dport", dstPortPod,
"-j", "MASQUERADE",
},
},
} {
if err := ipt.AppendUnique("nat", r.chain, r.rule...); err != nil {
func (e *podEnv) forwardPorts(fps []commonnet.ForwardedPort, podIP net.IP) error {
if len(fps) == 0 {
return nil
}
ipt, err := iptables.New()
if err != nil {
return err
}
chainDNAT := e.portFwdChain("DNAT")
chainSNAT := e.portFwdChain("SNAT")

for _, fp := range fps {
for _, r := range portRules(fp, podIP, chainDNAT, chainSNAT) {
if err := ipt.AppendUnique("nat", r.Chain, r.Rule...); err != nil {
return err
}
}
}
return nil
}

func (e *podEnv) unforwardPorts() error {
func (e *podEnv) unforwardPorts(fps []commonnet.ForwardedPort, podIP net.IP) error {
if len(fps) == 0 {
return nil
}

ipt, err := iptables.New()
if err != nil {
return err
}
chainDNAT := e.portFwdChain("DNAT")
chainSNAT := e.portFwdChain("SNAT")

for _, fp := range fps {
for _, r := range portRules(fp, podIP, chainDNAT, chainSNAT) {
if err := ipt.Delete("nat", r.Chain, r.Rule...); err != nil {
return err
}
}
}
return nil
}

func portRules(fp commonnet.ForwardedPort, podIP net.IP, chainDNAT, chainSNAT string) []iptablesRule {
socketPod := fmt.Sprintf("%v:%v", podIP, fp.PodPort.Port)
dstPortHost := strconv.Itoa(int(fp.HostPort.HostPort))
dstPortPod := strconv.Itoa(int(fp.PodPort.Port))
dstIPHost := fp.HostPort.HostIP.String()

if fp.HostPort.HostIP == nil || dstIPHost == "0.0.0.0" {
dstIPHost = "0.0.0.0/0"
}

return []iptablesRule{
{ // nat the destination
chainDNAT,
[]string{
"-d", dstIPHost,
"-p", fp.PodPort.Protocol,
"--dport", dstPortHost,
"-j", "DNAT",
"--to-destination", socketPod,
},
},
{ // Rewrite the source for connections to localhost on the host
chainSNAT,
[]string{
"-p", fp.PodPort.Protocol,
"-s", "127.0.0.1",
"-d", podIP.String(),
"--dport", dstPortPod,
"-j", "MASQUERADE",
},
},
}
}

func (e *podEnv) teardownForwarding() error {
ipt, err := iptables.New()
if err != nil {
return err
Expand Down Expand Up @@ -187,6 +224,8 @@ func (e *podEnv) unforwardPorts() error {
return nil
}

// portFwdChain generates the *name* of the chain for pod port forwarding.
// This name must be stable.
func (e *podEnv) portFwdChain(name string) string {
return fmt.Sprintf("RKT-PFWD-%s-%s", name, e.podID.String()[0:8])
}
Expand Down

0 comments on commit c37e6b8

Please sign in to comment.