github.com/kubearmor/cilium@v1.6.12/proxylib/memcached/binary/parser.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 binary
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/binary"
    20  	"strconv"
    21  
    22  	"github.com/cilium/cilium/proxylib/memcached/meta"
    23  	"github.com/cilium/cilium/proxylib/proxylib"
    24  
    25  	"github.com/cilium/proxy/go/cilium/api"
    26  	log "github.com/sirupsen/logrus"
    27  )
    28  
    29  // ParserFactory implements proxylib.ParserFactory
    30  type ParserFactory struct{}
    31  
    32  // Create creates binary memcached parser
    33  func (p *ParserFactory) Create(connection *proxylib.Connection) proxylib.Parser {
    34  	log.Debugf("ParserFactory: Create: %v", connection)
    35  	return &Parser{connection: connection, injectQueue: make([]queuedInject, 0)}
    36  }
    37  
    38  // compile time check for interface implementation
    39  var _ proxylib.ParserFactory = &ParserFactory{}
    40  
    41  // ParserFactoryInstance creates binary parser for unified parser
    42  var ParserFactoryInstance *ParserFactory
    43  
    44  // Parser implements proxylib.Parser
    45  type Parser struct {
    46  	connection *proxylib.Connection
    47  
    48  	requestCount uint32
    49  	replyCount   uint32
    50  	injectQueue  []queuedInject
    51  }
    52  
    53  var _ proxylib.Parser = &Parser{}
    54  
    55  const headerSize = 24
    56  
    57  // OnData parses binary memcached data
    58  func (p *Parser) OnData(reply, endStream bool, dataBuffers [][]byte) (proxylib.OpType, int) {
    59  	if reply {
    60  		if p.injectFromQueue() {
    61  			return proxylib.INJECT, len(DeniedMsgBase)
    62  		}
    63  		if len(dataBuffers) == 0 {
    64  			return proxylib.NOP, 0
    65  		}
    66  	}
    67  
    68  	//TODO don't copy data from buffers
    69  	data := bytes.Join(dataBuffers, []byte{})
    70  	log.Debugf("Data length: %d", len(data))
    71  
    72  	if headerSize > len(data) {
    73  		headerMissing := headerSize - len(data)
    74  		log.Debugf("Did not receive needed header data, need %d more bytes", headerMissing)
    75  		return proxylib.MORE, headerMissing
    76  	}
    77  
    78  	bodyLength := binary.BigEndian.Uint32(data[8:12])
    79  
    80  	keyLength := binary.BigEndian.Uint16(data[2:4])
    81  	extrasLength := data[4]
    82  
    83  	if keyLength > 0 {
    84  		neededData := headerSize + int(keyLength) + int(extrasLength)
    85  		if neededData > len(data) {
    86  			keyMissing := neededData - len(data)
    87  			log.Debugf("Did not receive enough bytes for key, need %d more bytes", keyMissing)
    88  			return proxylib.MORE, keyMissing
    89  		}
    90  	}
    91  
    92  	opcode, key, err := p.getOpcodeAndKey(data, extrasLength, keyLength)
    93  	if err != 0 {
    94  		return proxylib.ERROR, int(err)
    95  	}
    96  
    97  	logEntry := &cilium.LogEntry_GenericL7{
    98  		GenericL7: &cilium.L7LogEntry{
    99  			Proto: "binarymemcached",
   100  			Fields: map[string]string{
   101  				"opcode": strconv.Itoa(int(opcode)),
   102  				"key":    string(key),
   103  			},
   104  		},
   105  	}
   106  
   107  	// we don't filter reply traffic
   108  	if reply {
   109  		log.Debugf("reply, passing %d bytes", len(data))
   110  		p.connection.Log(cilium.EntryType_Response, logEntry)
   111  		p.replyCount++
   112  		return proxylib.PASS, int(bodyLength + headerSize)
   113  	}
   114  
   115  	p.requestCount++
   116  
   117  	matches := p.connection.Matches(meta.MemcacheMeta{
   118  		Opcode: opcode,
   119  		Keys:   [][]byte{key},
   120  	})
   121  	if matches {
   122  		p.connection.Log(cilium.EntryType_Request, logEntry)
   123  		return proxylib.PASS, int(bodyLength + headerSize)
   124  	}
   125  
   126  	magic := ResponseMagic | data[0]
   127  
   128  	// This is done to ensure in-order replies
   129  	if p.requestCount == p.replyCount+1 {
   130  		p.injectDeniedMessage(magic)
   131  	} else {
   132  		p.injectQueue = append(p.injectQueue, queuedInject{magic, p.requestCount})
   133  	}
   134  
   135  	p.injectQueue = append(p.injectQueue, queuedInject{magic, p.requestCount})
   136  
   137  	p.connection.Log(cilium.EntryType_Denied, logEntry)
   138  	return proxylib.DROP, int(bodyLength + headerSize)
   139  }
   140  
   141  type queuedInject struct {
   142  	magic     byte
   143  	requestID uint32
   144  }
   145  
   146  func (p *Parser) injectDeniedMessage(magic byte) {
   147  	deniedMsg := make([]byte, len(DeniedMsgBase))
   148  	copy(deniedMsg, DeniedMsgBase)
   149  
   150  	deniedMsg[0] = magic
   151  
   152  	p.connection.Inject(true, deniedMsg)
   153  	p.replyCount++
   154  }
   155  
   156  func (p *Parser) injectFromQueue() bool {
   157  	if len(p.injectQueue) > 0 {
   158  		if p.injectQueue[0].requestID == p.replyCount+1 {
   159  			p.injectDeniedMessage(p.injectQueue[0].magic)
   160  			p.injectQueue = p.injectQueue[1:]
   161  			return true
   162  		}
   163  	}
   164  	return false
   165  }
   166  
   167  const (
   168  	// RequestMagic says that memcache frame is a request
   169  	RequestMagic = 0x80
   170  	// ResponseMagic says that memcache frame is a response
   171  	ResponseMagic = 0x81
   172  )
   173  
   174  func (p *Parser) getOpcodeAndKey(data []byte, extrasLength byte, keyLength uint16) (byte, []byte, proxylib.OpError) {
   175  	if data[0]&RequestMagic != RequestMagic {
   176  		log.Warnf("Direction bit is 'response', but memcached parser only parses requests")
   177  		return 0, []byte{}, proxylib.ERROR_INVALID_FRAME_TYPE
   178  	}
   179  
   180  	opcode := data[1]
   181  	key := getMemcacheKey(data, extrasLength, keyLength)
   182  
   183  	return opcode, key, 0
   184  }
   185  
   186  func getMemcacheKey(packet []byte, extrasLength byte, keyLength uint16) []byte {
   187  	if keyLength == 0 {
   188  		return []byte{}
   189  	}
   190  	return packet[headerSize+int(extrasLength) : headerSize+int(extrasLength)+int(keyLength)]
   191  }
   192  
   193  // DeniedMsgBase is sent if policy denies the request. Exported for tests
   194  var DeniedMsgBase = []byte{
   195  	0x81, 0, 0, 0,
   196  	0, 0, 0, 8,
   197  	0, 0, 0, 0x0d,
   198  	0, 0, 0, 0,
   199  	0, 0, 0, 0,
   200  	0, 0, 0, 0,
   201  	'a', 'c', 'c',
   202  	'e', 's', 's',
   203  	' ', 'd', 'e',
   204  	'n', 'i', 'e',
   205  	'd'}