Repository: microsoft/go-mssqldb
Pull request #74 (2022-12-11 06:04:43)

Implement shared memory dialer
Shared memory connections can be made for "." or "localhost" or for a host name that matches the value returned by `os.Hostname()`. Just import the `sharedmemory` package to enable it.

To put shared memory as the primary connection option, set it as the first entry in `msdsn.ProtocolParsers`. To force only a shared memory connection, use the `lpc:` prefix on the host name in the connection string or set the "protocol" parameter to "lpc" in an `msdsn.Config`.
 



Patch:
diff --git a/README.md b/README.md
index 7d01b0c..aa06109 100644
--- a/README.md
+++ b/README.md
@@ -361,7 +361,8 @@ db.QueryContext(ctx, `select * from t2 where user_name = @p1;`, mssql.VarChar(na
 * Supports query notifications
 * Supports Kerberos Authentication
 * Pluggable Dialer implementations through `msdsn.ProtocolParsers` and `msdsn.ProtocolDialers`
-* A `namedpipe` package to support connections using named pipes on Windows
+* 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 c57f868..dafa972 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -46,6 +46,7 @@ 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
@@ -53,6 +54,14 @@ environment:
       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%
diff --git a/examples/azuread-service-principal-authtoken/service_principal_authtoken.go b/examples/azuread-service-principal-authtoken/service_principal_authtoken.go
index 8b84637..9e9e942 100644
--- a/examples/azuread-service-principal-authtoken/service_principal_authtoken.go
+++ b/examples/azuread-service-principal-authtoken/service_principal_authtoken.go
@@ -1,3 +1,6 @@
+//go:build go1.18
+// +build go1.18
+
 package main
 
 import (
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 c25f090..4a15866 100644
--- a/msdsn/conn_str.go
+++ b/msdsn/conn_str.go
@@ -276,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 {
@@ -649,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 {
@@ -669,10 +656,6 @@ 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
diff --git a/msdsn/extensions.go b/msdsn/extensions.go
index 221b92e..527c012 100644
--- a/msdsn/extensions.go
+++ b/msdsn/extensions.go
@@ -11,8 +11,8 @@ type BrowserData map[string]map[string]string
 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
-	DialConnection(ctx context.Context, p Config) (conn net.Conn, err 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
 }
diff --git a/namedpipe/namedpipe_windows.go b/namedpipe/namedpipe_windows.go
index d69bed1..ae3bd6a 100644
--- a/namedpipe/namedpipe_windows.go
+++ b/namedpipe/namedpipe_windows.go
@@ -6,10 +6,9 @@ import (
 	"net"
 	"reflect"
 	"strings"
-	"time"
 
+	"github.com/microsoft/go-mssqldb/internal/np"
 	"github.com/microsoft/go-mssqldb/msdsn"
-	"gopkg.in/natefinch/npipe.v2"
 )
 
 type namedPipeData struct {
@@ -56,16 +55,16 @@ func (n namedPipeDialer) ParseBrowserData(data msdsn.BrowserData, p *msdsn.Confi
 	return nil
 }
 
-func (n namedPipeDialer) DialConnection(ctx context.Context, p msdsn.Config) (conn net.Conn, err error) {
+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:
-		dl, ok := ctx.Deadline()
-		if ok {
-			duration := dl.Sub(time.Now())
-			return npipe.DialTimeout(d.PipeName, duration)
+		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 npipe.Dial(d.PipeName)
+		return
 	}
 	return nil, fmt.Errorf("Unexpected protocol data specified for connection: %v", reflect.TypeOf(data))
 }
diff --git a/namedpipe/stub.go b/namedpipe/stub.go
index acf5ed2..deb2fd0 100644
--- a/namedpipe/stub.go
+++ b/namedpipe/stub.go
@@ -23,7 +23,7 @@ func (n namedPipeDialer) ParseBrowserData(data msdsn.BrowserData, p *msdsn.Confi
 	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) {
+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")
 }
diff --git a/protocol.go b/protocol.go
index a3d3513..cf8f0be 100644
--- a/protocol.go
+++ b/protocol.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"net"
+	"os"
 	"strconv"
 	"strings"
 
@@ -12,7 +13,7 @@ import (
 
 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)
+	DialSqlConnection(ctx context.Context, c *Connector, p *msdsn.Config) (conn net.Conn, err error)
 }
 
 type tcpDialer struct{}
@@ -35,14 +36,14 @@ func (t tcpDialer) ParseBrowserData(data msdsn.BrowserData, p *msdsn.Config) err
 	return nil
 }
 
-func (t tcpDialer) DialConnection(ctx context.Context, p msdsn.Config) (conn net.Conn, err error) {
+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) {
+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 {
@@ -54,7 +55,7 @@ func (t tcpDialer) DialSqlConnection(ctx context.Context, c *Connector, p msdsn.
 		ips = []net.IP{ip}
 	}
 	if len(ips) == 1 {
-		d := c.getDialer(&p)
+		d := c.getDialer(p)
 		addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(resolveServerPort(p.Port))))
 		conn, err = d.DialContext(ctx, "tcp", addr)
 
@@ -65,7 +66,7 @@ func (t tcpDialer) DialSqlConnection(ctx context.Context, c *Connector, p msdsn.
 		portStr := strconv.Itoa(int(resolveServerPort(p.Port)))
 		for _, ip := range ips {
 			go func(ip net.IP) {
-				d := c.getDialer(&p)
+				d := c.getDialer(p)
 				addr := net.JoinHostPort(ip.String(), portStr)
 				conn, err := d.DialContext(ctx, "tcp", addr)
 				if err == nil {
@@ -102,6 +103,9 @@ func (t tcpDialer) DialSqlConnection(ctx context.Context, c *Connector, p msdsn.
 		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
 }
 
@@ -109,6 +113,14 @@ 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
@@ -116,3 +128,11 @@ func resolveServerPort(port uint64) uint64 {
 
 	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 abbe063..c231be1 100644
--- a/tds.go
+++ b/tds.go
@@ -837,11 +837,11 @@ func sendAttention(buf *tdsBuffer) error {
 }
 
 // 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) {
+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 {
+		if logger != nil && uint64(p.LogFlags)&logDebug != 0 {
 			logger.Log(ctx, msdsn.LogDebug, "Dialing with protocol "+protocol)
 		}
 		if !ok {
@@ -849,11 +849,11 @@ func dialConnection(ctx context.Context, c *Connector, p msdsn.Config, logger Co
 		} else {
 			conn, err = sqlDialer.DialSqlConnection(ctx, c, p)
 		}
-		if err != nil && logger != nil {
+		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 {
+			if logger != nil && uint64(p.LogFlags)&logDebug != 0 {
 				logger.Log(ctx, msdsn.LogDebug, "Returning connection from protocol "+protocol)
 			}
 			return
@@ -1009,7 +1009,7 @@ func queryBrowser(ctx context.Context, dialCtx context.Context, c *Connector, lo
 			pErr := pd.ParseBrowserData(instances, p)
 			if pErr != nil {
 				pErrors[protocol] = pErr
-				if logger != nil {
+				if logger != nil && uint64(p.LogFlags)&logErrors != 0 {
 					logger.Log(ctx, msdsn.Log(logErrors), "Removing protocol "+protocol+" from dialers. Error:"+pErr.Error())
 				}
 			}
@@ -1077,7 +1077,7 @@ func connect(ctx context.Context, c *Connector, logger ContextLogger, p msdsn.Co
 	}
 
 initiate_connection:
-	conn, err := dialConnection(dialCtx, c, p, logger)
+	conn, err := dialConnection(dialCtx, c, &p, logger)
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/namedpipe_test.go b/namedpipe_test.go
index a8fcfe5..f12fa3e 100644
--- a/namedpipe_test.go
+++ b/namedpipe_test.go
@@ -4,13 +4,33 @@
 package mssql
 
 import (
+	"testing"
+
 	"github.com/microsoft/go-mssqldb/msdsn"
 	_ "github.com/microsoft/go-mssqldb/namedpipe"
-	"testing"
 )
 
 func TestNamedPipeProtocolInstalled(t *testing.T) {
-	if len(msdsn.ProtocolParsers) != 2 {
-		t.Fatal("np protocol not registered")
+	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 c1b4952..eea01b2 100644
--- a/net_go116_test.go
+++ b/net_go116_test.go
@@ -70,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, nil)
+	toconn, err := dialConnection(ctx, connector, &connector.params, nil)
 	if err != nil {
 		t.Error(err)
 	}
@@ -130,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, nil)
+	toconn, err := dialConnection(ctx, connector, &connector.params, nil)
 	if err != nil {
 		t.Error(err)
 	}
diff --git a/queries_test.go b/queries_test.go
index e4f8d7c..13f045c 100644
--- a/queries_test.go
+++ b/queries_test.go
@@ -2093,12 +2093,12 @@ func getLatency(t *testing.T) time.Duration {
 		dialer := msdsn.ProtocolDialers[protocol]
 		sqlDialer, ok := dialer.(MssqlProtocolDialer)
 		if !ok {
-			conn, err := dialer.DialConnection(context.Background(), params)
+			conn, err := dialer.DialConnection(context.Background(), &params)
 			if err == nil {
 				conn.Close()
 			}
 		} else {
-			conn, err := sqlDialer.DialSqlConnection(context.Background(), c, params)
+			conn, err := sqlDialer.DialSqlConnection(context.Background(), c, &params)
 			if err == nil {
 				conn.Close()
 			}
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_test.go b/tds_test.go
index e48ffbf..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
@@ -366,7 +366,6 @@ func TestConnectViaIp(t *testing.T) {
 	if params.Encryption == msdsn.EncryptionRequired {
 		t.Skip("Unable to test connection to IP for servers that expect encryption")
 	}
-	skipIfNamedPipesEnabled(t)
 
 	if params.Host == "." {
 		params.Host = "127.0.0.1"
@@ -600,6 +599,7 @@ 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
@@ -611,7 +611,7 @@ 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
@@ -781,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)