Class: OverSIP::SIP::UdpConnection

Inherits:
Connection
  • Object
show all
Defined in:
lib/oversip/sip/listeners/udp_connection.rb

Direct Known Subclasses

IPv4UdpServer, IPv6UdpServer

Constant Summary

Constants included from MessageProcessor

MessageProcessor::MSG_TYPE

Instance Attribute Summary

Attributes inherited from Connection

#cvars

Instance Method Summary collapse

Methods inherited from Connection

#initialize, #open?, outbound_listener?, #receive_senderror, reliable_transport_listener?, #transport

Methods included from Logger

close, fg_system_msg2str, init_logger_mq, load_methods, #log_id, syslog_system_msg2str, syslog_user_msg2str

Constructor Details

This class inherits a constructor from OverSIP::SIP::Connection

Instance Method Details

#parse_messageObject



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/oversip/sip/listeners/udp_connection.rb', line 29

def parse_message
  return false if @buffer.empty?

  buffer_str = @buffer.to_str

  # Quikly ignore single CRLF (widely used by SIP UDP clients as keep-alive).
  if buffer_str == CRLF
    @buffer.clear
    @state = :init
    return false
  end

  begin
    source_port, source_ip = ::Socket.unpack_sockaddr_in(get_peername)
  rescue => e
    log_system_crit "error obtaining remote IP/port (#{e.class}: #{e.message})"
    @buffer.clear
    @state = :init
    return false
  end

  case stun_res = ::OverSIP::Stun.parse_request(buffer_str, source_ip, source_port)
    # Not a STUN request so continue with SIP parsing.
    when nil
    # An invalid STUN request, log it and drop it.
    when false
      log_system_debug "invalid STUN message received (not a valid STUN Binding Request)"  if $oversip_debug
      @buffer.clear
      @state = :init
      return false
    # A valid STUN Binding Request so we get a response to be sent.
    when ::String
      log_system_debug "STUN Binding Request received, replying to it"  if $oversip_debug
      send_data stun_res
      @buffer.clear
      @state = :init
      return false
    end

  # Parse the currently buffered data. If parsing fails @parser_nbytes gets nil value.
  unless @parser_nbytes = @parser.execute(buffer_str, @parser_nbytes)
    # The parsed data is invalid, however some data could be parsed so @parsed.parsed
    # can be:
    # - SIP::Request
    # - SIP::Response
    # - nil (the message is so wrong that cannot be neither a request or response).
    if wrong_message = @parser.parsed
      log_system_warn "parsing error for #{MSG_TYPE[wrong_message.class]}: \"#{@parser.error}\""
    else
      log_system_warn "parsing error: \"#{@parser.error}\""
    end

    @buffer.clear
    @state = :init
    return false
  end

  unless @parser.finished?
    # The parsing has not finished.
    # If UDP it's invalid as per RFC 3261 a UDP datagram MUST contain an entire
    # SIP request or response. Note we also allow double CRLF in UDP. If just a
    # single CRLF arrives ignore it and clear the buffer.
    # Maybe the parser has gone enought data to determine if the unfinished
    # message is a SIP request or response, log it if so.
    # If not, then @parser.parsed returns nil and nothing is logged.
    unfinished_msg = @parser.parsed
    log_system_warn "ignoring not finished #{MSG_TYPE[unfinished_msg.class]} via UDP" if
      unfinished_msg.is_a? ::OverSIP::SIP::Request or unfinished_msg.is_a? ::OverSIP::SIP::Response
    # Clear the buffer, set :init state and wait for new messages.
    @buffer.clear
    @state = :init
    return false
  end

  # At this point we've got a SIP::Request, SIP::Response or :outbound_keepalive symbol.
  @msg = @parser.parsed

  # Clear parsed data from the buffer.
  @buffer.read(@parser_nbytes)

  # Received data is a Outbound keealive (also allowed in UDP however). Reply single CRLF.
  if @msg == :outbound_keepalive
    log_system_debug "Outbound keepalive received, replying single CRLF"  if $oversip_debug
    # Reply a single CRLF over the same connection.
    send_data CRLF
    # If UDP there could be invalid data after double CRLF CRLF, just ignore it
    # and clear the buffer. Set :init state and return false so we leave receive_data()
    # method.
    @buffer.clear
    @state = :init
    return false
  end

  @parser.post_parsing

  # Here we have received the entire headers of a SIP request or response. Fill some
  # attributes.
  @msg.connection = self
  @msg.transport = :udp
  @msg.source_ip = source_ip
  @msg.source_port = source_port
  @msg.source_ip_type = self.class.ip_type

  unless valid_message? @parser
    @buffer.clear
    @state = :init
    return false
  end

  add_via_received_rport if @msg.request?

  unless check_via_branch
    @buffer.clear
    @state = :init
    return false
  end

  # Examine Content-Length header.
  # There is Content-Length header.
  if cl = @msg.content_length and cl > 0
    # Body size is correct. Read it and clear the buffer.
    # Set :finished state and return true so message will be processed.
    if cl == @buffer.size
      @msg.body = @buffer.read.force_encoding(::Encoding::UTF_8)
      @buffer.clear
      @state = :finished
      return true
    # In UDP the remaining data after headers must be the entire body
    # and fill exactly Content-Length bytes. If not it's invalid. Reply
    # 400 and clear the buffer.
    else
      if @msg.request?
        unless @msg.sip_method == :ACK
          log_system_warn "request body size doesn't match Content-Length => 400"
          @msg.reply 400, "Body size doesn't match Content-Length"
        else
          log_system_warn "ACK body size doesn't match Content-Length, ignoring it"
        end
      else
        log_system_warn "response body size doesn't match Content-Length, ignoring it"
      end
      @buffer.clear
      @state = :init
      return false
    end
  # No Content-Length header or 0 value. However it could occur that the datagram
  # contains remaining unuseful data, in this case reply 400. If not
  # set :finished state and return true so message will be processed.
  else
    # Ensure there is no more data in the buffer. If it's ok set :finished
    # state and return true so message will be processed.
    if @buffer.size.zero?
      @state = :finished
      return true
    # Non valid remaining data in the UDP datagram. Reply 400.
    else
      if @msg.request?
        log_system_warn "request contains body but Content-Length is zero or not present => 400"
        @msg.reply 400, "request contains body but Content-Length is zero or not present"
      else
        log_system_warn "response contains body but Content-Length is zero or not present, ignoring it"
      end
      @buffer.clear
      @state = :init
      return false
    end
  end

end

#receive_data(data) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/oversip/sip/listeners/udp_connection.rb', line 5

def receive_data data
  @buffer << data

  while (case @state
    when :init
      @parser.reset
      @parser_nbytes = 0
      @state = :message

    when :message
      parse_message

    when :finished
      if @msg.request?
        process_request
      else
        process_response
      end
      @state = :init
      false
    end)
  end  # while
end

#send_sip_msg(msg, ip, port) ⇒ Object

parse_headers



199
200
201
202
# File 'lib/oversip/sip/listeners/udp_connection.rb', line 199

def send_sip_msg msg, ip, port
  send_datagram msg, ip, port
  true
end

#unbind(cause = nil) ⇒ Object



205
206
207
208
209
# File 'lib/oversip/sip/listeners/udp_connection.rb', line 205

def unbind cause=nil
  unless $!
    log_system_crit "UDP socket closed!!! cause: #{cause.inspect}"
  end
end