Browse Source

Merge pull request #350 from dveeden/connattrs_v4

Support for connection attributes
Arne Hormann 9 years ago
parent
commit
2625e19062
6 changed files with 143 additions and 30 deletions
  1. 10 0
      README.md
  2. 13 0
      driver.go
  3. 5 5
      driver_test.go
  4. 42 10
      dsn.go
  5. 15 14
      dsn_test.go
  6. 58 1
      packets.go

+ 10 - 0
README.md

@@ -169,6 +169,16 @@ Sets the collation used for client-server interaction on connection. In contrast
 
 A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
 
+##### `connattrs`
+
+```
+Type:           comma seperated string of name/value pairs
+Valid Values:   (<name1>=<value1>,<name2>=<value2>,...)
+Default:        none
+```
+
+Sends custom connection attributes to the server.
+
 ##### `clientFoundRows`
 
 ```

+ 13 - 0
driver.go

@@ -20,6 +20,9 @@ import (
 	"database/sql"
 	"database/sql/driver"
 	"net"
+	"os"
+	"os/user"
+	"strconv"
 )
 
 // MySQLDriver is exported to make the driver directly accessible.
@@ -30,6 +33,10 @@ type MySQLDriver struct{}
 // Custom dial functions must be registered with RegisterDial
 type DialFunc func(addr string) (net.Conn, error)
 
+var pid string
+var os_user string
+var os_user_full string
+
 var dials map[string]DialFunc
 
 // RegisterDial registers a custom dial function. It can then be used by the
@@ -163,5 +170,11 @@ func handleAuthResult(mc *mysqlConn, cipher []byte) error {
 }
 
 func init() {
+	pid = strconv.Itoa(os.Getpid())
+	os_user_entry, err := user.Current()
+	if err == nil {
+		os_user_full = os_user_entry.Name
+		os_user = os_user_entry.Username
+	}
 	sql.Register("mysql", &MySQLDriver{})
 }

+ 5 - 5
driver_test.go

@@ -28,7 +28,7 @@ import (
 )
 
 var (
-	user      string
+	dbuser    string
 	pass      string
 	prot      string
 	addr      string
@@ -57,13 +57,13 @@ func init() {
 		}
 		return defaultValue
 	}
-	user = env("MYSQL_TEST_USER", "root")
+	dbuser = env("MYSQL_TEST_USER", "root")
 	pass = env("MYSQL_TEST_PASS", "")
 	prot = env("MYSQL_TEST_PROT", "tcp")
 	addr = env("MYSQL_TEST_ADDR", "localhost:3306")
 	dbname = env("MYSQL_TEST_DBNAME", "gotest")
 	netAddr = fmt.Sprintf("%s(%s)", prot, addr)
-	dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s&strict=true", user, pass, netAddr, dbname)
+	dsn = fmt.Sprintf("%s:%s@%s/%s?timeout=30s&strict=true", dbuser, pass, netAddr, dbname)
 	c, err := net.Dial(prot, addr)
 	if err == nil {
 		available = true
@@ -1693,7 +1693,7 @@ func TestCustomDial(t *testing.T) {
 		return net.Dial(prot, addr)
 	})
 
-	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s&strict=true", user, pass, addr, dbname))
+	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@mydial(%s)/%s?timeout=30s&strict=true", dbuser, pass, addr, dbname))
 	if err != nil {
 		t.Fatalf("error connecting: %s", err.Error())
 	}
@@ -1790,7 +1790,7 @@ func TestUnixSocketAuthFail(t *testing.T) {
 			}
 		}
 		t.Logf("socket: %s", socket)
-		badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s&strict=true", user, badPass, socket, dbname)
+		badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s&strict=true", dbuser, badPass, socket, dbname)
 		db, err := sql.Open("mysql", badDSN)
 		if err != nil {
 			t.Fatalf("error connecting: %s", err.Error())

+ 42 - 10
dsn.go

@@ -42,15 +42,16 @@ type Config struct {
 	ReadTimeout  time.Duration     // I/O read timeout
 	WriteTimeout time.Duration     // I/O write timeout
 
-	AllowAllFiles           bool // Allow all files to be used with LOAD DATA LOCAL INFILE
-	AllowCleartextPasswords bool // Allows the cleartext client side plugin
-	AllowOldPasswords       bool // Allows the old insecure password method
-	ClientFoundRows         bool // Return number of matching rows instead of rows changed
-	ColumnsWithAlias        bool // Prepend table alias to column names
-	InterpolateParams       bool // Interpolate placeholders into query string
-	MultiStatements         bool // Allow multiple statements in one query
-	ParseTime               bool // Parse time values to time.Time
-	Strict                  bool // Return warnings as errors
+	AllowAllFiles           bool              // Allow all files to be used with LOAD DATA LOCAL INFILE
+	AllowCleartextPasswords bool              // Allows the cleartext client side plugin
+	AllowOldPasswords       bool              // Allows the old insecure password method
+	ClientFoundRows         bool              // Return number of matching rows instead of rows changed
+	ColumnsWithAlias        bool              // Prepend table alias to column names
+	InterpolateParams       bool              // Interpolate placeholders into query string
+	MultiStatements         bool              // Allow multiple statements in one query
+	ParseTime               bool              // Parse time values to time.Time
+	Strict                  bool              // Return warnings as errors
+	ConnAttrs               map[string]string // Connection Attributes
 }
 
 // FormatDSN formats the given Config into a DSN string which can be passed to
@@ -222,6 +223,27 @@ func (cfg *Config) FormatDSN() string {
 		buf.WriteString(cfg.WriteTimeout.String())
 	}
 
+	if len(cfg.ConnAttrs) != 0 {
+		if hasParam {
+			buf.WriteString("&connattrs=(")
+		} else {
+			hasParam = true
+			buf.WriteString("?connattrs=(")
+		}
+		firstAttr := true
+		for attrname, attrvalue := range cfg.ConnAttrs {
+			if firstAttr {
+				firstAttr = false
+			} else {
+				buf.WriteString(",")
+			}
+			buf.WriteString(attrname)
+			buf.WriteString("=")
+			buf.WriteString(attrvalue)
+		}
+		buf.WriteString(")")
+	}
+
 	// other params
 	if cfg.Params != nil {
 		for param, value := range cfg.Params {
@@ -496,7 +518,17 @@ func parseDSNParams(cfg *Config, params string) (err error) {
 			if err != nil {
 				return
 			}
-
+		case "connattrs":
+			if cfg.ConnAttrs == nil {
+				cfg.ConnAttrs = make(map[string]string)
+			}
+			for _, conn_v := range strings.Split(strings.Trim(value, "()"), ",") {
+				attr := strings.SplitN(conn_v, "=", 2)
+				if len(attr) != 2 {
+					return fmt.Errorf("Invalid connection attribute: %s", conn_v)
+				}
+				cfg.ConnAttrs[attr[0]] = attr[1]
+			}
 		default:
 			// lazy init
 			if cfg.Params == nil {

+ 15 - 14
dsn_test.go

@@ -19,20 +19,21 @@ var testDSNs = []struct {
 	in  string
 	out string
 }{
-	{"username:password@protocol(address)/dbname?param=value", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:true ParseTime:false Strict:false}"},
-	{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{User:user Passwd: Net:unix Addr:/path/to/socket DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:true tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8mb4,utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:skip-verify tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{User:user Passwd:password Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8mb4_unicode_ci Loc:UTC TLSConfig: tls:<nil> Timeout:30s ReadTimeout:1s WriteTimeout:1s AllowAllFiles:true AllowCleartextPasswords:false AllowOldPasswords:true ClientFoundRows:true ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{User:user Passwd:p@ss(word) Net:tcp Addr:[de:ad:be:ef::ca:fe]:80 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:Local TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"/dbname", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"@/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"user:p@/ssword@/", "&{User:user Passwd:p@/ssword Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
-	{"unix/?arg=%2Fsome%2Fpath.ext", "&{User: Passwd: Net:unix Addr:/tmp/mysql.sock DBName: Params:map[arg:/some/path.ext] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"},
+	{"username:password@protocol(address)/dbname?param=value", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"username:password@protocol(address)/dbname?param=value&connattrs=(foo=bar)", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[foo:bar]}"},
+	{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:true ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{User:user Passwd: Net:unix Addr:/path/to/socket DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:true tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8mb4,utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:skip-verify tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{User:user Passwd:password Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8mb4_unicode_ci Loc:UTC TLSConfig: tls:<nil> Timeout:30s ReadTimeout:1s WriteTimeout:1s AllowAllFiles:true AllowCleartextPasswords:false AllowOldPasswords:true ClientFoundRows:true ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{User:user Passwd:p@ss(word) Net:tcp Addr:[de:ad:be:ef::ca:fe]:80 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:Local TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"/dbname", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"@/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"user:p@/ssword@/", "&{User:user Passwd:p@/ssword Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
+	{"unix/?arg=%2Fsome%2Fpath.ext", "&{User: Passwd: Net:unix Addr:/tmp/mysql.sock DBName: Params:map[arg:/some/path.ext] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false ConnAttrs:map[]}"},
 }
 
 func TestDSNParser(t *testing.T) {

+ 58 - 1
packets.go

@@ -17,6 +17,9 @@ import (
 	"fmt"
 	"io"
 	"math"
+	"os"
+	"path"
+	"runtime"
 	"time"
 )
 
@@ -175,10 +178,15 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
 	if len(data) > pos {
 		// character set [1 byte]
 		// status flags [2 bytes]
+		pos += 1 + 2
+
 		// capability flags (upper 2 bytes) [2 bytes]
+		mc.flags |= (clientFlag(binary.LittleEndian.Uint16(data[pos:pos+2])) << 16)
+		pos += 2
+
 		// length of auth-plugin-data [1 byte]
 		// reserved (all [00]) [10 bytes]
-		pos += 1 + 2 + 2 + 1 + 10
+		pos += 1 + 10
 
 		// second part of the password cipher [mininum 13 bytes],
 		// where len=MAX(13, length of auth-plugin-data - 8)
@@ -246,6 +254,40 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
 
 	pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1
 
+	// Default connection attributes
+	attrlen := 0
+	var attrs map[string]string
+	if mc.flags&clientConnectAttrs != 0 {
+		clientFlags |= clientConnectAttrs
+
+		attrs = map[string]string{
+			"_os":          runtime.GOOS,
+			"_client_name": "Go-MySQL-Driver",
+			"_pid":         pid,
+			"_platform":    runtime.GOARCH,
+			"program_name": path.Base(os.Args[0]),
+		}
+		if len(os_user_full) > 0 {
+			attrs["_os_user_full"] = os_user_full
+		}
+		if len(os_user) > 0 {
+			attrs["_os_user"] = os_user
+		}
+
+		// Merge the custom attributes and the default attributes
+		for cfganame, cfgaval := range mc.cfg.ConnAttrs {
+			attrs[cfganame] = cfgaval
+		}
+
+		for attrname, attrvalue := range attrs {
+			attrlen += len(attrname) + len(attrvalue)
+			// one byte to store attrname length and one byte to store attrvalue length
+			attrlen += 2
+		}
+
+		pktLen += attrlen + 1 // one byte to store the total length of attrs
+	}
+
 	// To specify a db name
 	if n := len(mc.cfg.DBName); n > 0 {
 		clientFlags |= clientConnectWithDB
@@ -326,6 +368,21 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
 	// Assume native client during response
 	pos += copy(data[pos:], "mysql_native_password")
 	data[pos] = 0x00
+	pos++
+
+	// Connection attributes
+	if attrlen > 0 {
+		data[pos] = byte(attrlen)
+		pos++
+
+		for attrname, attrvalue := range attrs {
+			data[pos] = byte(len(attrname))
+			pos += 1 + copy(data[pos+1:], attrname)
+
+			data[pos] = byte(len(attrvalue))
+			pos += 1 + copy(data[pos+1:], attrvalue)
+		}
+	}
 
 	// Send Auth packet
 	return mc.writePacket(data)