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  }