Class: OverSIP::WebSocket::TcpServer
- Inherits:
-
EM::Connection
- Object
- EM::Connection
- OverSIP::WebSocket::TcpServer
- Includes:
- Logger, DefaultPolicy
- Defined in:
- lib/oversip/websocket/listeners/tcp_server.rb
Direct Known Subclasses
Constant Summary collapse
- HEADERS_MAX_SIZE =
Max size (bytes) of the buffered data when receiving message headers (avoid DoS attacks).
2048
- WS_MAGIC_GUID_04 =
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11".freeze
- WS_VERSIONS =
{ 7=>true, 8=>true, 13=>true }
- HDR_SUPPORTED_WEBSOCKET_VERSIONS =
[ "X-Supported-WebSocket-Versions: #{WS_VERSIONS.keys.join(", ")}" ]
Constants included from Logger
Logger::SYSLOG_POSIXMQ_MAPPING
Class Attribute Summary collapse
-
.ip_type ⇒ Object
Returns the value of attribute ip_type.
-
.transport ⇒ Object
Returns the value of attribute transport.
Instance Attribute Summary collapse
-
#connection_log_id ⇒ Object
readonly
Returns the value of attribute connection_log_id.
-
#remote_ip ⇒ Object
readonly
Returns the value of attribute remote_ip.
-
#remote_ip_type ⇒ Object
readonly
Returns the value of attribute remote_ip_type.
-
#remote_port ⇒ Object
readonly
Returns the value of attribute remote_port.
-
#ws_app_klass ⇒ Object
Returns the value of attribute ws_app_klass.
-
#ws_protocol ⇒ Object
Returns the value of attribute ws_protocol.
Instance Method Summary collapse
- #accept_ws_handshake ⇒ Object
- #check_http_request ⇒ Object
- #http_reject(status_code, reason_phrase = nil, extra_headers = nil) ⇒ Object
- #ignore_incoming_data ⇒ Object
-
#initialize ⇒ TcpServer
constructor
A new instance of TcpServer.
- #parse_http_headers ⇒ Object
- #post_connection ⇒ Object
- #receive_data(data) ⇒ Object
- #remote_desc(force = nil) ⇒ Object
- #unbind(cause = nil) ⇒ Object
Methods included from DefaultPolicy
#check_hostport, #check_origin, #check_request_uri
Methods included from Logger
close, #fatal, fg_system_msg2str, init_logger_mq, load_methods, #log_id, syslog_system_msg2str, syslog_user_msg2str
Constructor Details
#initialize ⇒ TcpServer
Returns a new instance of TcpServer.
26 27 28 29 30 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 26 def initialize @http_parser = ::OverSIP::WebSocket::HttpRequestParser.new @buffer = ::IO::Buffer.new @state = :init end |
Class Attribute Details
.ip_type ⇒ Object
Returns the value of attribute ip_type.
19 20 21 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 19 def ip_type @ip_type end |
.transport ⇒ Object
Returns the value of attribute transport.
19 20 21 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 19 def transport @transport end |
Instance Attribute Details
#connection_log_id ⇒ Object (readonly)
Returns the value of attribute connection_log_id.
23 24 25 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 23 def connection_log_id @connection_log_id end |
#remote_ip ⇒ Object (readonly)
Returns the value of attribute remote_ip.
23 24 25 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 23 def remote_ip @remote_ip end |
#remote_ip_type ⇒ Object (readonly)
Returns the value of attribute remote_ip_type.
23 24 25 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 23 def remote_ip_type @remote_ip_type end |
#remote_port ⇒ Object (readonly)
Returns the value of attribute remote_port.
23 24 25 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 23 def remote_port @remote_port end |
#ws_app_klass ⇒ Object
Returns the value of attribute ws_app_klass.
22 23 24 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 22 def ws_app_klass @ws_app_klass end |
#ws_protocol ⇒ Object
Returns the value of attribute ws_protocol.
22 23 24 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 22 def ws_protocol @ws_protocol end |
Instance Method Details
#accept_ws_handshake ⇒ Object
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 222 def accept_ws_handshake sec_websocket_accept = Digest::SHA1::base64digest @http_request.hdr_sec_websocket_key + WS_MAGIC_GUID_04 extra_headers = [ "Upgrade: websocket", "Connection: Upgrade", "Sec-WebSocket-Accept: #{sec_websocket_accept}" ] if @websocket_protocol_negotiated extra_headers << "Sec-WebSocket-Protocol: #{@ws_protocol}" end if @websocket_extensions extra_headers << "Sec-WebSocket-Extensions: #{@websocket_extensions.to_s}" end @http_request.reply 101, nil, extra_headers # Set the WS framming layer and WS application layer. @ws_framing = ::OverSIP::WebSocket::WsFraming.new(self, @buffer) @ws_framing.ws_app = @ws_app_klass.new(self, @ws_framing) @state = :websocket_frames true end |
#check_http_request ⇒ Object
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 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 145 def check_http_request # HTTP method must be GET. if @http_request.http_method != :GET log_system_notice "rejecting HTTP #{@http_request.http_method} request => 405" http_reject 405 return false end # "Sec-WebSocket-Version" must be 8. unless WS_VERSIONS[@http_request.hdr_sec_websocket_version] if @http_request.hdr_sec_websocket_version log_system_notice "WebSocket version #{@http_request.hdr_sec_websocket_version} not implemented => 426" else log_system_notice "WebSocket version header not present => 426" end http_reject 426, nil, HDR_SUPPORTED_WEBSOCKET_VERSIONS return false end # Connection header must include "upgrade". unless @http_request.hdr_connection and @http_request.hdr_connection.include? "upgrade" log_system_notice "Connection header must include \"upgrade\" => 400" http_reject 400, "Connection header must include \"upgrade\"" return false end # "Upgrade: websocket" is required. unless @http_request.hdr_upgrade == "websocket" log_system_notice "Upgrade header must be \"websocket\" => 400" http_reject 400, "Upgrade header must be \"websocket\"" return false end # Sec-WebSocket-Key is required. unless @http_request.hdr_sec_websocket_key log_system_notice "Sec-WebSocket-Key header not present => 400" http_reject 400, "Sec-WebSocket-Key header not present" return false end # Check Sec-WebSocket-Protocol. if @http_request.hdr_sec_websocket_protocol if @http_request.hdr_sec_websocket_protocol.include? @ws_protocol @websocket_protocol_negotiated = true else log_system_notice "Sec-WebSocket-Protocol does not contain a supported protocol but #{@http_request.hdr_sec_websocket_protocol} => 501" http_reject 501, "No Suitable WebSocket Protocol" return false end end # Check WebSocket policy. unless check_hostport(@http_request.host, @http_request.port) log_system_notice "host/port policy not satisfied (host=#{@http_request.host.inspect}, port=#{@http_request.port.inspect}) => 403" http_reject 403, "request host/port not satisfied" return false end unless check_origin(@http_request.hdr_origin) log_system_notice "'Origin' policy not satisfied (origin=#{@http_request.hdr_origin.inspect}) => 403" http_reject 403, "request 'Origin' not satisfied" return false end unless check_request_uri(@http_request.uri_path, @http_request.uri_query) log_system_notice "request URI path/query not satisfied (path=#{@http_request.uri_path.inspect}, query=#{@http_request.uri_query.inspect}) => 403" http_reject 403, "request URI path/query not satisfied" return false end @state = :accept_ws_handshake true end |
#http_reject(status_code, reason_phrase = nil, extra_headers = nil) ⇒ Object
250 251 252 253 254 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 250 def http_reject status_code, reason_phrase=nil, extra_headers=nil @http_request.reply(status_code, reason_phrase, extra_headers) close_connection_after_writing @state = :ignore end |
#ignore_incoming_data ⇒ Object
257 258 259 260 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 257 def ignore_incoming_data @state = :ignore # The WS application needs to set the connection in :ignore state # after sending a close frame to the client. end |
#parse_http_headers ⇒ Object
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 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 110 def parse_http_headers return false if @buffer.empty? # Parse the currently buffered data. If parsing fails @http_parser_nbytes gets nil value. unless @http_parser_nbytes = @http_parser.execute(@http_request, @buffer.to_str, @http_parser_nbytes) log_system_warn "parsing error: \"#{@http_parser.error}\"" close_connection_after_writing @state = :ignore return false end # Avoid flood attacks in TCP (very long headers). if @http_parser_nbytes > HEADERS_MAX_SIZE log_system_warn "DoS attack detected: headers size exceedes #{HEADERS_MAX_SIZE} bytes, closing connection with #{remote_desc}" close_connection @state = :ignore return false end return false unless @http_parser.finished? # Clear parsed data from the buffer. @buffer.read(@http_parser_nbytes) @http_request.connection = self @http_request.transport = self.class.transport @http_request.source_ip = @remote_ip @http_request.source_port = @remote_port @http_request.source_ip_type = @remote_ip_type ||= self.class.ip_type @state = :check_http_request true end |
#post_connection ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 33 def post_connection begin @remote_port, @remote_ip = ::Socket.unpack_sockaddr_in(get_peername) rescue => e log_system_error "error obtaining remote IP/port (#{e.class}: #{e.}), closing connection" close_connection @state = :ignore return end @connection_log_id = ::SecureRandom.hex(4) log_system_info "connection opened from " << remote_desc end |
#receive_data(data) ⇒ Object
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 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 75 def receive_data data @state == :ignore and return @buffer << data while (case @state when :init @http_request = ::OverSIP::WebSocket::HttpRequest.new @http_parser.reset @http_parser_nbytes = 0 @bytes_remaining = 0 @state = :http_headers when :http_headers parse_http_headers when :check_http_request check_http_request when :accept_ws_handshake accept_ws_handshake when :websocket_frames return false if @buffer.size.zero? @ws_framing.receive_data false when :ignore false end) end # while end |
#remote_desc(force = nil) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 48 def remote_desc force=nil if force @remote_desc = case @remote_ip_type when :ipv4 ; "#{@remote_ip}:#{@remote_port.to_s}" when :ipv6 ; "[#{@remote_ip}]:#{@remote_port.to_s}" end else @remote_desc ||= case self.class.ip_type when :ipv4 ; "#{@remote_ip}:#{@remote_port.to_s}" when :ipv6 ; "[#{@remote_ip}]:#{@remote_port.to_s}" end end end |
#unbind(cause = nil) ⇒ Object
63 64 65 66 67 68 69 70 71 72 |
# File 'lib/oversip/websocket/listeners/tcp_server.rb', line 63 def unbind cause=nil @local_closed = true if cause == ::Errno::ETIMEDOUT log_msg = "connection from #{remote_desc} " log_msg << ( @local_closed ? "locally closed" : "remotely closed" ) log_msg << " (cause: #{cause.inspect})" if cause log_system_debug log_msg if $oversip_debug @ws_framing.tcp_closed if @ws_framing end |