summaryrefslogtreecommitdiffstats
path: root/iv/orodja/ldmitm
diff options
context:
space:
mode:
Diffstat (limited to 'iv/orodja/ldmitm')
-rw-r--r--iv/orodja/ldmitm/.gitignore1
-rw-r--r--iv/orodja/ldmitm/Makefile20
-rw-r--r--iv/orodja/ldmitm/ldmitm.c262
-rw-r--r--iv/orodja/ldmitm/tcp_times.c114
-rw-r--r--iv/orodja/ldmitm/tcp_times.h27
-rw-r--r--iv/orodja/ldmitm/tcp_times_example.c80
6 files changed, 504 insertions, 0 deletions
diff --git a/iv/orodja/ldmitm/.gitignore b/iv/orodja/ldmitm/.gitignore
new file mode 100644
index 0000000..3ce51cd
--- /dev/null
+++ b/iv/orodja/ldmitm/.gitignore
@@ -0,0 +1 @@
+tcp_times_example
diff --git a/iv/orodja/ldmitm/Makefile b/iv/orodja/ldmitm/Makefile
new file mode 100644
index 0000000..1ef8a92
--- /dev/null
+++ b/iv/orodja/ldmitm/Makefile
@@ -0,0 +1,20 @@
+obj-m += tcp_times.o
+
+all: allmods tcp_times_example ldmitm.so
+
+%.so: %.c
+ cc -shared -fPIC -ldl $< -o $@
+
+%: %.c
+ cc -g -Wall -Wextra -pedantic -Wformat -Wformat-security -o$@ $<
+
+allmods:
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
+clean:
+ make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
+ rm ldmitm.so tcp_times_example
+
+prepare:
+ apt install libsqlite3-dev linux-headers-generic
+
+.PHONY: allmods clean prepare
diff --git a/iv/orodja/ldmitm/ldmitm.c b/iv/orodja/ldmitm/ldmitm.c
new file mode 100644
index 0000000..2b8b815
--- /dev/null
+++ b/iv/orodja/ldmitm/ldmitm.c
@@ -0,0 +1,262 @@
+/*
+DISKUSIJA
+Bolje bi bilo uporabiti frida gadget: https://frida.re/docs/gadget/
+Kako deluje: https://tbrindus.ca/correct-ld-preload-hooking-libc/
+Prevedi z: gcc -shared -fPIC -ldl ldmitm.c -o ldmitm.so
+Poženi z: LD_PRELOAD=$PWD/ldmitm.so php -S 0:1234
+A je treba beležit execve in fopen? Po mojem ne. Odtekanje flagov itak vidimo v TCP sessionu.
+TODO: add mutex locks, openssl bio mitm, read, write, rtt, timestamps (if possible), namesto trenutnega mtu rajši dobi advertised mss iz tcp_info, dobi last ack timestamp option value: https://elixir.bootlin.com/linux/v6.11-rc3/source/include/linux/tcp.h#L302 <= tu notri je mogoče z BPF???
+*/
+#include <stdio.h>
+#include <dlfcn.h>
+#include <sys/socket.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sqlite3.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <sys/param.h>
+#include <netinet/ip.h>
+#if SQLITE_VERSION_NUMBER < 3037000
+#error "we require sqlite newer than 3037000, yours is " #SQLITE_VERSION_NUMBER " --- because of STRICT!"
+#endif
+#define LOG(x, ...) if (getenv("LDMITM_LOG")) printf("[ldmitm %s] " x "\n", __func__ __VA_OPT__(,) __VA_ARGS__)
+struct stream {
+ bool active; // only stream connections are true, listening sockets and inactive fds are false
+ bool silenced; // only makes sense for stream connections, true to silence traffic
+ int id; // id in sqlite3 database -- only makes sense for stream connections
+ int bound; // for listening sockets: port, for stream sockets: fd of listening socket
+};
+typedef int (*accept_t)(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
+typedef int (*close_t)(int fd);
+typedef int (*bind_t)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
+static accept_t real_accept = NULL;
+static close_t real_close = NULL;
+static bind_t real_bind = NULL;
+static sqlite3 * db = NULL;
+struct stream * streams = NULL;
+int streams_sizeof = 0;
+static void setup_db () { // should be able to ignore being called in case db is already set up on this thread
+ if (db)
+ return;
+ int ret = sqlite3_open_v2(getenv("LDMITM_DB") ? getenv("LDMITM_DB") : ":memory:", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_EXRESCODE, NULL);
+ if (ret != SQLITE_OK) {
+ LOG("failed to open db: %s", sqlite3_errstr(ret));
+ goto fail;
+ }
+ sqlite3_stmt * stmt = NULL;
+ ret = sqlite3_prepare_v3(db, "PRAGMA foreign_keys = ON;", -1, 0, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ LOG("failed to prepare pragma foreign_keys: %s", sqlite3_errstr(ret));
+ if (stmt)
+ sqlite3_finalize(stmt);
+ stmt = NULL;
+ goto fail;
+ }
+ ret = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ stmt = NULL;
+ if (ret != SQLITE_DONE) {
+ LOG("failed to step pragma foreign_keys: %s", sqlite3_errstr(ret));
+ goto fail;
+ }
+#define CREATE_TABLE(name, columns) \
+ ret = sqlite3_prepare_v3(db, "create table if not exists " name " (" columns ") STRICT;", -1, 0, &stmt, NULL); \
+ if (ret != SQLITE_OK) { \
+ LOG("failed to prepare create " name ": %s, statement: %s", sqlite3_errstr(ret), "create table if not exists " name " (" columns ") STRICT;"); \
+ if (stmt) \
+ sqlite3_finalize(stmt); \
+ stmt = NULL; \
+ goto fail; \
+ } \
+ ret = sqlite3_step(stmt); \
+ sqlite3_finalize(stmt); \
+ stmt = NULL; \
+ if (ret != SQLITE_DONE) { \
+ LOG("failed to step create " name ": %s", sqlite3_errstr(ret)); \
+ goto fail; \
+ }
+ CREATE_TABLE("connections", "id INTEGER PRIMARY KEY, bound INTEGER NOT NULL, peer TEXT NOT NULL, accepted TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, closed TEXT, silenced TEXT, mtu INTEGER NOT NULL"); // accepted, closed and silenced are iso datestrings, bound is listening port
+ CREATE_TABLE("messages", "connection INTEGER NOT NULL, direction INTEGER NOT NULL CHECK (direction IN (0, 1)), time TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, rtt INTEGER NOT NULL, FOREIGN KEY(connection) REFERENCES connections(id)");
+ return;
+fail:
+ if (db)
+ sqlite3_close_v2(db);
+ db = NULL;
+ return;
+}
+static void end_stream (int fd) { // cleanup function, should be able to handle being called on already ended stream
+ if (fd >= streams_sizeof || !streams[fd].active)
+ return;
+ setup_db();
+ if (db) {
+ sqlite3_stmt * stmt = NULL;
+ int ret = sqlite3_prepare_v3(db, "update connections set closed=strftime('%FT%R:%f', 'now') WHERE id=:i;", -1, 0, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ LOG("failed to prepare update connections set closed: %s", sqlite3_errstr(ret));
+ sqlite3_finalize(stmt);
+ stmt = NULL;
+ goto fail;
+ }
+ ret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":i"), streams[fd].id);
+ if (ret != SQLITE_OK) {
+ LOG("failed to bind update connections set closed: %s", sqlite3_errstr(ret));
+ sqlite3_finalize(stmt);
+ stmt = NULL;
+ goto fail;
+ }
+ ret = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ stmt = NULL;
+ if (ret != SQLITE_DONE) {
+ LOG("failed to step update connections set closed: %s", sqlite3_errstr(ret));
+ goto fail;
+ }
+ }
+fail:
+ memset(&(streams[fd]), 0, sizeof streams[fd]);
+}
+static bool more_fds (int largest_fd) {
+ if (largest_fd >= streams_sizeof) {
+ int streams_sizeof_new;
+ if (streams_sizeof == 0)
+ streams_sizeof_new = 128;
+ else
+ streams_sizeof_new = streams_sizeof * 2;
+ struct stream * streams_new = realloc(streams, streams_sizeof_new*sizeof *streams);
+ if (!streams_new) {
+ LOG("ENOMEM realloc streams!");
+ return false;
+ }
+ memset(streams_new+streams_sizeof, 0, (streams_sizeof_new-streams_sizeof)*sizeof *streams);
+ streams = streams_new;
+ streams_sizeof = streams_sizeof_new;
+ }
+ if (largest_fd < streams_sizeof)
+ return true;
+ return more_fds(largest_fd);
+}
+static int port_from_sa (const struct sockaddr * sa) { // gets port from sockaddr
+ switch (sa->sa_family) {
+ case AF_INET:
+ return ntohs(((struct sockaddr_in *) sa)->sin_port);
+ case AF_INET6:
+ return ntohs(((struct sockaddr_in6 *) sa)->sin6_port);
+ default:
+ return -1;
+ }
+}
+static void str_from_sa (char * peeraddress, const struct sockaddr * address) {
+ switch (address->sa_family) {
+ case AF_INET:
+ if (!inet_ntop(AF_INET, &(((struct sockaddr_in *) address)->sin_addr), peeraddress, *address_len)) {
+ int myerrno = errno;
+ strcpy(peeraddress, "!");
+ strcat(peeraddress, strerror(myerrno));
+ }
+ sprintf(peeraddress+strlen(peeraddress), "/%d", ntohs(((struct sockaddr_in *) address)->sin_port));
+ break;
+ case AF_INET6:
+ if (!inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) address)->sin6_addr), peeraddress, *address_len)) {
+ int myerrno = errno;
+ strcpy(peeraddress, "!");
+ strcat(peeraddress, strerror(myerrno));
+ }
+ sprintf(peeraddress+strlen(peeraddress), "/%d", ntohs(((struct sockaddr_in6 *) address)->sin6_port));
+ break;
+ default:
+ strcpy(peeraddress, "unknown family");
+ break;
+ }
+}
+int bind (int sockfd, const struct sockaddr * addr, socklen_t addrlen) {
+ if (!real_bind)
+ real_bind = dlsym(RTLD_NEXT, "bind");
+ LOG("called bind()");
+ int ret = real_bind(sockfd, addr, addrlen);
+ int old_errno = errno;
+ if (ret == -1)
+ goto fail;
+ if (!more_fds(sockfd))
+ goto fail; // enomem
+ streams[sockfd].bound = port_from_sa(addr);
+fail:
+ errno = old_errno;
+ return ret;
+}
+int close (int fd) {
+ if (!real_close)
+ real_close = dlsym(RTLD_NEXT, "close");
+ LOG("called close()");
+ int ret = real_close(fd);
+ int old_errno = errno;
+ end_stream(fd);
+ errno = old_errno;
+ return ret;
+}
+int accept (int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) {
+ if (!real_accept)
+ real_accept = dlsym(RTLD_NEXT, "accept");
+ LOG("called accept()");
+ int ret = real_accept(socket, address, address_len);
+ int saved_errno = errno;
+ if (ret == -1)
+ goto fail;
+ if (!more_fds(MAX(ret, socket))
+ goto fail; // enomem
+ end_stream(ret);
+ streams[ret].active = true;
+ streams[ret].bound = socket;
+ setup_db();
+ if (db) {
+ sqlite3_stmt * stmt = NULL;
+ int sqlret = sqlite3_prepare_v3(db, "insert into connections (peer, bound, mtu) values (:p, :b, :m);", -1, 0, &stmt, NULL);
+ if (sqlret != SQLITE_OK) {
+ LOG("failed to prepare insert connections: %s", sqlite3_errstr(sqlret));
+ goto sqlfail;
+ }
+ char peeraddress[INET_ADDRSTRLEN+128];
+ str_from_sa(peeraddress, address);
+ sqlret = sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, ":p"), peeraddress, -1, SQLITE_STATIC);
+ if (sqlret != SQLITE_OK) {
+ LOG("failed to bind insert connection text: %s", sqlite3_errstr(sqlret));
+ goto sqlfail;
+ }
+ sqlret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":b"), streams[streams[ret].bound].bound);
+ if (sqlret != SQLITE_OK) {
+ LOG("failed to bind insert connection bound: %s", sqlite3_errstr(sqlret));
+ goto sqlfail;
+ }
+ int mtu;
+ int mtusize = sizeof mtu;
+ if (getsockopt(socket, IPPROTO_IP, IP_MTU, &mtu, &mtusize) == -1) {
+ mtu = -errno;
+ LOG("failed to get MTU: %s", -mtu);
+ }
+ sqlret = sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, ":m"), mtu);
+ if (sqlret != SQLITE_OK) {
+ LOG("failed to bind insert connection mtu: %s", sqlite3_errstr(sqlret));
+ goto sqlfail;
+ }
+ sqlret = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ stmt = NULL;
+ if (sqlret != SQLITE_DONE) {
+ LOG("failed to step insert connection: %s", sqlite3_errstr(ret));
+ goto sqlfail;
+ }
+sqlfail:
+ sqlite3_finalize(stmt);
+ stmt = NULL;
+ goto fail;
+ }
+fail:
+ errno = saved_errno;
+ return ret;
+}
+__attribute__((constructor)) static void setup (void) {
+ LOG("called setup()");
+}
diff --git a/iv/orodja/ldmitm/tcp_times.c b/iv/orodja/ldmitm/tcp_times.c
new file mode 100644
index 0000000..02a067a
--- /dev/null
+++ b/iv/orodja/ldmitm/tcp_times.c
@@ -0,0 +1,114 @@
+/*
+Prevajanje: make
+Namestitev v jedro: insmod tcp_times.ko
+Uporaba v C:
+#include <stdint.h>
+#include "tcp_times.h"
+int tcp_times = open("/proc/tcp_times", O_RDWR);
+if (tcp_times == -1) {
+ perror("open tcp_times");
+ break;
+}
+int tcpsock = accept(boundsocket, &address, &addrlen);
+struct tcp_times tt = {
+ .fd = tcpsock
+};
+if (ioctl(tcp_times, 0, &tt) == -1) {
+ perror("ioctl tcp_times");
+ break;
+}
+// sedaj so polja v tt populirana
+*/
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/net.h>
+#include <linux/tcp.h>
+#include <linux/version.h>
+#include "tcp_times.h"
+MODULE_AUTHOR("Anton Luka Šijanec <anton@sijanec.eu>");
+MODULE_DESCRIPTION("tcp last received tsval, rtt procfs ioctl driver");
+MODULE_LICENSE("");
+static struct proc_dir_entry * ent;
+static long myioctl (struct file * filep, unsigned int cmd, unsigned long arg) {
+ switch(cmd) {
+ case 0:
+ struct tcp_times tt;
+ if (copy_from_user(&tt, (void *) arg, sizeof tt))
+ return -EFAULT;
+ struct fd f = fdget(tt.fd);
+ if (!f.file)
+ return -EBADF;
+ struct socket * sock = sock_from_file(f.file);
+ if (!sock) {
+ fdput(f);
+ return -ENOTSOCK;
+ }
+ if (!(sock->type & SOCK_STREAM)) {
+ fdput(f);
+ return -ENOSTR;
+ }
+ if (!sock->sk) {
+ fdput(f);
+ return -EBADFD;
+ }
+ if (sock->sk->sk_protocol != IPPROTO_TCP) {
+ fdput(f);
+ return -ESOCKTNOSUPPORT;
+ }
+ struct tcp_sock * tp = tcp_sk(sock->sk);
+ tt.ts_recent_stamp = tp->rx_opt.ts_recent_stamp;
+ tt.ts_recent = tp->rx_opt.ts_recent;
+ tt.rcv_tsval = tp->rx_opt.rcv_tsval;
+ tt.rcv_tsecr = tp->rx_opt.rcv_tsecr;
+ tt.saw_tstamp = tp->rx_opt.saw_tstamp;
+ tt.tstamp_ok = tp->rx_opt.tstamp_ok;
+ tt.dsack = tp->rx_opt.dsack;
+ tt.wscale_ok = tp->rx_opt.wscale_ok;
+ tt.sack_ok = tp->rx_opt.sack_ok;
+ tt.smc_ok = tp->rx_opt.smc_ok;
+ tt.snd_wscale = tp->rx_opt.snd_wscale;
+ tt.rcv_wscale = tp->rx_opt.rcv_wscale;
+ tt.advmss = tp->advmss;
+ tt.rttvar_us = tp->rttvar_us;
+ tt.srtt_us = tp->srtt_us;
+ tt.rcv_rtt_est.rtt_us = tp->rcv_rtt_est.rtt_us;
+ tt.rcv_rtt_est.seq = tp->rcv_rtt_est.seq;
+ tt.rcv_rtt_est.time = tp->rcv_rtt_est.time;
+ tt.rtt_us = tp->rack.rtt_us;
+ tt.mdev_max_us = tp->mdev_max_us;
+ fdput(f);
+ if (copy_to_user((void *) arg, &tt, sizeof tt))
+ return -EFAULT;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,6,0)
+static const struct file_operations ops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = myioctl,
+};
+#else
+static const struct proc_ops ops = {
+ .proc_ioctl = myioctl,
+};
+#endif
+static int __init custom_init (void) {
+ ent = proc_create("tcp_times", 0666, NULL, &ops);
+ if (!ent) {
+ printk(KERN_INFO "tcp_times failed to create procfs entry.");
+ return -EINVAL;
+ }
+ printk(KERN_INFO "tcp_times kernel module loaded.");
+ return 0;
+}
+static void __exit custom_exit (void) {
+ proc_remove(ent);
+ printk(KERN_INFO "tcp_times kernel module exiting ...");
+}
+module_init(custom_init);
+module_exit(custom_exit);
diff --git a/iv/orodja/ldmitm/tcp_times.h b/iv/orodja/ldmitm/tcp_times.h
new file mode 100644
index 0000000..3368c7b
--- /dev/null
+++ b/iv/orodja/ldmitm/tcp_times.h
@@ -0,0 +1,27 @@
+#define TCP_TIMES_PRINTF_FORMAT "fd: %d, ts_recent_stamp: %d, ts_recent: %u, rcv_tsval: %u, rcv_tsecr: %u, saw_tstamp: %u, tstamp_ok: %u, dsack: %u, wscale_ok: %u, sack_ok: %u, snd_wscale: %u, rcv_wscale: %u, advmss: %u, rttvar_us: %u, srtt_us: %u, rcv_rtt_est.rtt_us: %u, rcv_rtt_est.seq: %u, rcv_rtt_est.time: %lu, rtt_us: %u, mdev_max_us: %u"
+#define TCP_TIMES_PRINTF_VARIABLES(tt) tt fd, tt ts_recent_stamp, tt ts_recent, tt rcv_tsval, tt rcv_tsecr, tt saw_tstamp, tt tstamp_ok, tt dsack, tt wscale_ok, tt sack_ok, tt snd_wscale, tt rcv_wscale, tt advmss, tt rttvar_us, tt srtt_us, tt rcv_rtt_est.rtt_us, tt rcv_rtt_est.seq, tt rcv_rtt_est.time, tt rtt_us, tt mdev_max_us
+struct tcp_times { // Polja so pobrana iz jedra. Glej https://elixir.bootlin.com/linux/v6.11-rc4/source/include/linux/tcp.h#L302
+ int fd;
+ int ts_recent_stamp;/* Time we stored ts_recent (for aging) */
+ uint32_t ts_recent; /* Time stamp to echo next */
+ uint32_t rcv_tsval; /* Time stamp value */
+ uint32_t rcv_tsecr; /* Time stamp echo reply */
+ uint16_t saw_tstamp : 1, /* Saw TIMESTAMP on last packet */
+ tstamp_ok : 1, /* TIMESTAMP seen on SYN packet */
+ dsack : 1, /* D-SACK is scheduled */
+ wscale_ok : 1, /* Wscale seen on SYN packet */
+ sack_ok : 3, /* SACK seen on SYN packet */
+ smc_ok : 1, /* SMC seen on SYN packet */
+ snd_wscale : 4, /* Window scaling received from sender */
+ rcv_wscale : 4; /* Window scaling to send to receiver */
+ uint16_t advmss; /* Advertised MSS */
+ uint32_t rttvar_us; /* smoothed mdev_max */
+ uint32_t srtt_us; /* smoothed round trip time << 3 in usecs */
+ struct {
+ uint32_t rtt_us;
+ uint32_t seq;
+ uint64_t time;
+ } rcv_rtt_est;
+ uint32_t rtt_us; /* Associated RTT */
+ uint32_t mdev_max_us; /* maximal mdev for the last rtt period */
+}; // Ne vem, kaj veliko polj tu pomeni, vendar jih dodajam. Mogoče bodo uporabna.
diff --git a/iv/orodja/ldmitm/tcp_times_example.c b/iv/orodja/ldmitm/tcp_times_example.c
new file mode 100644
index 0000000..707bfce
--- /dev/null
+++ b/iv/orodja/ldmitm/tcp_times_example.c
@@ -0,0 +1,80 @@
+/*
+Posluša na TCP vratih 6969, prejme eno povezavo, vsako sekundo nanjo izpiše LF in piše statistiko, dobljeno iz jedrnega modula tcp_times.
+*/
+#include <stdint.h>
+#include "tcp_times.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <signal.h>
+int samomor = 0;
+void handler (int sig __attribute__((unused))) {
+ samomor = 1;
+}
+int main (void) {
+ if (signal(SIGINT, handler) == SIG_ERR) {
+ perror("signal");
+ return 1;
+ }
+ if (signal(SIGHUP, handler) == SIG_ERR) {
+ perror("signal");
+ return 1;
+ }
+ if (signal(SIGTERM, handler) == SIG_ERR) {
+ perror("signal");
+ return 1;
+ }
+ int tcp_socket = socket(AF_INET6, SOCK_STREAM, 0);
+ if (tcp_socket == -1) {
+ perror("socket");
+ return 1;
+ }
+ struct sockaddr_in6 sa6 = {
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(6969),
+ .sin6_addr = IN6ADDR_ANY_INIT,
+ };
+ if (bind(tcp_socket, (struct sockaddr *) &sa6, sizeof sa6) == -1) {
+ perror("bind");
+ return 1;
+ }
+ if (listen(tcp_socket, 1 /* only one client is handled*/) == -1) {
+ perror("listen");
+ goto die;
+ }
+ int flow = accept(tcp_socket, NULL, NULL);
+ if (flow == -1) {
+ perror("accept");
+ goto die;
+ }
+ int tcp_times = open("/proc/tcp_times", O_RDWR);
+ struct tcp_times tt = {
+ .fd = flow,
+ };
+ char buf = '\n';
+ while (true) {
+ if (ioctl(tcp_times, 0, &tt) == -1) {
+ perror("ioctl");
+ break;
+ }
+ printf(TCP_TIMES_PRINTF_FORMAT "\n", TCP_TIMES_PRINTF_VARIABLES(tt.));
+ if (send(flow, &buf, 1, MSG_NOSIGNAL) == -1) {
+ perror("write");
+ break;
+ }
+ if (samomor)
+ break;
+ sleep(1);
+ }
+die:
+ close(tcp_times);
+ close(flow);
+ close(tcp_socket);
+ return 1;
+}