/* * Copyright (C) 2015-2017 Simon Brummer * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup net_gnrc * @{ * * @file * @brief Implementation of internal/fsm.h * * @author Simon Brummer * @} */ #include "msg.h" #include "random.h" #include "ringbuffer.h" #include "net/af.h" #include "internal/common.h" #include "internal/fsm.h" #include "internal/pkt.h" #include "internal/option.h" #include "internal/rcvbuf.h" #ifdef MODULE_GNRC_IPV6 #include "net/gnrc/ipv6.h" #endif #define ENABLE_DEBUG (0) #include "debug.h" /** * @brief Checks if a given portnumber is currently used by a tcb as local_port. * * @param[in] portnumber Portnumber that should be checked * * @note Must be called from a context where the tcb list ist locked. * * @return Zero if @p portnumber is currently not used. * @return 1 if @p portnumber is used by an tcb. */ static int _is_local_port_in_use(const uint16_t portnumber) { gnrc_tcp_tcb_t *iter = NULL; LL_FOREACH(_list_tcb_head, iter) { if (iter->local_port == portnumber) { return 1; } } return 0; } /** * @brief Generate random, currently unused local port above the well-known ports (> 1024) * * @return The generated port number */ static uint16_t _get_random_local_port(void) { uint16_t ret = 0; do { ret = random_uint32(); if (ret < 1024) { continue; } } while(_is_local_port_in_use(ret)); return ret; } /** * @brief clears retransmit queue * * @param[in/out] conn TCP Connection, where the retransmit should be cleared * * @return zero on success */ static int _clear_retransmit(gnrc_tcp_tcb_t *tcb) { if (tcb->pkt_retransmit != NULL) { gnrc_pktbuf_release(tcb->pkt_retransmit); xtimer_remove(&(tcb->tim_tout)); tcb->pkt_retransmit = NULL; } return 0; } /** * @brief restarts time wait timer * * @param[in/out] conn TCP Connection, where the timewait_timer should be restarted * * @return Zero on success */ static int _restart_timewait_timer(gnrc_tcp_tcb_t *tcb) { xtimer_remove(&tcb->tim_tout); tcb->msg_tout.type = MSG_TYPE_TIMEWAIT; tcb->msg_tout.content.ptr = (void *)tcb; xtimer_set_msg(&tcb->tim_tout, 2 * GNRC_TCP_MSL, &tcb->msg_tout, gnrc_tcp_pid); return 0; } /** * @brief translates fsm into another state * * @param[in/out] tcb tcb, that specifies connection * @param[in] state state to translate in * @param[out] notify_owner non-negative if the tcb owner should be notified * * @return zero on success */ static int _transition_to(gnrc_tcp_tcb_t *tcb, fsm_state_t state, bool *notify_owner) { gnrc_tcp_tcb_t *iter = NULL; uint8_t found = 0; switch (state) { case FSM_STATE_CLOSED: /* Free Packets in Retransmit queue */ _clear_retransmit(tcb); /* Remove from Connection from active connections */ mutex_lock(&_list_tcb_lock); LL_FOREACH(_list_tcb_head, iter) { if (iter == tcb) { found = 1; } } if (found) { LL_DELETE(_list_tcb_head, iter); } mutex_unlock(&_list_tcb_lock); /* Free potencially allocated Receive Buffer */ _rcvbuf_release_buffer(tcb); *notify_owner = true; break; case FSM_STATE_LISTEN: /* Clear Adress Info */ switch (tcb->address_family) { #ifdef MODULE_GNRC_IPV6 case AF_INET6: if (tcb->status & STATUS_ALLOW_ANY_ADDR) { ipv6_addr_set_unspecified((ipv6_addr_t *) tcb->local_addr); } ipv6_addr_set_unspecified((ipv6_addr_t *) tcb->peer_addr); break; #endif default: DEBUG("gnrc_tcp_fsm.c : _transition_to() : Undefined Addresses\n"); break; } tcb->peer_port = PORT_UNSPEC; /* Allocate rcv Buffer */ if (_rcvbuf_get_buffer(tcb) == -ENOMEM) { return -ENOMEM; } /* Add to Connection to active connections (if not already active) */ mutex_lock(&_list_tcb_lock); LL_FOREACH(_list_tcb_head, iter) { if (iter == tcb) { found = 1; } } if (!found) { LL_APPEND(_list_tcb_head, tcb); } mutex_unlock(&_list_tcb_lock); break; case FSM_STATE_SYN_SENT: /* Allocate rcv Buffer */ if (_rcvbuf_get_buffer(tcb) == -ENOMEM) { return -ENOMEM; } /* Add to Connections to active connection (if not already active) */ mutex_lock(&_list_tcb_lock); LL_FOREACH(_list_tcb_head, iter) { if (iter == tcb) { found = 1; } } /* If not already active: Apped tcb but check portnumber first */ if (!found) { /* Check if Port Number is not in use */ if (tcb->local_port != PORT_UNSPEC) { /* If Portnumber is used: return error and release buffer */ if (_is_local_port_in_use(tcb->local_port)) { mutex_unlock(&_list_tcb_lock); _rcvbuf_release_buffer(tcb); return -EADDRINUSE; } } /* Pick Random Port */ else { tcb->local_port = _get_random_local_port(); } LL_APPEND(_list_tcb_head, tcb); } mutex_unlock(&_list_tcb_lock); break; case FSM_STATE_ESTABLISHED: *notify_owner = true; break; case FSM_STATE_CLOSE_WAIT: *notify_owner = true; break; case FSM_STATE_TIME_WAIT: _restart_timewait_timer(tcb); break; default: break; } tcb->state = state; return 0; } /** * @brief FSM Handling Function for active and passive open * * @param[in/out] tcb Specifies tcb to use fsm on. * @param[out] notify_owner non-negative if the tcb owner should be notified * * @return zero on success * @return -ENOMEM Can't allocate receive buffer. * @return -EADDRINUSE Given local port is already in use */ static int _fsm_call_open(gnrc_tcp_tcb_t *tcb, bool *notify_owner) { gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ int ret = 0; /* Return value */ DEBUG("gnrc_tcp_fsm.c : _fsm_call_open()\n"); tcb->rcv_wnd = GNRC_TCP_DEFAULT_WINDOW; if (tcb->status & STATUS_PASSIVE) { /* Passive Open, T: CLOSED -> LISTEN */ if (_transition_to(tcb, FSM_STATE_LISTEN, notify_owner) == -ENOMEM) { _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); return -ENOMEM; } } else { /* Active Open, init tcb values, send SYN, T: CLOSED -> SYN_SENT */ tcb->iss = random_uint32(); tcb->snd_nxt = tcb->iss; tcb->snd_una = tcb->iss; /* Translate to SYN_SENT */ ret = _transition_to(tcb, FSM_STATE_SYN_SENT, notify_owner); if (ret < 0) { _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); return ret; } /* Send SYN */ _pkt_build(tcb, &out_pkt, &seq_con, MSK_SYN, tcb->iss, 0, NULL, 0); _pkt_setup_retransmit(tcb, out_pkt, false); _pkt_send(tcb, out_pkt, seq_con, false); } return ret; } /** * @brief FSM Handling Function for sending data. * * @param[in/out] tcb Specifies tcb to use fsm on. * @param[in/out] buf Buffer containing data to send. * @param[in] len Maximum Number of Bytes to send. * * @return number of bytes that was sent. */ static int _fsm_call_send(gnrc_tcp_tcb_t *tcb, void *buf, size_t len) { gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ DEBUG("gnrc_tcp_fsm.c : _fsm_call_send()\n"); size_t payload = (tcb->snd_una + tcb->snd_wnd) - tcb->snd_nxt; /* We are allowed to send further bytes if window is open */ if (payload > 0 && tcb->snd_wnd > 0 && tcb->pkt_retransmit == NULL) { /* Calculate segment size */ payload = (payload < GNRC_TCP_MSS) ? payload : GNRC_TCP_MSS; payload = (payload < tcb->mss) ? payload : tcb->mss; payload = (payload < len) ? payload : len; /* Calculate payload size for this segment */ _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, buf, payload); _pkt_setup_retransmit(tcb, out_pkt, false); _pkt_send(tcb, out_pkt, seq_con, false); return payload; } return 0; } /** * @brief FSM Handling Function for receiving data. * * @param[in/out] tcb Specifies tcb to use fsm on. * @param[in/out] buf buffer to store received data into. * @param[in] len Maximum Number of Bytes to receive. * * @return number of bytes that was received. */ static int _fsm_call_recv(gnrc_tcp_tcb_t *tcb, void *buf, size_t len) { gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ DEBUG("gnrc_tcp_fsm.c : _fsm_call_recv()\n"); if (ringbuffer_empty(&tcb->rcv_buf)) { return 0; } /* Read up to the requesed amount of data */ size_t rcvd = ringbuffer_get(&(tcb->rcv_buf), buf, len); /* If the buffer can store more than the GNRC_TCP_MSS: open Window to available buffersize */ if (ringbuffer_get_free(&tcb->rcv_buf) >= GNRC_TCP_MSS) { tcb->rcv_wnd = ringbuffer_get_free(&(tcb->rcv_buf)); /* Send ACK to update window on reopening */ _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); } return rcvd; } /** * @brief FSM Handling Function for initiating a teardown. * * @param[in/out] tcb Specifies tcb to use fsm on. * @param[out] notify_owner non-negative if the tcb owner should be notified * * @return zero on success. */ static int _fsm_call_close(gnrc_tcp_tcb_t *tcb, bool *notify_owner) { gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ DEBUG("gnrc_tcp_fsm.c : _fsm_call_close()\n"); if (tcb->state == FSM_STATE_SYN_RCVD || tcb->state == FSM_STATE_ESTABLISHED || tcb->state == FSM_STATE_CLOSE_WAIT) { /* Send FIN packet */ _pkt_build(tcb, &out_pkt, &seq_con, MSK_FIN_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); _pkt_setup_retransmit(tcb, out_pkt, false); _pkt_send(tcb, out_pkt, seq_con, false); } switch (tcb->state) { case FSM_STATE_LISTEN: _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); break; case FSM_STATE_SYN_RCVD: case FSM_STATE_ESTABLISHED: _transition_to(tcb, FSM_STATE_FIN_WAIT_1, notify_owner); break; case FSM_STATE_CLOSE_WAIT: _transition_to(tcb, FSM_STATE_LAST_ACK, notify_owner); break; default: break; } return 0; } /** * @brief FSM Handling Function for forcefull teardown * * @return -EOPNOTSUPP, because function is currently not implemented */ static int _fsm_call_abort(void) { DEBUG("gnrc_tcp_fsm.c : _fsm_call_abort()\n"); DEBUG("gnrc_tcp_fsm.c : _fsm_call_abort() : ABORT not implemented\n"); return -EOPNOTSUPP; } /** * @brief FSM Handling Function for processing of a received packet * * @param[in/out] tcb Specifies tcb to use fsm on. * @param[in] in_pkt Packet that should be processed. * @param[out] notify_owner non-negative if the tcb owner should be notified * * @return zero on success. * @return -ENOMEM Can't allocate receive buffer. */ static int _fsm_rcvd_pkt(gnrc_tcp_tcb_t *tcb, gnrc_pktsnip_t *in_pkt, bool *notify_owner) { gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ gnrc_pktsnip_t *snp = NULL; /* Temporary Packet Snip */ gnrc_tcp_tcb_t *lst = NULL; /* Temporary tcb pointer */ uint16_t ctl = 0; /* Received control bits */ uint32_t seg_seq = 0; /* Received sequence number */ uint32_t seg_ack = 0; /* Received acknowledgment number */ uint32_t seg_len = 0; /* Segment length */ uint32_t pay_len = 0; /* Payload length */ uint32_t seg_wnd = 0; /* Segment window */ DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt()\n"); /* Search TCP header. */ LL_SEARCH_SCALAR(in_pkt, snp, type, GNRC_NETTYPE_TCP); tcp_hdr_t *tcp_hdr = (tcp_hdr_t *) snp->data; /* Verify packet options, return if they were faulty */ if (_option_parse(tcb, tcp_hdr) < 0) { return 0; } /* Extract header values */ ctl = byteorder_ntohs(tcp_hdr->off_ctl); seg_seq = byteorder_ntohl(tcp_hdr->seq_num); seg_ack = byteorder_ntohl(tcp_hdr->ack_num); seg_wnd = byteorder_ntohs(tcp_hdr->window); /* Extract IPv6-Header */ #ifdef MODULE_GNRC_IPV6 LL_SEARCH_SCALAR(in_pkt, snp, type, GNRC_NETTYPE_IPV6); if (snp == NULL) { DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : incomming packet had no ip header\n"); return 0; } void *ip = snp->data; #endif /* Handle state LISTEN */ if (tcb->state == FSM_STATE_LISTEN) { /* 1) Check RST: if set, return */ if (ctl & MSK_RST) { return 0; } /* 2) Check ACK: if set, send reset with seq_no = ack_no, return */ if (ctl & MSK_ACK) { _pkt_build_reset_from_pkt(&out_pkt, in_pkt); _pkt_send(tcb, out_pkt, 0, false); return 0; } /* 3) Check SYN: Setup incoming connection*/ if (ctl & MSK_SYN) { uint16_t src = byteorder_ntohs(tcp_hdr->src_port); uint16_t dst = byteorder_ntohs(tcp_hdr->dst_port); /* Check if SYN Request is handled by another connection */ lst = _list_tcb_head; while (lst) { /* Compare Portnumbers and Network Layer Adresses */ /* Note: Packets without ip-header were discarded earlier */ if (lst->local_port == dst && lst->peer_port == src) { #ifdef MODULE_GNRC_IPV6 if (snp->type == GNRC_NETTYPE_IPV6 && lst->address_family == AF_INET6) { ipv6_addr_t *dst_addr = &((ipv6_hdr_t *)ip)->dst; ipv6_addr_t *src_addr = &((ipv6_hdr_t *)ip)->src; if (ipv6_addr_equal((ipv6_addr_t *)lst->local_addr, dst_addr) && ipv6_addr_equal((ipv6_addr_t *)lst->peer_addr, src_addr)) { break; } } #endif } lst = lst->next; } /* Return if connection is already handled (port and addresses match) */ if (lst != NULL) { DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : Connection already handled\n"); return 0; } /* SYN Request is valid, fill connection struct with connection information */ /* Note: Packets without ipv6-header were discarded earlier */ #ifdef MODULE_GNRC_IPV6 if (snp->type == GNRC_NETTYPE_IPV6 && tcb->address_family == AF_INET6) { memcpy(tcb->local_addr, &((ipv6_hdr_t *)ip)->dst, sizeof(ipv6_addr_t)); memcpy(tcb->peer_addr, &((ipv6_hdr_t *)ip)->src, sizeof(ipv6_addr_t)); } #else DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : Received Address was not stored\n"); return 0; #endif tcb->local_port = dst; tcb->peer_port = src; tcb->irs = byteorder_ntohl(tcp_hdr->seq_num); tcb->rcv_nxt = tcb->irs + 1; tcb->iss = random_uint32(); tcb->snd_una = tcb->iss; tcb->snd_nxt = tcb->iss; tcb->snd_wnd = seg_wnd; /* Send SYN+ACK: seq_no = iss, ack_no = rcv_nxt, T: LISTEN -> SYN_RCVD */ _pkt_build(tcb, &out_pkt, &seq_con, MSK_SYN_ACK, tcb->iss, tcb->rcv_nxt, NULL, 0); _pkt_setup_retransmit(tcb, out_pkt, false); _pkt_send(tcb, out_pkt, seq_con, false); _transition_to(tcb, FSM_STATE_SYN_RCVD, notify_owner); } return 0; } /* Handle state SYN_SENT */ else if (tcb->state == FSM_STATE_SYN_SENT) { /* 1) Check ACK */ if (ctl & MSK_ACK) { /* If ACK is not acceptable ...*/ if (seg_ack <= tcb->iss || seg_ack > tcb->snd_nxt) { /* ... send Reset if RST is not set else return */ if ((ctl & MSK_RST) != MSK_RST) { _pkt_build(tcb, &out_pkt, &seq_con, MSK_RST, seg_ack, 0, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); } return 0; } } /* 2) Check RST: If RST set ... */ if (ctl & MSK_RST) { /* ... and ACK: Translate to CLOSED, if not return */ if (ctl & MSK_ACK) { _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); } return 0; } /* 3) Check SYN: Set TCB values accordingly */ if (ctl & MSK_SYN) { tcb->rcv_nxt = seg_seq + 1; tcb->irs = seg_seq; if (ctl & MSK_ACK) { tcb->snd_una = seg_ack; _pkt_acknowledge(tcb, seg_ack); } /* Set the local address accordingly */ /* Note: Packets without ipv6-header were discarded earlier */ #ifdef MODULE_GNRC_IPV6 if (snp->type == GNRC_NETTYPE_IPV6 && tcb->address_family == AF_INET6) { memcpy(tcb->local_addr, &((ipv6_hdr_t *)ip)->dst, sizeof(ipv6_addr_t)); } #else DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : Received Address was not stored\n"); return 0; #endif /* SYN has been ACKed, reply pure ACK, T: SYN_SENT -> ESTABLISHED */ if (tcb->snd_una > tcb->iss) { _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); _transition_to(tcb, FSM_STATE_ESTABLISHED, notify_owner); } /* Simultaneous SYN received send SYN+ACK, T: SYN_SENT -> SYN_RCVD */ else { _pkt_build(tcb, &out_pkt, &seq_con, MSK_SYN_ACK, tcb->iss, tcb->rcv_nxt, NULL, 0); _pkt_setup_retransmit(tcb, out_pkt, false); _pkt_send(tcb, out_pkt, seq_con, false); _transition_to(tcb, FSM_STATE_SYN_RCVD, notify_owner); } tcb->snd_wnd = seg_wnd; tcb->snd_wl1 = seg_seq; tcb->snd_wl2 = seg_ack; } return 0; } /* Handle other states */ else { seg_len = _pkt_get_seg_len(in_pkt); pay_len = _pkt_get_pay_len(in_pkt); /* 1) Verify Sequence Number ... */ if (!_pkt_chk_seq_num(tcb, seg_seq, pay_len)) { /* ... if invalid, and RST not set, reply with pure ACK, return */ if ((ctl & MSK_RST) != MSK_RST) { _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); } return 0; } /* 2) Check RST: If RST is set ... */ if (ctl & MSK_RST) { /* .. and State is SYN_RCVD and passive Open: SYN_RCVD -> LISTEN */ if (tcb->state == FSM_STATE_SYN_RCVD && (tcb->status & STATUS_PASSIVE)) { if (_transition_to(tcb, FSM_STATE_LISTEN, notify_owner) == -ENOMEM) { _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); return -ENOMEM; } } else { _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); } return 0; } /* 3) Check SYN: If SYN is set ... */ if (ctl & MSK_SYN) { /* ... send RST, seq_no = snd_nxt, ack_no = rcv_nxt */ _pkt_build(tcb, &out_pkt, &seq_con, MSK_RST, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); return 0; } /* 4) Check ACK */ if (!(ctl & MSK_ACK)) { return 0; } else { if (tcb->state == FSM_STATE_SYN_RCVD) { if (LSS_32_BIT(tcb->snd_una, seg_ack) && LEQ_32_BIT(seg_ack, tcb->snd_nxt)) { tcb->snd_wnd = seg_wnd; tcb->snd_wl1 = seg_seq; tcb->snd_wl2 = seg_ack; _transition_to(tcb, FSM_STATE_ESTABLISHED, notify_owner); } else { _pkt_build(tcb, &out_pkt, &seq_con, MSK_RST, seg_ack, 0, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); } } /* Acknowledgment processing */ if (tcb->state == FSM_STATE_ESTABLISHED || tcb->state == FSM_STATE_FIN_WAIT_1 || tcb->state == FSM_STATE_FIN_WAIT_2 || tcb->state == FSM_STATE_CLOSE_WAIT || tcb->state == FSM_STATE_CLOSING || tcb->state == FSM_STATE_LAST_ACK) { /* Sent data has been acknowledged */ if (LSS_32_BIT(tcb->snd_una, seg_ack) && LEQ_32_BIT(seg_ack, tcb->snd_nxt)) { tcb->snd_una = seg_ack; _pkt_acknowledge(tcb, seg_ack); } /* ACK received for something not yet sent: Reply with pure ACK */ else if (LSS_32_BIT(tcb->snd_nxt, seg_ack)) { _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); return 0; } /* Update Window */ if (LEQ_32_BIT(tcb->snd_una, seg_ack) && LEQ_32_BIT(seg_ack, tcb->snd_nxt)) { if (LSS_32_BIT(tcb->snd_wl1, seg_seq) || (tcb->snd_wl1 == seg_seq && LEQ_32_BIT(tcb->snd_wl2, seg_ack))) { tcb->snd_wnd = seg_wnd; tcb->snd_wl1 = seg_seq; tcb->snd_wl2 = seg_ack; /* Signal User after Window Update */ *notify_owner = true; } } /* Additional processing */ /* Check additionaly if previous our sent FIN has been acknowledged */ if (tcb->state == FSM_STATE_FIN_WAIT_1) { if (tcb->pkt_retransmit == NULL) { _transition_to(tcb, FSM_STATE_FIN_WAIT_2, notify_owner); } } /* If retransmission queue is empty, acknowledge close operation */ if (tcb->state == FSM_STATE_FIN_WAIT_2) { if (tcb->pkt_retransmit == NULL) { /* Optional: Unblock user close operation */ } } /* If our FIN has been acknowledged: Translate to TIME_WAIT */ if (tcb->state == FSM_STATE_CLOSING) { if (tcb->pkt_retransmit == NULL) { _transition_to(tcb, FSM_STATE_TIME_WAIT, notify_owner); } } /* If our FIN has been acknowledged: last ACK received, close connection */ if (tcb->state == FSM_STATE_LAST_ACK) { if (tcb->pkt_retransmit == NULL) { _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); return 0; } } } } /* 5) Check URG */ /* NOTE: Add Urgent Pointer Processing here ... */ /* 6) Process Payload, if existing */ if (pay_len > 0) { /* Check if State is valid */ if (tcb->state == FSM_STATE_ESTABLISHED || tcb->state == FSM_STATE_FIN_WAIT_1 || tcb->state == FSM_STATE_FIN_WAIT_2) { /* Search for begin of payload "chain" */ LL_SEARCH_SCALAR(in_pkt, snp, type, GNRC_NETTYPE_UNDEF); /* Add only Data that is expected, to be received */ if (tcb->rcv_nxt == seg_seq) { /* Copy contents in to buffer */ while (snp && snp->type == GNRC_NETTYPE_UNDEF) { tcb->rcv_nxt += ringbuffer_add(&(tcb->rcv_buf), snp->data, snp->size); snp = snp->next; } /* Shrink Receive Window */ tcb->rcv_wnd = ringbuffer_get_free(&(tcb->rcv_buf)); /* Notify Owner because new data is available */ *notify_owner = true; } /* Send pure ACK, if FIN doesn't this already */ /* NOTE: this is the place to add piggybagging in the future */ if (!(ctl & MSK_FIN)) { _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); } } } /* 7) Check FIN */ if (ctl & MSK_FIN) { if (tcb->state == FSM_STATE_CLOSED || tcb->state == FSM_STATE_LISTEN || tcb->state == FSM_STATE_SYN_SENT) { return 0; } /* Advance rcv_nxt over FIN bit. */ tcb->rcv_nxt = seg_seq + seg_len; _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); _pkt_send(tcb, out_pkt, seq_con, false); if (tcb->state == FSM_STATE_SYN_RCVD || tcb->state == FSM_STATE_ESTABLISHED) { _transition_to(tcb, FSM_STATE_CLOSE_WAIT, notify_owner); } else if (tcb->state == FSM_STATE_FIN_WAIT_1) { if (tcb->pkt_retransmit == NULL) { _transition_to(tcb, FSM_STATE_TIME_WAIT, notify_owner); } else { _transition_to(tcb, FSM_STATE_CLOSING, notify_owner); } } else if (tcb->state == FSM_STATE_FIN_WAIT_2) { _transition_to(tcb, FSM_STATE_TIME_WAIT, notify_owner); } else if (tcb->state == FSM_STATE_TIME_WAIT) { _restart_timewait_timer(tcb); } } } return 0; } /** * @brief FSM Handling Function for timewait timeout handling * * @param[in/out] tcb Specifies tcb to use fsm on. * @param[out] notify_owner non-negative if the tcb owner should be notified * * @return zero on success. */ static int _fsm_timeout_timewait(gnrc_tcp_tcb_t *tcb, bool *notify_owner) { DEBUG("gnrc_tcp_fsm.c : _fsm_timeout_timewait()\n"); _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); return 0; } /** * @brief FSM Handling Function for retransmissions * * @param[in/out] tcb Specifies tcb to use fsm on. * * @return zero on success. */ static int _fsm_timeout_retransmit(gnrc_tcp_tcb_t *tcb) { DEBUG("gnrc_tcp_fsm.c : _fsm_timeout_retransmit()\n"); if (tcb->pkt_retransmit != NULL) { _pkt_setup_retransmit(tcb, tcb->pkt_retransmit, true); _pkt_send(tcb, tcb->pkt_retransmit, 0, true); } else { DEBUG("gnrc_tcp_fsm.c : _fsm_timeout_retransmit() : Retransmit queue is empty\n"); } return 0; } /** * @brief FSM Handling Function for connection timeout handling * * @param[in/out] tcb Specifies tcb to use fsm on. * @param[out] notify_owner non-negative if the tcb owner should be notified * * @return zero on success. */ static int _fsm_timeout_connection(gnrc_tcp_tcb_t *tcb, bool *notify_owner) { DEBUG("gnrc_tcp_fsm.c : _fsm_timeout_connection()\n"); _transition_to(tcb, FSM_STATE_CLOSED, notify_owner); return 0; } /** * @brief FSM Handling Function for probe sending * * @param[in/out] tcb Specifies tcb to use fsm on. * * @return zero on success. */ static int _fsm_send_probe(gnrc_tcp_tcb_t *tcb) { gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ uint8_t probe_pay[] = {1}; /* Probe Payload */ DEBUG("gnrc_tcp_fsm.c : _fsm_send_probe()\n"); /* The Probe sends a already acknowledged Sequence No. with a garbage byte */ _pkt_build(tcb, &out_pkt, NULL, MSK_ACK, tcb->snd_una - 1, tcb->rcv_nxt, probe_pay, sizeof(probe_pay)); _pkt_send(tcb, out_pkt, 0, false); return 0; } /** * @brief FSM Handling Function for clearing the retransmit queue. * * @param[in/out] tcb Specifies tcb to use fsm on. * * @return zero on success. */ static int _fsm_clear_retransmit(gnrc_tcp_tcb_t *tcb) { DEBUG("gnrc_tcp_fsm.c : _fsm_clear_retransmit()\n"); _clear_retransmit(tcb); return 0; } /** * @brief real fsm: needs to be protected from the outside * * @param[in/out] tcb Specifies tcb to use fsm on. * @param[in] event current event that triggers fsm translation * @param[in] in_pkt packet that triggered fsm event. Only in case of RCVD_PKT * @param[in/out] buf buffer for send and receive functions * @param[in] len number of bytes to send or receive atmost * @param[out] notify_owner non-negative if the tcb owner should be notified * * @return Zero on success * @return -ENOMEM Can't allocate receive buffer. * @return -EADDRINUSE Given local port is already in use * @return -EOPNOTSUPP If event is not implemented */ static int _fsm_unprotected(gnrc_tcp_tcb_t *tcb, fsm_event_t event, gnrc_pktsnip_t *in_pkt, void *buf, size_t len, bool *notify_owner) { int ret = 0; DEBUG("gnrc_tcp_fsm.c : _fsm_unprotected()\n"); switch (event) { case FSM_EVENT_CALL_OPEN : ret = _fsm_call_open(tcb, notify_owner); break; case FSM_EVENT_CALL_SEND : ret = _fsm_call_send(tcb, buf, len); break; case FSM_EVENT_CALL_RECV : ret = _fsm_call_recv(tcb, buf, len); break; case FSM_EVENT_CALL_CLOSE : ret = _fsm_call_close(tcb, notify_owner); break; case FSM_EVENT_CALL_ABORT : ret = _fsm_call_abort(); break; case FSM_EVENT_RCVD_PKT : ret = _fsm_rcvd_pkt(tcb, in_pkt, notify_owner); break; case FSM_EVENT_TIMEOUT_TIMEWAIT : ret = _fsm_timeout_timewait(tcb, notify_owner); break; case FSM_EVENT_TIMEOUT_RETRANSMIT : ret = _fsm_timeout_retransmit(tcb); break; case FSM_EVENT_TIMEOUT_CONNECTION : ret = _fsm_timeout_connection(tcb, notify_owner); break; case FSM_EVENT_SEND_PROBE : ret = _fsm_send_probe(tcb); break; case FSM_EVENT_CLEAR_RETRANSMIT : ret = _fsm_clear_retransmit(tcb); break; } return ret; } int _fsm(gnrc_tcp_tcb_t *tcb, fsm_event_t event, gnrc_pktsnip_t *in_pkt, void *buf, size_t len) { msg_t msg; int32_t result; bool notify_owner; /* Lock FSM */ mutex_lock(&(tcb->fsm_lock)); notify_owner = false; result = _fsm_unprotected(tcb, event, in_pkt, buf, len, ¬ify_owner); /* Notify owner if something interesting happend */ if (notify_owner && tcb->owner != KERNEL_PID_UNDEF) { msg.type = MSG_TYPE_NOTIFY_USER; msg_send(&msg, tcb->owner); } /* Unlock FSM */ mutex_unlock(&(tcb->fsm_lock)); return result; }