Skip to content
This repository has been archived by the owner on Jun 5, 2021. It is now read-only.

Commit

Permalink
client: add "No retransmit" mode.
Browse files Browse the repository at this point in the history
Fixes #40
  • Loading branch information
ernado committed Aug 13, 2018
1 parent 4accb0f commit fd3ed9f
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 7 deletions.
25 changes: 19 additions & 6 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,29 @@ func WithCollector(coll Collector) ClientOption {
}
}

// WithNoRetransmit disables retransmissions and sets RTO to
// defaultMaxAttempts * defaultRTO which will be effectively time out
// if not set.
//
// Useful for TCP connections where transport handles RTO.
func WithNoRetransmit(c *Client) {
c.maxAttempts = 0
if c.rto == 0 {
c.rto = defaultMaxAttempts * int64(defaultRTO)
}
}

const (
defaultTimeoutRate = time.Millisecond * 5
defaultRTO = time.Millisecond * 300
defaultMaxAttempts = 7
)

// NewClient initializes new Client from provided options,
// starting internal goroutines and using default options fields
// if necessary. Call Close method after using Client to release
// resources.
func NewClient(conn Connection, options ...ClientOption) (*Client, error) {
const (
defaultTimeoutRate = time.Millisecond * 5
defaultRTO = time.Millisecond * 300
defaultMaxAttempts = 7
)
c := &Client{
close: make(chan struct{}),
c: conn,
Expand Down Expand Up @@ -491,7 +504,7 @@ func (c *Client) handleAgentCallback(e Event) {
return
}
h := t.h
if atomic.LoadInt32(&c.maxAttempts) < t.attempt || e.Error == nil {
if atomic.LoadInt32(&c.maxAttempts) <= t.attempt || e.Error == nil {
// Transaction completed.
putClientTransaction(t)
h(e)
Expand Down
57 changes: 57 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -933,3 +933,60 @@ func TestClientClosedStart(t *testing.T) {
t.Error("should error")
}
}

func TestWithNoRetransmit(t *testing.T) {
response := MustBuild(TransactionID, BindingSuccess)
response.Encode()
connL, connR := net.Pipe()
defer connL.Close()
collector := new(manualCollector)
clock := &manualClock{current: time.Now()}
agent := &manualAgent{}
attempt := 0
agent.start = func(id [TransactionIDSize]byte, deadline time.Time) error {
if attempt == 0 {
attempt++
go agent.h(Event{
TransactionID: id,
Error: ErrTransactionTimeOut,
})
} else {
t.Error("there should be no second attempt")
go agent.h(Event{
TransactionID: id,
Error: ErrTransactionTimeOut,
})
}
return nil
}
c, err := NewClient(connR,
WithAgent(agent),
WithClock(clock),
WithCollector(collector),
WithRTO(time.Millisecond),
WithNoRetransmit,
)
if err != nil {
t.Fatal(err)
}
gotReads := make(chan struct{})
go func() {
buf := make([]byte, 1500)
readN, readErr := connL.Read(buf)
if readErr != nil {
t.Error(readErr)
}
if !IsMessage(buf[:readN]) {
t.Error("should be STUN")
}
gotReads <- struct{}{}
}()
if doErr := c.Do(MustBuild(response, BindingRequest), func(event Event) {
if event.Error != ErrTransactionTimeOut {
t.Error("unexpected error")
}
}); doErr != nil {
t.Fatal(err)
}
<-gotReads
}
8 changes: 7 additions & 1 deletion e2e/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ func test(network string) {
if err != nil {
log.Fatalln("failed to dial conn:", err)
}
client, err := stun.NewClient(conn)
var options []stun.ClientOption
if network == "tcp" {
// Switching to "NO-RTO" mode.
fmt.Println("using WithNoRetransmit for TCP")
options = append(options, stun.WithNoRetransmit)
}
client, err := stun.NewClient(conn, options...)
if err != nil {
log.Fatal(err)
}
Expand Down

0 comments on commit fd3ed9f

Please sign in to comment.