github.com/fafucoder/cilium@v1.6.11/proxylib/npds/client.go (about) 1 // Copyright 2018 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package npds 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "time" 22 23 "github.com/cilium/cilium/pkg/backoff" 24 "github.com/cilium/cilium/pkg/lock" 25 "github.com/cilium/cilium/proxylib/proxylib" 26 27 "github.com/cilium/proxy/go/cilium/api" 28 envoy_api_v2 "github.com/cilium/proxy/go/envoy/api/v2" 29 envoy_api_v2_core "github.com/cilium/proxy/go/envoy/api/v2/core" 30 log "github.com/sirupsen/logrus" 31 "google.golang.org/genproto/googleapis/rpc/status" 32 "google.golang.org/grpc" 33 ) 34 35 const ( 36 DialDelay = 100 * time.Millisecond 37 BackOffLimit = 100 // Max 100 times DialDelay 38 NPDSTypeURL = "type.googleapis.com/cilium.NetworkPolicy" 39 ) 40 41 type Client struct { 42 updater proxylib.PolicyUpdater 43 mutex lock.Mutex 44 nodeId string 45 path string 46 conn *grpc.ClientConn 47 stream grpc.ClientStream 48 closing bool 49 } 50 51 func (c *Client) Close() { 52 c.mutex.Lock() 53 defer c.mutex.Unlock() 54 if !c.closing { 55 log.Debugf("NPDS: Client %s closing on %s", c.nodeId, c.path) 56 c.closing = true 57 if c.stream != nil { 58 c.stream.CloseSend() 59 } 60 if c.conn != nil { 61 c.conn.Close() 62 } 63 } 64 } 65 66 func (c *Client) Path() string { 67 return c.path 68 } 69 70 func NewClient(path, nodeId string, updater proxylib.PolicyUpdater) proxylib.PolicyClient { 71 if path == "" { 72 return nil 73 } 74 c := &Client{ 75 updater: updater, 76 path: path, 77 nodeId: nodeId, 78 } 79 log.Debugf("NPDS: Client %s starting on %s", c.nodeId, c.path) 80 81 // These are used to return error if the 1st try fails 82 // Only used for testing and logging, as we keep on trying anyway. 83 startErr := make(chan error) // Channel open as long as 'starting == true' 84 85 BackOff := backoff.Exponential{ 86 Min: DialDelay, 87 Max: BackOffLimit * DialDelay, 88 Name: "proxylib NPDS client", 89 } 90 91 go func() { 92 starting := true 93 backOff := BackOff 94 for { 95 err := c.Run(func() { 96 // Report successful start on the first try by closing the channel 97 if starting { 98 close(startErr) 99 starting = false 100 } 101 log.Debugf("NPDS: Client %s connected on %s", c.nodeId, c.path) 102 }) 103 c.mutex.Lock() 104 closing := c.closing 105 c.mutex.Unlock() 106 107 if err != nil { 108 log.Debug(err) 109 if starting { 110 startErr <- err 111 close(startErr) 112 starting = false 113 } 114 } else { 115 // Reset backoff after successful start 116 backOff = BackOff 117 } 118 119 if closing { 120 break 121 } 122 123 // Back off before retrying 124 backOff.Wait(context.TODO()) 125 } 126 }() 127 128 // Block until we know if the first connection try succeeded or failed 129 _ = <-startErr 130 return c 131 } 132 133 func (c *Client) Run(connected func()) (err error) { 134 unixPath := "unix://" + c.path 135 136 defer func() { 137 // Recover from any possible panics 138 if r := recover(); r != nil { 139 err = fmt.Errorf("NPDS Client %s: Panic: %v", c.nodeId, r) 140 } 141 }() 142 143 // 144 // WithInsecure() is safe here because we are connecting to a Unix-domain socket, 145 // data of whch is never on the wire and security for which can be managed with file permissions. 146 // 147 conn, err := grpc.Dial(unixPath, grpc.WithInsecure()) 148 if err != nil { 149 return fmt.Errorf("NPDS: Client %s grpc.Dial() on %s failed: %s", c.nodeId, c.path, err) 150 } 151 client := cilium.NewNetworkPolicyDiscoveryServiceClient(conn) 152 stream, err := client.StreamNetworkPolicies(context.Background()) 153 if err != nil { 154 conn.Close() 155 return fmt.Errorf("NPDS: Client %s stream failed on %s: %s", c.nodeId, c.path, err) 156 } 157 c.mutex.Lock() 158 c.conn = conn 159 c.stream = stream 160 c.mutex.Unlock() 161 defer func() { 162 c.mutex.Lock() 163 c.stream.CloseSend() 164 c.conn.Close() 165 c.mutex.Unlock() 166 }() 167 168 // VersionInfo must be empty as we have not received anything yet. 169 // ResourceNames is empty to request for all policies. 170 // ResponseNonce is copied from the response, initially empty. 171 req := envoy_api_v2.DiscoveryRequest{ 172 TypeUrl: NPDSTypeURL, 173 VersionInfo: "", 174 Node: &envoy_api_v2_core.Node{Id: c.nodeId}, 175 ResourceNames: nil, 176 ResponseNonce: "", 177 } 178 err = stream.Send(&req) 179 if err != nil { 180 return fmt.Errorf("NPDS: Client %s stream.Send() failed on %s: %s", c.nodeId, c.path, err) 181 } 182 183 connected() 184 185 for { 186 // Receive next policy configuration. This will block until the 187 // server has a new version to send, which may take a long time. 188 resp, err := stream.Recv() 189 if err == io.EOF || err == io.ErrUnexpectedEOF { 190 log.Debugf("NPDS: Client %s stream on %s closed.", c.nodeId, c.path) 191 break 192 } 193 if err != nil { 194 return fmt.Errorf("NPDS: Client %s stream.Recv() on %s failed: %s", c.nodeId, c.path, err) 195 } 196 197 // Validate the response 198 if resp.TypeUrl != req.TypeUrl { 199 msg := fmt.Sprintf("NPDS: Client %s rejecting mismatching resource type on %s: %s", c.nodeId, c.path, resp.TypeUrl) 200 req.ErrorDetail = &status.Status{Message: msg} 201 log.Warning(msg) 202 } else { 203 err = c.updater.PolicyUpdate(resp) 204 if err != nil { 205 msg := fmt.Sprintf("NPDS: Client %s rejecting invalid policy on %s: %s", c.nodeId, c.path, err) 206 req.ErrorDetail = &status.Status{Message: msg} 207 log.Warning(msg) 208 } else { 209 // Success, update the last applied version 210 log.Debugf("NPDS: Client %s acking new policy version on %s: %s", c.nodeId, c.path, resp.VersionInfo) 211 req.ErrorDetail = nil 212 req.VersionInfo = resp.VersionInfo 213 } 214 } 215 req.ResponseNonce = resp.Nonce 216 err = stream.Send(&req) 217 if err != nil { 218 return fmt.Errorf("NPDS: Client %s stream.Send() failed on %s: %s", c.nodeId, c.path, err) 219 } 220 } 221 return nil 222 }