github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/client/services.go (about)

     1  // Copyright 2017 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
    16  
    17  import (
    18  	"context"
    19  	"crypto/rand"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"sync"
    24  	"time"
    25  
    26  	log "github.com/golang/glog"
    27  	"github.com/google/fleetspeak/fleetspeak/src/client/service"
    28  	"github.com/google/fleetspeak/fleetspeak/src/client/stats"
    29  	"github.com/google/fleetspeak/fleetspeak/src/common"
    30  	"google.golang.org/protobuf/encoding/prototext"
    31  	"google.golang.org/protobuf/proto"
    32  
    33  	fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
    34  )
    35  
    36  const inboxSize = 100
    37  
    38  // A serviceConfiguration manages and communicates the services installed on a
    39  // client. In normal use it is a singleton.
    40  type serviceConfiguration struct {
    41  	services  map[string]*serviceData
    42  	lock      sync.RWMutex // Protects the structure of services.
    43  	client    *Client
    44  	factories map[string]service.Factory // Used to look up correct factory when configuring services.
    45  }
    46  
    47  func (c *serviceConfiguration) ProcessMessage(ctx context.Context, m *fspb.Message) error {
    48  	c.lock.RLock()
    49  	target := c.services[m.Destination.ServiceName]
    50  	c.lock.RUnlock()
    51  
    52  	if target == nil {
    53  		return fmt.Errorf("destination service not installed")
    54  	}
    55  	select {
    56  	case target.inbox <- m:
    57  
    58  		target.countLock.Lock()
    59  		target.acceptCount++
    60  		target.countLock.Unlock()
    61  
    62  		return nil
    63  	case <-ctx.Done():
    64  		return ctx.Err()
    65  	}
    66  }
    67  
    68  // InstallSignedService installs a service provided in signed service
    69  // configuration format. Note however that this is meant for backwards
    70  // compatibility and the signature is not checked.
    71  //
    72  // Currently, we assume that service configurations are kept in a secured
    73  // location.
    74  func (c *serviceConfiguration) InstallSignedService(sd *fspb.SignedClientServiceConfig) error {
    75  	var cfg fspb.ClientServiceConfig
    76  	if err := proto.Unmarshal(sd.ServiceConfig, &cfg); err != nil {
    77  		return fmt.Errorf("Unable to parse service config [%v], ignoring: %v", sd.Signature, err)
    78  	}
    79  
    80  ll:
    81  	for _, l := range cfg.RequiredLabels {
    82  		if l.ServiceName == "client" {
    83  			for _, cl := range c.client.cfg.ClientLabels {
    84  				if cl.Label == l.Label {
    85  					continue ll
    86  				}
    87  			}
    88  			return fmt.Errorf("service config [%s] requires label %v", cfg.Name, l)
    89  		}
    90  	}
    91  
    92  	if err := c.InstallService(&cfg, sd.Signature); err != nil {
    93  		return fmt.Errorf("installing [%s]: %w", cfg.Name, err)
    94  	}
    95  	return nil
    96  }
    97  
    98  func validateServiceName(sname string) error {
    99  	if sname == "" || sname == "system" || sname == "client" {
   100  		return fmt.Errorf("illegal service name [%v]", sname)
   101  	}
   102  	return nil
   103  }
   104  
   105  func (c *serviceConfiguration) InstallService(cfg *fspb.ClientServiceConfig, sig []byte) error {
   106  	if err := validateServiceName(cfg.Name); err != nil {
   107  		return fmt.Errorf("can't install service: %v", err)
   108  	}
   109  
   110  	f := c.factories[cfg.Factory]
   111  	if f == nil {
   112  		return fmt.Errorf("factory not found: %q", cfg.Factory)
   113  	}
   114  	s, err := f(cfg)
   115  	if err != nil {
   116  		return fmt.Errorf("unable to create service with factory %q: %v", cfg.Factory, err)
   117  	}
   118  
   119  	d := serviceData{
   120  		config:        c,
   121  		name:          cfg.Name,
   122  		serviceConfig: cfg,
   123  		service:       s,
   124  		inbox:         make(chan *fspb.Message, inboxSize),
   125  	}
   126  	if err := d.start(); err != nil {
   127  		return fmt.Errorf("unable to start service: %v", err)
   128  	}
   129  
   130  	ctx := context.TODO()
   131  	d.working.Add(1)
   132  	go func() {
   133  		defer d.working.Done()
   134  		ctx = context.WithoutCancel(ctx)
   135  		d.processingLoop(ctx)
   136  	}()
   137  
   138  	c.lock.Lock()
   139  	old := c.services[cfg.Name]
   140  	c.services[cfg.Name] = &d
   141  	c.client.config.RecordRunningService(cfg.Name, sig)
   142  	c.lock.Unlock()
   143  
   144  	if old != nil {
   145  		old.stop()
   146  	}
   147  
   148  	b, err := prototext.Marshal(cfg)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	log.Infof("Started service %v with config:\n%s", cfg.Name, string(b))
   153  	return nil
   154  }
   155  
   156  func (c *serviceConfiguration) removeService(sname string) (*serviceData, error) {
   157  	c.lock.Lock()
   158  	defer c.lock.Unlock()
   159  	srv := c.services[sname]
   160  	if srv == nil {
   161  		return nil, fmt.Errorf("falied to remove non-existent service: %v", sname)
   162  	}
   163  	delete(c.services, sname)
   164  	return srv, nil
   165  }
   166  
   167  func (c *serviceConfiguration) RestartService(sname string) error {
   168  	if err := validateServiceName(sname); err != nil {
   169  		return fmt.Errorf("can't restart service: %v", err)
   170  	}
   171  
   172  	srv, err := c.removeService(sname)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	srv.stop()
   178  
   179  	if err := c.InstallService(srv.serviceConfig, nil); err != nil {
   180  		return fmt.Errorf("can't reinstall service '%s' on restart: %v", sname, err)
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  // Counts returns the number of accepted and processed messages for each
   187  // service.
   188  func (c *serviceConfiguration) Counts() (accepted, processed map[string]uint64) {
   189  	am := make(map[string]uint64)
   190  	pm := make(map[string]uint64)
   191  	c.lock.RLock()
   192  	defer c.lock.RUnlock()
   193  
   194  	for _, sd := range c.services {
   195  		sd.countLock.Lock()
   196  		a, p := sd.acceptCount, sd.processedCount
   197  		sd.countLock.Unlock()
   198  		am[sd.name] = a
   199  		pm[sd.name] = p
   200  	}
   201  	return am, pm
   202  }
   203  
   204  func (c *serviceConfiguration) Stop() {
   205  	c.lock.Lock()
   206  	defer c.lock.Unlock()
   207  	for _, sd := range c.services {
   208  		sd.stop()
   209  	}
   210  	c.services = make(map[string]*serviceData)
   211  }
   212  
   213  // A serviceData contains the data we have about a configured service, wrapping
   214  // a Service interface and mediating communication between it and the rest of
   215  // the Fleetspeak client.
   216  type serviceData struct {
   217  	config        *serviceConfiguration
   218  	name          string
   219  	serviceConfig *fspb.ClientServiceConfig
   220  	working       sync.WaitGroup
   221  	service       service.Service
   222  	inbox         chan *fspb.Message
   223  
   224  	countLock                   sync.Mutex // Protects acceptCount, processCount
   225  	acceptCount, processedCount uint64
   226  }
   227  
   228  // Send implements service.Context.
   229  func (d *serviceData) Send(ctx context.Context, am service.AckMessage) error {
   230  	m := am.M
   231  	id := d.config.client.config.ClientID().Bytes()
   232  
   233  	m.Source = &fspb.Address{
   234  		ClientId:    id,
   235  		ServiceName: d.name,
   236  	}
   237  
   238  	if len(m.SourceMessageId) == 0 {
   239  		b := make([]byte, 16)
   240  		if _, err := rand.Read(b); err != nil {
   241  			return fmt.Errorf("unable to create random source message id: %v", err)
   242  		}
   243  		m.SourceMessageId = b
   244  	}
   245  
   246  	return d.config.client.ProcessMessage(ctx, am)
   247  }
   248  
   249  // GetLocalInfo implements service.Context.
   250  func (d *serviceData) GetLocalInfo() *service.LocalInfo {
   251  	ret := &service.LocalInfo{
   252  		ClientID: d.config.client.config.ClientID(),
   253  		Labels:   d.config.client.config.Labels(),
   254  	}
   255  
   256  	d.config.lock.RLock()
   257  	defer d.config.lock.RUnlock()
   258  	for s := range d.config.services {
   259  		if s != "system" {
   260  			ret.Services = append(ret.Services, s)
   261  		}
   262  	}
   263  	return ret
   264  }
   265  
   266  // GetFileIfModified implements service.Context.
   267  func (d *serviceData) GetFileIfModified(ctx context.Context, name string, modSince time.Time) (io.ReadCloser, time.Time, error) {
   268  	if d.config.client.com == nil {
   269  		// happens during tests
   270  		return nil, time.Time{}, errors.New("file not found")
   271  	}
   272  	return d.config.client.com.GetFileIfModified(ctx, d.name, name, modSince)
   273  }
   274  
   275  // Stats implements service.Context.
   276  func (d *serviceData) Stats() stats.Collector {
   277  	return d.config.client.stats
   278  }
   279  
   280  // processingLoop reads Fleetspeak message from d.inbox
   281  // and terminates once this channel is closed.
   282  func (d *serviceData) processingLoop(ctx context.Context) {
   283  	for {
   284  		m, ok := <-d.inbox
   285  
   286  		d.countLock.Lock()
   287  		d.processedCount++
   288  		cnt := d.processedCount
   289  		d.countLock.Unlock()
   290  
   291  		if cnt&0x1f == 0 {
   292  			select {
   293  			case d.config.client.processingBeacon <- struct{}{}:
   294  			default:
   295  			}
   296  		}
   297  
   298  		if !ok {
   299  			return
   300  		}
   301  		id, err := common.BytesToMessageID(m.MessageId)
   302  		if err != nil {
   303  			log.Errorf("ignoring message with bad message id: [%v]", m.MessageId)
   304  			continue
   305  		}
   306  		err = d.service.ProcessMessage(ctx, m)
   307  		if m.MessageType == "Die" && m.Destination != nil && m.Destination.ServiceName == "system" {
   308  			// Die messages are special and pre-acked on the server side.
   309  			// Don't send another ack or error report.
   310  			continue
   311  		}
   312  		if err != nil {
   313  			d.config.client.errs <- &fspb.MessageErrorData{
   314  				MessageId: id.Bytes(),
   315  				Error:     err.Error(),
   316  			}
   317  		} else {
   318  			d.config.client.acks <- id
   319  		}
   320  
   321  	}
   322  }
   323  
   324  func (d *serviceData) start() error {
   325  	return d.service.Start(d)
   326  }
   327  
   328  func (d *serviceData) stop() {
   329  	close(d.inbox)
   330  	d.working.Wait()
   331  	d.service.Stop()
   332  }