Repository: pion/turn (go)

Issue:
Panic in conn.bind
### Your environment.
  - Version: 1.3.4

### What happened?
Can't exactly reproduce this.

```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x171c272]

goroutine 249 [running]:
github.com/pion/turn/internal/client.(*UDPConn).bind(0xc0007b6600, 0xc0006970e0, 0xc000a30200, 0x0)
	/go/pkg/mod/github.com/pion/turn@v1.3.4/internal/client/conn.go:525 +0x452
github.com/pion/turn/internal/client.(*UDPConn).WriteTo.func2.1(0xc0007b6600, 0xc0006970e0)
	/go/pkg/mod/github.com/pion/turn@v1.3.4/internal/client/conn.go:220 +0x39
created by github.com/pion/turn/internal/client.(*UDPConn).WriteTo.func2
	/go/pkg/mod/github.com/pion/turn@v1.3.4/internal/client/conn.go:219 +0xad
```


Pull request description:
Fix panic when ChannelBind transaction failed
Add a reproducible test
Resolves #88


Patch:
diff --git a/internal/client/conn.go b/internal/client/conn.go
index 8e5e7af..232d338 100644
--- a/internal/client/conn.go
+++ b/internal/client/conn.go
@@ -515,6 +515,7 @@ func (c *UDPConn) bind(b *binding) error {
 	trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), false)
 	if err != nil {
 		c.bindingMgr.deleteByAddr(b.addr)
+		return err
 	}
 
 	res := trRes.Msg


Test patch:
diff --git a/internal/client/conn_test.go b/internal/client/conn_test.go
new file mode 100644
index 0000000..fcff102
--- /dev/null
+++ b/internal/client/conn_test.go
@@ -0,0 +1,77 @@
+package client
+
+import (
+	"fmt"
+	"net"
+	"testing"
+
+	"github.com/pion/stun"
+	"github.com/stretchr/testify/assert"
+)
+
+type dummyUDPConnObserver struct {
+	turnServerAddr      net.Addr
+	username            stun.Username
+	realm               stun.Realm
+	_writeTo            func(data []byte, to net.Addr) (int, error)
+	_performTransaction func(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error)
+	_onDeallocated      func(relayedAddr net.Addr)
+}
+
+func (obs *dummyUDPConnObserver) TURNServerAddr() net.Addr {
+	return obs.turnServerAddr
+}
+
+func (obs *dummyUDPConnObserver) Username() stun.Username {
+	return obs.username
+}
+
+func (obs *dummyUDPConnObserver) Realm() stun.Realm {
+	return obs.realm
+}
+
+func (obs *dummyUDPConnObserver) WriteTo(data []byte, to net.Addr) (int, error) {
+	if obs._writeTo != nil {
+		return obs._writeTo(data, to)
+	}
+	return 0, nil
+}
+
+func (obs *dummyUDPConnObserver) PerformTransaction(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) {
+	if obs._performTransaction != nil {
+		return obs._performTransaction(msg, to, dontWait)
+	}
+	return TransactionResult{}, nil
+}
+
+func (obs *dummyUDPConnObserver) OnDeallocated(relayedAddr net.Addr) {
+	if obs._onDeallocated != nil {
+		obs._onDeallocated(relayedAddr)
+	}
+}
+
+func TestUDPConn(t *testing.T) {
+	t.Run("bind()", func(t *testing.T) {
+		obs := &dummyUDPConnObserver{
+			_performTransaction: func(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) {
+				return TransactionResult{}, fmt.Errorf("fake error")
+			},
+		}
+
+		bm := newBindingManager()
+		b := bm.create(&net.UDPAddr{
+			IP:   net.ParseIP("127.0.0.1"),
+			Port: 1234,
+		})
+
+		conn := UDPConn{
+			obs:        obs,
+			bindingMgr: bm,
+		}
+
+		err := conn.bind(b)
+		assert.Error(t, err, "should fail")
+		assert.Equal(t, 0, len(bm.chanMap), "should be 0")
+		assert.Equal(t, 0, len(bm.addrMap), "should be 0")
+	})
+}