2 * Copyright (C) 2012 Free Software Foundation, Inc.
4 * Author: Olga Smolenchuk
6 * This file is part of GnuTLS.
8 * The GnuTLS is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; either version 3 of
11 * the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>
23 #include <gnutls_errors.h>
24 #include <gnutls_int.h>
25 #include <gnutls_dtls.h>
26 #include <gnutls_record.h>
27 #include <ext/heartbeat.h>
28 #include <gnutls_extensions.h>
32 * gnutls_heartbeat_enable:
33 * @session: is a #gnutls_session_t structure.
34 * @type: one of the GNUTLS_HB_* flags
36 * This function will allow heartbeat messages to be
42 gnutls_heartbeat_enable (gnutls_session_t session
, unsigned int type
)
44 extension_priv_data_t epriv
;
47 _gnutls_ext_set_session_data (session
, GNUTLS_EXTENSION_HEARTBEAT
,
55 * textual policy description or NULL.
57 static inline const char *
58 _gnutls_heartbeat (unsigned policy
)
60 if (policy
& GNUTLS_HB_PEER_ALLOWED_TO_SEND
)
61 return "PEER ALLOWED TO SEND";
62 else if (policy
& GNUTLS_HB_PEER_NOT_ALLOWED_TO_SEND
)
63 return "PEER NOT ALLOWED TO SEND";
64 return "Unknown policy";
68 * gnutls_heartbeat_allowed:
69 * @session: is a #gnutls_session_t structure.
70 * @type: one of %GNUTLS_HB_LOCAL_ALLOWED_TO_SEND and %GNUTLS_HB_PEER_ALLOWED_TO_SEND
72 * This function will check whether heartbeats are allowed
73 * to be sent or received in this session.
75 * Returns: Non zero if heartbeats are allowed.
80 gnutls_heartbeat_allowed (gnutls_session_t session
, unsigned int type
)
82 extension_priv_data_t epriv
;
84 if (_gnutls_ext_get_session_data
85 (session
, GNUTLS_EXTENSION_HEARTBEAT
, &epriv
) < 0)
86 return 0; /* Not enabled */
88 if (type
== GNUTLS_HB_LOCAL_ALLOWED_TO_SEND
)
90 if (epriv
.num
& LOCAL_ALLOWED_TO_SEND
)
93 else if (epriv
.num
& GNUTLS_HB_PEER_ALLOWED_TO_SEND
)
100 heartbeat_allow_recv (gnutls_session_t session
)
102 return gnutls_heartbeat_allowed(session
, GNUTLS_HB_PEER_ALLOWED_TO_SEND
);
106 heartbeat_allow_send (gnutls_session_t session
)
108 return gnutls_heartbeat_allowed(session
, GNUTLS_HB_LOCAL_ALLOWED_TO_SEND
);
111 #define DEFAULT_PAYLOAD_SIZE 16
114 * heartbeat_send_data:
115 * @session: is a #gnutls_session_t structure.
116 * @data: contains the data to send.
117 * @data_size: is the length of the data.
118 * @start_timer: true if the heartbeat timer is to be started.
120 * This function has the similar semantics with gnutls_record_send() The only
121 * difference is that it uses GNUTLS_HEARTBEAT content type.
123 * This function send either HeartBeat request or response message
124 * with proper padding. It set timeout and timestamp without check - it's up to
125 * caller to make sure no messages are already in-flight and handle timeout expiration.
127 * Returns: The number of bytes sent, or a negative error code. The
128 * number of bytes sent might be less than @data_size. The maximum
129 * number of bytes this function can send in a single call depends
130 * on the negotiated maximum record size.
133 heartbeat_send_data (gnutls_session_t session
, const void *data
,
134 size_t data_size
, uint8_t type
)
137 gnutls_buffer_st response
;
138 uint8_t payload
[DEFAULT_PAYLOAD_SIZE
];
139 _gnutls_buffer_init (&response
);
141 ret
= gnutls_rnd (GNUTLS_RND_RANDOM
, payload
, DEFAULT_PAYLOAD_SIZE
);
143 return gnutls_assert_val(ret
);
145 BUFFER_APPEND (&response
, &type
, 1);
146 BUFFER_APPEND_PFX2 (&response
, data
, data_size
);
148 BUFFER_APPEND (&response
, payload
, DEFAULT_PAYLOAD_SIZE
);
149 ret
= _gnutls_send_int (session
, GNUTLS_HEARTBEAT
, -1,
150 EPOCH_WRITE_CURRENT
, response
.data
,
151 response
.length
, MBUFFER_FLUSH
);
153 _gnutls_record_log ("REC[%p]: HB sent: %d\n", session
, ret
);
155 _gnutls_buffer_clear (&response
);
160 * gnutls_heartbeat_ping:
161 * @session: is a #gnutls_session_t structure.
162 * @data_size: is the length of the ping payload.
163 * @max_tries: if flags is %GNUTLS_HEARTBEAT_WAIT then this sets the number of retransmissions. Use zero for indefinite (until timeout).
164 * @flags: if %GNUTLS_HEARTBEAT_WAIT then wait for pong or timeout instead of returning immediately.
166 * This function sends a ping to the peer. If the @flags is set
167 * to %GNUTLS_HEARTBEAT_WAIT then it waits for a reply from the peer.
169 * Note that it is highly recommended to use this function with the
170 * flag %GNUTLS_HEARTBEAT_WAIT, or you need to handle retransmissions
171 * and timeouts manually.
173 * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code.
178 gnutls_heartbeat_ping (gnutls_session_t session
, size_t data_size
,
179 unsigned int max_tries
, unsigned int flags
)
182 unsigned int retries
= 1, diff
;
185 if (data_size
> MAX_HEARTBEAT_LENGTH
)
186 return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET_LENGTH
);
188 if (!heartbeat_allow_send (session
))
189 return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST
);
191 switch(session
->internals
.hb_state
)
195 ("REC[%p]: sending HB_REQUEST with length: %zu to peer\n", session
, data_size
);
197 if (data_size
> DEFAULT_PAYLOAD_SIZE
)
198 data_size
-= DEFAULT_PAYLOAD_SIZE
;
202 _gnutls_buffer_reset(&session
->internals
.hb_local_data
);
204 ret
= _gnutls_buffer_resize (&session
->internals
.hb_local_data
, data_size
);
206 return gnutls_assert_val(ret
);
208 ret
= _gnutls_rnd (GNUTLS_RND_NONCE
, session
->internals
.hb_local_data
.data
, data_size
);
210 return gnutls_assert_val(ret
);
212 gettime (&session
->internals
.hb_ping_start
);
213 session
->internals
.hb_local_data
.length
= data_size
;
214 session
->internals
.hb_state
= SHB_SEND2
;
216 session
->internals
.hb_actual_retrans_timeout_ms
= session
->internals
.hb_retrans_timeout_ms
;
218 ret
= heartbeat_send_data (session
, session
->internals
.hb_local_data
.data
,
219 session
->internals
.hb_local_data
.length
,
222 return gnutls_assert_val(ret
);
224 gettime (&session
->internals
.hb_ping_sent
);
226 if (!(flags
& GNUTLS_HEARTBEAT_WAIT
))
228 session
->internals
.hb_state
= SHB_SEND1
;
232 session
->internals
.hb_state
= SHB_RECV
;
235 ret
= _gnutls_recv_int(session
, GNUTLS_HEARTBEAT
, -1, NULL
, 0, NULL
, session
->internals
.hb_actual_retrans_timeout_ms
);
236 if (ret
== GNUTLS_E_HEARTBEAT_PONG_RECEIVED
)
238 session
->internals
.hb_state
= SHB_SEND1
;
241 else if (ret
== GNUTLS_E_TIMEDOUT
)
244 if (max_tries
> 0 && retries
> max_tries
)
246 session
->internals
.hb_state
= SHB_SEND1
;
247 return gnutls_assert_val(ret
);
251 diff
= _dtls_timespec_sub_ms(&now
, &session
->internals
.hb_ping_start
);
252 if (diff
> session
->internals
.hb_total_timeout_ms
)
254 session
->internals
.hb_state
= SHB_SEND1
;
255 return gnutls_assert_val(GNUTLS_E_TIMEDOUT
);
258 session
->internals
.hb_actual_retrans_timeout_ms
*= 2;
259 session
->internals
.hb_actual_retrans_timeout_ms
%= MAX_DTLS_TIMEOUT
;
261 session
->internals
.hb_state
= SHB_SEND2
;
266 session
->internals
.hb_state
= SHB_SEND1
;
267 return gnutls_assert_val(ret
);
275 * gnutls_heartbeat_pong:
276 * @session: is a #gnutls_session_t structure.
277 * @flags: should be zero
279 * This function replies to a ping by sending a pong to the peer.
281 * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code.
286 gnutls_heartbeat_pong (gnutls_session_t session
, unsigned int flags
)
290 if (session
->internals
.hb_remote_data
.length
== 0)
291 return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST
);
293 ret
= heartbeat_send_data (session
, session
->internals
.hb_remote_data
.data
,
294 session
->internals
.hb_remote_data
.length
,
297 _gnutls_buffer_reset (&session
->internals
.hb_remote_data
);
302 * Process HB message in buffer.
303 * @bufel: the (suspected) HeartBeat message (TLV+padding)
307 * GNUTLS_E_AGAIN if processed OK
308 * GNUTLS_E_HEARTBEAT_PONG_FAILED on response send failure for request message
309 * GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER on payload mismatch for response message
312 _gnutls_heartbeat_handle (gnutls_session_t session
, mbuffer_st
* bufel
)
316 uint8_t *msg
= _mbuffer_get_udata_ptr (bufel
);
317 size_t hb_len
, len
= _mbuffer_get_udata_size (bufel
);
319 if (!heartbeat_allow_recv (session
))
320 return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET
);
323 return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET_LENGTH
);
325 hb_len
= _gnutls_read_uint16 (msg
+ 1);
326 if (hb_len
> len
- 3)
327 return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET
);
331 case HEARTBEAT_REQUEST
:
333 ("REC[%p]: received HEARTBEAT_REQUEST\n", session
);
335 _gnutls_buffer_reset(&session
->internals
.hb_remote_data
);
337 ret
= _gnutls_buffer_resize (&session
->internals
.hb_remote_data
, hb_len
);
339 return gnutls_assert_val(ret
);
342 memcpy(session
->internals
.hb_remote_data
.data
, msg
+3, hb_len
);
343 session
->internals
.hb_remote_data
.length
= hb_len
;
345 return gnutls_assert_val(GNUTLS_E_HEARTBEAT_PING_RECEIVED
);
347 case HEARTBEAT_RESPONSE
:
349 if (hb_len
!= session
->internals
.hb_local_data
.length
)
350 return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET
);
352 if (hb_len
> 0 && memcmp (msg
+ 3, session
->internals
.hb_local_data
.data
,
355 _gnutls_record_log ("REC[%p]: HB: %s - received\n", session
,
356 _gnutls_bin2hex (msg
+ 3, hb_len
, pr
,
358 return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET
);
361 _gnutls_buffer_reset (&session
->internals
.hb_local_data
);
363 return gnutls_assert_val(GNUTLS_E_HEARTBEAT_PONG_RECEIVED
);
366 ("REC[%p]: HB: received unknown type %u\n",
368 return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET
);
373 * gnutls_heartbeat_get_timeout:
374 * @session: is a #gnutls_session_t structure.
376 * This function will return the milliseconds remaining
377 * for a retransmission of the previously sent ping
378 * message. This function is useful when ping is used in
379 * non-blocking mode, to estimate when to call gnutls_heartbeat_ping()
380 * if no packets have been received.
382 * Returns: the remaining time in milliseconds.
386 unsigned int gnutls_heartbeat_get_timeout (gnutls_session_t session
)
392 diff
= _dtls_timespec_sub_ms(&now
, &session
->internals
.hb_ping_sent
);
393 if (diff
>= session
->internals
.hb_actual_retrans_timeout_ms
)
396 return session
->internals
.hb_actual_retrans_timeout_ms
- diff
;
400 * gnutls_heartbeat_set_timeouts:
401 * @session: is a #gnutls_session_t structure.
402 * @retrans_timeout: The time at which a retransmission will occur in milliseconds
403 * @total_timeout: The time at which the connection will be aborted, in milliseconds.
405 * This function will set the timeouts required for the DTLS handshake
406 * protocol. The retransmission timeout is the time after which a
407 * message from the peer is not received, the previous messages will
408 * be retransmitted. The total timeout is the time after which the
409 * handshake will be aborted with %GNUTLS_E_TIMEDOUT.
411 * The DTLS protocol recommends the values of 1 sec and 60 seconds
414 * If the retransmission timeout is zero then the handshake will operate
415 * in a non-blocking way, i.e., return %GNUTLS_E_AGAIN.
419 void gnutls_heartbeat_set_timeouts (gnutls_session_t session
, unsigned int retrans_timeout
,
420 unsigned int total_timeout
)
422 session
->internals
.hb_retrans_timeout_ms
= retrans_timeout
;
423 session
->internals
.hb_total_timeout_ms
= total_timeout
;
428 _gnutls_heartbeat_recv_params (gnutls_session_t session
,
429 const uint8_t * data
, size_t _data_size
)
431 heartbeat_policy_t pol
;
432 extension_priv_data_t epriv
;
434 if (_gnutls_ext_get_session_data
435 (session
, GNUTLS_EXTENSION_HEARTBEAT
, &epriv
) < 0)
437 if (session
->security_parameters
.entity
== GNUTLS_CLIENT
)
438 return gnutls_assert_val (GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER
);
439 return 0; /* Not enabled */
443 return GNUTLS_E_UNEXPECTED_PACKET_LENGTH
;
445 _gnutls_debug_log ("HB: received parameter %u (%zu bytes)\n",
446 (unsigned)data
[0], _data_size
);
453 pol
|= LOCAL_ALLOWED_TO_SEND
;
456 pol
|= LOCAL_NOT_ALLOWED_TO_SEND
;
459 return gnutls_assert_val (GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER
);
463 _gnutls_ext_set_session_data (session
, GNUTLS_EXTENSION_HEARTBEAT
,
470 _gnutls_heartbeat_send_params (gnutls_session_t session
,
471 gnutls_buffer_st
* extdata
)
473 extension_priv_data_t epriv
;
476 if (_gnutls_ext_get_session_data
477 (session
, GNUTLS_EXTENSION_HEARTBEAT
, &epriv
) < 0)
478 return 0; /* nothing to send - not enabled */
480 if (epriv
.num
& GNUTLS_HB_PEER_ALLOWED_TO_SEND
)
482 else /*if (epriv.num & GNUTLS_HB_PEER_NOT_ALLOWED_TO_SEND)*/
485 _gnutls_debug_log ("HB: sending parameter %u\n", (unsigned)p
);
486 if (_gnutls_buffer_append_data (extdata
, &p
, 1) < 0)
487 return gnutls_assert_val (GNUTLS_E_MEMORY_ERROR
);
489 return 1; /* number of bytes added for sending */
493 _gnutls_heartbeat_pack (extension_priv_data_t _priv
, gnutls_buffer_st
* ps
)
496 BUFFER_APPEND_NUM (ps
, _priv
.num
);
497 return GNUTLS_E_SUCCESS
;
501 _gnutls_heartbeat_unpack (gnutls_buffer_st
* ps
,
502 extension_priv_data_t
* _priv
)
505 extension_priv_data_t epriv
;
506 BUFFER_POP_NUM (ps
, epriv
.num
);
514 extension_entry_st ext_mod_heartbeat
= {
516 .type
= GNUTLS_EXTENSION_HEARTBEAT
,
517 .parse_type
= GNUTLS_EXT_TLS
,
519 .recv_func
= _gnutls_heartbeat_recv_params
,
520 .send_func
= _gnutls_heartbeat_send_params
,
521 .pack_func
= _gnutls_heartbeat_pack
,
522 .unpack_func
= _gnutls_heartbeat_unpack
,