-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
conn: Reworking the conn API #5091
Comments
Personally, I prefer to use UDP over netapi compared to conn_udp so far. In the best/simplest case IMO we shouldn't need anything more than
or something similar. |
Any particular reason why, except
The problem is, that this API should (at least in my opinion) be thin, but also applicable for most (if not all) stacks we want to support. So doing this without any state seems to me pretty unrealistic to me (I'm not sure if you left the conn_udp_t conn;
conn_udp_create(&conn, AF_INET6, recv_cb);
conn_udp_bind(&conn, &unspec, sport);
conn_udp_sendto(&conn, data, len, dest, dport); For savvy developers like you using |
I just found the API more natural. I want to send -> netapi_send. I want to receive something -> register for this or that traffic. In conn I would have to create a conn object first, specify some source information even for sending (which might or might not be NULL) etc. Just seemed overly complicated at a first glance. I've never dug deeper into this, but always found netapi more comfortable. |
Here's what I came up with:
(missing asynchronous stuff). I did it another way around: I started writing examples using the API, then implemented it on top of POSIX. The API is very minimal, sending is possible without creating an object. |
For sending, no object should be needed. And, there is no need for a seperate bind. |
Then look into my
Why? |
I think we should be clear about the goal: a) the simplest possible API or b) something that covers all corner cases? If it is b) I'm not really interested in this topic. IMO this can and should be covered by some adaptation layer, well hidden from the user at the application level. |
If an API needs an adaption layer to implement it for something underlying it is not simple (in the sense of complexity, not usability) any more IMHO. This API should be simple in both complexity (to have our constraints in check) and usability (to make it appealing to users) terms IMHO. |
Why should it be needed? If the specified "src_port" is bound without sth like "O_REUSEADDR", the send fails with an error.
Why is it needed? |
I'm with Oleg here, we should make the API simple. The whole point here is to get rid of the useless complexity of berkley sockets, and get something that is easiy and fun to use while still being usable for more complex applications. If a stack doesn't support the resulting API (none will), the implementation of our API for that stack_is_ an adaption layer to the network stack's native interface / semantics. |
Because real world protocols have defined ports. If I can't do this c = conn_create(DNS_PORT);
while (1) {
sport = DNS_PORT;
conn_recvfrom(&c, &dst, &dport); // dport will become DNS_PORT
conn_sendto(data, src, sport, dst, dport);
} The API is not ready for real-world usage. With the current
I explained my reasoning in OP. True, it is based on the argument, that sendto needs a state, but I did not get any valid argument against that, except that you want to send packets with as less code as possible with a particular stack. |
IMO that's the most important point of it all: provide a really light-weight, usable API without any might-be-nice-to-have-most-of-the-time-optional parameters. |
I think we are talking about two different things here: you want to have an easy to use user API, I want an easy to port stack-to-os API. Your user API can be easily ported on top of my API. The thing is this: we currently have two stacks that require an adaption layer (that would look very similar) and one that doesn't (sorry Kaspar not counting yours until I see any code). Which would be easily fixed by making this API a fun to port API and providing some user API on top. |
Ok, if fthe thread is about "an easy to port stack-to-os API", I'm out here. Sorry for the misunderstanding. |
That's exactly the point I'm talking about: conn isn't optional most of the times for sending, especially when talking about scenarios were a node is e.g. a CoAP server. |
That is clearly a problem with those stacks, not of the API.
No, I want to send packets with as less code as possible with any stack. The API doesn't need the bind, there's no concept where the seperate bind is needed. |
Would be nice to have something that can serve both so we don't stack abstraction over abstraction. |
That will always require some compromises, I'm not willing to make here. |
That's kind of the point of a discussion... |
Curious then, that members of the opposition of my proposal then are requesting the same kind of behaviour for GNRC: #4387 ;-). They are not broken, their history just lies in TCP (which is why I will refer to them as TCP-based stack and stacks like GNRC as UDP-based stacks) Seriously, I don't see any point in a lightweight abstraction API that makes it harder to port for a popular stack than just porting sockets... I see however a possibility that is quiet convoluted, to be honest, but a compromise it is: my kind of API seems to be a good solution to port TCP-based stacks, while your solution seems to be a good solution to implement UDP-based stacks. However with a little bit of overhead they are pretty much interchangeable (you can implement my solution with your solution and vice-versa). How about we port the stack with the API that it is best suited for and provide a conversion layer for each API. Your API can then be used by users, while my API can be used to port sockets, libraries or other stuff that requires a state for the UDP session (e.g. servers). |
Another reason for a state variable came to my mind today: connection options like Traffic Class, Flow Label and hop-limit. Or do you want to add them as parameters, too? What are the values for default values then? Why do I have to add them to every send when at least flow label and traffic class are most likely not to change? |
@authmillenon I think we agree on the necessity of a state variable. Just some functions don't need it, e.g., "sock_udp_sendto()" as long as there's "sock_udp_send(state, ...)". |
But then why does
I'm not saying it isn't nice to have a function to send UDP packets without any state, because in some cases this is really, really comfortable, but in most cases you have some kind of state - a connection - even if this is just the same UDP port you use constantly to send something. Regardless, I'm not sure a low-level API is the right place for that. And I stress again, that that is what conn should be first and foremost: A low-level API for generalized stack access (if it makes fun to program with it, that's a bonus, but I would not count it as THE top priority). |
That is not true, as if there's `udp_send(state, ...)
That depends on the definition of "complicated" with the context of APIs.
Well, I remember having to set the hop limit of packets once when hand-crafting multicast. Never used traffic class or flow labels other than for hacky stuff. So I must say, I'm very perfectly fine with a UDP send function that takes buffer, target IP/port and source port and does what I want. I would also argue that this send function is enough to write 99.9% of all UDP applications. For all the others, there's the function that binds to state.
Why? Not allowing a send when an application requests it is a policy decision of a network stack. It is perfectly valid to return EACCES if that source is already bound.
Well, my goal is to end up with a socket API that is easy to use, powerful enough for everything and at the same time efficient, so it becomes a pleasure to write network applications. The sheer quantity of high quality code written against that API will make every network stack developer want to have that API for his/her stack. What I really don't want is another network API that sucks to use. Why should I invest effort in developing that (apart from the fact that we already have three of them)? |
These are no two-liners [1] [2] [3]. And in the case of lwIP and emb6 they are not even considering the fact yet, that sending from a source port that is already bound by a
Now I at least know you are not even reading a word I'm writing.... That this is a problem the basis of my whole argument! Because it makes server implementations plain impossible (or at least very hard to do) this way! When I listen on a connection we need to do this on a port. This port is usually expected to be the source port of a reply. But when the Yes, with GNRC and your nano-stack "your" way is possible and actually easy to implement because we (currently) don't care if there is already a port open on the same address by different applications, but not with the other 99.99999% of stacks that are out there, because they were written with TCP in mind first (were having multiple applications using the same source port/listening port is a very stupid thing to do) and thus even their UDP implementations are in a bad mood as soon as you try to allocate a port multiple times.
You are throwing huge blocks into the way of an API developer for those stacks, because they need to implement a whole adaption layer to make this API usable, which is supposed to be a thin and common adaption layer for OS/application<>stack communtication. Compare that to 2 lines of code more (that you need for at least 50% of applications anyway, even with your proposal) + a few parameters more ( [1] https://github.com/RIOT-OS/RIOT/blob/master/sys/net/gnrc/conn/udp/gnrc_conn_udp.c#L92-L135 conn_t conn;
conn_udp_endpoint_t dst = { .addr = { .i6 = addr }, .port = port }; /* iface should be set via option */
conn_udp_create(&conn, AF_INET6, NULL);
conn_udp_sendto(&conn, data, data_len, &dst);
/* vs. */
udp_endpoint6_t dst = { .port = port, .iface = ANY, .addr = addr };
sock_udp_sendto(&dst, data, data_len, (uint16_t)random_getuint32()); |
I think I found a suitable compromise! Let's revisit: A user generally has two use-cases for sending out datagrams: either they expect a reply or not. If they expect don't expect a reply, the content of source address and port is basically unimportant to them so it can as well be picked be picked by the stack (at random or by some internal measure). If they expect a reply they have to create a connectivity object anyway to wait for said reply. So my proposed compromise is: go with the prototype for int conn_udp_sendto(conn_udp_t *conn, void *data, size_t data_len,
conn_udp_endpoint_t *dst); but let |
At least lwIP and emb6 should be able to handle such cases and GNRC anyways. |
Seems to be a step into the right direction. |
@miri64 I still think the conn proposal is tackling the issue from the wrong direction. Did you try writing hypothetical simple and more complex applications against the API (without it being finished or implemented)? That way you get a grip on what you need and what is cumbersome. It seems to me that you a) try to keep it similar to sockets and b) try to make it easily implementable on a variety of network stacks. |
That might be, because I want it to be (in exactly that order of importance):
This is the order we should go IMHO, because otherwise we will invent the wheel over and over again, every time a new stack gets integrated. The API as I propose is now very similar to your approach (now that I have this solution we might not even need bind(), as NULL is now an unbound conn), so I really don't see why you think I try emulate sockets. |
Here is how the API would look like in total btw with typedef struct conn_udp conn_udp_t;
typedef union {
ipv6_addr_t ipv6;
ipv4_addr_t ipv4;
} conn_addr_t;
typedef struct {
conn_addr_t addr;
int family;
uint16_t port;
} conn_udp_ep_t; // ep == end point
typedef void (*conn_udp_recv_cb_t)(conn_udp_t *conn, void *data, size_t data_len,
conn_udp_ep_t *src);
// recv_cb may be NULL
int conn_udp_create(conn_udp_t *conn, // edit: removed superfluous `family` parameter
const conn_udp_ep_t *ep,
conn_udp_recv_cb_t *recv_cb);
int conn_udp_get_local(conn_udp_t *conn, const conn_udp_ep_t *ep);
int conn_udp_recv(conn_udp_t *conn, void *data, size_t data_len,
conn_udp_ep_t *src);
// conn may be NULL
int conn_udp_send(conn_udp_t *conn, void *data, size_t data_len,
conn_udp_ep_t *dst); Given the two use-cases I described (which are basically a simple and a more complex implementation as you asked for) I would implement them as such with this API: Periodic beacon (simple): conn_udp_ep_t all = { { ALL_NODES }, AF_INET6, 1337 };
char data[] = "wuff";
while (1) {
conn_udp_send(NULL, data, sizeof(data), &all);
xtimer_sleep(TIME);
} DNS server (more complex): void recv_cb(conn_udp_t *conn, void *data, size_t data_len,
conn_udp_ep_t *src) {
// do DNS stuff
// send reply
conn_udp_send(conn, reply, reply_len, src);
}
conn_udp_ep_t addr = { { ANY }, AF_INET6, 53 };
conn_udp_t conn;
uint8_t buf[BUFSIZE];
conn_create(&conn, &addr, recv_cb); Hit me with some challenges, because this wasn't cumbersome or tedious at all ;-). |
Two additional operations to manipulate state in the future (TTL, interface to send over, etc) I also would add are conn_udp_set(conn_udp_t *conn, conn_opt_t opt, void *val, size_t len);
conn_udp_get(conn_udp_t *conn, conn_opt_t opt, void *val, size_t maxlen);
|
As soon as we have a common interface API |
Please have a look at 928593f for some actual header files. |
I proposed the API as a PR in #5533. |
Sock (the result of that discussion) was merged in #5758. |
Motivation
After I worked a little bit with other stacks and also had some feedback from the community I would like to propose a change to the
conn
API, especially to the connection-less part i.e.conn_ip
andconn_udp
(there is no implementation ofconn_tcp
yet anyway to my knowledge). Some of these changes are based on already existing PRs (#3921, #4630, #4631).The API
As an example I use
conn_udp
, since it is pretty similar toconn_ip
(just replaceuint16_t port
withuint8_t proto
;-)):Most of the stuff should be pretty self explanatory so I did not add doxygen for now (Just ask questions if anything is unclear).
Overview over changes
The major changes are:
addr_len
parameters were removed.conn_udp_create()
receives a new callback parameter (an idea already proposed in conn: provide API support for asynchronous IO #4631)conn_udp_bind()
is introduced (no implicit bind throughconn_udp_create()
anymore)conn_udp_sendto()
needs aconn
parameter now (as introduced in conn: make conn_*_sendto require a conn object #4630)Discussion
Removal of address length parameters
conn_udp_create()
should be enough to identify the address lengthCallback parameter on connection creation
select()
orepoll()
in the POSIX layer)xtimer
No implicit bind on creation
conn
parameter on sendtosendto
(making this API not thin anymore)The text was updated successfully, but these errors were encountered: