Files
LinkPort/internal/protocol/sqlserverproxy/wire.go
T
曾志威 5b6cb3e2bf feat: 完善多账号网关、运维监控与配置管理
新增 HTTP/HTTPS 与 SQL Server 支持,完善账号映射、凭据生命周期、角色和 API Token 隔离。实现健康检查、告警、配置导入导出、模板与历史差异,并补充管理界面、回归测试和运维文档。
2026-09-12 22:19:10 +08:00

198 lines
4.8 KiB
Go

package sqlserverproxy
import (
"encoding/binary"
"errors"
"io"
"net"
"serviceport/internal/protocol/transport"
"unicode/utf16"
)
const maxMessage = 16 << 20
func readMessage(r io.Reader) (byte, []byte, error) {
return readWithFlags(r, nil)
}
func readWithFlags(r io.Reader, flags *byte) (byte, []byte, error) {
var result []byte
var kind byte
packets := 0
for {
var header [8]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return 0, nil, err
}
packets++
if packets > 65536 {
return 0, nil, errors.New("too many TDS fragments")
}
if flags != nil && packets == 1 {
*flags = header[1] & 0x18
}
n := int(binary.BigEndian.Uint16(header[2:])) - 8
if n < 0 || n > 32759 || len(result)+n > maxMessage || (len(result) > 0 && header[0] != kind) {
return 0, nil, errors.New("invalid TDS message")
}
kind = header[0]
start := len(result)
result = append(result, make([]byte, n)...)
if _, err := io.ReadFull(r, result[start:]); err != nil {
return 0, nil, err
}
if header[1]&1 != 0 {
return kind, result, nil
}
}
}
func writeMessage(w io.Writer, kind byte, data []byte) error {
return writeWithFlags(w, kind, data, 0)
}
func writeWithFlags(w io.Writer, kind byte, data []byte, flags byte) error {
for id := byte(1); ; id++ {
n := min(len(data), 4088)
header := []byte{kind, 0, 0, 0, 0, 0, id, 0}
if id == 1 {
header[1] = flags
}
if n == len(data) {
header[1] |= 1
}
binary.BigEndian.PutUint16(header[2:], uint16(n+8))
if err := transport.Write(w, append(header, data[:n]...)); err != nil {
return err
}
data = data[n:]
if len(data) == 0 {
return nil
}
}
}
func prelogin(encryption byte) []byte {
return []byte{0, 0, 16, 0, 6, 1, 0, 22, 0, 1, 4, 0, 23, 0, 1, 255, 15, 0, 0, 0, 0, 0, encryption, 0}
}
func encryptionOption(data []byte) (byte, error) {
for pos := 0; pos < len(data); pos += 5 {
if data[pos] == 255 {
break
}
if pos+5 > len(data) {
break
}
off, n := int(binary.BigEndian.Uint16(data[pos+1:])), int(binary.BigEndian.Uint16(data[pos+3:]))
if off+n > len(data) {
break
}
if data[pos] == 1 && n == 1 {
return data[off], nil
}
}
return 0, errors.New("invalid TDS prelogin")
}
type handshakeConn struct {
net.Conn
framed bool
pending []byte
}
func (c *handshakeConn) Read(b []byte) (int, error) {
if !c.framed && len(c.pending) == 0 {
return c.Conn.Read(b)
}
if len(c.pending) == 0 {
kind, data, err := readMessage(c.Conn)
if err != nil {
return 0, err
}
if kind != 18 && kind != 4 {
return 0, errors.New("invalid TDS TLS frame")
}
c.pending = data
}
n := copy(b, c.pending)
c.pending = c.pending[n:]
return n, nil
}
func (c *handshakeConn) Write(b []byte) (int, error) {
if !c.framed {
return c.Conn.Write(b)
}
if err := writeMessage(c.Conn, 18, b); err != nil {
return 0, err
}
return len(b), nil
}
func textBytes(s string) []byte {
units := utf16.Encode([]rune(s))
out := make([]byte, len(units)*2)
for i, v := range units {
binary.LittleEndian.PutUint16(out[i*2:], v)
}
return out
}
func decodeText(b []byte) string {
units := make([]uint16, len(b)/2)
for i := range units {
units[i] = binary.LittleEndian.Uint16(b[i*2:])
}
return string(utf16.Decode(units))
}
func loginField(data []byte, pos int, password bool) (string, error) {
if len(data) < 94 || pos+4 > len(data) {
return "", errors.New("short TDS login")
}
off, n := int(binary.LittleEndian.Uint16(data[pos:])), int(binary.LittleEndian.Uint16(data[pos+2:]))*2
if off < 94 && n != 0 || off+n > len(data) {
return "", errors.New("invalid TDS login field")
}
b := append([]byte(nil), data[off:off+n]...)
if password {
for i, v := range b {
v ^= 0xa5
b[i] = v<<4 | v>>4
}
}
return decodeText(b), nil
}
func upstreamLogin(original []byte, user, password, host string) ([]byte, error) {
if len(original) < 94 {
return nil, errors.New("short TDS login")
}
// Rebuild the supported SQL-password login fields; never forward client authentication extensions.
out := append([]byte(nil), original[:94]...)
out[25] &^= 0x80
out[27] &^= 0x11
for i := 36; i < 94; i++ {
out[i] = 0
}
for _, pos := range []int{36, 40, 44, 48, 52, 60, 64, 68} {
value, err := loginField(original, pos, pos == 44)
if err != nil {
return nil, err
}
switch pos {
case 40:
value = user
case 44:
value = password
case 52:
value = host
}
b := textBytes(value)
if len(b) > 8192 || len(out)+len(b) > 65535 {
return nil, errors.New("TDS login field too long")
}
binary.LittleEndian.PutUint16(out[pos:], uint16(len(out)))
binary.LittleEndian.PutUint16(out[pos+2:], uint16(len(b)/2))
if pos == 44 {
for i, v := range b {
b[i] = (v<<4 | v>>4) ^ 0xa5
}
}
out = append(out, b...)
}
binary.LittleEndian.PutUint32(out, uint32(len(out)))
return out, nil
}