Repository: microsoft/go-mssqldb
Pull request #66 (2022-11-07 22:48:03)

Added checks while reading prelogin for invalid data
Currently the driver panic If the server sends bad prelogin connection data.
Added additional checks and unit tests to verify the behavior.

https://github.com/microsoft/go-mssqldb/issues/64



Patch:
diff --git a/README.md b/README.md
index e42219d..aa06109 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,13 @@
 [![Build status](https://ci.appveyor.com/api/projects/status/jrln8cs62wj9i0a2?svg=true)](https://ci.appveyor.com/project/microsoft/go-mssqldb)
 [![codecov](https://codecov.io/gh/microsoft/go-mssqldb/branch/master/graph/badge.svg)](https://codecov.io/gh/microsoft/go-mssqldb)
 
+For more recent updates, see the [Microsoft fork](https://github.com/microsoft/go-mssqldb).
+
 ## Install
 
 Requires Go 1.8 or above.
 
-Install with `go get github.com/microsoft/go-mssqldb` .
+Install with `go install github.com/microsoft/go-mssqldb@latest`.
 
 ## Connection Parameters and DSN
 
@@ -22,7 +24,7 @@ Other supported formats are listed below.
 * `password`
 * `database`
 * `connection timeout` - in seconds (default is 0 for no timeout), set to 0 for no timeout. Recommended to set to 0 and use context to manage query and connection timeouts.
-* `dial timeout` - in seconds (default is 15), set to 0 for no timeout
+* `dial timeout` - in seconds (default is 15 times the number of registered protocols), set to 0 for no timeout.
 * `encrypt`
   * `disable` - Data send between client and server is not encrypted.
   * `false` - Data sent between client and server is not encrypted beyond the login packet. (Default)
@@ -358,6 +360,9 @@ db.QueryContext(ctx, `select * from t2 where user_name = @p1;`, mssql.VarChar(na
 * Supports connections to AlwaysOn Availability Group listeners, including re-direction to read-only replicas.
 * Supports query notifications
 * Supports Kerberos Authentication
+* Pluggable Dialer implementations through `msdsn.ProtocolParsers` and `msdsn.ProtocolDialers`
+* A `namedpipe` package to support connections using named pipes (np:) on Windows
+* A `sharedmemory` package to support connections using shared memory (lpc:) on Windows
 
 ## Tests
 
diff --git a/appveyor.yml b/appveyor.yml
index 1912109..dafa972 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -13,6 +13,7 @@ environment:
   DATABASE: test
   GOVERSION: 113
   RACE: -race -cpu 4
+  TAGS:
   matrix:
     - GOVERSION: 110
       SQLINSTANCE: SQL2017
@@ -45,11 +46,22 @@ environment:
     - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
       GOVERSION: 118
       SQLINSTANCE: SQL2017
+      # Cover 32bit and named pipes protocol
     - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
       GOVERSION: 118-x86
       SQLINSTANCE: SQL2017
       GOARCH: 386
       RACE:
+      PROTOCOL: np
+      TAGS: -tags np
+      # Cover SSPI and lpc protocol
+    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
+      GOVERSION: 118
+      SQLINSTANCE: SQL2019
+      PROTOCOL: lpc
+      TAGS: -tags sm
+      SQLUSER:
+      SQLPASSWORD:
 install:
   - set GOROOT=c:\go%GOVERSION%
   - set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%
@@ -58,19 +70,31 @@ install:
   - go get -u github.com/golang-sql/civil
   - go get -u github.com/golang-sql/sqlexp
   - go get -u golang.org/x/crypto/md4
+
 build_script:
   - go build
 
 before_test:
   # setup SQL Server
   - ps: |
+      [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null
+      [reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null
+      $smo = 'Microsoft.SqlServer.Management.Smo.'
+      $wmi = new-object ($smo + 'Wmi.ManagedComputer')
+      $serverName = $env:COMPUTERNAME
       $instanceName = $env:SQLINSTANCE
-      Start-Service "MSSQL`$$instanceName"
+      # Enable named pipes
+      $uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Np']"
+      $Np = $wmi.GetSmoObject($uri)
+      $Np.IsEnabled = $true
+      $Np.Alter()
       Start-Service "SQLBrowser"
+      Start-Service "MSSQL`$$instanceName"
+      Start-Sleep -Seconds 10
   - sqlcmd -S "(local)\%SQLINSTANCE%" -Q "Use [master]; CREATE DATABASE test;"
-  - sqlcmd -S "(local)\%SQLINSTANCE%" -h -1 -Q "set nocount on; Select @@version"
+  - sqlcmd -S "np:.\%SQLINSTANCE%" -h -1 -Q "set nocount on; Select @@version"
   - pip install codecov
 
 test_script:
-  - go test %RACE% -coverprofile=coverage.txt -covermode=atomic
+  - go test %RACE% -coverprofile=coverage.txt -covermode=atomic %TAGS%
   - codecov -f coverage.txt
diff --git a/azuread/configuration.go b/azuread/configuration.go
index e3c2eb7..f52cee3 100644
--- a/azuread/configuration.go
+++ b/azuread/configuration.go
@@ -27,9 +27,10 @@ const (
 	ActiveDirectoryMSI             = "ActiveDirectoryMSI"
 	ActiveDirectoryManagedIdentity = "ActiveDirectoryManagedIdentity"
 	// ActiveDirectoryApplication is a synonym for ActiveDirectoryServicePrincipal
-	ActiveDirectoryApplication      = "ActiveDirectoryApplication"
-	ActiveDirectoryServicePrincipal = "ActiveDirectoryServicePrincipal"
-	scopeDefaultSuffix              = "/.default"
+	ActiveDirectoryApplication                 = "ActiveDirectoryApplication"
+	ActiveDirectoryServicePrincipal            = "ActiveDirectoryServicePrincipal"
+	ActiveDirectoryServicePrincipalAccessToken = "ActiveDirectoryServicePrincipalAccessToken"
+	scopeDefaultSuffix                         = "/.default"
 )
 
 type azureFedAuthConfig struct {
@@ -127,7 +128,14 @@ func (p *azureFedAuthConfig) validateParameters(params map[string]string) error
 		p.user, _ = params["user id"]
 		// we don't really have a password but we need to use some value.
 		p.adalWorkflow = mssql.FedAuthADALWorkflowPassword
+	case strings.EqualFold(fedAuthWorkflow, ActiveDirectoryServicePrincipalAccessToken):
+		p.fedAuthLibrary = mssql.FedAuthLibrarySecurityToken
+		p.adalWorkflow = mssql.FedAuthADALWorkflowNone
+		p.password, _ = params["password"]
 
+		if p.password == "" {
+			return errors.New("Must provide 'password' parameter when using ActiveDirectoryApplicationAuthToken authentication")
+		}
 	default:
 		return fmt.Errorf("Invalid federated authentication type '%s': expected one of %+v",
 			fedAuthWorkflow,
@@ -181,6 +189,8 @@ func (p *azureFedAuthConfig) provideActiveDirectoryToken(ctx context.Context, se
 		default:
 			cred, err = azidentity.NewClientSecretCredential(tenant, p.clientID, p.clientSecret, nil)
 		}
+	case ActiveDirectoryServicePrincipalAccessToken:
+		return p.password, nil
 	case ActiveDirectoryPassword:
 		cred, err = azidentity.NewUsernamePasswordCredential(tenant, p.applicationClientID, p.user, p.password, nil)
 	case ActiveDirectoryMSI, ActiveDirectoryManagedIdentity:
diff --git a/azuread/driver.go b/azuread/driver.go
index 3320d7b..ccd9d82 100644
--- a/azuread/driver.go
+++ b/azuread/driver.go
@@ -1,57 +1,66 @@
-//go:build go1.18
-// +build go1.18
-
-package azuread
-
-import (
-	"context"
-	"database/sql"
-	"database/sql/driver"
-
-	mssql "github.com/microsoft/go-mssqldb"
-)
-
-// DriverName is the name used to register the driver
-const DriverName = "azuresql"
-
-func init() {
-	sql.Register(DriverName, &Driver{})
-}
-
-// Driver wraps the underlying MSSQL driver, but configures the Azure AD token provider
-type Driver struct {
-}
-
-// Open returns a new connection to the database.
-func (d *Driver) Open(dsn string) (driver.Conn, error) {
-	c, err := NewConnector(dsn)
-	if err != nil {
-		return nil, err
-	}
-
-	return c.Connect(context.Background())
-}
-
-// NewConnector creates a new connector from a DSN.
-// The returned connector may be used with sql.OpenDB.
-func NewConnector(dsn string) (*mssql.Connector, error) {
-
-	config, err := parse(dsn)
-	if err != nil {
-		return nil, err
-	}
-	return newConnectorConfig(config)
-}
-
-// newConnectorConfig creates a Connector from config.
-func newConnectorConfig(config *azureFedAuthConfig) (*mssql.Connector, error) {
-	if config.fedAuthLibrary == mssql.FedAuthLibraryADAL {
-		return mssql.NewActiveDirectoryTokenConnector(
-			config.mssqlConfig, config.adalWorkflow,
-			func(ctx context.Context, serverSPN, stsURL string) (string, error) {
-				return config.provideActiveDirectoryToken(ctx, serverSPN, stsURL)
-			},
-		)
-	}
-	return mssql.NewConnectorConfig(config.mssqlConfig), nil
-}
+//go:build go1.18
+// +build go1.18
+
+package azuread
+
+import (
+	"context"
+	"database/sql"
+	"database/sql/driver"
+
+	mssql "github.com/microsoft/go-mssqldb"
+)
+
+// DriverName is the name used to register the driver
+const DriverName = "azuresql"
+
+func init() {
+	sql.Register(DriverName, &Driver{})
+}
+
+// Driver wraps the underlying MSSQL driver, but configures the Azure AD token provider
+type Driver struct {
+}
+
+// Open returns a new connection to the database.
+func (d *Driver) Open(dsn string) (driver.Conn, error) {
+	c, err := NewConnector(dsn)
+	if err != nil {
+		return nil, err
+	}
+
+	return c.Connect(context.Background())
+}
+
+// NewConnector creates a new connector from a DSN.
+// The returned connector may be used with sql.OpenDB.
+func NewConnector(dsn string) (*mssql.Connector, error) {
+
+	config, err := parse(dsn)
+	if err != nil {
+		return nil, err
+	}
+	return newConnectorConfig(config)
+}
+
+// newConnectorConfig creates a Connector from config.
+func newConnectorConfig(config *azureFedAuthConfig) (*mssql.Connector, error) {
+	switch config.fedAuthLibrary {
+	case mssql.FedAuthLibraryADAL:
+		return mssql.NewActiveDirectoryTokenConnector(
+			config.mssqlConfig, config.adalWorkflow,
+			func(ctx context.Context, serverSPN, stsURL string) (string, error) {
+				return config.provideActiveDirectoryToken(ctx, serverSPN, stsURL)
+			},
+		)
+	case mssql.FedAuthLibrarySecurityToken:
+		return mssql.NewSecurityTokenConnector(
+			config.mssqlConfig,
+			func(ctx context.Context) (string, error) {
+				return config.password, nil
+			},
+		)
+	default:
+		return mssql.NewConnectorConfig(config.mssqlConfig), nil
+	}
+}
diff --git a/examples/azuread-service-principal-authtoken/service_principal_authtoken.go b/examples/azuread-service-principal-authtoken/service_principal_authtoken.go
index 5e57119..9e9e942 100644
--- a/examples/azuread-service-principal-authtoken/service_principal_authtoken.go
+++ b/examples/azuread-service-principal-authtoken/service_principal_authtoken.go
@@ -1,13 +1,16 @@
+//go:build go1.18
+// +build go1.18
+
 package main
 
 import (
-	"context"
 	"database/sql"
 	"flag"
 	"fmt"
 	"log"
 
-	mssql "github.com/microsoft/go-mssqldb"
+	_ "github.com/microsoft/go-mssqldb"
+	"github.com/microsoft/go-mssqldb/azuread"
 )
 
 var (
@@ -32,14 +35,7 @@ func main() {
 	if *debug {
 		fmt.Printf(" connString:%s\n", connString)
 	}
-
-	tokenProviderWithCtx := func(ctx context.Context) (string, error) {
-		return "access_token", nil
-	}
-
-	connector, err := mssql.NewConnectorWithAccessTokenProvider(connString, tokenProviderWithCtx)
-	conn := sql.OpenDB(connector)
-
+	conn, err := sql.Open(azuread.DriverName, connString)
 	if err != nil {
 		log.Fatal("Open connection failed:", err.Error())
 	}
diff --git a/fedauth.go b/fedauth.go
index 27ff3fc..454b61c 100644
--- a/fedauth.go
+++ b/fedauth.go
@@ -34,6 +34,9 @@ const (
 
 	// FedAuthADALWorkflowMSI uses the managed identity service to obtain a token
 	FedAuthADALWorkflowMSI = 0x03
+
+	// FedAuthADALWorkflowNone does not need to obtain token
+	FedAuthADALWorkflowNone = 0x04
 )
 
 // newSecurityTokenConnector creates a new connector from a Config and a token provider.
diff --git a/go.mod b/go.mod
index f9de173..681d6d7 100644
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/jcmturner/gokrb5/v8 v8.4.2
 	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
-	golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88
+	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
 	golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 // indirect
+	gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
 )
diff --git a/go.sum b/go.sum
index ff200c9..2cb53e9 100644
--- a/go.sum
+++ b/go.sum
@@ -60,8 +60,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw=
 golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -89,6 +90,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/internal/np/namedpipe_windows.go b/internal/np/namedpipe_windows.go
new file mode 100644
index 0000000..46176ee
--- /dev/null
+++ b/internal/np/namedpipe_windows.go
@@ -0,0 +1,34 @@
+package np
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"os"
+	"time"
+
+	"gopkg.in/natefinch/npipe.v2"
+)
+
+func DialConnection(ctx context.Context, pipename string, host string, instanceName string, inputServerSPN string) (conn net.Conn, serverSPN string, err error) {
+	dl, ok := ctx.Deadline()
+	if ok {
+		duration := time.Until(dl)
+		conn, err = npipe.DialTimeout(pipename, duration)
+	} else {
+		conn, err = npipe.Dial(pipename)
+	}
+	serverSPN = inputServerSPN
+	if err == nil && inputServerSPN == "" {
+		instance := ""
+		if instanceName != "" {
+			instance = fmt.Sprintf(":%s", instanceName)
+		}
+		ip := net.ParseIP(host)
+		if ip != nil && ip.IsLoopback() {
+			host, _ = os.Hostname()
+		}
+		serverSPN = fmt.Sprintf("MSSQLSvc/%s%s", host, instance)
+	}
+	return
+}
diff --git a/msdsn/conn_str.go b/msdsn/conn_str.go
index 74799ee..4a15866 100644
--- a/msdsn/conn_str.go
+++ b/msdsn/conn_str.go
@@ -69,12 +69,16 @@ type Config struct {
 
 	// Do not use the following.
 
-	DialTimeout time.Duration // DialTimeout defaults to 15s. Set negative to disable.
+	DialTimeout time.Duration // DialTimeout defaults to 15s per protocol. Set negative to disable.
 	ConnTimeout time.Duration // Use context for timeouts.
 	KeepAlive   time.Duration // Leave at default.
 	PacketSize  uint16
 
 	Parameters map[string]string
+	// Protocols is an ordered list of protocols to dial
+	Protocols []string
+	// ProtocolParameters are written by non-tcp ProtocolParser implementations
+	ProtocolParameters map[string]interface{}
 }
 
 func SetupTLS(certificate string, insecureSkipVerify bool, hostInCertificate string, minTLSVersion string) (*tls.Config, error) {
@@ -112,7 +116,10 @@ func SetupTLS(certificate string, insecureSkipVerify bool, hostInCertificate str
 var skipSetup = errors.New("skip setting up TLS")
 
 func Parse(dsn string) (Config, error) {
-	p := Config{}
+	p := Config{
+		ProtocolParameters: map[string]interface{}{},
+		Protocols:          []string{},
+	}
 
 	var params map[string]string
 	var err error
@@ -140,15 +147,7 @@ func Parse(dsn string) (Config, error) {
 		}
 		p.LogFlags = Log(flags)
 	}
-	server := params["server"]
-	parts := strings.SplitN(server, `\`, 2)
-	p.Host = parts[0]
-	if p.Host == "." || strings.ToUpper(p.Host) == "(LOCAL)" || p.Host == "" {
-		p.Host = "localhost"
-	}
-	if len(parts) > 1 {
-		p.Instance = parts[1]
-	}
+
 	p.Database = params["database"]
 	p.User = params["user id"]
 	p.Password = params["password"]
@@ -198,13 +197,18 @@ func Parse(dsn string) (Config, error) {
 		}
 		p.ConnTimeout = time.Duration(timeout) * time.Second
 	}
-	p.DialTimeout = 15 * time.Second
+	f := len(p.Protocols)
+	if f == 0 {
+		f = 1
+	}
+	p.DialTimeout = time.Duration(15*f) * time.Second
 	if strdialtimeout, ok := params["dial timeout"]; ok {
 		timeout, err := strconv.ParseUint(strdialtimeout, 10, 64)
 		if err != nil {
 			f := "invalid dial timeout '%v': %v"
 			return p, fmt.Errorf(f, strdialtimeout, err.Error())
 		}
+
 		p.DialTimeout = time.Duration(timeout) * time.Second
 	}
 
@@ -272,10 +276,7 @@ func Parse(dsn string) (Config, error) {
 	serverSPN, ok := params["serverspn"]
 	if ok {
 		p.ServerSPN = serverSPN
-	} else {
-		// allow connections to sql server instances
-		p.ServerSPN = generateSpn(p.Host, instanceOrPort(p.Instance, p.Port))
-	}
+	} // If not set by the app, ServerSPN will be set by the successful dialer.
 
 	workstation, ok := params["workstation id"]
 	if ok {
@@ -330,6 +331,28 @@ func Parse(dsn string) (Config, error) {
 		p.DisableRetry = disableRetryDefault
 	}
 
+	server := params["server"]
+	protocol, ok := params["protocol"]
+
+	for _, parser := range ProtocolParsers {
+		if !ok || parser.Protocol() == protocol {
+			err = parser.ParseServer(server, &p)
+			if err != nil {
+				// if the caller only wants this protocol , fail right away
+				if ok {
+					return p, err
+				}
+			} else {
+				// Only enable a protocol if it can handle the server name
+				p.Protocols = append(p.Protocols, parser.Protocol())
+			}
+
+		}
+	}
+	if ok && len(p.Protocols) == 0 {
+		return p, fmt.Errorf("No protocol handler is available for protocol: '%s'", protocol)
+	}
+
 	return p, nil
 }
 
@@ -348,6 +371,10 @@ func (p Config) URL() *url.URL {
 		host = fmt.Sprintf("%s:%d", p.Host, p.Port)
 	}
 	q.Add("disableRetry", fmt.Sprintf("%t", p.DisableRetry))
+	protocol, ok := p.Parameters["protocol"]
+	if ok {
+		q.Add("protocol", protocol)
+	}
 	res := url.URL{
 		Scheme: "sqlserver",
 		Host:   host,
@@ -356,9 +383,11 @@ func (p Config) URL() *url.URL {
 	if p.Instance != "" {
 		res.Path = p.Instance
 	}
+	q.Add("dial timeout", strconv.FormatFloat(float64(p.DialTimeout.Seconds()), 'f', 0, 64))
 	if len(q) > 0 {
 		res.RawQuery = q.Encode()
 	}
+
 	return &res
 }
 
@@ -394,9 +423,14 @@ func splitConnectionString(dsn string) (res map[string]string) {
 			name = synonym
 		}
 		// "server" in ADO can include a protocol and a port.
-		// We only support tcp protocol
 		if name == "server" {
-			value = strings.TrimPrefix(value, "tcp:")
+			for _, parser := range ProtocolParsers {
+				prot := parser.Protocol() + ":"
+				if strings.HasPrefix(value, prot) {
+					res["protocol"] = parser.Protocol()
+				}
+				value = strings.TrimPrefix(value, prot)
+			}
 			serverParts := strings.Split(value, ",")
 			if len(serverParts) == 2 && len(serverParts[1]) > 0 {
 				value = serverParts[0]
@@ -612,16 +646,6 @@ func normalizeOdbcKey(s string) string {
 	return strings.ToLower(strings.TrimRightFunc(s, unicode.IsSpace))
 }
 
-func instanceOrPort(instance string, port uint64) string {
-	if len(instance) > 0 {
-		return instance
-	}
-
-	port = resolveServerPort(port)
-
-	return strconv.FormatInt(int64(port), 10)
-}
-
 const defaultServerPort = 1433
 
 func resolveServerPort(port uint64) uint64 {
@@ -632,6 +656,32 @@ func resolveServerPort(port uint64) uint64 {
 	return port
 }
 
-func generateSpn(host string, port string) string {
-	return fmt.Sprintf("MSSQLSvc/%s:%s", host, port)
+// ProtocolParser can populate Config with parameters to dial using its protocol
+type ProtocolParser interface {
+	ParseServer(server string, p *Config) error
+	Protocol() string
+}
+
+// ProtocolParsers is an ordered list of protocols that can be dialed. Each parser must have a corresponding Dialer in mssql.ProtocolDialers
+var ProtocolParsers []ProtocolParser = []ProtocolParser{
+	tcpParser{},
+}
+
+type tcpParser struct{}
+
+func (t tcpParser) ParseServer(server string, p *Config) error {
+	// a server name can have different forms
+	parts := strings.SplitN(server, `\`, 2)
+	p.Host = parts[0]
+	if p.Host == "." || strings.ToUpper(p.Host) == "(LOCAL)" || p.Host == "" {
+		p.Host = "localhost"
+	}
+	if len(parts) > 1 {
+		p.Instance = parts[1]
+	}
+	return nil
+}
+
+func (t tcpParser) Protocol() string {
+	return "tcp"
 }
diff --git a/msdsn/extensions.go b/msdsn/extensions.go
new file mode 100644
index 0000000..527c012
--- /dev/null
+++ b/msdsn/extensions.go
@@ -0,0 +1,20 @@
+package msdsn
+
+import (
+	"context"
+	"net"
+)
+
+type BrowserData map[string]map[string]string
+
+// ProtocolDialer makes the network connection for a protocol
+type ProtocolDialer interface {
+	// Translates data from SQL Browser to parameters in the config
+	ParseBrowserData(data BrowserData, p *Config) error
+	// DialConnection eturns a Dialer to make the connection. On success, also set Config.ServerSPN if it is unset.
+	DialConnection(ctx context.Context, p *Config) (conn net.Conn, err error)
+	// Returns true if information is needed from the SQL Browser service to make a connection
+	CallBrowser(p *Config) bool
+}
+
+var ProtocolDialers map[string]ProtocolDialer = map[string]ProtocolDialer{}
diff --git a/mssql.go b/mssql.go
index d036129..28288a4 100644
--- a/mssql.go
+++ b/mssql.go
@@ -41,6 +41,7 @@ func init() {
 		}
 		return netDialer{&net.Dialer{KeepAlive: ka}}
 	}
+	msdsn.ProtocolDialers["tcp"] = tcpDialer{}
 }
 
 var createDialer func(p *msdsn.Config) Dialer
diff --git a/namedpipe/namedpipe.go b/namedpipe/namedpipe.go
new file mode 100644
index 0000000..d33e8a6
--- /dev/null
+++ b/namedpipe/namedpipe.go
@@ -0,0 +1,18 @@
+package namedpipe
+
+import (
+	"runtime"
+
+	"github.com/microsoft/go-mssqldb/msdsn"
+)
+
+type namedPipeDialer struct{}
+
+var dialer namedPipeDialer = namedPipeDialer{}
+
+func init() {
+	if runtime.GOOS == "windows" {
+		msdsn.ProtocolParsers = append(msdsn.ProtocolParsers, dialer)
+		msdsn.ProtocolDialers["np"] = dialer
+	}
+}
diff --git a/namedpipe/namedpipe_windows.go b/namedpipe/namedpipe_windows.go
new file mode 100644
index 0000000..ae3bd6a
--- /dev/null
+++ b/namedpipe/namedpipe_windows.go
@@ -0,0 +1,75 @@
+package namedpipe
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"reflect"
+	"strings"
+
+	"github.com/microsoft/go-mssqldb/internal/np"
+	"github.com/microsoft/go-mssqldb/msdsn"
+)
+
+type namedPipeData struct {
+	PipeName string
+}
+
+func (n namedPipeDialer) ParseServer(server string, p *msdsn.Config) error {
+	// assume a server name starting with \\ is the full named pipe path
+	if strings.HasPrefix(server, `\\`) {
+		p.ProtocolParameters[n.Protocol()] = &namedPipeData{PipeName: server}
+	} else if p.Port > 0 {
+		return fmt.Errorf("Named pipes disallowed due to port being specified")
+	} else if p.Host == "" { // if the string specifies np:host\instance, tcpParser won't have filled in p.Host
+		parts := strings.SplitN(server, `\`, 2)
+		p.Host = parts[0]
+		if p.Host == "." || strings.ToUpper(p.Host) == "(LOCAL)" {
+			p.Host = "localhost"
+		}
+		if len(parts) > 1 {
+			p.Instance = parts[1]
+		}
+	}
+	return nil
+}
+
+func (n namedPipeDialer) Protocol() string {
+	return "np"
+}
+
+func (n namedPipeDialer) ParseBrowserData(data msdsn.BrowserData, p *msdsn.Config) error {
+	// If instance is specified, but no port, check SQL Server Browser
+	// for the instance and discover its port.
+	p.Instance = strings.ToUpper(p.Instance)
+	instance := p.Instance
+	if instance == "" {
+		instance = "MSSQLSERVER"
+	}
+	pipename, ok := data[instance]["np"]
+	if !ok {
+		f := "no named pipe instance matching '%v' returned from host '%v'"
+		return fmt.Errorf(f, p.Instance, p.Host)
+	}
+	p.ProtocolParameters[n.Protocol()] = namedPipeData{PipeName: pipename}
+	return nil
+}
+
+func (n namedPipeDialer) DialConnection(ctx context.Context, p *msdsn.Config) (conn net.Conn, err error) {
+	data := p.ProtocolParameters[n.Protocol()]
+	switch d := data.(type) {
+	case namedPipeData:
+		serverSPN := p.ServerSPN
+		conn, serverSPN, err = np.DialConnection(ctx, d.PipeName, p.Host, p.Instance, serverSPN)
+		if err == nil && p.ServerSPN == "" {
+			p.ServerSPN = serverSPN
+		}
+		return
+	}
+	return nil, fmt.Errorf("Unexpected protocol data specified for connection: %v", reflect.TypeOf(data))
+}
+
+func (n namedPipeDialer) CallBrowser(p *msdsn.Config) bool {
+	_, ok := p.ProtocolParameters[n.Protocol()]
+	return !ok
+}
diff --git a/namedpipe/stub.go b/namedpipe/stub.go
new file mode 100644
index 0000000..deb2fd0
--- /dev/null
+++ b/namedpipe/stub.go
@@ -0,0 +1,33 @@
+//go:build !windows
+// +build !windows
+
+package namedpipe
+
+import (
+	"context"
+	"fmt"
+	"net"
+
+	"github.com/microsoft/go-mssqldb/msdsn"
+)
+
+func (n namedPipeDialer) ParseServer(server string, p *msdsn.Config) error {
+	return fmt.Errorf("Named pipe connections are not supported on this operating system")
+}
+
+func (n namedPipeDialer) Protocol() string {
+	return "np"
+}
+
+func (n namedPipeDialer) ParseBrowserData(data msdsn.BrowserData, p *msdsn.Config) error {
+	return fmt.Errorf("Named pipe connections are not supported on this operating system")
+}
+
+func (n namedPipeDialer) DialConnection(ctx context.Context, p *msdsn.Config) (conn net.Conn, err error) {
+
+	return nil, fmt.Errorf("Named pipe connections are not supported on this operating system")
+}
+
+func (n namedPipeDialer) CallBrowser(p *msdsn.Config) bool {
+	return false
+}
diff --git a/protocol.go b/protocol.go
new file mode 100644
index 0000000..cf8f0be
--- /dev/null
+++ b/protocol.go
@@ -0,0 +1,138 @@
+package mssql
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/microsoft/go-mssqldb/msdsn"
+)
+
+type MssqlProtocolDialer interface {
+	// DialSqlConnection creates a net.Conn from a Connector based on the Config
+	DialSqlConnection(ctx context.Context, c *Connector, p *msdsn.Config) (conn net.Conn, err error)
+}
+
+type tcpDialer struct{}
+
+func (t tcpDialer) ParseBrowserData(data msdsn.BrowserData, p *msdsn.Config) error {
+	// If instance is specified, but no port, check SQL Server Browser
+	// for the instance and discover its port.
+	p.Instance = strings.ToUpper(p.Instance)
+	strport, ok := data[p.Instance]["tcp"]
+	if !ok {
+		f := "no instance matching '%v' returned from host '%v'"
+		return fmt.Errorf(f, p.Instance, p.Host)
+	}
+	port, err := strconv.ParseUint(strport, 0, 16)
+	if err != nil {
+		f := "invalid tcp port returned from Sql Server Browser '%v': %v"
+		return fmt.Errorf(f, strport, err.Error())
+	}
+	p.Port = port
+	return nil
+}
+
+func (t tcpDialer) DialConnection(ctx context.Context, p *msdsn.Config) (conn net.Conn, err error) {
+	return nil, fmt.Errorf("tcp dialer requires a Connector instance")
+}
+
+// SQL Server AlwaysOn Availability Group Listeners are bound by DNS to a
+// list of IP addresses.  So if there is more than one, try them all and
+// use the first one that allows a connection.
+func (t tcpDialer) DialSqlConnection(ctx context.Context, c *Connector, p *msdsn.Config) (conn net.Conn, err error) {
+	var ips []net.IP
+	ip := net.ParseIP(p.Host)
+	if ip == nil {
+		ips, err = net.LookupIP(p.Host)
+		if err != nil {
+			return
+		}
+	} else {
+		ips = []net.IP{ip}
+	}
+	if len(ips) == 1 {
+		d := c.getDialer(p)
+		addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(resolveServerPort(p.Port))))
+		conn, err = d.DialContext(ctx, "tcp", addr)
+
+	} else {
+		//Try Dials in parallel to avoid waiting for timeouts.
+		connChan := make(chan net.Conn, len(ips))
+		errChan := make(chan error, len(ips))
+		portStr := strconv.Itoa(int(resolveServerPort(p.Port)))
+		for _, ip := range ips {
+			go func(ip net.IP) {
+				d := c.getDialer(p)
+				addr := net.JoinHostPort(ip.String(), portStr)
+				conn, err := d.DialContext(ctx, "tcp", addr)
+				if err == nil {
+					connChan <- conn
+				} else {
+					errChan <- err
+				}
+			}(ip)
+		}
+		// Wait for either the *first* successful connection, or all the errors
+	wait_loop:
+		for i := range ips {
+			select {
+			case conn = <-connChan:
+				// Got a connection to use, close any others
+				go func(n int) {
+					for i := 0; i < n; i++ {
+						select {
+						case conn := <-connChan:
+							conn.Close()
+						case <-errChan:
+						}
+					}
+				}(len(ips) - i - 1)
+				// Remove any earlier errors we may have collected
+				err = nil
+				break wait_loop
+			case err = <-errChan:
+			}
+		}
+	}
+	// Can't do the usual err != nil check, as it is possible to have gotten an error before a successful connection
+	if conn == nil {
+		f := "unable to open tcp connection with host '%v:%v': %v"
+		return nil, fmt.Errorf(f, p.Host, resolveServerPort(p.Port), err.Error())
+	}
+	if p.ServerSPN == "" {
+		p.ServerSPN = generateSpn(p.Host, instanceOrPort(p.Instance, p.Port))
+	}
+	return conn, err
+}
+
+func (t tcpDialer) CallBrowser(p *msdsn.Config) bool {
+	return len(p.Instance) > 0 && p.Port == 0
+}
+
+func instanceOrPort(instance string, port uint64) string {
+	if len(instance) > 0 {
+		return instance
+	}
+	port = resolveServerPort(port)
+	return strconv.FormatInt(int64(port), 10)
+}
+
+func resolveServerPort(port uint64) uint64 {
+	if port == 0 {
+		return defaultServerPort
+	}
+
+	return port
+}
+
+func generateSpn(host string, port string) string {
+	ip := net.ParseIP(host)
+	if ip != nil && ip.IsLoopback() {
+		host, _ = os.Hostname()
+	}
+	return fmt.Sprintf("MSSQLSvc/%s:%s", host, port)
+}
diff --git a/sharedmemory/sharedmemory.go b/sharedmemory/sharedmemory.go
new file mode 100644
index 0000000..ce461ad
--- /dev/null
+++ b/sharedmemory/sharedmemory.go
@@ -0,0 +1,18 @@
+package sharedmemory
+
+import (
+	"runtime"
+
+	"github.com/microsoft/go-mssqldb/msdsn"
+)
+
+type sharedMemoryDialer struct{}
+
+var dialer sharedMemoryDialer = sharedMemoryDialer{}
+
+func init() {
+	if runtime.GOOS == "windows" {
+		msdsn.ProtocolParsers = append(msdsn.ProtocolParsers, dialer)
+		msdsn.ProtocolDialers["lpc"] = dialer
+	}
+}
diff --git a/sharedmemory/sharedmemory_windows.go b/sharedmemory/sharedmemory_windows.go
new file mode 100644
index 0000000..b54d16f
--- /dev/null
+++ b/sharedmemory/sharedmemory_windows.go
@@ -0,0 +1,65 @@
+package sharedmemory
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"os"
+	"strings"
+
+	"github.com/microsoft/go-mssqldb/internal/np"
+	"github.com/microsoft/go-mssqldb/msdsn"
+)
+
+func (n sharedMemoryDialer) ParseServer(server string, p *msdsn.Config) error {
+	if p.Port > 0 {
+		return fmt.Errorf("Shared memory disallowed due to port being specified")
+	} else if p.Host == "" { // if the string specifies np:host\instance, tcpParser won't have filled in p.Host
+		parts := strings.SplitN(server, `\`, 2)
+		p.Host = parts[0]
+		if p.Host == "." || strings.ToUpper(p.Host) == "(LOCAL)" {
+			p.Host = "localhost"
+		}
+		if len(parts) > 1 {
+			p.Instance = parts[1]
+		}
+	}
+	hostName, err := os.Hostname()
+	if err != nil {
+		// Don't know when HostName would return an error, but if it does only support shared memory for localhost or .
+		hostName = "localhost"
+	}
+	ip := net.ParseIP(p.Host)
+
+	if (ip != nil && !ip.IsLoopback()) || (ip == nil && (!strings.EqualFold(p.Host, hostName) && !strings.EqualFold("localhost", p.Host))) {
+		return fmt.Errorf("Cannot open a Shared Memory connection to a remote SQL server")
+	}
+	return nil
+}
+
+func (n sharedMemoryDialer) Protocol() string {
+	return "lpc"
+}
+
+func (n sharedMemoryDialer) ParseBrowserData(data msdsn.BrowserData, p *msdsn.Config) error {
+	return nil
+}
+
+func (n sharedMemoryDialer) DialConnection(ctx context.Context, p *msdsn.Config) (conn net.Conn, err error) {
+	pipename := `\\.\pipe\SQLLocal\`
+	if p.Instance != "" {
+		pipename = pipename + p.Instance
+	} else {
+		pipename = pipename + "MSSQLSERVER"
+	}
+	serverSPN := p.ServerSPN
+	conn, serverSPN, err = np.DialConnection(ctx, pipename, p.Host, p.Instance, p.ServerSPN)
+	if err == nil && p.ServerSPN == "" {
+		p.ServerSPN = serverSPN
+	}
+	return
+}
+
+func (n sharedMemoryDialer) CallBrowser(p *msdsn.Config) bool {
+	return false
+}
diff --git a/sharedmemory/stub.go b/sharedmemory/stub.go
new file mode 100644
index 0000000..1322252
--- /dev/null
+++ b/sharedmemory/stub.go
@@ -0,0 +1,33 @@
+//go:build !windows
+// +build !windows
+
+package sharedmemory
+
+import (
+	"context"
+	"fmt"
+	"net"
+
+	"github.com/microsoft/go-mssqldb/msdsn"
+)
+
+func (n sharedMemoryDialer) ParseServer(server string, p *msdsn.Config) error {
+	return fmt.Errorf("Shared memory connections are not supported on this operating system")
+}
+
+func (n sharedMemoryDialer) Protocol() string {
+	return "np"
+}
+
+func (n sharedMemoryDialer) ParseBrowserData(data msdsn.BrowserData, p *msdsn.Config) error {
+	return fmt.Errorf("Shared memory connections are not supported on this operating system")
+}
+
+func (n sharedMemoryDialer) DialConnection(ctx context.Context, p *msdsn.Config) (conn net.Conn, err error) {
+
+	return nil, fmt.Errorf("Shared memory connections are not supported on this operating system")
+}
+
+func (n sharedMemoryDialer) CallBrowser(p *msdsn.Config) bool {
+	return false
+}
diff --git a/tds.go b/tds.go
index 82da5f0..cc27a8f 100644
--- a/tds.go
+++ b/tds.go
@@ -10,7 +10,6 @@ import (
 	"io/ioutil"
 	"net"
 	"sort"
-	"strconv"
 	"strings"
 	"time"
 	"unicode/utf16"
@@ -20,8 +19,8 @@ import (
 	"github.com/microsoft/go-mssqldb/msdsn"
 )
 
-func parseInstances(msg []byte) map[string]map[string]string {
-	results := map[string]map[string]string{}
+func parseInstances(msg []byte) msdsn.BrowserData {
+	results := msdsn.BrowserData{}
 	if len(msg) > 3 && msg[0] == 5 {
 		out_s := string(msg[3:])
 		tokens := strings.Split(out_s, ";")
@@ -49,8 +48,8 @@ func parseInstances(msg []byte) map[string]map[string]string {
 	return results
 }
 
-func getInstances(ctx context.Context, d Dialer, address string) (map[string]map[string]string, error) {
-	conn, err := d.DialContext(ctx, "udp", address+":1434")
+func getInstances(ctx context.Context, d Dialer, address string) (msdsn.BrowserData, error) {
+	conn, err := d.DialContext(ctx, "udp", net.JoinHostPort(address, "1434"))
 	if err != nil {
 		return nil, err
 	}
@@ -167,6 +166,14 @@ func (p keySlice) Len() int           { return len(p) }
 func (p keySlice) Less(i, j int) bool { return p[i] < p[j] }
 func (p keySlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
 
+type preloginOption struct {
+	token  byte
+	offset uint16
+	length uint16
+}
+
+var preloginOptionSize = binary.Size(preloginOption{})
+
 // http://msdn.microsoft.com/en-us/library/dd357559.aspx
 func writePrelogin(packetType packetType, w *tdsBuffer, fields map[uint8][]byte) error {
 	var err error
@@ -232,20 +239,71 @@ func readPrelogin(r *tdsBuffer) (map[uint8][]byte, error) {
 	offset := 0
 	results := map[uint8][]byte{}
 	for {
-		rec_type := struct_buf[offset]
-		if rec_type == preloginTERMINATOR {
+		// read prelogin option
+		plOption, err := readPreloginOption(struct_buf, offset)
+		if err != nil {
+			return nil, err
+		}
+
+		if plOption.token == preloginTERMINATOR {
 			break
 		}
 
-		rec_offset := binary.BigEndian.Uint16(struct_buf[offset+1:])
-		rec_len := binary.BigEndian.Uint16(struct_buf[offset+3:])
-		value := struct_buf[rec_offset : rec_offset+rec_len]
-		results[rec_type] = value
-		offset += 5
+		// read prelogin option data
+		value, err := readPreloginOptionData(plOption, struct_buf)
+		if err != nil {
+			return nil, err
+		}
+		results[plOption.token] = value
+
+		offset += preloginOptionSize
 	}
 	return results, nil
 }
 
+func readPreloginOption(buffer []byte, offset int) (*preloginOption, error) {
+	buffer_length := len(buffer)
+
+	// check if prelogin option record exists in buffer
+	if offset >= buffer_length {
+		return nil, fmt.Errorf("invalid buffer, invalid prelogin option")
+	}
+
+	rec_type := buffer[offset]
+	if rec_type == preloginTERMINATOR {
+		return &preloginOption{token: rec_type}, nil
+	}
+
+	// check if prelogin option exists in buffer
+	if offset+preloginOptionSize >= buffer_length {
+		return nil, fmt.Errorf("invalid buffer, invalid prelogin option")
+	}
+
+	plOption := &preloginOption{
+		token:  rec_type,
+		offset: binary.BigEndian.Uint16(buffer[offset+1:]),
+		length: binary.BigEndian.Uint16(buffer[offset+3:]),
+	}
+
+	return plOption, nil
+}
+
+func readPreloginOptionData(plOption *preloginOption, buffer []byte) ([]byte, error) {
+	buffer_length := len(buffer)
+	// check if prelogin option data exists in buffer
+	if plOption == nil || int(plOption.length+plOption.offset) > buffer_length ||
+		int(plOption.offset) >= buffer_length {
+		return nil, fmt.Errorf("invalid buffer, invalid prelogin option")
+	}
+
+	if plOption.token == preloginTERMINATOR {
+		return nil, fmt.Errorf("cannot read data for prelogin terminator record")
+	}
+
+	value := buffer[plOption.offset : plOption.length+plOption.offset]
+	return value, nil
+}
+
 // OptionFlags1
 // http://msdn.microsoft.com/en-us/library/dd304019.aspx
 const (
@@ -837,70 +895,30 @@ func sendAttention(buf *tdsBuffer) error {
 	return buf.FinishPacket()
 }
 
-// SQL Server AlwaysOn Availability Group Listeners are bound by DNS to a
-// list of IP addresses.  So if there is more than one, try them all and
-// use the first one that allows a connection.
-func dialConnection(ctx context.Context, c *Connector, p msdsn.Config) (conn net.Conn, err error) {
-	var ips []net.IP
-	ip := net.ParseIP(p.Host)
-	if ip == nil {
-		ips, err = net.LookupIP(p.Host)
-		if err != nil {
-			return
+// Makes an attempt to connect with each available protocol, in order, until one succeeds or the timeout elapses
+func dialConnection(ctx context.Context, c *Connector, p *msdsn.Config, logger ContextLogger) (conn net.Conn, err error) {
+	for _, protocol := range p.Protocols {
+		dialer := msdsn.ProtocolDialers[protocol]
+		sqlDialer, ok := dialer.(MssqlProtocolDialer)
+		if logger != nil && uint64(p.LogFlags)&logDebug != 0 {
+			logger.Log(ctx, msdsn.LogDebug, "Dialing with protocol "+protocol)
 		}
-	} else {
-		ips = []net.IP{ip}
-	}
-	if len(ips) == 1 {
-		d := c.getDialer(&p)
-		addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(resolveServerPort(p.Port))))
-		conn, err = d.DialContext(ctx, "tcp", addr)
-
-	} else {
-		//Try Dials in parallel to avoid waiting for timeouts.
-		connChan := make(chan net.Conn, len(ips))
-		errChan := make(chan error, len(ips))
-		portStr := strconv.Itoa(int(resolveServerPort(p.Port)))
-		for _, ip := range ips {
-			go func(ip net.IP) {
-				d := c.getDialer(&p)
-				addr := net.JoinHostPort(ip.String(), portStr)
-				conn, err := d.DialContext(ctx, "tcp", addr)
-				if err == nil {
-					connChan <- conn
-				} else {
-					errChan <- err
-				}
-			}(ip)
+		if !ok {
+			conn, err = dialer.DialConnection(ctx, p)
+		} else {
+			conn, err = sqlDialer.DialSqlConnection(ctx, c, p)
 		}
-		// Wait for either the *first* successful connection, or all the errors
-	wait_loop:
-		for i := range ips {
-			select {
-			case conn = <-connChan:
-				// Got a connection to use, close any others
-				go func(n int) {
-					for i := 0; i < n; i++ {
-						select {
-						case conn := <-connChan:
-							conn.Close()
-						case <-errChan:
-						}
-					}
-				}(len(ips) - i - 1)
-				// Remove any earlier errors we may have collected
-				err = nil
-				break wait_loop
-			case err = <-errChan:
+		if err != nil && logger != nil && uint64(p.LogFlags)&logErrors != 0 {
+			logger.Log(ctx, msdsn.LogErrors, "Unable to connect with protocol "+protocol+":"+err.Error())
+		}
+		if conn != nil {
+			if logger != nil && uint64(p.LogFlags)&logDebug != 0 {
+				logger.Log(ctx, msdsn.LogDebug, "Returning connection from protocol "+protocol)
 			}
+			return
 		}
 	}
-	// Can't do the usual err != nil check, as it is possible to have gotten an error before a successful connection
-	if conn == nil {
-		f := "unable to open tcp connection with host '%v:%v': %v"
-		return nil, fmt.Errorf(f, p.Host, resolveServerPort(p.Port), err.Error())
-	}
-	return conn, err
+	return
 }
 
 func preparePreloginFields(p msdsn.Config, fe *featureExtFedAuth) map[uint8][]byte {
@@ -1029,12 +1047,58 @@ func prepareLogin(ctx context.Context, c *Connector, p msdsn.Config, logger Cont
 	return l, nil
 }
 
+func queryBrowser(ctx context.Context, dialCtx context.Context, c *Connector, logger ContextLogger, p *msdsn.Config) error {
+	var instances msdsn.BrowserData
+	var err error
+	pErrors := make(map[string]error)
+	for _, protocol := range p.Protocols {
+		pd, ok := msdsn.ProtocolDialers[protocol]
+		if !ok {
+			return fmt.Errorf("No dialer is configured for protocol '%s'", protocol)
+		}
+		if pd.CallBrowser(p) {
+			if instances == nil {
+				d := c.getDialer(p)
+				instances, err = getInstances(dialCtx, d, p.Host)
+				if err != nil {
+					f := "unable to get instances from Sql Server Browser on host %v: %v"
+					return fmt.Errorf(f, p.Host, err.Error())
+				}
+			}
+			pErr := pd.ParseBrowserData(instances, p)
+			if pErr != nil {
+				pErrors[protocol] = pErr
+				if logger != nil && uint64(p.LogFlags)&logErrors != 0 {
+					logger.Log(ctx, msdsn.Log(logErrors), "Removing protocol "+protocol+" from dialers. Error:"+pErr.Error())
+				}
+			}
+		}
+	}
+	// If any dialer got an error parsing instances, remove it from the dialer list
+	// If no dialers are left, return an error
+	if len(pErrors) == len(p.Protocols) {
+		return fmt.Errorf("Unable to find a matching instance for any supported protocol on host %v", p.Host)
+	}
+	if len(pErrors) > 0 {
+		validProtocols := make([]string, len(p.Protocols)-len(pErrors))
+		i := 0
+		for _, protocol := range p.Protocols {
+			_, hasError := pErrors[protocol]
+			if !hasError {
+				validProtocols[i] = protocol
+				i++
+			}
+		}
+	}
+	return nil
+}
+
 func connect(ctx context.Context, c *Connector, logger ContextLogger, p msdsn.Config) (res *tdsSession, err error) {
 	dialCtx := ctx
 	if p.DialTimeout >= 0 {
 		dt := p.DialTimeout
 		if dt == 0 {
-			dt = 15 * time.Second
+			dt = time.Duration(15*len(p.Protocols)) * time.Second
 		}
 		var cancel func()
 		dialCtx, cancel = context.WithTimeout(ctx, dt)
@@ -1047,29 +1111,12 @@ func connect(ctx context.Context, c *Connector, logger ContextLogger, p msdsn.Co
 		// when port is specified instance name is not used
 		// you should not provide instance name when you provide port
 		logger.Log(ctx, msdsn.LogDebug, "WARN: You specified both instance name and port in the connection string, port will be used and instance name will be ignored")
-	} else if len(p.Instance) > 0 && p.Port == 0 {
-		// If instance is specified, but no port, check SQL Server Browser
-		// for the instance and discover its port.
-		p.Instance = strings.ToUpper(p.Instance)
-		d := c.getDialer(&p)
-		instances, err := getInstances(dialCtx, d, p.Host)
-		if err != nil {
-			f := "unable to get instances from Sql Server Browser on host %v: %v"
-			return nil, fmt.Errorf(f, p.Host, err.Error())
-		}
-		strport, ok := instances[p.Instance]["tcp"]
-		if !ok {
-			f := "no instance matching '%v' returned from host '%v'"
-			return nil, fmt.Errorf(f, p.Instance, p.Host)
-		}
-		port, err := strconv.ParseUint(strport, 0, 16)
-		if err != nil {
-			f := "invalid tcp port returned from Sql Server Browser '%v': %v"
-			return nil, fmt.Errorf(f, strport, err.Error())
-		}
-		p.Port = port
 	}
 
+	err = queryBrowser(ctx, dialCtx, c, logger, &p)
+	if err != nil {
+		return nil, err
+	}
 	if p.Port == 0 {
 		p.Port = defaultServerPort
 	}
@@ -1089,7 +1136,7 @@ func connect(ctx context.Context, c *Connector, logger ContextLogger, p msdsn.Co
 	}
 
 initiate_connection:
-	conn, err := dialConnection(dialCtx, c, p)
+	conn, err := dialConnection(dialCtx, c, &p, logger)
 	if err != nil {
 		return nil, err
 	}
@@ -1279,11 +1326,3 @@ initiate_connection:
 	}
 	return &sess, nil
 }
-
-func resolveServerPort(port uint64) uint64 {
-	if port == 0 {
-		return defaultServerPort
-	}
-
-	return port
-}
diff --git a/token.go b/token.go
index 5f9e820..76d4e02 100644
--- a/token.go
+++ b/token.go
@@ -3,10 +3,10 @@ package mssql
 import (
 	"context"
 	"encoding/binary"
-	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
+	"net"
 	"strconv"
 
 	"github.com/golang-sql/sqlexp"
@@ -674,6 +674,13 @@ func processSingleResponse(ctx context.Context, sess *tdsSession, ch chan tokenS
 		if sess.logFlags&logErrors != 0 {
 			sess.logger.Log(ctx, msdsn.LogErrors, fmt.Sprintf("BeginRead failed %v", err))
 		}
+		switch e := err.(type) {
+		case *net.OpError:
+			err = e
+		default:
+			// the named pipe provider returns a raw win32 error so fake an OpError
+			err = &net.OpError{Op: "Read", Err: err}
+		}
 		ch <- err
 		return
 	}
@@ -959,7 +966,7 @@ func (t tokenProcessor) nextToken() (tokenStruct, error) {
 		}
 		// we did not get cancellation confirmation, something is not
 		// right, this connection is not usable anymore
-		return nil, errors.New("did not get cancellation confirmation from the server")
+		return nil, ServerError{Error{Message: "did not get cancellation confirmation from the server"}}
 	}
 }
 
diff --git a/tvp_go19.go b/tvp_go19.go
index 32485a1..0d55547 100644
--- a/tvp_go19.go
+++ b/tvp_go19.go
@@ -1,3 +1,4 @@
+//go:build go1.9
 // +build go1.9
 
 package mssql
@@ -16,6 +17,7 @@ import (
 const (
 	jsonTag      = "json"
 	tvpTag       = "tvp"
+	tvpIdentity  = "@identity"
 	skipTagValue = "-"
 	sqlSeparator = "."
 )
@@ -29,7 +31,7 @@ var (
 	ErrorWrongTyping      = errors.New("the number of elements in columnStr and tvpFieldIndexes do not align")
 )
 
-//TVP is driver type, which allows supporting Table Valued Parameters (TVP) in SQL Server
+// TVP is driver type, which allows supporting Table Valued Parameters (TVP) in SQL Server
 type TVP struct {
 	//TypeName mustn't be default value
 	TypeName string
@@ -76,8 +78,8 @@ func (tvp TVP) encode(schema, name string, columnStr []columnStruct, tvpFieldInd
 	binary.Write(buf, binary.LittleEndian, uint16(len(columnStr)))
 
 	for i, column := range columnStr {
-		binary.Write(buf, binary.LittleEndian, uint32(column.UserType))
-		binary.Write(buf, binary.LittleEndian, uint16(column.Flags))
+		binary.Write(buf, binary.LittleEndian, column.UserType)
+		binary.Write(buf, binary.LittleEndian, column.Flags)
 		writeTypeInfo(buf, &columnStr[i].ti)
 		writeBVarChar(buf, "")
 	}
@@ -96,6 +98,9 @@ func (tvp TVP) encode(schema, name string, columnStr []columnStruct, tvpFieldInd
 		refStr := reflect.ValueOf(val.Index(i).Interface())
 		buf.WriteByte(_TVP_ROW_TOKEN)
 		for columnStrIdx, fieldIdx := range tvpFieldIndexes {
+			if columnStr[columnStrIdx].Flags == fDefault {
+				continue
+			}
 			field := refStr.Field(fieldIdx)
 			tvpVal := field.Interface()
 			if tvp.verifyStandardTypeOnNull(buf, tvpVal) {
@@ -135,6 +140,11 @@ func (tvp TVP) encode(schema, name string, columnStr []columnStruct, tvpFieldInd
 }
 
 func (tvp TVP) columnTypes() ([]columnStruct, []int, error) {
+	type fieldDetailStore struct {
+		defaultValue interface{}
+		isIdentity   bool
+	}
+
 	val := reflect.ValueOf(tvp.Value)
 	var firstRow interface{}
 	if val.Len() != 0 {
@@ -145,7 +155,7 @@ func (tvp TVP) columnTypes() ([]columnStruct, []int, error) {
 
 	tvpRow := reflect.TypeOf(firstRow)
 	columnCount := tvpRow.NumField()
-	defaultValues := make([]interface{}, 0, columnCount)
+	defaultValues := make([]fieldDetailStore, 0, columnCount)
 	tvpFieldIndexes := make([]int, 0, columnCount)
 	for i := 0; i < columnCount; i++ {
 		field := tvpRow.Field(i)
@@ -155,12 +165,19 @@ func (tvp TVP) columnTypes() ([]columnStruct, []int, error) {
 			continue
 		}
 		tvpFieldIndexes = append(tvpFieldIndexes, i)
+		isIdentity := tvpTagValue == tvpIdentity
 		if field.Type.Kind() == reflect.Ptr {
 			v := reflect.New(field.Type.Elem())
-			defaultValues = append(defaultValues, v.Interface())
+			defaultValues = append(defaultValues, fieldDetailStore{
+				defaultValue: v.Interface(),
+				isIdentity:   isIdentity,
+			})
 			continue
 		}
-		defaultValues = append(defaultValues, tvp.createZeroType(reflect.Zero(field.Type).Interface()))
+		defaultValues = append(defaultValues, fieldDetailStore{
+			defaultValue: tvp.createZeroType(reflect.Zero(field.Type).Interface()),
+			isIdentity:   isIdentity,
+		})
 	}
 
 	if columnCount-len(tvpFieldIndexes) == columnCount {
@@ -176,9 +193,9 @@ func (tvp TVP) columnTypes() ([]columnStruct, []int, error) {
 
 	columnConfiguration := make([]columnStruct, 0, columnCount)
 	for index, val := range defaultValues {
-		cval, err := convertInputParameter(val)
+		cval, err := convertInputParameter(val.defaultValue)
 		if err != nil {
-			return nil, nil, fmt.Errorf("failed to convert tvp parameter row %d col %d: %s", index, val, err)
+			return nil, nil, fmt.Errorf("failed to convert tvp parameter row %d col %d: %s", index, val.defaultValue, err)
 		}
 		param, err := stmt.makeParam(cval)
 		if err != nil {
@@ -187,6 +204,9 @@ func (tvp TVP) columnTypes() ([]columnStruct, []int, error) {
 		column := columnStruct{
 			ti: param.ti,
 		}
+		if val.isIdentity {
+			column.Flags = fDefault
+		}
 		switch param.ti.TypeId {
 		case typeNVarChar, typeBigVarBin:
 			column.ti.Size = 0
diff --git a/types.go b/types.go
index 8780341..3b4760e 100644
--- a/types.go
+++ b/types.go
@@ -79,6 +79,12 @@ const _PLP_TERMINATOR = 0x00000000
 const _TVP_END_TOKEN = 0x00
 const _TVP_ROW_TOKEN = 0x01
 
+// TVP_COLMETADATA definition
+// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/0dfc5367-a388-4c92-9ba4-4d28e775acbc
+const (
+	fDefault = 0x200
+)
+
 // TYPE_INFO rule
 // http://msdn.microsoft.com/en-us/library/dd358284.aspx
 type typeInfo struct {
@@ -1353,12 +1359,13 @@ func makeGoLangTypeName(ti typeInfo) string {
 // not a variable length type ok should return false.
 // If length is not limited other than system limits, it should return math.MaxInt64.
 // The following are examples of returned values for various types:
-//   TEXT          (math.MaxInt64, true)
-//   varchar(10)   (10, true)
-//   nvarchar(10)  (10, true)
-//   decimal       (0, false)
-//   int           (0, false)
-//   bytea(30)     (30, true)
+//
+//	TEXT          (math.MaxInt64, true)
+//	varchar(10)   (10, true)
+//	nvarchar(10)  (10, true)
+//	decimal       (0, false)
+//	int           (0, false)
+//	bytea(30)     (30, true)
 func makeGoLangTypeLength(ti typeInfo) (int64, bool) {
 	switch ti.TypeId {
 	case typeInt1:
@@ -1476,12 +1483,13 @@ func makeGoLangTypeLength(ti typeInfo) (int64, bool) {
 // not a variable length type ok should return false.
 // If length is not limited other than system limits, it should return math.MaxInt64.
 // The following are examples of returned values for various types:
-//   TEXT          (math.MaxInt64, true)
-//   varchar(10)   (10, true)
-//   nvarchar(10)  (10, true)
-//   decimal       (0, false)
-//   int           (0, false)
-//   bytea(30)     (30, true)
+//
+//	TEXT          (math.MaxInt64, true)
+//	varchar(10)   (10, true)
+//	nvarchar(10)  (10, true)
+//	decimal       (0, false)
+//	int           (0, false)
+//	bytea(30)     (30, true)
 func makeGoLangTypePrecisionScale(ti typeInfo) (int64, int64, bool) {
 	switch ti.TypeId {
 	case typeInt1:


Test patch:
diff --git a/accesstokenconnector_test.go b/accesstokenconnector_test.go
index 52940fc..303c531 100644
--- a/accesstokenconnector_test.go
+++ b/accesstokenconnector_test.go
@@ -1,3 +1,4 @@
+//go:build go1.10
 // +build go1.10
 
 package mssql
@@ -79,7 +80,7 @@ func TestNewAccessTokenConnector(t *testing.T) {
 
 func TestAccessTokenConnectorFailsToConnectIfNoAccessToken(t *testing.T) {
 	errorText := "This is a test"
-	dsn := "Server=server.database.windows.net;Database=db"
+	dsn := "Server=tcp:server.database.windows.net;Database=db"
 	tp := func() (string, error) { return "", errors.New(errorText) }
 	sut, err := NewAccessTokenConnector(dsn, tp)
 	if err != nil {
diff --git a/azuread/configuration_test.go b/azuread/configuration_test.go
index e830d17..0662df7 100644
--- a/azuread/configuration_test.go
+++ b/azuread/configuration_test.go
@@ -13,6 +13,7 @@ import (
 
 func TestValidateParameters(t *testing.T) {
 	passphrase := "somesecret"
+	accessToken := "some-access-token"
 	certificatepath := "/user/cert/cert.pfx"
 	appid := "applicationclientid=someguid"
 	certprop := "clientcertpath=" + certificatepath
@@ -101,6 +102,16 @@ func TestValidateParameters(t *testing.T) {
 				fedAuthWorkflow: ActiveDirectoryManagedIdentity,
 			},
 		},
+		{
+			name: "application with access token",
+			dsn:  "server=someserver.database.windows.net;fedauth=ActiveDirectoryServicePrincipalAccessToken;password=some-access-token;",
+			expected: &azureFedAuthConfig{
+				password:        accessToken,
+				adalWorkflow:    mssql.FedAuthADALWorkflowNone,
+				fedAuthWorkflow: ActiveDirectoryServicePrincipalAccessToken,
+				fedAuthLibrary:  mssql.FedAuthLibrarySecurityToken,
+			},
+		},
 	}
 	for _, tst := range tests {
 		config, err := parse(tst.dsn)
diff --git a/bad_server_test.go b/bad_server_test.go
index ff0e57d..6c57903 100644
--- a/bad_server_test.go
+++ b/bad_server_test.go
@@ -47,7 +47,7 @@ func testBadServer(t *testing.T, handler func(net.Conn)) {
 	tl := testLogger{t: t}
 	defer tl.StopLogging()
 	SetLogger(&tl)
-	testConnectionBad(t, fmt.Sprintf("host=%s;port=%d;log=255", addr.IP.String(), addr.Port))
+	_ = testConnectionBad(t, fmt.Sprintf("host=%s;port=%d;log=255;protocol=tcp", addr.IP.String(), addr.Port))
 }
 
 func TestBadServerCloseConnection(t *testing.T) {
@@ -119,6 +119,42 @@ func TestBadServerPreLoginPacketWithJustEncryptionField(t *testing.T) {
 	})
 }
 
+func TestBadServerPreLoginPacketWithInvalidOptions(t *testing.T) {
+	testBadServer(t, func(conn net.Conn) {
+		buf := newTdsBuffer(defaultPacketSize, conn)
+		preloginPacket := []byte{4, 1, 0, 32, 0, 0, 1}
+
+		_, err := buf.Write(preloginPacket)
+		if err != nil {
+			t.Fatal("Writing PRELOGIN packet failed", err)
+		}
+	})
+}
+
+func TestBadServerPreLoginPacketWithNoTerminator(t *testing.T) {
+	testBadServer(t, func(conn net.Conn) {
+		buf := newTdsBuffer(defaultPacketSize, conn)
+		preloginPacket := []byte{4, 1, 0, 32, 0, 0, 1, 0, 0, 0, 16, 0, 6, 1, 0, 22, 0, 1, 6, 0, 23, 0, 1, 0, 12, 0, 7, 208, 0, 0, 2, 1}
+
+		_, err := buf.Write(preloginPacket)
+		if err != nil {
+			t.Fatal("Writing PRELOGIN packet failed", err)
+		}
+	})
+}
+
+func TestBadServerPreLoginPacketWithMissingData(t *testing.T) {
+	testBadServer(t, func(conn net.Conn) {
+		buf := newTdsBuffer(defaultPacketSize, conn)
+		preloginPacket := []byte{4, 1, 0, 32, 0, 0, 1, 0, 0, 0, 16, 0, 6, 1, 0, 22, 0, 1, 6, 0, 23, 0, 1, 255, 12, 0, 7, 208, 0, 0, 2, 1}
+
+		_, err := buf.Write(preloginPacket)
+		if err != nil {
+			t.Fatal("Writing PRELOGIN packet failed", err)
+		}
+	})
+}
+
 func goodPreloginSequence(t *testing.T, buf *tdsBuffer) {
 	// read prelogin request
 	packetType, err := buf.BeginRead()
diff --git a/msdsn/conn_str_test.go b/msdsn/conn_str_test.go
index 20a6cd2..8b0f16f 100644
--- a/msdsn/conn_str_test.go
+++ b/msdsn/conn_str_test.go
@@ -203,7 +203,7 @@ func TestSplitConnectionStringURL(t *testing.T) {
 }
 
 func TestConnParseRoundTripFixed(t *testing.T) {
-	connStr := "sqlserver://sa:sa@localhost/sqlexpress?database=master&log=127&disableretry=true"
+	connStr := "sqlserver://sa:sa@localhost/sqlexpress?database=master&log=127&disableretry=true&dial+timeout=30"
 	params, err := Parse(connStr)
 	if err != nil {
 		t.Fatal("Test URL is not valid", err)
diff --git a/msdsn/protocolparse_test.go b/msdsn/protocolparse_test.go
new file mode 100644
index 0000000..8ea712c
--- /dev/null
+++ b/msdsn/protocolparse_test.go
@@ -0,0 +1,78 @@
+package msdsn
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+)
+
+type testProtocol struct{}
+
+var protocolImpl = testProtocol{}
+
+func (t testProtocol) ParseServer(server string, p *Config) error {
+	if strings.HasPrefix(server, "**") {
+		p.ProtocolParameters[t.Protocol()] = "special"
+	}
+	if server == "fail" {
+		return fmt.Errorf("ParseServer fail")
+	}
+	// p.Host is empty if tst protocol was specified
+	if p.Host == "" {
+		p.Host = strings.TrimPrefix(server, "**")
+	}
+	return nil
+}
+
+func (t testProtocol) Protocol() string {
+	return "tst"
+}
+
+func init() {
+	ProtocolParsers = append(ProtocolParsers, protocolImpl)
+}
+
+func TestProtocolParseExtension(t *testing.T) {
+	type tst struct {
+		dsn            string
+		expectedConfig func(c *Config) bool
+	}
+	tests := []tst{
+		{"server=myserver", func(c *Config) bool {
+			return len(c.Protocols) == 2 && c.Protocols[0] == "tcp" && c.Protocols[1] == "tst" && c.Host == "myserver" && c.ProtocolParameters["tst"] == nil
+		}},
+		{"server=**myserver", func(c *Config) bool {
+			return len(c.Protocols) == 2 && c.Protocols[0] == "tcp" && c.Protocols[1] == "tst" && c.Host == "**myserver" && c.ProtocolParameters["tst"] == "special"
+		}},
+		{"server=tst:**myserver", func(c *Config) bool {
+			return len(c.Protocols) == 1 && c.Protocols[0] == "tst" && c.Host == "myserver" && c.ProtocolParameters["tst"] == "special"
+		}},
+		{"server=tst:myserver", func(c *Config) bool {
+			return len(c.Protocols) == 1 && c.Protocols[0] == "tst" && c.Host == "myserver" && c.ProtocolParameters["tst"] == nil
+		}},
+		{"sqlserver://user@myserver", func(c *Config) bool {
+			return len(c.Protocols) == 2 && c.Protocols[0] == "tcp" && c.Protocols[1] == "tst" && c.Host == "myserver" && c.ProtocolParameters["tst"] == nil
+		}},
+		{"sqlserver://**myserver", func(c *Config) bool {
+			return len(c.Protocols) == 2 && c.Protocols[0] == "tcp" && c.Protocols[1] == "tst" && c.Host == "**myserver" && c.ProtocolParameters["tst"] == "special"
+		}},
+		{"sqlserver://**myserver?protocol=tst", func(c *Config) bool {
+			return len(c.Protocols) == 1 && c.Protocols[0] == "tst" && c.Host == "myserver" && c.ProtocolParameters["tst"] == "special"
+		}},
+		{"sqlserver://myserver?protocol=tst", func(c *Config) bool {
+			return len(c.Protocols) == 1 && c.Protocols[0] == "tst" && c.Host == "myserver" && c.ProtocolParameters["tst"] == nil
+		}},
+		{"sqlserver://fail", func(c *Config) bool {
+			return len(c.Protocols) == 1 && c.Protocols[0] == "tcp" && c.Host == "fail" && c.ProtocolParameters["tst"] == nil
+		}},
+	}
+	for _, test := range tests {
+		c, err := Parse(test.dsn)
+		if err != nil {
+			t.Fatalf("Unexpected error parsing '%s':'%s'", test.dsn, err.Error())
+		}
+		if !test.expectedConfig(&c) {
+			t.Fatalf("Config validation failed for '%s'. Config: '%v'", test.dsn, c)
+		}
+	}
+}
diff --git a/namedpipe_test.go b/namedpipe_test.go
new file mode 100644
index 0000000..f12fa3e
--- /dev/null
+++ b/namedpipe_test.go
@@ -0,0 +1,36 @@
+//go:build np
+// +build np
+
+package mssql
+
+import (
+	"testing"
+
+	"github.com/microsoft/go-mssqldb/msdsn"
+	_ "github.com/microsoft/go-mssqldb/namedpipe"
+)
+
+func TestNamedPipeProtocolInstalled(t *testing.T) {
+	for _, p := range msdsn.ProtocolParsers {
+		if p.Protocol() == "np" {
+			return
+		}
+	}
+	t.Fatalf("ProtocolParsers is missing np %v", msdsn.ProtocolParsers)
+}
+
+func TestNamedPipeConnection(t *testing.T) {
+	params := testConnParams(t)
+	protocol, ok := params.Parameters["protocol"]
+	if ok && protocol != "np" {
+		t.Skip("Test is not running with named pipe protocol set")
+	}
+	conn, _ := open(t)
+	row := conn.QueryRow(`SELECT net_transport FROM sys.dm_exec_connections WHERE session_id = @@SPID`)
+	if err := row.Scan(&protocol); err != nil {
+		t.Fatalf("Unable to query connection protocol %s", err.Error())
+	}
+	if protocol != "Named pipe" {
+		t.Fatalf("Named pips connection not made. Protocol: %s", protocol)
+	}
+}
diff --git a/net_go116_test.go b/net_go116_test.go
index 27776d3..eea01b2 100644
--- a/net_go116_test.go
+++ b/net_go116_test.go
@@ -1,3 +1,4 @@
+//go:build go1.16
 // +build go1.16
 
 package mssql
@@ -56,6 +57,7 @@ func TestTimeoutConn(t *testing.T) {
 }
 
 func TestTLSHandshakeConn(t *testing.T) {
+	skipIfNamedPipesEnabled(t)
 	tl := testLogger{t: t}
 	defer tl.StopLogging()
 	SetLogger(&tl)
@@ -68,7 +70,7 @@ func TestTLSHandshakeConn(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 	defer cancel()
 
-	toconn, err := dialConnection(ctx, connector, connector.params)
+	toconn, err := dialConnection(ctx, connector, &connector.params, nil)
 	if err != nil {
 		t.Error(err)
 	}
@@ -115,6 +117,7 @@ func TestTLSHandshakeConn(t *testing.T) {
 }
 
 func TestPassthroughConn(t *testing.T) {
+	skipIfNamedPipesEnabled(t)
 	tl := testLogger{t: t}
 	defer tl.StopLogging()
 	SetLogger(&tl)
@@ -127,7 +130,7 @@ func TestPassthroughConn(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 	defer cancel()
 
-	toconn, err := dialConnection(ctx, connector, connector.params)
+	toconn, err := dialConnection(ctx, connector, &connector.params, nil)
 	if err != nil {
 		t.Error(err)
 	}
diff --git a/queries_go19_test.go b/queries_go19_test.go
index 60cde99..6f462f0 100644
--- a/queries_go19_test.go
+++ b/queries_go19_test.go
@@ -746,6 +746,7 @@ func TestParamNoName(t *testing.T) {
 //
 // Issue https://github.com/denisenkom/go-mssqldb/issues/166
 func TestTLSServerReadClose(t *testing.T) {
+	skipIfNamedPipesEnabled(t)
 	query := `
 with
     config_cte (config) as (
@@ -1079,7 +1080,7 @@ func TestMessageQueue(t *testing.T) {
 	defer conn.Close()
 	defer logger.StopLogging()
 	retmsg := &sqlexp.ReturnMessage{}
-	latency, _ := getLatency(t)
+	latency := getLatency(t)
 	ctx, cancel := context.WithTimeout(context.Background(), latency+200000*time.Millisecond)
 	defer cancel()
 	rows, err := conn.QueryContext(ctx, "PRINT 'msg1'; select 100 as c; PRINT 'msg2'", retmsg)
@@ -1261,7 +1262,7 @@ func TestTimeoutWithNoResults(t *testing.T) {
 	defer conn.Close()
 	defer logger.StopLogging()
 
-	latency, _ := getLatency(t)
+	latency := getLatency(t)
 	ctx, cancel := context.WithTimeout(context.Background(), latency+5000*time.Millisecond)
 	defer cancel()
 	retmsg := &sqlexp.ReturnMessage{}
@@ -1299,21 +1300,27 @@ func TestCancelWithNoResults(t *testing.T) {
 	defer conn.Close()
 	defer logger.StopLogging()
 
-	latency, _ := getLatency(t)
-	ctx, cancel := context.WithTimeout(context.Background(), latency+5000*time.Millisecond)
+	latency := getLatency(t)
+
+	ctx, cancel := context.WithTimeout(context.Background(), latency+15000*time.Millisecond)
 	retmsg := &sqlexp.ReturnMessage{}
+	t.Logf("Calling QueryContext")
 	r, err := conn.QueryContext(ctx, `waitfor delay '00:00:15'; select 100`, retmsg)
+	t.Logf("QueryContext returned")
 	if err != nil {
 		cancel()
 		t.Fatal(err.Error())
 	}
 	defer r.Close()
-	time.Sleep(latency + 100*time.Millisecond)
+	// QueryContext doesn't return until the connection is established
+	time.Sleep(100 * time.Millisecond)
+	t.Logf("Calling Cancel")
 	cancel()
+	t.Logf("Cancel returned")
 	active := true
 	for active {
 		msg := retmsg.Message(ctx)
-		t.Logf("Got a message: %s", reflect.TypeOf(msg))
+		t.Logf("%v Got a message: %s", time.Now(), reflect.TypeOf(msg))
 		switch m := msg.(type) {
 		case sqlexp.MsgNextResultSet:
 			active = r.NextResultSet()
@@ -1371,7 +1378,7 @@ func TestSprocWithCursorNoResult(t *testing.T) {
 		t.Fatalf("Unable to create test sproc: %v", e)
 	}
 	defer conn.Exec(DropSprocWithCursor)
-	latency, _ := getLatency(t)
+	latency := getLatency(t)
 	ctx, cancel := context.WithTimeout(context.Background(), latency+500*time.Millisecond)
 	defer cancel()
 	retmsg := &sqlexp.ReturnMessage{}
@@ -1415,7 +1422,7 @@ func TestErrorAsLastResult(t *testing.T) {
 	conn, logger := open(t)
 	defer conn.Close()
 	defer logger.StopLogging()
-	latency, _ := getLatency(t)
+	latency := getLatency(t)
 	ctx, cancel := context.WithTimeout(context.Background(), latency+5000*time.Millisecond)
 	defer cancel()
 	retmsg := &sqlexp.ReturnMessage{}
diff --git a/queries_test.go b/queries_test.go
index 20bcfb6..13f045c 100644
--- a/queries_test.go
+++ b/queries_test.go
@@ -5,6 +5,7 @@ import (
 	"context"
 	"database/sql"
 	"database/sql/driver"
+	"errors"
 	"fmt"
 	"io"
 	"log"
@@ -1387,6 +1388,46 @@ func TestProcessQueryErrors(t *testing.T) {
 	}
 }
 
+type mockReadWriteCloser struct {
+	io.ReadWriteCloser
+}
+
+func (*mockReadWriteCloser) Read([]byte) (int, error) { return 0, errors.New("fake err") }
+
+func TestProcessQueryCancelConfirmationError(t *testing.T) {
+	tl := testLogger{t: t}
+	defer tl.StopLogging()
+	conn := internalConnection(t, &tl)
+	defer conn.Close()
+
+	stmt, err := conn.prepareContext(context.Background(), "select 1")
+	if err != nil {
+		t.Fatal("prepareContext expected to succeed, but it failed with", err)
+	}
+	err = stmt.sendQuery(context.Background(), []namedValue{})
+	if err != nil {
+		t.Fatal("sendQuery expected to succeed, but it failed with", err)
+	}
+	// mock real connection to imitate situation when you write but dont get response
+	conn.sess.buf.transport = &mockReadWriteCloser{ReadWriteCloser: conn.sess.buf.transport}
+	// canceling context to try to send attention request
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+
+	_, err = stmt.processQueryResponse(ctx)
+	if err == nil {
+		t.Error("processQueryResponse expected to fail but it succeeded")
+	}
+	// should not fail with ErrBadConn because query was successfully sent to server
+	if _, ok := err.(ServerError); !ok {
+		t.Error("processQueryResponse expected to fail with ServerError error but failed with other error: ", err)
+	}
+
+	if conn.connectionGood {
+		t.Fatal("Connection should be in a bad state")
+	}
+}
+
 func TestProcessQueryNextErrors(t *testing.T) {
 	tl := testLogger{t: t}
 	defer tl.StopLogging()
@@ -1499,7 +1540,7 @@ func TestLongConnection(t *testing.T) {
 		{"2", "00:00:01", 6 * time.Second, false},
 
 		// Check no connection timeout.
-		{"0", "00:00:01", 2 * time.Second, false},
+		{"0", "00:00:01", 6 * time.Second, false},
 		// {"0", "00:00:45", 60 * time.Second, false}, // Skip for normal testing to limit time.
 	}
 
@@ -1632,42 +1673,46 @@ func TestColumnTypeIntrospection(t *testing.T) {
 	defer conn.Close()
 	defer logger.StopLogging()
 	for _, tt := range tests {
-		rows, err := conn.Query("select " + tt.expr)
-		if err != nil {
-			t.Fatalf("Query failed with unexpected error %s", err)
-		}
-		defer rows.Close()
-		ct, err := rows.ColumnTypes()
-		if err != nil {
-			t.Fatalf("Query failed with unexpected error %s", err)
-		}
-		if ct[0].DatabaseTypeName() != tt.typeName {
-			t.Errorf("Expected type %s but returned %s", tt.typeName, ct[0].DatabaseTypeName())
-		}
-		size, ok := ct[0].Length()
-		if ok != tt.hasSize {
-			t.Errorf("Expected has size %v but returned %v for %s", tt.hasSize, ok, tt.expr)
-		} else {
-			if ok && size != tt.size {
-				t.Errorf("Expected size %d but returned %d for %s", tt.size, size, tt.expr)
+		t.Run(tt.expr, func(t *testing.T) {
+			rows, err := conn.Query("select " + tt.expr)
+			if err != nil {
+				t.Fatalf("Query failed with unexpected error %s", err)
 			}
-		}
-
-		prec, scale, ok := ct[0].DecimalSize()
-		if ok != tt.hasPrecScale {
-			t.Errorf("Expected has prec/scale %v but returned %v for %s", tt.hasPrecScale, ok, tt.expr)
-		} else {
-			if ok && prec != tt.precision {
-				t.Errorf("Expected precision %d but returned %d for %s", tt.precision, prec, tt.expr)
+			// defer keyword is function scoped, so calling it inside a for loop within one function
+			// prevents the open connection from being reused in subsequent loop iterations
+			defer rows.Close()
+			ct, err := rows.ColumnTypes()
+			if err != nil {
+				t.Fatalf("Query failed with unexpected error %s", err)
 			}
-			if ok && scale != tt.scale {
-				t.Errorf("Expected scale %d but returned %d for %s", tt.scale, scale, tt.expr)
+			if ct[0].DatabaseTypeName() != tt.typeName {
+				t.Errorf("Expected type %s but returned %s", tt.typeName, ct[0].DatabaseTypeName())
+			}
+			size, ok := ct[0].Length()
+			if ok != tt.hasSize {
+				t.Errorf("Expected has size %v but returned %v for %s", tt.hasSize, ok, tt.expr)
+			} else {
+				if ok && size != tt.size {
+					t.Errorf("Expected size %d but returned %d for %s", tt.size, size, tt.expr)
+				}
 			}
-		}
 
-		if ct[0].ScanType() != tt.reflType {
-			t.Errorf("Expected ScanType %v but got %v for %s", tt.reflType, ct[0].ScanType(), tt.expr)
-		}
+			prec, scale, ok := ct[0].DecimalSize()
+			if ok != tt.hasPrecScale {
+				t.Errorf("Expected has prec/scale %v but returned %v for %s", tt.hasPrecScale, ok, tt.expr)
+			} else {
+				if ok && prec != tt.precision {
+					t.Errorf("Expected precision %d but returned %d for %s", tt.precision, prec, tt.expr)
+				}
+				if ok && scale != tt.scale {
+					t.Errorf("Expected scale %d but returned %d for %s", tt.scale, scale, tt.expr)
+				}
+			}
+
+			if ct[0].ScanType() != tt.reflType {
+				t.Errorf("Expected ScanType %v but got %v for %s", tt.reflType, ct[0].ScanType(), tt.expr)
+			}
+		})
 	}
 }
 
@@ -2008,7 +2053,7 @@ func TestQueryCancelHighLevel(t *testing.T) {
 	defer logger.StopLogging()
 
 	ctx, cancel := context.WithCancel(context.Background())
-	latency, _ := getLatency(t)
+	latency := getLatency(t)
 
 	go func() {
 		time.Sleep(latency + 500*time.Millisecond)
@@ -2029,35 +2074,38 @@ func TestQueryCancelHighLevel(t *testing.T) {
 }
 
 // returns the minimum time needed to connect to the server
-// Minimum return value is 1 ms, max is 2k ms
-func getLatency(t *testing.T) (latency time.Duration, increment time.Duration) {
-	latency = 1 * time.Millisecond
-	connstr := makeConnStr(t)
-	// .Host may not have a port
-	host := connstr.Hostname()
-	port := connstr.Port()
-	increment = 50 * time.Millisecond
-	if host == "." || host == "localhost" {
-		increment = 1 * time.Millisecond
-	}
-	if port == "" {
-		port = "1433"
-	}
-	for latency < 2000*time.Millisecond {
-		t.Logf("Dialing host %s with timeout %s", host, latency)
-		_, err := net.DialTimeout("tcp", host+":"+port, latency)
-		if err == nil {
-			return latency, increment
-		}
-		if oe, ok := err.(*net.OpError); ok {
-			if !oe.Timeout() {
-				t.Logf("Got non-timeout error %s", oe.Error())
-				return latency, increment
+func getLatency(t *testing.T) time.Duration {
+	t.Helper()
+	params, err := msdsn.Parse(makeConnStr(t).String())
+	if err != nil {
+		t.Fatalf("Invalid connection string: %s", err.Error())
+	}
+	c := &Connector{params: params}
+	now := time.Now()
+	if err := queryBrowser(context.Background(), context.Background(), c, nil, &params); err != nil {
+		t.Fatalf("queryBrowser failed: %s", err.Error())
+	}
+	// Dialing both tcp and np for a named-pipes only connection takes a long time
+	if len(params.Protocols) > 1 && testing.Short() {
+		t.Skip("short")
+	}
+	for _, protocol := range params.Protocols {
+		dialer := msdsn.ProtocolDialers[protocol]
+		sqlDialer, ok := dialer.(MssqlProtocolDialer)
+		if !ok {
+			conn, err := dialer.DialConnection(context.Background(), &params)
+			if err == nil {
+				conn.Close()
+			}
+		} else {
+			conn, err := sqlDialer.DialSqlConnection(context.Background(), c, &params)
+			if err == nil {
+				conn.Close()
 			}
-			latency += increment
 		}
+
 	}
-	return latency, increment
+	return time.Since(now)
 }
 
 func TestQueryTimeout(t *testing.T) {
@@ -2065,7 +2113,7 @@ func TestQueryTimeout(t *testing.T) {
 	defer conn.Close()
 	defer logger.StopLogging()
 	// 2 seconds should be enough time to complete the login
-	latency, _ := getLatency(t)
+	latency := getLatency(t)
 	ctx, cancel := context.WithTimeout(context.Background(), latency+2000*time.Millisecond)
 	defer cancel()
 	_, err := conn.ExecContext(ctx, "waitfor delay '00:00:20'")
@@ -2089,9 +2137,9 @@ func TestLoginTimeout(t *testing.T) {
 	defer logger.StopLogging()
 	// Try to timeout during the login using a small delta from raw connect time
 	// In environments where latency is really low this degenerates into TestQueryTimeout
-	latency, increment := getLatency(t)
+	latency := getLatency(t)
 
-	ctx, cancel := context.WithTimeout(context.Background(), latency+(2*increment))
+	ctx, cancel := context.WithTimeout(context.Background(), latency+200*time.Millisecond)
 	defer cancel()
 	_, err := conn.ExecContext(ctx, "waitfor delay '00:00:03'")
 	t.Logf("Got error type %v: %s ", reflect.TypeOf(err), err.Error())
@@ -2288,6 +2336,8 @@ func TestDisconnect1(t *testing.T) {
 	if testing.Short() {
 		t.Skip("short")
 	}
+
+	skipIfNamedPipesEnabled(t)
 	checkConnStr(t)
 	tl := testLogger{t: t}
 	defer tl.StopLogging()
@@ -2344,6 +2394,7 @@ func TestDisconnect1(t *testing.T) {
 // TestDisconnect2 tests a read error so the query is started
 // but results cannot be read.
 func TestDisconnect2(t *testing.T) {
+	skipIfNamedPipesEnabled(t)
 	if testing.Short() {
 		t.Skip("short")
 	}
@@ -2351,7 +2402,7 @@ func TestDisconnect2(t *testing.T) {
 	tl := testLogger{t: t}
 	defer tl.StopLogging()
 	SetLogger(&tl)
-	latency, _ := getLatency(t)
+	latency := getLatency(t)
 
 	// Revert to the normal dialer after the test is done.
 	normalCreateDialer := createDialer
@@ -2420,6 +2471,7 @@ func TestDisconnect3(t *testing.T) {
 	if testing.Short() {
 		t.Skip("short")
 	}
+	skipIfNamedPipesEnabled(t)
 	checkConnStr(t)
 	tl := testLogger{t: t}
 	defer tl.StopLogging()
@@ -2705,3 +2757,10 @@ func TestTypeSizesFromQuery(t *testing.T) {
 		t.Errorf("VARBINARY(30) reported as (variable, length): (%v, %d)", ok, l)
 	}
 }
+
+func skipIfNamedPipesEnabled(t *testing.T) {
+	t.Helper()
+	if len(msdsn.ProtocolParsers) > 1 {
+		t.Skip("Skipping test due to named pipes dialer")
+	}
+}
diff --git a/sharedmemory_test.go b/sharedmemory_test.go
new file mode 100644
index 0000000..cca4037
--- /dev/null
+++ b/sharedmemory_test.go
@@ -0,0 +1,36 @@
+//go:build sm
+// +build sm
+
+package mssql
+
+import (
+	"testing"
+
+	"github.com/microsoft/go-mssqldb/msdsn"
+	_ "github.com/microsoft/go-mssqldb/sharedmemory"
+)
+
+func TestSharedMemoryProtocolInstalled(t *testing.T) {
+	for _, p := range msdsn.ProtocolParsers {
+		if p.Protocol() == "lpc" {
+			return
+		}
+	}
+	t.Fatalf("ProtocolParsers is missing lpc %v", msdsn.ProtocolParsers)
+}
+
+func TestSharedMemoryConnection(t *testing.T) {
+	params := testConnParams(t)
+	protocol, ok := params.Parameters["protocol"]
+	if !ok || protocol != "lpc" {
+		t.Skip("Test is not running with named pipe protocol set")
+	}
+	conn, _ := open(t)
+	row := conn.QueryRow("SELECT net_transport FROM sys.dm_exec_connections WHERE session_id = @@SPID")
+	if err := row.Scan(&protocol); err != nil {
+		t.Fatalf("Unable to query connection protocol %s", err.Error())
+	}
+	if protocol != "Shared memory" {
+		t.Fatalf("Shared memory connection not made. Protocol: %s", protocol)
+	}
+}
diff --git a/tds_login_test.go b/tds_login_test.go
index e6d1beb..ab5e00c 100644
--- a/tds_login_test.go
+++ b/tds_login_test.go
@@ -119,7 +119,7 @@ func testLoginSequenceServer(result chan error, conn net.Conn, expectedPackets,
 }
 
 func TestLoginWithSQLServerAuth(t *testing.T) {
-	conn, err := NewConnector("sqlserver://test:secret@localhost:1433?Workstation ID=localhost&log=128")
+	conn, err := NewConnector("sqlserver://test:secret@localhost:1433?Workstation ID=localhost&log=128&protocol=tcp")
 	if err != nil {
 		t.Errorf("Unable to parse dummy DSN: %v", err)
 	}
@@ -170,7 +170,7 @@ func TestLoginWithSQLServerAuth(t *testing.T) {
 }
 
 func TestLoginWithSecurityTokenAuth(t *testing.T) {
-	config, err := msdsn.Parse("sqlserver://localhost:1433?Workstation ID=localhost&log=128")
+	config, err := msdsn.Parse("sqlserver://localhost:1433?Workstation ID=localhost&log=128&protocol=tcp")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -231,7 +231,7 @@ func TestLoginWithSecurityTokenAuth(t *testing.T) {
 }
 
 func TestLoginWithADALUsernamePasswordAuth(t *testing.T) {
-	config, err := msdsn.Parse("sqlserver://localhost:1433?Workstation ID=localhost&log=128")
+	config, err := msdsn.Parse("sqlserver://localhost:1433?Workstation ID=localhost&log=128&protocol=tcp")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -305,7 +305,7 @@ func TestLoginWithADALUsernamePasswordAuth(t *testing.T) {
 }
 
 func TestLoginWithADALManagedIdentityAuth(t *testing.T) {
-	config, err := msdsn.Parse("sqlserver://localhost:1433?Workstation ID=localhost&log=128")
+	config, err := msdsn.Parse("sqlserver://localhost:1433?Workstation ID=localhost&log=128&protocol=tcp")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/tds_test.go b/tds_test.go
index 0dfeeb2..dc90835 100644
--- a/tds_test.go
+++ b/tds_test.go
@@ -218,9 +218,9 @@ func testConnParams(t testing.TB) msdsn.Config {
 // TestConnParams returns a connection configuration based on environment variables or the contents of a text file
 // Set environment variable SQLSERVER_DSN to provide an entire connection string
 // Set environment variables HOST and DATABASE from which a minimal config will be created.
-//  If HOST and DATABASE are set, you can optionally set INSTANCE, SQLUSER, and SQLPASSWORD as well
+// If HOST and DATABASE are set, you can optionally set INSTANCE, SQLUSER, and SQLPASSWORD as well
 // If environment variables are not set, it will look in the working directory for a file named .connstr
-//   If the file exists it will use the first line of the file as the file as the DSN
+// If the file exists it will use the first line of the file as the file as the DSN
 func GetConnParams() (*msdsn.Config, error) {
 	dsn := os.Getenv("SQLSERVER_DSN")
 	const logFlags = 127
@@ -233,14 +233,22 @@ func GetConnParams() (*msdsn.Config, error) {
 		return &params, nil
 	}
 	if len(os.Getenv("HOST")) > 0 && len(os.Getenv("DATABASE")) > 0 {
-		return &msdsn.Config{
-			Host:     os.Getenv("HOST"),
-			Instance: os.Getenv("INSTANCE"),
-			Database: os.Getenv("DATABASE"),
-			User:     os.Getenv("SQLUSER"),
-			Password: os.Getenv("SQLPASSWORD"),
-			LogFlags: logFlags,
-		}, nil
+		c := &msdsn.Config{
+			Host:       os.Getenv("HOST"),
+			Instance:   os.Getenv("INSTANCE"),
+			Database:   os.Getenv("DATABASE"),
+			User:       os.Getenv("SQLUSER"),
+			Password:   os.Getenv("SQLPASSWORD"),
+			LogFlags:   logFlags,
+			Parameters: make(map[string]string),
+		}
+		if c.Instance == "" {
+			c.Instance = os.Getenv("SQLINSTANCE")
+		}
+		if os.Getenv("PROTOCOL") != "" {
+			c.Parameters["protocol"] = os.Getenv("PROTOCOL")
+		}
+		return c, nil
 	}
 	// try loading connection string from file
 	f, err := os.Open(".connstr")
@@ -576,14 +584,14 @@ func TestBadCredentials(t *testing.T) {
 	params := testConnParams(t)
 	params.Password = "padpwd"
 	params.User = "baduser"
-	testConnectionBad(t, params.URL().String())
+	_ = testConnectionBad(t, params.URL().String())
 }
 
 func TestBadHost(t *testing.T) {
 	params := testConnParams(t)
 	params.Host = "badhost"
 	params.Instance = ""
-	testConnectionBad(t, params.URL().String())
+	_ = testConnectionBad(t, params.URL().String())
 }
 
 func TestSqlBrowserNotUsedIfPortSpecified(t *testing.T) {
@@ -591,8 +599,11 @@ func TestSqlBrowserNotUsedIfPortSpecified(t *testing.T) {
 
 	// Connect to an instance on a host that doesn't exist (so connection will always expectedly fail)
 	params := testConnParams(t)
+	delete(params.Parameters, "protocol")
 	params.Host = "badhost"
 	params.Instance = "foobar"
+	protocols := params.Protocols
+	params.Protocols = []string{"tcp"}
 
 	// Specify no port, so error must indicate SQL Browser lookup failed
 	params.Port = 0 // No port spcified, sql browser should be used
@@ -600,11 +611,12 @@ func TestSqlBrowserNotUsedIfPortSpecified(t *testing.T) {
 	err := testConnectionBad(t, params.URL().String())
 
 	if !strings.Contains(err.Error(), errorSubstrStringToCheckFor) {
-		t.Fatal("Connection should have tried to use SQL Browser")
+		t.Fatalf("Connection should have tried to use SQL Browser. Error:%s", err.Error())
 	}
 
 	// Specify port, ensure error does not indicate SQL Browser lookup failed
 	params.Port = 1500 // Specify a port, sql browser should not be tried
+	params.Protocols = protocols
 	err = testConnectionBad(t, params.URL().String())
 
 	if strings.Contains(err.Error(), errorSubstrStringToCheckFor) {
@@ -769,7 +781,7 @@ func TestUcs22str(t *testing.T) {
 
 var sideeffect_varchar string
 
-//ucs22str benchmarks
+// ucs22str benchmarks
 func BenchmarkUcs22strAscii(b *testing.B) {
 	for n := 0; n < b.N; n++ {
 		s, _ := ucs22str(encoded123Bytes)
diff --git a/tvp_go19_db_test.go b/tvp_go19_db_test.go
index a862698..da80c19 100644
--- a/tvp_go19_db_test.go
+++ b/tvp_go19_db_test.go
@@ -1348,3 +1348,129 @@ func TestTVPUnsigned(t *testing.T) {
 		t.Errorf("third result set had wrong value expected: %s actual: %s", "test", result3)
 	}
 }
+
+func TestTVPIdentity(t *testing.T) {
+	type TvpIdentityExample struct {
+		ID      int `tvp:"@identity"`
+		Message string
+	}
+
+	const (
+		crateSchema = `create schema TestTVPSchemaIdentity;`
+
+		dropSchema = `drop schema TestTVPSchemaIdentity;`
+
+		createTVP = `
+		CREATE TYPE TestTVPSchemaIdentity.exempleTVP AS TABLE
+		(
+		    id int  identity(1,1) not null,
+			message	NVARCHAR(100)
+		)`
+
+		dropTVP = `DROP TYPE TestTVPSchemaIdentity.exempleTVP;`
+
+		procedureWithTVP = `	
+	CREATE PROCEDURE ExecIdentityTVP
+		@param1 TestTVPSchemaIdentity.exempleTVP READONLY
+	AS   
+	BEGIN
+		SET NOCOUNT ON; 
+		SELECT * FROM @param1;
+	END;
+	`
+
+		dropProcedure = `drop PROCEDURE ExecIdentityTVP`
+
+		execTvp = `exec ExecIdentityTVP @param1;`
+	)
+
+	checkConnStr(t)
+	tl := testLogger{t: t}
+	defer tl.StopLogging()
+	SetLogger(&tl)
+
+	p := makeConnStr(t).String()
+	conn, err := sql.Open("sqlserver", p)
+	if err != nil {
+		log.Fatal("Open connection failed:", err.Error())
+	}
+	defer conn.Close()
+
+	_, err = conn.Exec(crateSchema)
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+	defer conn.Exec(dropSchema)
+
+	_, err = conn.Exec(createTVP)
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+	defer conn.Exec(dropTVP)
+
+	_, err = conn.Exec(procedureWithTVP)
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+	defer conn.Exec(dropProcedure)
+
+	exempleData := []TvpIdentityExample{
+		{
+			Message: "Hello",
+		},
+		{
+			Message: "World",
+		},
+		{
+			Message: "TVP",
+		},
+	}
+
+	tvpType := TVP{
+		TypeName: "TestTVPSchemaIdentity.exempleTVP",
+		Value:    exempleData,
+	}
+
+	rows, err := conn.Query(execTvp,
+		sql.Named("param1", tvpType),
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer rows.Close()
+
+	tvpResult := make([]TvpIdentityExample, 0)
+	for rows.Next() {
+		tvpExemple := TvpIdentityExample{}
+		err = rows.Scan(&tvpExemple.ID, &tvpExemple.Message)
+		if err != nil {
+			t.Fatal(err)
+		}
+		tvpResult = append(tvpResult, tvpExemple)
+	}
+
+	expectData := []TvpIdentityExample{
+		{
+			ID:      1,
+			Message: "Hello",
+		},
+		{
+			ID:      2,
+			Message: "World",
+		},
+		{
+			ID:      3,
+			Message: "TVP",
+		},
+	}
+
+	if len(expectData) != len(tvpResult) {
+		t.Fatal("TestTVPIdentity have to be len")
+	}
+	if !reflect.DeepEqual(expectData, tvpResult) {
+		t.Fatal("TestTVPIdentity have to be same")
+	}
+}