sip-notify 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +4 -0
- data/bin/sip-notify +138 -0
- data/lib/sip_notify.rb +855 -0
- metadata +65 -0
data/README.md
ADDED
data/bin/sip-notify
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift( File.join( File.dirname( __FILE__ ), '..', 'lib' ))
|
7
|
+
|
8
|
+
require 'sip_notify'
|
9
|
+
|
10
|
+
|
11
|
+
opts_defaults = {
|
12
|
+
:port => 5060,
|
13
|
+
:user => nil,
|
14
|
+
:verbosity => 0,
|
15
|
+
:event => 'check-sync;reboot=false',
|
16
|
+
:spoof_src_addr => nil,
|
17
|
+
}
|
18
|
+
opts = {}
|
19
|
+
|
20
|
+
opts_parser = ::OptionParser.new { |op|
|
21
|
+
op.banner = "Usage: #{ ::File.basename(__FILE__) } HOST [options]"
|
22
|
+
|
23
|
+
op.on( "-p", "--port=PORT", Integer,
|
24
|
+
"Port. (Default: #{opts_defaults[:port].inspect})"
|
25
|
+
) { |v|
|
26
|
+
opts[:port] = v.to_i
|
27
|
+
if ! v.between?( 1, 65535 )
|
28
|
+
$stderr.puts "Invalid port."
|
29
|
+
$stderr.puts op
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
op.on( "-u", "--user=USER", String,
|
35
|
+
"User/extension. (Default: #{opts_defaults[:user].inspect})"
|
36
|
+
) { |v|
|
37
|
+
opts[:user] = v
|
38
|
+
}
|
39
|
+
|
40
|
+
op.on( "-e", "--event=EVENT", String,
|
41
|
+
"Event. (Default: #{opts_defaults[:event].inspect})"
|
42
|
+
) { |v|
|
43
|
+
opts[:event] = v
|
44
|
+
}
|
45
|
+
|
46
|
+
op.on( "-t", "--type=EVENT_TYPE", String,
|
47
|
+
"Pre-defined event type."
|
48
|
+
) { |v|
|
49
|
+
opts[:event_type] = v
|
50
|
+
}
|
51
|
+
|
52
|
+
op.on( "--types", String,
|
53
|
+
"List pre-defined event types."
|
54
|
+
) { |v|
|
55
|
+
puts "Pre-defined event types:"
|
56
|
+
puts " #{'Name'.to_s.ljust(32)} #{'Event header'.to_s.ljust(30)} #{'Content-Type header'.to_s.ljust(35)} +"
|
57
|
+
puts " #{'-' * 32} #{'-' * 30} #{'-' * 35} #{'-' * 1}"
|
58
|
+
::SipNotify.event_templates.each { |name, info|
|
59
|
+
puts " #{name.to_s.ljust(32)} #{info[:event].to_s.ljust(30)} #{info[:content_type].to_s.ljust(35)} #{info[:content] ? '+' : ' '}"
|
60
|
+
}
|
61
|
+
exit 0
|
62
|
+
}
|
63
|
+
|
64
|
+
op.on( "--spoof-src-addr=SOURCE_ADDRESS", String,
|
65
|
+
"Spoof source IP address. (Must be run as root.)"
|
66
|
+
) { |v|
|
67
|
+
opts[:spoof_src_addr] = v
|
68
|
+
}
|
69
|
+
|
70
|
+
op.on_tail( "-v", "--verbose", "Increase verbosity level. Can be repeated." ) { |v|
|
71
|
+
opts[:verbosity] ||= 0
|
72
|
+
opts[:verbosity] += 1
|
73
|
+
}
|
74
|
+
|
75
|
+
op.on_tail("-?", "-h", "--help", "Show this help message." ) {
|
76
|
+
puts op
|
77
|
+
exit 0
|
78
|
+
}
|
79
|
+
|
80
|
+
}
|
81
|
+
begin
|
82
|
+
opts_parser.parse!
|
83
|
+
rescue ::OptionParser::ParseError => e
|
84
|
+
$stderr.puts e.message
|
85
|
+
$stderr.puts opts_parser
|
86
|
+
exit 1
|
87
|
+
end
|
88
|
+
|
89
|
+
opts[:host] = ::ARGV[0]
|
90
|
+
|
91
|
+
if ! opts[:host]
|
92
|
+
$stderr.puts "Missing host argument."
|
93
|
+
$stderr.puts opts_parser
|
94
|
+
exit 1
|
95
|
+
end
|
96
|
+
|
97
|
+
if opts[:event] && opts[:event_type]
|
98
|
+
$stderr.puts "Event and event type arguments don't make sense together."
|
99
|
+
$stderr.puts opts_parser
|
100
|
+
exit 1
|
101
|
+
end
|
102
|
+
|
103
|
+
opts = opts_defaults.merge( opts )
|
104
|
+
opts[:domain] = opts[:host]
|
105
|
+
|
106
|
+
if opts[:event_type]
|
107
|
+
et = ::SipNotify.event_templates[ opts[:event_type].to_sym ]
|
108
|
+
if ! et
|
109
|
+
$stderr.puts "Event type not found: #{opts[:event_type].inspect}"
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
opts[:event] = et[:event]
|
113
|
+
opts[:content_type] = et[:content_type]
|
114
|
+
opts[:content] = et[:content]
|
115
|
+
end
|
116
|
+
|
117
|
+
begin
|
118
|
+
::SipNotify.send_variations( opts[:host], {
|
119
|
+
:port => opts[:port],
|
120
|
+
:user => opts[:user],
|
121
|
+
:domain => opts[:domain],
|
122
|
+
:verbosity => opts[:verbosity],
|
123
|
+
:via_rport => true,
|
124
|
+
:event => opts[:event],
|
125
|
+
:content_type => opts[:content_type],
|
126
|
+
:content => opts[:content],
|
127
|
+
:spoof_src_addr => opts[:spoof_src_addr],
|
128
|
+
})
|
129
|
+
rescue ::SipNotifyError => e
|
130
|
+
$stderr.puts "Error: #{e.message}"
|
131
|
+
exit 1
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# Local Variables:
|
136
|
+
# mode: ruby
|
137
|
+
# End:
|
138
|
+
|
data/lib/sip_notify.rb
ADDED
@@ -0,0 +1,855 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Send SIP "NOTIFY" "check-sync" events.
|
4
|
+
|
5
|
+
require 'socket'
|
6
|
+
require 'securerandom'
|
7
|
+
require 'rbconfig'
|
8
|
+
require 'bindata'
|
9
|
+
|
10
|
+
|
11
|
+
class SipNotifyError < RuntimeError; end
|
12
|
+
|
13
|
+
class SipNotify
|
14
|
+
|
15
|
+
REQUEST_METHOD = 'NOTIFY'.freeze
|
16
|
+
VIA_BRANCH_TOKEN_MAGIC = 'z9hG4bK'.freeze # http://tools.ietf.org/html/rfc3261#section-8.1.1.7
|
17
|
+
|
18
|
+
# Whether the length in a raw IP packet must be little-endian
|
19
|
+
# (i.e. native-endian) and the Kernel auto-reverses the value.
|
20
|
+
# IP spec. says big-endian (i.e. network order).
|
21
|
+
RAW_IP_PKT_LITTLE_ENDIAN_LENGTH = !! ::RbConfig::CONFIG['host_os'].to_s.match( /\A darwin/xi )
|
22
|
+
|
23
|
+
# Whether the fragment offset in a raw IP packet must be
|
24
|
+
# little-endian (i.e. native-endian) and the Kernel
|
25
|
+
# auto-reverses the value. IP spec. says big-endian (i.e.
|
26
|
+
# network order).
|
27
|
+
RAW_IP_PKT_LITTLE_ENDIAN_FRAG_OFF = !! ::RbConfig::CONFIG['host_os'].to_s.match( /\A darwin/xi )
|
28
|
+
|
29
|
+
SOCK_OPT_LEVEL_IP = (
|
30
|
+
if defined?( ::Socket::SOL_IP )
|
31
|
+
::Socket::SOL_IP # Linux
|
32
|
+
else
|
33
|
+
::Socket::IPPROTO_IP # Solaris, BSD, Darwin
|
34
|
+
end
|
35
|
+
)
|
36
|
+
|
37
|
+
# Pre-defined event types.
|
38
|
+
#
|
39
|
+
EVENT_TEMPLATES = {
|
40
|
+
|
41
|
+
# Compatibility.
|
42
|
+
# The most widely implemented event:
|
43
|
+
:'compat-check-cfg' => { :event => 'check-sync' },
|
44
|
+
|
45
|
+
# Snom:
|
46
|
+
:'snom-check-cfg' => { :event => 'check-sync;reboot=false' },
|
47
|
+
:'snom-reboot' => { :event => 'reboot' },
|
48
|
+
|
49
|
+
# Polycom:
|
50
|
+
# In the Polycom's sip.cfg make sure that
|
51
|
+
# voIpProt.SIP.specialEvent.checkSync.alwaysReboot="0"
|
52
|
+
# (0 will only reboot if the files on the settings server have changed.)
|
53
|
+
:'polycom-check-cfg' => { :event => 'check-sync' },
|
54
|
+
:'polycom-reboot' => { :event => 'check-sync' },
|
55
|
+
|
56
|
+
# Aastra:
|
57
|
+
:'aastra-check-cfg' => { :event => 'check-sync' },
|
58
|
+
:'aastra-reboot' => { :event => 'check-sync' },
|
59
|
+
:'aastra-xml' => { :event => 'aastra-xml' }, # triggers the XML SIP NOTIFY action URI
|
60
|
+
|
61
|
+
# Sipura by Linksys by Cisco:
|
62
|
+
:'sipura-check-cfg' => { :event => 'resync' },
|
63
|
+
:'sipura-reboot' => { :event => 'reboot' },
|
64
|
+
:'sipura-get-report' => { :event => 'report' },
|
65
|
+
|
66
|
+
# Linksys by Cisco:
|
67
|
+
:'linksys-check-cfg' => { :event => 'restart_now' }, # warm restart
|
68
|
+
:'linksys-reboot' => { :event => 'reboot_now' }, # cold reboot
|
69
|
+
|
70
|
+
# Cisco:
|
71
|
+
# In order for "cisco-check-cfg"/"cisco-reboot" to work make
|
72
|
+
# sure the syncinfo.xml has a different sync number than the
|
73
|
+
# phone's current sync number (e.g. by setting the "sync"
|
74
|
+
# parameter to "0" and having "1" in syncinfo.xml). If that
|
75
|
+
# is the case the phone will reboot (no check-sync without
|
76
|
+
# reboot).
|
77
|
+
#
|
78
|
+
# http://dxr.mozilla.org/mozilla-central/media/webrtc/signaling/src/sipcc/core/sipstack/ccsip_task.c#l2358
|
79
|
+
# http://dxr.mozilla.org/mozilla-central/media/webrtc/signaling/src/sipcc/core/sipstack/h/ccsip_protocol.h#l196
|
80
|
+
# http://dxr.mozilla.org/mozilla-central/media/webrtc/signaling/src/sipcc/core/sipstack/h/ccsip_protocol.h#l224
|
81
|
+
# http://dxr.mozilla.org/mozilla-central/media/webrtc/signaling/src/sipcc/core/sipstack/ccsip_pmh.c#l4978
|
82
|
+
# https://issues.asterisk.org/jira/secure/attachment/32746/sip-trace-7941-9-1-1SR1.txt
|
83
|
+
# telnet: "debug sip-task"
|
84
|
+
|
85
|
+
:'cisco-check-cfg' => { :event => 'check-sync' },
|
86
|
+
:'cisco-reboot' => { :event => 'check-sync' },
|
87
|
+
|
88
|
+
# Phone must be in CCM (Cisco Call Manager) registration
|
89
|
+
# mode (TCP) for "service-control" events to work, and the
|
90
|
+
# firmware must be >= 8.0. 7905/7912/7949/7969.
|
91
|
+
|
92
|
+
# Causes the phone to unregister, request config file
|
93
|
+
# (SIP{mac}.cnf), register.
|
94
|
+
:'cisco-sc-restart-unregistered' => { :event => 'service-control',
|
95
|
+
:content_type => 'text/plain',
|
96
|
+
:content => [
|
97
|
+
'action=' + 'restart',
|
98
|
+
'RegisterCallId=' + '{' + '' + '}', # not registered
|
99
|
+
'ConfigVersionStamp=' + '{' + '0000000000000000' + '}',
|
100
|
+
'DialplanVersionStamp=' + '{' + '0000000000000000' + '}',
|
101
|
+
'SoftkeyVersionStamp=' + '{' + '0000000000000000' + '}',
|
102
|
+
'',
|
103
|
+
].join("\r\n"),
|
104
|
+
},
|
105
|
+
|
106
|
+
#:'cisco-sc-restart' => { :event => 'service-control',
|
107
|
+
# :content_type => 'text/plain',
|
108
|
+
# :content => [
|
109
|
+
# 'action=' + 'restart',
|
110
|
+
# # 'RegisterCallId=' + '{' + '${SIPPEER(${PEERNAME},regcallid)}' '}',
|
111
|
+
# 'RegisterCallId=' + '{0022555d-aa850002-a2b54180-bb702fe7@192.168.1.130}',
|
112
|
+
# #00119352-91f60002-1df7530e-4190441e@192.168.1.130
|
113
|
+
# #00119352-91f60041-4a7455b9-00e0f121@192.168.1.130
|
114
|
+
# 'ConfigVersionStamp=' + '{' + '0000000000000000' + '}',
|
115
|
+
# 'DialplanVersionStamp=' + '{' + '0000000000000000' + '}',
|
116
|
+
# 'SoftkeyVersionStamp=' + '{' + '0000000000000000' + '}',
|
117
|
+
# '',
|
118
|
+
# ].join("\r\n"),
|
119
|
+
#},
|
120
|
+
|
121
|
+
# Causes the phone to unregister and do a full reboot cycle.
|
122
|
+
:'cisco-sc-reset-unregistered' => { :event => 'service-control',
|
123
|
+
:content_type => 'text/plain',
|
124
|
+
:content => [
|
125
|
+
'action=' + 'reset',
|
126
|
+
'RegisterCallId=' + '{' + '' + '}', # not registered
|
127
|
+
'ConfigVersionStamp=' + '{' + '0000000000000000' + '}',
|
128
|
+
'DialplanVersionStamp=' + '{' + '0000000000000000' + '}',
|
129
|
+
'SoftkeyVersionStamp=' + '{' + '0000000000000000' + '}',
|
130
|
+
'',
|
131
|
+
].join("\r\n"),
|
132
|
+
},
|
133
|
+
|
134
|
+
#:'cisco-sc-reset' => { :event => 'service-control',
|
135
|
+
# :content_type => 'text/plain',
|
136
|
+
# :content => [
|
137
|
+
# 'action=' + 'reset',
|
138
|
+
# # 'RegisterCallId=' + '{' + '${SIPPEER(${PEERNAME},regcallid)}' '}',
|
139
|
+
# 'RegisterCallId=' + '{123}',
|
140
|
+
# 'ConfigVersionStamp=' + '{' + '0000000000000000' + '}',
|
141
|
+
# 'DialplanVersionStamp=' + '{' + '0000000000000000' + '}',
|
142
|
+
# 'SoftkeyVersionStamp=' + '{' + '0000000000000000' + '}',
|
143
|
+
# '',
|
144
|
+
# ].join("\r\n"),
|
145
|
+
#},
|
146
|
+
|
147
|
+
# Causes the phone to unregister, request dialplan
|
148
|
+
# (dialplan.xml) and config file (SIP{mac}.cnf), register.
|
149
|
+
# This is the action to send if the config "file" on the
|
150
|
+
# TFTP server changes.
|
151
|
+
:'cisco-sc-check-unregistered' => { :event => 'service-control',
|
152
|
+
:content_type => 'text/plain',
|
153
|
+
:content => [
|
154
|
+
'action=' + 'check-version',
|
155
|
+
'RegisterCallId=' + '{' + '' + '}', # not registered
|
156
|
+
'ConfigVersionStamp=' + '{' + '0000000000000000' + '}',
|
157
|
+
'DialplanVersionStamp=' + '{' + '0000000000000000' + '}',
|
158
|
+
'SoftkeyVersionStamp=' + '{' + '0000000000000000' + '}',
|
159
|
+
'',
|
160
|
+
].join("\r\n"),
|
161
|
+
},
|
162
|
+
|
163
|
+
#:'cisco-sc-apply-config' => { :event => 'service-control',
|
164
|
+
# :content_type => 'text/plain',
|
165
|
+
# :content => [
|
166
|
+
# 'action=' + 'apply-config',
|
167
|
+
# 'RegisterCallId=' + '{' + '' + '}', # not registered
|
168
|
+
# 'ConfigVersionStamp=' + '{' + '0000000000000000' + '}',
|
169
|
+
# 'DialplanVersionStamp=' + '{' + '0000000000000000' + '}',
|
170
|
+
# 'SoftkeyVersionStamp=' + '{' + '0000000000000000' + '}',
|
171
|
+
# 'FeatureControlVersionStamp=' + '{' + '0000000000000000' + '}',
|
172
|
+
# 'CUCMResult=' + '{' + 'config_applied' + '}', # "no_change" / "config_applied" / "reregister_needed"
|
173
|
+
# 'FirmwareLoadId=' + '{' + 'SIP70.8-4-0-28S' + '}',
|
174
|
+
# 'LoadServer=' + '{' + '192.168.1.97' + '}',
|
175
|
+
# 'LogServer=' + '{' + '192.168.1.97' + '}', # <ipv4 address or ipv6 address or fqdn> <port> // This is used for ppid
|
176
|
+
# 'PPID=' + '{' + 'disabled' + '}', # "enabled" / "disabled" // peer-to-peer upgrade
|
177
|
+
# '',
|
178
|
+
# ].join("\r\n"),
|
179
|
+
#},
|
180
|
+
|
181
|
+
#:'cisco-sc-call-preservation' => { :event => 'service-control',
|
182
|
+
# :content_type => 'text/plain',
|
183
|
+
# :content => [
|
184
|
+
# 'action=' + 'call-preservation',
|
185
|
+
# 'RegisterCallId=' + '{' + '' + '}', # not registered
|
186
|
+
# 'ConfigVersionStamp=' + '{' + '0000000000000000' + '}',
|
187
|
+
# 'DialplanVersionStamp=' + '{' + '0000000000000000' + '}',
|
188
|
+
# 'SoftkeyVersionStamp=' + '{' + '0000000000000000' + '}',
|
189
|
+
# '',
|
190
|
+
# ].join("\r\n"),
|
191
|
+
#},
|
192
|
+
|
193
|
+
# Grandstream:
|
194
|
+
:'grandstream-check-cfg' => { :event => 'sys-control' },
|
195
|
+
:'grandstream-reboot' => { :event => 'sys-control' },
|
196
|
+
:'grandstream-idle-screen-refresh' => { :event => 'x-gs-screen' },
|
197
|
+
|
198
|
+
# Gigaset (Pro Nxxx):
|
199
|
+
:'gigaset-check-cfg' => { :event => 'check-sync;reboot=false' },
|
200
|
+
:'gigaset-reboot' => { :event => 'check-sync;reboot=true' },
|
201
|
+
|
202
|
+
# Siemens (Enterprise Networks) OpenStage:
|
203
|
+
:'siemens-check-cfg' => { :event => 'check-sync;reboot=false' },
|
204
|
+
:'siemens-reboot' => { :event => 'check-sync;reboot=true' },
|
205
|
+
|
206
|
+
# Yealink:
|
207
|
+
:'yealink-check-cfg' => { :event => 'check-sync;reboot=true' }, #OPTIMIZE can do without reboot?
|
208
|
+
:'yealink-reboot' => { :event => 'check-sync;reboot=true' },
|
209
|
+
|
210
|
+
# Thomson (ST2030?):
|
211
|
+
:'thomson-check-cfg' => { :event => 'check-sync;reboot=false' },
|
212
|
+
:'thomson-reboot' => { :event => 'check-sync;reboot=true' },
|
213
|
+
:'thomson-talk' => { :event => 'talk' },
|
214
|
+
:'thomson-hold' => { :event => 'hold' },
|
215
|
+
|
216
|
+
# Misc:
|
217
|
+
#
|
218
|
+
|
219
|
+
:'mwi-clear-full' => { :event => 'message-summary',
|
220
|
+
:content_type => 'application/simple-message-summary',
|
221
|
+
:content => [
|
222
|
+
'Messages-Waiting: ' + 'no', # "yes"/"no"
|
223
|
+
# 'Message-Account: sip:voicemail@127.0.0.1',
|
224
|
+
'voice-message' + ': 0/0 (0/0)',
|
225
|
+
'fax-message' + ': 0/0 (0/0)',
|
226
|
+
'pager-message' + ': 0/0 (0/0)',
|
227
|
+
'multimedia-message' + ': 0/0 (0/0)',
|
228
|
+
'text-message' + ': 0/0 (0/0)',
|
229
|
+
'none' + ': 0/0 (0/0)',
|
230
|
+
'',
|
231
|
+
],#.join("\r\n"),
|
232
|
+
},
|
233
|
+
|
234
|
+
:'mwi-clear-simple' => { :event => 'message-summary',
|
235
|
+
:content_type => 'application/simple-message-summary',
|
236
|
+
:content => [
|
237
|
+
'Messages-Waiting: ' + 'no', # "yes"/"no"
|
238
|
+
# 'Message-Account: sip:voicemail@127.0.0.1',
|
239
|
+
'voice-message' + ': 0/0',
|
240
|
+
'',
|
241
|
+
],#.join("\r\n"),
|
242
|
+
},
|
243
|
+
|
244
|
+
:'mwi-test-full' => { :event => 'message-summary',
|
245
|
+
:content_type => 'application/simple-message-summary',
|
246
|
+
:content => [
|
247
|
+
'Messages-Waiting: ' + 'yes', # "yes"/"no"
|
248
|
+
# 'Message-Account: sip:voicemail@127.0.0.1',
|
249
|
+
'voice-message' + ': 3/4 (1/2)',
|
250
|
+
'fax-message' + ': 3/4 (1/2)',
|
251
|
+
'pager-message' + ': 3/4 (1/2)',
|
252
|
+
'multimedia-message' + ': 3/4 (1/2)',
|
253
|
+
'text-message' + ': 3/4 (1/2)',
|
254
|
+
'none' + ': 3/4 (1/2)',
|
255
|
+
'',
|
256
|
+
],#.join("\r\n"),
|
257
|
+
},
|
258
|
+
|
259
|
+
:'mwi-test-simple' => { :event => 'message-summary',
|
260
|
+
:content_type => 'application/simple-message-summary',
|
261
|
+
:content => [
|
262
|
+
'Messages-Waiting: ' + 'yes', # "yes"/"no"
|
263
|
+
# 'Message-Account: sip:voicemail@127.0.0.1',
|
264
|
+
'voice-message' + ': 3/4',
|
265
|
+
'',
|
266
|
+
],#.join("\r\n"),
|
267
|
+
},
|
268
|
+
|
269
|
+
}
|
270
|
+
|
271
|
+
def initialize( host, opts=nil )
|
272
|
+
re_initialize!( host, opts )
|
273
|
+
end
|
274
|
+
|
275
|
+
def re_initialize!( host, opts=nil )
|
276
|
+
@opts = {
|
277
|
+
:host => host,
|
278
|
+
:domain => host,
|
279
|
+
:port => 5060,
|
280
|
+
:user => nil,
|
281
|
+
:to_user => nil,
|
282
|
+
:verbosity => 0,
|
283
|
+
:via_rport => true,
|
284
|
+
:event => nil,
|
285
|
+
:content_type => nil,
|
286
|
+
:content => nil,
|
287
|
+
}.merge( opts || {} )
|
288
|
+
self
|
289
|
+
end
|
290
|
+
|
291
|
+
def self.event_templates
|
292
|
+
EVENT_TEMPLATES
|
293
|
+
end
|
294
|
+
|
295
|
+
# DSCP value (Differentiated Services Code Point).
|
296
|
+
#
|
297
|
+
def ip_dscp
|
298
|
+
@ip_dscp ||= 0b110_000 # == 48 == 0x30 == DSCP CS6 ~= IP Precedence 5
|
299
|
+
end
|
300
|
+
|
301
|
+
# IP ToS value (Type of Service).
|
302
|
+
#
|
303
|
+
def ip_tos
|
304
|
+
@ip_tos ||= (ip_dscp << 2)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Create a socket.
|
308
|
+
#
|
309
|
+
def socket
|
310
|
+
if @socket
|
311
|
+
if @socket_is_raw
|
312
|
+
if (! @opts[:spoof_src_addr]) || @opts[:spoof_src_addr].empty?
|
313
|
+
@socket = nil
|
314
|
+
end
|
315
|
+
else
|
316
|
+
if @opts[:spoof_src_addr]
|
317
|
+
@socket = nil
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
if @opts[:spoof_src_addr]
|
323
|
+
unless @raw_socket
|
324
|
+
#local_addr, local_port = * our_addr
|
325
|
+
#if @opts[:spoof_src_addr] != local_addr
|
326
|
+
puts "Spoofing source IP address: #{@opts[:spoof_src_addr].inspect}." if @opts[:verbosity] >= 1
|
327
|
+
@raw_socket = raw_socket
|
328
|
+
#end
|
329
|
+
end
|
330
|
+
@socket = @raw_socket
|
331
|
+
@socket_is_raw = true
|
332
|
+
return @raw_socket
|
333
|
+
end
|
334
|
+
|
335
|
+
unless @socket
|
336
|
+
begin
|
337
|
+
::BasicSocket.do_not_reverse_lookup = true
|
338
|
+
|
339
|
+
@socket = ::Socket.new( ::Socket::AF_INET, ::Socket::SOCK_DGRAM )
|
340
|
+
@socket.setsockopt( ::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1 )
|
341
|
+
@socket.setsockopt( SOCK_OPT_LEVEL_IP, ::Socket::IP_TOS, ip_tos )
|
342
|
+
@socket.setsockopt( SOCK_OPT_LEVEL_IP, ::Socket::IP_TTL, 255 ) # default 64
|
343
|
+
#@socket.settimeout( 1.0 )
|
344
|
+
|
345
|
+
rescue ::SystemCallError, ::SocketError, ::IOError => e
|
346
|
+
socket_destroy!
|
347
|
+
raise ::SipNotifyError.new( "Failed to create socket: #{e.message} (#{e.class.name})" )
|
348
|
+
end
|
349
|
+
|
350
|
+
begin
|
351
|
+
sock_addr = ::Socket.sockaddr_in( @opts[:port], @opts[:host] )
|
352
|
+
@socket.connect( sock_addr )
|
353
|
+
|
354
|
+
rescue ::SystemCallError, ::SocketError, ::IOError => e
|
355
|
+
socket_destroy!
|
356
|
+
raise ::SipNotifyError.new( "Failed to connect socket to %{addr}: #{e.message} (#{e.class.name})" % {
|
357
|
+
:addr => ip_addr_and_port_url_repr( @opts[:host], @opts[:port] ),
|
358
|
+
})
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
@socket_is_raw = false
|
363
|
+
return @socket
|
364
|
+
end
|
365
|
+
private :socket
|
366
|
+
|
367
|
+
# Close and unset the socket.
|
368
|
+
#
|
369
|
+
def socket_destroy!
|
370
|
+
@socket.close() if @socket && ! @socket.closed?
|
371
|
+
@socket = nil
|
372
|
+
end
|
373
|
+
|
374
|
+
# Create a raw socket.
|
375
|
+
#
|
376
|
+
def raw_socket
|
377
|
+
begin
|
378
|
+
::BasicSocket.do_not_reverse_lookup = true
|
379
|
+
|
380
|
+
#sock = ::Socket.new( ::Socket::PF_INET, ::Socket::SOCK_RAW, ::Socket::IPPROTO_RAW )
|
381
|
+
sock = ::Socket.new( ::Socket::PF_INET, ::Socket::SOCK_RAW, ::Socket::IPPROTO_UDP )
|
382
|
+
|
383
|
+
# Make sure IP_HDRINCL is set on the raw socket,
|
384
|
+
# otherwise the kernel would prepend outbound packets
|
385
|
+
# with an IP header.
|
386
|
+
# https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man4/ip.4.html
|
387
|
+
#
|
388
|
+
so = sock.getsockopt( SOCK_OPT_LEVEL_IP, ::Socket::IP_HDRINCL )
|
389
|
+
if so.bool == false || so.int == 0 || so.data == [0].pack('L')
|
390
|
+
#puts "IP_HDRINCL is supposed to be the default for IPPROTO_RAW."
|
391
|
+
# ... not on Darwin though.
|
392
|
+
sock.setsockopt( SOCK_OPT_LEVEL_IP, ::Socket::IP_HDRINCL, true )
|
393
|
+
end
|
394
|
+
|
395
|
+
sock.setsockopt( ::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1 )
|
396
|
+
|
397
|
+
rescue ::Errno::EPERM => e
|
398
|
+
$stderr.puts "Must be run as root."
|
399
|
+
raise ::SipNotifyError.new( "Failed to create socket: #{e.message} (#{e.class.name})" )
|
400
|
+
rescue ::SystemCallError, ::SocketError, ::IOError => e
|
401
|
+
raise ::SipNotifyError.new( "Failed to create socket: #{e.message} (#{e.class.name})" )
|
402
|
+
end
|
403
|
+
|
404
|
+
return sock
|
405
|
+
end
|
406
|
+
|
407
|
+
# The socket type.
|
408
|
+
#
|
409
|
+
# `nil` if no socket.
|
410
|
+
# 1 = Socket::SOCK_STREAM
|
411
|
+
# 2 = Socket::SOCK_DGRAM
|
412
|
+
# 3 = Socket::SOCK_RAW
|
413
|
+
# 4 = Socket::SOCK_RDM
|
414
|
+
# 5 = Socket::SOCK_SEQPACKET
|
415
|
+
#
|
416
|
+
def socket_type
|
417
|
+
@socket ? @socket.local_address.socktype : nil
|
418
|
+
end
|
419
|
+
|
420
|
+
# The UDP source port number to use for spoofed packets.
|
421
|
+
#
|
422
|
+
def spoof_src_port
|
423
|
+
# Note:
|
424
|
+
# Port 5060 is likely to cause the actual PBX/proxy receive
|
425
|
+
# responses for out NOTIFY requests.
|
426
|
+
# Port 0 is a valid port to use for UDP if responses are to
|
427
|
+
# be irgnored, but is likely to be taken for an invalid port
|
428
|
+
# number in devices.
|
429
|
+
65535
|
430
|
+
end
|
431
|
+
private :spoof_src_port
|
432
|
+
|
433
|
+
# Return out address.
|
434
|
+
#
|
435
|
+
def our_addr
|
436
|
+
if @opts[:spoof_src_addr]
|
437
|
+
@our_addr = [ @opts[:spoof_src_addr], spoof_src_port ]
|
438
|
+
end
|
439
|
+
|
440
|
+
unless @our_addr
|
441
|
+
our_sock_addr = socket.getsockname()
|
442
|
+
local_port, local_addr = * ::Socket.unpack_sockaddr_in( our_sock_addr )
|
443
|
+
@our_addr = [ local_addr, local_port ]
|
444
|
+
end
|
445
|
+
|
446
|
+
@our_addr
|
447
|
+
end
|
448
|
+
private :our_addr
|
449
|
+
|
450
|
+
# Returns an IP address (or hostname) in URL representation.
|
451
|
+
# I.e. an IPv6 address will be enclosed in "["..."]".
|
452
|
+
#
|
453
|
+
def self.ip_addr_url_repr( host )
|
454
|
+
host.include?(':') ? "[#{host}]" : host.to_s
|
455
|
+
end
|
456
|
+
|
457
|
+
# Shortcut as an instance method
|
458
|
+
#
|
459
|
+
def ip_addr_url_repr( host )
|
460
|
+
self.class.ip_addr_url_repr( host )
|
461
|
+
end
|
462
|
+
|
463
|
+
# Returns an IP address (or hostname) and port number in URL
|
464
|
+
# representation.
|
465
|
+
# I.e. an IPv6 address will be enclosed in "["..."]".
|
466
|
+
#
|
467
|
+
def self.ip_addr_and_port_url_repr( host, port )
|
468
|
+
"%{addr}:%{port}" % { :addr => ip_addr_url_repr( host ), :port => port }
|
469
|
+
end
|
470
|
+
|
471
|
+
# Shortcut as an instance method
|
472
|
+
#
|
473
|
+
def ip_addr_and_port_url_repr( host, port )
|
474
|
+
self.class.ip_addr_and_port_url_repr( host, port )
|
475
|
+
end
|
476
|
+
|
477
|
+
# Send the SIP NOTIFY message.
|
478
|
+
#
|
479
|
+
def send
|
480
|
+
begin
|
481
|
+
sip_msg = to_s
|
482
|
+
if @opts[:verbosity] >= 3
|
483
|
+
puts "-----------------------------------------------------------------{"
|
484
|
+
puts sip_msg.gsub( /\r\n/, "\n" )
|
485
|
+
puts "-----------------------------------------------------------------}"
|
486
|
+
end
|
487
|
+
|
488
|
+
socket # Touch socket
|
489
|
+
|
490
|
+
case socket_type
|
491
|
+
|
492
|
+
when ::Socket::SOCK_DGRAM
|
493
|
+
num_bytes_written = nil
|
494
|
+
2.times {
|
495
|
+
num_bytes_written = socket.write( sip_msg )
|
496
|
+
}
|
497
|
+
|
498
|
+
when ::Socket::SOCK_RAW
|
499
|
+
#local_addr, local_port = * our_addr
|
500
|
+
|
501
|
+
::BasicSocket.do_not_reverse_lookup = true
|
502
|
+
|
503
|
+
src_addr = @opts[:spoof_src_addr]
|
504
|
+
src_addr_info = (::Addrinfo.getaddrinfo( src_addr, 'sip', nil, :DGRAM, ::Socket::IPPROTO_UDP, ::Socket::AI_V4MAPPED || ::Socket::AI_ALL ) || []).select{ |a|
|
505
|
+
a.afamily == ::Socket::AF_INET
|
506
|
+
}.first
|
507
|
+
src_sock_addr = src_addr_info.to_sockaddr
|
508
|
+
|
509
|
+
src_addr_ipv4_packed = src_sock_addr[4,4]
|
510
|
+
src_port_packed = src_sock_addr[2,2]
|
511
|
+
|
512
|
+
dst_addr = @opts[:host]
|
513
|
+
dst_addr_info = (::Addrinfo.getaddrinfo( dst_addr, 'sip', nil, :DGRAM, ::Socket::IPPROTO_UDP, ::Socket::AI_V4MAPPED || ::Socket::AI_ALL ) || []).select{ |a|
|
514
|
+
a.afamily == ::Socket::AF_INET
|
515
|
+
}.first
|
516
|
+
dst_sock_addr = dst_addr_info.to_sockaddr
|
517
|
+
|
518
|
+
dst_addr_ipv4_packed = dst_sock_addr[4,4]
|
519
|
+
dst_port_packed = dst_sock_addr[2,2]
|
520
|
+
|
521
|
+
#udp_pkt = UdpPktBitStruct.new { |b|
|
522
|
+
# b.src_port = spoof_src_port
|
523
|
+
# b.dst_port = 5060
|
524
|
+
# b.body = sip_msg.to_s
|
525
|
+
# b.udp_len = b.length
|
526
|
+
# b.udp_sum = 0
|
527
|
+
#}
|
528
|
+
|
529
|
+
udp_pkt = UdpPktBinData.new
|
530
|
+
udp_pkt.src_port = spoof_src_port
|
531
|
+
udp_pkt.dst_port = 5060
|
532
|
+
udp_pkt.data = sip_msg.to_s
|
533
|
+
udp_pkt.len = 4 + udp_pkt.data.bytesize
|
534
|
+
udp_pkt.checksum = 0 # none. UDP checksum is optional.
|
535
|
+
|
536
|
+
#ip_pkt = IpPktBitStruct.new { |b|
|
537
|
+
# # ip_v and ip_hl are set for us by IpPktBitStruct class
|
538
|
+
# b.ip_tos = ip_tos
|
539
|
+
# b.ip_id = 0
|
540
|
+
# b.ip_off = 0
|
541
|
+
# b.ip_ttl = 255 # default: 64
|
542
|
+
# b.ip_p = ::Socket::IPPROTO_UDP
|
543
|
+
# b.ip_src = @opts[:spoof_src_addr]
|
544
|
+
# b.ip_dst = @opts[:host]
|
545
|
+
# b.body = udp_pkt.to_s
|
546
|
+
# b.ip_len = b.length
|
547
|
+
# b.ip_sum = 0 # Linux/Darwin will calculate this for us (QNX won't)
|
548
|
+
#}
|
549
|
+
|
550
|
+
ip_pkt = IpPktBinData.new
|
551
|
+
ip_pkt.hdr_len = 5 # 5 * 8 bytes == 20 bytes
|
552
|
+
ip_pkt.tos = ip_tos
|
553
|
+
ip_pkt.ident = 0 # kernel sets appropriate value
|
554
|
+
ip_pkt.flags = 0
|
555
|
+
frag_off = 0
|
556
|
+
if RAW_IP_PKT_LITTLE_ENDIAN_FRAG_OFF && frag_off != 0
|
557
|
+
ip_pkt.frag_os = [ frag_off ].pack('n').unpack('S').first
|
558
|
+
else
|
559
|
+
ip_pkt.frag_os = frag_off
|
560
|
+
end
|
561
|
+
ip_pkt.ttl = 255 # default: 64
|
562
|
+
ip_pkt.proto = ::Socket::IPPROTO_UDP
|
563
|
+
ip_pkt.src_addr = src_addr_ipv4_packed .unpack('N').first
|
564
|
+
ip_pkt.dst_addr = dst_addr_ipv4_packed .unpack('N').first
|
565
|
+
ip_pkt.data = udp_pkt.to_binary_s
|
566
|
+
#len = (ip_pkt.hdr_len * 8) + ip_pkt.data.bytesize
|
567
|
+
len = ip_pkt.to_binary_s.bytesize
|
568
|
+
if RAW_IP_PKT_LITTLE_ENDIAN_LENGTH
|
569
|
+
ip_pkt.len = [ len ].pack('n').unpack('S').first
|
570
|
+
else
|
571
|
+
ip_pkt.len = len
|
572
|
+
end
|
573
|
+
ip_pkt.checksum = 0 # Linux/Darwin will calculate this for us (QNX won't)
|
574
|
+
|
575
|
+
#puts "-" * 80,
|
576
|
+
# "UDP packet:",
|
577
|
+
# udp_pkt.inspect,
|
578
|
+
# "-" * 80,
|
579
|
+
# udp_pkt.to_binary_s.inspect,
|
580
|
+
# "-" * 80
|
581
|
+
|
582
|
+
#puts "-" * 80,
|
583
|
+
# "IP packet:",
|
584
|
+
# ip_pkt.inspect,
|
585
|
+
# "-" * 80,
|
586
|
+
# ip_pkt.to_binary_s.inspect,
|
587
|
+
# "-" * 80
|
588
|
+
|
589
|
+
sock_addr = ::Socket.sockaddr_in( @opts[:port], @opts[:host] )
|
590
|
+
|
591
|
+
# Send 2 times. (UDP is an unreliable transport.)
|
592
|
+
num_bytes_written = nil
|
593
|
+
2.times {
|
594
|
+
num_bytes_written = socket.send( ip_pkt.to_binary_s, 0, sock_addr )
|
595
|
+
}
|
596
|
+
|
597
|
+
else
|
598
|
+
raise ::SipNotifyError.new( "Socket type not supported." )
|
599
|
+
end
|
600
|
+
|
601
|
+
puts "Sent NOTIFY to #{@opts[:host]}." if @opts[:verbosity] >= 1
|
602
|
+
return num_bytes_written
|
603
|
+
|
604
|
+
rescue ::SystemCallError, ::SocketError, ::IOError => e
|
605
|
+
socket_destroy!
|
606
|
+
return false
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
# Generate a random token.
|
611
|
+
#
|
612
|
+
def self.random_token( num_bytes=5 )
|
613
|
+
::SecureRandom.random_bytes( num_bytes ).unpack('H*').first
|
614
|
+
end
|
615
|
+
|
616
|
+
# Shortcut as an instance method.
|
617
|
+
#
|
618
|
+
def random_token
|
619
|
+
self.class.random_token
|
620
|
+
end
|
621
|
+
private :random_token
|
622
|
+
|
623
|
+
# Build the SIP message.
|
624
|
+
#
|
625
|
+
def to_s
|
626
|
+
local_addr, local_port = * our_addr
|
627
|
+
local_ip_addr_and_port_url_repr = ip_addr_and_port_url_repr( local_addr, local_port )
|
628
|
+
puts "Local address is: #{local_ip_addr_and_port_url_repr}" if @opts[:verbosity] >= 2
|
629
|
+
|
630
|
+
now = ::Time.now()
|
631
|
+
|
632
|
+
transport = 'UDP' # "UDP"/"TCP"/"TLS"/"SCTP"
|
633
|
+
|
634
|
+
ra = 60466176 # 100000 (36)
|
635
|
+
rb = 2000000000 # x2qxvk (36)
|
636
|
+
|
637
|
+
via_branch_token = VIA_BRANCH_TOKEN_MAGIC + '_' + random_token()
|
638
|
+
|
639
|
+
ruri_scheme = 'sip' # "sip"/"sips"
|
640
|
+
if @opts[:user] == nil
|
641
|
+
ruri_userinfo = ''
|
642
|
+
elsif @opts[:user] == ''
|
643
|
+
ruri_userinfo = "#{@opts[:user]}@"
|
644
|
+
# This isn't a valid SIP Request-URI as the user must not be
|
645
|
+
# empty if the userinfo is present ("@").
|
646
|
+
else
|
647
|
+
ruri_userinfo = "#{@opts[:user]}@"
|
648
|
+
end
|
649
|
+
|
650
|
+
ruri_hostport = ip_addr_and_port_url_repr( @opts[:host], @opts[:port] )
|
651
|
+
ruri = "#{ruri_scheme}:#{ruri_userinfo}#{ruri_hostport}"
|
652
|
+
|
653
|
+
from_display_name = 'Provisioning'
|
654
|
+
from_scheme = 'sip'
|
655
|
+
from_user = '_'
|
656
|
+
from_userinfo = "#{from_user}@"
|
657
|
+
#from_hostport = domain # should be a domain
|
658
|
+
from_hostport = ip_addr_url_repr( local_addr )
|
659
|
+
from_uri = "#{from_scheme}:#{from_userinfo}#{from_hostport}"
|
660
|
+
from_tag_param_token = random_token()
|
661
|
+
|
662
|
+
to_scheme = 'sip'
|
663
|
+
#to_hostport = domain
|
664
|
+
to_hostport = ip_addr_url_repr( @opts[:host] )
|
665
|
+
if @opts[:to_user] != nil
|
666
|
+
to_userinfo = "#{@opts[:to_user]}@"
|
667
|
+
# Even for to_user == '' (which is invalid).
|
668
|
+
else
|
669
|
+
if @opts[:user] == nil
|
670
|
+
to_userinfo = ''
|
671
|
+
elsif @opts[:user] == ''
|
672
|
+
to_userinfo = '@'
|
673
|
+
# This isn't a valid SIP To-URI as the user must not be
|
674
|
+
# empty if the userinfo is present ("@").
|
675
|
+
else
|
676
|
+
to_userinfo = "#{@opts[:user]}@"
|
677
|
+
end
|
678
|
+
end
|
679
|
+
to_uri = "#{to_scheme}:#{to_userinfo}#{to_hostport}"
|
680
|
+
|
681
|
+
contact_display_name = from_display_name
|
682
|
+
contact_scheme = 'sip'
|
683
|
+
contact_user = '_'
|
684
|
+
contact_userinfo = "#{contact_user}@"
|
685
|
+
contact_hostport = local_ip_addr_and_port_url_repr
|
686
|
+
contact_uri = "#{contact_scheme}:#{contact_userinfo}#{contact_hostport}"
|
687
|
+
|
688
|
+
call_id = "#{random_token()}@#{local_ip_addr_and_port_url_repr}"
|
689
|
+
|
690
|
+
cseq_num = 102
|
691
|
+
cseq = "#{cseq_num} #{REQUEST_METHOD}"
|
692
|
+
|
693
|
+
max_forwards = 70
|
694
|
+
|
695
|
+
unless @opts[:event]
|
696
|
+
event_package = 'check-sync'
|
697
|
+
event_type = event_package + ''
|
698
|
+
@opts[:event] = "#{event_type};reboot=false"
|
699
|
+
end
|
700
|
+
|
701
|
+
if @opts[:content]
|
702
|
+
if @opts[:content].kind_of?( ::Array )
|
703
|
+
body = @opts[:content].map(& :to_s).join("\r\n")
|
704
|
+
else
|
705
|
+
body = @opts[:content].to_s
|
706
|
+
end
|
707
|
+
else
|
708
|
+
body = ''
|
709
|
+
end
|
710
|
+
body.encode!( ::Encoding::UTF_8, { :undef => :replace, :invalid => :replace })
|
711
|
+
|
712
|
+
sip_msg = [
|
713
|
+
'%{request_method} %{ruri} SIP/2.0',
|
714
|
+
'Via: SIP/2.0/%{transport} %{via_hostport}' +
|
715
|
+
';branch=%{via_branch_token}' +
|
716
|
+
'%{rport_param}',
|
717
|
+
'From: %{from_display_name} <%{from_uri}>' +
|
718
|
+
';tag=%{from_tag_param_token}',
|
719
|
+
'To: <%{to_uri}>',
|
720
|
+
'Contact: %{contact_display_name} <%{contact_uri}>',
|
721
|
+
'Call-ID: %{call_id}',
|
722
|
+
'CSeq: %{cseq}',
|
723
|
+
'Date: %{date}',
|
724
|
+
'Max-Forwards: %{max_forwards}',
|
725
|
+
# 'Allow: %{allow}',
|
726
|
+
'Subscription-State: %{subscription_state}',
|
727
|
+
'Event: %{event}',
|
728
|
+
(@opts[:content_type] ?
|
729
|
+
'Content-Type: %{content_type}' :
|
730
|
+
nil),
|
731
|
+
'Content-Length: %{content_length}',
|
732
|
+
'',
|
733
|
+
body,
|
734
|
+
].compact.join("\r\n") % {
|
735
|
+
:request_method => REQUEST_METHOD,
|
736
|
+
:ruri => ruri,
|
737
|
+
:transport => transport,
|
738
|
+
:via_hostport => local_ip_addr_and_port_url_repr,
|
739
|
+
:via_branch_token => via_branch_token,
|
740
|
+
:rport_param => (@opts[:via_rport] ? ';rport' : ''),
|
741
|
+
:from_display_name => from_display_name,
|
742
|
+
:from_uri => from_uri,
|
743
|
+
:from_tag_param_token => from_tag_param_token,
|
744
|
+
:to_uri => to_uri,
|
745
|
+
:contact_display_name => contact_display_name,
|
746
|
+
:contact_uri => contact_uri,
|
747
|
+
:call_id => call_id,
|
748
|
+
:cseq => cseq,
|
749
|
+
:date => now.utc.strftime('%c') +' GMT', # must be "GMT"
|
750
|
+
:max_forwards => max_forwards,
|
751
|
+
# :allow => [ 'ACK' ].join(', '),
|
752
|
+
:subscription_state => 'active',
|
753
|
+
:event => @opts[:event].to_s,
|
754
|
+
:content_type => @opts[:content_type].to_s.gsub(/\s+/, ' '),
|
755
|
+
:content_length => body.bytesize.to_s,
|
756
|
+
}
|
757
|
+
sip_msg.encode!( ::Encoding::UTF_8, { :undef => :replace, :invalid => :replace })
|
758
|
+
sip_msg
|
759
|
+
end
|
760
|
+
|
761
|
+
# Send variations of the SIP message.
|
762
|
+
#
|
763
|
+
def self.send_variations( host, opts=nil )
|
764
|
+
opts ||= {}
|
765
|
+
opts[:domain] ||= host
|
766
|
+
|
767
|
+
# Create a single SipNotify instance so it will reuse the
|
768
|
+
# socket.
|
769
|
+
notify = ::SipNotify.new( nil )
|
770
|
+
|
771
|
+
if opts[:user] && ! opts[:user].empty?
|
772
|
+
# Send NOTIFY with user:
|
773
|
+
puts "Sending NOTIFY with user ..." if opts[:verbosity] >= 1
|
774
|
+
notify.re_initialize!( host, opts.merge({
|
775
|
+
})).send
|
776
|
+
end
|
777
|
+
|
778
|
+
#if true
|
779
|
+
# Send NOTIFY without user:
|
780
|
+
puts "Sending NOTIFY without user ..." if opts[:verbosity] >= 1
|
781
|
+
notify.re_initialize!( host, opts.merge({
|
782
|
+
:user => nil,
|
783
|
+
})).send
|
784
|
+
#end
|
785
|
+
|
786
|
+
#if true
|
787
|
+
# Send invalid NOTIFY with empty user ("") in Request-URI:
|
788
|
+
puts "Sending invalid NOTIFY with empty user ..." if opts[:verbosity] >= 1
|
789
|
+
notify.re_initialize!( host, opts.merge({
|
790
|
+
:user => '',
|
791
|
+
})).send
|
792
|
+
#end
|
793
|
+
|
794
|
+
#if true
|
795
|
+
# Send invalid NOTIFY with empty user ("") in Request-URI
|
796
|
+
# but non-empty user in To-URI:
|
797
|
+
# (for Cisco 7960/7940)
|
798
|
+
puts "Sending invalid NOTIFY with empty user in Request-URI but non-empty user in To-URI ..." if opts[:verbosity] >= 1
|
799
|
+
notify.re_initialize!( host, opts.merge({
|
800
|
+
:user => '',
|
801
|
+
:to_user => '_',
|
802
|
+
})).send
|
803
|
+
#end
|
804
|
+
|
805
|
+
notify.socket_destroy!
|
806
|
+
|
807
|
+
nil
|
808
|
+
end
|
809
|
+
|
810
|
+
end
|
811
|
+
|
812
|
+
|
813
|
+
# An IP paket.
|
814
|
+
#
|
815
|
+
class IpPktBinData < BinData::Record
|
816
|
+
endian :big
|
817
|
+
bit4 :vers, :value => 4 # IP version
|
818
|
+
bit4 :hdr_len # header length
|
819
|
+
uint8 :tos # TOS / DiffServ
|
820
|
+
uint16 :len # total length
|
821
|
+
uint16 :ident # identifier
|
822
|
+
bit3 :flags # flags
|
823
|
+
bit13 :frag_os # fragment offset
|
824
|
+
uint8 :ttl # time-to-live
|
825
|
+
uint8 :proto # IP protocol
|
826
|
+
uint16 :checksum # checksum
|
827
|
+
uint32 :src_addr # source IP address
|
828
|
+
uint32 :dst_addr # destination IP address
|
829
|
+
string :options, :read_length => :options_length_in_bytes
|
830
|
+
string :data, :read_length => lambda { total_length - header_length_in_bytes }
|
831
|
+
|
832
|
+
def header_length_in_bytes
|
833
|
+
hdr_len * 4
|
834
|
+
end
|
835
|
+
|
836
|
+
def options_length_in_bytes
|
837
|
+
header_length_in_bytes - 20
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
|
842
|
+
# An UDP packet (payload in an IP packet).
|
843
|
+
#
|
844
|
+
class UdpPktBinData < BinData::Record
|
845
|
+
endian :big
|
846
|
+
uint16 :src_port
|
847
|
+
uint16 :dst_port
|
848
|
+
uint16 :len
|
849
|
+
uint16 :checksum
|
850
|
+
string :data, :read_length => :len
|
851
|
+
end
|
852
|
+
|
853
|
+
|
854
|
+
# vim:noexpandtab:
|
855
|
+
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sip-notify
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Philipp Kempgen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bindata
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.4.5
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.4.5
|
30
|
+
description: Sends SIP NOTIFY events ("check-sync" etc.).
|
31
|
+
email:
|
32
|
+
executables:
|
33
|
+
- sip-notify
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- lib/sip_notify.rb
|
38
|
+
- bin/sip-notify
|
39
|
+
- README.md
|
40
|
+
homepage: https://github.com/philipp-kempgen/sip-notify
|
41
|
+
licenses: []
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.8.25
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: Sends SIP NOTIFY events.
|
64
|
+
test_files: []
|
65
|
+
has_rdoc:
|