github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/client/client.go (about) 1 // Copyright 2018 Google Inc. 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 // https://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 client contains the components and utilities that every Fleetspeak client should include. 16 package client 17 18 import ( 19 "bytes" 20 "context" 21 "fmt" 22 "os" 23 "sync" 24 "time" 25 26 log "github.com/golang/glog" 27 28 "github.com/google/fleetspeak/fleetspeak/src/client/comms" 29 "github.com/google/fleetspeak/fleetspeak/src/client/config" 30 "github.com/google/fleetspeak/fleetspeak/src/client/flow" 31 intconfig "github.com/google/fleetspeak/fleetspeak/src/client/internal/config" 32 "github.com/google/fleetspeak/fleetspeak/src/client/internal/message" 33 "github.com/google/fleetspeak/fleetspeak/src/client/service" 34 "github.com/google/fleetspeak/fleetspeak/src/client/signer" 35 "github.com/google/fleetspeak/fleetspeak/src/client/stats" 36 "github.com/google/fleetspeak/fleetspeak/src/common" 37 38 fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak" 39 ) 40 41 const ( 42 // Maximum number of messages/bytes to buffer, per priority level. 43 maxBufferCount = 100 44 maxBufferBytes = 20 * 1024 * 1024 45 ) 46 47 // Components gathers the plug-ins used to instantiate a Fleetspeak Client. 48 type Components struct { 49 Communicator comms.Communicator // Required to communicate with a Fleetspeak server. 50 ServiceFactories map[string]service.Factory // Required to instantiate any local services. 51 Signers []signer.Signer // If set, will be given a chance to sign data before sending it to the server. 52 Filter *flow.Filter // If set, will be used to filter messages to the server. 53 Stats stats.Collector 54 } 55 56 // A Client is an active fleetspeak client instance. 57 type Client struct { 58 // Process id for the client. 59 pid int 60 // Time when the client was started (set by client.New()) 61 startTime time.Time 62 63 cfg config.Configuration 64 com comms.Communicator 65 sc *serviceConfiguration 66 67 // outbox produces prioritized MessageInfo records from the FS client buffer. 68 // These are drained by the Communicator component, which tries to send them 69 // to the FS server. 70 outbox chan comms.MessageInfo 71 // outUnsorted produces unsorted buffered messages 72 outUnsorted chan comms.MessageInfo 73 // out(High|Medium|Low) feed buffers of different priorities. 74 outHigh chan service.AckMessage 75 outMedium chan service.AckMessage 76 outLow chan service.AckMessage 77 // used to wait until the retry loop goroutines are done 78 retryLoopsDone sync.WaitGroup 79 80 acks chan common.MessageID 81 errs chan *fspb.MessageErrorData 82 signers []signer.Signer 83 config *intconfig.Manager 84 85 processingBeacon chan struct{} 86 stats stats.Collector 87 } 88 89 // New creates a new Client object based on the provided components. 90 // 91 // clientLabels becomes a list of hardcoded labels of the form "client:<label>", 92 // which is reported to the server as an initial set of labels for this client. 93 // In addition to those provided to NewClient, the client will also include 94 // labels indicating the CPU architecture and OS that the client was build for 95 // (based on runtime.GOARCH and runtime.GOOS). 96 // 97 // TODO: Add support for multiple Communicators. 98 func New(cfg config.Configuration, cmps Components) (*Client, error) { 99 if cmps.Stats == nil { 100 cmps.Stats = stats.NoopCollector{} 101 } 102 103 configChanges := make(chan *fspb.ClientInfoData) 104 cm, err := intconfig.StartManager(&cfg, configChanges, cmps.Stats) 105 if err != nil { 106 return nil, fmt.Errorf("bad configuration: %v", err) 107 } 108 109 ret := &Client{ 110 pid: os.Getpid(), 111 startTime: time.Now(), 112 113 cfg: cfg, 114 com: cmps.Communicator, 115 116 outbox: make(chan comms.MessageInfo), 117 outUnsorted: make(chan comms.MessageInfo), 118 outLow: make(chan service.AckMessage), 119 outMedium: make(chan service.AckMessage), 120 outHigh: make(chan service.AckMessage), 121 122 sc: &serviceConfiguration{ 123 services: make(map[string]*serviceData), 124 factories: cmps.ServiceFactories, 125 }, 126 config: cm, 127 acks: make(chan common.MessageID, 500), 128 errs: make(chan *fspb.MessageErrorData, 50), 129 signers: cmps.Signers, 130 131 processingBeacon: make(chan struct{}, 1), 132 stats: cmps.Stats, 133 } 134 ret.sc.client = ret 135 ret.retryLoopsDone.Add(3) 136 go func() { 137 message.RetryLoop(ret.outLow, ret.outUnsorted, cmps.Stats, maxBufferBytes, maxBufferCount) 138 ret.retryLoopsDone.Done() 139 }() 140 go func() { 141 message.RetryLoop(ret.outMedium, ret.outUnsorted, cmps.Stats, maxBufferBytes, maxBufferCount) 142 ret.retryLoopsDone.Done() 143 }() 144 go func() { 145 message.RetryLoop(ret.outHigh, ret.outUnsorted, cmps.Stats, maxBufferBytes, maxBufferCount) 146 ret.retryLoopsDone.Done() 147 }() 148 f := cmps.Filter 149 if f == nil { 150 f = flow.NewFilter() 151 } 152 go message.SortLoop(ret.outUnsorted, ret.outbox, f) 153 154 ssd := &serviceData{ 155 config: ret.sc, 156 name: "system", 157 service: &systemService{ 158 client: ret, 159 configChanges: configChanges, 160 }, 161 inbox: make(chan *fspb.Message, 5), 162 } 163 ret.sc.services["system"] = ssd 164 ssd.start() 165 ssd.working.Add(1) 166 go func() { 167 defer ssd.working.Done() 168 ssd.processingLoop(context.TODO()) 169 }() 170 171 for _, s := range cfg.FixedServices { 172 if err := ret.sc.InstallService(s, nil); err != nil { 173 log.Errorf("Unable to install fixed service [%s]: %v", s.Name, err) 174 } 175 } 176 177 if ss, err := ret.cfg.PersistenceHandler.ReadSignedServices(); err != nil { 178 log.Warningf("No signed service configs could be read; continuing: %v", err) 179 } else { 180 for _, s := range ss { 181 if err := ret.sc.InstallSignedService(s); err != nil { 182 log.Warningf("Unable to install signed service, ignoring: %v", err) 183 } 184 } 185 } 186 187 if ss, err := ret.cfg.PersistenceHandler.ReadServices(); err != nil { 188 log.Warningf("No unsigned service configs could be read; continuing: %v", err) 189 } else { 190 for _, s := range ss { 191 if err := ret.sc.InstallService(s, nil); err != nil { 192 log.Warningf("Unable to install service [%s], ignoring: %v", s.Name, err) 193 } 194 } 195 } 196 197 if ret.com != nil { 198 cctx := commsContext{c: ret} 199 if err := ret.com.Setup(cctx); err != nil { 200 ssd.stop() 201 return nil, fmt.Errorf("unable to configure communicator: %v", err) 202 } 203 ret.com.Start() 204 ssd.service.(*systemService).pollRevokedCerts() 205 } 206 cm.Sync() 207 cm.SendConfigUpdate() 208 return ret, nil 209 } 210 211 // ProcessMessage accepts a message into the Fleetspeak system. 212 // 213 // If m is for a service on the local client it will ask the service to process 214 // it. If m for the a server component, it will queue up the message to be 215 // delivered to the server. Fleetspeak does not support direct messages from one 216 // client to another. 217 func (c *Client) ProcessMessage(ctx context.Context, am service.AckMessage) (err error) { 218 m := am.M 219 220 var isLocal bool 221 defer func() { 222 c.stats.AfterMessageProcessed(m, isLocal, err) 223 }() 224 225 if m.Destination == nil || m.Destination.ServiceName == "" { 226 return fmt.Errorf("destination must have ServiceName, got: %v", m.Destination) 227 } 228 if clientID := m.Destination.ClientId; len(clientID) != 0 { 229 // This is a local message. No need to send it to the server. 230 isLocal = true 231 if myID := c.config.ClientID().Bytes(); !bytes.Equal(clientID, myID) { 232 return fmt.Errorf("cannot send directly to client %x from client %x", clientID, myID) 233 } 234 return c.sc.ProcessMessage(ctx, m) 235 } 236 237 var out chan service.AckMessage 238 switch m.Priority { 239 case fspb.Message_LOW: 240 out = c.outLow 241 case fspb.Message_MEDIUM: 242 out = c.outMedium 243 case fspb.Message_HIGH: 244 out = c.outHigh 245 default: 246 log.Warningf("Received message with unknown priority %v, treating as Medium.", m.Priority) 247 m.Priority = fspb.Message_MEDIUM 248 out = c.outMedium 249 } 250 251 select { 252 case out <- am: 253 return nil 254 case <-ctx.Done(): 255 return ctx.Err() 256 } 257 } 258 259 // Stop shuts the client down gracefully. This includes stopping all communicators and services. 260 func (c *Client) Stop() { 261 if c.com != nil { 262 c.com.Stop() 263 } 264 c.sc.Stop() 265 c.config.Stop() 266 close(c.outLow) 267 close(c.outMedium) 268 close(c.outHigh) 269 // From here, shutdown is a little subtle: 270 // 271 // 1) At this point, the communicator is off, so nothing else should be 272 // draining outbox. We do this ourselves and Ack everything so that the 273 // RetryLoops are guaranteed to terminate. 274 // 275 // 2) The fake Acks in 1) are safe because the config manager is stopped. 276 // This means that client services are shut down and the Acks will not be 277 // reported outside of this process. 278 // 279 // 3) Then we close outUnsorted so that the SortLoop terminates. 280 for { 281 select { 282 case m := <-c.outbox: 283 m.Ack() 284 default: 285 c.retryLoopsDone.Wait() 286 close(c.outUnsorted) 287 return 288 } 289 } 290 }