nbd: Implement NBD_OPT_GO on server
NBD_OPT_EXPORT_NAME is lousy: per the NBD protocol, any failure requires us to close the connection rather than report an error. Therefore, upstream NBD recently added NBD_OPT_GO as the improved version of the option that does what we want [1], along with NBD_OPT_INFO that returns the same information but does not transition to transmission phase. [1] https://github.com/NetworkBlockDevice/nbd/blob/extension-info/doc/proto.md This is a first cut at the information types, and only passes the same information already available through NBD_OPT_LIST and NBD_OPT_EXPORT_NAME; items like NBD_INFO_BLOCK_SIZE (and thus any use of NBD_REP_ERR_BLOCK_SIZE_REQD) are intentionally left for later patches. Signed-off-by: Eric Blake <eblake@redhat.com> Message-Id: <20170707203049.534-7-eblake@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
		
				
					committed by
					
						 Paolo Bonzini
						Paolo Bonzini
					
				
			
			
				
	
			
			
			
						parent
						
							23e099c34c
						
					
				
				
					commit
					f37708f6b8
				
			
							
								
								
									
										179
									
								
								nbd/server.c
									
									
									
									
									
								
							
							
						
						
									
										179
									
								
								nbd/server.c
									
									
									
									
									
								
							| @@ -141,6 +141,7 @@ static int nbd_negotiate_send_rep_len(QIOChannel *ioc, uint32_t type, | ||||
|     trace_nbd_negotiate_send_rep_len(opt, nbd_opt_lookup(opt), | ||||
|                                      type, nbd_rep_lookup(type), len); | ||||
|  | ||||
|     assert(len < NBD_MAX_BUFFER_SIZE); | ||||
|     magic = cpu_to_be64(NBD_REP_MAGIC); | ||||
|     if (nbd_write(ioc, &magic, sizeof(magic), errp) < 0) { | ||||
|         error_prepend(errp, "write failed (rep magic): "); | ||||
| @@ -275,6 +276,8 @@ static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length, | ||||
|     return nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, NBD_OPT_LIST, errp); | ||||
| } | ||||
|  | ||||
| /* Send a reply to NBD_OPT_EXPORT_NAME. | ||||
|  * Return -errno on error, 0 on success. */ | ||||
| static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length, | ||||
|                                             uint16_t myflags, bool no_zeroes, | ||||
|                                             Error **errp) | ||||
| @@ -323,6 +326,162 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length, | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* Send a single NBD_REP_INFO, with a buffer @buf of @length bytes. | ||||
|  * The buffer does NOT include the info type prefix. | ||||
|  * Return -errno on error, 0 if ready to send more. */ | ||||
| static int nbd_negotiate_send_info(NBDClient *client, uint32_t opt, | ||||
|                                    uint16_t info, uint32_t length, void *buf, | ||||
|                                    Error **errp) | ||||
| { | ||||
|     int rc; | ||||
|  | ||||
|     trace_nbd_negotiate_send_info(info, nbd_info_lookup(info), length); | ||||
|     rc = nbd_negotiate_send_rep_len(client->ioc, NBD_REP_INFO, opt, | ||||
|                                     sizeof(info) + length, errp); | ||||
|     if (rc < 0) { | ||||
|         return rc; | ||||
|     } | ||||
|     cpu_to_be16s(&info); | ||||
|     if (nbd_write(client->ioc, &info, sizeof(info), errp) < 0) { | ||||
|         return -EIO; | ||||
|     } | ||||
|     if (nbd_write(client->ioc, buf, length, errp) < 0) { | ||||
|         return -EIO; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* Handle NBD_OPT_INFO and NBD_OPT_GO. | ||||
|  * Return -errno on error, 0 if ready for next option, and 1 to move | ||||
|  * into transmission phase.  */ | ||||
| static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length, | ||||
|                                      uint32_t opt, uint16_t myflags, | ||||
|                                      Error **errp) | ||||
| { | ||||
|     int rc; | ||||
|     char name[NBD_MAX_NAME_SIZE + 1]; | ||||
|     NBDExport *exp; | ||||
|     uint16_t requests; | ||||
|     uint16_t request; | ||||
|     uint32_t namelen; | ||||
|     bool sendname = false; | ||||
|     char buf[sizeof(uint64_t) + sizeof(uint16_t)]; | ||||
|     const char *msg; | ||||
|  | ||||
|     /* Client sends: | ||||
|         4 bytes: L, name length (can be 0) | ||||
|         L bytes: export name | ||||
|         2 bytes: N, number of requests (can be 0) | ||||
|         N * 2 bytes: N requests | ||||
|     */ | ||||
|     if (length < sizeof(namelen) + sizeof(requests)) { | ||||
|         msg = "overall request too short"; | ||||
|         goto invalid; | ||||
|     } | ||||
|     if (nbd_read(client->ioc, &namelen, sizeof(namelen), errp) < 0) { | ||||
|         return -EIO; | ||||
|     } | ||||
|     be32_to_cpus(&namelen); | ||||
|     length -= sizeof(namelen); | ||||
|     if (namelen > length - sizeof(requests) || (length - namelen) % 2) { | ||||
|         msg = "name length is incorrect"; | ||||
|         goto invalid; | ||||
|     } | ||||
|     if (nbd_read(client->ioc, name, namelen, errp) < 0) { | ||||
|         return -EIO; | ||||
|     } | ||||
|     name[namelen] = '\0'; | ||||
|     length -= namelen; | ||||
|     trace_nbd_negotiate_handle_export_name_request(name); | ||||
|  | ||||
|     if (nbd_read(client->ioc, &requests, sizeof(requests), errp) < 0) { | ||||
|         return -EIO; | ||||
|     } | ||||
|     be16_to_cpus(&requests); | ||||
|     length -= sizeof(requests); | ||||
|     trace_nbd_negotiate_handle_info_requests(requests); | ||||
|     if (requests != length / sizeof(request)) { | ||||
|         msg = "incorrect number of  requests for overall length"; | ||||
|         goto invalid; | ||||
|     } | ||||
|     while (requests--) { | ||||
|         if (nbd_read(client->ioc, &request, sizeof(request), errp) < 0) { | ||||
|             return -EIO; | ||||
|         } | ||||
|         be16_to_cpus(&request); | ||||
|         length -= sizeof(request); | ||||
|         trace_nbd_negotiate_handle_info_request(request, | ||||
|                                                 nbd_info_lookup(request)); | ||||
|         /* For now, we only care about NBD_INFO_NAME; everything else | ||||
|          * is either a request we don't know or something we send | ||||
|          * regardless of request. */ | ||||
|         if (request == NBD_INFO_NAME) { | ||||
|             sendname = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     exp = nbd_export_find(name); | ||||
|     if (!exp) { | ||||
|         return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_UNKNOWN, | ||||
|                                           opt, errp, "export '%s' not present", | ||||
|                                           name); | ||||
|     } | ||||
|  | ||||
|     /* Don't bother sending NBD_INFO_NAME unless client requested it */ | ||||
|     if (sendname) { | ||||
|         rc = nbd_negotiate_send_info(client, opt, NBD_INFO_NAME, length, name, | ||||
|                                      errp); | ||||
|         if (rc < 0) { | ||||
|             return rc; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Send NBD_INFO_DESCRIPTION only if available, regardless of | ||||
|      * client request */ | ||||
|     if (exp->description) { | ||||
|         size_t len = strlen(exp->description); | ||||
|  | ||||
|         rc = nbd_negotiate_send_info(client, opt, NBD_INFO_DESCRIPTION, | ||||
|                                      len, exp->description, errp); | ||||
|         if (rc < 0) { | ||||
|             return rc; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Send NBD_INFO_EXPORT always */ | ||||
|     trace_nbd_negotiate_new_style_size_flags(exp->size, | ||||
|                                              exp->nbdflags | myflags); | ||||
|     stq_be_p(buf, exp->size); | ||||
|     stw_be_p(buf + 8, exp->nbdflags | myflags); | ||||
|     rc = nbd_negotiate_send_info(client, opt, NBD_INFO_EXPORT, | ||||
|                                  sizeof(buf), buf, errp); | ||||
|     if (rc < 0) { | ||||
|         return rc; | ||||
|     } | ||||
|  | ||||
|     /* Final reply */ | ||||
|     rc = nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, opt, errp); | ||||
|     if (rc < 0) { | ||||
|         return rc; | ||||
|     } | ||||
|  | ||||
|     if (opt == NBD_OPT_GO) { | ||||
|         client->exp = exp; | ||||
|         QTAILQ_INSERT_TAIL(&client->exp->clients, client, next); | ||||
|         nbd_export_get(client->exp); | ||||
|         rc = 1; | ||||
|     } | ||||
|     return rc; | ||||
|  | ||||
|  invalid: | ||||
|     if (nbd_drop(client->ioc, length, errp) < 0) { | ||||
|         return -EIO; | ||||
|     } | ||||
|     return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, opt, | ||||
|                                       errp, "%s", msg); | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Handle NBD_OPT_STARTTLS. Return NULL to drop connection, or else the | ||||
|  * new channel for all further (now-encrypted) communication. */ | ||||
| static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, | ||||
| @@ -380,7 +539,8 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, | ||||
| } | ||||
|  | ||||
| /* nbd_negotiate_options | ||||
|  * Process all NBD_OPT_* client option commands. | ||||
|  * Process all NBD_OPT_* client option commands, during fixed newstyle | ||||
|  * negotiation. | ||||
|  * Return: | ||||
|  * -errno  on error, errp is set | ||||
|  * 0       on successful negotiation, errp is not set | ||||
| @@ -397,7 +557,7 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, | ||||
|     /* Client sends: | ||||
|         [ 0 ..   3]   client flags | ||||
|  | ||||
|        Then we loop until NBD_OPT_EXPORT_NAME: | ||||
|        Then we loop until NBD_OPT_EXPORT_NAME or NBD_OPT_GO: | ||||
|         [ 0 ..   7]   NBD_OPTS_MAGIC | ||||
|         [ 8 ..  11]   NBD option | ||||
|         [12 ..  15]   Data length | ||||
| @@ -524,6 +684,19 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, | ||||
|                                                         myflags, no_zeroes, | ||||
|                                                         errp); | ||||
|  | ||||
|             case NBD_OPT_INFO: | ||||
|             case NBD_OPT_GO: | ||||
|                 ret = nbd_negotiate_handle_info(client, length, option, | ||||
|                                                 myflags, errp); | ||||
|                 if (ret == 1) { | ||||
|                     assert(option == NBD_OPT_GO); | ||||
|                     return 0; | ||||
|                 } | ||||
|                 if (ret) { | ||||
|                     return ret; | ||||
|                 } | ||||
|                 break; | ||||
|  | ||||
|             case NBD_OPT_STARTTLS: | ||||
|                 if (nbd_drop(client->ioc, length, errp) < 0) { | ||||
|                     return -EIO; | ||||
| @@ -606,7 +779,7 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) | ||||
|         [ 0 ..   7]   passwd       ("NBDMAGIC") | ||||
|         [ 8 ..  15]   magic        (NBD_OPTS_MAGIC) | ||||
|         [16 ..  17]   server flags (0) | ||||
|         ....options sent, ending in NBD_OPT_EXPORT_NAME.... | ||||
|         ....options sent, ending in NBD_OPT_EXPORT_NAME or NBD_OPT_GO.... | ||||
|      */ | ||||
|  | ||||
|     qio_channel_set_blocking(client->ioc, false, NULL); | ||||
|   | ||||
| @@ -33,6 +33,9 @@ nbd_negotiate_send_rep_err(const char *msg) "sending error message \"%s\"" | ||||
| nbd_negotiate_send_rep_list(const char *name, const char *desc) "Advertising export name '%s' description '%s'" | ||||
| nbd_negotiate_handle_export_name(void) "Checking length" | ||||
| nbd_negotiate_handle_export_name_request(const char *name) "Client requested export '%s'" | ||||
| nbd_negotiate_send_info(int info, const char *name, uint32_t length) "Sending NBD_REP_INFO type %d (%s) with remaining length %" PRIu32 | ||||
| nbd_negotiate_handle_info_requests(int requests) "Client requested %d items of info" | ||||
| nbd_negotiate_handle_info_request(int request, const char *name) "Client requested info %d (%s)" | ||||
| nbd_negotiate_handle_starttls(void) "Setting up TLS" | ||||
| nbd_negotiate_handle_starttls_handshake(void) "Starting TLS handshake" | ||||
| nbd_negotiate_options_flags(uint32_t flags) "Received client flags 0x%" PRIx32 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user