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'}