in pilot/pkg/networking/core/v1alpha3/listener.go [787:1046]
func (lb *ListenerBuilder) buildSidecarOutboundListenerForPortOrUDS(listenerOpts buildListenerOpts,
listenerMap map[string]*outboundListenerEntry, virtualServices []config.Config, actualWildcard string) {
var listenerMapKey string
var currentListenerEntry *outboundListenerEntry
var ret bool
var opts []*filterChainOpts
listenerOpts.class = istionetworking.ListenerClassSidecarOutbound
conflictType := NoConflict
outboundSniffingEnabled := features.EnableProtocolSniffingForOutbound
listenerPortProtocol := listenerOpts.port.Protocol
listenerProtocol := istionetworking.ModelProtocolToListenerProtocol(listenerOpts.port.Protocol, core.TrafficDirection_OUTBOUND)
// For HTTP_PROXY protocol defined by sidecars, just create the HTTP listener right away.
if listenerPortProtocol == protocol.HTTP_PROXY {
if ret, opts = buildSidecarOutboundHTTPListenerOptsForPortOrUDS(&listenerMapKey, ¤tListenerEntry,
&listenerOpts, listenerMap, actualWildcard); !ret {
return
}
listenerOpts.filterChainOpts = opts
} else {
switch listenerProtocol {
case istionetworking.ListenerProtocolHTTP:
if ret, opts = buildSidecarOutboundHTTPListenerOptsForPortOrUDS(&listenerMapKey,
¤tListenerEntry, &listenerOpts, listenerMap, actualWildcard); !ret {
return
}
// Check if conflict happens
if outboundSniffingEnabled && currentListenerEntry != nil {
// Build HTTP listener. If current listener entry is using HTTP or protocol sniffing,
// append the service. Otherwise (TCP), change current listener to use protocol sniffing.
if currentListenerEntry.protocol.IsHTTP() {
// conflictType is HTTPOverHTTP
// In these cases, we just add the services and exit early rather than recreate an identical listener
currentListenerEntry.services = append(currentListenerEntry.services, listenerOpts.service)
return
} else if currentListenerEntry.protocol.IsTCP() {
conflictType = HTTPOverTCP
} else {
// conflictType is HTTPOverAuto
// In these cases, we just add the services and exit early rather than recreate an identical listener
currentListenerEntry.services = append(currentListenerEntry.services, listenerOpts.service)
return
}
}
// Add application protocol filter chain match to the http filter chain. The application protocol will be set by http inspector
// Since application protocol filter chain match has been added to the http filter chain, a fall through filter chain will be
// appended to the listener later to allow arbitrary egress TCP traffic pass through when its port is conflicted with existing
// HTTP services, which can happen when a pod accesses a non registry service.
if outboundSniffingEnabled {
if listenerOpts.bind == actualWildcard {
for _, opt := range opts {
if opt.match == nil {
opt.match = &listener.FilterChainMatch{}
}
// Support HTTP/1.0, HTTP/1.1 and HTTP/2
opt.match.ApplicationProtocols = append(opt.match.ApplicationProtocols, plaintextHTTPALPNs...)
opt.match.TransportProtocol = xdsfilters.RawBufferTransportProtocol
}
listenerOpts.needHTTPInspector = true
// if we have a tcp fallthrough filter chain, this is no longer an HTTP listener - it
// is instead "unsupported" (auto detected), as we have a TCP and HTTP filter chain with
// inspection to route between them
listenerPortProtocol = protocol.Unsupported
}
}
listenerOpts.filterChainOpts = opts
case istionetworking.ListenerProtocolTCP:
if ret, opts = buildSidecarOutboundTCPListenerOptsForPortOrUDS(&listenerMapKey, ¤tListenerEntry,
&listenerOpts, listenerMap, virtualServices, actualWildcard); !ret {
return
}
// Check if conflict happens
if outboundSniffingEnabled && currentListenerEntry != nil {
// Build TCP listener. If current listener entry is using HTTP, add a new TCP filter chain
// If current listener is using protocol sniffing, merge the TCP filter chains.
if currentListenerEntry.protocol.IsHTTP() {
conflictType = TCPOverHTTP
} else if currentListenerEntry.protocol.IsTCP() {
conflictType = TCPOverTCP
} else {
conflictType = TCPOverAuto
}
}
listenerOpts.filterChainOpts = opts
case istionetworking.ListenerProtocolAuto:
// Add tcp filter chain, build TCP filter chain first.
if ret, opts = buildSidecarOutboundTCPListenerOptsForPortOrUDS(&listenerMapKey, ¤tListenerEntry,
&listenerOpts, listenerMap, virtualServices, actualWildcard); !ret {
return
}
listenerOpts.filterChainOpts = append(listenerOpts.filterChainOpts, opts...)
// Add http filter chain and tcp filter chain to the listener opts
if ret, opts = buildSidecarOutboundHTTPListenerOptsForPortOrUDS(&listenerMapKey, ¤tListenerEntry,
&listenerOpts, listenerMap, actualWildcard); !ret {
return
}
// Add application protocol filter chain match to the http filter chain. The application protocol will be set by http inspector
for _, opt := range opts {
if opt.match == nil {
opt.match = &listener.FilterChainMatch{}
}
// Support HTTP/1.0, HTTP/1.1 and HTTP/2
opt.match.ApplicationProtocols = append(opt.match.ApplicationProtocols, plaintextHTTPALPNs...)
opt.match.TransportProtocol = xdsfilters.RawBufferTransportProtocol
}
listenerOpts.filterChainOpts = append(listenerOpts.filterChainOpts, opts...)
listenerOpts.needHTTPInspector = true
if currentListenerEntry != nil {
if currentListenerEntry.protocol.IsHTTP() {
conflictType = AutoOverHTTP
} else if currentListenerEntry.protocol.IsTCP() {
conflictType = AutoOverTCP
} else {
// conflictType is AutoOverAuto
// In these cases, we just add the services and exit early rather than recreate an identical listener
currentListenerEntry.services = append(currentListenerEntry.services, listenerOpts.service)
return
}
}
default:
// UDP or other protocols: no need to log, it's too noisy
return
}
}
// Lets build the new listener with the filter chains. In the end, we will
// merge the filter chains with any existing listener on the same port/bind point
l := buildListener(listenerOpts, core.TrafficDirection_OUTBOUND)
mutable := &MutableListener{
MutableObjects: istionetworking.MutableObjects{
Listener: l,
FilterChains: getPluginFilterChain(listenerOpts),
},
}
// Filters are serialized one time into an opaque struct once we have the complete list.
if err := mutable.build(lb, listenerOpts); err != nil {
log.Warn("buildSidecarOutboundListeners: ", err.Error())
return
}
// If there is a TCP listener on well known port, cannot add any http filter chain
// with the inspector as it will break for server-first protocols. Similarly,
// if there was a HTTP listener on well known port, cannot add a tcp listener
// with the inspector as inspector breaks all server-first protocols.
if currentListenerEntry != nil &&
!isConflictWithWellKnownPort(listenerOpts.port.Protocol, currentListenerEntry.protocol, conflictType) {
log.Warnf("conflict happens on a well known port %d, incoming protocol %v, existing protocol %v, conflict type %v",
listenerOpts.port.Port, listenerOpts.port.Protocol, currentListenerEntry.protocol, conflictType)
return
}
// There are 9 types conflicts
// Incoming Existing
// 1. HTTP -> HTTP
// 2. HTTP -> TCP
// 3. HTTP -> unknown
// 4. TCP -> HTTP
// 5. TCP -> TCP
// 6. TCP -> unknown
// 7. unknown -> HTTP
// 8. unknown -> TCP
// 9. unknown -> unknown
// Type 1 can be resolved by appending service to existing services
// Type 2 can be resolved by merging TCP filter chain with HTTP filter chain
// Type 3 can be resolved by appending service to existing services
// Type 4 can be resolved by merging HTTP filter chain with TCP filter chain
// Type 5 can be resolved by merging TCP filter chains
// Type 6 can be resolved by merging TCP filter chains
// Type 7 can be resolved by appending service to existing services
// Type 8 can be resolved by merging TCP filter chains
// Type 9 can be resolved by merging TCP and HTTP filter chains
switch conflictType {
case NoConflict:
if currentListenerEntry != nil {
currentListenerEntry.listener.FilterChains = mergeTCPFilterChains(mutable.Listener.FilterChains,
listenerOpts, listenerMapKey, listenerMap)
} else {
listenerMap[listenerMapKey] = &outboundListenerEntry{
services: []*model.Service{listenerOpts.service},
servicePort: listenerOpts.port,
bind: listenerOpts.bind,
listener: mutable.Listener,
protocol: listenerPortProtocol,
}
}
case HTTPOverTCP:
// Merge HTTP filter chain to TCP filter chain
currentListenerEntry.listener.FilterChains = mergeFilterChains(mutable.Listener.FilterChains, currentListenerEntry.listener.FilterChains)
currentListenerEntry.protocol = protocol.Unsupported
currentListenerEntry.listener.ListenerFilters = appendListenerFilters(currentListenerEntry.listener.ListenerFilters)
currentListenerEntry.services = append(currentListenerEntry.services, listenerOpts.service)
case TCPOverHTTP:
// Merge TCP filter chain to HTTP filter chain
currentListenerEntry.listener.FilterChains = mergeFilterChains(currentListenerEntry.listener.FilterChains, mutable.Listener.FilterChains)
currentListenerEntry.protocol = protocol.Unsupported
currentListenerEntry.listener.ListenerFilters = appendListenerFilters(currentListenerEntry.listener.ListenerFilters)
case TCPOverTCP:
// Merge two TCP filter chains. HTTP filter chain will not conflict with TCP filter chain because HTTP filter chain match for
// HTTP filter chain is different from TCP filter chain's.
currentListenerEntry.listener.FilterChains = mergeTCPFilterChains(mutable.Listener.FilterChains, listenerOpts, listenerMapKey, listenerMap)
case TCPOverAuto:
// Merge two TCP filter chains. HTTP filter chain will not conflict with TCP filter chain because HTTP filter chain match for
// HTTP filter chain is different from TCP filter chain's.
currentListenerEntry.listener.FilterChains = mergeTCPFilterChains(mutable.Listener.FilterChains, listenerOpts, listenerMapKey, listenerMap)
case AutoOverHTTP:
listenerMap[listenerMapKey] = &outboundListenerEntry{
services: append(currentListenerEntry.services, listenerOpts.service),
servicePort: listenerOpts.port,
bind: listenerOpts.bind,
listener: mutable.Listener,
protocol: protocol.Unsupported,
}
currentListenerEntry.listener.ListenerFilters = appendListenerFilters(currentListenerEntry.listener.ListenerFilters)
case AutoOverTCP:
// Merge two TCP filter chains. HTTP filter chain will not conflict with TCP filter chain because HTTP filter chain match for
// HTTP filter chain is different from TCP filter chain's.
currentListenerEntry.listener.FilterChains = mergeTCPFilterChains(mutable.Listener.FilterChains,
listenerOpts, listenerMapKey, listenerMap)
currentListenerEntry.protocol = protocol.Unsupported
currentListenerEntry.listener.ListenerFilters = appendListenerFilters(currentListenerEntry.listener.ListenerFilters)
default:
// Covered previously - in this case we return early to prevent creating listeners that we end up throwing away
// This should never happen
log.Errorf("Got unexpected conflict type %v. This should never happen", conflictType)
}
if log.DebugEnabled() && len(mutable.Listener.FilterChains) > 1 || currentListenerEntry != nil {
var numChains int
if currentListenerEntry != nil {
numChains = len(currentListenerEntry.listener.FilterChains)
} else {
numChains = len(mutable.Listener.FilterChains)
}
log.Debugf("buildSidecarOutboundListeners: multiple filter chain listener %s with %d chains", mutable.Listener.Name, numChains)
}
}