openssl3.2 - 官方demo学习 - guide - quic-client-block.c

发布时间:2024年01月14日

openssl3.2 - 官方demo学习 - guide - quic-client-block.c

概述

在程序运行时, 要指定环境变量 SSL_CERT_FILE=rootcert.pem, 同时将rootcert.pem拷贝到工程目录下, 否则不好使
吐槽啊, 为啥不用命令行参数或者API参数传进来啊, 整啥环境变量啊, 看着膈应.

quic服务启动(openssl3.2 - quic服务的运行)时的命令行为 quicserver.exe -trace localhost 23456 servercert.pem serverkey.pem
本程序(quic客户端)命令行只能为 localhost 23456 才行
用 127.0.0.1 23456 不好使.

如果要单步调试, 得赶紧的. quic服务启动后, 如果30秒内没有客户端来, quic服务会退出, 这太不礼貌了…
只能跑一下, 听个响, 学不到东西.

这个demo, 是不是只想展示, openssl可以作为quic客户端程序的tls实现?

笔记

/*!
* \file quic-client-block.c
* \note openssl3.2 - 官方demo学习 - guide - quic-client-block.c
* 在程序运行时, 要指定环境变量 SSL_CERT_FILE=rootcert.pem, 同时将rootcert.pem拷贝到工程目录下, 否则不好使
* 吐槽啊, 为啥不用命令行参数或者API参数传进来啊, 整啥环境变量啊, 看着膈应.
* 
* quic服务启动时的命令行为 quicserver.exe -trace localhost 23456 servercert.pem serverkey.pem
本程序(quic客户端)命令行只能为 localhost 23456 才行
用 127.0.0.1 23456 不好使.

如果要单步调试, 得赶紧的. quic服务启动后, 如果30秒内没有客户端来, quic服务会退出, 这太不礼貌了...
只能跑一下, 听个响, 学不到东西.

这个demo, 是不是只想展示, openssl可以作为quic客户端程序的tls实现?
*/

/*
 *  Copyright 2023 The OpenSSL Project Authors. All Rights Reserved.
 *
 *  Licensed under the Apache License 2.0 (the "License").  You may not use
 *  this file except in compliance with the License.  You can obtain a copy
 *  in the file LICENSE in the source distribution or at
 *  https://www.openssl.org/source/license.html
 */

 /*
  * NB: Changes to this file should also be reflected in
  * doc/man7/ossl-guide-quic-client-block.pod
  */

#include <string.h>

  /* Include the appropriate header file for SOCK_DGRAM */
#ifdef _WIN32 /* Windows */
# include <winsock2.h>
#else /* Linux/Unix */
# include <sys/socket.h>
#endif

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#include "my_openSSL_lib.h"

/* Helper function to create a BIO connected to the server */
static BIO* create_socket_bio(const char* hostname, const char* port,
	int family, BIO_ADDR** peer_addr)
{
	int sock = -1;
	BIO_ADDRINFO* res;
	const BIO_ADDRINFO* ai = NULL;
	BIO* bio;

	/*
	 * Lookup IP address info for the server.
	 */
	if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_DGRAM, 0,
		&res))
		return NULL;

	/*
	 * Loop through all the possible addresses for the server and find one
	 * we can connect to.
	 */
	for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) {
		/*
		 * Create a UDP socket. We could equally use non-OpenSSL calls such
		 * as "socket" here for this and the subsequent connect and close
		 * functions. But for portability reasons and also so that we get
		 * errors on the OpenSSL stack in the event of a failure we use
		 * OpenSSL's versions of these functions.
		 */
		sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0);
		if (sock == -1)
			continue;

		/* Connect the socket to the server's address */
		if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) {
			BIO_closesocket(sock);
			sock = -1;
			continue;
		}

		/* Set to nonblocking mode */
		if (!BIO_socket_nbio(sock, 1)) {
			BIO_closesocket(sock);
			sock = -1;
			continue;
		}

		break;
	}

	if (sock != -1) {
		*peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai));
		if (*peer_addr == NULL) {
			BIO_closesocket(sock);
			return NULL;
		}
	}

	/* Free the address information resources we allocated earlier */
	BIO_ADDRINFO_free(res);

	/* If sock is -1 then we've been unable to connect to the server */
	if (sock == -1)
		return NULL;

	/* Create a BIO to wrap the socket */
	bio = BIO_new(BIO_s_datagram());
	if (bio == NULL) {
		BIO_closesocket(sock);
		return NULL;
	}

	/*
	 * Associate the newly created BIO with the underlying socket. By
	 * passing BIO_CLOSE here the socket will be automatically closed when
	 * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which
	 * case you must close the socket explicitly when it is no longer
	 * needed.
	 */
	BIO_set_fd(bio, sock, BIO_CLOSE);

	return bio;
}

/*
 * Simple application to send a basic HTTP/1.0 request to a server and
 * print the response on the screen. Note that HTTP/1.0 over QUIC is
 * non-standard and will not typically be supported by real world servers. This
 * is for demonstration purposes only.
 */
int main(int argc, char* argv[])
{
	SSL_CTX* ctx = NULL;
	SSL* ssl = NULL;
	BIO* bio = NULL;
	int res = EXIT_FAILURE;
	int ret;
	unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' };
	const char* request_start = "GET / HTTP/1.0\r\nConnection: close\r\nHost: ";
	const char* request_end = "\r\n\r\n";
	size_t written, readbytes;
	char buf[160];
	BIO_ADDR* peer_addr = NULL;
	char* hostname, * port;
	int argnext = 1;
	int ipv6 = 0;

	if (argc < 3) {
		printf("Usage: quic-client-block [-6] hostname port\n");
		goto end;
	}

	if (!strcmp(argv[argnext], "-6")) {
		if (argc < 4) {
			printf("Usage: quic-client-block [-6] hostname port\n");
			goto end;
		}
		ipv6 = 1;
		argnext++;
	}
	hostname = argv[argnext++];
	port = argv[argnext];

	/*
	 * Create an SSL_CTX which we can use to create SSL objects from. We
	 * want an SSL_CTX for creating clients so we use
	 * OSSL_QUIC_client_method() here.
	 */
	ctx = SSL_CTX_new(OSSL_QUIC_client_method());
	if (ctx == NULL) {
		printf("Failed to create the SSL_CTX\n");
		goto end;
	}

	/*
	 * Configure the client to abort the handshake if certificate
	 * verification fails. Virtually all clients should do this unless you
	 * really know what you are doing.
	 */
	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

	/* Use the default trusted certificate store */
	if (!SSL_CTX_set_default_verify_paths(ctx)) {
		printf("Failed to set the default trusted certificate store\n");
		goto end;
	}

	/* Create an SSL object to represent the TLS connection */
	ssl = SSL_new(ctx);
	if (ssl == NULL) {
		printf("Failed to create the SSL object\n");
		goto end;
	}

	/*
	 * Create the underlying transport socket/BIO and associate it with the
	 * connection.
	 */
	bio = create_socket_bio(hostname, port, ipv6 ? AF_INET6 : AF_INET, &peer_addr);
	if (bio == NULL) {
		printf("Failed to crete the BIO\n");
		goto end;
	}
	SSL_set_bio(ssl, bio, bio);

	/*
	 * Tell the server during the handshake which hostname we are attempting
	 * to connect to in case the server supports multiple hosts.
	 */
	if (!SSL_set_tlsext_host_name(ssl, hostname)) {
		printf("Failed to set the SNI hostname\n");
		goto end;
	}

	/*
	 * Ensure we check during certificate verification that the server has
	 * supplied a certificate for the hostname that we were expecting.
	 * Virtually all clients should do this unless you really know what you
	 * are doing.
	 */
	if (!SSL_set1_host(ssl, hostname)) {
		printf("Failed to set the certificate verification hostname");
		goto end;
	}

	/* SSL_set_alpn_protos returns 0 for success! */
	if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn)) != 0) {
		printf("Failed to set the ALPN for the connection\n");
		goto end;
	}

	/* Set the IP address of the remote peer */
	if (!SSL_set1_initial_peer_addr(ssl, peer_addr)) {
		printf("Failed to set the initial peer address\n");
		goto end;
	}

	/*! 到这就要将quic服务开起来, 否则连接失败 */
	/* Do the handshake with the server */
	if (SSL_connect(ssl) < 1) {
		printf("Failed to connect to the server\n");
		/*
		 * If the failure is due to a verification error we can get more
		 * information about it from SSL_get_verify_result().
		 */
		if (SSL_get_verify_result(ssl) != X509_V_OK)
			printf("Verify error: %s\n",
				X509_verify_cert_error_string(SSL_get_verify_result(ssl)));
		goto end;
	}

	/* Write an HTTP GET request to the peer */
	if (!SSL_write_ex(ssl, request_start, strlen(request_start), &written)) {
		printf("Failed to write start of HTTP request\n");
		goto end;
	}
	if (!SSL_write_ex(ssl, hostname, strlen(hostname), &written)) {
		printf("Failed to write hostname in HTTP request\n");
		goto end;
	}
	if (!SSL_write_ex(ssl, request_end, strlen(request_end), &written)) {
		printf("Failed to write end of HTTP request\n");
		goto end;
	}

	/*
	 * Get up to sizeof(buf) bytes of the response. We keep reading until the
	 * server closes the connection.
	 */

	 /*! 这前面, 给服务器发了3句话
	 这下面循环, 然后将服务器回包读完, 就往下走了 */
	while (SSL_read_ex(ssl, buf, sizeof(buf), &readbytes)) {
		/*
		* OpenSSL does not guarantee that the returned data is a string or
		* that it is NUL terminated so we use fwrite() to write the exact
		* number of bytes that we read. The data could be non-printable or
		* have NUL characters in the middle of it. For this simple example
		* we're going to print it to stdout anyway.
		*/
		fwrite(buf, 1, readbytes, stdout);
	}
	/* In case the response didn't finish with a newline we add one now */
	printf("\n");


	/*
	 * Check whether we finished the while loop above normally or as the
	 * result of an error. The 0 argument to SSL_get_error() is the return
	 * code we received from the SSL_read_ex() call. It must be 0 in order
	 * to get here. Normal completion is indicated by SSL_ERROR_ZERO_RETURN. In
	 * QUIC terms this means that the peer has sent FIN on the stream to
	 * indicate that no further data will be sent.
	 */
	switch (SSL_get_error(ssl, 0)) {
	case SSL_ERROR_ZERO_RETURN:
		/* Normal completion of the stream */
		/*! 最后是从这里break的 */
		break;

	case SSL_ERROR_SSL:
		/*
		 * Some stream fatal error occurred. This could be because of a stream
		 * reset - or some failure occurred on the underlying connection.
		 */
		switch (SSL_get_stream_read_state(ssl)) {
		case SSL_STREAM_STATE_RESET_REMOTE:
			printf("Stream reset occurred\n");
			/* The stream has been reset but the connection is still healthy. */
			break;

		case SSL_STREAM_STATE_CONN_CLOSED:
			printf("Connection closed\n");
			/* Connection is already closed. Skip SSL_shutdown() */
			goto end;

		default:
			printf("Unknown stream failure\n");
			break;
		}
		break;

	default:
		/* Some other unexpected error occurred */
		printf("Failed reading remaining data\n");
		break;
	}

	/*
	 * Repeatedly call SSL_shutdown() until the connection is fully
	 * closed.
	 */
	do {
		ret = SSL_shutdown(ssl); // 关断ssl需要好久...
		if (ret < 0) {
			printf("Error shutting down: %d\n", ret);
			goto end;
		}
	} while (ret != 1);

	/* Success! */
	res = EXIT_SUCCESS;
end:
	/*
	 * If something bad happened then we will dump the contents of the
	 * OpenSSL error stack to stderr. There might be some useful diagnostic
	 * information there.
	 */
	if (res == EXIT_FAILURE)
		ERR_print_errors_fp(stderr);

	/*
	 * Free the resources we allocated. We do not free the BIO object here
	 * because ownership of it was immediately transferred to the SSL object
	 * via SSL_set_bio(). The BIO will be freed when we free the SSL object.
	 */
	SSL_free(ssl);
	SSL_CTX_free(ctx);
	BIO_ADDR_free(peer_addr);
	return res;
}

END

文章来源:https://blog.csdn.net/LostSpeed/article/details/135586320
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。