Part 1
요약·해설
zsock namespacing, short I/O, TLS·DTLS socket, offload registration과 dispatcher 선택 흐름을 다룹니다.
zsock · POSIX alias
TLS · DTLS · credential
Priority · Dispatcher
Part 2
접을 수 있는 영어 원문 전체
영어 원문 전체 펼치기
FB75ABD066E09300E8963F7BE5658549A24961A265278510E8D61A07A41F9482
.. _bsd_sockets_interface:
BSD Sockets
###########
.. contents::
:local:
:depth: 2
Overview
********
Zephyr offers an implementation of a subset of the BSD Sockets API (a part
of the POSIX standard). This API allows to reuse existing programming experience
and port existing simple networking applications to Zephyr.
Here are the key requirements and concepts which governed BSD Sockets
compatible API implementation for Zephyr:
* Has minimal overhead, similar to the requirement for other
Zephyr subsystems.
* Is namespaced by default, to avoid name conflicts with well-known
names like ``close()``, which may be part of libc or other POSIX
compatibility libraries.
If enabled by :kconfig:option:`CONFIG_POSIX_API`, it will also
expose native POSIX names.
BSD Sockets compatible API is enabled using :kconfig:option:`CONFIG_NET_SOCKETS`
config option and implements the following operations: ``socket()``, ``close()``,
``recv()``, ``recvfrom()``, ``send()``, ``sendto()``, ``connect()``, ``bind()``,
``listen()``, ``accept()``, ``fcntl()`` (to set non-blocking mode),
``getsockopt()``, ``setsockopt()``, ``poll()``, ``select()``,
``getaddrinfo()``, ``getnameinfo()``.
Based on the namespacing requirements above, these operations are by
default exposed as functions with ``zsock_`` prefix, e.g.
:c:func:`zsock_socket` and :c:func:`zsock_close`. If the config option
:kconfig:option:`CONFIG_POSIX_API` is defined, all the functions
will be also exposed as aliases without the prefix. This includes the
functions like ``close()`` and ``fcntl()`` (which may conflict with
functions in libc or other libraries, for example, with the filesystem
libraries).
Another entailment of the design requirements above is that the Zephyr
API aggressively employs the short-read/short-write property of the POSIX API
whenever possible (to minimize complexity and overheads). POSIX allows
for calls like ``recv()`` and ``send()`` to actually process (receive
or send) less data than requested by the user (on ``SOCK_STREAM`` type
sockets). For example, a call ``recv(sock, 1000, 0)`` may return 100,
meaning that only 100 bytes were read (short read), and the application
needs to retry call(s) to receive the remaining 900 bytes.
The BSD Sockets API uses file descriptors to represent sockets. File
descriptors are small integers, consecutively assigned from zero, shared
among sockets, files, special devices (like stdin/stdout), etc. Internally,
there is a table mapping file descriptors to internal object pointers.
The file descriptor table is used by the BSD Sockets API even if the rest
of the POSIX subsystem (filesystem, stdin/stdout) is not enabled.
See :zephyr:code-sample:`sockets-echo-server` and :zephyr:code-sample:`sockets-echo-client`
sample applications to learn how to create a simple server or client BSD socket based
application.
.. _secure_sockets_interface:
Secure Sockets
**************
Zephyr provides an extension of standard POSIX socket API, allowing to create
and configure sockets with TLS protocol types, facilitating secure
communication. Secure functions for the implementation are provided by
mbedTLS library. Secure sockets implementation allows use of both TLS and DTLS
protocols with standard socket calls. See :c:enum:`net_ip_protocol_secure` type
for supported secure protocol versions.
To enable secure sockets, set the :kconfig:option:`CONFIG_NET_SOCKETS_SOCKOPT_TLS`
option. To enable DTLS support, use :kconfig:option:`CONFIG_NET_SOCKETS_ENABLE_DTLS`
option.
.. _sockets_tls_credentials_subsys:
TLS credentials subsystem
=========================
TLS credentials must be registered in the system before they can be used with
secure sockets. See :c:func:`tls_credential_add` for more information.
When a specific TLS credential is registered in the system, it is assigned with
numeric value of type :c:type:`sec_tag_t`, called a tag. This value can be used
later on to reference the credential during secure socket configuration with
socket options.
The following TLS credential types can be registered in the system:
- ``TLS_CREDENTIAL_CA_CERTIFICATE``
- ``TLS_CREDENTIAL_SERVER_CERTIFICATE``
- ``TLS_CREDENTIAL_PRIVATE_KEY``
- ``TLS_CREDENTIAL_PSK``
- ``TLS_CREDENTIAL_PSK_ID``
An example registration of CA certificate (provided in ``ca_certificate``
array) looks like this:
.. code-block:: c
ret = tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE,
ca_certificate, sizeof(ca_certificate));
By default certificates in DER format are supported. PEM support can be enabled
in mbedTLS settings.
Secure Socket Creation
======================
A secure socket can be created by specifying secure protocol type, for instance:
.. code-block:: c
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
Once created, it can be configured with socket options. For instance, the
CA certificate and hostname can be set:
.. code-block:: c
sec_tag_t sec_tag_opt[] = {
CA_CERTIFICATE_TAG,
};
ret = setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST,
sec_tag_opt, sizeof(sec_tag_opt));
.. code-block:: c
char host[] = "google.com";
ret = setsockopt(sock, SOL_TLS, TLS_HOSTNAME, host, sizeof(host));
Once configured, socket can be used just like a regular TCP socket.
Several samples in Zephyr use secure sockets for communication. For a sample use
see e.g. :zephyr:code-sample:`echo-server sample application <sockets-echo-server>` or
:zephyr:code-sample:`HTTP GET sample application <sockets-http-get>`.
Secure Sockets options
======================
Secure sockets offer the following options for socket management:
.. doxygengroup:: secure_sockets_options
Socket offloading
*****************
Zephyr allows to register custom socket implementations (called offloaded
sockets). This allows for seamless integration for devices which provide an
external IP stack and expose socket-like API.
Socket offloading can be enabled with :kconfig:option:`CONFIG_NET_SOCKETS_OFFLOAD`
option. A network driver that wants to register a new socket implementation
should use :c:macro:`NET_SOCKET_OFFLOAD_REGISTER` macro. The macro accepts the
following parameters:
* ``socket_name``
An arbitrary name for the socket implementation.
* ``prio``
Socket implementation's priority. The higher the priority, the earlier this
particular implementation will be processed when creating a new socket.
Lower numeric value indicates higher priority.
* ``_family``
Socket family implemented by the offloaded socket. ``AF_UNSPEC`` indicates
any family.
* ``_is_supported``
A filtering function, used to verify whether a particular socket family,
type and protocol are supported by the offloaded socket implementation.
* ``_handler``
A function compatible with :c:func:`socket` API, used to create an
offloaded socket.
Every offloaded socket implementation should also implement a set of socket
APIs, specified in :c:struct:`socket_op_vtable` struct.
The function registered for socket creation should allocate a new file
descriptor using :c:func:`zvfs_reserve_fd` function. Any additional actions,
specific to the creation of a particular offloaded socket implementation,
should take place after the file descriptor is allocated. As a final step,
if the offloaded socket was created successfully, the file descriptor should
be finalized with :c:func:`zvfs_finalize_typed_fd`, or :c:func:`zvfs_finalize_fd`
functions. The finalize function allows to register a
:c:struct:`socket_op_vtable` structure implementing socket APIs for an
offloaded socket along with an optional socket context data pointer.
Finally, when an offloaded network interface is initialized, it should indicate
that the interface is offloaded with :c:func:`net_if_socket_offload_set`
function. The function registers the function used to create an offloaded socket
(the same as the one provided in :c:macro:`NET_SOCKET_OFFLOAD_REGISTER`) at the
network interface.
Offloaded socket creation
=========================
When application creates a new socket with :c:func:`socket` function, the
network stack iterates over all registered socket implementations (native and
offloaded). Higher priority socket implementations are processed first.
For each registered socket implementation, an address family is verified, and if
it matches (or the socket was registered as ``AF_UNSPEC``), the corresponding
``_is_supported`` function is called to verify the remaining socket parameters.
The first implementation that fulfills the socket requirements (i. e.
``_is_supported`` returns true) will create a new socket with its ``_handler``
function.
The above indicates the importance of the socket priority. If multiple socket
implementations support the same set of socket family/type/protocol, the first
implementation processed by the system will create a socket. Therefore it's
important to give the highest priority to the implementation that should be the
system default.
The socket priority for native socket implementation is configured with Kconfig.
Use :kconfig:option:`CONFIG_NET_SOCKETS_TLS_PRIORITY` to set the priority for
the native TLS sockets.
Use :kconfig:option:`CONFIG_NET_SOCKETS_PRIORITY_DEFAULT` to set the priority
for the remaining native sockets.
Dealing with multiple offloaded interfaces
==========================================
As the :c:func:`socket` function does not allow to specify which network
interface should be used by a socket, it's not possible to choose a specific
implementation in case multiple offloaded socket implementations, supporting the
same type of sockets, are available. The same problem arises when both native
and offloaded sockets are available in the system.
To address this problem, a special socket implementation (called socket
dispatcher) was introduced. The sole reason for this module is to postpone the
socket creation for until the first operation on a socket is performed. This
leaves an opening to use ``SO_BINDTODEVICE`` socket option, to bind a socket to
a particular network interface (and thus offloaded socket implementation).
The socket dispatcher can be enabled with :kconfig:option:`CONFIG_NET_SOCKETS_OFFLOAD_DISPATCHER`
Kconfig option.
When enabled, the application can specify the network interface to use with
:c:func:`setsockopt` function:
.. code-block:: c
/* A "dispatcher" socket is created */
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct ifreq ifreq = {
.ifr_name = "SimpleLink"
};
/* The socket is "dispatched" to a particular network interface
* (offloaded or not).
*/
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq));
Similarly, if TLS is supported by both native and offloaded sockets,
``TLS_NATIVE`` socket option can be used to indicate that a native TLS socket
should be created. The underlying socket can then be bound to a particular
network interface:
.. code-block:: c
/* A "dispatcher" socket is created */
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
int tls_native = 1;
/* The socket is "dispatched" to a native TLS socket implmeentation.
* The underlying socket is a "dispatcher" socket now.
*/
setsockopt(sock, SOL_TLS, TLS_NATIVE, &tls_native, sizeof(tls_native));
struct ifreq ifreq = {
.ifr_name = "SimpleLink"
};
/* The underlying socket is "dispatched" to a particular network interface
* (offloaded or not).
*/
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq));
In case no ``SO_BINDTODEVICE`` socket option is used on a socket, the socket
will be dispatched according to the default priority and filtering rules on a
first socket API call.
API Reference
*************
BSD Sockets
===========
.. doxygengroup:: bsd_sockets
TLS Credentials
===============
.. doxygengroup:: tls_credentials
Part 3
한국어 전문 번역
개요
Zephyr는 POSIX standard의 일부인 BSD Sockets API subset을 구현합니다. 기존 programming 경험을 재사용하고 간단한 기존 networking application을 Zephyr로 port할 수 있게 하는 API입니다.
Zephyr의 BSD Sockets compatible API 구현을 이끈 핵심 요구와 개념은 다음과 같습니다.
- 다른 Zephyr subsystem과 마찬가지로 overhead를 최소화합니다.
close()처럼 libc 또는 다른 POSIX compatibility library에 있을 수 있는 잘 알려진 이름과 충돌하지 않도록 기본적으로 namespace를 둡니다.CONFIG_POSIX_API를 활성화하면 native POSIX name도 노출합니다.
CONFIG_NET_SOCKETS config option으로 BSD Sockets compatible API를 활성화합니다. 구현하는 operation은 socket(), close(), recv(), recvfrom(), send(), sendto(), connect(), bind(), listen(), accept(), non-blocking mode 설정용 fcntl(), getsockopt(), setsockopt(), poll(), select(), getaddrinfo(), getnameinfo()입니다.
Namespacing 요구 때문에 기본 function에는 zsock_ prefix가 붙습니다. 예를 들면 zsock_socket과 zsock_close입니다. CONFIG_POSIX_API를 정의하면 모든 function을 prefix 없는 alias로도 노출합니다. 여기에는 libc나 filesystem library의 function과 충돌할 수 있는 close()와 fcntl()도 포함됩니다.
설계 요구의 또 다른 결과로 Zephyr API는 complexity와 overhead를 줄일 수 있는 곳에서 POSIX API의 short-read·short-write 특성을 적극적으로 사용합니다. POSIX는 SOCK_STREAM socket의 recv()나 send()가 요청보다 적은 data를 실제 처리하는 것을 허용합니다. 예를 들어 recv(sock, 1000, 0)가 100을 반환하면 100 byte만 읽은 short read이며, application은 남은 900 byte를 받기 위해 다시 호출해야 합니다.
BSD Sockets API는 socket을 file descriptor로 나타냅니다. File descriptor는 0부터 연속으로 할당되는 작은 integer이며 socket, file, stdin/stdout 같은 special device가 공유합니다. 내부 table이 file descriptor를 internal object pointer에 mapping합니다. BSD Sockets API는 filesystem·stdin/stdout 등 나머지 POSIX subsystem이 활성화되지 않아도 이 file descriptor table을 사용합니다.
간단한 BSD socket server 또는 client application을 만드는 방법은 Sockets Echo Server와 Sockets Echo Client sample을 참고하십시오.
Secure Sockets
Zephyr는 standard POSIX socket API를 확장해 TLS protocol type으로 socket을 생성·설정하고 secure communication을 쉽게 구현할 수 있게 합니다. Secure implementation function은 mbedTLS library가 제공합니다. Standard socket call로 TLS와 DTLS protocol을 모두 사용할 수 있으며, 지원 secure protocol version은 net_ip_protocol_secure enum을 참고하십시오.
CONFIG_NET_SOCKETS_SOCKOPT_TLS option으로 secure socket을 활성화합니다. DTLS support는 CONFIG_NET_SOCKETS_ENABLE_DTLS option으로 활성화합니다.
TLS credential subsystem
Secure socket에서 사용하기 전에 TLS credential을 system에 등록해야 합니다. 자세한 내용은 tls_credential_add()를 참고하십시오.
특정 TLS credential을 system에 등록하면 tag라고 부르는 sec_tag_t type의 numeric value가 할당됩니다. 이후 secure socket을 socket option으로 설정할 때 이 값으로 credential을 참조합니다.
System에 등록할 수 있는 TLS credential type은 다음과 같습니다.
TLS_CREDENTIAL_CA_CERTIFICATETLS_CREDENTIAL_SERVER_CERTIFICATETLS_CREDENTIAL_PRIVATE_KEYTLS_CREDENTIAL_PSKTLS_CREDENTIAL_PSK_ID
ca_certificate array에 든 CA certificate를 등록하는 예는 다음과 같습니다.
ret = tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE,
ca_certificate, sizeof(ca_certificate));기본적으로 DER format certificate를 지원합니다. PEM support는 mbedTLS setting에서 활성화할 수 있습니다.
Secure Socket 생성
Secure protocol type을 지정해 secure socket을 만들 수 있습니다.
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);생성한 뒤 socket option으로 설정합니다. 예를 들어 다음처럼 CA certificate와 hostname을 설정할 수 있습니다.
sec_tag_t sec_tag_opt[] = {
CA_CERTIFICATE_TAG,
};
ret = setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST,
sec_tag_opt, sizeof(sec_tag_opt));char host[] = "google.com";
ret = setsockopt(sock, SOL_TLS, TLS_HOSTNAME, host, sizeof(host));설정을 마친 secure socket은 일반 TCP socket처럼 사용할 수 있습니다.
여러 Zephyr sample이 secure socket으로 통신합니다. 예를 들어 Echo Server sample 또는 HTTP GET sample을 참고하십시오.
Secure Sockets option
Secure socket management option은 Doxygen group secure_sockets_options에서 제공합니다.
Socket offloading
Zephyr는 offloaded socket이라고 부르는 custom socket implementation을 등록할 수 있습니다. External IP stack을 제공하고 socket-like API를 노출하는 device를 자연스럽게 통합하는 기능입니다.
CONFIG_NET_SOCKETS_OFFLOAD option으로 socket offloading을 활성화합니다. 새 socket implementation을 등록하려는 network driver는 NET_SOCKET_OFFLOAD_REGISTER macro를 사용해야 합니다. Macro parameter는 다음과 같습니다.
socket_name- Socket implementation의 임의 name입니다.
prio- Socket implementation priority입니다. Priority가 높을수록 새 socket을 만들 때 먼저 처리합니다. Numeric value가 낮을수록 priority가 높습니다.
_family- Offloaded socket이 구현하는 socket family입니다.
AF_UNSPEC은 모든 family를 뜻합니다. _is_supported- 특정 socket family·type·protocol을 이 implementation이 지원하는지 확인하는 filtering function입니다.
_handlersocket()API와 호환되며 offloaded socket을 생성하는 function입니다.
모든 offloaded socket implementation은 struct socket_op_vtable에 명시된 socket API 집합도 구현해야 합니다.
Socket 생성용으로 등록한 function은 zvfs_reserve_fd()로 새 file descriptor를 할당해야 합니다. 특정 offloaded socket 생성에 필요한 추가 작업은 descriptor 할당 뒤에 수행합니다. 생성에 성공하면 마지막 단계에서 zvfs_finalize_typed_fd() 또는 zvfs_finalize_fd()로 descriptor를 finalize해야 합니다. Finalize function은 offloaded socket API를 구현하는 struct socket_op_vtable과 optional socket context data pointer를 함께 등록합니다.
마지막으로 offloaded network interface를 initialize할 때 net_if_socket_offload_set()으로 interface가 offloaded임을 표시해야 합니다. 이 function은 NET_SOCKET_OFFLOAD_REGISTER에 전달한 것과 같은 offloaded socket 생성 function을 network interface에 등록합니다.
Offloaded socket 생성
socket()으로 새 socket을 생성하면 network stack은 등록된 native·offloaded socket implementation을 모두 순회합니다. 높은 priority부터 처리합니다. 각 implementation의 address family를 확인하고 일치하거나 AF_UNSPEC으로 등록됐다면 _is_supported를 호출해 나머지 parameter를 검사합니다. 요구를 처음 충족한 implementation, 즉 _is_supported가 true를 반환한 implementation의 _handler가 새 socket을 생성합니다.
따라서 socket priority가 중요합니다. 여러 implementation이 같은 family·type·protocol을 지원하면 system이 먼저 처리한 implementation이 socket을 생성합니다. System default가 돼야 할 implementation에 가장 높은 priority를 줘야 합니다.
Native socket priority는 Kconfig로 설정합니다. Native TLS socket은 CONFIG_NET_SOCKETS_TLS_PRIORITY, 나머지 native socket은 CONFIG_NET_SOCKETS_PRIORITY_DEFAULT를 사용합니다.
여러 offloaded interface 처리
socket() function은 socket이 사용할 network interface를 지정할 수 없습니다. 따라서 같은 socket type을 지원하는 offloaded implementation이 여러 개 있으면 특정 implementation을 선택할 수 없습니다. Native socket과 offloaded socket이 system에 함께 있을 때도 같은 문제가 생깁니다.
이 문제를 해결하기 위해 socket dispatcher라는 특별한 socket implementation이 도입됐습니다. 이 module의 유일한 목적은 socket의 첫 operation이 실행될 때까지 실제 socket 생성을 미루는 것입니다. 그러면 SO_BINDTODEVICE socket option으로 socket을 특정 network interface, 따라서 특정 offloaded implementation에 bind할 수 있습니다. CONFIG_NET_SOCKETS_OFFLOAD_DISPATCHER로 dispatcher를 활성화합니다.
활성화한 뒤 application은 setsockopt()로 사용할 network interface를 지정할 수 있습니다.
/* A "dispatcher" socket is created */
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct ifreq ifreq = {
.ifr_name = "SimpleLink"
};
/* The socket is "dispatched" to a particular network interface
* (offloaded or not).
*/
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq));예제는 먼저 dispatcher UDP socket을 만들고 ifreq.ifr_name에 SimpleLink를 지정한 다음, SOL_SOCKET level의 SO_BINDTODEVICE로 해당 interface에 dispatch합니다. Interface가 offloaded인지 여부와 관계없이 같은 방식입니다.
Native와 offloaded socket이 모두 TLS를 지원한다면 TLS_NATIVE option으로 native TLS socket을 생성하도록 지정할 수 있습니다. 이어서 underlying socket을 특정 network interface에 bind할 수 있습니다.
/* A "dispatcher" socket is created */
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);
int tls_native = 1;
/* The socket is "dispatched" to a native TLS socket implmeentation.
* The underlying socket is a "dispatcher" socket now.
*/
setsockopt(sock, SOL_TLS, TLS_NATIVE, &tls_native, sizeof(tls_native));
struct ifreq ifreq = {
.ifr_name = "SimpleLink"
};
/* The underlying socket is "dispatched" to a particular network interface
* (offloaded or not).
*/
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq));SO_BINDTODEVICE를 사용하지 않으면 첫 socket API call에서 default priority와 filtering rule에 따라 socket이 dispatch됩니다.
API Reference
BSD Sockets
Doxygen group bsd_sockets가 BSD Sockets API를 제공합니다.
TLS Credentials
Doxygen group tls_credentials가 TLS credential API를 제공합니다.
Source
출처
영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.