in pilot/pkg/networking/core/v1alpha3/listener.go [290:518]
func (lb *ListenerBuilder) buildSidecarOutboundListeners(node *model.Proxy,
push *model.PushContext) []*listener.Listener {
noneMode := node.GetInterceptionMode() == model.InterceptionNone
actualWildcard, actualLocalHostAddress := getActualWildcardAndLocalHost(node)
var tcpListeners, httpListeners []*listener.Listener
// For conflict resolution
listenerMap := make(map[string]*outboundListenerEntry)
// The sidecarConfig if provided could filter the list of
// services/virtual services that we need to process. It could also
// define one or more listeners with specific ports. Once we generate
// listeners for these user specified ports, we will auto generate
// configs for other ports if and only if the sidecarConfig has an
// egressListener on wildcard port.
//
// Validation will ensure that we have utmost one wildcard egress listener
// occurring in the end
// Add listeners based on the config in the sidecar.EgressListeners if
// no Sidecar CRD is provided for this config namespace,
// push.SidecarScope will generate a default catch all egress listener.
for _, egressListener := range node.SidecarScope.EgressListeners {
services := egressListener.Services()
virtualServices := egressListener.VirtualServices()
// determine the bindToPort setting for listeners
bindToPort := false
if noneMode {
// do not care what the listener's capture mode setting is. The proxy does not use iptables
bindToPort = true
} else if egressListener.IstioListener != nil {
if egressListener.IstioListener.CaptureMode == networking.CaptureMode_NONE {
// proxy uses iptables redirect or tproxy. IF mode is not set
// for older proxies, it defaults to iptables redirect. If the
// listener's capture mode specifies NONE, then the proxy wants
// this listener alone to be on a physical port. If the
// listener's capture mode is default, then its same as
// iptables i.e. bindToPort is false.
bindToPort = true
} else if strings.HasPrefix(egressListener.IstioListener.Bind, model.UnixAddressPrefix) {
// If the bind is a Unix domain socket, set bindtoPort to true as it makes no
// sense to have ORIG_DST listener for unix domain socket listeners.
bindToPort = true
}
}
if egressListener.IstioListener != nil &&
egressListener.IstioListener.Port != nil {
// We have a non catch all listener on some user specified port
// The user specified port may or may not match a service port.
// If it does not match any service port and the service has only
// one port, then we pick a default service port. If service has
// multiple ports, we expect the user to provide a virtualService
// that will route to a proper Service.
// Skip ports we cannot bind to
if !node.CanBindToPort(bindToPort, egressListener.IstioListener.Port.Number) {
log.Warnf("buildSidecarOutboundListeners: skipping privileged sidecar port %d for node %s as it is an unprivileged proxy",
egressListener.IstioListener.Port.Number, node.ID)
continue
}
listenPort := &model.Port{
Port: int(egressListener.IstioListener.Port.Number),
Protocol: protocol.Parse(egressListener.IstioListener.Port.Protocol),
Name: egressListener.IstioListener.Port.Name,
}
// If capture mode is NONE i.e., bindToPort is true, and
// Bind IP + Port is specified, we will bind to the specified IP and Port.
// This specified IP is ideally expected to be a loopback IP.
//
// If capture mode is NONE i.e., bindToPort is true, and
// only Port is specified, we will bind to the default loopback IP
// 127.0.0.1 and the specified Port.
//
// If capture mode is NONE, i.e., bindToPort is true, and
// only Bind IP is specified, we will bind to the specified IP
// for each port as defined in the service registry.
//
// If captureMode is not NONE, i.e., bindToPort is false, then
// we will bind to user specified IP (if any) or to the VIPs of services in
// this egress listener.
bind := egressListener.IstioListener.Bind
if bind == "" {
if bindToPort {
bind = actualLocalHostAddress
} else {
bind = actualWildcard
}
}
// Build ListenerOpts and PluginParams once and reuse across all Services to avoid unnecessary allocations.
listenerOpts := buildListenerOpts{
push: push,
proxy: node,
bind: bind,
port: listenPort,
bindToPort: bindToPort,
}
for _, service := range services {
listenerOpts.service = service
// Set service specific attributes here.
lb.buildSidecarOutboundListenerForPortOrUDS(listenerOpts, listenerMap, virtualServices, actualWildcard)
}
} else {
// This is a catch all egress listener with no port. This
// should be the last egress listener in the sidecar
// Scope. Construct a listener for each service and service
// port, if and only if this port was not specified in any of
// the preceding listeners from the sidecarScope. This allows
// users to specify a trimmed set of services for one or more
// listeners and then add a catch all egress listener for all
// other ports. Doing so allows people to restrict the set of
// services exposed on one or more listeners, and avoid hard
// port conflicts like tcp taking over http or http taking over
// tcp, or simply specify that of all the listeners that Istio
// generates, the user would like to have only specific sets of
// services exposed on a particular listener.
//
// To ensure that we do not add anything to listeners we have
// already generated, run through the outboundListenerEntry map and set
// the locked bit to true.
// buildSidecarOutboundListenerForPortOrUDS will not add/merge
// any HTTP/TCP listener if there is already a outboundListenerEntry
// with locked bit set to true
for _, e := range listenerMap {
e.locked = true
}
bind := ""
if egressListener.IstioListener != nil && egressListener.IstioListener.Bind != "" {
bind = egressListener.IstioListener.Bind
}
if bindToPort && bind == "" {
bind = actualLocalHostAddress
}
// Build ListenerOpts and PluginParams once and reuse across all Services to avoid unnecessary allocations.
listenerOpts := buildListenerOpts{
push: push,
proxy: node,
bindToPort: bindToPort,
}
for _, service := range services {
saddress := service.GetAddressForProxy(node)
for _, servicePort := range service.Ports {
// Skip ports we cannot bind to
if !node.CanBindToPort(bindToPort, uint32(servicePort.Port)) {
// here, we log at DEBUG level instead of WARN to avoid noise
// when the catch all egress listener hits ports 80 and 443
log.Debugf("buildSidecarOutboundListeners: skipping privileged sidecar port %d for node %s as it is an unprivileged proxy",
servicePort.Port, node.ID)
continue
}
// bind might have been modified by below code, so reset it for every Service.
listenerOpts.bind = bind
// port depends on servicePort.
listenerOpts.port = servicePort
listenerOpts.service = service
// Support statefulsets/headless services with TCP ports, and empty service address field.
// Instead of generating a single 0.0.0.0:Port listener, generate a listener
// for each instance. HTTP services can happily reside on 0.0.0.0:PORT and use the
// wildcard route match to get to the appropriate IP through original dst clusters.
if features.EnableHeadlessService && bind == "" && service.Resolution == model.Passthrough &&
saddress == constants.UnspecifiedIP && (servicePort.Protocol.IsTCP() || servicePort.Protocol.IsUnsupported()) {
instances := push.ServiceInstancesByPort(service, servicePort.Port, nil)
if service.Attributes.ServiceRegistry != provider.Kubernetes && len(instances) == 0 && service.Attributes.LabelSelectors == nil {
// A Kubernetes service with no endpoints means there are no endpoints at
// all, so don't bother sending, as traffic will never work. If we did
// send a wildcard listener, we may get into a situation where a scale
// down leads to a listener conflict. Similarly, if we have a
// labelSelector on the Service, then this may have endpoints not yet
// selected or scaled down, so we skip these as well. This leaves us with
// only a plain ServiceEntry with resolution NONE. In this case, we will
// fallback to a wildcard listener.
lb.buildSidecarOutboundListenerForPortOrUDS(listenerOpts, listenerMap, virtualServices, actualWildcard)
continue
}
for _, instance := range instances {
// Make sure each endpoint address is a valid address
// as service entries could have NONE resolution with label selectors for workload
// entries (which could technically have hostnames).
if net.ParseIP(instance.Endpoint.Address) == nil {
continue
}
// Skip build outbound listener to the node itself,
// as when app access itself by pod ip will not flow through this listener.
// Simultaneously, it will be duplicate with inbound listener.
if instance.Endpoint.Address == node.IPAddresses[0] {
continue
}
listenerOpts.bind = instance.Endpoint.Address
lb.buildSidecarOutboundListenerForPortOrUDS(listenerOpts, listenerMap, virtualServices, actualWildcard)
}
} else {
// Standard logic for headless and non headless services
lb.buildSidecarOutboundListenerForPortOrUDS(listenerOpts, listenerMap, virtualServices, actualWildcard)
}
}
}
}
}
// Now validate all the listeners. Collate the tcp listeners first and then the HTTP listeners
// TODO: This is going to be bad for caching as the order of listeners in tcpListeners or httpListeners is not
// guaranteed.
for _, l := range listenerMap {
if l.servicePort.Protocol.IsTCP() {
tcpListeners = append(tcpListeners, l.listener)
} else {
httpListeners = append(httpListeners, l.listener)
}
}
tcpListeners = append(tcpListeners, httpListeners...)
// Build pass through filter chains now that all the non-passthrough filter chains are ready.
for _, l := range tcpListeners {
appendListenerFallthroughRouteForCompleteListener(l, node, push)
}
removeListenerFilterTimeout(tcpListeners)
return tcpListeners
}