Commit 1bafb09e authored by Jon Grimm's avatar Jon Grimm

[SCTP] Add SEND_FAILED support. (Ardelle Fan)

parent 9850a96f
...@@ -872,6 +872,9 @@ struct sctp_outq { ...@@ -872,6 +872,9 @@ struct sctp_outq {
unsigned out_qlen; /* Total length of queued data chunks. */ unsigned out_qlen; /* Total length of queued data chunks. */
/* Error of send failed, may used in SCTP_SEND_FAILED event. */
unsigned error;
/* These are control chunks we want to send. */ /* These are control chunks we want to send. */
struct sk_buff_head control; struct sk_buff_head control;
......
...@@ -138,13 +138,13 @@ void sctp_outq_init(sctp_association_t *asoc, struct sctp_outq *q) ...@@ -138,13 +138,13 @@ void sctp_outq_init(sctp_association_t *asoc, struct sctp_outq *q)
} }
/* Free the outqueue structure and any related pending chunks. /* Free the outqueue structure and any related pending chunks.
* FIXME: Add SEND_FAILED support.
*/ */
void sctp_outq_teardown(struct sctp_outq *q) void sctp_outq_teardown(struct sctp_outq *q)
{ {
struct sctp_transport *transport; struct sctp_transport *transport;
struct list_head *lchunk, *pos, *temp; struct list_head *lchunk, *pos, *temp;
sctp_chunk_t *chunk; sctp_chunk_t *chunk;
struct sctp_ulpevent *ev;
/* Throw away unacknowledged chunks. */ /* Throw away unacknowledged chunks. */
list_for_each(pos, &q->asoc->peer.transport_addr_list) { list_for_each(pos, &q->asoc->peer.transport_addr_list) {
...@@ -152,6 +152,14 @@ void sctp_outq_teardown(struct sctp_outq *q) ...@@ -152,6 +152,14 @@ void sctp_outq_teardown(struct sctp_outq *q)
while ((lchunk = sctp_list_dequeue(&transport->transmitted))) { while ((lchunk = sctp_list_dequeue(&transport->transmitted))) {
chunk = list_entry(lchunk, sctp_chunk_t, chunk = list_entry(lchunk, sctp_chunk_t,
transmitted_list); transmitted_list);
/* Generate a SEND FAILED event. */
ev = sctp_ulpevent_make_send_failed(q->asoc,
chunk, SCTP_DATA_SENT,
q->error, GFP_ATOMIC);
if (ev)
sctp_ulpq_tail_event(&q->asoc->ulpq, ev);
sctp_free_chunk(chunk); sctp_free_chunk(chunk);
} }
} }
...@@ -171,8 +179,19 @@ void sctp_outq_teardown(struct sctp_outq *q) ...@@ -171,8 +179,19 @@ void sctp_outq_teardown(struct sctp_outq *q)
} }
/* Throw away any leftover data chunks. */ /* Throw away any leftover data chunks. */
while ((chunk = sctp_outq_dequeue_data(q))) while ((chunk = sctp_outq_dequeue_data(q))) {
/* Generate a SEND FAILED event. */
ev = sctp_ulpevent_make_send_failed(q->asoc,
chunk, SCTP_DATA_UNSENT,
q->error, GFP_ATOMIC);
if (ev)
sctp_ulpq_tail_event(&q->asoc->ulpq, ev);
sctp_free_chunk(chunk); sctp_free_chunk(chunk);
}
q->error = 0;
/* Throw away any leftover control chunks. */ /* Throw away any leftover control chunks. */
while ((chunk = (sctp_chunk_t *) skb_dequeue(&q->control))) while ((chunk = (sctp_chunk_t *) skb_dequeue(&q->control)))
......
...@@ -410,7 +410,8 @@ static void sctp_do_8_2_transport_strike(sctp_association_t *asoc, ...@@ -410,7 +410,8 @@ static void sctp_do_8_2_transport_strike(sctp_association_t *asoc,
/* Worker routine to handle INIT command failure. */ /* Worker routine to handle INIT command failure. */
static void sctp_cmd_init_failed(sctp_cmd_seq_t *commands, static void sctp_cmd_init_failed(sctp_cmd_seq_t *commands,
sctp_association_t *asoc) sctp_association_t *asoc,
unsigned error)
{ {
struct sctp_ulpevent *event; struct sctp_ulpevent *event;
...@@ -421,46 +422,27 @@ static void sctp_cmd_init_failed(sctp_cmd_seq_t *commands, ...@@ -421,46 +422,27 @@ static void sctp_cmd_init_failed(sctp_cmd_seq_t *commands,
sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
SCTP_ULPEVENT(event)); SCTP_ULPEVENT(event));
/* FIXME: We need to handle data possibly either /* SEND_FAILED sent later when cleaning up the association. */
* sent via COOKIE-ECHO bundling or just waiting in asoc->outqueue.error = error;
* the transmit queue, if the user has enabled
* SEND_FAILED notifications.
*/
sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
} }
/* Worker routine to handle SCTP_CMD_ASSOC_FAILED. */ /* Worker routine to handle SCTP_CMD_ASSOC_FAILED. */
static void sctp_cmd_assoc_failed(sctp_cmd_seq_t *commands, static void sctp_cmd_assoc_failed(sctp_cmd_seq_t *commands,
sctp_association_t *asoc, struct sctp_association *asoc,
sctp_event_t event_type, sctp_event_t event_type,
sctp_subtype_t subtype, sctp_subtype_t subtype,
sctp_chunk_t *chunk) struct sctp_chunk *chunk,
unsigned error)
{ {
struct sctp_ulpevent *event; struct sctp_ulpevent *event;
__u16 error = 0;
switch(event_type) {
case SCTP_EVENT_T_PRIMITIVE:
if (SCTP_PRIMITIVE_ABORT == subtype.primitive)
error = SCTP_ERROR_USER_ABORT;
break;
case SCTP_EVENT_T_CHUNK:
if (chunk && (SCTP_CID_ABORT == chunk->chunk_hdr->type) &&
(ntohs(chunk->chunk_hdr->length) >=
(sizeof(struct sctp_chunkhdr) +
sizeof(struct sctp_errhdr)))) {
error = ((sctp_errhdr_t *)chunk->skb->data)->cause;
}
break;
default:
break;
}
/* Cancel any partial delivery in progress. */ /* Cancel any partial delivery in progress. */
sctp_ulpq_abort_pd(&asoc->ulpq, GFP_ATOMIC); sctp_ulpq_abort_pd(&asoc->ulpq, GFP_ATOMIC);
event = sctp_ulpevent_make_assoc_change(asoc, 0, SCTP_COMM_LOST, event = sctp_ulpevent_make_assoc_change(asoc, 0, SCTP_COMM_LOST,
error, 0, 0, GFP_ATOMIC); (__u16)error, 0, 0,
GFP_ATOMIC);
if (event) if (event)
sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
SCTP_ULPEVENT(event)); SCTP_ULPEVENT(event));
...@@ -468,9 +450,8 @@ static void sctp_cmd_assoc_failed(sctp_cmd_seq_t *commands, ...@@ -468,9 +450,8 @@ static void sctp_cmd_assoc_failed(sctp_cmd_seq_t *commands,
sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE, sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
SCTP_STATE(SCTP_STATE_CLOSED)); SCTP_STATE(SCTP_STATE_CLOSED));
/* FIXME: We need to handle data that could not be sent or was not /* SEND_FAILED sent later when cleaning up the association. */
* acked, if the user has enabled SEND_FAILED notifications. asoc->outqueue.error = error;
*/
sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
} }
...@@ -1077,12 +1058,12 @@ int sctp_cmd_interpreter(sctp_event_t event_type, sctp_subtype_t subtype, ...@@ -1077,12 +1058,12 @@ int sctp_cmd_interpreter(sctp_event_t event_type, sctp_subtype_t subtype,
break; break;
case SCTP_CMD_INIT_FAILED: case SCTP_CMD_INIT_FAILED:
sctp_cmd_init_failed(commands, asoc); sctp_cmd_init_failed(commands, asoc, cmd->obj.u32);
break; break;
case SCTP_CMD_ASSOC_FAILED: case SCTP_CMD_ASSOC_FAILED:
sctp_cmd_assoc_failed(commands, asoc, event_type, sctp_cmd_assoc_failed(commands, asoc, event_type,
subtype, chunk); subtype, chunk, cmd->obj.u32);
break; break;
case SCTP_CMD_COUNTER_INC: case SCTP_CMD_COUNTER_INC:
......
...@@ -729,7 +729,8 @@ sctp_disposition_t sctp_sf_sendbeat_8_3(const sctp_endpoint_t *ep, ...@@ -729,7 +729,8 @@ sctp_disposition_t sctp_sf_sendbeat_8_3(const sctp_endpoint_t *ep,
if (asoc->overall_error_count >= asoc->overall_error_threshold) { if (asoc->overall_error_count >= asoc->overall_error_threshold) {
/* CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */ /* CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */
sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
SCTP_U32(SCTP_ERROR_NO_ERROR));
SCTP_INC_STATS(SctpAborteds); SCTP_INC_STATS(SctpAborteds);
SCTP_DEC_STATS(SctpCurrEstab); SCTP_DEC_STATS(SctpCurrEstab);
return SCTP_DISPOSITION_DELETE_TCB; return SCTP_DISPOSITION_DELETE_TCB;
...@@ -1772,14 +1773,16 @@ sctp_disposition_t sctp_sf_cookie_echoed_err(const sctp_endpoint_t *ep, ...@@ -1772,14 +1773,16 @@ sctp_disposition_t sctp_sf_cookie_echoed_err(const sctp_endpoint_t *ep,
sctp_chunk_t *chunk = arg; sctp_chunk_t *chunk = arg;
sctp_errhdr_t *err; sctp_errhdr_t *err;
err = (sctp_errhdr_t *)(chunk->skb->data);
/* If we have gotten too many failures, give up. */ /* If we have gotten too many failures, give up. */
if (1 + asoc->counters[SCTP_COUNTER_INIT_ERROR] > if (1 + asoc->counters[SCTP_COUNTER_INIT_ERROR] >
asoc->max_init_attempts) { asoc->max_init_attempts) {
/* INIT_FAILED will issue an ulpevent. */ /* INIT_FAILED will issue an ulpevent. */
sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
SCTP_U32(err->cause));
return SCTP_DISPOSITION_DELETE_TCB; return SCTP_DISPOSITION_DELETE_TCB;
} }
err = (sctp_errhdr_t *)(chunk->skb->data);
/* Process the error here */ /* Process the error here */
switch (err->cause) { switch (err->cause) {
...@@ -1834,7 +1837,8 @@ sctp_disposition_t sctp_sf_do_5_2_6_stale(const sctp_endpoint_t *ep, ...@@ -1834,7 +1837,8 @@ sctp_disposition_t sctp_sf_do_5_2_6_stale(const sctp_endpoint_t *ep,
attempts = asoc->counters[SCTP_COUNTER_INIT_ERROR] + 1; attempts = asoc->counters[SCTP_COUNTER_INIT_ERROR] + 1;
if (attempts >= asoc->max_init_attempts) { if (attempts >= asoc->max_init_attempts) {
sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
SCTP_U32(SCTP_ERROR_STALE_COOKIE));
return SCTP_DISPOSITION_DELETE_TCB; return SCTP_DISPOSITION_DELETE_TCB;
} }
...@@ -1936,12 +1940,18 @@ sctp_disposition_t sctp_sf_do_9_1_abort(const sctp_endpoint_t *ep, ...@@ -1936,12 +1940,18 @@ sctp_disposition_t sctp_sf_do_9_1_abort(const sctp_endpoint_t *ep,
sctp_cmd_seq_t *commands) sctp_cmd_seq_t *commands)
{ {
sctp_chunk_t *chunk = arg; sctp_chunk_t *chunk = arg;
__u16 error = SCTP_ERROR_NO_ERROR;
if (!sctp_vtag_verify_either(chunk, asoc)) if (!sctp_vtag_verify_either(chunk, asoc))
return sctp_sf_pdiscard(ep, asoc, type, arg, commands); return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
if (chunk && (ntohs(chunk->chunk_hdr->length) >=
(sizeof(struct sctp_chunkhdr) +
sizeof(struct sctp_errhdr))))
error = ((sctp_errhdr_t *)chunk->skb->data)->cause;
/* ASSOC_FAILED will DELETE_TCB. */ /* ASSOC_FAILED will DELETE_TCB. */
sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_U32(error));
SCTP_INC_STATS(SctpAborteds); SCTP_INC_STATS(SctpAborteds);
SCTP_DEC_STATS(SctpCurrEstab); SCTP_DEC_STATS(SctpCurrEstab);
...@@ -1961,6 +1971,7 @@ sctp_disposition_t sctp_sf_cookie_wait_abort(const sctp_endpoint_t *ep, ...@@ -1961,6 +1971,7 @@ sctp_disposition_t sctp_sf_cookie_wait_abort(const sctp_endpoint_t *ep,
sctp_cmd_seq_t *commands) sctp_cmd_seq_t *commands)
{ {
sctp_chunk_t *chunk = arg; sctp_chunk_t *chunk = arg;
__u16 error = SCTP_ERROR_NO_ERROR;
if (!sctp_vtag_verify_either(chunk, asoc)) if (!sctp_vtag_verify_either(chunk, asoc))
return sctp_sf_pdiscard(ep, asoc, type, arg, commands); return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
...@@ -1971,10 +1982,14 @@ sctp_disposition_t sctp_sf_cookie_wait_abort(const sctp_endpoint_t *ep, ...@@ -1971,10 +1982,14 @@ sctp_disposition_t sctp_sf_cookie_wait_abort(const sctp_endpoint_t *ep,
sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP, sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT)); SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
if (chunk && (ntohs(chunk->chunk_hdr->length) >=
(sizeof(struct sctp_chunkhdr) +
sizeof(struct sctp_errhdr))))
error = ((sctp_errhdr_t *)chunk->skb->data)->cause;
/* CMD_INIT_FAILED will DELETE_TCB. */ /* CMD_INIT_FAILED will DELETE_TCB. */
sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED, SCTP_U32(error));
/* BUG? This does not look complete... */
return SCTP_DISPOSITION_ABORT; return SCTP_DISPOSITION_ABORT;
} }
...@@ -2381,7 +2396,8 @@ sctp_disposition_t sctp_sf_eat_data_6_2(const sctp_endpoint_t *ep, ...@@ -2381,7 +2396,8 @@ sctp_disposition_t sctp_sf_eat_data_6_2(const sctp_endpoint_t *ep,
* processing the rest of the chunks in the packet. * processing the rest of the chunks in the packet.
*/ */
sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET,SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET,SCTP_NULL());
sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
SCTP_U32(SCTP_ERROR_NO_DATA));
SCTP_INC_STATS(SctpAborteds); SCTP_INC_STATS(SctpAborteds);
SCTP_DEC_STATS(SctpCurrEstab); SCTP_DEC_STATS(SctpCurrEstab);
return SCTP_DISPOSITION_CONSUME; return SCTP_DISPOSITION_CONSUME;
...@@ -2596,7 +2612,8 @@ sctp_disposition_t sctp_sf_eat_data_fast_4_4(const sctp_endpoint_t *ep, ...@@ -2596,7 +2612,8 @@ sctp_disposition_t sctp_sf_eat_data_fast_4_4(const sctp_endpoint_t *ep,
* processing the rest of the chunks in the packet. * processing the rest of the chunks in the packet.
*/ */
sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET,SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET,SCTP_NULL());
sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
SCTP_U32(SCTP_ERROR_NO_DATA));
SCTP_INC_STATS(SctpAborteds); SCTP_INC_STATS(SctpAborteds);
SCTP_DEC_STATS(SctpCurrEstab); SCTP_DEC_STATS(SctpCurrEstab);
return SCTP_DISPOSITION_CONSUME; return SCTP_DISPOSITION_CONSUME;
...@@ -3547,7 +3564,8 @@ sctp_disposition_t sctp_sf_do_9_1_prm_abort(const sctp_endpoint_t *ep, ...@@ -3547,7 +3564,8 @@ sctp_disposition_t sctp_sf_do_9_1_prm_abort(const sctp_endpoint_t *ep,
*/ */
/* Delete the established association. */ /* Delete the established association. */
sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
SCTP_U32(SCTP_ERROR_USER_ABORT));
SCTP_INC_STATS(SctpAborteds); SCTP_INC_STATS(SctpAborteds);
SCTP_DEC_STATS(SctpCurrEstab); SCTP_DEC_STATS(SctpCurrEstab);
...@@ -3686,7 +3704,8 @@ sctp_disposition_t sctp_sf_cookie_wait_prm_abort(const sctp_endpoint_t *ep, ...@@ -3686,7 +3704,8 @@ sctp_disposition_t sctp_sf_cookie_wait_prm_abort(const sctp_endpoint_t *ep,
*/ */
/* Delete the established association. */ /* Delete the established association. */
sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
SCTP_U32(SCTP_ERROR_USER_ABORT));
return retval; return retval;
} }
...@@ -4012,7 +4031,8 @@ sctp_disposition_t sctp_sf_do_6_3_3_rtx(const sctp_endpoint_t *ep, ...@@ -4012,7 +4031,8 @@ sctp_disposition_t sctp_sf_do_6_3_3_rtx(const sctp_endpoint_t *ep,
if (asoc->overall_error_count >= asoc->overall_error_threshold) { if (asoc->overall_error_count >= asoc->overall_error_threshold) {
/* CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */ /* CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */
sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
SCTP_U32(SCTP_ERROR_NO_ERROR));
SCTP_INC_STATS(SctpAborteds); SCTP_INC_STATS(SctpAborteds);
SCTP_DEC_STATS(SctpCurrEstab); SCTP_DEC_STATS(SctpCurrEstab);
return SCTP_DISPOSITION_DELETE_TCB; return SCTP_DISPOSITION_DELETE_TCB;
...@@ -4147,7 +4167,8 @@ sctp_disposition_t sctp_sf_t1_timer_expire(const sctp_endpoint_t *ep, ...@@ -4147,7 +4167,8 @@ sctp_disposition_t sctp_sf_t1_timer_expire(const sctp_endpoint_t *ep,
SCTP_TO(timer)); SCTP_TO(timer));
sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl)); sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
} else { } else {
sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
SCTP_U32(SCTP_ERROR_NO_ERROR));
return SCTP_DISPOSITION_DELETE_TCB; return SCTP_DISPOSITION_DELETE_TCB;
} }
...@@ -4181,7 +4202,8 @@ sctp_disposition_t sctp_sf_t2_timer_expire(const sctp_endpoint_t *ep, ...@@ -4181,7 +4202,8 @@ sctp_disposition_t sctp_sf_t2_timer_expire(const sctp_endpoint_t *ep,
SCTP_DEBUG_PRINTK("Timer T2 expired.\n"); SCTP_DEBUG_PRINTK("Timer T2 expired.\n");
if (asoc->overall_error_count >= asoc->overall_error_threshold) { if (asoc->overall_error_count >= asoc->overall_error_threshold) {
/* Note: CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */ /* Note: CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */
sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
SCTP_U32(SCTP_ERROR_NO_ERROR));
SCTP_INC_STATS(SctpAborteds); SCTP_INC_STATS(SctpAborteds);
SCTP_DEC_STATS(SctpCurrEstab); SCTP_DEC_STATS(SctpCurrEstab);
return SCTP_DISPOSITION_DELETE_TCB; return SCTP_DISPOSITION_DELETE_TCB;
...@@ -4244,7 +4266,8 @@ sctp_disposition_t sctp_sf_t5_timer_expire(const sctp_endpoint_t *ep, ...@@ -4244,7 +4266,8 @@ sctp_disposition_t sctp_sf_t5_timer_expire(const sctp_endpoint_t *ep,
goto nomem; goto nomem;
sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply)); sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_NULL()); sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
SCTP_U32(SCTP_ERROR_NO_ERROR));
return SCTP_DISPOSITION_DELETE_TCB; return SCTP_DISPOSITION_DELETE_TCB;
nomem: nomem:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment