Repository: pion/turn
Pull request #67 (2019-07-03 09:21:50)

Bug fix for invalid channel address
I did:
1. fix the bug about invalid address for `GetChannelByAddr`  in `(a *Allocation) packetHandler(m *Manager)` .
2.  rename ChannelBind's `ID` to `Number` and `Allocation`'s method `GetChannelByID` to `GetChannelByNumber`, the ` number` is more intuitive than id.
3. improve `Allocation`'s test coverage.


codecov-io: # [Codecov](https://codecov.io/gh/pion/turn/pull/67?src=pr&el=h1) Report
> Merging [#67](https://codecov.io/gh/pion/turn/pull/67?src=pr&el=desc) into [master](https://codecov.io/gh/pion/turn/commit/042543609d4fe15ce3c0a0763d606d41f1971269?src=pr&el=desc) will **increase** coverage by `4.07%`.
> The diff coverage is `65%`.

[![Impacted file tree graph](https://codecov.io/gh/pion/turn/pull/67/graphs/tree.svg?width=650&token=1UomjQ7lwC&height=150&src=pr)](https://codecov.io/gh/pion/turn/pull/67?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #67      +/-   ##
==========================================
+ Coverage   35.75%   39.82%   +4.07%     
==========================================
  Files          14       14              
  Lines         786      786              
==========================================
+ Hits          281      313      +32     
+ Misses        469      436      -33     
- Partials       36       37       +1
```


| [Impacted Files](https://codecov.io/gh/pion/turn/pull/67?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [turn.go](https://codecov.io/gh/pion/turn/pull/67/diff?src=pr&el=tree#diff-dHVybi5nbw==) | `2.71% <0%> (ø)` | :arrow_up: |
| [internal/allocation/channel\_bind.go](https://codecov.io/gh/pion/turn/pull/67/diff?src=pr&el=tree#diff-aW50ZXJuYWwvYWxsb2NhdGlvbi9jaGFubmVsX2JpbmQuZ28=) | `66.66% <57.14%> (+41.66%)` | :arrow_up: |
| [internal/allocation/allocation.go](https://codecov.io/gh/pion/turn/pull/67/diff?src=pr&el=tree#diff-aW50ZXJuYWwvYWxsb2NhdGlvbi9hbGxvY2F0aW9uLmdv) | `83.33% <81.81%> (+19.29%)` | :arrow_up: |
| [internal/allocation/permission.go](https://codecov.io/gh/pion/turn/pull/67/diff?src=pr&el=tree#diff-aW50ZXJuYWwvYWxsb2NhdGlvbi9wZXJtaXNzaW9uLmdv) | `54.54% <0%> (+9.09%)` | :arrow_up: |
| [internal/ipnet/util.go](https://codecov.io/gh/pion/turn/pull/67/diff?src=pr&el=tree#diff-aW50ZXJuYWwvaXBuZXQvdXRpbC5nbw==) | `53.84% <0%> (+30.76%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/turn/pull/67?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/turn/pull/67?src=pr&el=footer). Last update [0425436...dea4636](https://codecov.io/gh/pion/turn/pull/67?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 7093f28..a3a5b9c 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,7 @@ Check out the [CONTRIBUTING.md](CONTRIBUTING.md) to join the group of amazing pe
 * [winds2016](https://github.com/winds2016) - *Windows platform testing*
 * [songjiayang](https://github.com/songjiayang) - *SongJiaYang*
 * [Yutaka Takeda](https://github.com/enobufs) - *vnet*
+* [namreg](https://github.com/namreg) - *Igor German*
 
 ### License
 MIT License - see [LICENSE.md](LICENSE.md) for full text
diff --git a/client.go b/client.go
index 97c52bf..e30db2d 100644
--- a/client.go
+++ b/client.go
@@ -17,14 +17,18 @@ type ClientConfig struct {
 	ListeningAddress string
 	LoggerFactory    logging.LoggerFactory
 	Net              *vnet.Net
+	Software         *stun.Software
+	Sender           Sender
 }
 
 // Client is a STUN server client
 type Client struct {
-	conn net.PacketConn
-	mux  sync.Mutex
-	net  *vnet.Net
-	log  logging.LeveledLogger
+	conn     net.PacketConn
+	mux      sync.Mutex
+	net      *vnet.Net
+	log      logging.LeveledLogger
+	software *stun.Software
+	sender   Sender
 }
 
 // NewClient returns a new Client instance. listeningAddress is the address and port to listen on, default "0.0.0.0:0"
@@ -38,9 +42,15 @@ func NewClient(config *ClientConfig) (*Client, error) {
 		log.Warn("vnet is enabled")
 	}
 
+	if config.Sender == nil {
+		config.Sender = defaultBuildAndSend
+	}
+
 	c := &Client{
-		net: config.Net,
-		log: log,
+		net:      config.Net,
+		log:      log,
+		software: config.Software,
+		sender:   config.Sender,
 	}
 
 	var err error
@@ -58,7 +68,12 @@ func (c *Client) SendSTUNRequest(serverIP net.IP, serverPort int) (net.Addr, err
 	defer c.mux.Unlock()
 
 	c.log.Debug("sending STUN request")
-	if err := sendStunRequest(c.conn, serverIP, serverPort); err != nil {
+
+	attrs := []stun.Setter{stun.TransactionID, stun.BindingRequest}
+	if c.software != nil {
+		attrs = append(attrs, *c.software)
+	}
+	if err := c.sender(c.conn, &net.UDPAddr{IP: serverIP, Port: serverPort}, attrs...); err != nil {
 		return nil, err
 	}
 
@@ -86,12 +101,3 @@ func (c *Client) SendSTUNRequest(serverIP net.IP, serverPort int) (net.Addr, err
 		Port: reflAddr.Port,
 	}, nil
 }
-
-func sendStunRequest(conn net.PacketConn, serverIP net.IP, serverPort int) error {
-	msg, err := stun.Build(stun.TransactionID, stun.BindingRequest)
-	if err != nil {
-		return err
-	}
-	_, err = conn.WriteTo(msg.Raw, &net.UDPAddr{IP: serverIP, Port: serverPort})
-	return err
-}
diff --git a/cmd/client/main.go b/cmd/client/main.go
index be1ea00..15d75ad 100644
--- a/cmd/client/main.go
+++ b/cmd/client/main.go
@@ -7,12 +7,14 @@ import (
 	"net"
 
 	"github.com/pion/logging"
+	"github.com/pion/stun"
 	"github.com/pion/turn"
 )
 
 func main() {
 	host := flag.String("host", "74.125.143.127", "IP of TURN Server. Default is the IP of stun1.l.google.com.")
 	port := flag.Int("port", 19302, "Port of TURN server.")
+	software := flag.String("software", "", "The STUN SOFTWARE attribute. Useful for debugging purpose.")
 	flag.Parse()
 
 	ip := net.ParseIP(*host)
@@ -20,10 +22,17 @@ func main() {
 		panic(errors.New("failed to parse host IP"))
 	}
 
-	c, err := turn.NewClient(&turn.ClientConfig{
+	cfg := &turn.ClientConfig{
 		ListeningAddress: "0.0.0.0:0",
 		LoggerFactory:    logging.NewDefaultLoggerFactory(),
-	})
+	}
+
+	if *software != "" {
+		attr := stun.NewSoftware(*software)
+		cfg.Software = &attr
+	}
+
+	c, err := turn.NewClient(cfg)
 	if err != nil {
 		panic(err)
 	}
diff --git a/cmd/simple-turn/main.go b/cmd/simple-turn/main.go
index 4d3d6c5..43c0cd4 100644
--- a/cmd/simple-turn/main.go
+++ b/cmd/simple-turn/main.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	"github.com/pion/logging"
+	"github.com/pion/stun"
 	"github.com/pion/turn"
 )
 
@@ -60,12 +61,19 @@ func main() {
 		}
 	}
 
+	var software *stun.Software
+	if sw := os.Getenv("SOFTWARE"); sw != "" {
+		attr := stun.NewSoftware(sw)
+		software = &attr
+	}
+
 	s := turn.NewServer(&turn.ServerConfig{
 		Realm:              realm,
 		AuthHandler:        createAuthHandler(usersMap),
 		ChannelBindTimeout: channelBindTimeout,
 		ListeningPort:      udpPort,
 		LoggerFactory:      logging.NewDefaultLoggerFactory(),
+		Software:           software,
 	})
 
 	err = s.Start()
diff --git a/internal/allocation/allocation.go b/internal/allocation/allocation.go
index 772cced..f78bbd3 100644
--- a/internal/allocation/allocation.go
+++ b/internal/allocation/allocation.go
@@ -92,15 +92,15 @@ func (a *Allocation) RemovePermission(addr net.Addr) {
 // permissions needed for this ChannelBind
 func (a *Allocation) AddChannelBind(c *ChannelBind, lifetime time.Duration) error {
 	// Check that this channel id isn't bound to another transport address, and
-	// that this transport address isn't bound to another channel id.
-	channelByID := a.GetChannelByID(c.ID)
+	// that this transport address isn't bound to another channel number.
+	channelByNumber := a.GetChannelByNumber(c.Number)
 	channelByPeer := a.GetChannelByAddr(c.Peer)
-	if channelByID != channelByPeer {
+	if channelByNumber != channelByPeer {
 		return errors.Errorf("You cannot use the same channel number with different peer")
 	}
 
 	// Add or refresh this channel.
-	if channelByID == nil {
+	if channelByNumber == nil {
 		a.channelBindingsLock.Lock()
 		defer a.channelBindingsLock.Unlock()
 
@@ -111,22 +111,22 @@ func (a *Allocation) AddChannelBind(c *ChannelBind, lifetime time.Duration) erro
 		// Channel binds also refresh permissions.
 		a.AddPermission(NewPermission(c.Peer, a.log))
 	} else {
-		channelByID.refresh(lifetime)
+		channelByNumber.refresh(lifetime)
 
 		// Channel binds also refresh permissions.
-		a.AddPermission(NewPermission(channelByID.Peer, a.log))
+		a.AddPermission(NewPermission(channelByNumber.Peer, a.log))
 	}
 
 	return nil
 }
 
 // RemoveChannelBind removes the ChannelBind from this allocation by id
-func (a *Allocation) RemoveChannelBind(id turn.ChannelNumber) bool {
+func (a *Allocation) RemoveChannelBind(number turn.ChannelNumber) bool {
 	a.channelBindingsLock.Lock()
 	defer a.channelBindingsLock.Unlock()
 
 	for i := len(a.channelBindings) - 1; i >= 0; i-- {
-		if a.channelBindings[i].ID == id {
+		if a.channelBindings[i].Number == number {
 			a.channelBindings = append(a.channelBindings[:i], a.channelBindings[i+1:]...)
 			return true
 		}
@@ -135,12 +135,12 @@ func (a *Allocation) RemoveChannelBind(id turn.ChannelNumber) bool {
 	return false
 }
 
-// GetChannelByID gets the ChannelBind from this allocation by id
-func (a *Allocation) GetChannelByID(id turn.ChannelNumber) *ChannelBind {
+// GetChannelByNumber gets the ChannelBind from this allocation by id
+func (a *Allocation) GetChannelByNumber(number turn.ChannelNumber) *ChannelBind {
 	a.channelBindingsLock.RLock()
 	defer a.channelBindingsLock.RUnlock()
 	for _, cb := range a.channelBindings {
-		if cb.ID == id {
+		if cb.Number == number {
 			return cb
 		}
 	}
@@ -235,9 +235,9 @@ func (a *Allocation) packetHandler(m *Manager) {
 			n,
 			srcAddr.String())
 
-		if channel := a.GetChannelByAddr(a.RelaySocket.LocalAddr()); channel != nil {
+		if channel := a.GetChannelByAddr(srcAddr); channel != nil {
 			channelData := make([]byte, 4)
-			binary.BigEndian.PutUint16(channelData[0:], uint16(channel.ID))
+			binary.BigEndian.PutUint16(channelData[0:], uint16(channel.Number))
 			binary.BigEndian.PutUint16(channelData[2:], uint16(n))
 			channelData = append(channelData, buffer[:n]...)
 
diff --git a/internal/allocation/allocation_manager.go b/internal/allocation/allocation_manager.go
index 598fc97..5509b51 100644
--- a/internal/allocation/allocation_manager.go
+++ b/internal/allocation/allocation_manager.go
@@ -59,7 +59,13 @@ func (m *Manager) Close() error {
 }
 
 // CreateAllocation creates a new allocation and starts relaying
-func (m *Manager) CreateAllocation(fiveTuple *FiveTuple, turnSocket net.PacketConn, requestedPort int, lifetime time.Duration) (*Allocation, error) {
+func (m *Manager) CreateAllocation(
+	fiveTuple *FiveTuple,
+	turnSocket net.PacketConn,
+	relayIP net.IP, // nolint:interfacer
+	requestedPort int,
+	lifetime time.Duration) (*Allocation, error) {
+
 	if fiveTuple == nil {
 		return nil, errors.Errorf("Allocations must not be created with nil FivTuple")
 	}
@@ -82,11 +88,14 @@ func (m *Manager) CreateAllocation(fiveTuple *FiveTuple, turnSocket net.PacketCo
 	a := NewAllocation(turnSocket, fiveTuple, m.log)
 
 	network := "udp4"
-	conn, err := m.net.ListenPacket(network, fmt.Sprintf("0.0.0.0:%d", requestedPort))
+	relayAddr := fmt.Sprintf("%s:%d", relayIP.String(), requestedPort)
+	conn, err := m.net.ListenPacket(network, relayAddr)
 	if err != nil {
 		return nil, err
 	}
 
+	m.log.Debugf("listening on relay addr: %s", conn.LocalAddr().String())
+
 	a.RelaySocket = conn
 	a.RelayAddr = conn.LocalAddr()
 
diff --git a/internal/allocation/channel_bind.go b/internal/allocation/channel_bind.go
index 9c3b70d..5fe4722 100644
--- a/internal/allocation/channel_bind.go
+++ b/internal/allocation/channel_bind.go
@@ -11,8 +11,8 @@ import (
 // ChannelBind represents a TURN Channel
 // https://tools.ietf.org/html/rfc5766#section-2.5
 type ChannelBind struct {
-	Peer net.Addr
-	ID   turn.ChannelNumber
+	Peer   net.Addr
+	Number turn.ChannelNumber
 
 	allocation    *Allocation
 	lifetimeTimer *time.Timer
@@ -20,24 +20,24 @@ type ChannelBind struct {
 }
 
 // NewChannelBind creates a new ChannelBind
-func NewChannelBind(id turn.ChannelNumber, peer net.Addr, log logging.LeveledLogger) *ChannelBind {
+func NewChannelBind(number turn.ChannelNumber, peer net.Addr, log logging.LeveledLogger) *ChannelBind {
 	return &ChannelBind{
-		ID:   id,
-		Peer: peer,
-		log:  log,
+		Number: number,
+		Peer:   peer,
+		log:    log,
 	}
 }
 
 func (c *ChannelBind) start(lifetime time.Duration) {
 	c.lifetimeTimer = time.AfterFunc(lifetime, func() {
-		if !c.allocation.RemoveChannelBind(c.ID) {
-			c.log.Errorf("Failed to remove ChannelBind for %v %x %v", c.ID, c.Peer, c.allocation.fiveTuple)
+		if !c.allocation.RemoveChannelBind(c.Number) {
+			c.log.Errorf("Failed to remove ChannelBind for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple)
 		}
 	})
 }
 
 func (c *ChannelBind) refresh(lifetime time.Duration) {
 	if !c.lifetimeTimer.Reset(lifetime) {
-		c.log.Errorf("Failed to reset ChannelBind timer for %v %x %v", c.ID, c.Peer, c.allocation.fiveTuple)
+		c.log.Errorf("Failed to reset ChannelBind timer for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple)
 	}
 }
diff --git a/sender.go b/sender.go
new file mode 100644
index 0000000..bb0bc1c
--- /dev/null
+++ b/sender.go
@@ -0,0 +1,20 @@
+package turn
+
+import (
+	"net"
+
+	"github.com/pion/stun"
+)
+
+// Sender is responsible for building a message and sending it to the given addr.
+type Sender func(conn net.PacketConn, addr net.Addr, attrs ...stun.Setter) error
+
+// defaultBuildAndSend is a sender default implementation.
+func defaultBuildAndSend(conn net.PacketConn, dst net.Addr, attrs ...stun.Setter) error {
+	msg, err := stun.Build(attrs...)
+	if err != nil {
+		return err
+	}
+	_, err = conn.WriteTo(msg.Raw, dst)
+	return err
+}
diff --git a/server.go b/server.go
index 1e7c1e4..c98f788 100644
--- a/server.go
+++ b/server.go
@@ -40,6 +40,10 @@ type ServerConfig struct {
 	LoggerFactory logging.LoggerFactory
 	// Net is used by pion developers. Do not use in your application.
 	Net *vnet.Net
+	// Software is the STUN SOFTWARE attribute. Useful for debugging purpose.
+	Software *stun.Software
+	// Sender is a custom implementation of the request Sender.
+	Sender Sender
 }
 
 type listener struct {
@@ -52,6 +56,7 @@ type Server struct {
 	lock               sync.RWMutex
 	listeners          []*listener
 	listenIPs          []net.IP
+	relayIPs           []net.IP
 	listenPort         int
 	realm              string
 	authHandler        AuthHandler
@@ -60,6 +65,8 @@ type Server struct {
 	channelBindTimeout time.Duration
 	log                logging.LeveledLogger
 	net                *vnet.Net
+	software           *stun.Software
+	sender             Sender
 }
 
 // NewServer creates the Pion TURN server
@@ -72,6 +79,10 @@ func NewServer(config *ServerConfig) *Server {
 		log.Warn("vnet is enabled")
 	}
 
+	if config.Sender == nil {
+		config.Sender = defaultBuildAndSend
+	}
+
 	manager := allocation.NewManager(&allocation.ManagerConfig{
 		LeveledLogger: log,
 		Net:           config.Net,
@@ -96,10 +107,14 @@ func NewServer(config *ServerConfig) *Server {
 		channelBindTimeout: channelBindTimeout,
 		log:                log,
 		net:                config.Net,
+		software:           config.Software,
+		sender:             config.Sender,
 	}
 }
 
 // AddListeningIPAddr adds a listening IP address.
+// If not specified, it will automatically assigns the listening IP addresses
+// from the system.
 // This method must be called before calling Start().
 func (s *Server) AddListeningIPAddr(addrStr string) error {
 	ip := net.ParseIP(addrStr)
@@ -114,13 +129,37 @@ func (s *Server) AddListeningIPAddr(addrStr string) error {
 	return nil
 }
 
+// AddRelayIPAddr adds a listening IP address.
+// Note: current implementation can have only one relay IP address.
+// If not specified, it will automatically assigns the relay IP addresses
+// from the system.
+// This method must be called before calling Start().
+func (s *Server) AddRelayIPAddr(addrStr string) error {
+	ip := net.ParseIP(addrStr)
+	if ip.To4() == nil {
+		return fmt.Errorf("Non-IPv4 address is not supported")
+	}
+
+	if ip.IsLinkLocalUnicast() {
+		return fmt.Errorf("link-local unicast address is not allowed")
+	}
+
+	if ip.IsUnspecified() {
+		return fmt.Errorf("unspecified IP is not allowed")
+	}
+	s.relayIPs = append(s.relayIPs, ip)
+	return nil
+}
+
 // caller must hold the mutex
-func (s *Server) gatherSystemIPAddrs() error {
+func (s *Server) gatherSystemIPAddrs() ([]net.IP, error) {
 	s.log.Debug("gathering local IP address...")
 
+	var ips []net.IP
+
 	ifs, err := s.net.Interfaces()
 	if err != nil {
-		return err
+		return nil, err
 	}
 	for _, ifc := range ifs {
 		if ifc.Flags&net.FlagUp == 0 {
@@ -146,7 +185,7 @@ func (s *Server) gatherSystemIPAddrs() error {
 			}
 
 			if ip == nil {
-				return fmt.Errorf("invalid IP address: %s", addr.String())
+				return nil, fmt.Errorf("invalid IP address: %s", addr.String())
 			}
 
 			if ip.To4() == nil {
@@ -158,11 +197,11 @@ func (s *Server) gatherSystemIPAddrs() error {
 			}
 
 			s.log.Debugf("- found local IP: %s", ip.String())
-			s.listenIPs = append(s.listenIPs, ip)
+			ips = append(ips, ip)
 		}
 	}
 
-	return nil
+	return ips, nil
 }
 
 // Listen starts listening and handling TURN traffic
@@ -209,16 +248,24 @@ func (s *Server) Start() error {
 	s.lock.Lock()
 	defer s.lock.Unlock()
 
-	// If s.listenIPs is empty, gather system IPs
+	var ips []net.IP
+
 	if len(s.listenIPs) == 0 {
-		err := s.gatherSystemIPAddrs()
+		var err error
+		ips, err = s.gatherSystemIPAddrs()
 		if err != nil {
 			return err
 		}
-
-		if len(s.listenIPs) == 0 {
+		if len(ips) == 0 {
 			return fmt.Errorf("no local IP address found")
 		}
+
+		s.listenIPs = ips
+	}
+
+	// If s.relayIPs is empty, use s.listenIPs
+	if len(s.relayIPs) == 0 {
+		s.relayIPs = s.listenIPs
 	}
 
 	for _, localIP := range s.listenIPs {
@@ -367,13 +414,3 @@ func assertMessageIntegrity(m *stun.Message, ourKey []byte) error {
 	messageIntegrityAttr := stun.MessageIntegrity(ourKey)
 	return messageIntegrityAttr.Check(m)
 }
-
-func buildAndSend(conn net.PacketConn, dst net.Addr, attrs ...stun.Setter) error {
-	msg, err := stun.Build(attrs...)
-	if err != nil {
-		return err
-	}
-
-	_, err = conn.WriteTo(msg.Raw, dst)
-	return err
-}
diff --git a/stun.go b/stun.go
index 44297aa..ae0131f 100644
--- a/stun.go
+++ b/stun.go
@@ -14,11 +14,10 @@ func (s *Server) handleBindingRequest(conn net.PacketConn, srcAddr net.Addr, m *
 		return err
 	}
 
-	return buildAndSend(conn, srcAddr, &stun.Message{TransactionID: m.TransactionID}, stun.BindingSuccess,
-		&stun.XORMappedAddress{
-			IP:   ip,
-			Port: port,
-		},
-		stun.Fingerprint,
-	)
+	attrs := s.makeAttrs(m.TransactionID, stun.BindingSuccess, &stun.XORMappedAddress{
+		IP:   ip,
+		Port: port,
+	}, stun.Fingerprint)
+
+	return s.sender(conn, srcAddr, attrs...)
 }
diff --git a/turn.go b/turn.go
index 48593d6..d6839c5 100644
--- a/turn.go
+++ b/turn.go
@@ -96,7 +96,7 @@ func assertDontFragment(curriedSend curriedSend, m *stun.Message, attr stun.Sett
 func (s *Server) handleAllocateRequest(conn net.PacketConn, srcAddr net.Addr, m *stun.Message) error {
 	dstAddr := conn.LocalAddr()
 	curriedSend := func(class stun.MessageClass, method stun.Method, transactionID [stun.TransactionIDSize]byte, attrs ...stun.Setter) error {
-		return buildAndSend(conn, srcAddr, append([]stun.Setter{&stun.Message{TransactionID: transactionID}, stun.NewType(method, class)}, attrs...)...)
+		return s.sender(conn, srcAddr, s.makeAttrs(transactionID, stun.NewType(method, class), attrs...)...)
 	}
 	respondWithError := func(err error, messageIntegrity stun.MessageIntegrity, errorCode stun.ErrorCode) error {
 		if sendErr := curriedSend(stun.ClassErrorResponse, stun.MethodAllocate, m.TransactionID,
@@ -132,7 +132,7 @@ func (s *Server) handleAllocateRequest(conn net.PacketConn, srcAddr net.Addr, m
 	// 2. The server checks if the 5-tuple is currently in use by an
 	//    existing allocation.  If yes, the server rejects the request with
 	//    a 437 (Allocation Mismatch) error.
-	if allocation := s.manager.GetAllocation(fiveTuple); allocation != nil {
+	if alloc := s.manager.GetAllocation(fiveTuple); alloc != nil {
 		return respondWithError(errors.Errorf("Relay already allocated for 5-TUPLE"), messageIntegrity, stun.CodeAllocMismatch)
 	}
 
@@ -212,7 +212,11 @@ func (s *Server) handleAllocateRequest(conn net.PacketConn, srcAddr net.Addr, m
 	// Check current usage vs redis usage of other servers
 	// if bad, redirect { stun.AttrErrorCode, 300 }
 	lifetimeDuration := allocationLifeTime(m)
-	a, err := s.manager.CreateAllocation(fiveTuple, conn, requestedPort, lifetimeDuration)
+	a, err := s.manager.CreateAllocation(
+		fiveTuple, conn,
+		s.relayIPs[0], // TODO: allow more than one relay IP
+		requestedPort,
+		lifetimeDuration)
 	if err != nil {
 		return respondWithError(err, messageIntegrity, stun.CodeInsufficientCapacity)
 	}
@@ -269,7 +273,7 @@ func (s *Server) handleAllocateRequest(conn net.PacketConn, srcAddr net.Addr, m
 func (s *Server) handleRefreshRequest(conn net.PacketConn, srcAddr net.Addr, m *stun.Message) error {
 	dstAddr := conn.LocalAddr()
 	curriedSend := func(class stun.MessageClass, method stun.Method, transactionID [stun.TransactionIDSize]byte, attrs ...stun.Setter) error {
-		return buildAndSend(conn, srcAddr, append([]stun.Setter{&stun.Message{TransactionID: transactionID}, stun.NewType(method, class)}, attrs...)...)
+		return s.sender(conn, srcAddr, s.makeAttrs(transactionID, stun.NewType(method, class), attrs...)...)
 	}
 	messageIntegrity, _, err := authenticateRequest(curriedSend, m, stun.MethodCreatePermission, s.realm, s.authHandler, srcAddr)
 	if err != nil {
@@ -300,7 +304,7 @@ func (s *Server) handleRefreshRequest(conn net.PacketConn, srcAddr net.Addr, m *
 func (s *Server) handleCreatePermissionRequest(conn net.PacketConn, srcAddr net.Addr, m *stun.Message) error {
 	dstAddr := conn.LocalAddr()
 	curriedSend := func(class stun.MessageClass, method stun.Method, transactionID [stun.TransactionIDSize]byte, attrs ...stun.Setter) error {
-		return buildAndSend(conn, srcAddr, append([]stun.Setter{&stun.Message{TransactionID: transactionID}, stun.NewType(method, class)}, attrs...)...)
+		return s.sender(conn, srcAddr, s.makeAttrs(transactionID, stun.NewType(method, class), attrs...)...)
 	}
 
 	a := s.manager.GetAllocation(&allocation.FiveTuple{
@@ -384,7 +388,7 @@ func (s *Server) handleSendIndication(conn net.PacketConn, srcAddr net.Addr, m *
 func (s *Server) handleChannelBindRequest(conn net.PacketConn, srcAddr net.Addr, m *stun.Message) error {
 	dstAddr := conn.LocalAddr()
 	errorSend := func(err error, attrs ...stun.Setter) error {
-		sendErr := buildAndSend(conn, srcAddr, append([]stun.Setter{&stun.Message{TransactionID: m.TransactionID}, stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse)}, attrs...)...)
+		sendErr := s.sender(conn, srcAddr, s.makeAttrs(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse), attrs...)...)
 		if sendErr != nil {
 			err = errors.Errorf(strings.Join([]string{sendErr.Error(), err.Error()}, "\n"))
 		}
@@ -401,7 +405,7 @@ func (s *Server) handleChannelBindRequest(conn net.PacketConn, srcAddr net.Addr,
 	}
 
 	messageIntegrity, _, err := authenticateRequest(func(class stun.MessageClass, method stun.Method, transactionID [stun.TransactionIDSize]byte, attrs ...stun.Setter) error {
-		return buildAndSend(conn, srcAddr, append([]stun.Setter{&stun.Message{TransactionID: m.TransactionID}, stun.NewType(method, class)}, attrs...)...)
+		return s.sender(conn, srcAddr, s.makeAttrs(m.TransactionID, stun.NewType(method, class))...)
 	}, m, stun.MethodChannelBind, s.realm, s.authHandler, srcAddr)
 	if err != nil {
 		return errorSend(err, stun.CodeBadRequest)
@@ -426,10 +430,11 @@ func (s *Server) handleChannelBindRequest(conn net.PacketConn, srcAddr net.Addr,
 		return errorSend(err, stun.CodeBadRequest)
 	}
 
-	return buildAndSend(conn, srcAddr, &stun.Message{TransactionID: m.TransactionID}, stun.BindingSuccess, messageIntegrity)
+	return s.sender(conn, srcAddr, s.makeAttrs(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse), messageIntegrity)...)
 }
 
 func (s *Server) handleChannelData(conn net.PacketConn, srcAddr net.Addr, c *turn.ChannelData) error {
+	s.log.Debugf("received ChannelData from %s", srcAddr.String())
 	dstAddr := conn.LocalAddr()
 	a := s.manager.GetAllocation(&allocation.FiveTuple{
 		SrcAddr:  srcAddr,
@@ -440,7 +445,7 @@ func (s *Server) handleChannelData(conn net.PacketConn, srcAddr net.Addr, c *tur
 		return errors.Errorf("No allocation found for %v:%v", srcAddr, dstAddr)
 	}
 
-	channel := a.GetChannelByID(c.Number)
+	channel := a.GetChannelByNumber(c.Number)
 	if channel == nil {
 		return errors.Errorf("No channel bind found for %x \n", uint16(c.Number))
 	}
@@ -457,6 +462,14 @@ func (s *Server) handleChannelData(conn net.PacketConn, srcAddr net.Addr, c *tur
 	return nil
 }
 
+func (s *Server) makeAttrs(transactionID [stun.TransactionIDSize]byte, msgType stun.MessageType, additional ...stun.Setter) []stun.Setter {
+	attrs := append([]stun.Setter{&stun.Message{TransactionID: transactionID}, msgType}, additional...)
+	if s.software != nil {
+		attrs = append(attrs, *s.software)
+	}
+	return attrs
+}
+
 func allocationLifeTime(m *stun.Message) time.Duration {
 	lifetimeDuration := turn.DefaultLifetime
 


Test patch:
diff --git a/client_test.go b/client_test.go
index 3f4fa10..7e81e64 100644
--- a/client_test.go
+++ b/client_test.go
@@ -4,6 +4,10 @@ import (
 	"net"
 	"testing"
 
+	"github.com/pion/stun"
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/assert"
+
 	"github.com/pion/logging"
 )
 
@@ -51,6 +55,40 @@ func TestClient(t *testing.T) {
 		}
 	})
 
+	t.Run("SendSTUNRequest adds SOFTWARE attribute to message", func(t *testing.T) {
+		const testSoftware = "CLIENT_SOFTWARE"
+
+		cfg := &ClientConfig{
+			ListeningAddress: "0.0.0.0:0",
+			LoggerFactory:    loggerFactory,
+			Sender: func(conn net.PacketConn, addr net.Addr, attrs ...stun.Setter) error {
+				msg, err := stun.Build(attrs...)
+				if err != nil {
+					return errors.Wrap(err, "could not build message")
+				}
+				var software stun.Software
+				if err = software.GetFrom(msg); err != nil {
+					return errors.Wrap(err, "could not get SOFTWARE attribute")
+				}
+
+				assert.Equal(t, testSoftware, software.String())
+
+				// just forward to the default sender.
+				return defaultBuildAndSend(conn, addr, attrs...)
+			},
+		}
+		software := stun.NewSoftware(testSoftware)
+		cfg.Software = &software
+
+		c, err := NewClient(cfg)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if _, err = c.SendSTUNRequest(net.IPv4(74, 125, 143, 127), 19302); err != nil {
+			t.Fatal(err)
+		}
+	})
+
 	t.Run("Listen error", func(t *testing.T) {
 		_, err := NewClient(&ClientConfig{
 			ListeningAddress: "255.255.255.256:65535",
diff --git a/internal/allocation/allocation_manager_test.go b/internal/allocation/allocation_manager_test.go
index c460110..d343bcd 100644
--- a/internal/allocation/allocation_manager_test.go
+++ b/internal/allocation/allocation_manager_test.go
@@ -48,13 +48,13 @@ func TestManager(t *testing.T) {
 // test invalid Allocation creations
 func subTestCreateInvalidAllocation(t *testing.T, turnSocket ipnet.PacketConn) {
 	m := newTestManager()
-	if a, err := m.CreateAllocation(nil, turnSocket, 0, turn.DefaultLifetime); a != nil || err == nil {
+	if a, err := m.CreateAllocation(nil, turnSocket, net.IPv4zero, 0, turn.DefaultLifetime); a != nil || err == nil {
 		t.Errorf("Illegally created allocation with nil FiveTuple")
 	}
-	if a, err := m.CreateAllocation(randomFiveTuple(), nil, 0, turn.DefaultLifetime); a != nil || err == nil {
+	if a, err := m.CreateAllocation(randomFiveTuple(), nil, net.IPv4zero, 0, turn.DefaultLifetime); a != nil || err == nil {
 		t.Errorf("Illegally created allocation with nil turnSocket")
 	}
-	if a, err := m.CreateAllocation(randomFiveTuple(), turnSocket, 0, 0); a != nil || err == nil {
+	if a, err := m.CreateAllocation(randomFiveTuple(), turnSocket, net.IPv4zero, 0, 0); a != nil || err == nil {
 		t.Errorf("Illegally created allocation with 0 lifetime")
 	}
 }
@@ -63,7 +63,7 @@ func subTestCreateInvalidAllocation(t *testing.T, turnSocket ipnet.PacketConn) {
 func subTestCreateAllocation(t *testing.T, turnSocket ipnet.PacketConn) {
 	m := newTestManager()
 	fiveTuple := randomFiveTuple()
-	if a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, turn.DefaultLifetime); a == nil || err != nil {
+	if a, err := m.CreateAllocation(fiveTuple, turnSocket, net.IPv4zero, 0, turn.DefaultLifetime); a == nil || err != nil {
 		t.Errorf("Failed to create allocation %v %v", a, err)
 	}
 
@@ -76,11 +76,11 @@ func subTestCreateAllocation(t *testing.T, turnSocket ipnet.PacketConn) {
 func subTestCreateAllocationDuplicateFiveTuple(t *testing.T, turnSocket ipnet.PacketConn) {
 	m := newTestManager()
 	fiveTuple := randomFiveTuple()
-	if a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, turn.DefaultLifetime); a == nil || err != nil {
+	if a, err := m.CreateAllocation(fiveTuple, turnSocket, net.IPv4zero, 0, turn.DefaultLifetime); a == nil || err != nil {
 		t.Errorf("Failed to create allocation %v %v", a, err)
 	}
 
-	if a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, turn.DefaultLifetime); a != nil || err == nil {
+	if a, err := m.CreateAllocation(fiveTuple, turnSocket, net.IPv4zero, 0, turn.DefaultLifetime); a != nil || err == nil {
 		t.Errorf("Was able to create allocation with same FiveTuple twice")
 	}
 }
@@ -88,7 +88,7 @@ func subTestCreateAllocationDuplicateFiveTuple(t *testing.T, turnSocket ipnet.Pa
 func subTestDeleteAllocation(t *testing.T, turnSocket ipnet.PacketConn) {
 	m := newTestManager()
 	fiveTuple := randomFiveTuple()
-	if a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, turn.DefaultLifetime); a == nil || err != nil {
+	if a, err := m.CreateAllocation(fiveTuple, turnSocket, net.IPv4zero, 0, turn.DefaultLifetime); a == nil || err != nil {
 		t.Errorf("Failed to create allocation %v %v", a, err)
 	}
 
@@ -111,7 +111,7 @@ func subTestAllocationTimeout(t *testing.T, turnSocket ipnet.PacketConn) {
 	for index := range allocations {
 		fiveTuple := randomFiveTuple()
 
-		a, err := m.CreateAllocation(fiveTuple, turnSocket, 0, lifetime)
+		a, err := m.CreateAllocation(fiveTuple, turnSocket, net.IPv4zero, 0, lifetime)
 		if err != nil {
 			t.Errorf("Failed to create allocation with %v", fiveTuple)
 		}
@@ -133,9 +133,9 @@ func subTestManagerClose(t *testing.T, turnSocket ipnet.PacketConn) {
 	m := newTestManager()
 	allocations := make([]*Allocation, 2)
 
-	a1, _ := m.CreateAllocation(randomFiveTuple(), turnSocket, 0, time.Second)
+	a1, _ := m.CreateAllocation(randomFiveTuple(), turnSocket, net.IPv4zero, 0, time.Second)
 	allocations[0] = a1
-	a2, _ := m.CreateAllocation(randomFiveTuple(), turnSocket, 0, time.Minute)
+	a2, _ := m.CreateAllocation(randomFiveTuple(), turnSocket, net.IPv4zero, 0, time.Minute)
 	allocations[1] = a2
 
 	// make a1 timeout
diff --git a/internal/allocation/allocation_test.go b/internal/allocation/allocation_test.go
index 5e03bbd..1ce4cbe 100644
--- a/internal/allocation/allocation_test.go
+++ b/internal/allocation/allocation_test.go
@@ -1,8 +1,16 @@
 package allocation
 
 import (
+	"fmt"
 	"net"
 	"testing"
+	"time"
+
+	"github.com/gortc/turn"
+	"github.com/pion/stun"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/pion/turn/internal/ipnet"
 )
 
 func TestAllocation(t *testing.T) {
@@ -13,6 +21,12 @@ func TestAllocation(t *testing.T) {
 		{"GetPermission", subTestGetPermission},
 		{"AddPermission", subTestAddPermission},
 		{"RemovePermission", subTestRemovePermission},
+		{"AddChannelBind", subTestAddChannelBind},
+		{"GetChannelByNumber", subTestGetChannelByNumber},
+		{"GetChannelByAddr", subTestGetChannelByAddr},
+		{"RemoveChannelBind", subTestRemoveChannelBind},
+		{"Close", subTestAllocationClose},
+		{"packetHandler", subTestPacketHandler},
 	}
 
 	for _, tc := range tt {
@@ -45,19 +59,13 @@ func subTestGetPermission(t *testing.T) {
 	a.AddPermission(p3)
 
 	foundP1 := a.GetPermission(addr)
-	if foundP1 != p {
-		t.Error("Should keep the first one.")
-	}
+	assert.Equal(t, p, foundP1, "Should keep the first one.")
 
 	foundP2 := a.GetPermission(addr2)
-	if foundP2 != p {
-		t.Error("Second one should be ignored.")
-	}
+	assert.Equal(t, p, foundP2, "Second one should be ignored.")
 
 	foundP3 := a.GetPermission(addr3)
-	if foundP3 != p3 {
-		t.Error("Permission with another IP should be found")
-	}
+	assert.Equal(t, p3, foundP3, "Permission with another IP should be found")
 }
 
 func subTestAddPermission(t *testing.T) {
@@ -69,14 +77,11 @@ func subTestAddPermission(t *testing.T) {
 	}
 
 	a.AddPermission(p)
-	if p.allocation != a {
-		t.Error("Permission's allocation should be the adder.")
-	}
+	assert.Equal(t, a, p.allocation, "Permission's allocation should be the adder.")
 
 	foundPermission := a.GetPermission(p.Addr)
-	if foundPermission != p {
-		t.Error("Got permission is not same as the the added.")
-	}
+	assert.Equal(t, p, foundPermission)
+
 }
 
 func subTestRemovePermission(t *testing.T) {
@@ -90,14 +95,202 @@ func subTestRemovePermission(t *testing.T) {
 	a.AddPermission(p)
 
 	foundPermission := a.GetPermission(p.Addr)
-	if foundPermission != p {
-		t.Error("Got permission is not same as the the added.")
-	}
+	assert.Equal(t, p, foundPermission, "Got permission is not same as the the added.")
 
 	a.RemovePermission(p.Addr)
 
 	foundPermission = a.GetPermission(p.Addr)
-	if foundPermission != nil {
-		t.Error("Got permission should be nil after removed.")
+	assert.Nil(t, foundPermission, "Got permission should be nil after removed.")
+}
+
+func subTestAddChannelBind(t *testing.T) {
+	a := NewAllocation(nil, nil, nil)
+
+	addr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:3478")
+	c := NewChannelBind(turn.MinChannelNumber, addr, nil)
+
+	err := a.AddChannelBind(c, turn.DefaultLifetime)
+	assert.Nil(t, err, "should succeed")
+	assert.Equal(t, a, c.allocation, "allocation should be the caller.")
+
+	c2 := NewChannelBind(turn.MinChannelNumber+1, addr, nil)
+	err = a.AddChannelBind(c2, turn.DefaultLifetime)
+	assert.NotNil(t, err, "should failed with conflicted peer address")
+
+	addr2, _ := net.ResolveUDPAddr("udp", "127.0.0.1:3479")
+	c3 := NewChannelBind(turn.MinChannelNumber, addr2, nil)
+	err = a.AddChannelBind(c3, turn.DefaultLifetime)
+	assert.NotNil(t, err, "should fail with conflicted number.")
+}
+
+func subTestGetChannelByNumber(t *testing.T) {
+	a := NewAllocation(nil, nil, nil)
+
+	addr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:3478")
+	c := NewChannelBind(turn.MinChannelNumber, addr, nil)
+
+	_ = a.AddChannelBind(c, turn.DefaultLifetime)
+
+	existChannel := a.GetChannelByNumber(c.Number)
+	assert.Equal(t, c, existChannel)
+
+	notExistChannel := a.GetChannelByNumber(turn.MinChannelNumber + 1)
+	assert.Nil(t, notExistChannel, "should be nil for not existed channel.")
+}
+
+func subTestGetChannelByAddr(t *testing.T) {
+	a := NewAllocation(nil, nil, nil)
+
+	addr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:3478")
+	c := NewChannelBind(turn.MinChannelNumber, addr, nil)
+
+	_ = a.AddChannelBind(c, turn.DefaultLifetime)
+
+	existChannel := a.GetChannelByAddr(c.Peer)
+	assert.Equal(t, c, existChannel)
+
+	addr2, _ := net.ResolveUDPAddr("udp", "127.0.0.1:3479")
+	notExistChannel := a.GetChannelByAddr(addr2)
+	assert.Nil(t, notExistChannel, "should be nil for not existed channel.")
+}
+
+func subTestRemoveChannelBind(t *testing.T) {
+	a := NewAllocation(nil, nil, nil)
+
+	addr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:3478")
+	c := NewChannelBind(turn.MinChannelNumber, addr, nil)
+
+	_ = a.AddChannelBind(c, turn.DefaultLifetime)
+
+	a.RemoveChannelBind(c.Number)
+
+	channelByNumber := a.GetChannelByNumber(c.Number)
+	assert.Nil(t, channelByNumber)
+
+	channelByAddr := a.GetChannelByAddr(c.Peer)
+	assert.Nil(t, channelByAddr)
+}
+
+func subTestAllocationClose(t *testing.T) {
+	network := "udp"
+
+	l, err := net.ListenPacket(network, "0.0.0.0:0")
+	if err != nil {
+		panic(err)
+	}
+
+	a := NewAllocation(nil, nil, nil)
+	a.RelaySocket = l
+	// add mock lifetimeTimer
+	a.lifetimeTimer = time.AfterFunc(turn.DefaultLifetime, func() {})
+
+	// add channel
+	addr, _ := net.ResolveUDPAddr(network, "127.0.0.1:3478")
+	c := NewChannelBind(turn.MinChannelNumber, addr, nil)
+	_ = a.AddChannelBind(c, turn.DefaultLifetime)
+
+	// add permission
+	a.AddPermission(NewPermission(addr, nil))
+
+	err = a.Close()
+	assert.Nil(t, err, "should succeed")
+	assert.True(t, isClose(a.RelaySocket), "should be closed")
+}
+
+func subTestPacketHandler(t *testing.T) {
+	network := "udp"
+
+	m := newTestManager()
+
+	// turn server initialization
+	turnSocket, err := net.ListenPacket(network, "127.0.0.1:0")
+	if err != nil {
+		panic(err)
+	}
+
+	// client listener initialization
+	clientListener, err := net.ListenPacket(network, "127.0.0.1:0")
+	if err != nil {
+		panic(err)
 	}
+
+	dataCh := make(chan []byte)
+	// client listener read data
+	go func() {
+		buffer := make([]byte, rtpMTU)
+		for {
+			n, _, err2 := clientListener.ReadFrom(buffer)
+			if err2 != nil {
+				return
+			}
+
+			dataCh <- buffer[:n]
+		}
+	}()
+
+	a, err := m.CreateAllocation(&FiveTuple{
+		SrcAddr: clientListener.LocalAddr(),
+		DstAddr: turnSocket.LocalAddr(),
+	}, turnSocket, 0, turn.DefaultLifetime)
+
+	assert.Nil(t, err, "should succeed")
+
+	peerListener1, err := net.ListenPacket(network, "127.0.0.1:0")
+	if err != nil {
+		panic(err)
+	}
+
+	peerListener2, err := net.ListenPacket(network, "127.0.0.1:0")
+	if err != nil {
+		panic(err)
+	}
+
+	// add permission with peer1 address
+	a.AddPermission(NewPermission(peerListener1.LocalAddr(), m.log))
+	// add channel with min channel number and peer2 address
+	channelBind := NewChannelBind(turn.MinChannelNumber, peerListener2.LocalAddr(), m.log)
+	_ = a.AddChannelBind(channelBind, turn.DefaultLifetime)
+
+	_, port, _ := ipnet.AddrIPPort(a.RelaySocket.LocalAddr())
+	relayAddrWithHostStr := fmt.Sprintf("127.0.0.1:%d", port)
+	relayAddrWithHost, _ := net.ResolveUDPAddr(network, relayAddrWithHostStr)
+
+	// test for permission and data message
+	targetText := "permission"
+	_, _ = peerListener1.WriteTo([]byte(targetText), relayAddrWithHost)
+	data := <-dataCh
+
+	// resolve stun data message
+	assert.True(t, stun.IsMessage(data), "should be stun message")
+
+	var msg stun.Message
+	err = stun.Decode(data, &msg)
+	assert.Nil(t, err, "decode data to stun message failed")
+
+	var msgData turn.Data
+	err = msgData.GetFrom(&msg)
+	assert.Nil(t, err, "get data from stun message failed")
+	assert.Equal(t, targetText, string(msgData), "get message doesn't equal the target text")
+
+	// test for channel bind and channel data
+	targetText2 := "channel bind"
+	_, _ = peerListener2.WriteTo([]byte(targetText2), relayAddrWithHost)
+	data = <-dataCh
+
+	// resolve channel data
+	assert.True(t, turn.IsChannelData(data), "should be channel data")
+
+	channelData := turn.ChannelData{
+		Raw: data,
+	}
+	err = channelData.Decode()
+	assert.Nil(t, err, fmt.Sprintf("channel data decode with error: %v", err))
+	assert.Equal(t, channelBind.Number, channelData.Number, "get channel data's number is invalid")
+	assert.Equal(t, targetText2, string(channelData.Data), "get data doesn't equal the target text.")
+
+	// listeners close
+	_ = m.Close()
+	_ = clientListener.Close()
+	_ = peerListener1.Close()
+	_ = peerListener2.Close()
 }
diff --git a/internal/allocation/channel_bind_test.go b/internal/allocation/channel_bind_test.go
index 4c586e7..5afb235 100644
--- a/internal/allocation/channel_bind_test.go
+++ b/internal/allocation/channel_bind_test.go
@@ -11,8 +11,8 @@ import (
 func TestChannelBind(t *testing.T) {
 	c := newChannelBind(2 * time.Second)
 
-	if c.allocation.GetChannelByID(c.ID) != c {
-		t.Errorf("GetChannelByID(%d) shouldn't be nil after added to allocation", c.ID)
+	if c.allocation.GetChannelByNumber(c.Number) != c {
+		t.Errorf("GetChannelByNumber(%d) shouldn't be nil after added to allocation", c.Number)
 	}
 }
 
@@ -21,8 +21,8 @@ func TestChannelBindStart(t *testing.T) {
 
 	time.Sleep(3 * time.Second)
 
-	if c.allocation.GetChannelByID(c.ID) != nil {
-		t.Errorf("GetChannelByID(%d) should be nil if timeout", c.ID)
+	if c.allocation.GetChannelByNumber(c.Number) != nil {
+		t.Errorf("GetChannelByNumber(%d) should be nil if timeout", c.Number)
 	}
 }
 
@@ -33,8 +33,8 @@ func TestChannelBindReset(t *testing.T) {
 	c.refresh(3 * time.Second)
 	time.Sleep(2 * time.Second)
 
-	if c.allocation.GetChannelByID(c.ID) == nil {
-		t.Errorf("GetChannelByID(%d) shouldn't be nil after refresh", c.ID)
+	if c.allocation.GetChannelByNumber(c.Number) == nil {
+		t.Errorf("GetChannelByNumber(%d) shouldn't be nil after refresh", c.Number)
 	}
 }
 
@@ -43,8 +43,8 @@ func newChannelBind(lifetime time.Duration) *ChannelBind {
 
 	addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:0")
 	c := &ChannelBind{
-		ID:   turn.ChannelNumber(turn.MinChannelNumber + 1),
-		Peer: addr,
+		Number: turn.MinChannelNumber,
+		Peer:   addr,
 	}
 
 	_ = a.AddChannelBind(c, lifetime)
diff --git a/server_test.go b/server_test.go
index 2366008..61dcd0a 100644
--- a/server_test.go
+++ b/server_test.go
@@ -5,6 +5,9 @@ import (
 	"testing"
 	"time"
 
+	"github.com/pion/stun"
+	"github.com/pkg/errors"
+
 	"github.com/gortc/turn"
 	"github.com/pion/logging"
 	"github.com/stretchr/testify/assert"
@@ -14,9 +17,10 @@ func TestServer(t *testing.T) {
 	loggerFactory := logging.NewDefaultLoggerFactory()
 	log := loggerFactory.NewLogger("test")
 
+	credMap := map[string]string{}
+	credMap["user"] = "pass"
+
 	t.Run("simple", func(t *testing.T) {
-		credMap := map[string]string{}
-		credMap["user"] = "pass"
 
 		server := NewServer(&ServerConfig{
 			AuthHandler: func(username string, srcAddr net.Addr) (password string, ok bool) {
@@ -34,16 +38,145 @@ func TestServer(t *testing.T) {
 		err := server.AddListeningIPAddr("127.0.0.1")
 		assert.NoError(t, err, "should succeed")
 
-		doneCh := make(chan struct{})
+		log.Debug("start listening...")
+		err = server.Start()
+		assert.NoError(t, err, "should succeed")
+
+		// make sure the server is listening before running
+		// the client.
+		time.Sleep(100 * time.Microsecond)
+
+		log.Debug("creating a client.")
+		client, err := NewClient(&ClientConfig{
+			ListeningAddress: "0.0.0.0:0",
+			LoggerFactory:    loggerFactory,
+		})
+		if !assert.NoError(t, err, "should succeed") {
+			return
+		}
+
+		log.Debug("sending a binding request.")
+		resp, err := client.SendSTUNRequest(net.IPv4(127, 0, 0, 1), 3478)
+		assert.NoError(t, err, "should succeed")
+		t.Logf("resp: %v", resp)
+
+		log.Debug("now closing the server...")
+
+		// Close server
+		err = server.Close()
+		assert.NoError(t, err, "should succeed")
+	})
+
+	t.Run("Relay IPs default to the listening IPs", func(t *testing.T) {
+		server := NewServer(&ServerConfig{
+			AuthHandler: func(username string, srcAddr net.Addr) (password string, ok bool) {
+				if pw, ok := credMap[username]; ok {
+					return pw, true
+				}
+				return "", false
+			},
+			Realm:         "pion.ly",
+			LoggerFactory: loggerFactory,
+		})
+
+		assert.Equal(t, turn.DefaultLifetime, server.channelBindTimeout, "should match")
+
+		err := server.AddListeningIPAddr("127.0.0.1")
+		assert.NoError(t, err, "should succeed")
+
+		log.Debug("start listening...")
+		err = server.Start()
+		assert.NoError(t, err, "should succeed")
 
-		go func() {
-			log.Debug("start listening...")
-			err2 := server.Start()
-			if err2 != nil {
-				t.Logf("Start returned with err: %v", err2)
-			}
-			close(doneCh)
-		}()
+		// make sure the server is listening before running
+		// the client.
+		time.Sleep(100 * time.Microsecond)
+
+		assert.Equal(t, 1, len(server.relayIPs), "should match")
+		assert.True(t, server.relayIPs[0].Equal(net.IPv4(127, 0, 0, 1)), "should match")
+
+		// Close server
+		err = server.Close()
+		assert.NoError(t, err, "should succeed")
+	})
+
+	t.Run("AddRelayIPAddr", func(t *testing.T) {
+		server := NewServer(&ServerConfig{
+			AuthHandler: func(username string, srcAddr net.Addr) (password string, ok bool) {
+				if pw, ok := credMap[username]; ok {
+					return pw, true
+				}
+				return "", false
+			},
+			Realm:         "pion.ly",
+			LoggerFactory: loggerFactory,
+		})
+
+		assert.Equal(t, turn.DefaultLifetime, server.channelBindTimeout, "should match")
+
+		err := server.AddListeningIPAddr("127.0.0.1")
+		assert.NoError(t, err, "should succeed")
+
+		err = server.AddRelayIPAddr("127.0.0.2")
+		assert.NoError(t, err, "should succeed")
+
+		err = server.AddRelayIPAddr("127.0.0.3")
+		assert.NoError(t, err, "should succeed")
+
+		log.Debug("start listening...")
+		err = server.Start()
+		assert.NoError(t, err, "should succeed")
+
+		// make sure the server is listening before running
+		// the client.
+		time.Sleep(100 * time.Microsecond)
+
+		assert.Equal(t, 2, len(server.relayIPs), "should match")
+		assert.True(t, server.relayIPs[0].Equal(net.IPv4(127, 0, 0, 2)), "should match")
+		assert.True(t, server.relayIPs[1].Equal(net.IPv4(127, 0, 0, 3)), "should match")
+
+		// Close server
+		err = server.Close()
+		assert.NoError(t, err, "should succeed")
+	})
+
+	t.Run("Adds SOFTWARE attribute to response", func(t *testing.T) {
+		const testSoftware = "SERVER_SOFTWARE"
+		cfg := &ServerConfig{
+			AuthHandler: func(username string, srcAddr net.Addr) (password string, ok bool) {
+				if pw, ok := credMap[username]; ok {
+					return pw, true
+				}
+				return "", false
+			},
+			Realm:         "pion.ly",
+			LoggerFactory: loggerFactory,
+			Sender: func(conn net.PacketConn, addr net.Addr, attrs ...stun.Setter) error {
+				msg, err := stun.Build(attrs...)
+				if err != nil {
+					return errors.Wrap(err, "could not build message")
+				}
+				var software stun.Software
+				if err = software.GetFrom(msg); err != nil {
+					return errors.Wrap(err, "could not get SOFTWARE attribute")
+				}
+
+				assert.Equal(t, testSoftware, software.String())
+				// just forward to the default sender.
+				return defaultBuildAndSend(conn, addr, attrs...)
+			},
+		}
+		software := stun.NewSoftware(testSoftware)
+		cfg.Software = &software
+
+		server := NewServer(cfg)
+
+		err := server.AddListeningIPAddr("127.0.0.1")
+		assert.NoError(t, err, "should succeed")
+
+		log.Debug("start listening...")
+		err = server.Start()
+		assert.NoError(t, err, "should succeed")
 
 		// make sure the server is listening before running
 		// the client.
@@ -68,7 +201,5 @@ func TestServer(t *testing.T) {
 		// Close server
 		err = server.Close()
 		assert.NoError(t, err, "should succeed")
-		log.Debug("wainting the server to terminate...")
-		<-doneCh
 	})
 }