// Copyright (c) 2022 Alibaba Group Holding Ltd. // // 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 proxy import ( "fmt" "regexp" "strings" "istio.io/api/networking/v1alpha3" apiv1 "github.com/alibaba/higress/v2/api/networking/v1" "github.com/alibaba/higress/v2/pkg/common" ingress "github.com/alibaba/higress/v2/pkg/ingress/kube/common" "github.com/alibaba/higress/v2/pkg/ingress/kube/util" ) const ( proxyPortRangeStart uint32 = 50001 proxyPortRangeEnd uint32 = 51000 // Exclusive defaultProxyConnectTimeout = 1200 proxyClusterPatchTemplate = `{ "name": "{{name}}", "connect_timeout": "{{connect_timeout}}ms", "type": "{{type}}", "dns_lookup_family": "V4_ONLY", "load_assignment": { "cluster_name": "{{name}}", "endpoints": [ { "lb_endpoints": [ { "endpoint": { "address": { "socket_address": { "address": "{{address}}", "port_value": {{port}} } } } } ] } ] } }` proxyListenerPatchTemplate = `{ "name": "istio-autogenerated-proxy-listener-{{proxy_name}}", "address": { "socket_address": { "address": "127.0.0.1", "port_value": {{port}} } }, "listener_filters": [ { "name": "envoy.filters.listener.tls_inspector", "typed_config": { "@type": "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector" } } ], "filter_chains": [ { "filters": [ { "name": "tcp", "typed_config": { "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", "stat_prefix": "tcpproxy.{{proxy_name}}", "cluster": "{{cluster_name}}", "tunneling_config": { "hostname": "%REQUESTED_SERVER_NAME%:443" } } } ] } ] }` ) var ( configPatchesBuilders = map[common.ProxyType]func(*apiv1.ProxyConfig) []*v1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ common.ProxyType_HTTP: buildConfigPatchesForHttpProxy, } ipv4AddressRegexp = regexp.MustCompile("(\\b25[0-5]|\\b2[0-4][0-9]|\\b[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}") ) func NeedToFillProxyListenerPorts(proxies []*apiv1.ProxyConfig) bool { if proxies == nil || len(proxies) == 0 { return false } for _, proxy := range proxies { if proxy.ListenerPort <= 0 { return true } } return false } func FillProxyListenerPorts(proxies []*apiv1.ProxyConfig) bool { if proxies == nil || len(proxies) == 0 { return false } filled := false usedPorts := make(map[uint32]bool) for _, proxy := range proxies { if proxy.ListenerPort > 0 { usedPorts[proxy.ListenerPort] = true } } for _, proxy := range proxies { if proxy.ListenerPort > 0 { continue } for port := proxyPortRangeStart; port < proxyPortRangeEnd; port++ { if !usedPorts[port] { proxy.ListenerPort = port usedPorts[port] = true filled = true break } } } return filled } func BuildProxyWrapper(config *apiv1.ProxyConfig) *ingress.ProxyWrapper { if config == nil { return nil } if len(config.ServerAddress) == 0 || config.ServerPort <= 0 || config.ServerPort > 65535 || config.ListenerPort <= 0 || config.ListenerPort > 65535 { return nil } envoyFilter := buildEnvoyFilter(config) if envoyFilter == nil { return nil } return &ingress.ProxyWrapper{ ProxyName: config.Name, ListenerPort: config.ListenerPort, EnvoyFilter: envoyFilter, } } func buildEnvoyFilter(config *apiv1.ProxyConfig) *v1alpha3.EnvoyFilter { if config == nil { return nil } configPatchesBuilder := configPatchesBuilders[common.ParseProxyType(config.Type)] if configPatchesBuilder == nil { return nil } configPatches := configPatchesBuilder(config) return &v1alpha3.EnvoyFilter{ConfigPatches: configPatches} } func buildConfigPatchesForHttpProxy(config *apiv1.ProxyConfig) []*v1alpha3.EnvoyFilter_EnvoyConfigObjectPatch { if common.ParseProxyType(config.Type) != common.ProxyType_HTTP { return nil } clusterName := buildClusterName(config) var patches []*v1alpha3.EnvoyFilter_EnvoyConfigObjectPatch // Add a cluster for the proxy server proxyClusterPatchJson := proxyClusterPatchTemplate { clusterType := "" if ipv4AddressRegexp.MatchString(config.ServerAddress) { clusterType = "STATIC" } else { clusterType = "STRICT_DNS" } connectTimeout := config.ConnectTimeout if connectTimeout <= 0 { connectTimeout = defaultProxyConnectTimeout } proxyClusterPatchJson = strings.ReplaceAll(proxyClusterPatchJson, "{{name}}", clusterName) proxyClusterPatchJson = strings.ReplaceAll(proxyClusterPatchJson, "{{type}}", clusterType) proxyClusterPatchJson = strings.ReplaceAll(proxyClusterPatchJson, "{{connect_timeout}}", fmt.Sprintf("%d", connectTimeout)) proxyClusterPatchJson = strings.ReplaceAll(proxyClusterPatchJson, "{{address}}", config.ServerAddress) proxyClusterPatchJson = strings.ReplaceAll(proxyClusterPatchJson, "{{port}}", fmt.Sprintf("%d", config.ServerPort)) } proxyClusterPatch := &v1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ ApplyTo: v1alpha3.EnvoyFilter_CLUSTER, Match: &v1alpha3.EnvoyFilter_EnvoyConfigObjectMatch{ Context: v1alpha3.EnvoyFilter_GATEWAY, }, Patch: &v1alpha3.EnvoyFilter_Patch{ Operation: v1alpha3.EnvoyFilter_Patch_ADD, Value: util.BuildPatchStruct(proxyClusterPatchJson), }, } patches = append(patches, proxyClusterPatch) // Add a listener to accept requests from the gateway itself proxyListenerPatchJson := proxyListenerPatchTemplate proxyListenerPatchJson = strings.ReplaceAll(proxyListenerPatchJson, "{{proxy_name}}", config.Name) proxyListenerPatchJson = strings.ReplaceAll(proxyListenerPatchJson, "{{port}}", fmt.Sprintf("%d", config.ListenerPort)) proxyListenerPatchJson = strings.ReplaceAll(proxyListenerPatchJson, "{{cluster_name}}", clusterName) proxyListenerPatch := &v1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ ApplyTo: v1alpha3.EnvoyFilter_LISTENER, Match: &v1alpha3.EnvoyFilter_EnvoyConfigObjectMatch{ Context: v1alpha3.EnvoyFilter_GATEWAY, }, Patch: &v1alpha3.EnvoyFilter_Patch{ Operation: v1alpha3.EnvoyFilter_Patch_ADD, Value: util.BuildPatchStruct(proxyListenerPatchJson), }, } patches = append(patches, proxyListenerPatch) return patches } func buildClusterName(config *apiv1.ProxyConfig) string { if config == nil { return "" } return fmt.Sprintf("outbound|%d||%s.proxy", config.ServerPort, config.Name) }