Skip to content

Commit cc00838

Browse files
committed
fix: remote CSEQ must be differentely validated #187
1 parent 14f10d8 commit cc00838

File tree

5 files changed

+95
-36
lines changed

5 files changed

+95
-36
lines changed

dialog.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ type Dialog struct {
3535
InviteRequest *sip.Request
3636

3737
// lastCSeqNo is set for every request within dialog except ACK CANCEL
38-
lastCSeqNo atomic.Uint32
38+
lastCSeqNo atomic.Uint32
39+
remoteCSeqNo atomic.Uint32
3940

4041
// InviteResponse is last response received or sent. It is not thread safe!
4142
// Use it only as read only and do not change values
@@ -58,6 +59,7 @@ func (d *Dialog) Init() {
5859
// We may have sequence number initialized
5960
if cseq := d.InviteRequest.CSeq(); cseq != nil {
6061
d.lastCSeqNo.Store(cseq.SeqNo)
62+
d.remoteCSeqNo.Store(cseq.SeqNo)
6163
}
6264
d.onStatePointer = atomic.Pointer[DialogStateFn]{}
6365
}
@@ -144,3 +146,23 @@ func (d *Dialog) CSEQ() uint32 {
144146
func (d *Dialog) Context() context.Context {
145147
return d.ctx
146148
}
149+
150+
func (d *Dialog) validateRemoteRequest(req *sip.Request) (err error) {
151+
// Make sure this is bye for this dialog
152+
if req.CSeq().SeqNo < d.remoteCSeqNo.Load() {
153+
return ErrDialogInvalidCseq
154+
}
155+
return nil
156+
}
157+
158+
func (d *Dialog) ReadRequest(req *sip.Request, tx sip.ServerTransaction) error {
159+
// UAS role of dialog SHOULD be
160+
// prepared to receive and process requests with CSeq values more than
161+
// one higher than the previous received request.
162+
if err := d.validateRemoteRequest(req); err != nil {
163+
return err
164+
}
165+
166+
d.remoteCSeqNo.Store(req.CSeq().SeqNo)
167+
return nil
168+
}

dialog_client.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,6 @@ type DialogClientSession struct {
2121
onClose func()
2222
}
2323

24-
func (dt *DialogClientSession) validateRequest(req *sip.Request) (err error) {
25-
// Make sure this is bye for this dialog
26-
if req.CSeq().SeqNo < dt.lastCSeqNo.Load() {
27-
return ErrDialogInvalidCseq
28-
}
29-
return nil
30-
}
31-
3224
func (s *DialogClientSession) ReadBye(req *sip.Request, tx sip.ServerTransaction) error {
3325
s.setState(sip.DialogStateEnded)
3426

@@ -46,16 +38,6 @@ func (s *DialogClientSession) ReadBye(req *sip.Request, tx sip.ServerTransaction
4638
return nil
4739
}
4840

49-
// ReadRequest is generic func to validate new request in dialog and update seq. Use it if there are no predefined
50-
func (s *DialogClientSession) ReadRequest(req *sip.Request, tx sip.ServerTransaction) error {
51-
if err := s.validateRequest(req); err != nil {
52-
return err
53-
}
54-
55-
s.lastCSeqNo.Store(req.CSeq().SeqNo)
56-
return nil
57-
}
58-
5941
// Do sends request and waits final response using Dialog rules
6042
// For more control use TransactionRequest
6143
//

dialog_server.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,6 @@ func (s *DialogServerSession) ReadBye(req *sip.Request, tx sip.ServerTransaction
5151
return nil
5252
}
5353

54-
// ReadRequest is generic func to validate new request in dialog and update seq. Use it if there are no predefined
55-
func (s *DialogServerSession) ReadRequest(req *sip.Request, tx sip.ServerTransaction) error {
56-
if err := s.validateRequest(req); err != nil {
57-
return err
58-
}
59-
60-
s.lastCSeqNo.Store(req.CSeq().SeqNo)
61-
return nil
62-
}
63-
6454
// Do does request response pattern. For more control over transaction use TransactionRequest
6555
func (s *DialogServerSession) Do(ctx context.Context, req *sip.Request) (*sip.Response, error) {
6656
tx, err := s.TransactionRequest(ctx, req)
@@ -431,11 +421,7 @@ func (s *DialogServerSession) WriteBye(ctx context.Context, bye *sip.Request) er
431421
func (dt *DialogServerSession) validateRequest(req *sip.Request) (err error) {
432422
// Make sure this is bye for this dialog
433423

434-
// UAS SHOULD be
435-
// prepared to receive and process requests with CSeq values more than
436-
// one higher than the previous received request.
437-
438-
if req.CSeq().SeqNo < dt.lastCSeqNo.Load() {
424+
if req.CSeq().SeqNo < dt.InviteRequest.CSeq().SeqNo {
439425
return ErrDialogInvalidCseq
440426
}
441427
return nil

dialog_server_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,75 @@ func TestDialogServerTransactionCanceled(t *testing.T) {
112112

113113
}
114114

115+
func TestDialogServerRequestsWithinDialog(t *testing.T) {
116+
// https://datatracker.ietf.org/doc/html/rfc3261#section-12.2.2
117+
118+
ua, _ := NewUA()
119+
defer ua.Close()
120+
cli, _ := NewClient(ua)
121+
122+
uasContact := sip.ContactHeader{
123+
Address: sip.Uri{User: "test", Host: "127.0.0.200", Port: 5099},
124+
}
125+
dialogSrv := NewDialogServerCache(cli, uasContact)
126+
127+
invite, _, _ := createTestInvite(t, "sip:[email protected]", "udp", "127.0.0.1:5090")
128+
invite.AppendHeader(&sip.ContactHeader{Address: sip.Uri{Host: "uas", Port: 1234}})
129+
130+
t.Run("InvalidCseq", func(t *testing.T) {
131+
// This covers issue explained as
132+
// https://github.com/emiago/sipgo/issues/187
133+
conn := &sip.UDPConnection{
134+
PacketConn: &fakes.UDPConn{
135+
Writers: map[string]io.Writer{
136+
"127.0.0.1:5090": bytes.NewBuffer(make([]byte, 0)),
137+
},
138+
},
139+
}
140+
tx := sip.NewServerTx("test", invite, conn, slog.Default())
141+
tx.Init()
142+
143+
dialog, err := dialogSrv.ReadInvite(invite, tx)
144+
require.NoError(t, err)
145+
defer dialog.Close()
146+
147+
byeWrongCseq := newByeRequestUAC(invite, sip.NewResponseFromRequest(invite, 200, "OK", nil), nil)
148+
byeWrongCseq.CSeq().SeqNo--
149+
tx = sip.NewServerTx("test", byeWrongCseq, conn, slog.Default())
150+
tx.Init()
151+
err = dialog.ReadBye(byeWrongCseq, tx)
152+
require.ErrorIs(t, err, ErrDialogInvalidCseq)
153+
})
154+
155+
t.Run("TerminateAfterSentRequest", func(t *testing.T) {
156+
// This covers issue explained as
157+
// https://github.com/emiago/sipgo/issues/187
158+
conn := &sip.UDPConnection{
159+
PacketConn: &fakes.UDPConn{
160+
Writers: map[string]io.Writer{
161+
"127.0.0.1:5090": bytes.NewBuffer(make([]byte, 0)),
162+
},
163+
},
164+
}
165+
tx := sip.NewServerTx("test", invite, conn, slog.Default())
166+
tx.Init()
167+
168+
dialog, err := dialogSrv.ReadInvite(invite, tx)
169+
require.NoError(t, err)
170+
defer dialog.Close()
171+
172+
reinvite := sip.NewRequest(sip.INVITE, invite.From().Address)
173+
_, err = dialog.TransactionRequest(context.TODO(), reinvite)
174+
require.NoError(t, err)
175+
176+
bye := newByeRequestUAC(invite, sip.NewResponseFromRequest(invite, 200, "OK", nil), nil)
177+
tx = sip.NewServerTx("test-bye", bye, conn, slog.Default())
178+
tx.Init()
179+
err = dialog.ReadBye(bye, tx)
180+
require.NoError(t, err)
181+
})
182+
}
183+
115184
func TestDialogServer2xxRetransmission(t *testing.T) {
116185
// sip.T1 = 1
117186
ua, _ := NewUA()

server_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func createTestInvite(t testing.TB, targetSipUri string, transport, addr string)
7373
"From: \"Alice\" <sip:alice@" + addr + ">;tag=" + ftag,
7474
"To: \"Bob\" <" + targetSipUri + ">",
7575
"Call-ID: " + callid,
76-
"CSeq: 1 INVITE",
76+
"CSeq: 10 INVITE",
7777
"Content-Length: 0",
7878
"",
7979
"",
@@ -88,7 +88,7 @@ func createTestBye(t testing.TB, targetSipUri string, transport, addr string, ca
8888
"From: \"Alice\" <sip:alice@" + addr + ">;tag=" + ftag,
8989
"To: \"Bob\" <" + targetSipUri + ">;tag=" + totag,
9090
"Call-ID: " + callid,
91-
"CSeq: 1 INVITE",
91+
"CSeq: 10 INVITE",
9292
"Content-Length: 0",
9393
"",
9494
"",

0 commit comments

Comments
 (0)