Re: [B.A.T.M.A.N.] [RFC v2 0/5] batman-adv netlink query API

Matthias Schiffer
2016-03-17T17:04:56Z

[-- Attachment #1.1.1: Type: text/plain, Size: 1947 bytes --]

On 03/17/2016 05:45 PM, Matthias Schiffer wrote:
> Hi,
> this is the second take of my netlink API patches. As mentioned before, the
> netlink API is superior to the current debugfs API in many aspects:
>
> * debugfs is broken (see PATCH 1 for details)
> * Netlink is namespace-aware, and can be used in unprivileged containers
> without problems
> * Netlink packets are more machine-readable than text files, and can be
> easily extended without potentially breaking userspace
> * On older kernels, seq_file can't fall back to vmalloc if kmalloc fails,
> which often leads to OOM when reading "originators" in large meshes, as
> the whole file must fit into a single buffer
>
> Of course, are also a few downsides; when the data is too big to fit into
> a single netlink packet, the provided data may be inconsistent (entries may
> be missing or duplicated.) This will happen in large meshes only, and be
> improbable in any case.
>
> The patches have been developed on top of the netns patchset, but should
> be applicable independently (maybe with minor changes.)
>
> All netlink queries returning lists of any kind can only be used with
> NLM_F_DUMP queries, so that arbitrarity large responses are possible (split
> across multiple packets if necessary.)
>
> At the moment, the following debugfs files have corresponding netlink APIs:
>
> * routing_algos
> * neighbors
> * originators
> * transtable_global
> * transtable_local
> * (hardinterfaces for a softif can be queried)
>
> The following files are still missing:
>
> * gateways
> * bla_claim_table
> * bla_backbone_table
> * dat_cache
> * nc_nodes
>
> Obviously, documentation is also a TODO. Comments about the API design are
> very welcome...
>
> Regards,
> Matthias
>
>

And here is the new version of the PoC userspace tool.
Build with:

gcc -o batnl batnl.c $$(pkg-config --cflags --libs libnl-1) -Wall

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.1.2: batnl.c --]
[-- Type: text/x-csrc; name="batnl.c", Size: 16521 bytes --]

#include <stdio.h>
#include <string.h>

#include <net/if.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "batman_adv.h"

static void usage()
{
fprintf(stderr, "Usage:\n\n");
fprintf(stderr, " batnl routing_algos\n");
fprintf(stderr, " batnl info <mesh>\n");
fprintf(stderr, " batnl interface <mesh>\n");
fprintf(stderr, " batnl translocal <mesh>\n");
fprintf(stderr, " batnl transglobal <mesh>\n");
fprintf(stderr, " batnl neighbors <mesh> [<hardif>]\n");
fprintf(stderr, " batnl originators <mesh> [<hardif>]\n");
exit(2);
}

static int print_error(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg)
{
fprintf(stderr, "Error received: %s\n", strerror(-nlerr->error));
exit(1);
}

static int stop_callback(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
int *error = nlmsg_data(nlh);

if (*error)
fprintf(stderr, "Error received: %s\n", strerror(-*error));

return NL_STOP;
}

static int routing_algos_callback(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct genlmsghdr *ghdr;
struct nlattr *attrs[BATADV_ATTR_MAX+1];

if (!genlmsg_valid_hdr(nlh, 0)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

ghdr = nlmsg_data(nlh);

if (ghdr->cmd != BATADV_CMD_GET_ROUTING_ALGOS)
return NL_OK;

if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), NULL)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

const char *algo_name = nla_get_string(attrs[BATADV_ATTR_ALGO_NAME]);

printf(" * %s\n", algo_name);

return NL_OK;
}

static void routing_algos(struct nl_handle *sock, int family)
{
struct nl_msg *msg;
struct nl_cb *cb;

msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, BATADV_CMD_GET_ROUTING_ALGOS, 1);

nl_send_auto_complete(sock, msg);

nlmsg_free(msg);

cb = nl_cb_alloc(NL_CB_DEFAULT);
nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, routing_algos_callback, NULL);
nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, stop_callback, NULL);
nl_cb_err(cb, NL_CB_CUSTOM, print_error, NULL);

nl_recvmsgs(sock, cb);
}

static int info_callback(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct genlmsghdr *ghdr;
struct nlattr *attrs[BATADV_ATTR_MAX+1];

if (!genlmsg_valid_hdr(nlh, 0)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

ghdr = nlmsg_data(nlh);

if (ghdr->cmd != BATADV_CMD_GET_MESH_INFO)
return NL_OK;

if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), NULL)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

const char *mesh_name = nla_get_string(attrs[BATADV_ATTR_MESH_IFNAME]);

if (attrs[BATADV_ATTR_HARD_IFNAME]) {
const char *version = nla_get_string(attrs[BATADV_ATTR_VERSION]);
const char *algo_name = nla_get_string(attrs[BATADV_ATTR_ALGO_NAME]);
const char *primary_if = nla_get_string(attrs[BATADV_ATTR_HARD_IFNAME]);
const uint8_t *primary_mac = nla_data(attrs[BATADV_ATTR_HARD_ADDRESS]);

printf("[B.A.T.M.A.N. adv %s, MainIF/MAC: %s/%02x:%02x:%02x:%02x:%02x:%02x (%s %s)]\n",
version, primary_if,
primary_mac[0], primary_mac[1], primary_mac[2],
primary_mac[3], primary_mac[4], primary_mac[5],
mesh_name, algo_name);

}
else {
printf("BATMAN mesh %s disabled\n", mesh_name);
}

return NL_STOP;
}

static void info(struct nl_handle *sock, int family, int ifindex)
{
struct nl_msg *msg;
struct nl_cb *cb;

msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, 0, BATADV_CMD_GET_MESH_INFO, 1);

nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, ifindex);

nl_send_auto_complete(sock, msg);

nlmsg_free(msg);

cb = nl_cb_alloc(NL_CB_DEFAULT);
nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, info_callback, NULL);
nl_cb_err(cb, NL_CB_CUSTOM, print_error, NULL);

nl_recvmsgs(sock, cb);
}

static int interface_callback(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct genlmsghdr *ghdr;
struct nlattr *attrs[BATADV_ATTR_MAX+1];

if (!genlmsg_valid_hdr(nlh, 0)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

ghdr = nlmsg_data(nlh);

if (ghdr->cmd != BATADV_CMD_GET_HARDIFS)
return NL_OK;

if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), NULL)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

const char *ifname = nla_get_string(attrs[BATADV_ATTR_HARD_IFNAME]);

printf("%s: %sactive\n", ifname, attrs[BATADV_ATTR_ACTIVE] ? "" : "in");

return NL_OK;
}

static void interface(struct nl_handle *sock, int family, int ifindex)
{
struct nl_msg *msg;
struct nl_cb *cb;

msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, BATADV_CMD_GET_HARDIFS, 1);

nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, ifindex);

nl_send_auto_complete(sock, msg);

nlmsg_free(msg);

cb = nl_cb_alloc(NL_CB_DEFAULT);
nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, interface_callback, NULL);
nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, stop_callback, NULL);
nl_cb_err(cb, NL_CB_CUSTOM, print_error, NULL);

nl_recvmsgs(sock, cb);
}

static int translocal_callback(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct genlmsghdr *ghdr;
struct nlattr *attrs[BATADV_ATTR_MAX+1];

if (!genlmsg_valid_hdr(nlh, 0)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

ghdr = nlmsg_data(nlh);

if (ghdr->cmd != BATADV_CMD_GET_TRANSTABLE_LOCAL)
return NL_OK;

if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), NULL)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

uint8_t *addr = nla_data(attrs[BATADV_ATTR_TT_ADDRESS]);
int16_t vid = nla_get_u16(attrs[BATADV_ATTR_TT_VID]);
uint32_t crc32 = nla_get_u32(attrs[BATADV_ATTR_TT_CRC32]);
uint32_t flags = nla_get_u32(attrs[BATADV_ATTR_TT_FLAGS]);
int last_seen_msecs = 0, last_seen_secs = 0;

char r = '.', p = '.', n = '.', x = '.', w = '.', i = '.';
if (flags & BATADV_TT_CLIENT_ROAM)
r = 'R';
if (flags & BATADV_TT_CLIENT_NEW)
n = 'N';
if (flags & BATADV_TT_CLIENT_PENDING)
x = 'X';
if (flags & BATADV_TT_CLIENT_WIFI)
w = 'W';
if (flags & BATADV_TT_CLIENT_ISOLA)
i = 'I';

if (flags & BATADV_TT_CLIENT_NOPURGE) {
p = 'P';
}
else {
last_seen_msecs = nla_get_u32(attrs[BATADV_ATTR_LAST_SEEN_MSECS]);
last_seen_secs = last_seen_msecs / 1000;
last_seen_msecs = last_seen_msecs % 1000;
}

printf(" * %02x:%02x:%02x:%02x:%02x:%02x %4i [%c%c%c%c%c%c] %3u.%03u (0x%.8x)\n",
addr[0], addr[1], addr[2],
addr[3], addr[4], addr[5],
vid, r, p, n, x, w, i,
last_seen_secs, last_seen_msecs,
crc32);

return NL_OK;
}

static void translocal(struct nl_handle *sock, int family, int ifindex)
{
struct nl_msg *msg;
struct nl_cb *cb;

msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, BATADV_CMD_GET_TRANSTABLE_LOCAL, 1);

nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, ifindex);

nl_send_auto_complete(sock, msg);

nlmsg_free(msg);

cb = nl_cb_alloc(NL_CB_DEFAULT);
nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, translocal_callback, NULL);
nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, stop_callback, NULL);
nl_cb_err(cb, NL_CB_CUSTOM, print_error, NULL);

nl_recvmsgs(sock, cb);
}

static int transglobal_callback(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct genlmsghdr *ghdr;
struct nlattr *attrs[BATADV_ATTR_MAX+1];

if (!genlmsg_valid_hdr(nlh, 0)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

ghdr = nlmsg_data(nlh);

if (ghdr->cmd != BATADV_CMD_GET_TRANSTABLE_GLOBAL)
return NL_OK;

if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), NULL)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

uint8_t *addr = nla_data(attrs[BATADV_ATTR_TT_ADDRESS]);
uint8_t *orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]);
int16_t vid = nla_get_u16(attrs[BATADV_ATTR_TT_VID]);
uint8_t ttvn = nla_get_u8(attrs[BATADV_ATTR_TT_TTVN]);
uint8_t last_ttvn = nla_get_u8(attrs[BATADV_ATTR_TT_LAST_TTVN]);
uint32_t crc32 = nla_get_u32(attrs[BATADV_ATTR_TT_CRC32]);
uint32_t flags = nla_get_u32(attrs[BATADV_ATTR_TT_FLAGS]);

char c = '+', r = '.', w = '.', i = '.', t = '.';
if (attrs[BATADV_ATTR_FLAG_BEST])
c = '*';
if (flags & BATADV_TT_CLIENT_ROAM)
r = 'R';
if (flags & BATADV_TT_CLIENT_WIFI)
w = 'W';
if (flags & BATADV_TT_CLIENT_ISOLA)
i = 'I';
if (flags & BATADV_TT_CLIENT_TEMP)
t = 'T';

printf(" %c %02x:%02x:%02x:%02x:%02x:%02x %4i (%3u) via %02x:%02x:%02x:%02x:%02x:%02x (%3u) (0x%.8x) [%c%c%c%c]\n",
c,
addr[0], addr[1], addr[2],
addr[3], addr[4], addr[5],
vid, ttvn,
orig[0], orig[1], orig[2],
orig[3], orig[4], orig[5],
last_ttvn, crc32, r, w, i, t);

return NL_OK;
}

static void transglobal(struct nl_handle *sock, int family, int ifindex)
{
struct nl_msg *msg;
struct nl_cb *cb;

msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, BATADV_CMD_GET_TRANSTABLE_GLOBAL, 1);

nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, ifindex);

nl_send_auto_complete(sock, msg);

nlmsg_free(msg);

cb = nl_cb_alloc(NL_CB_DEFAULT);
nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, transglobal_callback, NULL);
nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, stop_callback, NULL);
nl_cb_err(cb, NL_CB_CUSTOM, print_error, NULL);

nl_recvmsgs(sock, cb);
}

static int originators_callback(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct genlmsghdr *ghdr;
struct nlattr *attrs[BATADV_ATTR_MAX+1];

if (!genlmsg_valid_hdr(nlh, 0)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

ghdr = nlmsg_data(nlh);

if (ghdr->cmd != BATADV_CMD_GET_ORIGINATORS)
return NL_OK;

if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), NULL)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

uint8_t *orig = nla_data(attrs[BATADV_ATTR_ORIG_ADDRESS]);
uint8_t *neigh = nla_data(attrs[BATADV_ATTR_NEIGH_ADDRESS]);

char ifname[IF_NAMESIZE];
if (!if_indextoname(nla_get_u32(attrs[BATADV_ATTR_HARD_IFINDEX]), ifname))
ifname[0] = '\0';

char c = '+';
if (attrs[BATADV_ATTR_FLAG_BEST])
c = '*';

int last_seen_msecs, last_seen_secs;
last_seen_msecs = nla_get_u32(attrs[BATADV_ATTR_LAST_SEEN_MSECS]);
last_seen_secs = last_seen_msecs / 1000;
last_seen_msecs = last_seen_msecs % 1000;

if (attrs[BATADV_ATTR_THROUGHPUT]) {
unsigned throughput_mbits, throughput_kbits;
throughput_kbits = nla_get_u32(attrs[BATADV_ATTR_THROUGHPUT]);
throughput_mbits = throughput_kbits / 1000;
throughput_kbits = throughput_kbits % 1000;

printf(" %c %02x:%02x:%02x:%02x:%02x:%02x %4i.%03is (%9u.%1u) %02x:%02x:%02x:%02x:%02x:%02x [%10s]\n",
c,
orig[0], orig[1], orig[2],
orig[3], orig[4], orig[5],
last_seen_secs, last_seen_msecs,
throughput_mbits, throughput_kbits / 100,
neigh[0], neigh[1], neigh[2],
neigh[3], neigh[4], neigh[5],
ifname);
}
else {
uint8_t tq = nla_get_u8(attrs[BATADV_ATTR_TQ]);

printf(" %c %02x:%02x:%02x:%02x:%02x:%02x %4i.%03is (%3i) %02x:%02x:%02x:%02x:%02x:%02x [%10s]\n",
c,
orig[0], orig[1], orig[2],
orig[3], orig[4], orig[5],
last_seen_secs, last_seen_msecs, tq,
neigh[0], neigh[1], neigh[2],
neigh[3], neigh[4], neigh[5],
ifname);
}

return NL_OK;
}

static void originators(struct nl_handle *sock, int family, int ifindex, int hard_ifindex)
{
struct nl_msg *msg;
struct nl_cb *cb;

msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, BATADV_CMD_GET_ORIGINATORS, 1);

nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, ifindex);

if (hard_ifindex)
nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, hard_ifindex);

nl_send_auto_complete(sock, msg);

nlmsg_free(msg);

cb = nl_cb_alloc(NL_CB_DEFAULT);
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, originators_callback, NULL);
nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, stop_callback, NULL);
nl_cb_err(cb, NL_CB_CUSTOM, print_error, NULL);

nl_recvmsgs(sock, cb);
}

static int neighbors_callback(struct nl_msg *msg, void *arg)
{
struct nlmsghdr *nlh = nlmsg_hdr(msg);
struct genlmsghdr *ghdr;
struct nlattr *attrs[BATADV_ATTR_MAX+1];

if (!genlmsg_valid_hdr(nlh, 0)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

ghdr = nlmsg_data(nlh);

if (ghdr->cmd != BATADV_CMD_GET_NEIGHBORS)
return NL_OK;

if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0), genlmsg_len(ghdr), NULL)) {
fputs("Received invalid data from kernel.", stderr);
exit(1);
}

uint8_t *neigh = nla_data(attrs[BATADV_ATTR_NEIGH_ADDRESS]);

char ifname[IF_NAMESIZE];
if (!if_indextoname(nla_get_u32(attrs[BATADV_ATTR_HARD_IFINDEX]), ifname))
ifname[0] = '\0';

int last_seen_msecs, last_seen_secs;
last_seen_msecs = nla_get_u32(attrs[BATADV_ATTR_LAST_SEEN_MSECS]);
last_seen_secs = last_seen_msecs / 1000;
last_seen_msecs = last_seen_msecs % 1000;

if (attrs[BATADV_ATTR_THROUGHPUT]) {
unsigned throughput_mbits, throughput_kbits;
throughput_kbits = nla_get_u32(attrs[BATADV_ATTR_THROUGHPUT]);
throughput_mbits = throughput_kbits / 1000;
throughput_kbits = throughput_kbits % 1000;

printf("%02x:%02x:%02x:%02x:%02x:%02x %4i.%03is (%9u.%1u) [%10s]\n",
neigh[0], neigh[1], neigh[2],
neigh[3], neigh[4], neigh[5],
last_seen_secs, last_seen_msecs,
throughput_mbits, throughput_kbits / 100,
ifname);
}
else {
printf(" %10s %02x:%02x:%02x:%02x:%02x:%02x %4i.%03is\n",
ifname,
neigh[0], neigh[1], neigh[2],
neigh[3], neigh[4], neigh[5],
last_seen_secs, last_seen_msecs);
}

return NL_OK;
}

static void neighbors(struct nl_handle *sock, int family, int ifindex, int hard_ifindex)
{
struct nl_msg *msg;
struct nl_cb *cb;

msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, NLM_F_DUMP, BATADV_CMD_GET_NEIGHBORS, 1);

nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, ifindex);

if (hard_ifindex)
nla_put_u32(msg, BATADV_ATTR_HARD_IFINDEX, hard_ifindex);

nl_send_auto_complete(sock, msg);

nlmsg_free(msg);

cb = nl_cb_alloc(NL_CB_DEFAULT);
nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, neighbors_callback, NULL);
nl_cb_set (cb, NL_CB_FINISH, NL_CB_CUSTOM, stop_callback, NULL);
nl_cb_err(cb, NL_CB_CUSTOM, print_error, NULL);

nl_recvmsgs(sock, cb);
}

int main(int argc, char *argv[])
{
struct nl_handle *sock;
int family;

sock = nl_handle_alloc();
genl_connect(sock);

family = genl_ctrl_resolve(sock, BATADV_NL_NAME);
if (family < 0) {
fputs("Your kernel doesn't support the batman-adv netlink interface.\n", stderr);
exit(1);
}

if (argc < 2 || argc > 4)
usage();

int ifindex = 0;
if (argc > 2) {
ifindex = if_nametoindex(argv[2]);
if (!ifindex)
usage();
}

int hard_ifindex = 0;
if (argc > 3) {
hard_ifindex = if_nametoindex(argv[3]);
if (!hard_ifindex)
usage();
}

if (!strcmp(argv[1], "routing_algos"))
routing_algos(sock, family);
else if (!strcmp(argv[1], "info"))
info(sock, family, ifindex);
else if (!strcmp(argv[1], "interface"))
interface(sock, family, ifindex);
else if (!strcmp(argv[1], "translocal"))
translocal(sock, family, ifindex);
else if (!strcmp(argv[1], "transglobal"))
transglobal(sock, family, ifindex);
else if (!strcmp(argv[1], "originators"))
originators(sock, family, ifindex, hard_ifindex);
else if (!strcmp(argv[1], "neighbors"))
neighbors(sock, family, ifindex, hard_ifindex);
else
usage();

return 0;
}

[-- Attachment #2: OpenPGP digital signature --]