Repository: miekg/dns (go)
Issue: not support unix socks
I have a piece of test code：
```go
package main

import (
	"fmt"
	"net"
	"strings"
)

func main() {
	var tcp net.Conn = &net.TCPConn{}
	_, ok := tcp.(net.PacketConn)
	fmt.Println("tcp", ok)

	var unix net.Conn = &net.UnixConn{}
	_, ok = unix.(net.PacketConn)
	fmt.Println("unix", ok)

	var udp net.Conn = &net.UDPConn{}
	_, ok = udp.(net.PacketConn)
	fmt.Println("udp", ok)
}
```
result：
tcp false
unix true
udp true

I want TCP and UNIX results to be consistent，But the end result is that UDP and UNIX are consistent，The problem code is as follows
```go
// ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil).
// Returns message as a byte slice to be parsed with Msg.Unpack later on.
// Note that error handling on the message body is not possible as only the header is parsed.
func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
	var (
		p   []byte
		n   int
		err error
	)

// FIXME   The problem code is as follows
	if _, ok := co.Conn.(net.PacketConn); ok {
		if co.UDPSize > MinMsgSize {
			p = make([]byte, co.UDPSize)
		} else {
			p = make([]byte, MinMsgSize)
		}
		n, err = co.Read(p)
	} else {
		var length uint16
		if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil {
			return nil, err
		}

		p = make([]byte, length)
		n, err = io.ReadFull(co.Conn, p)
	}

	if err != nil {
		return nil, err
	} else if n < headerSize {
		return nil, ErrShortRead
	}

	p = p[:n]
	if hdr != nil {
		dh, _, err := unpackMsgHdr(p, 0)
		if err != nil {
			return nil, err
		}
		*hdr = dh
	}
	return p, err
}
```
```go
// Write implements the net.Conn Write method.
func (co *Conn) Write(p []byte) (int, error) {
	if len(p) > MaxMsgSize {
		return 0, &Error{err: "message too large"}
	}

//   FIXME  The problem code is as follows
	if _, ok := co.Conn.(net.PacketConn); ok {
		return co.Conn.Write(p)
	}

	msg := make([]byte, 2+len(p))
	binary.BigEndian.PutUint16(msg, uint16(len(p)))
	copy(msg[2:], p)
	return co.Conn.Write(msg)
}
```
At present, I use the following methods in my project
```go
conn, err = net.DialTimeout(c.Net, addr, c.Timeout)
if _, ok := conn.(*net.UnixConn); ok {
	conn = &mc{c: conn}   //  Here are my changes
}
return &dns.Conn{
	UDPSize: c.UDPSize,
	Conn:    conn,
}, nil

type mc struct{ c net.Conn }
func (c *mc) Read(b []byte) (n int, err error)   { return c.c.Read(b) }
func (c *mc) Write(b []byte) (n int, err error)  { return c.c.Write(b) }
func (c *mc) Close() error                       { return c.c.Close() }
func (c *mc) LocalAddr() net.Addr                { return c.c.LocalAddr() }
func (c *mc) RemoteAddr() net.Addr               { return c.c.RemoteAddr() }
func (c *mc) SetDeadline(t time.Time) error      { return c.c.SetDeadline(t) }
func (c *mc) SetReadDeadline(t time.Time) error  { return c.c.SetReadDeadline(t) }
func (c *mc) SetWriteDeadline(t time.Time) error { return c.c.SetWriteDeadline(t) }
```
Pull request description: Only treat a *net.UnixConn of unixgram as a packet conn
Go supports three different types of unix connections `SOCK_STREAM` (unix), `SOCK_DGRAM` (unixgram) and `SOCK_SEQPACKET` (unixpacket) all of which are `*net.UnixConn`s. That means `*net.UnixConn` always implements `net.PacketConn` regardless of the socket type, though certain methods will return errors.

`SOCK_DGRAM` is the only one we want to treat as a packet connection and use the UDP framing for (IIUC). To do this we special case `*net.UnixConn`s and only treat `SOCK_DGRAM` as a `net.PacketConn`.

This will make it possible to properly use the DNS client over unix `SOCK_STREAM` connections (the most common type).

Fixes #1321

Patch:
diff --git a/client.go b/client.go
index 6bae3a1c..99bd1896 100644
--- a/client.go
+++ b/client.go
@@ -18,6 +18,18 @@ const (
 	tcpIdleTimeout time.Duration = 8 * time.Second
 )
 
+func isPacketConn(c net.Conn) bool {
+	if _, ok := c.(net.PacketConn); !ok {
+		return false
+	}
+
+	if ua, ok := c.LocalAddr().(*net.UnixAddr); ok {
+		return ua.Net == "unixgram"
+	}
+
+	return true
+}
+
 // A Conn represents a connection to a DNS server.
 type Conn struct {
 	net.Conn                         // a net.Conn holding the connection
@@ -221,7 +233,7 @@ func (c *Client) exchangeContext(ctx context.Context, m *Msg, co *Conn) (r *Msg,
 		return nil, 0, err
 	}
 
-	if _, ok := co.Conn.(net.PacketConn); ok {
+	if isPacketConn(co.Conn) {
 		for {
 			r, err = co.ReadMsg()
 			// Ignore replies with mismatched IDs because they might be
@@ -282,7 +294,7 @@ func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
 		err error
 	)
 
-	if _, ok := co.Conn.(net.PacketConn); ok {
+	if isPacketConn(co.Conn) {
 		if co.UDPSize > MinMsgSize {
 			p = make([]byte, co.UDPSize)
 		} else {
@@ -322,7 +334,7 @@ func (co *Conn) Read(p []byte) (n int, err error) {
 		return 0, ErrConnEmpty
 	}
 
-	if _, ok := co.Conn.(net.PacketConn); ok {
+	if isPacketConn(co.Conn) {
 		// UDP connection
 		return co.Conn.Read(p)
 	}
@@ -371,7 +383,7 @@ func (co *Conn) Write(p []byte) (int, error) {
 		return 0, &Error{err: "message too large"}
 	}
 
-	if _, ok := co.Conn.(net.PacketConn); ok {
+	if isPacketConn(co.Conn) {
 		return co.Conn.Write(p)
 	}
 


Test patch:
diff --git a/client_test.go b/client_test.go
index 4bff6eb4..71c61770 100644
--- a/client_test.go
+++ b/client_test.go
@@ -6,12 +6,87 @@ import (
 	"errors"
 	"fmt"
 	"net"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"testing"
 	"time"
 )
 
+func TestIsPacketConn(t *testing.T) {
+	// UDP
+	s, addrstr, _, err := RunLocalUDPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+	c, err := net.Dial("udp", addrstr)
+	if err != nil {
+		t.Fatalf("failed to dial: %v", err)
+	}
+	defer c.Close()
+	if !isPacketConn(c) {
+		t.Error("UDP connection should be a packet conn")
+	}
+	if !isPacketConn(struct{ *net.UDPConn }{c.(*net.UDPConn)}) {
+		t.Error("UDP connection (wrapped type) should be a packet conn")
+	}
+
+	// TCP
+	s, addrstr, _, err = RunLocalTCPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+	c, err = net.Dial("tcp", addrstr)
+	if err != nil {
+		t.Fatalf("failed to dial: %v", err)
+	}
+	defer c.Close()
+	if isPacketConn(c) {
+		t.Error("TCP connection should not be a packet conn")
+	}
+	if isPacketConn(struct{ *net.TCPConn }{c.(*net.TCPConn)}) {
+		t.Error("TCP connection (wrapped type) should not be a packet conn")
+	}
+
+	// Unix datagram
+	s, addrstr, _, err = RunLocalUnixGramServer(filepath.Join(t.TempDir(), "unixgram.sock"))
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+	c, err = net.Dial("unixgram", addrstr)
+	if err != nil {
+		t.Fatalf("failed to dial: %v", err)
+	}
+	defer c.Close()
+	if !isPacketConn(c) {
+		t.Error("Unix datagram connection should be a packet conn")
+	}
+	if !isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
+		t.Error("Unix datagram connection (wrapped type) should be a packet conn")
+	}
+
+	// Unix stream
+	s, addrstr, _, err = RunLocalUnixServer(filepath.Join(t.TempDir(), "unixstream.sock"))
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+	c, err = net.Dial("unix", addrstr)
+	if err != nil {
+		t.Fatalf("failed to dial: %v", err)
+	}
+	defer c.Close()
+	if isPacketConn(c) {
+		t.Error("Unix stream connection should not be a packet conn")
+	}
+	if isPacketConn(struct{ *net.UnixConn }{c.(*net.UnixConn)}) {
+		t.Error("Unix stream connection (wrapped type) should not be a packet conn")
+	}
+}
+
 func TestDialUDP(t *testing.T) {
 	HandleFunc("miek.nl.", HelloServer)
 	defer HandleRemove("miek.nl.")
diff --git a/server_test.go b/server_test.go
index a5207aa5..85da176d 100644
--- a/server_test.go
+++ b/server_test.go
@@ -67,12 +67,14 @@ func AnotherHelloServer(w ResponseWriter, req *Msg) {
 	w.WriteMsg(m)
 }
 
-func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
-	pc, err := net.ListenPacket("udp", laddr)
-	if err != nil {
-		return nil, "", nil, err
+func RunLocalServer(pc net.PacketConn, l net.Listener, opts ...func(*Server)) (*Server, string, chan error, error) {
+	server := &Server{
+		PacketConn: pc,
+		Listener:   l,
+
+		ReadTimeout:  time.Hour,
+		WriteTimeout: time.Hour,
 	}
-	server := &Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
 
 	waitLock := sync.Mutex{}
 	waitLock.Lock()
@@ -82,6 +84,18 @@ func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
 		opt(server)
 	}
 
+	var (
+		addr   string
+		closer io.Closer
+	)
+	if l != nil {
+		addr = l.Addr().String()
+		closer = l
+	} else {
+		addr = pc.LocalAddr().String()
+		closer = pc
+	}
+
 	// fin must be buffered so the goroutine below won't block
 	// forever if fin is never read from. This always happens
 	// if the channel is discarded and can happen in TestShutdownUDP.
@@ -89,11 +103,20 @@ func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
 
 	go func() {
 		fin <- server.ActivateAndServe()
-		pc.Close()
+		closer.Close()
 	}()
 
 	waitLock.Lock()
-	return server, pc.LocalAddr().String(), fin, nil
+	return server, addr, fin, nil
+}
+
+func RunLocalUDPServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
+	pc, err := net.ListenPacket("udp", laddr)
+	if err != nil {
+		return nil, "", nil, err
+	}
+
+	return RunLocalServer(pc, nil, opts...)
 }
 
 func RunLocalPacketConnServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
@@ -109,26 +132,7 @@ func RunLocalTCPServer(laddr string, opts ...func(*Server)) (*Server, string, ch
 		return nil, "", nil, err
 	}
 
-	server := &Server{Listener: l, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
-
-	waitLock := sync.Mutex{}
-	waitLock.Lock()
-	server.NotifyStartedFunc = waitLock.Unlock
-
-	for _, opt := range opts {
-		opt(server)
-	}
-
-	// See the comment in RunLocalUDPServer as to why fin must be buffered.
-	fin := make(chan error, 1)
-
-	go func() {
-		fin <- server.ActivateAndServe()
-		l.Close()
-	}()
-
-	waitLock.Lock()
-	return server, l.Addr().String(), fin, nil
+	return RunLocalServer(nil, l, opts...)
 }
 
 func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, chan error, error) {
@@ -137,6 +141,24 @@ func RunLocalTLSServer(laddr string, config *tls.Config) (*Server, string, chan
 	})
 }
 
+func RunLocalUnixServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
+	l, err := net.Listen("unix", laddr)
+	if err != nil {
+		return nil, "", nil, err
+	}
+
+	return RunLocalServer(nil, l, opts...)
+}
+
+func RunLocalUnixGramServer(laddr string, opts ...func(*Server)) (*Server, string, chan error, error) {
+	pc, err := net.ListenPacket("unixgram", laddr)
+	if err != nil {
+		return nil, "", nil, err
+	}
+
+	return RunLocalServer(pc, nil, opts...)
+}
+
 func TestServing(t *testing.T) {
 	for _, tc := range []struct {
 		name      string