github.com/fafucoder/cilium@v1.6.11/proxylib/test/accesslog_server.go (about)

     1  // Copyright 2017-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 test
    16  
    17  import (
    18  	"io"
    19  	"net"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"sync/atomic"
    24  	"syscall"
    25  	"time"
    26  
    27  	"github.com/cilium/proxy/go/cilium/api"
    28  	"github.com/golang/protobuf/proto"
    29  	log "github.com/sirupsen/logrus"
    30  )
    31  
    32  type AccessLogServer struct {
    33  	Path     string
    34  	Logs     chan cilium.LogEntry
    35  	closing  uint32 // non-zero if closing, accessed atomically
    36  	listener *net.UnixListener
    37  	conns    []*net.UnixConn
    38  }
    39  
    40  // Close removes the unix domain socket from the filesystem
    41  func (s *AccessLogServer) Close() {
    42  	if s != nil {
    43  		atomic.StoreUint32(&s.closing, 1)
    44  		s.listener.Close()
    45  		for _, conn := range s.conns {
    46  			conn.Close()
    47  		}
    48  		os.Remove(s.Path)
    49  	}
    50  }
    51  
    52  // Clear empties the access log server buffer, counting the passes and drops
    53  func (s *AccessLogServer) Clear() (passed, drops int) {
    54  	passes, drops := 0, 0
    55  	empty := false
    56  	for !empty {
    57  		select {
    58  		case pblog := <-s.Logs:
    59  			if pblog.EntryType == cilium.EntryType_Denied {
    60  				drops++
    61  			} else {
    62  				passes++
    63  			}
    64  		case <-time.After(10 * time.Millisecond):
    65  			empty = true
    66  		}
    67  	}
    68  	return passes, drops
    69  }
    70  
    71  // StartAccessLogServer starts the access log server.
    72  func StartAccessLogServer(accessLogName string, bufSize int) *AccessLogServer {
    73  	accessLogPath := filepath.Join(Tmpdir, accessLogName)
    74  
    75  	server := &AccessLogServer{
    76  		Path: accessLogPath,
    77  		Logs: make(chan cilium.LogEntry, bufSize),
    78  	}
    79  
    80  	// Create the access log listener
    81  	os.Remove(accessLogPath) // Remove/Unlink the old unix domain socket, if any.
    82  	var err error
    83  	server.listener, err = net.ListenUnix("unixpacket", &net.UnixAddr{Name: accessLogPath, Net: "unixpacket"})
    84  	if err != nil {
    85  		log.Fatalf("Failed to open access log listen socket at %s: %v", accessLogPath, err)
    86  	}
    87  	server.listener.SetUnlinkOnClose(true)
    88  
    89  	// Make the socket accessible by non-root Envoy proxies, e.g. running in
    90  	// sidecar containers.
    91  	if err = os.Chmod(accessLogPath, 0777); err != nil {
    92  		log.Fatalf("Failed to change mode of access log listen socket at %s: %v", accessLogPath, err)
    93  	}
    94  
    95  	log.Debug("Starting Access Log Server")
    96  	go func() {
    97  		for {
    98  			// Each Envoy listener opens a new connection over the Unix domain socket.
    99  			// Multiple worker threads serving the listener share that same connection
   100  			uc, err := server.listener.AcceptUnix()
   101  			if err != nil {
   102  				// These errors are expected when we are closing down
   103  				if atomic.LoadUint32(&server.closing) != 0 ||
   104  					strings.Contains(err.Error(), "closed network connection") ||
   105  					strings.Contains(err.Error(), "invalid argument") {
   106  					break
   107  				}
   108  				log.WithError(err).Warn("Failed to accept access log connection")
   109  				continue
   110  			}
   111  			log.Debug("Accepted access log connection")
   112  
   113  			server.conns = append(server.conns, uc)
   114  			// Serve this access log socket in a goroutine, so we can serve multiple
   115  			// connections concurrently.
   116  			go server.accessLogger(uc)
   117  		}
   118  	}()
   119  
   120  	return server
   121  }
   122  
   123  // isEOF returns true if the error message ends in "EOF". ReadMsgUnix returns extra info in the beginning.
   124  func isEOF(err error) bool {
   125  	strerr := err.Error()
   126  	errlen := len(strerr)
   127  	return errlen >= 3 && strerr[errlen-3:] == io.EOF.Error()
   128  }
   129  
   130  func (s *AccessLogServer) accessLogger(conn *net.UnixConn) {
   131  	defer func() {
   132  		log.Debug("Closing access log connection")
   133  		conn.Close()
   134  	}()
   135  
   136  	buf := make([]byte, 4096)
   137  	for {
   138  		n, _, flags, _, err := conn.ReadMsgUnix(buf, nil)
   139  		if err != nil {
   140  			if !isEOF(err) && atomic.LoadUint32(&s.closing) == 0 {
   141  				log.WithError(err).Error("Error while reading from access log connection")
   142  			}
   143  			break
   144  		}
   145  		if flags&syscall.MSG_TRUNC != 0 {
   146  			log.Warning("Discarded truncated access log message")
   147  			continue
   148  		}
   149  		pblog := cilium.LogEntry{}
   150  		err = proto.Unmarshal(buf[:n], &pblog)
   151  		if err != nil {
   152  			log.WithError(err).Warning("Discarded invalid access log message")
   153  			continue
   154  		}
   155  
   156  		log.Debugf("Access log message: %s", pblog.String())
   157  		s.Logs <- pblog
   158  	}
   159  }