Repository: cilium/cilium
Pull request #12816 (2020-08-07 13:58:18)

v1.8 backports 2020-08-07
 * #12567 -- Ignore collisions for named ports that are not actually used in an egress policy (@jrajahalme)
* #12688 -- Cleanup unused methods and fields in pkg/policy (@aanm)
 * #12801 -- fqdn/dnsproxy: set SO_REUSEPORT on listening socket (@tklauser)

Once this PR is merged, you can update the PR labels via:
```upstream-prs
$ for pr in 12567 12688 12801; do contrib/backporting/set-labels.py $pr done 1.8; done
```


jrajahalme: test-backport-1.8
tklauser: Failure on Travis CI looks real:

```
# github.com/cilium/cilium/pkg/proxy
vet: pkg/proxy/kafka_test.go:112:45: cannot use &(proxyUpdaterMock literal) (value of type *proxyUpdaterMock) as logger.EndpointUpdater value in variable declaration: missing method GetNamedPortLocked
```

https://travis-ci.com/github/cilium/cilium/jobs/369729475

Patch:
diff --git a/.github/workflows/go-check.yaml b/.github/workflows/go-check.yaml
index 78ce040265..dc1a72966b 100644
--- a/.github/workflows/go-check.yaml
+++ b/.github/workflows/go-check.yaml
@@ -13,7 +13,7 @@ jobs:
       - name: Install Go
         uses: actions/setup-go@v1
         with:
-          go-version: 1.14.6
+          go-version: 1.14.7
       - name: Checkout code
         uses: actions/checkout@v2
       - name: Check module vendoring
@@ -28,7 +28,7 @@ jobs:
       - name: Install Go
         uses: actions/setup-go@v1
         with:
-          go-version: 1.14.6
+          go-version: 1.14.7
       - name: Checkout code
         uses: actions/checkout@v2
         with:
diff --git a/.travis.yml b/.travis.yml
index cf4e04b758..bd5373ae9f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ language: go
 dist: bionic
 sudo: required
 
-go: "1.14.6"
+go: "1.14.7"
 
 arch:
   - amd64
diff --git a/Dockerfile b/Dockerfile
index 7256e9aa86..453aecd88e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -23,7 +23,7 @@ LABEL cilium-sha=${CILIUM_SHA}
 # versions to be built while allowing the new versions to make changes
 # that are not backwards compatible.
 #
-FROM quay.io/cilium/cilium-builder:2020-07-20-v1.8@sha256:2391cd7bb88718735de64c8d46a8e4e972c433c14bcf4da8da0e7be1a746c47f as builder
+FROM quay.io/cilium/cilium-builder:2020-08-07-v1.8@sha256:bf609a2e415dfd6d583ae7a4fe15627210d8963143008a3497916dff6dd597bb as builder
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 LABEL maintainer="maintainer@cilium.io"
@@ -50,7 +50,7 @@ RUN make NOSTRIP=$NOSTRIP LOCKDEBUG=$LOCKDEBUG PKG_BUILD=1 V=$V LIBNETWORK_PLUGI
 # built while allowing the new versions to make changes that are not
 # backwards compatible.
 #
-FROM quay.io/cilium/cilium-runtime:2020-07-20-v1.8@sha256:919b0084a343431d695e8f1f6abebb6040f620c28d7a9982bc5b653ccaadf67e
+FROM quay.io/cilium/cilium-runtime:2020-08-07-v1.8@sha256:2d1937ce4a9038b55e2159cbeb9fd826cc37acadb520d3d42ab059eaab1be472
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 LABEL maintainer="maintainer@cilium.io"
diff --git a/Dockerfile.builder b/Dockerfile.builder
index 8243272e56..27d49ad368 100644
--- a/Dockerfile.builder
+++ b/Dockerfile.builder
@@ -1,7 +1,7 @@
 #
 # Cilium build-time base image (image created from this file is used to build Cilium)
 #
-FROM quay.io/cilium/cilium-runtime:2020-07-20-v1.8@sha256:919b0084a343431d695e8f1f6abebb6040f620c28d7a9982bc5b653ccaadf67e
+FROM quay.io/cilium/cilium-runtime:2020-08-07-v1.8@sha256:2d1937ce4a9038b55e2159cbeb9fd826cc37acadb520d3d42ab059eaab1be472
 LABEL maintainer="maintainer@cilium.io"
 ARG ARCH=amd64
 WORKDIR /go/src/github.com/cilium/cilium
@@ -12,7 +12,7 @@ WORKDIR /go/src/github.com/cilium/cilium
 ENV GOROOT /usr/local/go
 ENV GOPATH /go
 ENV PATH "$GOROOT/bin:$GOPATH/bin:$PATH"
-ENV GO_VERSION 1.14.6
+ENV GO_VERSION 1.14.7
 
 #
 # Build dependencies
diff --git a/GO_VERSION b/GO_VERSION
index c6ba3bc1a4..52e779f28f 100644
--- a/GO_VERSION
+++ b/GO_VERSION
@@ -1,1 +1,1 @@
-1.14.6
+1.14.7
diff --git a/cilium-dev.Dockerfile b/cilium-dev.Dockerfile
index 49071eaf60..74d510757a 100644
--- a/cilium-dev.Dockerfile
+++ b/cilium-dev.Dockerfile
@@ -3,7 +3,7 @@
 # development environmennt
 FROM quay.io/cilium/cilium-envoy:63de0bd958d05d82e2396125dcf6286d92464c56 as cilium-envoy
 
-FROM quay.io/cilium/cilium-runtime:2020-07-20-v1.8@sha256:919b0084a343431d695e8f1f6abebb6040f620c28d7a9982bc5b653ccaadf67e
+FROM quay.io/cilium/cilium-runtime:2020-08-07-v1.8@sha256:2d1937ce4a9038b55e2159cbeb9fd826cc37acadb520d3d42ab059eaab1be472
 LABEL maintainer="maintainer@cilium.io"
 RUN apt-get update && apt-get install make -y
 WORKDIR /go/src/github.com/cilium/cilium
diff --git a/cilium-docker-plugin.Dockerfile b/cilium-docker-plugin.Dockerfile
index 92ab423a13..535037a338 100644
--- a/cilium-docker-plugin.Dockerfile
+++ b/cilium-docker-plugin.Dockerfile
@@ -1,6 +1,6 @@
 # (first line comment needed for DOCKER_BUILDKIT use)
 #
-FROM docker.io/library/golang:1.14.6 as builder
+FROM docker.io/library/golang:1.14.7 as builder
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 LABEL maintainer="maintainer@cilium.io"
diff --git a/cilium-operator-aws.Dockerfile b/cilium-operator-aws.Dockerfile
index 6a50a5505f..b0cd5e52d5 100644
--- a/cilium-operator-aws.Dockerfile
+++ b/cilium-operator-aws.Dockerfile
@@ -1,6 +1,6 @@
 # (first line comment needed for DOCKER_BUILDKIT use)
 #
-FROM docker.io/library/golang:1.14.6 as builder
+FROM docker.io/library/golang:1.14.7 as builder
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 LABEL maintainer="maintainer@cilium.io"
@@ -17,7 +17,7 @@ ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN apk --update add ca-certificates
 
-FROM docker.io/library/golang:1.14.6 as gops
+FROM docker.io/library/golang:1.14.7 as gops
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN go get -d github.com/google/gops && \
diff --git a/cilium-operator-azure.Dockerfile b/cilium-operator-azure.Dockerfile
index 5e2414d245..6e32a1f9cf 100644
--- a/cilium-operator-azure.Dockerfile
+++ b/cilium-operator-azure.Dockerfile
@@ -1,6 +1,6 @@
 # (first line comment needed for DOCKER_BUILDKIT use)
 #
-FROM docker.io/library/golang:1.14.6 as builder
+FROM docker.io/library/golang:1.14.7 as builder
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 LABEL maintainer="maintainer@cilium.io"
@@ -17,7 +17,7 @@ ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN apk --update add ca-certificates
 
-FROM docker.io/library/golang:1.14.6 as gops
+FROM docker.io/library/golang:1.14.7 as gops
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN go get -d github.com/google/gops && \
diff --git a/cilium-operator-generic.Dockerfile b/cilium-operator-generic.Dockerfile
index 1e5ed58bf3..f002728858 100644
--- a/cilium-operator-generic.Dockerfile
+++ b/cilium-operator-generic.Dockerfile
@@ -1,6 +1,6 @@
 # (first line comment needed for DOCKER_BUILDKIT use)
 #
-FROM docker.io/library/golang:1.14.6 as builder
+FROM docker.io/library/golang:1.14.7 as builder
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 LABEL maintainer="maintainer@cilium.io"
@@ -17,7 +17,7 @@ ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN apk --update add ca-certificates
 
-FROM docker.io/library/golang:1.14.6 as gops
+FROM docker.io/library/golang:1.14.7 as gops
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN go get -d github.com/google/gops && \
diff --git a/cilium-operator.Dockerfile b/cilium-operator.Dockerfile
index 8a13d42c24..07e8da5ea0 100644
--- a/cilium-operator.Dockerfile
+++ b/cilium-operator.Dockerfile
@@ -1,6 +1,6 @@
 # (first line comment needed for DOCKER_BUILDKIT use)
 #
-FROM docker.io/library/golang:1.14.6 as builder
+FROM docker.io/library/golang:1.14.7 as builder
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 LABEL maintainer="maintainer@cilium.io"
@@ -17,7 +17,7 @@ ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN apk --update add ca-certificates
 
-FROM docker.io/library/golang:1.14.6 as gops
+FROM docker.io/library/golang:1.14.7 as gops
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN go get -d github.com/google/gops && \
diff --git a/contrib/packaging/docker/Dockerfile.runtime b/contrib/packaging/docker/Dockerfile.runtime
index 569dc8a46d..f8e9752d3a 100644
--- a/contrib/packaging/docker/Dockerfile.runtime
+++ b/contrib/packaging/docker/Dockerfile.runtime
@@ -28,7 +28,7 @@ RUN update-alternatives \
 #
 # Go-based tools for runtime image
 #
-FROM docker.io/library/golang:1.14.6 as gops
+FROM docker.io/library/golang:1.14.7 as gops
 WORKDIR /tmp
 RUN go get -d github.com/google/gops && \
     cd /go/src/github.com/google/gops && \
diff --git a/hubble-relay.Dockerfile b/hubble-relay.Dockerfile
index 59818d76a8..d4db38b1a9 100644
--- a/hubble-relay.Dockerfile
+++ b/hubble-relay.Dockerfile
@@ -1,6 +1,6 @@
 # (first line comment needed for DOCKER_BUILDKIT use)
 #
-FROM docker.io/library/golang:1.14.6 as builder
+FROM docker.io/library/golang:1.14.7 as builder
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 ADD . /go/src/github.com/cilium/cilium
@@ -13,7 +13,7 @@ ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN apk --update add ca-certificates
 
-FROM docker.io/library/golang:1.14.6 as gops
+FROM docker.io/library/golang:1.14.7 as gops
 ARG CILIUM_SHA=""
 LABEL cilium-sha=${CILIUM_SHA}
 RUN go get -d github.com/google/gops && \
diff --git a/pkg/checker/checker.go b/pkg/checker/checker.go
index e5f99a5484..46154bea11 100644
--- a/pkg/checker/checker.go
+++ b/pkg/checker/checker.go
@@ -15,6 +15,7 @@
 package checker
 
 import (
+	"fmt"
 	"reflect"
 	"regexp"
 
@@ -172,3 +173,42 @@ func (checker *matchesChecker) Check(params []interface{}, _ []string) (result b
 	}
 	return matches, ""
 }
+
+// -----------------------------------------------------------------------
+// HasKey checker.
+
+type hasKeyChecker struct {
+	*check.CheckerInfo
+}
+
+// The HasKey checker verifies that the obtained map contains the
+// provided key.
+//
+// For example:
+//
+//     c.Assert(myMap, HasKey, "five")
+//
+var HasKey check.Checker = &hasKeyChecker{
+	&check.CheckerInfo{Name: "HasKey", Params: []string{"map", "key"}},
+}
+
+func (checker *hasKeyChecker) Check(params []interface{}, names []string) (result bool, error string) {
+	m := reflect.ValueOf(params[0])
+	mType := m.Type()
+	key := reflect.ValueOf(params[1])
+	keyType := key.Type()
+
+	if mType.Kind() != reflect.Map {
+		return false, fmt.Sprintf("'%s' must be a map", names[0])
+	}
+	if mType.Key() != keyType {
+		return false, fmt.Sprintf("'%s' must be of '%s's key type (%s, not %s)",
+			names[1], names[0], mType.Key(), keyType)
+	}
+	for _, v := range m.MapKeys() {
+		if v.Interface() == key.Interface() {
+			return true, ""
+		}
+	}
+	return false, fmt.Sprintf("'%s' has no key %v", names[0], key.Interface())
+}
diff --git a/pkg/endpoint/bpf.go b/pkg/endpoint/bpf.go
index b9c8fd0591..fa8ba0245e 100644
--- a/pkg/endpoint/bpf.go
+++ b/pkg/endpoint/bpf.go
@@ -211,15 +211,18 @@ func (e *Endpoint) addNewRedirectsFromDesiredPolicy(ingress bool, desiredRedirec
 				var finalizeFunc revert.FinalizeFunc
 				var revertFunc revert.RevertFunc
 
-				proxyID, err := e.ProxyID(e.desiredPolicy.NamedPortsMap, l4)
-				if err != nil {
-					// skip redirects for which a proxyID cannot be created.
-					// Right now this happens only due to named port mapping not existing and
-					// we'll be called again when the mapping is available.
-					log.WithError(err).WithField(logfields.EndpointID, e.ID).Warning("Skipping adding redirect")
+				proxyID := e.proxyID(l4)
+				if proxyID == "" {
+					// Skip redirects for which a proxyID cannot be created.
+					// This may happen due to the named port mapping not
+					// existing or multiple PODs defining the same port name
+					// with different port values. The redirect will be created
+					// when the mapping is available or when the port name
+					// conflicts have been resolved in POD specs.
 					continue
 				}
 
+				var err error
 				redirectPort, err, finalizeFunc, revertFunc = e.proxy.CreateOrUpdateRedirect(l4, proxyID, e, proxyWaitGroup)
 				if err != nil {
 					revertStack.Revert() // Ignore errors while reverting. This is best-effort.
@@ -259,7 +262,7 @@ func (e *Endpoint) addNewRedirectsFromDesiredPolicy(ingress bool, desiredRedirec
 				direction = trafficdirection.Egress
 			}
 
-			keysFromFilter := l4.ToMapState(e.desiredPolicy.NamedPortsMap, direction)
+			keysFromFilter := l4.ToMapState(e, direction)
 
 			for keyFromFilter, entry := range keysFromFilter {
 				if oldEntry, ok := e.desiredPolicy.PolicyMapState[keyFromFilter]; ok {
diff --git a/pkg/endpoint/endpoint.go b/pkg/endpoint/endpoint.go
index fa5ad342ab..6095a0c851 100644
--- a/pkg/endpoint/endpoint.go
+++ b/pkg/endpoint/endpoint.go
@@ -47,6 +47,7 @@ import (
 	"github.com/cilium/cilium/pkg/labels"
 	pkgLabels "github.com/cilium/cilium/pkg/labels"
 	"github.com/cilium/cilium/pkg/lock"
+	"github.com/cilium/cilium/pkg/logging"
 	"github.com/cilium/cilium/pkg/logging/logfields"
 	"github.com/cilium/cilium/pkg/mac"
 	"github.com/cilium/cilium/pkg/maps/ctmap"
@@ -224,7 +225,10 @@ type Endpoint struct {
 
 	// k8sPorts contains container ports associated in the pod.
 	// It is used to enforce k8s network policies with port names.
-	k8sPorts policy.NamedPortsMap
+	k8sPorts policy.NamedPortMap
+
+	// logLimiter rate limits potentially repeating warning logs
+	logLimiter logging.Limiter
 
 	// k8sPortsSet keep track when k8sPorts was set at least one time.
 	hasK8sMetadata bool
@@ -376,18 +380,6 @@ func (e *Endpoint) HasIpvlanDataPath() bool {
 	return false
 }
 
-// GetIngressPolicyEnabledLocked returns whether ingress policy enforcement is
-// enabled for endpoint or not. The endpoint's mutex must be held.
-func (e *Endpoint) GetIngressPolicyEnabledLocked() bool {
-	return e.desiredPolicy.IngressPolicyEnabled
-}
-
-// GetEgressPolicyEnabledLocked returns whether egress policy enforcement is
-// enabled for endpoint or not. The endpoint's mutex must be held.
-func (e *Endpoint) GetEgressPolicyEnabledLocked() bool {
-	return e.desiredPolicy.EgressPolicyEnabled
-}
-
 // waitForProxyCompletions blocks until all proxy changes have been completed.
 // Called with buildMutex held.
 func (e *Endpoint) waitForProxyCompletions(proxyWaitGroup *completion.WaitGroup) error {
@@ -441,6 +433,7 @@ func createEndpoint(owner regeneration.Owner, proxy EndpointProxy, allocator cac
 		controllers:     controller.NewManager(),
 		regenFailedChan: make(chan struct{}, 1),
 		allocator:       allocator,
+		logLimiter:      logging.NewLimiter(10*time.Second, 3), // 1 log / 10 secs, burst of 3
 	}
 
 	ctx, cancel := context.WithCancel(context.Background())
@@ -1148,7 +1141,7 @@ func (e *Endpoint) SetK8sNamespace(name string) {
 // Reading the 'e.k8sPorts' member (the "map pointer") *itself* requires the endpoint lock!
 // Can't really error out as that might break backwards compatibility.
 func (e *Endpoint) SetK8sMetadata(containerPorts []slim_corev1.ContainerPort) error {
-	k8sPorts := make(policy.NamedPortsMap, len(containerPorts))
+	k8sPorts := make(policy.NamedPortMap, len(containerPorts))
 	for _, cp := range containerPorts {
 		if cp.Name == "" {
 			continue // silently skip unnamed ports
@@ -1169,6 +1162,17 @@ func (e *Endpoint) SetK8sMetadata(containerPorts []slim_corev1.ContainerPort) er
 	return nil
 }
 
+// GetK8sPorts returns the k8sPorts, which must not be modified by the caller
+func (e *Endpoint) GetK8sPorts() (k8sPorts policy.NamedPortMap, err error) {
+	err = e.rlockAlive()
+	if err != nil {
+		return nil, err
+	}
+	k8sPorts = e.k8sPorts
+	e.mutex.RUnlock()
+	return k8sPorts, nil
+}
+
 // HaveK8sMetadata returns true once hasK8sMetadata was set
 func (e *Endpoint) HaveK8sMetadata() (metadataSet bool) {
 	e.mutex.RLock()
diff --git a/pkg/endpoint/policy.go b/pkg/endpoint/policy.go
index f51941adc3..a8e5944228 100644
--- a/pkg/endpoint/policy.go
+++ b/pkg/endpoint/policy.go
@@ -16,6 +16,7 @@ package endpoint
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"os"
 	"strconv"
@@ -40,29 +41,89 @@ import (
 	"github.com/cilium/cilium/pkg/option"
 	"github.com/cilium/cilium/pkg/policy"
 	"github.com/cilium/cilium/pkg/revert"
+	"github.com/cilium/cilium/pkg/u8proto"
 
 	"github.com/sirupsen/logrus"
 )
 
-// ProxyID returns a unique string to identify a proxy mapping.
-func (e *Endpoint) ProxyID(npMap policy.NamedPortsMap, l4 *policy.L4Filter) (string, error) {
-	return policy.ProxyIDFromFilter(e.ID, npMap, l4)
+// GetNamedPort returns the port for the given name.
+// Must be called with e.mutex NOT held
+func (e *Endpoint) GetNamedPort(ingress bool, name string, proto uint8) uint16 {
+	if ingress {
+		// Ingress only needs the ports of the POD itself
+		k8sPorts, err := e.GetK8sPorts()
+		if err != nil {
+			if e.logLimiter.Allow() {
+				e.getLogger().WithFields(logrus.Fields{
+					logfields.PortName:         name,
+					logfields.Protocol:         u8proto.U8proto(proto).String(),
+					logfields.TrafficDirection: "ingress",
+				}).WithError(err).Warning("Skipping named port")
+			}
+			return 0
+		}
+		return e.getNamedPortIngress(k8sPorts, name, proto)
+	}
+	// egress needs named ports of all the pods
+	return e.getNamedPortEgress(ipcache.IPIdentityCache.GetNamedPorts(), name, proto)
+}
+
+// GetNamedPortLocked returns port for the given name. May return an invalid (0) port
+// Must be called with e.mutex held.
+func (e *Endpoint) GetNamedPortLocked(ingress bool, name string, proto uint8) uint16 {
+	if ingress {
+		// Ingress only needs the ports of the POD itself
+		return e.getNamedPortIngress(e.k8sPorts, name, proto)
+	}
+	// egress needs named ports of all the pods
+	return e.getNamedPortEgress(ipcache.IPIdentityCache.GetNamedPorts(), name, proto)
+}
+
+func (e *Endpoint) getNamedPortIngress(npMap policy.NamedPortMap, name string, proto uint8) uint16 {
+	port, err := npMap.GetNamedPort(name, proto)
+	if err != nil && e.logLimiter.Allow() {
+		e.getLogger().WithFields(logrus.Fields{
+			logfields.PortName:         name,
+			logfields.Protocol:         u8proto.U8proto(proto).String(),
+			logfields.TrafficDirection: "ingress",
+		}).WithError(err).Warning("Skipping named port")
+	}
+	return port
+}
+
+func (e *Endpoint) getNamedPortEgress(npMap policy.NamedPortMultiMap, name string, proto uint8) uint16 {
+	port, err := npMap.GetNamedPort(name, proto)
+	// Skip logging for ErrUnknownNamedPort on egress, as the destination POD with the port name
+	// is likely not scheduled yet.
+	if err != nil && !errors.Is(err, policy.ErrUnknownNamedPort) && e.logLimiter.Allow() {
+		e.getLogger().WithFields(logrus.Fields{
+			logfields.PortName:         name,
+			logfields.Protocol:         u8proto.U8proto(proto).String(),
+			logfields.TrafficDirection: "egress",
+		}).WithError(err).Warning("Skipping named port")
+	}
+	return port
+}
+
+// proxyID returns a unique string to identify a proxy mapping.
+// Must be called with e.mutex held.
+func (e *Endpoint) proxyID(l4 *policy.L4Filter) string {
+	port := uint16(l4.Port)
+	if port == 0 && l4.PortName != "" {
+		port = e.GetNamedPortLocked(l4.Ingress, l4.PortName, uint8(l4.U8Proto))
+		if port == 0 {
+			return ""
+		}
+	}
+	return policy.ProxyID(e.ID, l4.Ingress, string(l4.Protocol), port)
 }
 
 // lookupRedirectPort returns the redirect L4 proxy port for the given L4
 // policy map key, in host byte order. Returns 0 if not found or the
 // filter doesn't require a redirect.
-// Must be called with Endpoint.Mutex held.
-func (e *Endpoint) LookupRedirectPortLocked(npMap policy.NamedPortsMap, l4Filter *policy.L4Filter) uint16 {
-	if !l4Filter.IsRedirect() {
-		return 0
-	}
-	proxyID, err := e.ProxyID(npMap, l4Filter)
-	if err != nil {
-		e.getLogger().WithError(err).Warn("ProxyID failed")
-		return 0
-	}
-	return e.realizedRedirects[proxyID]
+// Must be called with Endpoint.mutex held.
+func (e *Endpoint) LookupRedirectPortLocked(ingress bool, protocol string, port uint16) uint16 {
+	return e.realizedRedirects[policy.ProxyID(e.ID, ingress, protocol, port)]
 }
 
 // Note that this function assumes that endpoint policy has already been generated!
@@ -88,7 +149,7 @@ func (e *Endpoint) updateNetworkPolicy(proxyWaitGroup *completion.WaitGroup) (re
 	}
 
 	// Publish the updated policy to L7 proxies.
-	return e.proxy.UpdateNetworkPolicy(e, e.desiredPolicy.L4Policy, e.desiredPolicy.NamedPortsMap, e.desiredPolicy.IngressPolicyEnabled, e.desiredPolicy.EgressPolicyEnabled, proxyWaitGroup)
+	return e.proxy.UpdateNetworkPolicy(e, e.desiredPolicy.L4Policy, e.desiredPolicy.IngressPolicyEnabled, e.desiredPolicy.EgressPolicyEnabled, proxyWaitGroup)
 }
 
 func (e *Endpoint) useCurrentNetworkPolicy(proxyWaitGroup *completion.WaitGroup) {
@@ -181,7 +242,7 @@ func (e *Endpoint) regeneratePolicy() (retErr error) {
 		e.getLogger().WithError(err).Warning("Failed to update policy")
 		return err
 	}
-	calculatedPolicy := e.selectorPolicy.Consume(e, ipcache.IPIdentityCache.GetNamedPorts())
+	calculatedPolicy := e.selectorPolicy.Consume(e)
 
 	stats.policyCalculation.End(true)
 
diff --git a/pkg/endpoint/proxy.go b/pkg/endpoint/proxy.go
index 636f700b3c..f18d6fbbf8 100644
--- a/pkg/endpoint/proxy.go
+++ b/pkg/endpoint/proxy.go
@@ -27,7 +27,7 @@ import (
 type EndpointProxy interface {
 	CreateOrUpdateRedirect(l4 policy.ProxyPolicy, id string, localEndpoint logger.EndpointUpdater, wg *completion.WaitGroup) (proxyPort uint16, err error, finalizeFunc revert.FinalizeFunc, revertFunc revert.RevertFunc)
 	RemoveRedirect(id string, wg *completion.WaitGroup) (error, revert.FinalizeFunc, revert.RevertFunc)
-	UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, npMap policy.NamedPortsMap, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error)
+	UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error)
 	UseCurrentNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, wg *completion.WaitGroup)
 	RemoveNetworkPolicy(ep logger.EndpointInfoSource)
 }
@@ -64,7 +64,7 @@ func (f *FakeEndpointProxy) RemoveRedirect(id string, wg *completion.WaitGroup)
 }
 
 // UpdateNetworkPolicy does nothing.
-func (f *FakeEndpointProxy) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, npMap policy.NamedPortsMap, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) {
+func (f *FakeEndpointProxy) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) {
 	return nil, nil
 }
 
diff --git a/pkg/endpoint/regeneration/owner.go b/pkg/endpoint/regeneration/owner.go
index 86d9673176..5d7dbcac41 100644
--- a/pkg/endpoint/regeneration/owner.go
+++ b/pkg/endpoint/regeneration/owner.go
@@ -1,4 +1,4 @@
-// Copyright 2016-2019 Authors of Cilium
+// Copyright 2016-2020 Authors of Cilium
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -60,9 +60,6 @@ type EndpointInfoSource interface {
 	HasSidecarProxy() bool
 	ConntrackName() string
 	ConntrackNameLocked() string
-	GetIngressPolicyEnabledLocked() bool
-	GetEgressPolicyEnabledLocked() bool
-	ProxyID(npMap policy.NamedPortsMap, l4 *policy.L4Filter) (string, error)
 	GetProxyInfoByFields() (uint64, string, string, []string, string, uint64, error)
 }
 
diff --git a/pkg/envoy/server.go b/pkg/envoy/server.go
index 444cc4bfcd..426c40bb4b 100644
--- a/pkg/envoy/server.go
+++ b/pkg/envoy/server.go
@@ -28,9 +28,7 @@ import (
 	"github.com/cilium/cilium/pkg/bpf"
 	"github.com/cilium/cilium/pkg/completion"
 	"github.com/cilium/cilium/pkg/envoy/xds"
-	"github.com/cilium/cilium/pkg/identity"
 	"github.com/cilium/cilium/pkg/lock"
-	"github.com/cilium/cilium/pkg/logging/logfields"
 	"github.com/cilium/cilium/pkg/option"
 	"github.com/cilium/cilium/pkg/policy"
 	"github.com/cilium/cilium/pkg/policy/api"
@@ -879,7 +877,7 @@ func getWildcardNetworkPolicyRule(selectors policy.L7DataMap) *cilium.PortNetwor
 	}
 }
 
-func getDirectionNetworkPolicy(l4Policy policy.L4PolicyMap, npMap policy.NamedPortsMap, policyEnforced bool) []*cilium.PortNetworkPolicy {
+func getDirectionNetworkPolicy(ep logger.EndpointUpdater, l4Policy policy.L4PolicyMap, policyEnforced bool) []*cilium.PortNetworkPolicy {
 	if !policyEnforced {
 		// Return an allow-all policy.
 		return allowAllPortNetworkPolicy
@@ -903,10 +901,8 @@ func getDirectionNetworkPolicy(l4Policy policy.L4PolicyMap, npMap policy.NamedPo
 
 		port := uint16(l4.Port)
 		if port == 0 && l4.PortName != "" {
-			var err error
-			port, err = npMap.GetNamedPort(l4.PortName, uint8(l4.U8Proto))
-			if err != nil {
-				log.WithError(err).WithField(logfields.PortName, l4.PortName).Debug("getDirectionNetworkPolicy: Skipping named port")
+			port = ep.GetNamedPortLocked(l4.Ingress, l4.PortName, uint8(l4.U8Proto))
+			if port == 0 {
 				continue
 			}
 		}
@@ -989,18 +985,18 @@ func getDirectionNetworkPolicy(l4Policy policy.L4PolicyMap, npMap policy.NamedPo
 }
 
 // getNetworkPolicy converts a network policy into a cilium.NetworkPolicy.
-func getNetworkPolicy(name string, id identity.NumericIdentity, conntrackName string, policy *policy.L4Policy,
-	npMap policy.NamedPortsMap, ingressPolicyEnforced, egressPolicyEnforced bool) *cilium.NetworkPolicy {
+func getNetworkPolicy(ep logger.EndpointUpdater, name string, policy *policy.L4Policy,
+	ingressPolicyEnforced, egressPolicyEnforced bool) *cilium.NetworkPolicy {
 	p := &cilium.NetworkPolicy{
 		Name:             name,
-		Policy:           uint64(id),
-		ConntrackMapName: conntrackName,
+		Policy:           uint64(ep.GetIdentityLocked()),
+		ConntrackMapName: ep.ConntrackNameLocked(),
 	}
 
 	// If no policy, deny all traffic. Otherwise, convert the policies for ingress and egress.
 	if policy != nil {
-		p.IngressPerPortPolicies = getDirectionNetworkPolicy(policy.Ingress, npMap, ingressPolicyEnforced)
-		p.EgressPerPortPolicies = getDirectionNetworkPolicy(policy.Egress, npMap, egressPolicyEnforced)
+		p.IngressPerPortPolicies = getDirectionNetworkPolicy(ep, policy.Ingress, ingressPolicyEnforced)
+		p.EgressPerPortPolicies = getDirectionNetworkPolicy(ep, policy.Egress, egressPolicyEnforced)
 	}
 
 	return p
@@ -1039,7 +1035,7 @@ func getNodeIDs(ep logger.EndpointUpdater, policy *policy.L4Policy) []string {
 // to L7 proxies.
 // When the proxy acknowledges the network policy update, it will result in
 // a subsequent call to the endpoint's OnProxyPolicyUpdate() function.
-func (s *XDSServer) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, npMap policy.NamedPortsMap,
+func (s *XDSServer) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy,
 	ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) {
 	s.mutex.Lock()
 	defer s.mutex.Unlock()
@@ -1054,8 +1050,7 @@ func (s *XDSServer) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *polic
 		if ip == "" {
 			continue
 		}
-		networkPolicy := getNetworkPolicy(ip, ep.GetIdentityLocked(), ep.ConntrackNameLocked(), policy, npMap,
-			ingressPolicyEnforced, egressPolicyEnforced)
+		networkPolicy := getNetworkPolicy(ep, ip, policy, ingressPolicyEnforced, egressPolicyEnforced)
 		err := networkPolicy.Validate()
 		if err != nil {
 			return fmt.Errorf("error validating generated NetworkPolicy for %s: %s", ip, err), nil
diff --git a/pkg/fqdn/dnsproxy/udp.go b/pkg/fqdn/dnsproxy/udp.go
index 124a67cb1c..00a2a4bdb6 100644
--- a/pkg/fqdn/dnsproxy/udp.go
+++ b/pkg/fqdn/dnsproxy/udp.go
@@ -102,6 +102,9 @@ func listenConfig(mark int, ipv4, ipv6 bool) *net.ListenConfig {
 				if opErr == nil {
 					opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
 				}
+				if opErr == nil {
+					opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
+				}
 			})
 			if err != nil {
 				return err
diff --git a/pkg/ipcache/ipcache.go b/pkg/ipcache/ipcache.go
index 396124d470..73c68542cc 100644
--- a/pkg/ipcache/ipcache.go
+++ b/pkg/ipcache/ipcache.go
@@ -23,7 +23,6 @@ import (
 	"github.com/cilium/cilium/pkg/option"
 	"github.com/cilium/cilium/pkg/policy"
 	"github.com/cilium/cilium/pkg/source"
-	"github.com/cilium/cilium/pkg/u8proto"
 	"github.com/sirupsen/logrus"
 )
 
@@ -62,7 +61,7 @@ type K8sMetadata struct {
 	// PodName is the Kubernetes pod name behind the IP
 	PodName string
 	// NamedPorts is the set of named ports for the pod
-	NamedPorts policy.NamedPortsMap
+	NamedPorts policy.NamedPortMap
 }
 
 // IPCache is a collection of mappings:
@@ -83,7 +82,18 @@ type IPCache struct {
 
 	listeners []IPIdentityMappingListener
 
-	namedPorts policy.NamedPortsMap
+	// needNamedPorts is initially 'false', but will be changd to 'true' when the
+	// clusterwide named port mappings are needed for network policy computation
+	// for the first time. This avoids the overhead of maintaining 'namedPorts' map
+	// when it is known not to be needed.
+	// Protected by 'mutex'.
+	needNamedPorts bool
+
+	// namedPorts is a collection of all named ports in the cluster. This is needed
+	// only if an egress policy refers to a port by name.
+	// This map is returned to users so all updates must be made into a fresh map that
+	// is then swapped in place while 'mutex' is being held.
+	namedPorts policy.NamedPortMultiMap
 }
 
 // NewIPCache returns a new IPCache with the mappings of endpoint IP to security
@@ -177,49 +187,22 @@ func (ipc *IPCache) getK8sMetadata(ip string) *K8sMetadata {
 	return nil
 }
 
-func (ipc *IPCache) updateNamedPorts(newIP string, newNamedPorts policy.NamedPortsMap) (namedPortsChanged bool) {
+// updateNamedPorts accumulates named ports from all K8sMetadata entries to a single map
+func (ipc *IPCache) updateNamedPorts() (namedPortsChanged bool) {
+	if !ipc.needNamedPorts {
+		return false
+	}
 	// Collect new named Ports
-	npm := make(policy.NamedPortsMap, len(ipc.namedPorts))
-	for ip, km := range ipc.ipToK8sMetadata {
-		for name, value := range km.NamedPorts {
-			if ip != newIP {
-				// Check if there is a conflict between any old named port from other IPs
-				// with any of the newly added (or changed) values
-				if newValue, exists := newNamedPorts[name]; exists && newValue != value {
-					scopedLog := log.WithFields(logrus.Fields{
-						logfields.IPAddr:   newIP,
-						logfields.Identity: ipc.ipToIdentityCache[newIP],
-					})
-					newMeta := ipc.ipToK8sMetadata[newIP]
-					scopedLog = scopedLog.WithFields(logrus.Fields{
-						logfields.K8sPodName:   newMeta.PodName,
-						logfields.K8sNamespace: newMeta.Namespace,
-						logfields.NamedPorts:   newMeta.NamedPorts,
-					})
-					oldMeta := ipc.ipToK8sMetadata[ip]
-					scopedLog = scopedLog.WithFields(logrus.Fields{
-						"other-" + logfields.K8sPodName:   oldMeta.PodName,
-						"other-" + logfields.K8sNamespace: oldMeta.Namespace,
-						"other-" + logfields.NamedPorts:   oldMeta.NamedPorts,
-					})
-					scopedLog.Warningf("Conflicting named port definition: %s: %d/%s != %d/%s", name,
-						newValue.Port, u8proto.U8proto(newValue.Proto).String(),
-						value.Port, u8proto.U8proto(value.Proto).String())
-				}
-			}
-			if _, done := npm[name]; done {
-				// Do not process duplicate values
-				continue
+	npm := make(policy.NamedPortMultiMap, len(ipc.namedPorts))
+	for _, km := range ipc.ipToK8sMetadata {
+		for name, port := range km.NamedPorts {
+			if npm[name] == nil {
+				npm[name] = make(policy.PortProtoSet)
 			}
-			if oldValue, ok := ipc.namedPorts[name]; !ok || oldValue != value {
-				namedPortsChanged = true
-			}
-			npm[name] = value
+			npm[name][port] = struct{}{}
 		}
 	}
-	if len(npm) != len(ipc.namedPorts) {
-		namedPortsChanged = true
-	}
+	namedPortsChanged = !npm.Equal(ipc.namedPorts)
 	if namedPortsChanged {
 		// swap the new map in
 		if len(npm) == 0 {
@@ -241,7 +224,7 @@ func (ipc *IPCache) updateNamedPorts(newIP string, newNamedPorts policy.NamedPor
 // nil) and is propagated to the listeners. k8sMeta contains Kubernetes-specific
 // metadata such as pod namespace and pod name belonging to the IP (may be nil).
 func (ipc *IPCache) Upsert(ip string, hostIP net.IP, hostKey uint8, k8sMeta *K8sMetadata, newIdentity Identity) (updated bool, namedPortsChanged bool) {
-	var newNamedPorts policy.NamedPortsMap
+	var newNamedPorts policy.NamedPortMap
 	if k8sMeta != nil {
 		newNamedPorts = k8sMeta.NamedPorts
 	}
@@ -382,7 +365,7 @@ func (ipc *IPCache) Upsert(ip string, hostIP net.IP, hostKey uint8, k8sMeta *K8s
 		if namedPortsChanged {
 			// It is possible that some other POD defines same values, check if
 			// anything changes over all the PODs.
-			namedPortsChanged = ipc.updateNamedPorts(ip, newNamedPorts)
+			namedPortsChanged = ipc.updateNamedPorts()
 		}
 	}
 
@@ -490,7 +473,7 @@ func (ipc *IPCache) deleteLocked(ip string, source source.Source) (namedPortsCha
 	// Update named ports
 	namedPortsChanged = false
 	if oldK8sMeta != nil && len(oldK8sMeta.NamedPorts) > 0 {
-		namedPortsChanged = ipc.updateNamedPorts(ip, nil)
+		namedPortsChanged = ipc.updateNamedPorts()
 	}
 
 	if callbackListeners {
@@ -504,8 +487,12 @@ func (ipc *IPCache) deleteLocked(ip string, source source.Source) (namedPortsCha
 }
 
 // GetNamedPorts returns a copy of the named ports map. May return nil.
-func (ipc *IPCache) GetNamedPorts() (npm policy.NamedPortsMap) {
+func (ipc *IPCache) GetNamedPorts() (npm policy.NamedPortMultiMap) {
 	ipc.mutex.Lock()
+	if !ipc.needNamedPorts {
+		ipc.needNamedPorts = true
+		ipc.updateNamedPorts()
+	}
 	// Caller can keep using the map after the lock is released, as the map is never changed
 	// once published.
 	npm = ipc.namedPorts
diff --git a/pkg/ipcache/kvstore.go b/pkg/ipcache/kvstore.go
index d132426f3d..20670cc778 100644
--- a/pkg/ipcache/kvstore.go
+++ b/pkg/ipcache/kvstore.go
@@ -108,7 +108,7 @@ func newKVReferenceCounter(s store) *kvReferenceCounter {
 // UpsertIPToKVStore updates / inserts the provided IP->Identity mapping into the
 // kvstore, which will subsequently trigger an event in NewIPIdentityWatcher().
 func UpsertIPToKVStore(ctx context.Context, IP, hostIP net.IP, ID identity.NumericIdentity, key uint8,
-	metadata, k8sNamespace, k8sPodName string, npm policy.NamedPortsMap) error {
+	metadata, k8sNamespace, k8sPodName string, npm policy.NamedPortMap) error {
 	// Sort named ports into a slice
 	namedPorts := make([]identity.NamedPort, 0, len(npm))
 	for name, value := range npm {
@@ -291,7 +291,7 @@ restart:
 					k8sMeta = &K8sMetadata{
 						Namespace:  ipIDPair.K8sNamespace,
 						PodName:    ipIDPair.K8sPodName,
-						NamedPorts: make(policy.NamedPortsMap, len(ipIDPair.NamedPorts)),
+						NamedPorts: make(policy.NamedPortMap, len(ipIDPair.NamedPorts)),
 					}
 					for _, np := range ipIDPair.NamedPorts {
 						err = k8sMeta.NamedPorts.AddPort(np.Name, int(np.Port), np.Protocol)
diff --git a/pkg/k8s/watchers/cilium_endpoint.go b/pkg/k8s/watchers/cilium_endpoint.go
index 6877aea4fe..c66f1f54a8 100644
--- a/pkg/k8s/watchers/cilium_endpoint.go
+++ b/pkg/k8s/watchers/cilium_endpoint.go
@@ -141,16 +141,16 @@ func (k *K8sWatcher) endpointUpdated(endpoint *types.CiliumEndpoint) {
 		k8sMeta := &ipcache.K8sMetadata{
 			Namespace:  endpoint.Namespace,
 			PodName:    endpoint.Name,
-			NamedPorts: make(policy.NamedPortsMap, len(endpoint.NamedPorts)),
+			NamedPorts: make(policy.NamedPortMap, len(endpoint.NamedPorts)),
 		}
 		for _, port := range endpoint.NamedPorts {
 			p, err := u8proto.ParseProtocol(port.Protocol)
 			if err != nil {
 				continue
 			}
-			k8sMeta.NamedPorts[port.Name] = policy.NamedPort{
-				Proto: uint8(p),
+			k8sMeta.NamedPorts[port.Name] = policy.PortProto{
 				Port:  uint16(port.Port),
+				Proto: uint8(p),
 			}
 		}
 
diff --git a/pkg/k8s/watchers/pod.go b/pkg/k8s/watchers/pod.go
index 2643444b48..cc4e235bfb 100644
--- a/pkg/k8s/watchers/pod.go
+++ b/pkg/k8s/watchers/pod.go
@@ -520,11 +520,11 @@ func (k *K8sWatcher) updatePodHostData(pod *slim_corev1.Pod) (bool, error) {
 				return true, fmt.Errorf("ContainerPort: invalid port: %d", port.ContainerPort)
 			}
 			if k8sMeta.NamedPorts == nil {
-				k8sMeta.NamedPorts = make(policy.NamedPortsMap)
+				k8sMeta.NamedPorts = make(policy.NamedPortMap)
 			}
-			k8sMeta.NamedPorts[port.Name] = policy.NamedPort{
-				Proto: uint8(p),
+			k8sMeta.NamedPorts[port.Name] = policy.PortProto{
 				Port:  uint16(port.ContainerPort),
+				Proto: uint8(p),
 			}
 		}
 	}
diff --git a/pkg/logging/limiter.go b/pkg/logging/limiter.go
new file mode 100644
index 0000000000..c7578838b2
--- /dev/null
+++ b/pkg/logging/limiter.go
@@ -0,0 +1,47 @@
+// Copyright 2020 Authors of Cilium
+//
+// 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 logging
+
+import (
+	"time"
+
+	"golang.org/x/time/rate"
+)
+
+// Limiter is a wrapper around rate.Limiter that does not panic when
+// the limiter is uninitialized. The wrapping also allows more logging
+// specific functionality to be added later without changing all the call
+// sites.
+type Limiter struct {
+	bucket *rate.Limiter
+}
+
+// NewLimiter returns a new Limiter allowing log messages to be
+// emitted on average once every 'interval' and upto 'burst' messages
+// during any 'interval'.
+func NewLimiter(interval time.Duration, burst int) Limiter {
+	return Limiter{
+		bucket: rate.NewLimiter(rate.Every(interval), burst),
+	}
+}
+
+// Allow returns true if the log message is allowed under the
+// configured rate limit.
+func (ll Limiter) Allow() bool {
+	if ll.bucket == nil {
+		return true // limiter not initialized => no limit
+	}
+	return ll.bucket.Allow()
+}
diff --git a/pkg/policy/distillery.go b/pkg/policy/distillery.go
index 8ff1caddb7..294afdd534 100644
--- a/pkg/policy/distillery.go
+++ b/pkg/policy/distillery.go
@@ -1,4 +1,4 @@
-// Copyright 2019 Authors of Cilium
+// Copyright 2019-2020 Authors of Cilium
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ import (
 type SelectorPolicy interface {
 	// Consume returns the policy in terms of connectivity to peer
 	// Identities.
-	Consume(owner PolicyOwner, npMap NamedPortsMap) *EndpointPolicy
+	Consume(owner PolicyOwner) *EndpointPolicy
 }
 
 // PolicyCache represents a cache of resolved policies for identities.
@@ -62,9 +62,8 @@ func (cache *PolicyCache) GetSelectorCache() *SelectorCache {
 }
 
 // lookupOrCreate adds the specified Identity to the policy cache, with a reference
-// from the specified Endpoint, then returns the threadsafe copy of the policy
-// and whether policy has been computed for this identity.
-func (cache *PolicyCache) lookupOrCreate(identity *identityPkg.Identity, create bool) (SelectorPolicy, bool) {
+// from the specified Endpoint, then returns the threadsafe copy of the policy.
+func (cache *PolicyCache) lookupOrCreate(identity *identityPkg.Identity, create bool) SelectorPolicy {
 	cache.Lock()
 	defer cache.Unlock()
 	cip, ok := cache.policies[identity.ID]
@@ -72,16 +71,12 @@ func (cache *PolicyCache) lookupOrCreate(identity *identityPkg.Identity, create
 		cip = newCachedSelectorPolicy(identity, cache.repo.GetSelectorCache())
 		cache.policies[identity.ID] = cip
 	}
-	if cip != nil {
-		return cip, cip.getPolicy().Revision > 0
-	}
-	return nil, false
+	return cip
 }
 
 // insert adds the specified Identity to the policy cache, with a reference
-// from the specified Endpoint, then returns the threadsafe copy of the policy
-// and whether policy has been computed for this identity.
-func (cache *PolicyCache) insert(identity *identityPkg.Identity) (SelectorPolicy, bool) {
+// from the specified Endpoint, then returns the threadsafe copy of the policy.
+func (cache *PolicyCache) insert(identity *identityPkg.Identity) SelectorPolicy {
 	return cache.lookupOrCreate(identity, true)
 }
 
@@ -157,8 +152,7 @@ func (cache *PolicyCache) LocalEndpointIdentityRemoved(identity *identityPkg.Ide
 // Lookup attempts to locate the SelectorPolicy corresponding to the specified
 // identity. If policy is not cached for the identity, it returns nil.
 func (cache *PolicyCache) Lookup(identity *identityPkg.Identity) SelectorPolicy {
-	cip, _ := cache.lookupOrCreate(identity, false)
-	return cip
+	return cache.lookupOrCreate(identity, false)
 }
 
 // UpdatePolicy resolves the policy for the security identity of the specified
@@ -211,10 +205,10 @@ func (cip *cachedSelectorPolicy) setPolicy(policy *selectorPolicy) {
 //
 // This denotes that a particular endpoint is 'consuming' the policy from the
 // selector policy cache.
-func (cip *cachedSelectorPolicy) Consume(owner PolicyOwner, npMap NamedPortsMap) *EndpointPolicy {
+func (cip *cachedSelectorPolicy) Consume(owner PolicyOwner) *EndpointPolicy {
 	// TODO: This currently computes the EndpointPolicy from SelectorPolicy
 	// on-demand, however in future the cip is intended to cache the
 	// EndpointPolicy for this Identity and emit datapath deltas instead.
 	isHost := cip.identity.ID == identityPkg.ReservedIdentityHost
-	return cip.getPolicy().DistillPolicy(owner, npMap, isHost)
+	return cip.getPolicy().DistillPolicy(owner, isHost)
 }
diff --git a/pkg/policy/l4.go b/pkg/policy/l4.go
index 1e612ff0e8..5aaffadaf3 100644
--- a/pkg/policy/l4.go
+++ b/pkg/policy/l4.go
@@ -1,4 +1,4 @@
-// Copyright 2016-2019 Authors of Cilium
+// Copyright 2016-2020 Authors of Cilium
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import (
 	"reflect"
 	"sort"
 	"strconv"
-	"strings"
 	"sync/atomic"
 	"unsafe"
 
@@ -244,71 +243,6 @@ func (l4 *L4Filter) GetPort() uint16 {
 	return uint16(l4.Port)
 }
 
-// NamedPort represents a mapping from a port name to the port number and protocol
-type NamedPort struct {
-	Port  uint16 // non-0
-	Proto uint8  // 0 for any
-}
-
-type NamedPortsMap map[string]NamedPort
-
-func ValidatePortName(name string) (string, error) {
-	if !iana.IsSvcName(name) { // Port names are formatted as IANA Service Names
-		return "", fmt.Errorf("Invalid port name \"%s\", not using as a named port", name)
-	}
-	return strings.ToLower(name), nil // Normalize for case-insensitive comparison
-}
-
-func ParsePortProtocol(port int, protocol string) (np NamedPort, err error) {
-	var u8p u8proto.U8proto
-	if protocol == "" {
-		u8p = u8proto.TCP // K8s ContainerPort protocol defaults to TCP
-	} else {
-		var err error
-		u8p, err = u8proto.ParseProtocol(protocol)
-		if err != nil {
-			return np, fmt.Errorf("Invalid protocol \"%s\": %s", protocol, err)
-		}
-	}
-	if port < 1 || port > 65535 {
-		return np, fmt.Errorf("Port number %d out of 16-bit range", port)
-	}
-	return NamedPort{
-		Proto: uint8(u8p),
-		Port:  uint16(port),
-	}, nil
-}
-
-func (npm NamedPortsMap) AddPort(name string, port int, protocol string) error {
-	name, err := ValidatePortName(name)
-	if err != nil {
-		return err
-	}
-	np, err := ParsePortProtocol(port, protocol)
-	if err != nil {
-		return err
-	}
-	npm[name] = np
-	return nil
-}
-
-func (npm NamedPortsMap) GetNamedPort(name string, proto uint8) (port uint16, err error) {
-	if npm == nil {
-		return 0, fmt.Errorf("nil map")
-	}
-	np, ok := npm[name]
-	if !ok {
-		return 0, fmt.Errorf("named port %s not found in %v", name, npm)
-	}
-	if np.Proto != 0 && proto != np.Proto {
-		return 0, fmt.Errorf("incompatible proto")
-	}
-	if np.Port == 0 {
-		return 0, fmt.Errorf("named port has zero value")
-	}
-	return np.Port, nil
-}
-
 // ToMapState converts filter into a MapState with two possible values:
 // - Entry with ProxyPort = 0: No proxy redirection is needed for this key
 // - Entry with any other port #: Proxy redirection is required for this key,
@@ -317,7 +251,8 @@ func (npm NamedPortsMap) GetNamedPort(name string, proto uint8) (port uint16, er
 // Note: It is possible for two selectors to select the same security ID.
 // To give priority for L7 redirection (e.g., for visibility purposes), we use
 // RedirectPreferredInsert() instead of directly inserting the value to the map.
-func (l4 *L4Filter) ToMapState(npMap NamedPortsMap, direction trafficdirection.TrafficDirection) MapState {
+// PolicyOwner (aka Endpoint) is locked during this call.
+func (l4 *L4Filter) ToMapState(policyOwner PolicyOwner, direction trafficdirection.TrafficDirection) MapState {
 	port := uint16(l4.Port)
 	proto := uint8(l4.U8Proto)
 
@@ -335,10 +270,8 @@ func (l4 *L4Filter) ToMapState(npMap NamedPortsMap, direction trafficdirection.T
 
 	// resolve named port
 	if port == 0 && l4.PortName != "" {
-		var err error
-		port, err = npMap.GetNamedPort(l4.PortName, proto)
-		if err != nil {
-			logger.Debugf("ToMapState: Skipping named port: %s", err)
+		port = policyOwner.GetNamedPortLocked(l4.Ingress, l4.PortName, proto)
+		if port == 0 {
 			return keysToAdd
 		}
 	}
@@ -853,6 +786,8 @@ type L4Policy struct {
 
 	// Endpoint policies using this L4Policy
 	// These are circular references, cleaned up in Detach()
+	// This mutex is taken while Endpoint mutex is held, so Endpoint lock
+	// MUST always be taken before this mutex.
 	mutex lock.RWMutex
 	users map[*EndpointPolicy]struct{}
 }
@@ -899,30 +834,26 @@ func (l4 *L4Policy) AccumulateMapChanges(adds, deletes []identity.NumericIdentit
 	proto := uint8(l4Filter.U8Proto)
 	derivedFrom := l4Filter.DerivedFromRules
 
+	// Must take a copy of 'users' as GetNamedPort() will lock the Endpoint below and
+	// the Endpoint lock may not be taken while 'l4.mutex' is held.
 	l4.mutex.RLock()
-	for epPolicy := range l4.users {
+	users := make(map[*EndpointPolicy]struct{}, len(l4.users))
+	for user := range l4.users {
+		users[user] = struct{}{}
+	}
+	l4.mutex.RUnlock()
+
+	for epPolicy := range users {
 		// resolve named port
 		if port == 0 && l4Filter.PortName != "" {
-			var err error
-			port, err = epPolicy.NamedPortsMap.GetNamedPort(l4Filter.PortName, proto)
-			if err != nil {
-				if option.Config.Debug {
-					logger := log.WithFields(logrus.Fields{
-						logfields.Port:             port,
-						logfields.PortName:         l4Filter.PortName,
-						logfields.Protocol:         proto,
-						logfields.TrafficDirection: direction,
-						logfields.EndpointID:       epPolicy.PolicyOwner.GetID(),
-					})
-					logger.WithError(err).Debug("AccumulateMapChanges: Skipping named port")
-				}
+			port = epPolicy.PolicyOwner.GetNamedPort(direction == trafficdirection.Ingress, l4Filter.PortName, proto)
+			if port == 0 {
 				continue
 			}
 		}
 
 		epPolicy.policyMapChanges.AccumulateMapChanges(adds, deletes, port, proto, direction, redirect, derivedFrom)
 	}
-	l4.mutex.RUnlock()
 }
 
 // Detach makes the L4Policy ready for garbage collection, removing
diff --git a/pkg/policy/portmap.go b/pkg/policy/portmap.go
new file mode 100644
index 0000000000..d196c575d7
--- /dev/null
+++ b/pkg/policy/portmap.go
@@ -0,0 +1,174 @@
+// Copyright 2020 Authors of Cilium
+//
+// 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 policy
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/cilium/cilium/pkg/iana"
+	"github.com/cilium/cilium/pkg/u8proto"
+)
+
+var (
+	ErrNilMap               = errors.New("nil map")
+	ErrUnknownNamedPort     = errors.New("unknown named port")
+	ErrIncompatibleProtocol = errors.New("incompatible protocol")
+	ErrNamedPortIsZero      = errors.New("named port is zero")
+	ErrDuplicateNamedPorts  = errors.New("duplicate named ports")
+)
+
+// PortProto is a pair of port number and protocol and is used as the
+// value type in named port maps.
+type PortProto struct {
+	Port  uint16 // non-0
+	Proto uint8  // 0 for any
+}
+
+// NamedPortMap maps port names to port numbers and protocols.
+type NamedPortMap map[string]PortProto
+
+// PortProtoSet is a set of unique PortProto values.
+type PortProtoSet map[PortProto]struct{}
+
+// Equal returns true if the PortProtoSets are equal.
+func (pps PortProtoSet) Equal(other PortProtoSet) bool {
+	if len(pps) != len(other) {
+		return false
+	}
+
+	for port := range pps {
+		if _, exists := other[port]; !exists {
+			return false
+		}
+	}
+	return true
+}
+
+// NamedPortMultiMap may have multiple entries for a name if multiple PODs
+// define the same name with different values.
+type NamedPortMultiMap map[string]PortProtoSet
+
+// Equal returns true if the NamedPortMultiMaps are equal.
+func (npm NamedPortMultiMap) Equal(other NamedPortMultiMap) bool {
+	if len(npm) != len(other) {
+		return false
+	}
+	for name, ports := range npm {
+		if otherPorts, exists := other[name]; !exists || !ports.Equal(otherPorts) {
+			return false
+		}
+	}
+	return true
+}
+
+// ValidatePortName checks that the port name conforms to the IANA Service Names spec
+// and converts the port name to lower case for case-insensitive comparisons.
+func ValidatePortName(name string) (string, error) {
+	if !iana.IsSvcName(name) { // Port names are formatted as IANA Service Names
+		return "", fmt.Errorf("Invalid port name \"%s\", not using as a named port", name)
+	}
+	return strings.ToLower(name), nil // Normalize for case-insensitive comparison
+}
+
+func newPortProto(port int, protocol string) (pp PortProto, err error) {
+	var u8p u8proto.U8proto
+	if protocol == "" {
+		u8p = u8proto.TCP // K8s ContainerPort protocol defaults to TCP
+	} else {
+		var err error
+		u8p, err = u8proto.ParseProtocol(protocol)
+		if err != nil {
+			return pp, err
+		}
+	}
+	if port < 1 || port > 65535 {
+		if port == 0 {
+			return pp, ErrNamedPortIsZero
+		}
+		return pp, fmt.Errorf("Port number %d out of 16-bit range", port)
+	}
+	return PortProto{
+		Proto: uint8(u8p),
+		Port:  uint16(port),
+	}, nil
+}
+
+// AddPort adds a new PortProto to the NamedPortMap
+func (npm NamedPortMap) AddPort(name string, port int, protocol string) error {
+	name, err := ValidatePortName(name)
+	if err != nil {
+		return err
+	}
+	pp, err := newPortProto(port, protocol)
+	if err != nil {
+		return err
+	}
+	npm[name] = pp
+	return nil
+}
+
+// GetNamedPort returns the port number for the named port, if any.
+func (npm NamedPortMap) GetNamedPort(name string, proto uint8) (uint16, error) {
+	if npm == nil {
+		return 0, ErrNilMap
+	}
+	pp, ok := npm[name]
+	if !ok {
+		return 0, ErrUnknownNamedPort
+	}
+	if pp.Proto != 0 && proto != pp.Proto {
+		return 0, ErrIncompatibleProtocol
+	}
+	if pp.Port == 0 {
+		return 0, ErrNamedPortIsZero
+	}
+	return pp.Port, nil
+}
+
+// GetNamedPort returns the port number for the named port, if any.
+func (npm NamedPortMultiMap) GetNamedPort(name string, proto uint8) (uint16, error) {
+	if npm == nil {
+		return 0, ErrNilMap
+	}
+	pps, ok := npm[name]
+	if !ok {
+		// Return an error the caller can filter out as this happens only for egress policy
+		// and it is likely the destination POD with the port name is simply not scheduled yet.
+		return 0, ErrUnknownNamedPort
+	}
+	// Find if there is a single port that has no proto conflict and no zero port value
+	port := uint16(0)
+	err := ErrUnknownNamedPort
+	for pp := range pps {
+		if pp.Proto != 0 && proto != pp.Proto {
+			err = ErrIncompatibleProtocol
+			continue // conflicting proto
+		}
+		if pp.Port == 0 {
+			err = ErrNamedPortIsZero
+			continue // zero port
+		}
+		if port != 0 && pp.Port != port {
+			return 0, ErrDuplicateNamedPorts
+		}
+		port = pp.Port
+	}
+	if port == 0 {
+		return 0, err
+	}
+	return port, nil
+}
diff --git a/pkg/policy/proxyid.go b/pkg/policy/proxyid.go
index 8f4ec8ee99..b894f063bb 100644
--- a/pkg/policy/proxyid.go
+++ b/pkg/policy/proxyid.go
@@ -37,18 +37,6 @@ func ProxyIDFromKey(endpointID uint16, key Key) string {
 	return ProxyID(endpointID, key.TrafficDirection == trafficdirection.Ingress.Uint8(), u8proto.U8proto(key.Nexthdr).String(), key.DestPort)
 }
 
-// ProxyIDFromFilter returns a unique string to identify a proxy mapping.
-func ProxyIDFromFilter(endpointID uint16, npMap NamedPortsMap, l4 *L4Filter) (id string, err error) {
-	port := uint16(l4.Port)
-	if port == 0 && l4.PortName != "" {
-		port, err = npMap.GetNamedPort(l4.PortName, uint8(l4.U8Proto))
-		if err != nil {
-			return "", err
-		}
-	}
-	return ProxyID(endpointID, l4.Ingress, string(l4.Protocol), port), nil
-}
-
 // ParseProxyID parses a proxy ID returned by ProxyID and returns its components.
 func ParseProxyID(proxyID string) (endpointID uint16, ingress bool, protocol string, port uint16, err error) {
 	comps := strings.Split(proxyID, ":")
diff --git a/pkg/policy/resolve.go b/pkg/policy/resolve.go
index 9e99e88960..1f2a52e9f3 100644
--- a/pkg/policy/resolve.go
+++ b/pkg/policy/resolve.go
@@ -57,10 +57,6 @@ type EndpointPolicy struct {
 	// referring to a shared selectorPolicy!
 	*selectorPolicy
 
-	// NamedPortsMap maps PortNames in L4Filters to port numbers. This mapping
-	// is endpoint specific. Can be nil!
-	NamedPortsMap NamedPortsMap
-
 	// PolicyMapState contains the state of this policy as it relates to the
 	// datapath. In the future, this will be factored out of this object to
 	// decouple the policy as it relates to the datapath vs. its userspace
@@ -80,7 +76,9 @@ type EndpointPolicy struct {
 // PolicyOwner is anything which consumes a EndpointPolicy.
 type PolicyOwner interface {
 	GetID() uint64
-	LookupRedirectPortLocked(npMap NamedPortsMap, l4 *L4Filter) uint16
+	LookupRedirectPortLocked(ingress bool, protocol string, port uint16) uint16
+	GetNamedPort(ingress bool, name string, proto uint8) uint16
+	GetNamedPortLocked(ingress bool, name string, proto uint8) uint16
 }
 
 // newSelectorPolicy returns an empty selectorPolicy stub.
@@ -114,10 +112,9 @@ func (p *selectorPolicy) Detach() {
 //
 // Must be performed while holding the Repository lock.
 // PolicyOwner (aka Endpoint) is also locked during this call.
-func (p *selectorPolicy) DistillPolicy(policyOwner PolicyOwner, npMap NamedPortsMap, isHost bool) *EndpointPolicy {
+func (p *selectorPolicy) DistillPolicy(policyOwner PolicyOwner, isHost bool) *EndpointPolicy {
 	calculatedPolicy := &EndpointPolicy{
 		selectorPolicy: p,
-		NamedPortsMap:  npMap,
 		PolicyMapState: make(MapState),
 		PolicyOwner:    policyOwner,
 	}
@@ -165,13 +162,15 @@ func (p *EndpointPolicy) computeDirectionL4PolicyMapEntries(l4PolicyMap L4Policy
 	for _, filter := range l4PolicyMap {
 		lookupDone := false
 		proxyport := uint16(0)
-		keysFromFilter := filter.ToMapState(p.NamedPortsMap, direction)
+		keysFromFilter := filter.ToMapState(p.PolicyOwner, direction)
 		for keyFromFilter, entry := range keysFromFilter {
 			// Fix up the proxy port for entries that need proxy redirection
 			if entry.IsRedirectEntry() {
 				if !lookupDone {
 					// only lookup once for each filter
-					proxyport = p.PolicyOwner.LookupRedirectPortLocked(p.NamedPortsMap, filter)
+					// Use 'destPort' from the key as it is already resolved
+					// from a named port if needed.
+					proxyport = p.PolicyOwner.LookupRedirectPortLocked(filter.Ingress, string(filter.Protocol), keyFromFilter.DestPort)
 					lookupDone = true
 				}
 				entry.ProxyPort = proxyport
diff --git a/pkg/proxy/logger/epinfo.go b/pkg/proxy/logger/epinfo.go
index 1e6d49731e..41e1c94bd0 100644
--- a/pkg/proxy/logger/epinfo.go
+++ b/pkg/proxy/logger/epinfo.go
@@ -1,4 +1,4 @@
-// Copyright 2018 Authors of Cilium
+// Copyright 2018-2020 Authors of Cilium
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ import (
 	"net"
 
 	"github.com/cilium/cilium/pkg/identity"
-	"github.com/cilium/cilium/pkg/policy"
 	"github.com/cilium/cilium/pkg/proxy/accesslog"
 )
 
@@ -39,9 +38,7 @@ type EndpointInfoSource interface {
 	// implementation.
 	ConntrackName() string
 	ConntrackNameLocked() string
-	GetIngressPolicyEnabledLocked() bool
-	GetEgressPolicyEnabledLocked() bool
-	ProxyID(npMap policy.NamedPortsMap, l4 *policy.L4Filter) (string, error)
+	GetNamedPortLocked(ingress bool, name string, proto uint8) uint16
 	GetProxyInfoByFields() (uint64, string, string, []string, string, uint64, error)
 }
 
diff --git a/pkg/proxy/proxy.go b/pkg/proxy/proxy.go
index cc1decc5be..c4269a8fab 100644
--- a/pkg/proxy/proxy.go
+++ b/pkg/proxy/proxy.go
@@ -660,8 +660,8 @@ func (p *Proxy) updateRedirectMetrics() {
 }
 
 // UpdateNetworkPolicy must update the redirect configuration of an endpoint in the proxy
-func (p *Proxy) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, npMap policy.NamedPortsMap, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) {
-	return p.XDSServer.UpdateNetworkPolicy(ep, policy, npMap, ingressPolicyEnforced, egressPolicyEnforced, wg)
+func (p *Proxy) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) {
+	return p.XDSServer.UpdateNetworkPolicy(ep, policy, ingressPolicyEnforced, egressPolicyEnforced, wg)
 }
 
 // UseCurrentNetworkPolicy inserts a Completion to the WaitGroup if the current network policy has not yet been acked


Test patch:
diff --git a/pkg/endpoint/endpoint_test.go b/pkg/endpoint/endpoint_test.go
index 130cfb7531..72072636a0 100644
--- a/pkg/endpoint/endpoint_test.go
+++ b/pkg/endpoint/endpoint_test.go
@@ -506,8 +506,8 @@ func (s *EndpointSuite) TestWaitForPolicyRevision(c *C) {
 func (s *EndpointSuite) TestProxyID(c *C) {
 	e := &Endpoint{ID: 123, policyRevision: 0}
 
-	id, err := e.ProxyID(nil, &policy.L4Filter{Port: 8080, Protocol: api.ProtoTCP, Ingress: true})
-	c.Assert(err, IsNil)
+	id := e.proxyID(&policy.L4Filter{Port: 8080, Protocol: api.ProtoTCP, Ingress: true})
+	c.Assert(id, Not(Equals), "")
 	endpointID, ingress, protocol, port, err := policy.ParseProxyID(id)
 	c.Assert(endpointID, Equals, uint16(123))
 	c.Assert(ingress, Equals, true)
diff --git a/pkg/endpoint/redirect_test.go b/pkg/endpoint/redirect_test.go
index efb4c3e920..1a7669a8af 100644
--- a/pkg/endpoint/redirect_test.go
+++ b/pkg/endpoint/redirect_test.go
@@ -63,7 +63,7 @@ func (r *RedirectSuiteProxy) RemoveRedirect(id string, wg *completion.WaitGroup)
 }
 
 // UpdateNetworkPolicy does nothing.
-func (r *RedirectSuiteProxy) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, npMap policy.NamedPortsMap, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) {
+func (r *RedirectSuiteProxy) UpdateNetworkPolicy(ep logger.EndpointUpdater, policy *policy.L4Policy, ingressPolicyEnforced, egressPolicyEnforced bool, wg *completion.WaitGroup) (error, func() error) {
 	return nil, nil
 }
 
diff --git a/pkg/envoy/server_test.go b/pkg/envoy/server_test.go
index 1459bf15d6..995fd291eb 100644
--- a/pkg/envoy/server_test.go
+++ b/pkg/envoy/server_test.go
@@ -23,6 +23,8 @@ import (
 	"github.com/cilium/cilium/pkg/labels"
 	"github.com/cilium/cilium/pkg/policy"
 	"github.com/cilium/cilium/pkg/policy/api"
+	"github.com/cilium/cilium/pkg/proxy/logger"
+	"github.com/cilium/cilium/pkg/proxy/logger/test"
 
 	"github.com/cilium/proxy/go/cilium/api"
 	envoy_api_v2_core "github.com/cilium/proxy/go/envoy/api/v2/core"
@@ -45,6 +47,14 @@ var (
 	_        = Suite(&ServerSuite{})
 	IPv4Addr = "10.1.1.1"
 	Identity = identity.NumericIdentity(123)
+
+	ep logger.EndpointUpdater = &test.ProxyUpdaterMock{
+		Id:       1000,
+		Ipv4:     "10.0.0.1",
+		Ipv6:     "f00d::1",
+		Labels:   []string{"id.foo", "id.bar"},
+		Identity: Identity,
+	}
 )
 
 var PortRuleHTTP1 = &api.PortRuleHTTP{
@@ -490,99 +500,106 @@ func (s *ServerSuite) TestGetPortNetworkPolicyRule(c *C) {
 
 func (s *ServerSuite) TestGetDirectionNetworkPolicy(c *C) {
 	// L4+L7
-	obtained := getDirectionNetworkPolicy(L4PolicyMap1, nil, true)
+	obtained := getDirectionNetworkPolicy(ep, L4PolicyMap1, true)
 	c.Assert(obtained, checker.Equals, ExpectedPerPortPolicies1)
 
 	// L4+L7 with header mods
-	obtained = getDirectionNetworkPolicy(L4PolicyMap1HeaderMatch, nil, true)
+	obtained = getDirectionNetworkPolicy(ep, L4PolicyMap1HeaderMatch, true)
 	c.Assert(obtained, checker.Equals, ExpectedPerPortPolicies1HeaderMatch)
 
 	// L4+L7
-	obtained = getDirectionNetworkPolicy(L4PolicyMap2, nil, true)
+	obtained = getDirectionNetworkPolicy(ep, L4PolicyMap2, true)
 	c.Assert(obtained, checker.Equals, ExpectedPerPortPolicies2)
 
 	// L4-only
-	obtained = getDirectionNetworkPolicy(L4PolicyMap4, nil, true)
+	obtained = getDirectionNetworkPolicy(ep, L4PolicyMap4, true)
 	c.Assert(obtained, checker.Equals, ExpectedPerPortPolicies6)
 
 	// L4-only
-	obtained = getDirectionNetworkPolicy(L4PolicyMap5, nil, true)
+	obtained = getDirectionNetworkPolicy(ep, L4PolicyMap5, true)
 	c.Assert(obtained, checker.Equals, ExpectedPerPortPolicies7)
 }
 
 func (s *ServerSuite) TestGetNetworkPolicy(c *C) {
-	obtained := getNetworkPolicy(IPv4Addr, Identity, "", L4Policy1, nil, true, true)
+	obtained := getNetworkPolicy(ep, IPv4Addr, L4Policy1, true, true)
 	expected := &cilium.NetworkPolicy{
 		Name:                   IPv4Addr,
 		Policy:                 uint64(Identity),
 		IngressPerPortPolicies: ExpectedPerPortPolicies1,
 		EgressPerPortPolicies:  ExpectedPerPortPolicies2,
+		ConntrackMapName:       "global",
 	}
 	c.Assert(obtained, checker.Equals, expected)
 }
 
 func (s *ServerSuite) TestGetNetworkPolicyWildcard(c *C) {
-	obtained := getNetworkPolicy(IPv4Addr, Identity, "", L4Policy2, nil, true, true)
+	obtained := getNetworkPolicy(ep, IPv4Addr, L4Policy2, true, true)
 	expected := &cilium.NetworkPolicy{
 		Name:                   IPv4Addr,
 		Policy:                 uint64(Identity),
 		IngressPerPortPolicies: ExpectedPerPortPolicies3,
 		EgressPerPortPolicies:  ExpectedPerPortPolicies2,
+		ConntrackMapName:       "global",
 	}
 	c.Assert(obtained, checker.Equals, expected)
 }
 
 func (s *ServerSuite) TestGetNetworkPolicyDeny(c *C) {
-	obtained := getNetworkPolicy(IPv4Addr, Identity, "", L4Policy1RequiresV2, nil, true, true)
+	obtained := getNetworkPolicy(ep, IPv4Addr, L4Policy1RequiresV2, true, true)
 	expected := &cilium.NetworkPolicy{
 		Name:                   IPv4Addr,
 		Policy:                 uint64(Identity),
 		IngressPerPortPolicies: ExpectedPerPortPolicies4RequiresV2,
 		EgressPerPortPolicies:  ExpectedPerPortPolicies2,
+		ConntrackMapName:       "global",
 	}
 	c.Assert(obtained, checker.Equals, expected)
 }
 
 func (s *ServerSuite) TestGetNetworkPolicyWildcardDeny(c *C) {
-	obtained := getNetworkPolicy(IPv4Addr, Identity, "", L4Policy2RequiresV2, nil, true, true)
+	obtained := getNetworkPolicy(ep, IPv4Addr, L4Policy2RequiresV2, true, true)
 	expected := &cilium.NetworkPolicy{
 		Name:                   IPv4Addr,
 		Policy:                 uint64(Identity),
 		IngressPerPortPolicies: ExpectedPerPortPolicies5RequiresV2,
 		EgressPerPortPolicies:  ExpectedPerPortPolicies2,
+		ConntrackMapName:       "global",
 	}
 	c.Assert(obtained, checker.Equals, expected)
 }
 
 func (s *ServerSuite) TestGetNetworkPolicyNil(c *C) {
-	obtained := getNetworkPolicy(IPv4Addr, Identity, "", nil, nil, true, true)
+	obtained := getNetworkPolicy(ep, IPv4Addr, nil, true, true)
 	expected := &cilium.NetworkPolicy{
 		Name:                   IPv4Addr,
 		Policy:                 uint64(Identity),
 		IngressPerPortPolicies: nil,
 		EgressPerPortPolicies:  nil,
+		ConntrackMapName:       "global",
 	}
 	c.Assert(obtained, checker.Equals, expected)
 }
 
 func (s *ServerSuite) TestGetNetworkPolicyIngressNotEnforced(c *C) {
-	obtained := getNetworkPolicy(IPv4Addr, Identity, "", L4Policy2, nil, false, true)
+	obtained := getNetworkPolicy(ep, IPv4Addr, L4Policy2, false, true)
 	expected := &cilium.NetworkPolicy{
 		Name:                   IPv4Addr,
 		Policy:                 uint64(Identity),
 		IngressPerPortPolicies: allowAllPortNetworkPolicy,
 		EgressPerPortPolicies:  ExpectedPerPortPolicies2,
+		ConntrackMapName:       "global",
 	}
 	c.Assert(obtained, checker.Equals, expected)
 }
 
 func (s *ServerSuite) TestGetNetworkPolicyEgressNotEnforced(c *C) {
-	obtained := getNetworkPolicy(IPv4Addr, Identity, "", L4Policy2RequiresV2, nil, true, false)
+	obtained := getNetworkPolicy(ep, IPv4Addr, L4Policy2RequiresV2, true, false)
 	expected := &cilium.NetworkPolicy{
 		Name:                   IPv4Addr,
 		Policy:                 uint64(Identity),
 		IngressPerPortPolicies: ExpectedPerPortPolicies5RequiresV2,
 		EgressPerPortPolicies:  allowAllPortNetworkPolicy,
+		ConntrackMapName:       "global",
 	}
 	c.Assert(obtained, checker.Equals, expected)
 }
@@ -634,11 +651,12 @@ var ExpectedPerPortPoliciesL7 = []*cilium.PortNetworkPolicy{
 }
 
 func (s *ServerSuite) TestGetNetworkPolicyL7(c *C) {
-	obtained := getNetworkPolicy(IPv4Addr, Identity, "", L4PolicyL7, nil, true, true)
+	obtained := getNetworkPolicy(ep, IPv4Addr, L4PolicyL7, true, true)
 	expected := &cilium.NetworkPolicy{
 		Name:                   IPv4Addr,
 		Policy:                 uint64(Identity),
 		IngressPerPortPolicies: ExpectedPerPortPoliciesL7,
+		ConntrackMapName:       "global",
 	}
 	c.Assert(obtained, checker.Equals, expected)
 }
diff --git a/pkg/ipcache/ipcache_test.go b/pkg/ipcache/ipcache_test.go
index 3c280c4146..f9e6ab34cd 100644
--- a/pkg/ipcache/ipcache_test.go
+++ b/pkg/ipcache/ipcache_test.go
@@ -277,9 +277,9 @@ func (s *IPCacheTestSuite) TestIPCacheNamedPorts(c *C) {
 	meta := K8sMetadata{
 		Namespace: "default",
 		PodName:   "app1",
-		NamedPorts: policy.NamedPortsMap{
-			"http": policy.NamedPort{Port: 80, Proto: uint8(u8proto.TCP)},
-			"dns":  policy.NamedPort{Port: 53},
+		NamedPorts: policy.NamedPortMap{
+			"http": policy.PortProto{Port: 80, Proto: uint8(u8proto.TCP)},
+			"dns":  policy.PortProto{Port: 53},
 		},
 	}
 
@@ -301,14 +301,12 @@ func (s *IPCacheTestSuite) TestIPCacheNamedPorts(c *C) {
 	c.Assert(cachedIdentity.Source, Equals, source.Kubernetes)
 
 	// Named ports have been updated
-	c.Assert(namedPortsChanged, Equals, true)
+	c.Assert(namedPortsChanged, Equals, false) // not before GetNamedPorts() has been called once
 	npm := IPIdentityCache.GetNamedPorts()
 	c.Assert(npm, NotNil)
 	c.Assert(len(npm), Equals, 2)
-	c.Assert(npm["http"].Port, Equals, uint16(80))
-	c.Assert(npm["http"].Proto, Equals, uint8(6)) // TCP
-	c.Assert(npm["dns"].Port, Equals, uint16(53))
-	c.Assert(npm["dns"].Proto, Equals, uint8(0)) // ANY
+	c.Assert(npm["http"], checker.HasKey, policy.PortProto{Port: uint16(80), Proto: uint8(6)})
+	c.Assert(npm["dns"], checker.HasKey, policy.PortProto{Port: uint16(53), Proto: uint8(0)})
 
 	// No duplicates.
 	c.Assert(len(IPIdentityCache.ipToIdentityCache), Equals, 1)
@@ -328,9 +326,9 @@ func (s *IPCacheTestSuite) TestIPCacheNamedPorts(c *C) {
 	meta2 := K8sMetadata{
 		Namespace: "testing",
 		PodName:   "app2",
-		NamedPorts: policy.NamedPortsMap{
-			"https": policy.NamedPort{Port: 443, Proto: uint8(u8proto.TCP)},
-			"dns":   policy.NamedPort{Port: 53},
+		NamedPorts: policy.NamedPortMap{
+			"https": policy.PortProto{Port: 443, Proto: uint8(u8proto.TCP)},
+			"dns":   policy.PortProto{Port: 53},
 		},
 	}
 
@@ -356,22 +354,17 @@ func (s *IPCacheTestSuite) TestIPCacheNamedPorts(c *C) {
 	npm = IPIdentityCache.GetNamedPorts()
 	c.Assert(npm, NotNil)
 	c.Assert(len(npm), Equals, 3)
-	c.Assert(npm["http"].Port, Equals, uint16(80))
-	c.Assert(npm["http"].Proto, Equals, uint8(6)) // TCP
-	c.Assert(npm["dns"].Port, Equals, uint16(53))
-	c.Assert(npm["dns"].Proto, Equals, uint8(0)) // ANY
-	c.Assert(npm["https"].Port, Equals, uint16(443))
-	c.Assert(npm["https"].Proto, Equals, uint8(6)) // TCP
+	c.Assert(npm["http"], checker.HasKey, policy.PortProto{Port: uint16(80), Proto: uint8(6)})
+	c.Assert(npm["dns"], checker.HasKey, policy.PortProto{Port: uint16(53), Proto: uint8(0)})
+	c.Assert(npm["https"], checker.HasKey, policy.PortProto{Port: uint16(443), Proto: uint8(6)})
 
 	namedPortsChanged = IPIdentityCache.Delete(endpointIP, source.Kubernetes)
 	c.Assert(namedPortsChanged, Equals, true)
 	npm = IPIdentityCache.GetNamedPorts()
 	c.Assert(npm, NotNil)
 	c.Assert(len(npm), Equals, 2)
-	c.Assert(npm["dns"].Port, Equals, uint16(53))
-	c.Assert(npm["dns"].Proto, Equals, uint8(0)) // ANY
-	c.Assert(npm["https"].Port, Equals, uint16(443))
-	c.Assert(npm["https"].Proto, Equals, uint8(6)) // TCP
+	c.Assert(npm["dns"], checker.HasKey, policy.PortProto{Port: uint16(53), Proto: uint8(0)})
+	c.Assert(npm["https"], checker.HasKey, policy.PortProto{Port: uint16(443), Proto: uint8(6)})
 
 	// Assure deletion occurs across all mappings.
 	c.Assert(len(IPIdentityCache.ipToIdentityCache), Equals, 1)
@@ -444,8 +437,8 @@ func (s *IPCacheTestSuite) TestIPCacheNamedPorts(c *C) {
 	// Test mapping of multiple IPs to same identity.
 	endpointIPs := []string{"192.168.0.1", "20.3.75.3", "27.2.2.2", "127.0.0.1", "127.0.0.1"}
 	identities := []identityPkg.NumericIdentity{5, 67, 29, 29, 29}
-	k8sMeta.NamedPorts = policy.NamedPortsMap{
-		"http2": policy.NamedPort{Port: 8080, Proto: uint8(u8proto.TCP)},
+	k8sMeta.NamedPorts = policy.NamedPortMap{
+		"http2": policy.PortProto{Port: 8080, Proto: uint8(u8proto.TCP)},
 	}
 
 	for index := range endpointIPs {
@@ -457,7 +450,7 @@ func (s *IPCacheTestSuite) TestIPCacheNamedPorts(c *C) {
 		c.Assert(updated, Equals, true)
 		npm = IPIdentityCache.GetNamedPorts()
 		c.Assert(npm, NotNil)
-		c.Assert(npm["http2"].Port, Equals, uint16(8080))
+		c.Assert(npm["http2"], checker.HasKey, policy.PortProto{Port: uint16(8080), Proto: uint8(6)})
 		// only the first changes named ports, as they are all the same
 		c.Assert(namedPortsChanged, Equals, index == 0)
 		cachedIdentity, _ := IPIdentityCache.LookupByIP(endpointIPs[index])
diff --git a/pkg/logging/limiter_test.go b/pkg/logging/limiter_test.go
new file mode 100644
index 0000000000..af9b2239ff
--- /dev/null
+++ b/pkg/logging/limiter_test.go
@@ -0,0 +1,59 @@
+// Copyright 2020 Authors of Cilium
+//
+// 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.
+
+// +build !privileged_tests
+
+package logging
+
+import (
+	"time"
+
+	. "gopkg.in/check.v1"
+)
+
+func (s *LoggingSuite) TestLimiter(c *C) {
+	// Set up a limiter that allows one event every half second with the burts of 3.
+	// The underlying token bucket has the capacity of three and fill rate of
+	// 2 per second.
+	limiter := NewLimiter(500*time.Millisecond, 3)
+
+	// Initially tree events should be allowed and the rest denied.
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, false)
+	c.Assert(limiter.Allow(), Equals, false)
+	c.Assert(limiter.Allow(), Equals, false)
+
+	// After half second one more event should be allowed, the rest denied
+	time.Sleep(500 * time.Millisecond)
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, false)
+	c.Assert(limiter.Allow(), Equals, false)
+
+	// After one more second two events should be allowed, the rest denied
+	time.Sleep(1 * time.Second)
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, false)
+	c.Assert(limiter.Allow(), Equals, false)
+
+	// After two more seconds three events should be allowed, the rest denied
+	time.Sleep(2 * time.Second)
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, true)
+	c.Assert(limiter.Allow(), Equals, false)
+	c.Assert(limiter.Allow(), Equals, false)
+}
diff --git a/pkg/policy/distillery_test.go b/pkg/policy/distillery_test.go
index 9214aba4dc..6af3edff0c 100644
--- a/pkg/policy/distillery_test.go
+++ b/pkg/policy/distillery_test.go
@@ -1,4 +1,4 @@
-// Copyright 2019 Authors of Cilium
+// Copyright 2019-2020 Authors of Cilium
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -58,8 +58,8 @@ func (s *DistilleryTestSuite) TestCacheManagement(c *C) {
 	c.Assert(deleted, Equals, false)
 
 	// Insert identity twice. Should be the same policy.
-	policy1, _ := cache.insert(identity)
-	policy2, _ := cache.insert(identity)
+	policy1 := cache.insert(identity)
+	policy2 := cache.insert(identity)
 	c.Assert(policy1, Equals, policy2)
 
 	// Despite two insert calls, there is no reference tracking; any delete
@@ -75,11 +75,11 @@ func (s *DistilleryTestSuite) TestCacheManagement(c *C) {
 	ep3.SetIdentity(1234, true)
 	identity3 := ep3.GetSecurityIdentity()
 	c.Assert(identity3, Not(Equals), identity)
-	policy1, _ = cache.insert(identity)
-	policy3, _ := cache.insert(identity3)
+	policy1 = cache.insert(identity)
+	policy3 := cache.insert(identity3)
 	c.Assert(policy1, Not(Equals), policy3)
 	_ = cache.delete(identity)
-	policy3, _ = cache.lookupOrCreate(identity3, false)
+	policy3 = cache.lookupOrCreate(identity3, false)
 	c.Assert(policy3, NotNil)
 }
 
@@ -90,8 +90,7 @@ func (s *DistilleryTestSuite) TestCachePopulation(c *C) {
 
 	identity1 := ep1.GetSecurityIdentity()
 	c.Assert(ep2.GetSecurityIdentity(), Equals, identity1)
-	policy1, computed := cache.insert(identity1)
-	c.Assert(computed, Equals, false)
+	policy1 := cache.insert(identity1)
 
 	// Calculate the policy and observe that it's cached
 	updated, err := cache.updateSelectorPolicy(identity1)
@@ -100,8 +99,7 @@ func (s *DistilleryTestSuite) TestCachePopulation(c *C) {
 	updated, err = cache.updateSelectorPolicy(identity1)
 	c.Assert(err, IsNil)
 	c.Assert(updated, Equals, false)
-	policy2, computed := cache.insert(identity1)
-	c.Assert(computed, Equals, true)
+	policy2 := cache.insert(identity1)
 	idp1 := policy1.(*cachedSelectorPolicy).getPolicy()
 	idp2 := policy2.(*cachedSelectorPolicy).getPolicy()
 	c.Assert(idp1, Equals, idp2)
@@ -121,14 +119,12 @@ func (s *DistilleryTestSuite) TestCachePopulation(c *C) {
 
 	// Insert endpoint with different identity and observe that the cache
 	// is different from ep1, ep2
-	policy1, computed = cache.insert(identity1)
-	c.Assert(computed, Equals, false)
+	policy1 = cache.insert(identity1)
 	idp1 = policy1.(*cachedSelectorPolicy).getPolicy()
 	c.Assert(idp1, NotNil)
 	identity3 := ep3.GetSecurityIdentity()
-	policy3, computed := cache.insert(identity3)
+	policy3 := cache.insert(identity3)
 	c.Assert(policy3, Not(Equals), policy1)
-	c.Assert(computed, Equals, false)
 	updated, err = cache.updateSelectorPolicy(identity3)
 	c.Assert(err, IsNil)
 	c.Assert(updated, Equals, true)
@@ -317,7 +313,7 @@ func (d *policyDistillery) WithLogBuffer(w io.Writer) *policyDistillery {
 
 // distillPolicy distills the policy repository into a set of bpf map state
 // entries for an endpoint with the specified labels.
-func (d *policyDistillery) distillPolicy(npMap NamedPortsMap, epLabels labels.LabelArray) (MapState, error) {
+func (d *policyDistillery) distillPolicy(owner PolicyOwner, epLabels labels.LabelArray) (MapState, error) {
 	result := make(MapState)
 
 	endpointSelected, _ := d.Repository.GetRulesMatching(epLabels)
@@ -345,7 +341,7 @@ func (d *policyDistillery) distillPolicy(npMap NamedPortsMap, epLabels labels.La
 	io.WriteString(d.log, "[distill] Producing L4 filter keys\n")
 	for _, l4 := range l4IngressPolicy {
 		io.WriteString(d.log, fmt.Sprintf("[distill] Processing L4Filter (l4: %d/%s), (l3/7: %+v)\n", l4.Port, l4.Protocol, l4.L7RulesPerSelector))
-		for key, entry := range l4.ToMapState(npMap, 0) {
+		for key, entry := range l4.ToMapState(owner, 0) {
 			io.WriteString(d.log, fmt.Sprintf("[distill] L4 ingress allow %+v (parser=%s, redirect=%t)\n", key, l4.L7Parser, entry.IsRedirectEntry()))
 			result[key] = entry
 		}
@@ -381,7 +377,7 @@ func Test_MergeL3(t *testing.T) {
 		t.Run(fmt.Sprintf("permutation_%d", tt.test), func(t *testing.T) {
 			logBuffer := new(bytes.Buffer)
 			repo = repo.WithLogBuffer(logBuffer)
-			mapstate, err := repo.distillPolicy(nil, labelsFoo)
+			mapstate, err := repo.distillPolicy(DummyOwner{}, labelsFoo)
 			if err != nil {
 				t.Errorf("Policy resolution failure: %s", err)
 			}
@@ -453,7 +449,7 @@ func Test_MergeRules(t *testing.T) {
 		t.Run(fmt.Sprintf("permutation_%d", tt.test), func(t *testing.T) {
 			logBuffer := new(bytes.Buffer)
 			repo = repo.WithLogBuffer(logBuffer)
-			mapstate, err := repo.distillPolicy(nil, labelsFoo)
+			mapstate, err := repo.distillPolicy(DummyOwner{}, labelsFoo)
 			if err != nil {
 				t.Errorf("Policy resolution failure: %s", err)
 			}
@@ -472,10 +468,6 @@ func Test_MergeRulesWithNamedPorts(t *testing.T) {
 	}
 	selectorCache := testNewSelectorCache(identityCache)
 
-	npMap := NamedPortsMap{
-		"port-80": NamedPort{Proto: uint8(0), Port: uint16(80)},
-	}
-
 	tests := []struct {
 		test   int
 		rules  api.Rules
@@ -529,7 +521,7 @@ func Test_MergeRulesWithNamedPorts(t *testing.T) {
 		t.Run(fmt.Sprintf("permutation_%d", tt.test), func(t *testing.T) {
 			logBuffer := new(bytes.Buffer)
 			repo = repo.WithLogBuffer(logBuffer)
-			mapstate, err := repo.distillPolicy(npMap, labelsFoo)
+			mapstate, err := repo.distillPolicy(DummyOwner{}, labelsFoo)
 			if err != nil {
 				t.Errorf("Policy resolution failure: %s", err)
 			}
@@ -570,7 +562,7 @@ func Test_AllowAll(t *testing.T) {
 		t.Run(fmt.Sprintf("permutation_%d", tt.test), func(t *testing.T) {
 			logBuffer := new(bytes.Buffer)
 			repo = repo.WithLogBuffer(logBuffer)
-			mapstate, err := repo.distillPolicy(nil, labelsFoo)
+			mapstate, err := repo.distillPolicy(DummyOwner{}, labelsFoo)
 			if err != nil {
 				t.Errorf("Policy resolution failure: %s", err)
 			}
diff --git a/pkg/policy/portmap_test.go b/pkg/policy/portmap_test.go
new file mode 100644
index 0000000000..d5091772a0
--- /dev/null
+++ b/pkg/policy/portmap_test.go
@@ -0,0 +1,196 @@
+// Copyright 2019 Authors of Cilium
+//
+// 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.
+
+// +build !privileged_tests
+
+package policy
+
+import (
+	"github.com/cilium/cilium/pkg/u8proto"
+
+	. "gopkg.in/check.v1"
+)
+
+func (ds *PolicyTestSuite) TestPolicyValidateName(c *C) {
+	name, err := ValidatePortName("Http")
+	c.Assert(err, IsNil)
+	c.Assert(name, Equals, "http")
+
+	name, err = ValidatePortName("dns-tcp")
+	c.Assert(err, IsNil)
+	c.Assert(name, Equals, "dns-tcp")
+
+	_, err = ValidatePortName("-http")
+	c.Assert(err, Not(IsNil))
+
+	_, err = ValidatePortName("http-")
+	c.Assert(err, Not(IsNil))
+
+	name, err = ValidatePortName("http-80")
+	c.Assert(err, IsNil)
+	c.Assert(name, Equals, "http-80")
+
+	_, err = ValidatePortName("http--s")
+	c.Assert(err, Not(IsNil))
+}
+
+func (ds *PolicyTestSuite) TestPolicyNewPortProto(c *C) {
+	np, err := newPortProto(80, "tcp")
+	c.Assert(err, IsNil)
+	c.Assert(np, Equals, PortProto{Port: uint16(80), Proto: uint8(6)})
+
+	_, err = newPortProto(88888, "tcp")
+	c.Assert(err, Not(IsNil))
+	c.Assert(err.Error(), Equals, "Port number 88888 out of 16-bit range")
+
+	_, err = newPortProto(80, "cccp")
+	c.Assert(err, Not(IsNil))
+	c.Assert(err.Error(), Equals, "unknown protocol 'cccp'")
+
+	np, err = newPortProto(88, "")
+	c.Assert(err, IsNil)
+	c.Assert(np, Equals, PortProto{Port: uint16(88), Proto: uint8(6)})
+}
+
+func (ds *PolicyTestSuite) TestPolicyNamedPortMap(c *C) {
+	npm := make(NamedPortMap)
+
+	err := npm.AddPort("http", 80, "tcp")
+	c.Assert(err, IsNil)
+	c.Assert(npm, HasLen, 1)
+
+	err = npm.AddPort("dns", 53, "UDP")
+	c.Assert(err, IsNil)
+	c.Assert(npm, HasLen, 2)
+
+	err = npm.AddPort("zero", 0, "TCP")
+	c.Assert(err, Equals, ErrNamedPortIsZero)
+	c.Assert(npm, HasLen, 2)
+
+	proto, err := u8proto.ParseProtocol("UDP")
+	c.Assert(err, IsNil)
+	c.Assert(uint8(proto), Equals, uint8(17))
+
+	port, err := npm.GetNamedPort("dns", uint8(proto))
+	c.Assert(err, IsNil)
+	c.Assert(port, Equals, uint16(53))
+
+	port, err = npm.GetNamedPort("dns", uint8(6))
+	c.Assert(err, Equals, ErrIncompatibleProtocol)
+	c.Assert(port, Equals, uint16(0))
+
+	port, err = npm.GetNamedPort("unknown", uint8(proto))
+	c.Assert(err, Equals, ErrUnknownNamedPort)
+	c.Assert(port, Equals, uint16(0))
+}
+
+func (ds *PolicyTestSuite) TestPolicyPortProtoSet(c *C) {
+	a := PortProtoSet{
+		PortProto{Port: 80, Proto: 6}:  struct{}{},
+		PortProto{Port: 443, Proto: 6}: struct{}{},
+		PortProto{Port: 53, Proto: 17}: struct{}{},
+	}
+	b := PortProtoSet{
+		PortProto{Port: 80, Proto: 6}:  struct{}{},
+		PortProto{Port: 443, Proto: 6}: struct{}{},
+		PortProto{Port: 53, Proto: 6}:  struct{}{},
+	}
+	c.Assert(a.Equal(a), Equals, true)
+	c.Assert(a.Equal(b), Equals, false)
+	c.Assert(b.Equal(b), Equals, true)
+}
+
+func (ds *PolicyTestSuite) TestPolicyNamedPortMultiMap(c *C) {
+	a := NamedPortMultiMap{
+		"http": PortProtoSet{
+			PortProto{Port: 80, Proto: 6}:   struct{}{},
+			PortProto{Port: 8080, Proto: 6}: struct{}{},
+		},
+		"https": PortProtoSet{
+			PortProto{Port: 443, Proto: 6}: struct{}{},
+		},
+		"zero": PortProtoSet{
+			PortProto{Port: 0, Proto: 6}: struct{}{},
+		},
+		"none": PortProtoSet{},
+		"dns": PortProtoSet{
+			PortProto{Port: 53, Proto: 17}: struct{}{},
+			PortProto{Port: 53, Proto: 6}:  struct{}{},
+		},
+	}
+	b := NamedPortMultiMap{
+		"http": PortProtoSet{
+			PortProto{Port: 80, Proto: 6}:   struct{}{},
+			PortProto{Port: 8080, Proto: 6}: struct{}{},
+		},
+		"https": PortProtoSet{
+			PortProto{Port: 443, Proto: 6}: struct{}{},
+		},
+		"zero": PortProtoSet{
+			PortProto{Port: 0, Proto: 6}: struct{}{},
+		},
+		"none": PortProtoSet{},
+		"dns": PortProtoSet{
+			PortProto{Port: 53, Proto: 0}: struct{}{},
+		},
+	}
+
+	c.Assert(a.Equal(a), Equals, true)
+	c.Assert(a.Equal(b), Equals, false)
+	c.Assert(b.Equal(b), Equals, true)
+
+	port, err := a.GetNamedPort("http", 6)
+	c.Assert(err, Equals, ErrDuplicateNamedPorts)
+	c.Assert(port, Equals, uint16(0))
+
+	port, err = a.GetNamedPort("http", 17)
+	c.Assert(err, Equals, ErrIncompatibleProtocol)
+	c.Assert(port, Equals, uint16(0))
+
+	port, err = a.GetNamedPort("zero", 6)
+	c.Assert(err, Equals, ErrNamedPortIsZero)
+	c.Assert(port, Equals, uint16(0))
+
+	port, err = a.GetNamedPort("none", 6)
+	c.Assert(err, Equals, ErrUnknownNamedPort)
+	c.Assert(port, Equals, uint16(0))
+
+	port, err = a.GetNamedPort("unknown", 6)
+	c.Assert(err, Equals, ErrUnknownNamedPort)
+	c.Assert(port, Equals, uint16(0))
+
+	port, err = NamedPortMultiMap(nil).GetNamedPort("unknown", 6)
+	c.Assert(err, Equals, ErrNilMap)
+	c.Assert(port, Equals, uint16(0))
+
+	port, err = a.GetNamedPort("https", 6)
+	c.Assert(err, IsNil)
+	c.Assert(port, Equals, uint16(443))
+
+	port, err = a.GetNamedPort("dns", 6)
+	c.Assert(err, IsNil)
+	c.Assert(port, Equals, uint16(53))
+
+	port, err = b.GetNamedPort("dns", 6)
+	c.Assert(err, IsNil)
+	c.Assert(port, Equals, uint16(53))
+
+	port, err = a.GetNamedPort("dns", 17)
+	c.Assert(err, IsNil)
+	c.Assert(port, Equals, uint16(53))
+
+	port, err = b.GetNamedPort("dns", 17)
+	c.Assert(err, IsNil)
+	c.Assert(port, Equals, uint16(53))
+}
diff --git a/pkg/policy/resolve_test.go b/pkg/policy/resolve_test.go
index b5f7dfdf9c..8554c96152 100644
--- a/pkg/policy/resolve_test.go
+++ b/pkg/policy/resolve_test.go
@@ -174,12 +174,16 @@ func GenerateCIDRRules(numRules int) api.Rules {
 
 type DummyOwner struct{}
 
-func (d DummyOwner) LookupRedirectPortLocked(npMap NamedPortsMap, l4 *L4Filter) uint16 {
-	// Return a fake non-0 listening port number for redirect filters.
-	if l4.IsRedirect() {
-		return 4242
-	}
-	return 0
+func (d DummyOwner) LookupRedirectPortLocked(bool, string, uint16) uint16 {
+	return 4242
+}
+
+func (d DummyOwner) GetNamedPort(ingress bool, name string, proto uint8) uint16 {
+	return 80
+}
+
+func (d DummyOwner) GetNamedPortLocked(ingress bool, name string, proto uint8) uint16 {
+	return 80
 }
 
 func (d DummyOwner) GetID() uint64 {
@@ -220,7 +224,7 @@ func (ds *PolicyTestSuite) BenchmarkRegenerateCIDRPolicyRules(c *C) {
 	c.ResetTimer()
 	for i := 0; i < c.N; i++ {
 		ip, _ := testRepo.resolvePolicyLocked(fooIdentity)
-		_ = ip.DistillPolicy(DummyOwner{}, nil, false)
+		_ = ip.DistillPolicy(DummyOwner{}, false)
 		ip.Detach()
 	}
 }
@@ -230,7 +234,7 @@ func (ds *PolicyTestSuite) BenchmarkRegenerateL3IngressPolicyRules(c *C) {
 	c.ResetTimer()
 	for i := 0; i < c.N; i++ {
 		ip, _ := testRepo.resolvePolicyLocked(fooIdentity)
-		_ = ip.DistillPolicy(DummyOwner{}, nil, false)
+		_ = ip.DistillPolicy(DummyOwner{}, false)
 		ip.Detach()
 	}
 }
@@ -240,7 +244,7 @@ func (ds *PolicyTestSuite) BenchmarkRegenerateL3EgressPolicyRules(c *C) {
 	c.ResetTimer()
 	for i := 0; i < c.N; i++ {
 		ip, _ := testRepo.resolvePolicyLocked(fooIdentity)
-		_ = ip.DistillPolicy(DummyOwner{}, nil, false)
+		_ = ip.DistillPolicy(DummyOwner{}, false)
 		ip.Detach()
 	}
 }
@@ -282,7 +286,7 @@ func (ds *PolicyTestSuite) TestL7WithIngressWildcard(c *C) {
 	defer repo.Mutex.RUnlock()
 	selPolicy, err := repo.resolvePolicyLocked(fooIdentity)
 	c.Assert(err, IsNil)
-	policy := selPolicy.DistillPolicy(DummyOwner{}, nil, false)
+	policy := selPolicy.DistillPolicy(DummyOwner{}, false)
 
 	expectedEndpointPolicy := EndpointPolicy{
 		selectorPolicy: &selectorPolicy{
@@ -371,7 +375,7 @@ func (ds *PolicyTestSuite) TestL7WithLocalHostWildcardd(c *C) {
 
 	selPolicy, err := repo.resolvePolicyLocked(fooIdentity)
 	c.Assert(err, IsNil)
-	policy := selPolicy.DistillPolicy(DummyOwner{}, nil, false)
+	policy := selPolicy.DistillPolicy(DummyOwner{}, false)
 
 	cachedSelectorHost := testSelectorCache.FindCachedIdentitySelector(api.ReservedEndpointSelectors[labels.IDNameHost])
 	c.Assert(cachedSelectorHost, Not(IsNil))
@@ -459,7 +463,7 @@ func (ds *PolicyTestSuite) TestMapStateWithIngressWildcard(c *C) {
 	defer repo.Mutex.RUnlock()
 	selPolicy, err := repo.resolvePolicyLocked(fooIdentity)
 	c.Assert(err, IsNil)
-	policy := selPolicy.DistillPolicy(DummyOwner{}, nil, false)
+	policy := selPolicy.DistillPolicy(DummyOwner{}, false)
 
 	rule1MapStateEntry := NewMapStateEntry(labels.LabelArrayList{ruleLabel}, false)
 	allowEgressMapStateEntry := NewMapStateEntry(labels.LabelArrayList{ruleLabelAllowAnyEgress}, false)
@@ -564,7 +568,7 @@ func (ds *PolicyTestSuite) TestMapStateWithIngress(c *C) {
 	defer repo.Mutex.RUnlock()
 	selPolicy, err := repo.resolvePolicyLocked(fooIdentity)
 	c.Assert(err, IsNil)
-	policy := selPolicy.DistillPolicy(DummyOwner{}, nil, false)
+	policy := selPolicy.DistillPolicy(DummyOwner{}, false)
 
 	// Add new identity to test accumulation of PolicyMapChanges
 	added1 := cache.IdentityCache{
diff --git a/pkg/proxy/logger/test/epinfo.go b/pkg/proxy/logger/test/epinfo.go
new file mode 100644
index 0000000000..e94a5feb6b
--- /dev/null
+++ b/pkg/proxy/logger/test/epinfo.go
@@ -0,0 +1,61 @@
+// Copyright 2017-2019 Authors of Cilium
+//
+// 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.
+
+// +build !privileged_tests
+
+package test
+
+import (
+	"github.com/cilium/cilium/pkg/identity"
+	"github.com/cilium/cilium/pkg/labels"
+	"github.com/cilium/cilium/pkg/lock"
+	"github.com/cilium/cilium/pkg/proxy/accesslog"
+)
+
+type ProxyUpdaterMock struct {
+	lock.RWMutex
+	Id           uint64
+	Ipv4         string
+	Ipv6         string
+	Labels       []string
+	Identity     identity.NumericIdentity
+	SidecarProxy bool
+}
+
+func (m *ProxyUpdaterMock) GetProxyInfoByFields() (uint64, string, string, []string, string, uint64, error) {
+	return m.GetID(), m.GetIPv4Address(), m.GetIPv6Address(), m.GetLabels(), m.GetLabelsSHA(), uint64(m.GetIdentityLocked()), nil
+}
+
+func (m *ProxyUpdaterMock) UnconditionalRLock() { m.RWMutex.RLock() }
+func (m *ProxyUpdaterMock) RUnlock()            { m.RWMutex.RUnlock() }
+
+func (m *ProxyUpdaterMock) GetID() uint64                                 { return m.Id }
+func (m *ProxyUpdaterMock) GetIPv4Address() string                        { return m.Ipv4 }
+func (m *ProxyUpdaterMock) GetIPv6Address() string                        { return m.Ipv6 }
+func (m *ProxyUpdaterMock) GetLabels() []string                           { return m.Labels }
+func (m *ProxyUpdaterMock) GetEgressPolicyEnabledLocked() bool            { return true }
+func (m *ProxyUpdaterMock) GetIngressPolicyEnabledLocked() bool           { return true }
+func (m *ProxyUpdaterMock) GetIdentityLocked() identity.NumericIdentity   { return m.Identity }
+func (m *ProxyUpdaterMock) GetNamedPortLocked(bool, string, uint8) uint16 { return 0 }
+func (m *ProxyUpdaterMock) GetLabelsSHA() string {
+	return labels.NewLabelsFromModel(m.Labels).SHA256Sum()
+}
+func (m *ProxyUpdaterMock) HasSidecarProxy() bool       { return m.SidecarProxy }
+func (m *ProxyUpdaterMock) ConntrackName() string       { return m.ConntrackNameLocked() }
+func (m *ProxyUpdaterMock) ConntrackNameLocked() string { return "global" }
+
+func (m *ProxyUpdaterMock) OnProxyPolicyUpdate(policyRevision uint64) {}
+func (m *ProxyUpdaterMock) UpdateProxyStatistics(l4Protocol string, port uint16, ingress, request bool,
+	verdict accesslog.FlowVerdict) {
+}
diff --git a/pkg/proxy/mock_test.go b/pkg/proxy/mock_test.go
index a7341a24ad..2e7d487ae6 100644
--- a/pkg/proxy/mock_test.go
+++ b/pkg/proxy/mock_test.go
@@ -1,4 +1,4 @@
-// Copyright 2017-2019 Authors of Cilium
+// Copyright 2017-2020 Authors of Cilium
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import (
 	"github.com/cilium/cilium/pkg/identity"
 	"github.com/cilium/cilium/pkg/labels"
 	"github.com/cilium/cilium/pkg/lock"
-	"github.com/cilium/cilium/pkg/policy"
 	"github.com/cilium/cilium/pkg/proxy/accesslog"
 )
 
@@ -41,16 +40,12 @@ func (m *proxyUpdaterMock) GetProxyInfoByFields() (uint64, string, string, []str
 func (m *proxyUpdaterMock) UnconditionalRLock() { m.RWMutex.RLock() }
 func (m *proxyUpdaterMock) RUnlock()            { m.RWMutex.RUnlock() }
 
-func (m *proxyUpdaterMock) GetID() uint64                               { return m.id }
-func (m *proxyUpdaterMock) GetIPv4Address() string                      { return m.ipv4 }
-func (m *proxyUpdaterMock) GetIPv6Address() string                      { return m.ipv6 }
-func (m *proxyUpdaterMock) GetLabels() []string                         { return m.labels }
-func (m *proxyUpdaterMock) GetEgressPolicyEnabledLocked() bool          { return true }
-func (m *proxyUpdaterMock) GetIngressPolicyEnabledLocked() bool         { return true }
-func (m *proxyUpdaterMock) GetIdentityLocked() identity.NumericIdentity { return m.identity }
-func (m *proxyUpdaterMock) ProxyID(npMap policy.NamedPortsMap, l4 *policy.L4Filter) (string, error) {
-	return "", nil
-}
+func (m *proxyUpdaterMock) GetID() uint64                                 { return m.id }
+func (m *proxyUpdaterMock) GetIPv4Address() string                        { return m.ipv4 }
+func (m *proxyUpdaterMock) GetIPv6Address() string                        { return m.ipv6 }
+func (m *proxyUpdaterMock) GetLabels() []string                           { return m.labels }
+func (m *proxyUpdaterMock) GetIdentityLocked() identity.NumericIdentity   { return m.identity }
+func (m *proxyUpdaterMock) GetNamedPortLocked(bool, string, uint8) uint16 { return 0 }
 func (m *proxyUpdaterMock) GetLabelsSHA() string {
 	return labels.NewLabelsFromModel(m.labels).SHA256Sum()
 }
diff --git a/test/docker-compose.yml b/test/docker-compose.yml
index b7d786fbd5..290e9472a0 100644
--- a/test/docker-compose.yml
+++ b/test/docker-compose.yml
@@ -15,7 +15,7 @@ services:
     command: "etcd -name etcd0 -advertise-client-urls http://0.0.0.0:4002 -listen-client-urls http://0.0.0.0:4002 -initial-cluster-token etcd-cluster-1 -initial-cluster-state new"
     privileged: true
   base_image:
-    image: "quay.io/cilium/cilium-builder:2020-07-20-v1.8@sha256:2391cd7bb88718735de64c8d46a8e4e972c433c14bcf4da8da0e7be1a746c47f"
+    image: "quay.io/cilium/cilium-builder:2020-08-07-v1.8@sha256:bf609a2e415dfd6d583ae7a4fe15627210d8963143008a3497916dff6dd597bb"
     volumes:
       - "./../:/go/src/github.com/cilium/cilium/"
     privileged: true
diff --git a/test/kubernetes-test.sh b/test/kubernetes-test.sh
index f7e9ad5408..420b0a346e 100755
--- a/test/kubernetes-test.sh
+++ b/test/kubernetes-test.sh
@@ -62,7 +62,7 @@ git clone https://github.com/kubernetes/kubernetes.git -b ${KUBERNETES_VERSION}
 cd kubernetes
 
 # Kubernetes is only compiling with golang 1.13.4 for versions >=1.17
-GO_VERSION="1.14.6"
+GO_VERSION="1.14.7"
 sudo rm -fr /usr/local/go
 curl -LO https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz
 sudo tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
diff --git a/test/packet/scripts/install.sh b/test/packet/scripts/install.sh
index 0ebc39dc73..c1e5aedf05 100644
--- a/test/packet/scripts/install.sh
+++ b/test/packet/scripts/install.sh
@@ -5,7 +5,7 @@ set -e
 # Ensure no prompts from apt & co.
 export DEBIAN_FRONTEND=noninteractive
 
-GOLANG_VERSION="1.14.6"
+GOLANG_VERSION="1.14.7"
 VAGRANT_VERSION="2.2.4"
 PACKER_VERSION="1.3.5"
 VIRTUALBOX_VERSION="6.0"