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  }