-
Notifications
You must be signed in to change notification settings - Fork 17
Examples
This section will guide you through making a fairly simple first picoTCP application. It's designed to run on a Linux machine, using tap-devices. We assume you've already set up the environment, and successfully built picoTCP. However, previously, you may have built picotcp by simply running make
. For this example, we'll need the tap drivers, so you should go into the picoTCP directory again, and run make clean; make TAP=1
.
Create a sibling directory next to picoTCP, and create a file main.c
with the content as below. This code sets up picoTCP with a tap device, starts a series of pings, and prints the info on those pings.
#include <time.h>
#include "pico_stack.h"
#include "pico_ipv4.h"
#include "pico_icmp4.h"
#include "pico_dev_tap.h"
#define NUM_PING 10
static int finished = 0;
/* gets called when the ping receives a reply, or encounters a problem */
void cb_ping(struct pico_icmp4_stats *s)
{
char host[30];
pico_ipv4_to_string(host, s->dst.addr);
if (s->err == 0) {
/* if all is well, print some pretty info */
printf("%lu bytes from %s: icmp_req=%lu ttl=%lu time=%lu ms\n", s->size,
host, s->seq, s->ttl, (long unsigned int)s->time);
if (s->seq >= NUM_PING)
finished = 1;
} else {
/* if something went wrong, print it and signal we want to stop */
printf("PING %lu to %s: Error %d\n", s->seq, host, s->err);
finished = 1;
}
}
int main(void){
int id;
struct pico_ip4 ipaddr, netmask;
struct pico_device* dev;
/* initialise the stack. Super important if you don't want ugly stuff like
* segfaults and such! */
pico_stack_init();
/* create the tap device */
dev = pico_tap_create("tap0");
if (!dev)
return -1;
/* assign the IP address to the tap interface */
pico_string_to_ipv4("192.168.5.4", &ipaddr.addr);
pico_string_to_ipv4("255.255.255.0", &netmask.addr);
pico_ipv4_link_add(dev, ipaddr, netmask);
printf("starting ping\n");
id = pico_icmp4_ping("192.168.5.5", NUM_PING, 1000, 10000, 64, cb_ping);
if (id == -1)
return -1;
/* keep running stack ticks to have picoTCP do its network magic. Note that
* you can do other stuff here as well, or sleep a little. This will impact
* your network performance, but everything should keep working (provided
* you don't go overboard with the delays). */
while (finished != 1)
{
usleep(1000);
pico_stack_tick();
}
printf("finished !\n");
return 0;
}
We can compile and link this by running
gcc -c -o main.o -I../picotcp/build/include main.c
gcc -o main.elf main.o ../picotcp/build/lib/libpicotcp.a
Create a persistent tap device - a virtual network port. You don't need to repeat this each time, the device will exist until you reboot, or go sudo tunctl -d tap0
sudo tunctl -u <username>
sudo ifconfig tap0 192.168.5.5
Now, you should be able to run ./main.elf
, and see output like
Protocol ethernet registered (layer: 2).
Protocol ipv4 registered (layer: 3).
Protocol ipv6 registered (layer: 3).
Protocol icmp4 registered (layer: 4).
Protocol icmp6 registered (layer: 4).
Protocol igmp registered (layer: 4).
Protocol udp registered (layer: 4).
Protocol tcp registered (layer: 4).
Device tap0 created.
Assigned ipv4 192.168.5.4 to device tap0
starting ping
64 bytes from 192.168.5.5: icmp_req=1 ttl=64 time=5 ms
64 bytes from 192.168.5.5: icmp_req=2 ttl=64 time=0 ms
64 bytes from 192.168.5.5: icmp_req=3 ttl=64 time=0 ms
64 bytes from 192.168.5.5: icmp_req=4 ttl=64 time=0 ms
64 bytes from 192.168.5.5: icmp_req=5 ttl=64 time=0 ms
64 bytes from 192.168.5.5: icmp_req=6 ttl=64 time=0 ms
64 bytes from 192.168.5.5: icmp_req=7 ttl=64 time=0 ms
64 bytes from 192.168.5.5: icmp_req=8 ttl=64 time=0 ms
64 bytes from 192.168.5.5: icmp_req=9 ttl=64 time=0 ms
64 bytes from 192.168.5.5: icmp_req=10 ttl=64 time=0 ms
finished !
While the application is running, you can also run
ping 192.168.5.4
to send pings in the other direction.
Run wireshark, and sniff the tap0 interface. Then run the ./main.elf
again, and see what happens. You should see an ARP request from picoTCP to Linux, and a reply. After that you should see the ping requests and replies going back and forth.
Note, sometimes you may see lots of other stuff, IPv6 router sollicitations, various broadcasts, mDNS, DNS-SD, etc - this is your when your Linux notices the new network interface is up, and starts all sorts of discoveries. With the persistent TAP device, this usually only happens the first time you start the application. Start a new wireshark capture, and start the application again, it should be much cleaner now.
Now you could make some changes to the main.c
file, and experiment a bit! Keep some statistics of your pings (max, min, avg time). Open a UDP socket, send some stuff to a netcat instance on your linux. Or build a rudimentary port scanner, see what ports are open on your machine.
We include some more advanced examples in the picoTCP source code, as picoapp.elf
. These are actually intended mainly for our smoke tests (and can be found in our test directory), but they can also be interesting to read or to experiment with, to get to know picoTCP a little better. However, these hide some parts of picoTCP (the pico_stack_init
is in test/picoapp.c
, while the actual application you'l be looking at will be in one of the files under test/examples
), and complicate other matters (each application takes a bunch of parameters from command line, which adds lots of complexity that isn't really interesting).
In this section we explore these applications and explain how they work. Since they require picoapp, the code of these examples can not be run standalone and so are solely to give you some insight in featureset of picoTCP.
The different steps are currently under construction
-
Build picoapp.elf (and the IPv6 equivalent picoapp6.elf), which you'll then find in ./build/test/. These 2 are compiled versions of code you can find in ./test/examples/
term1$ make test
-
From here on, you can use picoapp.elf in many different ways. The first step is to run it without any specific functionality. This will allow you to ping the picoTCP instance. For this we'll use a TAP device.
term1$ sudo ./build/test/picoapp.elf --tap myTAP:192.168.13.37:255.255.255.0 --app noop term2$ sudo ifconfig myTAP 192.168.13.38/24 term2$ ping 192.168.13.37
In this case, the picoTCP instance has the .37 ip, while the host has .38. You can easily use wireshark to look at the myTAP interface. 3. Next, we'll use picoapp as a udp echo server
```
term1$ sudo ./build/test/picoapp.elf --tap myTAP:192.168.13.37:255.255.255.0 --app udpecho:192.168.13.37:6667:6667:1400
term2$ sudo ifconfig myTAP 192.168.13.38/24
term2$ nc -vu 192.168.13.37 6667
send "end" through netcat
```
The first instruction starts pico as a udp echo server, receiving and transmitting on port 6667 and it allocates a 1400 byte buffer for bytes to be transferred. You can then netcat to your instance and start chatting with yourself. 4. The same we can do for tcp echo, which has a slightly different syntax, as it listens to all addresses
```
term1$ sudo ./build/test/picoapp.elf --tap myTAP:192.168.13.37:255.255.255.0 --app tcpecho:6667
term2$ sudo ifconfig myTAP 192.168.13.38/24
term2$ nc 192.168.13.37 6667
```
The first instruction starts pico as a tcp echo server, receiving and transmitting on port 6667. You can then netcat to your instance (.37) and start chatting with yourself. 5. Next, we can also use the udp example with a tun device (a layer 3 network device)
```
term1$ sudo ./build/test/picoapp.elf --tun myTUN:192.168.13.37:255.255.255.0 --app udpecho:192.168.13.37:6667:6667:1400
term2$ sudo ifconfig myTUN 192.168.13.38/24
term2$ nc -vu 192.168.13.37 6667
send "end" through netcat
```
-
You can use VDE (Virtual Distributed Ethernet) as well, try the udp echo example with
term1$ sudo ./test/vde_sock_start.sh term1$ sudo ./build/test/picoapp.elf --vde pic0:/tmp/pic0.ctl:10.40.0.3:255.255.255.0 -a udpecho:10.40.0.3:6667:6667:1400 term2$ sudo ifconfig pic0 10.40.0.4/24 term2$ nc -vu 10.40.0.3 6667
To work with VDE devices on linux, you'll need a switch first (created in the shell script)
When looking at the source code of the examples bear in mind they support on picoapp.c for basic functionality. To see how the stack is initialized and how the loop is maintained take a closer look at picoapp.c as well!
This ping example is entirely stand-alone, where the picoTCP instance pings itself. Run the example with
$ sudo ./build/test/picoapp.elf --tun myTUN:192.168.13.37:255.255.255.0 --app ping:192.168.13.37
Let’s take a look at what’s happening behind the scenes in a (simplified version) of ping.c (find this file in test/examples/
Since ping uses the ICMPv4 protocol, we need to add the proper header file. While we’re at it, we define the amount of times we’d like to ping a given IPv4 address.
#include "utils.h"
#include <pico_icmp4.h>
#define NUM_PING 5
We define a destination IP address and a timeout. All we have to do then is call pico_icmp4_ping(dest, NUM_PING, 1000, 10000, 64, cb_ping);
to initiate the pinging. We pass the function an IPv4 destination address, a number of pings, the interval between them, the timeout, a payload size and a callback function.
For the timeout to take effect, we need to create a timer with pico_timer_add(timeout * 1000, ping_abort_timer, &id);
to be able to handle the situation where a ping times out.
All of this can be done as follows:
void app_ping(char *arg)
{
static int id;
char *dest = "192.168.1.5";
int timeout = 20;
id = pico_icmp4_ping(dest, NUM_PING, 1000, 10000, 64, cb_ping);
if (timeout > 0) {
printf("Adding abort timer after %d seconds for id %d\n", timeout, id);
pico_timer_add(timeout * 1000, ping_abort_timer, &id);
}
}
We still need to handle both callbacks! Let’s take a look at the ping callback function
void cb_ping(struct pico_icmp4_stats *s)
{
char host[30];
pico_ipv4_to_string(host, s->dst.addr);
if (s->err == 0) {
dbg("%lu bytes from %s: icmp_req=%lu ttl=%lu time=%lu ms\n", s->size, host, s->seq, s->ttl, s->time);
if (s->seq >= NUM_PING)
exit(0);
} else {
dbg("PING %lu to %s: Error %d\n", s->seq, host, s->err);
exit(1);
}
}
What happens here? The library will call this function and pass it a struct with some statistics. To print the IPv4 address we need to convert it to a readable format with pico_ipv4_to_string(host, s->dst.addr);
Once we’ve got the amount of pings we requested, we exit the application.
The last thing to do is write the timeout callback. This function is called when it takes too long for a ping reply to be received.
void ping_abort_timer(pico_time now, void *_id)
{
int *id = (int *) _id;
printf("Ping: aborting...\n");
pico_icmp4_ping_abort(*id);
}
When a timeout happens, we want to stop pinging. An abort can be done with pico_icmp4_ping_abort(*id);
This will tell the library to stop pinging the address we’ve supplied.
You can now run picoTCP instances on your linux machine, and learned some more about applications. Use the other examples, the API documentation and most importantly, this wiki.
We also encourage you to learn more about our Test environment which will show even more applications
Getting Started
- Setting up the environment
- Testing
- Configuring and compiling
- Running picoTCP on Linux - Deprecated (see setting up)
- Running picoTCP on Windows
Porting
- Build process explained
- Porting the build to another compiler or IDE
- Porting picoTCP to your favorite embedded target
- Porting picoTCP to your favorite Operating System
- Example device driver
Development