]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
libcephfs_proxy: implement client side negotiation
authorXavi Hernandez <xhernandez@gmail.com>
Tue, 11 Feb 2025 16:56:49 +0000 (17:56 +0100)
committerXavi Hernandez <xhernandez@gmail.com>
Fri, 14 Feb 2025 15:09:47 +0000 (16:09 +0100)
This commit implements the client side of the protocol to negotiate the
features that can be used.

Signed-off-by: Xavi Hernandez <xhernandez@gmail.com>
src/libcephfs_proxy/proxy.h
src/libcephfs_proxy/proxy_link.c
src/libcephfs_proxy/proxy_link.h

index 239cd5d1cb542ca3e3ecfda0e2140fe7a8753ff3..6f2fec8a59b9993186ee1b6d764610ad012fd0dc 100644 (file)
@@ -9,7 +9,11 @@
 #include <stdbool.h>
 
 #define LIBCEPHFSD_MAJOR 0
+
+// Legacy version without negotiation support
 #define LIBCEPHFSD_MINOR 2
+// Current version with negotiation support
+#define LIBCEPHFSD_MINOR_NEG 3
 
 #define LIBCEPHFS_LIB_CLIENT 0xe3e5f0e8 // 'ceph' xor 0x80808080
 
index 0f3cd6588edc54189fa096b1230717494420c0f8..1d0bb90f7119cce4e6eb03afd6d49de3fc2eb328 100644 (file)
@@ -8,6 +8,11 @@
 #include "proxy_helpers.h"
 #include "proxy_log.h"
 
+typedef struct _proxy_version {
+       uint16_t major;
+       uint16_t minor;
+} proxy_version_t;
+
 #define DEFINE_VERSION(_num) [_num] = NEG_VERSION_SIZE(_num)
 
 static uint32_t negotiation_sizes[PROXY_LINK_NEGOTIATE_VERSION + 1] = {
@@ -297,6 +302,168 @@ static int32_t proxy_link_negotiate_read(proxy_link_t *link, int32_t sd,
        return 0;
 }
 
+static int32_t proxy_link_negotiate_check(proxy_link_negotiate_t *local,
+                                         proxy_link_negotiate_t *remote,
+                                         proxy_link_negotiate_cbk_t cbk)
+{
+       uint32_t supported, enabled;
+       int32_t err;
+
+       if (local->v0.version > remote->v0.version) {
+               local->v0.version = remote->v0.version;
+               local->v0.size = remote->v0.size;
+       }
+
+       if (remote->v0.version == 0) {
+               /* Legacy peer. If we require any feature, the peer won't
+                * support it, so we can't continue */
+               if (local->v1.required != 0) {
+                       return proxy_log(LOG_ERR, ENOTSUP,
+                                        "The peer doesn't support any "
+                                        "features");
+               }
+
+               /* The peer is running the old version, but it is compatible
+                * with us, so everything is fine and the connection can be
+                * completed successfully with all features disabled. */
+
+               local->v1.enabled = 0;
+
+               proxy_log(LOG_INFO, 0,
+                         "Connected to legacy peer. No features enabled");
+
+               goto validate;
+       }
+
+       supported = local->v1.supported & remote->v1.supported;
+       local->v1.supported = supported;
+
+       if ((local->v1.required & ~supported) != 0) {
+               return proxy_log(LOG_ERR, ENOTSUP,
+                                "Required features are not supported by the "
+                                "peer");
+       }
+       if ((remote->v1.required & ~supported) != 0) {
+               return proxy_log(LOG_ERR, ENOTSUP,
+                                "The peer requires some features that are "
+                                "not supported");
+       }
+
+       /* For now just combine the desired features from each side. In the
+        * future, if there are two features to implement the same, they may
+        * be chosen here and enable just one of them. */
+
+       enabled = (local->v1.enabled | remote->v1.enabled) & supported;
+       local->v1.enabled = enabled;
+
+       /* NEG_VERSION: Implement handling of negotiate extensions. */
+
+validate:
+       if (cbk != NULL) {
+               err = cbk(local);
+               if (err < 0) {
+                       return err;
+               }
+       }
+
+       return 0;
+}
+
+static int32_t proxy_link_negotiate_client(proxy_link_t *link, int32_t sd,
+                                          proxy_link_negotiate_t *neg,
+                                          proxy_link_negotiate_cbk_t cbk)
+{
+       proxy_link_negotiate_t remote;
+       int32_t err;
+
+       err = proxy_link_write(link, sd, neg, neg->v0.size);
+       if (err < 0) {
+               return err;
+       }
+
+       err = proxy_link_negotiate_read(link, sd, &remote);
+       if (err < 0) {
+               return err;
+       }
+
+       err = proxy_link_negotiate_check(neg, &remote, cbk);
+       if (err < 0) {
+               return err;
+       }
+
+       if (remote.v0.version == 0) {
+               return 0;
+       }
+
+       /* For version 1 and higher, send the agreed enabled features. */
+       err =  proxy_link_write(link, sd, &neg->v1.enabled,
+                               sizeof(neg->v1.enabled));
+       if (err < 0) {
+               return err;
+       }
+
+       return 0;
+}
+
+int32_t proxy_link_handshake_client(proxy_link_t *link, int32_t sd,
+                                   proxy_link_negotiate_t *neg,
+                                   proxy_link_negotiate_cbk_t cbk)
+{
+       proxy_link_negotiate_t legacy;
+       proxy_version_t version;
+       uint32_t id;
+       int32_t err;
+
+       /* To make negotiation backward compatible, we send the same data that
+        * the previous version was sending, so if the server is still running
+        * the old version it will get the correct message and accept the
+        * connection.
+        *
+        * We use ancillary data to tell the server that further negotiation
+        * will happen. If the server runs the old version, it will ignore
+        * this data and return the normal answer, containing an old version
+        * number. If the server supports negotiation, it will answer with the
+        * new version number.
+        */
+
+       id = LIBCEPHFS_LIB_CLIENT;
+
+       err = proxy_link_ctrl_send(sd, &id, sizeof(id), SCM_RIGHTS, &sd,
+                                  sizeof(sd));
+       if (err < 0) {
+               return err;
+       }
+
+       err = proxy_link_read(link, sd, &version, sizeof(version));
+       if (err < 0) {
+               return proxy_log(LOG_ERR, -err,
+                                "Failed to get initial answer from server");
+       }
+
+       if (version.major != LIBCEPHFSD_MAJOR) {
+               return proxy_log(LOG_ERR, ENOTSUP,
+                                "Unsupported major version received from "
+                                "server");
+       }
+
+       if (version.minor == LIBCEPHFSD_MINOR) {
+               /* The server doesn't support negotiation. */
+               proxy_link_negotiate_init_v0(&legacy, 0, 0);
+
+               return proxy_link_negotiate_check(neg, &legacy, cbk);
+       }
+
+       if (version.minor != LIBCEPHFSD_MINOR_NEG) {
+               return proxy_log(LOG_ERR, ENOTSUP,
+                                "Unsupported minor version recevied from "
+                                "server");
+       }
+
+       /* The server supports negotiation. Let's do it. */
+
+       return proxy_link_negotiate_client(link, sd, neg, cbk);
+}
+
 int32_t proxy_link_client(proxy_link_t *link, const char *path,
                          proxy_link_stop_t stop)
 {
index 09fa3be673462c2914490e2524c49687c09225ca..516e5a68a7ea0adb63c3a4d4eb58cf6eca72b1e2 100644 (file)
@@ -109,4 +109,8 @@ int32_t proxy_link_ctrl_send(int32_t sd, void *data, int32_t size, int32_t type,
 int32_t proxy_link_ctrl_recv(int32_t sd, void *data, int32_t size, int32_t type,
                             void *ctrl, int32_t *ctrl_size);
 
+int32_t proxy_link_handshake_client(proxy_link_t *link, int32_t sd,
+                                   proxy_link_negotiate_t *neg,
+                                   proxy_link_negotiate_cbk_t cbk);
+
 #endif