Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 486 lines (388 sloc) 18.984 kb
b567b74 @mranney Add simple README
authored
1 node_pcap
2 =========
3
58701b8 @mranney Add some actual documentation and some updated examples.
authored
4 This is a set of bindings from `libpcap` to node as well as some useful libraries to decode, print, and
5 analyze packets. `libpcap` is a packet capture library used by programs like `tcpdump` and `wireshark`.
6 It has been tested on OSX and Linux.
b567b74 @mranney Add simple README
authored
7
58701b8 @mranney Add some actual documentation and some updated examples.
authored
8 Sadly, `node_pcap` is _not done_ yet. While it is incomplete, it is still already useful for capturing
0ebc243 @mranney Improve error messages and documentation.
authored
9 and manipulating packets in JavaScript. The best example of this so far is `http_trace`, described below.
58701b8 @mranney Add some actual documentation and some updated examples.
authored
10
11 ## Why?
12
13 There are already many tools for capturing, decoding, and analyzing packets. Many of them are thoroughly
14 tested and very fast. Why would anybody want to capture and manipulate packets in JavaScript? A few reasons:
15
16 * JavaScript makes writing event-based programs very natural. Each packet that is captured generates an
17 event, and as higher level protocols are decoded, they might generate events as well. Writing code to handle
18 these events is much easier and more readable with anonymous functions and closures.
19
20 * node makes handling binary data in JavaScript fast and efficient with its Buffer class. Decoding packets involves
21 a lot of binary slicing and dicing which can be awkward with JavaScript strings.
22
23 * Writing servers that capture packets, process them somehow, and then serve the processed data up in some way is
24 very straightforward in node.
25
0ebc243 @mranney Improve error messages and documentation.
authored
26 * Node has a very good HTTP parser that is used to progressively decode HTTP sessions.
b567b74 @mranney Add simple README
authored
27
3054c0e @mranney Add installation example
authored
28 ## Installation
29
30 You will need `libpcap` installed. Most OSX machines seem to have it. All major Linux distributions have it available
31 either by default or with a package like `libpcap-dev`.
32
878080a @mranney Update installation instructions.
authored
33 The easiest way to get `node_pcap` and its tools is with `npm`:
34
35 npm install pcap
36
37 If you want to hack on the source code, you can get it from github. Clone the repo like this:
3054c0e @mranney Add installation example
authored
38
39 git clone git://github.com/mranney/node_pcap.git
40
41 To compile the native code bindings, do this:
42
43 cd node_pcap
44 node-waf configure build
45
46 Assuming it built without errors, you should be able to run the examples and then write your own packet
47 capture programs.
48
b567b74 @mranney Add simple README
authored
49
06423e3 @mranney Update examples.
authored
50 ## Usage
b567b74 @mranney Add simple README
authored
51
58701b8 @mranney Add some actual documentation and some updated examples.
authored
52 There are several example programs that show how to use `node_pcap`. These examples are best documentation.
53 Try them out and see what they do.
54
35011ae @mranney Documentation updates for TCP_tracker events
authored
55 To use this library in your own program, `pcap.js` and `pcap_binding.node` must be in `NODE_PATH`. `npm`
0ebc243 @mranney Improve error messages and documentation.
authored
56 takes care of this automatically.
57
35011ae @mranney Documentation updates for TCP_tracker events
authored
58 ### Starting a capture session
59
60 To start a capture session, call `pcap.createSession` with an interface name and a pcap filter string:
58701b8 @mranney Add some actual documentation and some updated examples.
authored
61
0ebc243 @mranney Improve error messages and documentation.
authored
62 var pcap = require('pcap'),
58701b8 @mranney Add some actual documentation and some updated examples.
authored
63 pcap_session = pcap.createSession(interface, filter);
64
65 `interface` is the name of the interface on which to capture packets. If passed an empty string, `libpcap`
0ebc243 @mranney Improve error messages and documentation.
authored
66 will try to pick a "default" interface, which is often just the first one in some list and not what you want.
58701b8 @mranney Add some actual documentation and some updated examples.
authored
67
68 `fitler` is a pcap filter expression, see `pcap-filter(7)` for more information. An empty string will capture
69 all packets visible on the interface.
06423e3 @mranney Update examples.
authored
70
35011ae @mranney Documentation updates for TCP_tracker events
authored
71 Note that `node_pcap` always opens the interface in promiscuous mode, which generally requires running as root.
72 Unless you are recklessly roaming about as root already, you'll probably want to start your node program like this:
73
74 sudo node test.js
75
58701b8 @mranney Add some actual documentation and some updated examples.
authored
76 `pcap_session` is an `EventEmitter` that emits a `packet` event. The only argument to the callback will be a
77 `Buffer` object with the raw bytes returned by `libpcap`.
78
79 Listening for packets:
80
35011ae @mranney Documentation updates for TCP_tracker events
authored
81 pcap_session.on('packet', function (raw_packet) {
58701b8 @mranney Add some actual documentation and some updated examples.
authored
82 // do some stuff with a raw packet
06423e3 @mranney Update examples.
authored
83 });
b567b74 @mranney Add simple README
authored
84
35011ae @mranney Documentation updates for TCP_tracker events
authored
85 To convert `raw_packet` into a JavaScript object that is easy to work with, decode it:
58701b8 @mranney Add some actual documentation and some updated examples.
authored
86
87 var packet = pcap.decode.packet(raw_packet);
88
89 The protocol stack is exposed as a nested set of objects. For example, the TCP destination port is part of TCP
90 which is encapsulated within IP, which is encapsulated within a link layer. Access it like this:
91
92 packet.link.ip.tcp.dport
ad9d634 @mranney Update examples and show payload dump.
authored
93
58701b8 @mranney Add some actual documentation and some updated examples.
authored
94 This structure is easy to explore with `sys.inspect`.
ad9d634 @mranney Update examples and show payload dump.
authored
95
35011ae @mranney Documentation updates for TCP_tracker events
authored
96 ### TCP Analysis
97
98 TCP can be analyzed by feeding the packets into a `TCP_tracker` and then listening for `start` and `end` events.
99
100 var pcap = require('pcap'),
101 tcp_tracker = new pcap.TCP_tracker(),
102 pcap_session = pcap.createSession(interface, "ip proto \tcp");
103
104 tcp_tracker.on('start', function (session) {
105 console.log("Start of TCP session between " + session.src_name + " and " + session.dst_name);
106 });
107
108 tcp_tracker.on('end', function (session) {
109 console.log("End of TCP session between " + session.src_name + " and " + session.dst_name);
110 });
111
112 pcap_session.on('packet', function (raw_packet) {
113 var packet = pcap.decode.packet(raw_packet);
114 tcp_tracker.track_packet(packet);
115 });
116
117 You must only send IPv4 TCP packets to the TCP tracker. Explore the `session` object with `sys.inspect` to
118 see the wonderful things it can do for you. Hopefully the names of the properties are self-explanatory:
119
120 { src: '10.51.2.130:55965'
121 , dst: '75.119.207.0:80'
122 , syn_time: 1280425738896.771
123 , state: 'ESTAB'
124 , key: '10.51.2.130:55965-75.119.207.0:80'
125 , send_isn: 2869922608
126 , send_window_scale: 8
127 , send_packets: { '2869922609': 1280425738896.771 }
128 , send_acks: { '1063203923': 1280425738911.618 }
129 , send_retrans: {}
130 , send_next_seq: 2869922609
131 , send_acked_seq: null
132 , send_bytes_ip: 60
133 , send_bytes_tcp: 108
134 , send_bytes_payload: 144
135 , recv_isn: 1063203922
136 , recv_window_scale: 128
137 , recv_packets: { '1063203923': 1280425738911.536 }
138 , recv_acks: { '2869922609': 1280425738911.536 }
139 , recv_retrans: {}
140 , recv_next_seq: null
141 , recv_acked_seq: null
142 , recv_bytes_ip: 20
143 , recv_bytes_tcp: 40
144 , recv_bytes_payload: 0
145 , src_name: '10.51.2.130:55965'
146 , dst_name: '75.119.207.0:80'
147 , current_cap_time: 1280425738911.65
148
149 ### HTTP Analysis
150
151 The `TCP_tracker` also detects and decodes HTTP on all streams it receives. If HTTP is detected, several
152 new events will be emitted: `http_request`, `http_request_body`, `http_request_complete`, `http_response`,
153 `http_response_body`, and `http_response_complete`.
154
155 See `examples/http_trace` for an example of how to use these events to decode HTTP.
156
157 ### WebSocket Analysis
158
159 The `TCP_tracker` further detects and decodes WebSocket traffic on all streams it receives.
160
161 * `websocket_upgrade`: function(session, http)
162 * `websocket_message`: function(session, dir, message)
163
164 See `examples/http_trace` for an example of how to use these events to decode WebSocket.
ad9d634 @mranney Update examples and show payload dump.
authored
165
0ebc243 @mranney Improve error messages and documentation.
authored
166
167 ## Some Common Problems
168
169 ### TCP Segmentation Offload - TSO
170
171 TSO is a technique that modern operating systems use to offload the burden of IP/TCP header computation to
172 the network hardware. It also reduces the number of times that data is moved data between the kernel and the
173 network hardware. TSO saves CPU when sending data that is larger than a single IP packet.
174
175 This is amazing and wonderful, but it does make some kinds of packet sniffing more difficult. In many cases,
176 it is important to see the exact packets that are sent, but if the network hardware is sending the packets,
177 these are not available to `libpcap`. The solution is to disable TSO.
178
179 OSX:
180
181 sudo sysctl -w net.inet.tcp.tso=0
182
183 Linux (substitute correct interface name):
184
185 sudo ethtool -K eth0 tso off
186
187 The symptoms of needing to disable TSO are messages like, "Received ACK for packet we didn't see get sent".
188
189 ### IPv6
190
191 Sadly, `node_pcap` does not know how to decode IPv6 packets yet. Often when capturing traffic to `localhost`, IPv6 traffic
192 will arrive surprisingly, even though you were expecting IPv4. A common case is the hostname `localhost`, which many client programs will
193 resolve to the IPv6 address `::1` and then will try `127.0.0.1`. Until we get IPv6 decode support, a `libpcap` filter can be
194 set to only see IPv4 traffic:
195
35011ae @mranney Documentation updates for TCP_tracker events
authored
196 sudo http_trace lo0 "ip proto \tcp"
0ebc243 @mranney Improve error messages and documentation.
authored
197
198 The backslash is important. The pcap filter language has an ambiguity with the word "tcp", so by escaping it,
199 you'll get the correct interpretation for this case.
200
201 ### Dropped packets
202
203 There are several levels of buffering involved in capturing packets. Sometimes these buffers fill up, and
204 you'll drop packets. If this happens, it becomes difficult to reconstruct higher level protocols. The best
205 way to keep the buffers from filling up is to use pcap filters to only consider traffic that you need to decode.
206 The pcap filters are very efficient and run close to the kernel where they can process high packet rates.
207
208 If the pcap filters are set correctly and `libpcap` still drops packets, it is possible to increase `libpcap`'s
209 buffer size. At the moment, this requires changing `pcap_binding.cc`. Look for `pcap_set_buffer_size()` and
210 set to a larger value.
3663bd0 @mranney Add http_trace.js example.
authored
211
46b1ea7 @mranney Update docs for http_trace slightly and add image.
authored
212 ## examples/http_trace
213
6f43642 @mranney Support for WebSocket decoding and printing
authored
214 This is a handy standalone program that can help diagnose HTTP and WebSocket traffic.
3663bd0 @mranney Add http_trace.js example.
authored
215
216 The TCP tracker looks for HTTP at the beginning of every TCP connection. If found, all captured on this connection
46b1ea7 @mranney Update docs for http_trace slightly and add image.
authored
217 will be fed to node's HTTP parser and events will be generated. `http_trace` has listeners for these events and will
218 print out some helpful information.
219
6f43642 @mranney Support for WebSocket decoding and printing
authored
220 If a WebSocket upgrade is detected, `http_trace` will start looking for WebSocket messages on that connection.
221
a86e1fa @mranney Fix image URL.
authored
222 ![http_trace screenshot](http://ranney.com/httptrace.jpg)
46b1ea7 @mranney Update docs for http_trace slightly and add image.
authored
223
224
225 ## examples/simple_capture
58701b8 @mranney Add some actual documentation and some updated examples.
authored
226
35011ae @mranney Documentation updates for TCP_tracker events
authored
227 This program captures packets and prints them using the built in simple printer. Here's a sample of it's output.
58701b8 @mranney Add some actual documentation and some updated examples.
authored
228 In another window I ran `curl nodejs.org`.
229
230 mjr:~/work/node_pcap$ sudo node examples/simple_capture.js en1 ""
231 libpcap version 1.0.0
232 en0 no address
233 * en1 10.240.0.133/255.255.255.0
234 lo0 127.0.0.1/255.0.0.0
235 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 10.240.0.1 ARP request 10.240.0.133
236 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c 10.240.0.133 ARP reply 10.240.0.1 hwaddr 00:18:39:ff:f9:1c
237 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c 10.240.0.133:53808 -> 97.107.132.72:80 TCP len 64
238 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c 10.240.0.133:57052 -> 10.240.0.1:53 DNS question 133.0.240.10.in-addr.arpa PTR
239 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c 10.240.0.133:57052 -> 10.240.0.1:53 DNS question 72.132.107.97.in-addr.arpa PTR
240 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c 10.240.0.133:57052 -> 10.240.0.1:53 DNS question 1.0.240.10.in-addr.arpa PTR
241 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 10.240.0.1:53 -> 10.240.0.133:57052 DNS answer 133.0.240.10.in-addr.arpa PTR
242 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 10.240.0.1:53 -> rv-mjr2.ranney.com:57052 DNS answer 72.132.107.97.in-addr.arpa PTR
243 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 10.240.0.1:53 -> rv-mjr2.ranney.com:57052 DNS answer 1.0.240.10.in-addr.arpa PTR
244 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 60
245 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
246 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 196
247 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 52
248 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 1500
249 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 1500
250 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
251 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 1500
252 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
253 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 1500
254 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 1500
255 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
256 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 1500
257 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
258 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 1500
259 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 337
260 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
261 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
262 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
263 00:18:39:ff:f9:1c -> 00:1f:5b:ce:3e:29 tinyclouds.org:80 -> rv-mjr2.ranney.com:53808 TCP len 52
264 00:1f:5b:ce:3e:29 -> 00:18:39:ff:f9:1c rv-mjr2.ranney.com:53808 -> tinyclouds.org:80 TCP len 52
ad9d634 @mranney Update examples and show payload dump.
authored
265
266
267 ## Output from `session.findalldevs`:
b567b74 @mranney Add simple README
authored
268
269 [ { name: 'en0'
270 , addresses:
271 [ { addr: '10.51.2.183'
272 , netmask: '255.255.255.0'
273 , broadaddr: '10.51.2.255'
274 }
275 ]
276 }
277 , { name: 'fw0', addresses: [] }
278 , { name: 'en1', addresses: [] }
279 , { name: 'lo0'
280 , addresses: [ { addr: '127.0.0.1', netmask: '255.0.0.0' } ]
281 , flags: 'PCAP_IF_LOOPBACK'
282 }
283 ]
284
285
58701b8 @mranney Add some actual documentation and some updated examples.
authored
286 ### Deep decode of `curl nodejs.org`:
287
288 Running `sys.inspect` on the first three decoded packets of this TCP session.
b9a7d4a @mranney Update example output.
authored
289
290 First packet, TCP SYN:
291
292 { ethernet:
293 { dhost: '00:18:39:ff:f9:1c'
294 , shost: '00:1f:5b:ce:3e:29'
295 , ethertype: 2048
296 , ip:
297 { version: 4
298 , header_length: 5
299 , diffserv: 0
300 , total_length: 64
301 , identification: 49042
302 , flags: { reserved: 0, df: 1, mf: 0 }
303 , fragment_offset: 0
304 , ttl: 64
305 , protocol: 6
306 , header_checksum: 35325
307 , saddr: '10.240.0.133'
308 , daddr: '97.107.132.72'
309 , protocol_name: 'TCP'
310 , tcp:
311 { sport: 57230
312 , dport: 80
313 , seqno: 4179361823
314 , ackno: 1540242985
315 , data_offset: 11
316 , reserved: 0
317 , flags:
318 { cwr: 0
319 , ece: 0
320 , urg: 0
321 , ack: 0
322 , psh: 0
323 , rst: 0
324 , syn: 1
325 , fin: 0
326 }
327 , window_size: 65535
328 , checksum: 2601
329 , urgent_pointer: 0
330 , payload_offset: 78
331 , payload: { length: 0 }
332 }
333 }
334 }
335 , pcap_header:
336 { time: Sat, 22 May 2010 07:48:40 GMT
337 , tv_sec: 1274514520
338 , tv_usec: 820479
339 , caplen: 78
340 , len: 78
341 , link_type: 'LINKTYPE_ETHERNET'
342 }
b567b74 @mranney Add simple README
authored
343 }
b9a7d4a @mranney Update example output.
authored
344
345 Second packet, TCP SYN+ACK:
346
347 { ethernet:
348 { dhost: '00:1f:5b:ce:3e:29'
349 , shost: '00:18:39:ff:f9:1c'
350 , ethertype: 2048
351 , ip:
352 { version: 4
353 , header_length: 5
354 , diffserv: 32
355 , total_length: 60
356 , identification: 0
357 , flags: { reserved: 0, df: 1, mf: 0 }
358 , fragment_offset: 0
359 , ttl: 48
360 , protocol: 6
361 , header_checksum: 22900
362 , saddr: '97.107.132.72'
363 , daddr: '10.240.0.133'
364 , protocol_name: 'TCP'
365 , tcp:
366 { sport: 80
367 , dport: 57230
368 , seqno: 1042874392
369 , ackno: 973076764
370 , data_offset: 10
371 , reserved: 0
372 , flags:
373 { cwr: 0
374 , ece: 0
375 , urg: 0
376 , ack: 1
377 , psh: 0
378 , rst: 0
379 , syn: 1
380 , fin: 0
381 }
382 , window_size: 5792
383 , checksum: 35930
384 , urgent_pointer: 0
385 , payload_offset: 74
386 , payload: { length: 0 }
387 }
388 }
389 }
390 , pcap_header:
391 { time: Sat, 22 May 2010 07:48:40 GMT
392 , tv_sec: 1274514520
393 , tv_usec: 915980
394 , caplen: 74
395 , len: 74
396 , link_type: 'LINKTYPE_ETHERNET'
397 }
06423e3 @mranney Update examples.
authored
398 }
b9a7d4a @mranney Update example output.
authored
399
400 Third packet, TCP ACK, 3-way handshake is now complete:
401
402 { ethernet:
403 { dhost: '00:18:39:ff:f9:1c'
404 , shost: '00:1f:5b:ce:3e:29'
405 , ethertype: 2048
406 , ip:
407 { version: 4
408 , header_length: 5
409 , diffserv: 0
410 , total_length: 52
411 , identification: 39874
412 , flags: { reserved: 0, df: 1, mf: 0 }
413 , fragment_offset: 0
414 , ttl: 64
415 , protocol: 6
416 , header_checksum: 44505
417 , saddr: '10.240.0.133'
418 , daddr: '97.107.132.72'
419 , protocol_name: 'TCP'
420 , tcp:
421 { sport: 57230
422 , dport: 80
423 , seqno: 4179361823
424 , ackno: 1540242985
425 , data_offset: 8
426 , reserved: 0
427 , flags:
428 { cwr: 0
429 , ece: 0
430 , urg: 0
431 , ack: 1
432 , psh: 0
433 , rst: 0
434 , syn: 0
435 , fin: 0
436 }
437 , window_size: 65535
438 , checksum: 53698
439 , urgent_pointer: 0
440 , payload_offset: 66
441 , payload: { length: 0 }
442 }
443 }
444 }
445 , pcap_header:
446 { time: Sat, 22 May 2010 07:48:40 GMT
447 , tv_sec: 1274514520
448 , tv_usec: 916054
449 , caplen: 66
450 , len: 66
451 , link_type: 'LINKTYPE_ETHERNET'
452 }
453 }
454
0ebc243 @mranney Improve error messages and documentation.
authored
455 ## Help Wanted
456
457 I want to build up decoders and printers for all popular protocols. I'm already working on HTTP 802.11
458 "monitor" mode. If you want to write a decoder or printer for another protocol, let me know,
459 or just send me a patch.
460
461
31a0616 @mranney Decode ARP.
authored
462 ## LICENSE - "MIT License"
463
464 Copyright (c) 2010 Matthew Ranney, http://ranney.com/
465
466 Permission is hereby granted, free of charge, to any person
467 obtaining a copy of this software and associated documentation
468 files (the "Software"), to deal in the Software without
469 restriction, including without limitation the rights to use,
470 copy, modify, merge, publish, distribute, sublicense, and/or sell
471 copies of the Software, and to permit persons to whom the
472 Software is furnished to do so, subject to the following
473 conditions:
474
475 The above copyright notice and this permission notice shall be
476 included in all copies or substantial portions of the Software.
477
478 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
479 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
480 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
481 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
482 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
483 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
484 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
485 OTHER DEALINGS IN THE SOFTWARE.
Something went wrong with that request. Please try again.