/* * Samba Unix/Linux SMB client library * Copyright (C) Volker Lendecke 2011 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "includes.h" #include "lib/addrchange.h" #include "../lib/util/tevent_ntstatus.h" #ifdef HAVE_LINUX_RTNETLINK_H #include "asm/types.h" #include "linux/netlink.h" #include "linux/rtnetlink.h" #include "lib/tsocket/tsocket.h" struct addrchange_context { struct tdgram_context *sock; }; NTSTATUS addrchange_context_create(TALLOC_CTX *mem_ctx, struct addrchange_context **pctx) { struct addrchange_context *ctx; struct sockaddr_nl addr; NTSTATUS status; int sock = -1; int res; bool ok; ctx = talloc(mem_ctx, struct addrchange_context); if (ctx == NULL) { return NT_STATUS_NO_MEMORY; } sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (sock == -1) { status = map_nt_error_from_unix(errno); goto fail; } ok = smb_set_close_on_exec(sock); if (!ok) { status = map_nt_error_from_unix(errno); goto fail; } res = set_blocking(sock, false); if (res == -1) { status = map_nt_error_from_unix(errno); goto fail; } /* * We're interested in address changes */ ZERO_STRUCT(addr); addr.nl_family = AF_NETLINK; addr.nl_groups = RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_IFADDR; res = bind(sock, (struct sockaddr *)(void *)&addr, sizeof(addr)); if (res == -1) { status = map_nt_error_from_unix(errno); goto fail; } res = tdgram_bsd_existing_socket(ctx, sock, &ctx->sock); if (res == -1) { status = map_nt_error_from_unix(errno); goto fail; } *pctx = ctx; return NT_STATUS_OK; fail: if (sock != -1) { close(sock); } TALLOC_FREE(ctx); return status; } struct addrchange_state { struct tevent_context *ev; struct addrchange_context *ctx; uint8_t *buf; struct tsocket_address *fromaddr; enum addrchange_type type; struct sockaddr_storage addr; }; static void addrchange_done(struct tevent_req *subreq); struct tevent_req *addrchange_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct addrchange_context *ctx) { struct tevent_req *req, *subreq; struct addrchange_state *state; req = tevent_req_create(mem_ctx, &state, struct addrchange_state); if (req == NULL) { return NULL; } state->ev = ev; state->ctx = ctx; subreq = tdgram_recvfrom_send(state, state->ev, state->ctx->sock); if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, state->ev); } tevent_req_set_callback(subreq, addrchange_done, req); return req; } static void addrchange_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct addrchange_state *state = tevent_req_data( req, struct addrchange_state); union { struct sockaddr sa; struct sockaddr_nl nl; struct sockaddr_storage ss; } fromaddr; struct nlmsghdr *h; struct ifaddrmsg *ifa; struct rtattr *rta; ssize_t received; int len; int err; bool found; received = tdgram_recvfrom_recv(subreq, &err, state, &state->buf, &state->fromaddr); TALLOC_FREE(subreq); if (received == -1) { DEBUG(10, ("tdgram_recvfrom_recv returned %s\n", strerror(err))); tevent_req_nterror(req, map_nt_error_from_unix(err)); return; } len = tsocket_address_bsd_sockaddr(state->fromaddr, &fromaddr.sa, sizeof(fromaddr)); if ((len != sizeof(fromaddr.nl) || fromaddr.sa.sa_family != AF_NETLINK)) { DEBUG(10, ("Got message from wrong addr\n")); goto retry; } if (fromaddr.nl.nl_pid != 0) { DEBUG(10, ("Got msg from pid %d, not from the kernel\n", (int)fromaddr.nl.nl_pid)); goto retry; } if (received < sizeof(struct nlmsghdr)) { DEBUG(10, ("received %d, expected at least %d\n", (int)received, (int)sizeof(struct nlmsghdr))); goto retry; } h = (struct nlmsghdr *)state->buf; if (h->nlmsg_len < sizeof(struct nlmsghdr)) { DEBUG(10, ("nlmsg_len=%d, expected at least %d\n", (int)h->nlmsg_len, (int)sizeof(struct nlmsghdr))); goto retry; } if (h->nlmsg_len > received) { DEBUG(10, ("nlmsg_len=%d, expected at most %d\n", (int)h->nlmsg_len, (int)received)); goto retry; } switch (h->nlmsg_type) { case RTM_NEWADDR: state->type = ADDRCHANGE_ADD; break; case RTM_DELADDR: state->type = ADDRCHANGE_DEL; break; default: DEBUG(10, ("Got unexpected type %d - ignoring\n", h->nlmsg_type)); goto retry; } if (h->nlmsg_len < sizeof(struct nlmsghdr)+sizeof(struct ifaddrmsg)) { DEBUG(10, ("nlmsg_len=%d, expected at least %d\n", (int)h->nlmsg_len, (int)(sizeof(struct nlmsghdr) +sizeof(struct ifaddrmsg)))); tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR); return; } ifa = (struct ifaddrmsg *)NLMSG_DATA(h); state->addr.ss_family = ifa->ifa_family; len = h->nlmsg_len - sizeof(struct nlmsghdr) + sizeof(struct ifaddrmsg); found = false; for (rta = IFA_RTA(ifa); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { if ((rta->rta_type != IFA_LOCAL) && (rta->rta_type != IFA_ADDRESS)) { continue; } switch (ifa->ifa_family) { case AF_INET: { struct sockaddr_in *v4_addr; v4_addr = (struct sockaddr_in *)(void *)&state->addr; if (RTA_PAYLOAD(rta) != sizeof(uint32_t)) { continue; } v4_addr->sin_addr.s_addr = *(uint32_t *)RTA_DATA(rta); found = true; break; } case AF_INET6: { struct sockaddr_in6 *v6_addr; v6_addr = (struct sockaddr_in6 *)(void *)&state->addr; if (RTA_PAYLOAD(rta) != sizeof(v6_addr->sin6_addr.s6_addr)) { continue; } memcpy(v6_addr->sin6_addr.s6_addr, RTA_DATA(rta), sizeof(v6_addr->sin6_addr.s6_addr)); found = true; break; } } } if (!found) { tevent_req_nterror(req, NT_STATUS_INVALID_ADDRESS); return; } tevent_req_done(req); return; retry: TALLOC_FREE(state->buf); TALLOC_FREE(state->fromaddr); subreq = tdgram_recvfrom_send(state, state->ev, state->ctx->sock); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, addrchange_done, req); } NTSTATUS addrchange_recv(struct tevent_req *req, enum addrchange_type *type, struct sockaddr_storage *addr) { struct addrchange_state *state = tevent_req_data( req, struct addrchange_state); NTSTATUS status; if (tevent_req_is_nterror(req, &status)) { tevent_req_received(req); return status; } *type = state->type; *addr = state->addr; tevent_req_received(req); return NT_STATUS_OK; } #else NTSTATUS addrchange_context_create(TALLOC_CTX *mem_ctx, struct addrchange_context **pctx) { return NT_STATUS_NOT_SUPPORTED; } struct tevent_req *addrchange_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct addrchange_context *ctx) { return NULL; } NTSTATUS addrchange_recv(struct tevent_req *req, enum addrchange_type *type, struct sockaddr_storage *addr) { return NT_STATUS_NOT_IMPLEMENTED; } #endif