Skip to content

Instantly share code, notes, and snippets.

@NicolasT
Created May 10, 2016 17:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save NicolasT/f2a0e537bc0c2329ae985ebd39bf58bb to your computer and use it in GitHub Desktop.
Save NicolasT/f2a0e537bc0c2329ae985ebd39bf58bb to your computer and use it in GitHub Desktop.
Demonstration of splice, tee and AF_ALG hashing for zero-copy storage and data hash calculation
/* Usage:
*
* $ make cryptosplice
* cc cryptosplice.c -o cryptosplice
*
* $ rm -f cryptosplice.out
* $ ./cryptosplice
*
* Now, in another terminal:
*
* $ echo "Hello, world!" | nc localhost 8080
*
* In the primary terminal, the process should have printed
*
* 09fac8dbfd27bd9b4d23a00eb648aa751789536d
*
* and quit.
*
* Validation:
*
* $ cat cryptosplice.out
* Hello, world!
* $ sha1sum cryptosplice.out
* 09fac8dbfd27bd9b4d23a00eb648aa751789536d cryptosplice.out
*/
#define _GNU_SOURCE /* For F_GETPIPE_SZ and others */
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <linux/if_alg.h>
/* Create a 'master' crypto-socket for the given algorithm */
static int init_crypto_master(const struct sockaddr_alg *alg) {
int fd, rc;
fd = socket(AF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
if(fd < 0) {
perror("socket");
rc = -1;
goto out;
}
rc = bind(fd, (struct sockaddr *)alg, sizeof(*alg));
if(rc < 0) {
perror("bind");
rc = -1;
goto out;
}
rc = fd;
out:
if(rc < 0) {
if(fd >= 0) {
close(fd);
}
}
return rc;
}
/* Create a TCP socket listening on port 8080 */
static int init_listen_socket(void) {
int fd, rc;
struct sockaddr_in addr;
fd = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
if(fd < 0) {
perror("socket");
rc = -1;
goto out;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if(rc < 0) {
perror("bind");
rc = -1;
goto out;
}
rc = listen(fd, 1);
if(rc < 0) {
perror("listen");
rc = -1;
goto out;
}
rc = fd;
out:
if(rc < 0) {
if(fd >= 0) {
close(fd);
}
}
return rc;
}
int main(int argc, char **argv) {
int rc;
const struct sockaddr_alg sha1 = {
.salg_family = AF_ALG,
.salg_type = "hash",
.salg_name = "sha1",
};
const int crypto_master = init_crypto_master(&sha1);
if(crypto_master < 0) {
rc = 1;
goto err_init_crypto_master;
}
const int server_socket = init_listen_socket();
if(server_socket < 0) {
rc = 1;
goto err_init_listen_socket;
}
/* Wait for client to connect */
const int client_socket = accept(server_socket, NULL, NULL);
if(client_socket < 0) {
perror("accept");
rc = 1;
goto err_accept_client;
}
/* Create crypto-socket */
const int crypto_client = accept(crypto_master, NULL, NULL);
if(crypto_client < 0) {
perror("accept");
rc = 1;
goto err_accept_crypto;
}
/* Pipes to transfer data from the client socket into */
int pipes[2];
rc = pipe(pipes);
if(rc < 0) {
perror("pipe");
rc = 1;
goto err_pipe;
}
/* Pipes to transfer data from `pipes` into, before sending to `crypto_client` */
int crypto_pipes[2];
rc = pipe(crypto_pipes);
if(rc < 0) {
perror("pipe");
rc = 1;
goto err_crypto_pipes;
}
/* Target file for all the data */
const int out = open("cryptosplice.out", O_CLOEXEC | O_EXCL | O_CREAT | O_WRONLY, 0644);
if(out < 0) {
perror("open");
rc = 1;
goto err_open;
}
/* TODO Adjust pipe sizes using F_SETPIPE_SZ. Can be set to MIN of input
* data size (if known) and /proc/sys/fs/pipe-max-size */
/* Size of a pipe. Assume (in this demo) it's always the same */
const int size = fcntl(pipes[0], F_GETPIPE_SZ);
if(size < 0) {
perror("fcntl");
rc = 1;
goto cleanup;
}
/* At this point, here's what we have:
*
* - A socket from the client, `client_socket`
* - A 'crypto-socket' which does SHA1-hashing, `crypto_client`
* - A target file descriptor
* - 2 pairs of pipes, `pipes` and `crypto_pipes`
*
* Here's a 'diagram' of how things will flow:
*
* +---------------+ splice +-------+ tee +--------------+ splice +---------------+
* | client_socket | =====> | pipes | ==> | crypto_pipes | =====> | crypto_client |
* +---------------+ +-------+ +--------------+ +---------------+
* |
* | splice
* |
* V
* +-----+
* | out |
* +-----+
*/
while(1) {
/* Splice at most `size` bytes from `client_socket` into the
* input `pipes`
*/
const ssize_t cnt = splice(client_socket, NULL, pipes[1], NULL, size, 0);
if(cnt < 0) {
perror("splice");
rc = 1;
goto cleanup;
}
if(cnt == 0) {
/* EOF */
break;
}
/* First, `tee` that data into the pipes used to eventually send
* data into `crypto_client`, not consuming the data from
* `pipes`
*/
ssize_t cnt2 = cnt;
while(cnt2 > 0) {
const ssize_t cnt3 = tee(pipes[0], crypto_pipes[1], cnt2, 0);
if(cnt3 < 0) {
perror("tee");
rc = 1;
goto cleanup;
}
cnt2 -= cnt3;
}
/* Step 2: `splice` the data `tee`d into the intermediate pipes
* into the `crypto_client` socket, consuming it from
* `crypto_pipes`.
*/
cnt2 = cnt;
while(cnt2 > 0) {
const ssize_t cnt3 = splice(crypto_pipes[0], NULL, crypto_client, NULL, cnt2, SPLICE_F_MOVE | SPLICE_F_MORE);
if(cnt3 < 0) {
perror("splice");
rc = 1;
goto cleanup;
}
cnt2 -= cnt3;
}
/* Step 3: `splice` the data from `pipes` into `out`, our
* temporary file, at the current file location (could use
* offsets here as well of course). This effectively consumes
* the data from `pipes`. At the end of the loop `pipes` should
* be empty.
*/
cnt2 = cnt;
while(cnt2 > 0) {
const ssize_t cnt3 = splice(pipes[0], NULL, out, NULL, cnt2, SPLICE_F_MOVE | SPLICE_F_MORE);
if(cnt3 < 0) {
perror("splice");
rc = 1;
goto cleanup;
}
cnt2 -= cnt3;
}
}
/* Read the final SHA1-sum of all transferred data */
char buf[20];
rc = read(crypto_client, buf, sizeof(buf));
if(rc < 0) {
perror("read");
rc = 1;
goto cleanup;
}
/* And print it */
for(int i = 0; i < sizeof(buf); i++) {
printf("%02x", buf[i] & 0xff);
}
printf("\n");
rc = 0;
cleanup:
close(out);
err_open:
close(crypto_pipes[0]);
close(crypto_pipes[1]);
err_crypto_pipes:
close(pipes[0]);
close(pipes[1]);
err_pipe:
close(crypto_client);
err_accept_crypto:
close(client_socket);
err_accept_client:
close(server_socket);
err_init_listen_socket:
close(crypto_master);
err_init_crypto_master:
return rc;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment