github.com/matrixorigin/matrixone@v1.2.0/pkg/proxy/mysql_conn_buf.go (about) 1 // Copyright 2021 - 2023 Matrix Origin 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 proxy 16 17 import ( 18 "fmt" 19 "io" 20 "net" 21 "sync" 22 23 "github.com/matrixorigin/matrixone/pkg/common/moerr" 24 "github.com/matrixorigin/matrixone/pkg/logutil" 25 "go.uber.org/zap" 26 ) 27 28 const ( 29 // the default message buffer size, 8K. 30 defaultBufLen = 8192 31 // defaultExtraBufLen is the default extra buffer size, 2K. 32 defaultExtraBufLen = 2048 33 // MySQL header length is 4 bytes, with 3 bytes data length 34 // and 1 byte sequence number. 35 mysqlHeadLen = 4 36 // MySQL query cmd is 1 byte. 37 cmdLen = 1 38 // The header and cmd must be received first. 39 preRecvLen = mysqlHeadLen + cmdLen 40 ) 41 42 // MySQLCmd is the type indicate the cmd of statement. 43 type MySQLCmd byte 44 45 // cmdQuery is a query cmd. 46 const ( 47 cmdQuery MySQLCmd = 0x03 48 cmdInitDB MySQLCmd = 0x02 49 // For stmt prepare and execute cmd from JDBC. 50 cmdStmtPrepare MySQLCmd = 0x16 51 cmdStmtClose MySQLCmd = 0x19 52 ) 53 54 // MySQLConn contains a buffer to save data which may be only part 55 // of a packet. 56 type MySQLConn struct { 57 net.Conn 58 *msgBuf 59 } 60 61 // newMySQLConn creates a new MySQLConn. reqC and respC are used for client 62 // connection to handle events from client. 63 func newMySQLConn( 64 name string, c net.Conn, sz int, reqC chan IEvent, respC chan []byte, cid uint32, 65 ) *MySQLConn { 66 return &MySQLConn{ 67 Conn: c, 68 msgBuf: newMsgBuf(name, c, sz, reqC, respC, cid), 69 } 70 } 71 72 // msgBuf holds a buffer to save MySQL packets. It is mainly used to 73 // identify what kind the statement is, and to check whether it is safe 74 // to transfer a connection. 75 type msgBuf struct { 76 cid uint32 77 // for debug 78 name string 79 src io.Reader 80 81 // buf keeps message which is read from src. It can contain multiple messages. 82 // The default available part of buffer is only [0:defaultBufLen]. The rest part 83 // [defaultBufLen:] is used to save data to handle events when the first part is full. 84 // 85 // TODO(volgariver6): As a result, only the event statement whose length is less 86 // than defaultExtraBufLen is supported. 87 buf []byte 88 // availLen is the available length of the buffer. 89 availLen int 90 extraLen int 91 // begin, end is the range that the data is available in the buf. 92 begin, end int 93 // writeMu controls the mutex to lock when write a MySQL packet with net.Write(). 94 writeMu sync.Mutex 95 // reqC is the channel of event request. 96 reqC chan IEvent 97 // respC is the channel of event response. 98 respC chan []byte 99 } 100 101 // newMsgBuf creates a new message buffer. 102 func newMsgBuf( 103 name string, src io.Reader, bufLen int, reqC chan IEvent, respC chan []byte, cid uint32, 104 ) *msgBuf { 105 var availLen, extraLen int 106 if bufLen < mysqlHeadLen { 107 availLen = defaultBufLen 108 extraLen = defaultExtraBufLen 109 bufLen = availLen + extraLen 110 } else { 111 availLen = bufLen 112 } 113 return &msgBuf{ 114 cid: cid, 115 src: src, 116 buf: make([]byte, bufLen), 117 availLen: availLen, 118 extraLen: extraLen, 119 name: name, 120 reqC: reqC, 121 respC: respC, 122 } 123 } 124 125 // readAvail returns the position that is available to read. 126 func (b *msgBuf) readAvail() int { 127 return b.end - b.begin 128 } 129 130 func (b *msgBuf) readAvailBuf() []byte { 131 return b.buf[b.begin:b.end] 132 } 133 134 // writeAvail returns the position that is available to write. 135 func (b *msgBuf) writeAvail() int { 136 return b.availLen - b.end 137 } 138 139 // preRecv tries to receive a MySQL packet from remote. It receives 140 // a packet header at least, and it blocks when there are nothing to 141 // receive. 142 func (b *msgBuf) preRecv() (int, error) { 143 // First we try to receive at least preRecvLen data and put it into 144 // the buffer. 145 if err := b.receiveAtLeast(mysqlHeadLen); err != nil { 146 return 0, err 147 } 148 149 bodyLen := int(uint32(b.buf[b.begin]) | uint32(b.buf[b.begin+1])<<8 | uint32(b.buf[b.begin+2])<<16) 150 if bodyLen == 0 { 151 return mysqlHeadLen, nil 152 } 153 154 // Max length is 3 bytes. 26MB-1 is the legal max length of a MySQL packet. 155 // Reference To : https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_packets.html 156 if bodyLen < 1 || bodyLen > 1<<24-1 { 157 return 0, moerr.NewInternalErrorNoCtx("mysql protocol error: body length %d", bodyLen) 158 } 159 160 // Data length does not count header length, so header length is added to it. 161 return bodyLen + mysqlHeadLen, nil 162 } 163 164 // consumeMsg consumes the MySQL packet in the buffer, handles it by event 165 // mechanism. 166 // The first return value is true if the command is handled, means it does not need 167 // to be sent through tunnel anymore; false otherwise. 168 // The second return value is true means that the transfer happened. 169 func (b *msgBuf) consumeClient(msg []byte) bool { 170 if b.reqC == nil { 171 return false 172 } 173 e, r := makeEvent(msg, b) 174 if e == nil { 175 return false 176 } 177 sendReq(e, b.reqC) 178 // We cannot write to b.src directly here. The response has 179 // to go to the server conn buf, and lock writeMu then 180 // write to client. 181 return r 182 } 183 184 func (b *msgBuf) debugLogs(data []byte, dataLeft int) { 185 logger := logutil.GetSkip1Logger() 186 if logger.Core().Enabled(zap.DebugLevel) { 187 if b.name == connClientName { 188 logutil.Debugf("proxy debug: client, conn ID: %d, data left: %d, data: %v", 189 b.cid, dataLeft, data) 190 } else { 191 logutil.Debugf("proxy debug: server, conn ID: %d, data left: %d, data: %v", 192 b.cid, dataLeft, data) 193 } 194 } 195 } 196 197 // sendTo sends the data in buffer to destination. 198 func (b *msgBuf) sendTo(dst io.Writer) error { 199 l, err := b.preRecv() 200 if err != nil { 201 return err 202 } 203 readPos := b.begin 204 writePos := readPos + l 205 dataLeft := 0 206 if writePos > b.end { 207 dataLeft = writePos - b.end 208 writePos = b.end 209 } 210 b.begin = writePos 211 if writePos-readPos < mysqlHeadLen { 212 panic(fmt.Sprintf("%d bytes have to be read", mysqlHeadLen)) 213 } 214 215 // Try to consume the message synchronously. 216 extraLen := 0 217 if dataLeft > 0 && dataLeft < b.extraLen { 218 // If the available part can hold the left data, receive it and save 219 // the data at the position of writePos. 220 extraLen, err = io.ReadFull(b.src, b.buf[writePos:writePos+dataLeft]) 221 if err != nil { 222 return err 223 } 224 if extraLen < dataLeft { 225 return io.ErrShortWrite 226 } 227 writePos += extraLen 228 dataLeft = 0 229 } 230 231 // add debug logs 232 b.debugLogs(b.buf[readPos:writePos], dataLeft) 233 234 var handled bool 235 236 if dataLeft == 0 { 237 handled = b.consumeClient(b.buf[readPos:writePos]) 238 // means the query has been handled 239 if handled { 240 return nil 241 } 242 } 243 244 b.writeMu.Lock() 245 defer b.writeMu.Unlock() 246 // Write the data in buffer. 247 n, err := dst.Write(b.buf[readPos:writePos]) 248 if err != nil { 249 return err 250 } 251 if n < writePos-readPos { 252 return io.ErrShortWrite 253 } 254 255 // The buffer does not hold all packet data, so continue to read the packet. 256 if dataLeft > 0 { 257 m, err := io.CopyN(dst, b.src, int64(dataLeft)) 258 if err != nil { 259 return err 260 } 261 if int(m) < dataLeft { 262 return io.ErrShortWrite 263 } 264 } 265 return err 266 } 267 268 // receive receives a MySQL packet. This is used in test only. 269 func (b *msgBuf) receive() ([]byte, error) { 270 // Receive header of the current packet. 271 size, err := b.preRecv() 272 if err != nil { 273 return nil, err 274 } 275 276 if size <= b.availLen { 277 if err := b.receiveAtLeast(size); err != nil { 278 return nil, err 279 } 280 retBuf := b.buf[b.begin : b.begin+size] 281 b.begin += size 282 return retBuf, nil 283 } 284 285 // Packet cannot fit, so we will have to allocate new space. 286 msg := make([]byte, size) 287 // Copy bytes which have already been read. 288 n := copy(msg, b.buf[b.begin:b.end]) 289 b.begin += n 290 291 // Read more bytes. 292 if _, err := io.ReadFull(b.src, msg[n:]); err != nil { 293 return nil, err 294 } 295 296 return msg, nil 297 } 298 299 func (b *msgBuf) receiveAtLeast(n int) error { 300 if n < 0 || n > b.availLen { 301 return moerr.NewInternalErrorNoCtx("invalid receive bytes size %d", n) 302 } 303 // Buffer already has n bytes. 304 if b.readAvail() >= n { 305 return nil 306 } 307 minReadSize := n - b.readAvail() 308 if b.writeAvail() < minReadSize { 309 b.end = copy(b.buf, b.buf[b.begin:b.end]) 310 b.begin = 0 311 } 312 c, err := io.ReadAtLeast(b.src, b.buf[b.end:b.availLen], minReadSize) 313 b.end += c 314 return err 315 } 316 317 // writeDataDirectly writes data to dst directly without put it into buffer. 318 // This operation happens in server connection, and the dst is client conn. 319 // 320 // A call (net.Conn).Write() is go-routine safe, so we need to make sure the 321 // data we are trying to write is a whole MySQL packet. 322 // 323 // NB: The write operation needs a lock to keep safe because in sendTo method, 324 // the write operation of a whole MySQL packet may be divided in two steps. 325 // The data must is a whole MySQL packet, otherwise it is not safe to write it. 326 func (b *msgBuf) writeDataDirectly(dst io.Writer, data []byte) error { 327 b.writeMu.Lock() 328 defer b.writeMu.Unlock() 329 _, err := dst.Write(data) 330 if err != nil { 331 return err 332 } 333 return nil 334 }