Skip to content

Commit

Permalink
Add a backend abstraction to the portmap plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Dan Winship <[email protected]>
  • Loading branch information
danwinship committed Aug 29, 2024
1 parent 28cffdc commit ec2b162
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 255 deletions.
49 changes: 37 additions & 12 deletions plugins/meta/portmap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ import (
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)

type PortMapper interface {
forwardPorts(config *PortMapConf, containerNet net.IPNet) error
checkPorts(config *PortMapConf, containerNet net.IPNet) error
unforwardPorts(config *PortMapConf) error
}

// PortMapEntry corresponds to a single entry in the port_mappings argument,
// see CONVENTIONS.md
type PortMapEntry struct {
Expand All @@ -51,16 +57,22 @@ type PortMapEntry struct {

type PortMapConf struct {
types.NetConf
SNAT *bool `json:"snat,omitempty"`
ConditionsV4 *[]string `json:"conditionsV4"`
ConditionsV6 *[]string `json:"conditionsV6"`
MasqAll bool `json:"masqAll,omitempty"`
MarkMasqBit *int `json:"markMasqBit"`
ExternalSetMarkChain *string `json:"externalSetMarkChain"`
RuntimeConfig struct {

mapper PortMapper

// Generic config
SNAT *bool `json:"snat,omitempty"`
ConditionsV4 *[]string `json:"conditionsV4"`
ConditionsV6 *[]string `json:"conditionsV6"`
MasqAll bool `json:"masqAll,omitempty"`
MarkMasqBit *int `json:"markMasqBit"`
RuntimeConfig struct {
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
} `json:"runtimeConfig,omitempty"`

// iptables-backend-specific config
ExternalSetMarkChain *string `json:"externalSetMarkChain"`

// These are fields parsed out of the config or the environment;
// included here for convenience
ContainerID string `json:"-"`
Expand Down Expand Up @@ -89,7 +101,7 @@ func cmdAdd(args *skel.CmdArgs) error {
netConf.ContainerID = args.ContainerID

if netConf.ContIPv4.IP != nil {
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
if err := netConf.mapper.forwardPorts(netConf, netConf.ContIPv4); err != nil {
return err
}
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
Expand All @@ -98,10 +110,21 @@ func cmdAdd(args *skel.CmdArgs) error {
if err := deletePortmapStaleConnections(netConf.RuntimeConfig.PortMaps, unix.AF_INET); err != nil {
log.Printf("failed to delete stale UDP conntrack entries for %s: %v", netConf.ContIPv4.IP, err)
}

if *netConf.SNAT {
// Set the route_localnet bit on the host interface, so that
// 127/8 can cross a routing boundary.
hostIfName := getRoutableHostIF(netConf.ContIPv4.IP)
if hostIfName != "" {
if err := enableLocalnetRouting(hostIfName); err != nil {
return fmt.Errorf("unable to enable route_localnet: %v", err)
}
}
}
}

if netConf.ContIPv6.IP != nil {
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
if err := netConf.mapper.forwardPorts(netConf, netConf.ContIPv6); err != nil {
return err
}
// Delete conntrack entries for UDP to avoid conntrack blackholing traffic
Expand Down Expand Up @@ -130,7 +153,7 @@ func cmdDel(args *skel.CmdArgs) error {

// We don't need to parse out whether or not we're using v6 or snat,
// deletion is idempotent
return unforwardPorts(netConf)
return netConf.mapper.unforwardPorts(netConf)
}

func main() {
Expand Down Expand Up @@ -161,13 +184,13 @@ func cmdCheck(args *skel.CmdArgs) error {
conf.ContainerID = args.ContainerID

if conf.ContIPv4.IP != nil {
if err := checkPorts(conf, conf.ContIPv4); err != nil {
if err := conf.mapper.checkPorts(conf, conf.ContIPv4); err != nil {
return err
}
}

if conf.ContIPv6.IP != nil {
if err := checkPorts(conf, conf.ContIPv6); err != nil {
if err := conf.mapper.checkPorts(conf, conf.ContIPv6); err != nil {
return err
}
}
Expand Down Expand Up @@ -197,6 +220,8 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
}
}

conf.mapper = &portMapperIPTables{}

if conf.SNAT == nil {
tvar := true
conf.SNAT = &tvar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"github.com/vishvananda/netlink"

"github.com/containernetworking/plugins/pkg/utils"
"github.com/containernetworking/plugins/pkg/utils/sysctl"
)

// This creates the chains to be added to iptables. The basic structure is
Expand All @@ -52,9 +51,11 @@ const (
OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
)

type portMapperIPTables struct{}

// forwardPorts establishes port forwarding to a given container IP.
// containerNet.IP can be either v4 or v6.
func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
func (*portMapperIPTables) forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
isV6 := (containerNet.IP.To4() == nil)

var ipt *iptables.IPTables
Expand Down Expand Up @@ -87,17 +88,6 @@ func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
return fmt.Errorf("unable to create chain %s: %v", setMarkChain.name, err)
}
}

if !isV6 {
// Set the route_localnet bit on the host interface, so that
// 127/8 can cross a routing boundary.
hostIfName := getRoutableHostIF(containerNet.IP)
if hostIfName != "" {
if err := enableLocalnetRouting(hostIfName); err != nil {
return fmt.Errorf("unable to enable route_localnet: %v", err)
}
}
}
}

// Generate the DNAT (actual port forwarding) rules
Expand All @@ -117,7 +107,7 @@ func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
return nil
}

func checkPorts(config *PortMapConf, containerNet net.IPNet) error {
func (*portMapperIPTables) checkPorts(config *PortMapConf, containerNet net.IPNet) error {
isV6 := (containerNet.IP.To4() == nil)
dnatChain := genDnatChain(config.Name, config.ContainerID)
fillDnatRules(&dnatChain, config, containerNet)
Expand Down Expand Up @@ -344,14 +334,6 @@ func genMarkMasqChain(markBit int) chain {
return ch
}

// enableLocalnetRouting tells the kernel not to treat 127/8 as a martian,
// so that connections with a source ip of 127/8 can cross a routing boundary.
func enableLocalnetRouting(ifName string) error {
routeLocalnetPath := "net/ipv4/conf/" + ifName + "/route_localnet"
_, err := sysctl.Sysctl(routeLocalnetPath, "1")
return err
}

// genOldSnatChain is no longer used, but used to be created. We'll try and
// tear it down in case the plugin version changed between ADD and DEL
func genOldSnatChain(netName, containerID string) chain {
Expand All @@ -372,7 +354,7 @@ func genOldSnatChain(netName, containerID string) chain {
// don't know which protocols were used.
// So, we first check that iptables is "generally OK" by doing a check. If
// not, we ignore the error, unless neither v4 nor v6 are OK.
func unforwardPorts(config *PortMapConf) error {
func (*portMapperIPTables) unforwardPorts(config *PortMapConf) error {
dnatChain := genDnatChain(config.Name, config.ContainerID)

// Might be lying around from old versions
Expand Down
Loading

0 comments on commit ec2b162

Please sign in to comment.