quaff 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/auth.rb +13 -0
- data/lib/call.rb +57 -41
- data/lib/endpoint.rb +64 -27
- data/lib/message.rb +9 -1
- metadata +18 -2
data/lib/auth.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
1
3
|
module Quaff
|
2
4
|
module Auth
|
3
5
|
def Auth.gen_nonce auth_pairs, username, passwd, method, sip_uri
|
@@ -9,6 +11,17 @@ module Quaff
|
|
9
11
|
return digest
|
10
12
|
end
|
11
13
|
|
14
|
+
def Auth.extract_rand auth_line
|
15
|
+
# Split auth line on commas
|
16
|
+
auth_pairs = {}
|
17
|
+
auth_line.sub("Digest ", "").split(",") .each do |pair|
|
18
|
+
key, value = pair.split "="
|
19
|
+
auth_pairs[key.gsub(" ", "")] = value.gsub("\"", "").gsub(" ", "")
|
20
|
+
end
|
21
|
+
# First 128 bits are the RAND
|
22
|
+
return Base64.decode64(auth_pairs["nonce"])[0..15]
|
23
|
+
end
|
24
|
+
|
12
25
|
def Auth.gen_auth_header auth_line, username, passwd, method, sip_uri
|
13
26
|
# Split auth line on commas
|
14
27
|
auth_pairs = {}
|
data/lib/call.rb
CHANGED
@@ -5,21 +5,21 @@ require_relative './auth.rb'
|
|
5
5
|
require_relative './message.rb'
|
6
6
|
|
7
7
|
module Quaff
|
8
|
-
class CSeq # :nodoc:
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def increment
|
15
|
-
@num = @num + 1
|
16
|
-
to_s
|
17
|
-
end
|
8
|
+
class CSeq # :nodoc:
|
9
|
+
attr_reader :num
|
10
|
+
def initialize cseq_str
|
11
|
+
@num, @method = cseq_str.split
|
12
|
+
@num = @num.to_i
|
13
|
+
end
|
18
14
|
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
def increment
|
16
|
+
@num = @num + 1
|
17
|
+
to_s
|
18
|
+
end
|
22
19
|
|
20
|
+
def to_s
|
21
|
+
"#{@num.to_s} #{@method}"
|
22
|
+
end
|
23
23
|
end
|
24
24
|
|
25
25
|
class Call
|
@@ -49,8 +49,15 @@ class Call
|
|
49
49
|
@cxn.add_call_id @cid
|
50
50
|
end
|
51
51
|
|
52
|
-
def update_branch
|
53
|
-
|
52
|
+
def update_branch via_hdr=nil
|
53
|
+
via_hdr ||= get_new_via_hdr
|
54
|
+
@last_Via = via_hdr
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :new_transaction, :update_branch
|
58
|
+
|
59
|
+
def get_new_via_hdr
|
60
|
+
"SIP/2.0/#{@cxn.transport} #{Quaff::Utils.local_ip}:#{@cxn.local_port};rport;branch=#{Quaff::Utils::new_branch}"
|
54
61
|
end
|
55
62
|
|
56
63
|
def create_dialog msg
|
@@ -72,36 +79,46 @@ class Call
|
|
72
79
|
end
|
73
80
|
end
|
74
81
|
|
75
|
-
def recv_request(method)
|
82
|
+
def recv_request(method, dialog_creating=true)
|
76
83
|
begin
|
77
|
-
|
84
|
+
msg = recv_something
|
78
85
|
rescue
|
79
86
|
raise "#{ @uri } timed out waiting for #{ method }"
|
80
87
|
end
|
81
|
-
unless
|
82
|
-
and Regexp.new(method) =~
|
83
|
-
raise((
|
88
|
+
unless msg.type == :request \
|
89
|
+
and Regexp.new(method) =~ msg.method
|
90
|
+
raise((msg.to_s || "Message is nil!"))
|
84
91
|
end
|
85
|
-
|
86
|
-
|
92
|
+
if dialog_creating
|
93
|
+
set_callee msg.first_header("Contact")
|
94
|
+
unless msg.all_headers("Record-Route").nil?
|
95
|
+
@routeset = msg.all_headers("Record-Route")
|
96
|
+
end
|
87
97
|
end
|
88
|
-
|
98
|
+
msg
|
89
99
|
end
|
90
100
|
|
91
|
-
def recv_response(code)
|
101
|
+
def recv_response(code, dialog_creating=false)
|
92
102
|
begin
|
93
|
-
|
103
|
+
msg = recv_something
|
94
104
|
rescue
|
95
105
|
raise "#{ @uri } timed out waiting for #{ code }"
|
96
106
|
end
|
97
|
-
unless
|
98
|
-
and Regexp.new(code) =~
|
99
|
-
raise "Expected #{ code}, got #{
|
107
|
+
unless msg.type == :response \
|
108
|
+
and Regexp.new(code) =~ msg.status_code
|
109
|
+
raise "Expected #{ code}, got #{msg.status_code || msg}"
|
100
110
|
end
|
101
|
-
|
102
|
-
|
111
|
+
if dialog_creating
|
112
|
+
set_callee msg.first_header("Contact")
|
113
|
+
unless msg.all_headers("Record-Route").nil?
|
114
|
+
@routeset = msg.all_headers("Record-Route").reverse
|
115
|
+
end
|
103
116
|
end
|
104
|
-
|
117
|
+
msg
|
118
|
+
end
|
119
|
+
|
120
|
+
def recv_response_and_create_dialog(code)
|
121
|
+
recv_response code, true
|
105
122
|
end
|
106
123
|
|
107
124
|
def send_response(code, phrase, body="", retrans=nil, headers={})
|
@@ -119,7 +136,6 @@ class Call
|
|
119
136
|
@cxn.mark_call_dead @cid
|
120
137
|
end
|
121
138
|
|
122
|
-
|
123
139
|
def clear_tag str
|
124
140
|
str
|
125
141
|
end
|
@@ -143,22 +159,22 @@ class Call
|
|
143
159
|
|
144
160
|
private
|
145
161
|
def recv_something
|
146
|
-
|
162
|
+
msg = @cxn.get_new_message @cid
|
147
163
|
@retrans = nil
|
148
|
-
@src =
|
149
|
-
@last_To =
|
150
|
-
@last_From =
|
151
|
-
set_callee
|
152
|
-
@last_Via =
|
153
|
-
@last_CSeq = CSeq.new(
|
154
|
-
|
164
|
+
@src = msg.source
|
165
|
+
@last_To = msg.header("To")
|
166
|
+
@last_From = msg.header("From")
|
167
|
+
set_callee msg.header("From")
|
168
|
+
@last_Via = msg.headers["Via"]
|
169
|
+
@last_CSeq = CSeq.new(msg.header("CSeq"))
|
170
|
+
msg
|
155
171
|
end
|
156
172
|
|
157
173
|
def calculate_cseq type, method
|
158
174
|
if (type == :response)
|
159
175
|
@last_CSeq.to_s
|
160
176
|
elsif (method == "ACK")
|
161
|
-
@last_CSeq.
|
177
|
+
"#{@last_CSeq.num} ACK"
|
162
178
|
else
|
163
179
|
@cseq_number = @cseq_number + 1
|
164
180
|
"#{@cseq_number} #{method}"
|
data/lib/endpoint.rb
CHANGED
@@ -4,12 +4,19 @@ require 'thread'
|
|
4
4
|
require 'timeout'
|
5
5
|
require 'resolv'
|
6
6
|
require 'digest/md5'
|
7
|
+
#require 'milenage'
|
7
8
|
require_relative './sip_parser.rb'
|
8
9
|
require_relative './sources.rb'
|
9
10
|
|
10
11
|
module Quaff
|
11
12
|
class BaseEndpoint
|
12
|
-
attr_accessor :msg_trace, :uri
|
13
|
+
attr_accessor :msg_trace, :uri, :sdp_port, :sdp_socket
|
14
|
+
|
15
|
+
def setup_sdp
|
16
|
+
@sdp_socket = UDPSocket.new
|
17
|
+
@sdp_socket.bind('0.0.0.0', 0)
|
18
|
+
@sdp_port = @sdp_socket.addr[1]
|
19
|
+
end
|
13
20
|
|
14
21
|
def generate_call_id
|
15
22
|
digest = Digest::MD5.hexdigest(rand(60000).to_s)
|
@@ -33,13 +40,23 @@ module Quaff
|
|
33
40
|
Call.new(self, call_id, @uri, @outbound_connection, to_uri)
|
34
41
|
end
|
35
42
|
|
43
|
+
def create_client(uri, username, password, outbound_proxy, outbound_port=5060)
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_server(uri, local_port=5060, outbound_proxy=nil, outbound_port=5060)
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_aka_client(uri, username, key, op, outbound_proxy, outbound_port=5060)
|
51
|
+
end
|
52
|
+
|
36
53
|
def initialize(uri, username, password, local_port, outbound_proxy=nil, outbound_port=5060)
|
37
54
|
@uri = uri
|
38
55
|
@resolver = Resolv::DNS.new
|
39
56
|
@username = username
|
40
57
|
@password = password
|
41
58
|
@lport = local_port
|
42
|
-
initialize_connection
|
59
|
+
initialize_connection
|
43
60
|
if outbound_proxy
|
44
61
|
@outbound_connection = new_connection(outbound_proxy, outbound_port)
|
45
62
|
end
|
@@ -75,18 +92,28 @@ module Quaff
|
|
75
92
|
source.send_msg(@cxn, data)
|
76
93
|
end
|
77
94
|
|
78
|
-
def
|
95
|
+
def set_aka_credentials key, op
|
96
|
+
@kernel = Milenage.Kernel key
|
97
|
+
@kernel.op = op
|
98
|
+
end
|
99
|
+
|
100
|
+
def register expires="3600", aka=false
|
79
101
|
call = outgoing_call(@uri)
|
80
102
|
call.send_request("REGISTER", "", { "Expires" => expires.to_s })
|
81
103
|
response_data = call.recv_response("401|200")
|
82
|
-
if response_data
|
83
|
-
|
84
|
-
|
104
|
+
if response_data.status_code == "401"
|
105
|
+
if aka
|
106
|
+
rand = Quaff::Auth.extract_rand response_data.header("WWW-Authenticate")
|
107
|
+
password = @kernel.f3 rand
|
108
|
+
else
|
109
|
+
password = @password
|
110
|
+
end
|
111
|
+
auth_hdr = Quaff::Auth.gen_auth_header response_data.header("WWW-Authenticate"), @username, @password, "REGISTER", @uri
|
85
112
|
call.update_branch
|
86
113
|
call.send_request("REGISTER", "", {"Authorization" => auth_hdr, "Expires" => expires.to_s, "CSeq" => "2 REGISTER"})
|
87
|
-
call.recv_response("200")
|
114
|
+
response_data = call.recv_response("200")
|
88
115
|
end
|
89
|
-
return
|
116
|
+
return response_data # always the 200 OK
|
90
117
|
end
|
91
118
|
|
92
119
|
def unregister
|
@@ -110,27 +137,32 @@ module Quaff
|
|
110
137
|
end
|
111
138
|
|
112
139
|
def queue_msg(msg, source)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@messages[cid].enq({"message" => msg, "source" => source})
|
140
|
+
puts "Endpoint on #{@lport} received #{msg} from
|
141
|
+
##{source.inspect}" if @msg_trace
|
142
|
+
msg.source = source
|
143
|
+
cid = @parser.message_identifier msg
|
144
|
+
if cid and not @dead_calls.has_key? cid then
|
145
|
+
unless @messages.has_key? cid then
|
146
|
+
add_call_id cid
|
147
|
+
@call_ids.enq cid
|
122
148
|
end
|
149
|
+
@messages[cid].enq(msg)
|
150
|
+
end
|
123
151
|
end
|
124
|
-
|
125
152
|
end
|
126
153
|
|
127
154
|
class TCPSIPEndpoint < BaseEndpoint
|
128
155
|
attr_accessor :sockets
|
129
156
|
|
130
|
-
def initialize_connection
|
131
|
-
|
132
|
-
@
|
133
|
-
|
157
|
+
def initialize_connection
|
158
|
+
if @lport != :anyport
|
159
|
+
@cxn = TCPServer.new(@lport)
|
160
|
+
else
|
161
|
+
@cxn = TCPServer.new(0)
|
162
|
+
@lport = @cxn.addr[1]
|
163
|
+
end
|
164
|
+
@parser = SipParser.new
|
165
|
+
@sockets = []
|
134
166
|
end
|
135
167
|
|
136
168
|
def transport
|
@@ -201,11 +233,16 @@ module Quaff
|
|
201
233
|
alias_method :new_connection, :new_source
|
202
234
|
|
203
235
|
private
|
204
|
-
def initialize_connection
|
205
|
-
|
206
|
-
|
207
|
-
@
|
208
|
-
|
236
|
+
def initialize_connection
|
237
|
+
@cxn = UDPSocket.new
|
238
|
+
if @lport != :anyport
|
239
|
+
@cxn.bind('0.0.0.0', @lport)
|
240
|
+
else
|
241
|
+
@cxn.bind('0.0.0.0', 0)
|
242
|
+
@lport = @cxn.addr[1]
|
243
|
+
end
|
244
|
+
@sockets = []
|
245
|
+
@parser = SipParser.new
|
209
246
|
end
|
210
247
|
|
211
248
|
def recv_msg
|
data/lib/message.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Quaff
|
2
2
|
class SipMessage
|
3
|
-
attr_accessor :method, :requri, :reason, :status_code, :headers
|
3
|
+
attr_accessor :method, :requri, :reason, :status_code, :headers, :source
|
4
4
|
attr_reader :body
|
5
5
|
|
6
6
|
def initialize method=nil, status_code=nil, reason=nil,
|
@@ -11,6 +11,14 @@ class SipMessage
|
|
11
11
|
@headers['Content-Length'] = [body.length]
|
12
12
|
end
|
13
13
|
|
14
|
+
def [] key
|
15
|
+
if key == "message"
|
16
|
+
self
|
17
|
+
elsif key == "source"
|
18
|
+
@source
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
14
22
|
def type
|
15
23
|
if @method
|
16
24
|
:request
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quaff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-12-
|
12
|
+
date: 2013-12-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: facter
|
@@ -27,6 +27,22 @@ dependencies:
|
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 1.7.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: milenage
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.1.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.1.0
|
30
46
|
description: A Ruby library for writing SIP test scenarios
|
31
47
|
email: rkd@rkd.me.uk
|
32
48
|
executables: []
|