tass-belgium / picotcp Goto Github PK
View Code? Open in Web Editor NEWPicoTCP is a free TCP/IP stack implementation
License: Other
PicoTCP is a free TCP/IP stack implementation
License: Other
When you close a connection with close() or when remote host closes the connection with FIN, the connection state variable _is_connected is never made false.
Test setup:
client (linux) -> server (picotcp)
After this testsequence the connection is still in the established state. This is not the case when picotcp has empty buffers.
The state is checked from within the socket callback function (mbed code: wakeup() from stack_endpoint.cpp)
Every time the stack receives and IGMPv3 packet, the stack replies with arp messages presenting his mac address.
After a few packets the stack dies with a hard fault, probably out of ram.
This is easy reproducible on Windows.
If you flood a udp socket with packets(in this case I used small packets-1 byte payload), but the socket doesn't read anything, the board will enter hard fault.
The script I used
import sys, socket
import random, string
import select
from time import sleep
SERVER_ADDRESS = "192.168.1.81"
SERVER_PORT = 6677
PACKET = 'a'
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((SERVER_ADDRESS,SERVER_PORT))
cnt = 0
while(True):
print "Sending packet %d" % cnt
s.sendall(PACKET)
sleep(0.001)
cnt+=1
The mbed application opens a socket, binds it to 6677 and then calls thread::wait forever, so the stack can tick.
This is issue 724 in internal redmine
System tick counter is 32 bit in size, and counts every ms..
2^32
/ 1000 --> ms
/ 3600 --> hours
/ 24 --> days
= 49.71 days.
I propose to increase the counter to an unsigned long long everywhere... That timer should never ever overflow.
In the function pico_tree_insert we create a node by create_node(tree,key). When out of memory the return value is NULL. This return value is never checked and the value is being dereferenced later on.
SCENARIO:
RESULT:
EXPECTED RESULT:
SOLUTION PROPOSED:
If the ACK packet after the first SYN, SYNACK contains payload, PicoTCP responds with a RST-ACK packet.
reproduce with:
sudo ./run.sh --test normalconnectwithdata testcases/hardware/tcp_connect_bench_dut.txt
PicoTCP has the following IP : 192.168.1.81
Linux has the following IP : 192.168.1.2
Linux sends a SYN towards 192.168.1.100 with a broadcast MAC.
PicoTCP gets the packet, starts sending ARPs on the network asking who has
192.168.1.100?
After it doesn't get an answer it replies to 192.168.1.2 with ICMP host unreachable.
At a first glance, this looks non-severe, but if I flood the network with these kind of packets PicoTCP will finish the memory and in the end kill the rtos.
To reproduce this init picoTCP either on linux or mbed and use the script to send packets:
https://docs.google.com/a/tass.be/file/d/0B1BRitC1U1OeeHhLNklVa2xGS1E/edit?usp=sharing
This is issue 745 on internal redmine.
See test test/python/tcpbench-delay.py
If loss is decreased, the test succeeds. If 5% as with the current script, the connection lingers indefinitely.
Find the cause and fix.
If you create a few incomplete socket connections and you call pico_socket_accept it will give a socket that's not connected.
When a DHCP server offers the client an IP in the BOOTP yiaddr-field, the client sends a DHCP request with the offered IP in the yiaddr field.
When the server then ACK's, but he places a different IP in the yiaddr field now, the picotcp client assigns himself the IP that was originally offered.
(The ubuntu stack assigns himself the second one)
For each retransmission of the dns query a socket remains in the tree,
without ever being closed.
Each time pico_dns_client_send is called it opens a new socket but it doesn't check
if the previous socket was closed.
In pico_dns_client_retransmission we could close the socket before calling pico_dns_client_send(key);
While working on the mbed board I could observe that from time to time the board is freezing.
The last function calls to the stack are :
tcp_retrans_timeout->tcp_add_retransmission_timer and the offending memory pointer is passed to pico_tree_first_node which will rise a Hard Fault.
We expect a RST segment when we try to send a segment with payload (with ACK or ACK|PSH flags) to picotcp when it is in the listen state. We receive two identical RST segments instaid of one.
This issue is located within the pico_tcp_input() function from pico_tcp.c.
Found this on the mbed.
Each time a new connection comes and you have to receive some data,
the size of the free ram decreases with 8 bytes.
remote client -> PicoTCP server
When a PicoTCP server is within the connection established state, it does not react to another SYN from the same socket (same port and ip). This can be the case when the remote client has crashed, reboots and tries to reconnect to the PicoTCP server. Normally the client will choose another source port, but this is not guaranteed.
The server should react with a simple ACK instaid. The remote client will discover that the ack number is wrong (number from original connection) and will send a RST to the server. The connection on the server should be closed now.
See RFC793 p34 half open connection discovery
The additional flags: -W -Wextra -Wconversion -Wshadow -Wcast-qual -Wwrite-strings
Theoretically we should get an A on compiler warnings if all is fixed.
Because portability is important: -Wcast-align should also be added for 16-bit devices
Host A is a client not listening on any port. Host B is a malicious server listening on port 80.
A --- SYN ---> B
A <--- SYN --- B
A --- ???? ---> B
If host A is a picotcp instance, it will react with a RST|ACK segment.
If host A is a linux stack, it will react with SYN|ACK.
This is known as the split-handshake attack.
Is there an intentional reason why picotcp responds differently, or is this a bug?
With the current implementation, if we open a tcp socket to a certain port and flood it with 1 byte packets, after ~50 packets the stack will report a zero window and the board will be unresponsive for a few minutes until the socket is dropped( the board doesn't enter hard fault, but it will not respond to any ping, arping etc).
After the memory is freed the stack is responsive again.
This is caused because we are not taking into consideration the overhead on this frames.
I don't think it correct that we report zero window after 50 packets (50 bytes used from a 4K buffer).
https://docs.google.com/a/tass.be/file/d/0B1BRitC1U1OeSHhzSlNzeGFvYUk/edit?usp=sharing
commit 9315c3e
Author: Andrei Carp <andrei@andrei-Vostro-3560.(none)>
Date: Tue Jul 16 08:50:50 2013 +0300
Philippe's patch that fixes #753. It doesn't fix #745, the connection still stalls.
The above introduces a regression in pico-to-pico TCP connection (the connection in autotest.sh stalls).
When T1 (renewal-time) expires the client sends a DHCP Request (for renewal) to the leasing server. When the servers answers this request with a NAK, the DHCP client goes to the 'init' state. (see RFC2131 DHCP p34).
At this point the PicoTCP stack just sends DHCP Discover messages regardless DHCP Offer messages from the server.
Wireshark MBED: https://dl.dropboxusercontent.com/u/49342572/dhcp_mbed
As a comparison: Wireshark Linux stack: https://dl.dropboxusercontent.com/u/49342572/dhcp_linux_stack
Letting an mbed test to run I could observe that there are cases when socket connections start to accumulate into memory.
The case when this happens is this one:
The mbed site sends out FIN,ACK
We reply with FIN,ACK
the socket remains in PICO_SOCKET_STATE_TCP_LAST_ACK and
the final ACK never comes and the deletion of the socket is never triggered.
Maybe when going to PICO_SOCKET_STATE_TCP_LAST_ACK we start a timer to make sure we drop the socket.
TCP layer needs a component that is able to close and remove from the socket tree the sockets that haven't had activity in some time or sockets which for some reason closing or opening remained stuck in some state.
While working with the mbed platform I could observe that during time tcp sockets in the following states
0x070F (PICO_SOCKET_STATE_TCP_LAST_ACK)
0x090F (PICO_SOCKET_STATE_TCP_FIN_WAIT2)
0x0004 (PICO_SOCKET_STATE_BOUND)
As an idea we could use a timer to check all sockets and a timestamp to see the last time that socket had activity and/or we should make the states when establishing/closing a connection to timeout and lead to dropping of sockets.
This is a bug within the mbed stack_endpoint.cpp.
When a valid RST TCP segment is received instaid of an ACK after an initial SYN, SYNACK, a hardfault occures due to NULL pointer access. When RST is received, the wakeup function (in stack_endpoint.cpp) gets an ev
equal to PICO_SOCK_EV_ERR
and pico_err
is equal to PICO_ERR_ECONNRESET
, thus the socket will be closed and ep->s
will be accessed. But before that on line 66, ep
is overwritten with ep_accepted
(ep_accepted seems not NULL in the debugger, therefore the if statement on line 64 is executed). Because no socket was accepted before, ep
(overwritten by ep_accepted) has a non valid socket field.
One line 82, pico_socket_close() will access the socket that is a NULL pointer.
Code snippet:
60 static void wakeup(uint16_t ev, struct pico_socket *s)
61 {
62 struct stack_endpoint *ep = (struct stack_endpoint *)s->priv;
63 if (!ep) {
64 if (ep_accepting != NULL) {
65 ptsock_dbg("Delivering %02x to accepting socket...\n", ev);
66 ep = ep_accepting;
67 } else {
68 ptsock_dbg("WAKEUP: socket not found! ev=%04x\n", ev);
69 return;
70 }
71 }
72 //if (((ep->revents & PICO_SOCK_EV_RD) == 0) && (ev & PICO_SOCK_EV_RD))
73 // printf("Activating RD\n");
74 ep->revents |= ev;
75
76 if(ev & PICO_SOCK_EV_ERR)
77 {
78 if(pico_err == PICO_ERR_ECONNRESET)
79 {
80 ptsock_dbg("Connection reset by peer...\n");
81 ep->state = SOCK_RESET_BY_PEER;
82 pico_socket_close(ep->s);
83 ep->s->priv = NULL;
84 }
85 }
Memory content of ep after line 66 has been executed:
In some cases after previous connection was closed and still a retransmission comes
from the mbed website the board would hang. This is caused by an illegal memory access since Keil reported that the Fault registers were set.
The offending instruction is in tcp_send_rst,
f = t->sock.net->alloc(t->sock.net, PICO_SIZE_TCPHDR + opt_len);
because t->sock.net was NULL, I'm working to figure out exactly what is happening.
I know that it has something to do with the socket being removed from tree and the socket was in PICO_SOCKET_STATE_TCP_LAST_ACK.
The reason why this happens is still not clear, keep checking this.
Discovered on mbed, when a Zero Window message comes, the application believes an error was received and closes the connection, which remains in an undefined state.
Maybe the application should be noticed it should try again later(via pico_err).
This was discovered on the mbed platform.
If you connect the board to a network where there is no DHCP server
for each xid generated and tried for 5 times 100 bytes of ram is lost.
Even if the server responds later on the previous memory is lost.
Each time Ethernet::connect is called it will call pico_dhcp_initiate_negotiation which will create a new cookie, but when it fails there is no way to cleanup the used memory.
Basically if you repeat the call to pico_dhcp_initiate_negotiation memory is leaked.
A SYN flood with only a changing source port (same ip's same destination port) produces a HardFault after 40 to 50 SYN packets.
HOWTO reproduce:
sudo ./run.sh --test denialofserviceattack testcases/hardware/tcp_connect_bench_dut.txt
No discover messages are send by PicoTCP after he received the NAK.
Reproduce with:
sudo ./run.sh --test nackafterrequest testcases/hardware/dhcp_client_dora.txt
The test will fail with: "Sniff timed out after 40.00 seconds"
If you send a fragmented packet larger than the buffer size of the socket,
the packet remains blocked somewhere on ip layer or causes hard fault.
If the fragmented packet total size is lower than the socket buffer everything is fine.
Test and main can be found here.
https://docs.google.com/a/tass.be/file/d/0B1BRitC1U1OedHZKc1I5Zndxck0/edit?usp=sharing
https://docs.google.com/a/tass.be/file/d/0B1BRitC1U1Oedk5PUVFGNXVNLUk/edit?usp=sharing
When sending an ICMP echo request with for example '12345' as additional payload, a ICMP echo reply is returned with '12345' followed by a number of zero's.
This can be a performance drawback since useless calls are made.
At the beginning this bug seemed to be directly connected with the mbed porting.
This is untrue.
After checking the queues I could observe that a frame that was supposed to go out
was getting stuck in the output queue.
This is the reason why when we were closing the socket nothing was being sent out (output queue still had frames).
The root cause is in pico_sockets_loop.
if (s != NULL)
break;
s is never NULL so this instruction breaks all the time.
The correct check here is the following:
if(index && index->keyValue)
break;
When filling up the TCP queues (based on rbtrees), the size of the headers is not taken into account, leading to a miscalculation of the memory currently used.
In particular, when using very small segments (e.g. no-nagle and frequent writes with a few bytes), the overhead can be 10x the payload of the queue, quickly consuming the memory available even if the socket size is correctly set. The socket space limit is never reached so the TCP memory pressure feedbacks are ineffective.
Suggested possible solutions:
The first solution would be preferred if possible because it requires less configuration on the user/integrator side.
@maximevince please add your comments below and feel free to reassign to @andreixc who is currently working on the module in case you don't have time to introduce a fix.
When running the robustness test, the following behavior was observed:
we send out FIN,ACK once
the other side hasn't finished sending out its data so it retransmits some frames
we reply with ICMP port unreachable.
Shouldn't we wait for the other side to send FIN,ACK and then delete the socket?
And until we get back the FIN,ACK back maybe we should resend it.
After this point, the next connection passes the 3whs then the board hangs.
I saved a log file.
If you have a TCPSocketConnection object and the first thing you do is calling the receive() method, it generated a HardFault. This is probably because it wants to access some unallocated space. There is a gap in the protection for this.
If you run the test from task #719
This is the output:
Starting the receiving part...
Received : 1048576 bytes
Expected : 1048576 bytes
Receiving has passed...
Starting the sending part...
Send error
Sent : 5120 bytes
Expected : 1048576 bytes
Sending part of the test has failed. Exiting connection.
Socket closed!
Test was finished...
Testing on the mbed board (using Pico_Robustness_Test).
The board hangs in like a minute or so.
Using the memory wrapper I could see that the stack is using ~17 Kb of ram.
Adding a counter to the trees I could observe that the UDPTable has ~20 connections left (hanging) in the tree.
Investigating...
If there is no DHCP server to answer the first 3 queries (first call to pico_dhcp_initiate_negotiation) , there will be no other request for a new IP, even if pico_dhcp_initiate_negotiation is called again. So if the dhcp doesn't get an answer at its first question it will never ask again.
If the tcp retransmission timer never gets the ack for a frame, will never stop re-sending the frame.
Discover, Offer, Request, but the server does not respond with ACK or NAK. The following should happen:
RFC 2131 p17:
"The client retransmits the DHCPREQUEST according to the retransmission algorithm. A client might retransmit the DHCPREQUEST message four times, for a total delay of 60 seconds, before restarting the initialization procedure. If the client receives neither a DHCPACK or a DHCPNAK message after employing the retransmission algorithm, the client reverts to INIT state and restarts the initialization process.
The client SHOULD notify the user that the initialization process has failed and is restarting."
This notification of failure is done by the connect() method (EthernetInterface class) by returning -1. The DHCP client however, restarts the negotiation procedure in the background. How can the user program know (from the mbed BSD interface) that the second negotiation will succeed or not?
While I was digging through the stack I observed that the LEAF color was being changed to red which is not allowed!
The fix I suggest is in pico_tree.c :
fix_insert_collisions changed the while to this :
while(node->parent->color == RED && IS_NOT_LEAF(GRANPA(node)) )
This happens because tcp_send_fin is called only in pico_tcp_output.
Internal redmine issue #672
The offending test is the one starting at line 52 in the script (DHCP test)
DHCP TEST
./test/autotest.sh: line 52: 10741 Segmentation fault ./build/test/picoapp.elf --barevde pic0:/tmp/pic0.ctl: -a dhcpclient:pic0
Core was generated by `./build/test/picoapp.elf --barevde pic0 /tmp/pic0.ctl -a dhcpclient pic0'.
Program terminated with signal 11, Segmentation fault.
#0 0x08054d58 in cookie_compare (ka=0x9a63a20, kb=0x9a60020) at modules/pico_icmp4.c:175
175 if (a->id < b->id)
(gdb) p a
$1 = (struct pico_icmp4_ping_cookie *) 0x9a63a20
(gdb) p b
$2 = (struct pico_icmp4_ping_cookie *) 0x9a60020
(gdb) p a->id
$3 = 37312
(gdb) p b->id
Cannot access memory at address 0x9a60026
(gdb) up
#1 0x08067ea1 in pico_tree_findKey (tree=0x80704b4, key=0x9a60020) at stack/pico_tree.c:179
179 result = tree->compare(found->keyValue, key);
(gdb) p key
$4 = (void *) 0x9a60020
(gdb) l
174
175 while(IS_NOT_LEAF(found))
176 {
177 int result;
178
179 result = tree->compare(found->keyValue, key);
180 if(result == 0)
181 return found->keyValue;
182 else if(result < 0)
183 found = found->rightChild;
(gdb) up
#2 0x08054f7e in next_ping (now=4235431418, arg=0x9a60020) at modules/pico_icmp4.c:238
238 if(pico_tree_findKey(&Pings,cookie)){
(gdb) l
233
234 static void next_ping(unsigned long now, void *arg)
235 {
236 struct pico_icmp4_ping_cookie *newcookie, *cookie = (struct pico_icmp4_ping_cookie *)arg;
237
238 if(pico_tree_findKey(&Pings,cookie)){
239 if (cookie->seq < cookie->count) {
240 newcookie = pico_zalloc(sizeof(struct pico_icmp4_ping_cookie));
241 if (!newcookie)
242 return;
(gdb) p cookie
$5 = (struct pico_icmp4_ping_cookie *) 0x9a60020
(gdb) up
#3 0x08067483 in pico_check_timers () at stack/pico_stack.c:479
479 t->timer(pico_tick, t->arg);
(gdb) quit
When you send a TCP segment to a closed port of picotcp (SYN, SYN|ACK, ACK, PSH|ACK,... except for RST), it responds with ICMP unreachable.
RFC793:
"If the connection does not exist (CLOSED) then a reset is sent in response to any incoming segment except another reset. In particular, SYNs addressed to a non-existent connection are rejected by this means. "
The linux stack will respond with a RST segment when you send a segment to a non listening port.
The definition of ICMP port unreachable:
"Port Unreachable - generated if the designated transport protocol (e.g., UDP) is unable to demultiplex the datagram in the transport layer of the final destination but has no protocol mechanism to inform the sender"
Is the ICMP unreachable a way for picotcp to protect the TCP layer from overload on a SYN attack? What should be the correct response for pico?
enabled ip fragmentation
created a udp socket that reads data from network
if you send a small packet (non fragmented) everything is fine.
if the packet is fragmented the socket goes nuts and the call to receiveFrom returns 1024 always (we are asking 1024 from it).
This the link to test (both mbed app and py/scapy script)
https://docs.google.com/a/tass.be/file/d/0B1BRitC1U1OeRWhuUFZiSWlxMEU/edit?usp=sharing
https://docs.google.com/a/tass.be/file/d/0B1BRitC1U1OeZkdWLWdJQTJSbXc/edit?usp=sharing
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.