Created
May 10, 2016 17:52
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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