]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s3:rpc_server/witness: add implementation based on CTDB_SRVID_IPREALLOCATED and ctdbd...
authorStefan Metzmacher <metze@samba.org>
Wed, 9 Aug 2023 18:24:52 +0000 (20:24 +0200)
committerStefan Metzmacher <metze@samba.org>
Fri, 26 Jan 2024 17:00:33 +0000 (17:00 +0000)
The design is relatively simple in the end:

- We use ctdbd_all_ip_foreach() in order to build an
  in memory list of interfaces(ip addresses) and
  record if:
  - they are currently available or not
  - if they node local or not

- The current list is would we use for the
  GetInterfaceList() call.

- Register[Ex] will create an in memory structure
  holding a queue for pending AsyncNotify requests.

- Unregister() will cancel pending AsyncNotify requests and
  let them return NOT_FOUND.

- CTDB_SRVID_IPREALLOCATED messages will cause we refresh
  with ctdbd_all_ip_foreach():
  - this will detect changes in the interface state
    and remove stale interfaces.
  - for each change the list of registrations is checked
    for a matching ip address and a RESOURCE_CHANGE
    will be scheduled in the queue of the registration,
    the started queue will trigger AsyncNotify responses

- We also register the connections with ctdb in order
  to give other nodes a chance to generate tickle-acks
  for the witness tcp connections.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Günther Deschner <gd@samba.org>
source3/rpc_server/witness/srv_witness_nt.c

index 21bdae3a188986e615f47296c96d4b8ed5f7677d..5f8897a260ff40264e9b9351ecb98feec061108d 100644 (file)
@@ -1,7 +1,9 @@
 /*
  *  Unix SMB/CIFS implementation.
  *
- *  Copyright (C) 2023 Stefan Metzmacher
+ *  Copyright (C) 2012,2023 Stefan Metzmacher
+ *  Copyright (C) 2015 Guenther Deschner
+ *  Copyright (C) 2018 Samuel Cabrero
  *
  *  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
  */
 
 #include "includes.h"
+#include "ctdbd_conn.h"
+#include "ctdb/protocol/protocol.h"
+#include "ctdb_srvids.h"
+#include "messages.h"
+#include "lib/messages_ctdb.h"
+#include "lib/global_contexts.h"
+#include "lib/util/util_tdb.h"
+#include "lib/util/util_str_escape.h"
+#include "source3/include/util_tdb.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "lib/param/param.h"
+#include "lib/util/tevent_werror.h"
+#include "lib/tsocket/tsocket.h"
 #include "librpc/rpc/dcesrv_core.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/auth.h"
+#include "librpc/gen_ndr/ndr_witness.h"
 #include "librpc/gen_ndr/ndr_witness_scompat.h"
 #include "rpc_server/rpc_server.h"
 
+#define SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION 0x01
+
+struct swn_service_interface;
+struct swn_service_registration;
+struct swn_service_async_notify_state;
+
+struct swn_service_globals {
+       struct dcesrv_context *dce_ctx;
+       struct db_context *dce_conn_register;
+
+       const char *server_global_name;
+       uint32_t local_vnn;
+
+       struct {
+               bool valid;
+               uint64_t generation;
+               struct swn_service_interface *list;
+       } interfaces;
+
+       struct {
+               uint32_t unused_timeout_secs;
+               struct swn_service_registration *list;
+       } registrations;
+};
+
+struct swn_service_interface {
+       struct swn_service_interface *prev, *next;
+
+       const char *group_name;
+       struct samba_sockaddr addr;
+       enum witness_interfaceInfo_state state;
+       bool local_iface;
+       uint32_t current_vnn;
+       uint64_t change_generation;
+       uint64_t check_generation;
+};
+
+struct swn_service_registration {
+       struct swn_service_registration *prev, *next;
+
+       struct swn_service_globals *swn;
+
+       struct {
+               struct policy_handle handle;
+               void *ptr;
+       } key;
+
+       struct {
+               enum witness_version version;
+               const char *computer_name;
+       } client;
+
+       const char *net_name;
+       const char *share_name;
+       struct samba_sockaddr ip_address;
+
+       struct {
+               bool triggered;
+               /*
+                * We only do ip based RESOURCE_CHANGE notifications for now
+                * and it means we do just one notification at a time
+                * and don't need to queue pending notifications.
+                */
+               enum witness_interfaceInfo_state last_ip_state;
+       } change_notification;
+
+       struct {
+               struct timeval create_time;
+               struct timeval last_time;
+               uint32_t unused_timeout_secs;
+               struct timeval expire_time;
+               struct tevent_timer *timer;
+       } usage;
+
+       struct {
+               /*
+                * In order to let a Windows server 2022
+                * correctly re-register after moving
+                * to a new connection, we force an
+                * unregistration after 5 seconds.
+                *
+                * It means the client gets WERR_NOT_FOUND
+                * from a pending AsyncNotify() and calls
+                * Unregister() (which also gets WERR_NOT_FOUND).
+                * Then the client calls GetInterfaceList()
+                * and RegisterEx() again.
+                */
+               struct tevent_timer *timer;
+       } forced_unregister;
+
+       struct {
+               uint32_t timeout_secs;
+               struct tevent_queue *queue;
+               struct swn_service_async_notify_state *list;
+       } async_notify;
+};
+
+static struct swn_service_globals *swn_globals = NULL;
+
+static int swn_service_globals_destructor(struct swn_service_globals *swn)
+{
+       SMB_ASSERT(swn == swn_globals);
+       swn_globals = NULL;
+
+       while (swn->registrations.list != NULL) {
+               /*
+                * NO TALLOC_FREE() because of DLIST_REMOVE()
+                * in swn_service_registration_destructor()
+                */
+               talloc_free(swn->registrations.list);
+       }
+
+       return 0;
+}
+
+static void swn_service_async_notify_reg_destroyed(struct swn_service_async_notify_state *state);
+
+static int swn_service_registration_destructor(struct swn_service_registration *reg)
+{
+       struct swn_service_globals *swn = reg->swn;
+
+       tevent_queue_stop(reg->async_notify.queue);
+       while (reg->async_notify.list != NULL) {
+               swn_service_async_notify_reg_destroyed(reg->async_notify.list);
+       }
+
+       DLIST_REMOVE(swn->registrations.list, reg);
+       reg->swn = NULL;
+
+       /*
+        * make sure to drop the policy/context handle from
+        * the assoc_group
+        */
+       TALLOC_FREE(reg->key.ptr);
+
+       return 0;
+}
+
+static void swn_service_registration_update_usage(struct swn_service_registration *reg,
+                                                 struct timeval now)
+{
+       uint64_t expire_timeout_secs = 0;
+       uint64_t max_expire_timeout_secs = TIME_T_MAX;
+
+       reg->usage.last_time = now;
+
+       if (max_expire_timeout_secs > reg->usage.last_time.tv_sec) {
+               max_expire_timeout_secs -= reg->usage.last_time.tv_sec;
+       } else {
+               /*
+                * This should never happen unless
+                * a 32 bit system hits its limit
+                */
+               max_expire_timeout_secs = 0;
+       }
+
+       if (tevent_queue_length(reg->async_notify.queue) != 0) {
+               expire_timeout_secs += reg->async_notify.timeout_secs;
+       }
+
+       expire_timeout_secs += reg->usage.unused_timeout_secs;
+       expire_timeout_secs = MIN(expire_timeout_secs, max_expire_timeout_secs);
+
+       reg->usage.expire_time = timeval_add(&reg->usage.last_time,
+                                            expire_timeout_secs, 0);
+
+       if (expire_timeout_secs == 0) {
+               /*
+                * No timer needed, witness v1
+                * or max_expire_timeout_secs = 0
+                */
+               TALLOC_FREE(reg->usage.timer);
+       }
+
+       if (reg->usage.timer == NULL) {
+               /* no timer to update */
+               reg->usage.expire_time = (struct timeval) { .tv_sec = TIME_T_MAX, };
+               return;
+       }
+
+       tevent_update_timer(reg->usage.timer, reg->usage.expire_time);
+}
+
+static void swn_service_registration_unused(struct tevent_context *ev,
+                                           struct tevent_timer *te,
+                                           struct timeval current_time,
+                                           void *private_data)
+{
+       struct swn_service_registration *reg =
+               talloc_get_type_abort(private_data,
+               struct swn_service_registration);
+
+       reg->usage.timer = NULL;
+
+       TALLOC_FREE(reg);
+}
+
+static void swn_service_registration_force_unregister(struct tevent_context *ev,
+                                                     struct tevent_timer *te,
+                                                     struct timeval current_time,
+                                                     void *private_data)
+{
+       struct swn_service_registration *reg =
+               talloc_get_type_abort(private_data,
+               struct swn_service_registration);
+
+       reg->forced_unregister.timer = NULL;
+
+       TALLOC_FREE(reg);
+}
+
+static int swn_service_ctdb_ipreallocated(struct tevent_context *ev,
+                                         uint32_t src_vnn, uint32_t dst_vnn,
+                                         uint64_t dst_srvid,
+                                         const uint8_t *msg, size_t msglen,
+                                         void *private_data);
+
+static NTSTATUS swn_service_init_globals(struct dcesrv_context *dce_ctx)
+{
+       struct swn_service_globals *swn = NULL;
+       const char *realm = NULL;
+       const char *nbname = NULL;
+       int ret;
+       bool ok;
+
+       if (swn_globals != NULL) {
+               SMB_ASSERT(swn_globals->dce_ctx == dce_ctx);
+               return NT_STATUS_OK;
+       }
+
+       swn = talloc_zero(dce_ctx, struct swn_service_globals);
+       if (swn == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       swn->dce_ctx = dce_ctx;
+
+       swn->dce_conn_register = db_open_rbt(swn);
+       if (swn->dce_conn_register == NULL) {
+               TALLOC_FREE(swn);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       nbname = lpcfg_netbios_name(dce_ctx->lp_ctx);
+       realm = lpcfg_realm(dce_ctx->lp_ctx);
+       if (realm != NULL && realm[0] != '\0') {
+               char *name = NULL;
+
+               name = talloc_asprintf(swn, "%s.%s", nbname, realm);
+               if (name == NULL) {
+                       TALLOC_FREE(swn);
+                       return NT_STATUS_NO_MEMORY;
+               }
+               ok = strlower_m(name);
+               if (!ok) {
+                       TALLOC_FREE(swn);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+               swn->server_global_name = name;
+       } else {
+               swn->server_global_name = talloc_strdup(swn, nbname);
+               if (swn->server_global_name == NULL) {
+                       TALLOC_FREE(swn);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       }
+
+       swn->local_vnn = get_my_vnn();
+
+       ret = register_with_ctdbd(messaging_ctdb_connection(),
+                                 CTDB_SRVID_IPREALLOCATED,
+                                 swn_service_ctdb_ipreallocated,
+                                 swn);
+       if (ret != 0) {
+               TALLOC_FREE(swn);
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       swn->registrations.unused_timeout_secs = 30;
+
+       talloc_set_destructor(swn, swn_service_globals_destructor);
+       swn_globals = swn;
+       return NT_STATUS_OK;
+}
+
+static struct swn_service_interface *swn_service_interface_by_addr(
+                                       struct swn_service_globals *swn,
+                                       const struct samba_sockaddr *addr)
+{
+       struct swn_service_interface *iface = NULL;
+
+       for (iface = swn->interfaces.list; iface != NULL; iface = iface->next) {
+               bool ok;
+
+               ok = sockaddr_equal(&iface->addr.u.sa, &addr->u.sa);
+               if (ok) {
+                       return iface;
+               }
+       }
+
+       return NULL;
+}
+
+static void swn_service_interface_changed(struct swn_service_globals *swn,
+                                         struct swn_service_interface *iface)
+{
+       struct swn_service_registration *reg = NULL;
+       char addr[INET6_ADDRSTRLEN] = { 0, };
+
+       print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss);
+       DBG_NOTICE("addr[%s] state[%u] local_iface[%u] "
+                  "current_vnn[%"PRIu32"] generation[%"PRIu64"][%"PRIu64"]\n",
+                  addr,
+                  iface->state,
+                  iface->local_iface,
+                  iface->current_vnn,
+                  iface->change_generation,
+                  iface->check_generation);
+
+       for (reg = swn->registrations.list; reg != NULL; reg = reg->next) {
+               bool match;
+
+               /*
+                * We only check the ip address,
+                * we do not make real use of the group name.
+                */
+
+               match = sockaddr_equal(&reg->ip_address.u.sa,
+                                      &iface->addr.u.sa);
+               if (!match) {
+                       continue;
+               }
+
+               if (reg->change_notification.last_ip_state
+                   != WITNESS_STATE_UNAVAILABLE)
+               {
+                       /*
+                        * Remember the current state unless we already
+                        * hit WITNESS_STATE_UNAVAILAVLE before we notified
+                        * the client
+                        */
+                       reg->change_notification.last_ip_state = iface->state;
+               }
+
+               reg->change_notification.triggered = true;
+
+               tevent_queue_start(reg->async_notify.queue);
+       }
+
+       return;
+}
+
+static NTSTATUS swn_service_add_or_update_interface(struct swn_service_globals *swn,
+                                       const char *group_name,
+                                       const struct samba_sockaddr *addr,
+                                       enum witness_interfaceInfo_state state,
+                                       bool local_iface,
+                                       uint32_t current_vnn)
+{
+       struct swn_service_interface *iface = NULL;
+       bool changed = false;
+       bool force_unavailable = false;
+       bool filter;
+
+       if (addr->u.sa.sa_family != AF_INET &&
+           addr->u.sa.sa_family != AF_INET6)
+       {
+               /*
+                * We only support ipv4 and ipv6
+                */
+               return NT_STATUS_OK;
+       }
+
+       filter = is_loopback_addr(&addr->u.sa);
+       if (filter) {
+               return NT_STATUS_OK;
+       }
+       filter = is_linklocal_addr(&addr->u.ss);
+       if (filter) {
+               return NT_STATUS_OK;
+       }
+
+       for (iface = swn->interfaces.list; iface != NULL; iface = iface->next) {
+               bool match;
+
+               match = strequal(group_name, iface->group_name);
+               if (!match) {
+                       continue;
+               }
+
+               match = sockaddr_equal(&addr->u.sa, &iface->addr.u.sa);
+               if (!match) {
+                       continue;
+               }
+
+               break;
+       }
+
+       if (iface == NULL) {
+               iface = talloc_zero(swn, struct swn_service_interface);
+               if (iface == NULL) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               iface->group_name = talloc_strdup(iface, group_name);
+               if (iface->group_name == NULL) {
+                       TALLOC_FREE(iface);
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               iface->addr = *addr;
+
+               iface->state = WITNESS_STATE_UNKNOWN;
+               iface->current_vnn = NONCLUSTER_VNN;
+               DLIST_ADD_END(swn->interfaces.list, iface);
+
+               iface->change_generation = swn->interfaces.generation;
+       }
+
+       if (iface->state != state) {
+               changed = true;
+               iface->state = state;
+       }
+
+       if (iface->current_vnn != current_vnn) {
+               changed = true;
+               if (iface->current_vnn != NONCLUSTER_VNN) {
+                       force_unavailable = true;
+               }
+               iface->current_vnn = current_vnn;
+       }
+
+       if (iface->local_iface != local_iface) {
+               changed = true;
+               force_unavailable = true;
+               iface->local_iface = local_iface;
+       }
+
+       iface->check_generation = swn->interfaces.generation;
+
+       if (!changed) {
+               return NT_STATUS_OK;
+       }
+
+       iface->change_generation = swn->interfaces.generation;
+
+       if (force_unavailable) {
+               iface->state = WITNESS_STATE_UNAVAILABLE;
+       }
+
+       swn_service_interface_changed(swn, iface);
+
+       if (force_unavailable) {
+               iface->state = state;
+       }
+
+       return NT_STATUS_OK;
+};
+
+static int swn_service_ctdb_all_ip_cb(uint32_t total_ip_count,
+                                     const struct sockaddr_storage *ip,
+                                     uint32_t pinned_vnn,
+                                     uint32_t current_vnn,
+                                     void *private_data)
+{
+       struct swn_service_globals *swn =
+               talloc_get_type_abort(private_data,
+               struct swn_service_globals);
+       enum witness_interfaceInfo_state state = WITNESS_STATE_UNKNOWN;
+       struct samba_sockaddr addr = {
+               .u = {
+                       .ss = *ip,
+               },
+       };
+       NTSTATUS status;
+       bool local_iface = false;
+
+       SMB_ASSERT(swn->local_vnn != NONCLUSTER_VNN);
+
+       if (current_vnn == NONCLUSTER_VNN) {
+               /*
+                * No node hosts this address
+                */
+               state = WITNESS_STATE_UNAVAILABLE;
+       } else {
+               state = WITNESS_STATE_AVAILABLE;
+       }
+
+       if (current_vnn == swn->local_vnn || pinned_vnn == swn->local_vnn) {
+               local_iface = true;
+       }
+
+       status = swn_service_add_or_update_interface(swn,
+                                                    swn->server_global_name,
+                                                    &addr,
+                                                    state,
+                                                    local_iface,
+                                                    current_vnn);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("swn_service_add_or_update_interface() failed: %s\n",
+                       nt_errstr(status));
+               return map_errno_from_nt_status(status);
+       }
+
+       return 0;
+}
+
+static NTSTATUS swn_service_reload_interfaces(struct dcesrv_context *dce_ctx)
+{
+       struct swn_service_interface *iface = NULL;
+       struct swn_service_interface *next = NULL;
+       bool include_node_ips = false;
+       bool include_public_ips = true;
+       int ret;
+       NTSTATUS status;
+
+       status = swn_service_init_globals(dce_ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       if (swn_globals->interfaces.valid) {
+               return NT_STATUS_OK;
+       }
+
+       swn_globals->interfaces.generation += 1;
+
+       include_node_ips = lpcfg_parm_bool(dce_ctx->lp_ctx,
+                                          NULL,
+                                          "rpcd witness",
+                                          "include node ips",
+                                          include_node_ips);
+       include_public_ips = lpcfg_parm_bool(dce_ctx->lp_ctx,
+                                            NULL,
+                                            "rpcd witness",
+                                            "include public ips",
+                                            include_public_ips);
+
+       ret = ctdbd_all_ip_foreach(messaging_ctdb_connection(),
+                                  include_node_ips,
+                                  include_public_ips,
+                                  swn_service_ctdb_all_ip_cb,
+                                  swn_globals);
+       if (ret != 0) {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       for (iface = swn_globals->interfaces.list; iface != NULL; iface = next) {
+               next = iface->next;
+
+               if (iface->check_generation == swn_globals->interfaces.generation) {
+                       continue;
+               }
+
+               status = swn_service_add_or_update_interface(swn_globals,
+                                                            iface->group_name,
+                                                            &iface->addr,
+                                                            WITNESS_STATE_UNAVAILABLE,
+                                                            iface->local_iface,
+                                                            iface->current_vnn);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+               }
+
+               DLIST_REMOVE(swn_globals->interfaces.list, iface);
+               TALLOC_FREE(iface);
+       }
+
+       /* CTDB_SRVID_IPREALLOCATED is still registered */
+
+       swn_globals->interfaces.valid = true;
+       return NT_STATUS_OK;
+}
+
+static int swn_service_ctdb_ipreallocated(struct tevent_context *ev,
+                                         uint32_t src_vnn, uint32_t dst_vnn,
+                                         uint64_t dst_srvid,
+                                         const uint8_t *msg, size_t msglen,
+                                         void *private_data)
+{
+       struct swn_service_globals *swn =
+               talloc_get_type_abort(private_data,
+               struct swn_service_globals);
+       NTSTATUS status;
+
+       DBG_DEBUG("PID[%d] swn[%p] IPREALLOCATED\n", getpid(), swn);
+
+       swn->interfaces.valid = false;
+       status = swn_service_reload_interfaces(swn->dce_ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               swn->interfaces.valid = false;
+               return 0;
+       }
+
+       return 0;
+}
+
+struct swn_dcesrv_connection {
+       struct db_context *rbt;
+       struct dcesrv_connection *conn;
+       struct samba_sockaddr cli_addr;
+       struct samba_sockaddr srv_addr;
+       char addr[INET6_ADDRSTRLEN];
+};
+
+static int swn_dcesrv_connection_release_ip(struct tevent_context *ev,
+                                           uint32_t src_vnn,
+                                           uint32_t dst_vnn,
+                                           uint64_t dst_srvid,
+                                           const uint8_t *msg,
+                                           size_t msglen,
+                                           void *private_data)
+{
+       struct swn_dcesrv_connection *sc =
+               talloc_get_type_abort(private_data,
+               struct swn_dcesrv_connection);
+       struct dcesrv_connection *conn = sc->conn;
+       const char *ip = NULL;
+       const char *addr = sc->addr;
+       const char *p = addr;
+
+       if (conn->terminate != NULL) {
+               /* avoid recursion */
+               return 0;
+       }
+
+       if (msglen == 0) {
+               return 0;
+       }
+       if (msg[msglen-1] != '\0') {
+               return 0;
+       }
+
+       ip = (const char *)msg;
+
+       if (strncmp("::ffff:", addr, 7) == 0) {
+               p = addr + 7;
+       }
+
+       DBG_DEBUG("Got release IP message for %s, our address is %s\n", ip, p);
+
+       if ((strcmp(p, ip) == 0) || ((p != addr) && strcmp(addr, ip) == 0)) {
+               DBG_NOTICE("Got release IP message for our IP %s - exiting immediately\n",
+                          ip);
+               talloc_free(sc);
+               dcesrv_terminate_connection(conn, "CTDB_SRVID_RELEASE_IP");
+               return EADDRNOTAVAIL;
+       }
+
+       return 0;
+
+}
+
+static int swn_dcesrv_connection_destructor(struct swn_dcesrv_connection *sc)
+{
+       struct ctdbd_connection *cconn = messaging_ctdb_connection();
+       struct dcesrv_connection *conn = sc->conn;
+       uintptr_t conn_ptr = (uintptr_t)conn;
+       NTSTATUS status;
+       TDB_DATA key;
+
+       key = make_tdb_data((uint8_t *)&conn_ptr, sizeof(conn_ptr));
+
+       status = dbwrap_delete(sc->rbt, key);
+       SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+       if (cconn == NULL) {
+               return 0;
+       }
+
+       ctdbd_unregister_ips(cconn,
+                            &sc->srv_addr.u.ss,
+                            &sc->cli_addr.u.ss,
+                            swn_dcesrv_connection_release_ip,
+                            sc);
+
+       return 0;
+}
+
+static NTSTATUS dcesrv_interface_witness_register_ips(struct dcesrv_connection *conn)
+{
+       struct ctdbd_connection *cconn = messaging_ctdb_connection();
+       struct dcesrv_context *dce_ctx = conn->dce_ctx;
+       const struct tsocket_address *client_address =
+               dcesrv_connection_get_remote_address(conn);
+       const struct tsocket_address *server_address =
+               dcesrv_connection_get_local_address(conn);
+       NTSTATUS status;
+       uintptr_t conn_ptr = (uintptr_t)conn;
+       struct swn_dcesrv_connection *sc = NULL;
+       uintptr_t sc_ptr;
+       const char *addr = NULL;
+       TDB_DATA key;
+       TDB_DATA val;
+       bool exists;
+       int ret;
+
+       status = swn_service_init_globals(dce_ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("swn_service_init_globals() failed: %s\n",
+                       nt_errstr(status));
+               return status;
+       }
+
+       key = make_tdb_data((uint8_t *)&conn_ptr, sizeof(conn_ptr));
+
+       exists = dbwrap_exists(swn_globals->dce_conn_register, key);
+       if (exists) {
+               /* Already registered */
+               return NT_STATUS_OK;
+       }
+
+       sc = talloc_zero(conn, struct swn_dcesrv_connection);
+       if (sc == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       sc->rbt = swn_globals->dce_conn_register;
+       sc->conn = conn;
+
+       if (tsocket_address_is_inet(client_address, "ip")) {
+               ssize_t sret;
+
+               sret = tsocket_address_bsd_sockaddr(client_address,
+                                                   &sc->cli_addr.u.sa,
+                                                   sizeof(sc->cli_addr.u.ss));
+               if (sret == -1) {
+                       TALLOC_FREE(sc);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+               sc->cli_addr.sa_socklen = sret;
+       }
+
+       if (tsocket_address_is_inet(server_address, "ip")) {
+               ssize_t sret;
+
+               sret = tsocket_address_bsd_sockaddr(server_address,
+                                                   &sc->srv_addr.u.sa,
+                                                   sizeof(sc->srv_addr.u.ss));
+               if (sret == -1) {
+                       TALLOC_FREE(sc);
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+               sc->srv_addr.sa_socklen = sret;
+       }
+
+       addr = print_sockaddr(sc->addr, sizeof(sc->addr), &sc->srv_addr.u.ss);
+       if (addr == NULL) {
+               TALLOC_FREE(sc);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       sc_ptr = (uintptr_t)sc;
+       val = make_tdb_data((uint8_t *)&sc_ptr, sizeof(sc_ptr));
+
+       status = dbwrap_store(sc->rbt, key, val, TDB_INSERT);
+       if (!NT_STATUS_IS_OK(status)) {
+               TALLOC_FREE(sc);
+               return status;
+       }
+
+       talloc_set_destructor(sc, swn_dcesrv_connection_destructor);
+
+
+       ret = ctdbd_register_ips(cconn,
+                                &sc->srv_addr.u.ss,
+                                &sc->cli_addr.u.ss,
+                                swn_dcesrv_connection_release_ip,
+                                sc);
+       if (ret != 0) {
+               TALLOC_FREE(sc);
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       return NT_STATUS_OK;
+}
+
+#define DCESRV_INTERFACE_WITNESS_BIND(context, iface) \
+       dcesrv_interface_witness_bind(context, iface)
+static NTSTATUS dcesrv_interface_witness_bind(struct dcesrv_connection_context *context,
+                                             const struct dcesrv_interface *iface)
+{
+       NTSTATUS status;
+
+       status = dcesrv_interface_witness_register_ips(context->conn);
+       if (!NT_STATUS_IS_OK(status)) {
+               /*
+                * This is not really critical, so we just print
+                * as warning...
+                */
+               DBG_WARNING("dcesrv_interface_witness_register_ips() failed: %s\n",
+                           nt_errstr(status));
+       }
+
+       /*
+        * [MS-SWN] Section 7. If the authentication level is not
+        * integrity or privacy level, Windows servers will fail the call
+        * with access denied
+        */
+       return dcesrv_interface_bind_require_integrity(context, iface);
+}
+
 /****************************************************************
  _witness_GetInterfaceList
 ****************************************************************/
 WERROR _witness_GetInterfaceList(struct pipes_struct *p,
                                 struct witness_GetInterfaceList *r)
 {
-       p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
-       return WERR_NOT_SUPPORTED;
+       struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx;
+       struct swn_service_interface *iface = NULL;
+       struct witness_interfaceList *list = NULL;
+       size_t num_interfaces = 0;
+       NTSTATUS status;
+
+       status = swn_service_reload_interfaces(dce_ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               return ntstatus_to_werror(status);
+       }
+
+       for (iface = swn_globals->interfaces.list; iface != NULL; iface = iface->next) {
+               num_interfaces += 1;
+       }
+
+       list = talloc_zero(p->mem_ctx, struct witness_interfaceList);
+       if (list == NULL) {
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+       list->interfaces = talloc_zero_array(list,
+                                            struct witness_interfaceInfo,
+                                            num_interfaces);
+       if (list->interfaces == NULL) {
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+
+       for (iface = swn_globals->interfaces.list; iface != NULL; iface = iface->next) {
+               struct witness_interfaceInfo *info =
+                       &list->interfaces[list->num_interfaces++];
+               char addr[INET6_ADDRSTRLEN] = { 0, };
+               const char *ipv4 = "0.0.0.0";
+               const char *ipv6 = "::";
+               uint32_t flags = 0;
+
+               print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss);
+               if (iface->addr.u.sa.sa_family == AF_INET) {
+                       flags |= WITNESS_INFO_IPv4_VALID;
+                       ipv4 = addr;
+               } else if (iface->addr.u.sa.sa_family == AF_INET6) {
+                       flags |= WITNESS_INFO_IPv6_VALID;
+                       ipv6 = addr;
+               }
+
+               if (!iface->local_iface) {
+                       /*
+                        * If it's not a local interface
+                        * it is able to serve as
+                        * witness server
+                        */
+                       flags |= WITNESS_INFO_WITNESS_IF;
+               }
+
+               info->group_name = talloc_strdup(list, iface->group_name);
+               if (info->group_name == NULL) {
+                       return WERR_NOT_ENOUGH_MEMORY;
+               }
+               info->version = WITNESS_V2; /* WitnessServiceVersion; */
+               info->state = iface->state;
+               info->ipv4 = talloc_strdup(list, ipv4);
+               if (info->ipv4 == NULL) {
+                       return WERR_NOT_ENOUGH_MEMORY;
+               }
+               info->ipv6 = talloc_strdup(list, ipv6);
+               if (info->ipv6 == NULL) {
+                       return WERR_NOT_ENOUGH_MEMORY;
+               }
+               info->flags = flags;
+       }
+
+       *r->out.interface_list = list;
+       return WERR_OK;
+}
+
+static WERROR swn_server_registration_create(struct swn_service_globals *swn,
+                                            struct pipes_struct *p,
+                                            const struct witness_RegisterEx *r,
+                                            const struct swn_service_interface *iface,
+                                            struct swn_service_registration **preg)
+{
+       struct swn_service_registration *reg = NULL;
+
+       /*
+        * [MS-SWN] 3.1.4.5
+        * The server MUST create a WitnessRegistration entry as follows and
+        * insert it into the WitnessRegistrationList.
+        */
+       reg = talloc_zero(p->mem_ctx, struct swn_service_registration);
+       if (reg == NULL) {
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+       reg->swn = swn;
+
+       reg->client.version = r->in.version;
+
+       /*
+        * all other string values are checked against
+        * well known expected values.
+        *
+        * So we better escape the client_computer_name
+        * if it contains strange things...
+        */
+       reg->client.computer_name = log_escape(reg, r->in.client_computer_name);
+       if (reg->client.computer_name == NULL) {
+               TALLOC_FREE(reg);
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+
+       reg->net_name = talloc_strdup(reg, r->in.net_name);
+       if (reg->net_name == NULL) {
+               TALLOC_FREE(reg);
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+
+       reg->ip_address = iface->addr;
+
+       if (r->in.share_name != NULL) {
+               reg->share_name = talloc_strdup(reg, r->in.share_name);
+               if (reg->share_name == NULL) {
+                       TALLOC_FREE(reg);
+                       return WERR_NOT_ENOUGH_MEMORY;
+               }
+       }
+
+       reg->async_notify.timeout_secs = r->in.timeout;
+       reg->async_notify.queue = tevent_queue_create(reg, "async_notify");
+       if (reg->async_notify.queue == NULL) {
+               TALLOC_FREE(reg);
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+       tevent_queue_stop(reg->async_notify.queue);
+
+       reg->usage.create_time = p->dce_call->time;
+       reg->usage.unused_timeout_secs =
+               swn_globals->registrations.unused_timeout_secs;
+       /*
+        * swn_service_registration_update_usage() below
+        * will update the timer to its real expire time!
+        */
+       reg->usage.expire_time = (struct timeval) { .tv_sec = TIME_T_MAX, };
+       reg->usage.timer = tevent_add_timer(p->dce_call->event_ctx,
+                                           reg,
+                                           reg->usage.expire_time,
+                                           swn_service_registration_unused,
+                                           reg);
+       if (reg->usage.timer == NULL) {
+               TALLOC_FREE(reg);
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+       swn_service_registration_update_usage(reg, reg->usage.create_time);
+
+       reg->key.ptr = create_policy_hnd(p, &reg->key.handle,
+                                        SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION,
+                                        reg);
+       if (reg->key.ptr == NULL) {
+               TALLOC_FREE(reg);
+               return WERR_NO_SYSTEM_RESOURCES;
+       }
+
+       DLIST_ADD_END(swn_globals->registrations.list, reg);
+       talloc_set_destructor(reg, swn_service_registration_destructor);
+
+       *preg = reg;
+       return WERR_OK;
+}
+
+static WERROR swn_server_check_net_name(struct swn_service_globals *swn,
+                                       const char *net_name)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       char *stripped_net_name = NULL;
+       char *p = NULL;
+       bool ok;
+
+       ok = strequal(swn->server_global_name, net_name);
+       if (ok) {
+               TALLOC_FREE(frame);
+               return WERR_OK;
+       }
+
+       stripped_net_name = talloc_strdup(frame, net_name);
+       if (stripped_net_name == NULL) {
+               TALLOC_FREE(frame);
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+
+       p = strchr(stripped_net_name, '.');
+       if (p != NULL) {
+               *p = '\0';
+       }
+
+       ok = is_myname(stripped_net_name);
+       if (ok) {
+               TALLOC_FREE(frame);
+               return WERR_OK;
+       }
+
+       TALLOC_FREE(frame);
+       return WERR_INVALID_PARAMETER;
 }
 
 /****************************************************************
@@ -40,8 +1055,124 @@ WERROR _witness_GetInterfaceList(struct pipes_struct *p,
 WERROR _witness_Register(struct pipes_struct *p,
                         struct witness_Register *r)
 {
-       p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
-       return WERR_NOT_SUPPORTED;
+       struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx;
+       struct swn_service_registration *reg = NULL;
+       struct samba_sockaddr addr = { .sa_socklen = 0, };
+       struct swn_service_interface *iface = NULL;
+       const struct witness_RegisterEx rex = {
+               .in = {
+                       .version = r->in.version,
+                       .net_name = r->in.net_name,
+                       .ip_address = r->in.ip_address,
+                       .client_computer_name = r->in.client_computer_name,
+               },
+       };
+       NTSTATUS status;
+       WERROR werr;
+       bool ok;
+
+       /*
+        * [MS-SWN] 3.1.4.2
+        * If the Version field of the request is not 0x00010001, the server
+        * MUST stop processing the request and return the error code
+        * ERROR_REVISION_MISMATCH
+        */
+       if (r->in.version != WITNESS_V1) {
+               return WERR_REVISION_MISMATCH;
+       }
+
+       /*
+        * [MS-SWN] 3.1.4.2
+        * If NetName, IpAddress or ClientComputerName is NULL, the server
+        * MUST fail the request and return the error code
+        * ERROR_INVALID_PARAMETER
+        */
+       if (r->in.net_name == NULL ||
+           r->in.ip_address == NULL ||
+           r->in.client_computer_name == NULL)
+       {
+               return WERR_INVALID_PARAMETER;
+       }
+
+       status = swn_service_reload_interfaces(dce_ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               return ntstatus_to_werror(status);
+       }
+
+       /*
+        * [MS-SWN] 3.1.4.2
+        * If the NetName parameter is not equal to ServerGlobalName, the
+        * server MUST fail the request and return the error code
+        * ERROR_INVALID_PARAMETER
+        */
+       werr = swn_server_check_net_name(swn_globals, r->in.net_name);
+       if (!W_ERROR_IS_OK(werr)) {
+               DBG_INFO("Invalid net_name[%s], "
+                        "server_global_name[%s]: %s\n",
+                        log_escape(p->mem_ctx, r->in.net_name),
+                        swn_globals->server_global_name,
+                        win_errstr(werr));
+               return werr;
+       }
+
+       /*
+        * [MS-SWN] 3.1.4.2
+        * The server MUST enumerate the shares by calling NetrShareEnum as
+        * specified in [MS-SRVS] section 3.1.4.8. In the enumerated list,
+        * if any of the shares has shi*_type set to STYPE_CLUSTER_SOFS, as
+        * specified in [MS-SRVS] section 2.2.2.4, the server MUST search for
+        * an Interface in InterfaceList, where Interface.IPv4Address or
+        * Interface.IPv6Address matches the IpAddress parameter based on its
+        * format. If no matching entry is found, the server MUST fail the
+        * request and return the error code ERROR_INVALID_STATE.
+        *
+        * After clarifying this point with dochelp:
+        * A server only sets the CLUSTER_SOFS, CLUSTER_FS, or CLUSTER_DFS bit
+        * flags in NetrShareEnum when the call is local and never will be set
+        * by remote calls. This point only serves the purpose of identifying
+        * the SOFS shares.
+        * The server returns the error code ERROR_INVALID_STATE if the share
+        * enumeration of SMB share resources fails with any error other than
+        * STATUS_SUCCESS. It’s not the absence of SOFS shares, or just the
+        * call to ShareEnum. When the server enumerates the shares by calling
+        * NetrShareEnum locally, it tries to filter out only shares with
+        * STYPE_CLUSTER_SOFS. The scope of 'If no matching entry is found'
+        * is broader. Even if shares have STYPE_CLUSTER_SOFS, but no match
+        * could be found with the IpAddress, ERROR_INVALID_STATE will be
+        * returned too.
+        *
+        * In a CTDB cluster, all shares in the clustered filesystem are
+        * scale-out. We can skip this check and proceed to find the matching
+        * IP address.
+        */
+       ok = is_ipaddress(r->in.ip_address);
+       if (!ok) {
+               DBG_INFO("Invalid ip_address[%s]\n",
+                        log_escape(p->mem_ctx, r->in.ip_address));
+               return WERR_INVALID_STATE;
+       }
+       ok = interpret_string_addr(&addr.u.ss,
+                                  r->in.ip_address,
+                                  AI_PASSIVE|AI_NUMERICHOST);
+       if (!ok) {
+               DBG_INFO("Invalid ip_address[%s]\n",
+                        log_escape(p->mem_ctx, r->in.ip_address));
+               return WERR_INVALID_STATE;
+       }
+       iface = swn_service_interface_by_addr(swn_globals, &addr);
+       if (iface == NULL) {
+               DBG_INFO("Invalid ip_address[%s]\n",
+                        log_escape(p->mem_ctx, r->in.ip_address));
+               return WERR_INVALID_STATE;
+       }
+
+       werr = swn_server_registration_create(swn_globals, p, &rex, iface, &reg);
+       if (!W_ERROR_IS_OK(werr)) {
+               return werr;
+       }
+
+       *r->out.context_handle = reg->key.handle;
+       return WERR_OK;
 }
 
 
@@ -52,19 +1183,421 @@ WERROR _witness_Register(struct pipes_struct *p,
 WERROR _witness_UnRegister(struct pipes_struct *p,
                           struct witness_UnRegister *r)
 {
-       p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
-       return WERR_NOT_SUPPORTED;
+       bool ok;
+
+       /*
+        * [MS-SWN] 3.1.4.3
+        * The server MUST search for the WitnessRegistration in
+        * WitnessRegistrationList, where WitnessRegistration.RegistrationKey
+        * matches the pContext parameter. If no matching entry is found,
+        * the server SHOULD<4> stop processing the request and return the
+        * error code ERROR_NOT_FOUND.
+        */
+       ok = close_policy_hnd(p, &r->in.context_handle);
+       if (!ok) {
+               if (p->fault_state != 0) {
+                       p->fault_state = 0;
+               }
+               return WERR_NOT_FOUND;
+       }
+
+       return WERR_OK;
 }
 
 /****************************************************************
  _witness_AsyncNotify
 ****************************************************************/
 
+struct swn_service_async_notify_state {
+       struct swn_service_async_notify_state *prev, *next;
+       struct tevent_context *ev;
+       struct tevent_req *req;
+       TALLOC_CTX *r_mem_ctx;
+       struct witness_AsyncNotify *r;
+       struct swn_service_registration *reg;
+       struct tevent_queue_entry *qe;
+};
+
+static void swn_service_async_notify_trigger(struct tevent_req *req,
+                                            void *private_data);
+
+static void swn_service_async_notify_cleanup(struct tevent_req *req,
+                                            enum tevent_req_state req_state)
+{
+       struct swn_service_async_notify_state *state =
+               tevent_req_data(req,
+               struct swn_service_async_notify_state);
+
+       TALLOC_FREE(state->qe);
+
+       if (state->reg != NULL) {
+               DLIST_REMOVE(state->reg->async_notify.list, state);
+               state->reg = NULL;
+       }
+}
+
+static void swn_service_async_notify_reg_destroyed(struct swn_service_async_notify_state *state)
+{
+       swn_service_async_notify_cleanup(state->req, TEVENT_REQ_USER_ERROR);
+       swn_service_async_notify_trigger(state->req, NULL);
+}
+
+static bool swn_service_async_notify_cancel(struct tevent_req *req)
+{
+       return false;
+}
+
+static struct tevent_req *swn_service_async_notify_send(TALLOC_CTX *mem_ctx,
+                                                       struct tevent_context *ev,
+                                                       TALLOC_CTX *r_mem_ctx,
+                                                       struct witness_AsyncNotify *r,
+                                                       struct swn_service_registration *reg)
+{
+       struct tevent_req *req = NULL;
+       struct swn_service_async_notify_state *state = NULL;
+       struct timeval now = timeval_current();
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct swn_service_async_notify_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->ev = ev;
+       state->req = req;
+       state->r_mem_ctx = r_mem_ctx;
+       state->r = r;
+       state->reg = reg;
+
+       /*
+        * triggered changes likely wakeup
+        * more than one waiter, so we better
+        * let all individual waiters go through
+        * a tevent_immediate round.
+        */
+       tevent_req_defer_callback(req, ev);
+
+       tevent_req_set_cleanup_fn(req, swn_service_async_notify_cleanup);
+       tevent_req_set_cancel_fn(req, swn_service_async_notify_cancel);
+
+       if (!reg->change_notification.triggered) {
+               tevent_queue_stop(reg->async_notify.queue);
+       }
+
+       DLIST_ADD_END(reg->async_notify.list, state);
+
+       state->qe = tevent_queue_add_entry(reg->async_notify.queue, ev, req,
+                                          swn_service_async_notify_trigger,
+                                          NULL)
+       if (tevent_req_nomem(state->qe, req)) {
+               return tevent_req_post(req, ev);
+       }
+
+       if (reg->async_notify.timeout_secs != 0) {
+               struct timeval endtime;
+               bool ok;
+
+               endtime = timeval_add(&now, reg->async_notify.timeout_secs, 0);
+               ok = tevent_req_set_endtime(req, ev, endtime);
+               if (!ok) {
+                       tevent_req_oom(req);
+                       return tevent_req_post(req, ev);
+               }
+       }
+
+       /*
+        * Once we added the queue entry
+        * swn_service_registration_update_usage()
+        * will adjust the registration expire time...
+        */
+       swn_service_registration_update_usage(state->reg, now);
+
+       /*
+        * Wait for trigger or timeout...
+        */
+       return req;
+}
+
+static void swn_service_async_notify_trigger(struct tevent_req *req,
+                                            void *private_data)
+{
+       struct swn_service_async_notify_state *state =
+               tevent_req_data(req,
+               struct swn_service_async_notify_state);
+       struct swn_service_registration *reg = state->reg;
+       struct witness_notifyResponse *resp = NULL;
+       WERROR forced_result = WERR_OK;
+       bool defer_forced_unregister = false;
+
+       if (reg == NULL) {
+               tevent_req_werror(req, WERR_NOT_FOUND);
+               return;
+       }
+
+       if (reg->change_notification.triggered) {
+               struct swn_service_globals *swn = reg->swn;
+               const struct swn_service_interface *iface = NULL;
+               union witness_notifyResponse_message *msgs = NULL;
+               char reg_ip[INET6_ADDRSTRLEN] = { 0, };
+               struct witness_ResourceChange *rc = NULL;
+               enum witness_interfaceInfo_state cur_state;
+
+               print_sockaddr(reg_ip, sizeof(reg_ip), &reg->ip_address.u.ss);
+
+               iface = swn_service_interface_by_addr(swn, &reg->ip_address);
+               if (iface != NULL) {
+                       cur_state = iface->state;
+               } else {
+                       /*
+                        * If the interface is no longer in our list
+                        * it must be unavailable
+                        */
+                       cur_state = WITNESS_STATE_UNAVAILABLE;
+               }
+               if (cur_state != WITNESS_STATE_AVAILABLE) {
+                       reg->change_notification.last_ip_state = cur_state;
+               }
+
+               resp = talloc_zero(state, struct witness_notifyResponse);
+               if (tevent_req_nomem(resp, req)) {
+                       return;
+               }
+
+               msgs = talloc_zero_array(resp,
+                                        union witness_notifyResponse_message,
+                                        1);
+               if (tevent_req_nomem(msgs, req)) {
+                       return;
+               }
+
+               resp->type = WITNESS_NOTIFY_RESOURCE_CHANGE;
+               resp->num = 0;
+               resp->messages = msgs;
+
+               rc = &msgs[resp->num].resource_change;
+
+               switch (reg->change_notification.last_ip_state) {
+               case WITNESS_STATE_AVAILABLE:
+                       rc->type = WITNESS_RESOURCE_STATE_AVAILABLE;
+                       break;
+               case WITNESS_STATE_UNAVAILABLE:
+                       rc->type = WITNESS_RESOURCE_STATE_UNAVAILABLE;
+                       break;
+               case WITNESS_STATE_UNKNOWN:
+                       rc->type = WITNESS_RESOURCE_STATE_UNKNOWN;
+                       break;
+               }
+
+               /*
+                * We use the ip address as resource name
+                */
+               rc->name = talloc_strdup(msgs, reg_ip);
+               if (tevent_req_nomem(rc->name, req)) {
+                       return;
+               }
+
+               resp->num += 1;
+
+               if (rc->type != WITNESS_RESOURCE_STATE_AVAILABLE) {
+                       /*
+                        * In order to let a Windows server 2022
+                        * correctly re-register after moving
+                        * to a new connection, we force an
+                        * unregistration after 5 seconds.
+                        *
+                        * It means the client gets WERR_NOT_FOUND
+                        * from a pending AsyncNotify() and calls
+                        * Unregister() (which also gets WERR_NOT_FOUND).
+                        * Then the client calls GetInterfaceList()
+                        * and RegisterEx() again.
+                        */
+                       defer_forced_unregister = true;
+               }
+
+               if (reg->change_notification.last_ip_state != cur_state) {
+                       /*
+                        * This means the last_ip_state was *not* available,
+                        * and the current_state *is* available.
+                        *
+                        * keep the queue running and return the available
+                        * message in the next run
+                        */
+                       reg->change_notification.last_ip_state = cur_state;
+                       goto finished;
+               }
+
+               reg->change_notification.triggered = false;
+               reg->change_notification.last_ip_state = WITNESS_STATE_UNKNOWN;
+               goto finished;
+       }
+
+finished:
+       if (!reg->change_notification.triggered) {
+               tevent_queue_stop(reg->async_notify.queue);
+       }
+
+       if (defer_forced_unregister) {
+               struct tevent_timer *te = NULL;
+
+               /*
+                * In order to let a Windows server 2022
+                * correctly re-register after moving
+                * to a new connection, we force an
+                * unregistration after 5 seconds.
+                *
+                * It means the client gets WERR_NOT_FOUND
+                * from a pending AsyncNotify() and calls
+                * Unregister() (which also gets WERR_NOT_FOUND).
+                * Then the client calls GetInterfaceList()
+                * and RegisterEx() again.
+                */
+               TALLOC_FREE(reg->forced_unregister.timer);
+               te = tevent_add_timer(state->ev,
+                                     reg,
+                                     timeval_current_ofs(5,0),
+                                     swn_service_registration_force_unregister,
+                                     reg);
+               if (tevent_req_nomem(te, req)) {
+                       return;
+               }
+               reg->forced_unregister.timer = te;
+       }
+
+       *state->r->out.response = talloc_move(state->r_mem_ctx, &resp);
+       state->r->out.result = forced_result;
+       if (!W_ERROR_IS_OK(forced_result)) {
+               tevent_req_werror(req, forced_result);
+               return;
+       }
+       tevent_req_done(req);
+}
+
+static WERROR swn_service_async_notify_recv(struct tevent_req *req)
+{
+       return tevent_req_simple_recv_werror(req);
+}
+
+struct _witness_AsyncNotify_state {
+       struct dcesrv_call_state *dce_call;
+       struct witness_AsyncNotify *r;
+       struct swn_service_registration *reg;
+       struct tevent_req *subreq;
+};
+
+static bool _witness_AsyncNotify_cancel(struct tevent_req *req);
+static void _witness_AsyncNotify_done(struct tevent_req *subreq);
+
 WERROR _witness_AsyncNotify(struct pipes_struct *p,
                            struct witness_AsyncNotify *r)
 {
-       p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
-       return WERR_NOT_SUPPORTED;
+       struct tevent_req *req = NULL;
+       struct _witness_AsyncNotify_state *state = NULL;
+       struct swn_service_registration *reg = NULL;
+       NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+
+       /*
+        * [MS-SWN] 3.1.4.4
+        * The server MUST search for the WitnessRegistration in
+        * WitnessRegistrationList, where WitnessRegistration.RegistrationKey
+        * matches the pContext parameter. If no matching entry is found, the
+        * server MUST fail the request and return the error code
+        * ERROR_NOT_FOUND.
+        */
+       reg = find_policy_by_hnd(p, &r->in.context_handle,
+                                SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION,
+                                struct swn_service_registration,
+                                &status);
+       if (!NT_STATUS_IS_OK(status)) {
+               if (p->fault_state != 0) {
+                       p->fault_state = 0;
+               }
+               return WERR_NOT_FOUND;
+       }
+
+       swn_service_registration_update_usage(reg, p->dce_call->time);
+
+       req = tevent_req_create(p->mem_ctx, &state,
+                               struct _witness_AsyncNotify_state);
+       if (req == NULL) {
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+       state->dce_call = p->dce_call;
+       state->r = r;
+       state->reg = reg;
+
+       tevent_req_set_cancel_fn(req, _witness_AsyncNotify_cancel);
+
+       state->subreq = swn_service_async_notify_send(state,
+                                                     state->dce_call->event_ctx,
+                                                     state->dce_call,
+                                                     state->r,
+                                                     state->reg);
+       if (state->subreq == NULL) {
+               TALLOC_FREE(state);
+               return WERR_NOT_ENOUGH_MEMORY;
+       }
+       tevent_req_set_callback(state->subreq,
+                               _witness_AsyncNotify_done,
+                               req);
+
+       state->dce_call->subreq = req;
+       state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC;
+       return WERR_EVENT_PENDING; /* hidden by DCESRV_CALL_STATE_FLAG_ASYNC */
+}
+
+static bool _witness_AsyncNotify_cancel(struct tevent_req *req)
+{
+       struct _witness_AsyncNotify_state *state =
+               tevent_req_data(req,
+               struct _witness_AsyncNotify_state);
+       struct dcesrv_call_state *dce_call = state->dce_call;
+
+       SMB_ASSERT(dce_call->subreq == req);
+       dce_call->subreq = NULL;
+
+       TALLOC_FREE(state->subreq);
+
+       if (dce_call->got_orphaned) {
+               dce_call->fault_code = DCERPC_FAULT_SERVER_UNAVAILABLE;
+       } else {
+               dce_call->fault_code = DCERPC_NCA_S_FAULT_CANCEL;
+       }
+       state->r->out.result = WERR_RPC_S_CALL_CANCELLED;
+
+       dcesrv_async_reply(dce_call);
+       return true;
+}
+
+static void _witness_AsyncNotify_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct _witness_AsyncNotify_state *state =
+               tevent_req_data(req,
+               struct _witness_AsyncNotify_state);
+       struct dcesrv_call_state *dce_call = state->dce_call;
+
+       SMB_ASSERT(dce_call->subreq == req);
+       dce_call->subreq = NULL;
+
+       SMB_ASSERT(state->subreq == subreq);
+       state->subreq = NULL;
+
+       state->r->out.result = swn_service_async_notify_recv(subreq);
+       TALLOC_FREE(subreq);
+
+       if (W_ERROR_EQUAL(state->r->out.result, WERR_NOT_FOUND)) {
+               state->reg = NULL;
+       }
+
+       if (state->reg != NULL &&
+           tevent_queue_length(state->reg->async_notify.queue) == 0)
+       {
+               struct timeval now = timeval_current();
+               swn_service_registration_update_usage(state->reg, now);
+       }
+
+       dcesrv_async_reply(dce_call);
 }
 
 /****************************************************************
@@ -74,8 +1607,136 @@ WERROR _witness_AsyncNotify(struct pipes_struct *p,
 WERROR _witness_RegisterEx(struct pipes_struct *p,
                           struct witness_RegisterEx *r)
 {
-       p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
-       return WERR_NOT_SUPPORTED;
+       struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx;
+       struct swn_service_registration *reg = NULL;
+       struct samba_sockaddr addr = { .sa_socklen = 0, };
+       struct swn_service_interface *iface = NULL;
+       NTSTATUS status;
+       WERROR werr;
+       bool ok;
+
+       /*
+        * [MS-SWN] 3.1.4.5
+        * If the Version field of the request is not 0x00020000, the server
+        * MUST stop processing the request and return the error code
+        * ERROR_REVISION_MISMATCH
+        */
+       if (r->in.version != WITNESS_V2) {
+               return WERR_REVISION_MISMATCH;
+       }
+
+       /*
+        * [MS-SWN] 3.1.4.5
+        * If NetName, IpAddress or ClientComputerName is NULL, the server
+        * MUST fail the request and return the error code
+        * ERROR_INVALID_PARAMETER
+        */
+       if (r->in.net_name == NULL ||
+           r->in.ip_address == NULL ||
+           r->in.client_computer_name == NULL)
+       {
+               return WERR_INVALID_PARAMETER;
+       }
+
+       status = swn_service_reload_interfaces(dce_ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               return ntstatus_to_werror(status);
+       }
+
+       /*
+        * [MS-SWN] 3.1.4.5
+        * If the NetName parameter is not equal to ServerGlobalName, the
+        * server MUST fail the request and return the error code
+        * ERROR_INVALID_PARAMETER
+        */
+       werr = swn_server_check_net_name(swn_globals, r->in.net_name);
+       if (!W_ERROR_IS_OK(werr)) {
+               DBG_INFO("Invalid net_name[%s], "
+                        "server_global_name[%s]: %s\n",
+                        log_escape(p->mem_ctx, r->in.net_name),
+                        swn_globals->server_global_name,
+                        win_errstr(werr));
+               return werr;
+       }
+
+       /*
+        * [MS-SWN] 3.1.4.5
+        * If ShareName is not NULL, the server MUST enumerate the shares by
+        * calling NetrShareEnum as specified in [MS-SRVS] section 3.1.4.8.
+        * If the enumeration fails or if no shares are returned, the server
+        * MUST return the error code ERROR_INVALID_STATE.
+        *
+        * If none of the shares in the list has shi*_type set to
+        * STYPE_CLUSTER_SOFS as specified in [MS-SRVS] section 3.1.4.8,
+        * the server MUST ignore ShareName.
+        *
+        * In a CTDB cluster, all shares in the clustered filesystem are
+        * scale-out. Check if the provided share name is in a clustered FS
+        */
+       if (r->in.share_name != NULL) {
+               char *save_share = NULL;
+               int cmp;
+
+               /*
+                * For now we allow all shares...
+                *
+                * The main reason is that windows
+                * clients typically connect as
+                * machine account, so things like %U
+                * wouldn't work anyway.
+                *
+                * And in the end it's just a string,
+                * so we just check it's sane.
+                */
+               save_share = log_escape(p->mem_ctx, r->in.share_name);
+               if (save_share == NULL) {
+                       return WERR_NOT_ENOUGH_MEMORY;
+               }
+               cmp = strcmp(save_share, r->in.share_name);
+               if (cmp != 0) {
+                       DBG_INFO("Invalid share_name[%s]\n",
+                                save_share);
+                       return WERR_INVALID_STATE;
+               }
+               TALLOC_FREE(save_share);
+       }
+
+       /*
+        * [MS-SWN] 3.1.4.5
+        * The server MUST search for an Interface in InterfaceList, where
+        * Interface.IPv4Address or Interface.IPv6Address matches the
+        * IpAddress parameter based on its format. If no matching entry is
+        * found, the server MUST fail the request and return the error code
+        * ERROR_INVALID_STATE.
+        */
+       ok = is_ipaddress(r->in.ip_address);
+       if (!ok) {
+               DBG_INFO("Invalid ip_address[%s]\n",
+                        log_escape(p->mem_ctx, r->in.ip_address));
+               return WERR_INVALID_STATE;
+       }
+       ok = interpret_string_addr(&addr.u.ss,
+                                  r->in.ip_address,
+                                  AI_PASSIVE|AI_NUMERICHOST);
+       if (!ok) {
+               DBG_INFO("Invalid ip_address[%s]\n",
+                        log_escape(p->mem_ctx, r->in.ip_address));
+               return WERR_INVALID_STATE;
+       }
+       iface = swn_service_interface_by_addr(swn_globals, &addr);
+       if (iface == NULL) {
+               DBG_INFO("Invalid ip_address[%s]\n",
+                        log_escape(p->mem_ctx, r->in.ip_address));
+               return WERR_INVALID_STATE;
+       }
+
+       werr = swn_server_registration_create(swn_globals, p, r, iface, &reg);
+       if (!W_ERROR_IS_OK(werr)) {
+               return werr;
+       }
+
+       *r->out.context_handle = reg->key.handle;
+       return WERR_OK;
 }
 
 /* include the generated boilerplate */