Repository: samm-git/btest-opensource Branch: master Commit: 5040a01267c3 Files: 20 Total size: 79.2 KB Directory structure: gitextract_hqjlj7ey/ ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile.am ├── README.md ├── bclient.pl ├── bootstrap ├── bserver.pl ├── btest.c ├── build.sh ├── configure.ac ├── docker-compose.yml ├── install-sh ├── md5.c ├── md5.h ├── timing_mach.c ├── timing_mach.h ├── utils.c └── utils.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ ================================================ FILE: .gitignore ================================================ btest rust_btest-server/** .vscode/** ================================================ FILE: Dockerfile ================================================ # Use a minimal Alpine Linux base image with build tools FROM alpine:latest # Set the working directory inside the container WORKDIR /app # Install necessary build tools RUN apk --no-cache add build-base # Copy the build.sh script and btest source code into the container COPY build.sh . COPY . /app/ # Run the build.sh script to compile btest RUN chmod +x build.sh && ./build.sh COPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscaled /usr/local/bin/tailscaled COPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscale /usr/local/bin/tailscale # Define environment variables ENV USERNAME admin ENV PASSWORD admin RUN [ -n "$AUTHKEY" ] && [ ! -z "$AUTHKEY" ] && tailscale up --authkey $AUTHKEY || { echo "AuthKey is not set or empty. Exiting."; exit 1; } # Set the entry point for the container ENTRYPOINT ["./btest"] # Specify the default command to run when the container starts CMD ["-a", "${USERNAME}", "-p", "${PASSWORD}", "-s"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016 Alex Samorukov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile.am ================================================ bin_PROGRAMS = btest btest_SOURCES = btest.c md5.c timing_mach.c AM_LDFLAGS = -lpthread ================================================ FILE: README.md ================================================ # Mikrotik bandwith test protocol description ## about This is attempt to create opensource version of the btest tool. Currently it is possible to run bandwith-test only from another mikrotik devices or from the windows closed source client. Protocol itsef seems to be not complicated, so it should not be easy to create 3rd party client for it. ## Protocol description There is no official protocol description, so everything was obtained using WireShark tool and RouterOS 6 running in the virtualbox, which was connecting to the real device. For now i am inspecting only TCP, later will try to do UDP if TCP works. Please keep in mind that this data is guessed on the captures, so possibly is not accurate or wrong. ``` > bserver: hello 01:00:00:00 > client: Start test cmd, depending on the client settings: 01:01:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (transmit) 01:01:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (transmit, random data) 00:01:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (transmit, UDP) 01:02:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (receive) 01:02:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (receive, random data) 00:02:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: default (1500)) 00:02:01:00:d0:07:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: 2000) 00:02:01:00:00:fa:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: 64000) 01:02:01:00:00:80:00:00:01:00:00:00:00:00:00:00 (receive, remote-tx-speed=1) 01:02:01:00:00:80:00:00:ff:ff:ff:ff:00:00:00:00 (receive, remote-tx-speed=4294967295) 01:02:01:00:00:80:00:00:00:00:00:00:01:00:00:00 (receive, local-tx-speed=1) 01:02:01:00:00:80:00:00:00:00:00:00:ff:ff:ff:ff (receive, local-tx-speed=4294967295) 01:02:01:0a:00:80:00:00:00:00:00:00:00:00:00:00 (receive, tcp-connection-count=10) 01:03:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (both) 01:03:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (both, random data) 00:03:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (both, UDP) > bserver: Start test confirm (auth is disabled on server): 01:00:00:00 (tcp-connection-count=1 on a client) 01:bc:04:00 (tcp-connection-count>1 on a client) > bserver: auth requested, with 16 challenge bytes (3 random packets provided) 02:00:00:00:90:67:3f:0f:5c:c7:4e:17:a0:e0:9e:1c:b9:ee:3b:0c 02:00:00:00:17:e7:ee:84:83:cc:15:53:e8:fa:9c:0d:ad:ac:b8:e1 02:00:00:00:ee:d1:19:b9:d3:f2:df:6d:04:46:da:25:55:44:49:81 > client: auth reply (3 random packets, userid test) 90:75:1f:b0:2a:f7:25:51:46:25:71:c3:16:ce:cc:2b:74:65:73:74:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 a0:1b:fa:27:78:55:08:71:93:09:70:86:15:30:84:ac:74:65:73:74:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 b4:f2:9e:06:5e:74:da:89:65:c9:be:94:4d:bf:8f:20:74:65:73:74:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 > bserver: packet data follows 00:00:00:00........ ``` 1. Server always starts with "hello" (01:00:00:00) command after establishing TCP connection to port 2000. If UDP protocl is specified in the client TCP connection is still established. 2. Client sends 16 bytes command started with 01 (TCP) or 00 (UDP) command to the server. Some of the bytes guess: - **cmd[0]**: protocol, 01: TCP, 00: UDP - **cmd[1]**: direction, 01 - transmit, 02 - receive, 03 - both - **cmd[2]**: use random data, 00 - use random, 01: use \00 character - **cmd[3]**: tcp-connection-count, 0 if tcp-connection-count=1, number if more - **cmd[4:5]**: remote-udp-tx-size (dc:05) on UDP, 00:80 on TCP, UINT16 - Little Endian - **cmd[6:7]**: client buffer size. Maximum value for TCP is 65535 (FF:FF). - **cmd[8:11]**: remote-tx-speed, 0: unlimimted, 1-4294967295: value, UINT32 - Little Endian - **cmd[12:15]**: local-tx-speed, 0: unlimimted, 1-4294967295: value, UINT32 - Little Endian 3. If server authentication is disabled it sends 01:00:00:00 and starts to transmit/recieve data. If auth is enabled and **btest server versions is < 6.43** server sends 20bytes reply started with 02:00:00:00 in the beginning and random bytes (challenge) in the [4:15] range. Customer sends back 48 bytes reply containing user name (unencrypted) and 16 bytes hash of the password with challenge. Hashing alghoritm is double md5, hashed by challenge, see "authentication" section. If **btest server versions is >= 6.43** server sends **03:00:00:00** reply and new authentication method is used, based on the EC-SRP5. Exact implementation details are not yet known and method is not yet supported in the OSS software. See https://github.com/haakonnessjoen/MAC-Telnet/issues/42 for the related discussion. 4. If auth failed server sends back `00000000` (client shows "authentication failed"), if succeed - `01000000` packet and test is starting. 5. If tcp-connection-count > 1 server should reply with `01:xx:xx:00` where xx seems to be some kind of authentification data to start other connections. This number is used in other threads. 6. From time to time (~1 per second) server or client sends 12 bytes messages started with `07`, e.g. `07:00:00:00:01:00:00:00:36:6e:03:00`. Btest client relies on this information to show "transmit" speed. It is server-level statistic, where values are: - **stat[0]** is 07 (message id) - **stat[4-7]** number or time from start in seconds, sends one per second, UINT32 - Little Endian - **stat[8-11]** number of bytes transferred per sequence, UINT32 - Little Endian ## Old authentication methoud Sample of the challenge-response for the "test" password: ``` ad32d6f94d28161625f2f390bb895637 3c968565bc0314f281a6da1571cf7255 ``` Hashing alghoritm was found with implementation of the testing server and help from the **Chupaka** on a Mikrotik forum. It is md5('password' + md5('password' + hash)). This is an example on the Perl language, which using challenge from the example and returns same hash: ```perl use Digest::MD5 qw(md5 md5_hex); my $salt='ad32d6f94d28161625f2f390bb895637'; my $pass='test'; print md5_hex($pass.md5($pass.pack( 'H*', $salt)))."\n"; ``` Script should return `3c968565bc0314f281a6da1571cf7255` and its matching captured traffic. ================================================ FILE: bclient.pl ================================================ #!/usr/bin/env perl # this is PoC for the Mikrotik bandwith server. On mikorotik use # /tool bandwidth-test duration=10s protocol=tcp tcp-connection-count=1 # to connect to it use IO::Socket::INET; use Digest::MD5 qw(md5 md5_hex md5_base64); use Data::Dumper; # auto-flush on socket $| = 1; my $udp_port_offset=256; my $require_auth=0; # creating a listening socket my $socket = new IO::Socket::INET ( PeerHost => '192.168.10.1', PeerPort => '2000', Proto => 'tcp', ); die "cannot create socket $!\n" unless $socket; print "connecting to server port 2000\n"; $socket->recv($data, 1024); print "client hello data is ".unpack('H*', "$data")."\n"; $data=pack('CCCCvvNN', 1, # UDP 2, # TX 1, # Not random 0, # TCP Count 1500, # TX Size 0, # Unknown 0, # Unlimited 0, # Unlimited ); print "client command is ".unpack('H*', "$data")."\n"; $socket->send($data); while(defined($socket->recv($data, 32768))) { print "client recv data is ".unpack('H*', "$data")."\n"; } exit(0); while(1) { # waiting for a new client connection my $client_socket = $socket->accept(); # get information about a newly connected client my $client_address = $client_socket->peerhost(); my $client_port = $client_socket->peerport(); print "connection from $client_address:$client_port\n"; print "sending hello\n"; # write response data to the connected client $data = pack 'H*', '01000000'; $client_socket->send($data); print "reading reply\n"; $client_socket->recv($data, 1024); my $action=unpackCmd($data); print(Dumper($action)); # send auth requested command if ($require_auth) { $client_socket->send(pack 'H*', '02000000'); $digest=pack 'H*', '00000000000000000000000000000000'; # print "digest md5=".md5_hex($digest)." expected reply is: ".md5_hex(md5($digest))."\n"; $client_socket->send($digest); $client_socket->recv($data, 1024); print "client auth data is ".unpack('H*', "$data")."\n"; # $client_socket->send(pack 'H*', '00000000'); # auth failed $client_socket->send(pack 'H*', '01000000'); # auth accepted } if ($action->{proto} eq 'TCP') { # send data while (1) { $client_socket->send(pack 'H*', '00' x $action->{tx_size}); print "."; } # notify client that response has been sent shutdown($client_socket, 1); } else { my $tx_socket = new IO::Socket::INET ( PeerAddr => "$client_address:2000", Proto => 'udp', ); die "cannot create socket $!\n" unless $tx_socket; sleep(10); #while (1) { # $tx_socket->send(pack 'H*', '00' x $action->{tx_size}); # print "."; #} $tx_socket->close(); } } $socket->close(); sub unpackCmd { my $data=shift; my @cmdlist=unpack('CCCCvvNN', $data); my $cmd={ proto => $cmdlist[0] ? 'TCP' : 'UDP', direction => ($cmdlist[1]==1) ? 'RX' : (($cmdlist[1]==2) ? 'TX' : 'TXRX'), random => ($cmdlist[2]==0), tcp_conn_count => ($cmdlist[3]==0) ? 1 : $cmdlist[3], tx_size => $cmdlist[4], unknown => $cmdlist[5], remote_tx_speed => $cmdlist[6], local_tx_speed => $cmdlist[7], }; #print "data is ".unpack('H*', "$data")."\n"; return($cmd); } ================================================ FILE: bootstrap ================================================ #! /bin/sh # bootstrap — generic bootstrap/autogen.sh script for autotools projects # # Copyright © 2002—2015 Sam Hocevar # # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms of the Do What the Fuck You Want # to Public License, Version 2, as published by the WTFPL Task Force. # See http://www.wtfpl.net/ for more details. # # The latest version of this script can be found at the following place: # http://caca.zoy.org/wiki/build # Die if an error occurs set -e # Guess whether we are using configure.ac or configure.in if test -f configure.ac; then conffile="configure.ac" elif test -f configure.in; then conffile="configure.in" else echo "$0: could not find configure.ac or configure.in" exit 1 fi # Check for needed features auxdir="`sed -ne 's/^[ \t]*A._CONFIG_AUX_DIR *([[ ]*\([^] )]*\).*/\1/p' $conffile`" pkgconfig="`grep '^[ \t]*PKG_PROG_PKG_CONFIG' $conffile >/dev/null 2>&1 && echo yes || echo no`" libtool="`grep '^[ \t]*A._PROG_LIBTOOL' $conffile >/dev/null 2>&1 && echo yes || echo no`" header="`grep '^[ \t]*A._CONFIG_HEADER' $conffile >/dev/null 2>&1 && echo yes || echo no`" makefile="`[ -f Makefile.am ] && echo yes || echo no`" aclocalflags="`sed -ne 's/^[ \t]*ACLOCAL_AMFLAGS[ \t]*=//p' Makefile.am 2>/dev/null || :`" # Check for automake amvers="no" for v in "" "-1.15" "-1.14" "-1.13" "-1.12" "-1.11"; do if automake${v} --version > /dev/null 2>&1; then amvers=${v} break fi done if test "$amvers" = "no"; then echo "$0: automake not found" exit 1 fi # Check for autoconf acvers="no" for v in "" "259" "253"; do if autoconf${v} --version >/dev/null 2>&1; then acvers="${v}" break fi done if test "$acvers" = "no"; then echo "$0: autoconf not found" exit 1 fi # Check for libtool if test "$libtool" = "yes"; then libtoolize="no" if glibtoolize --version >/dev/null 2>&1; then libtoolize="glibtoolize" else for v in "16" "15" "" "14"; do if libtoolize${v} --version >/dev/null 2>&1; then libtoolize="libtoolize${v}" break fi done fi if test "$libtoolize" = "no"; then echo "$0: libtool not found" exit 1 fi fi # Check for pkg-config if test "$pkgconfig" = "yes"; then if ! pkg-config --version >/dev/null 2>&1; then echo "$0: pkg-config not found" exit 1 fi fi # Remove old cruft for x in aclocal.m4 configure config.guess config.log config.sub config.cache config.h.in config.h compile libtool.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 ltmain.sh libtool ltconfig missing mkinstalldirs depcomp install-sh; do rm -f $x autotools/$x; if test -n "$auxdir"; then rm -f "$auxdir/$x"; fi; done rm -Rf autom4te.cache if test -n "$auxdir"; then if test ! -d "$auxdir"; then mkdir "$auxdir" fi aclocalflags="-I $auxdir -I . ${aclocalflags}" fi # Honour M4PATH because sometimes M4 doesn't save_IFS=$IFS IFS=: tmp="$M4PATH" for x in $tmp; do if test -n "$x"; then aclocalflags="-I $x ${aclocalflags}" fi done IFS=$save_IFS # Explain what we are doing from now set -x # Bootstrap package if test "$libtool" = "yes"; then ${libtoolize} --copy --force if test -n "$auxdir" -a ! "$auxdir" = "." -a -f "ltmain.sh"; then echo "$0: working around a minor libtool issue" mv ltmain.sh "$auxdir/" fi fi aclocal${amvers} ${aclocalflags} autoconf${acvers} if test "$header" = "yes"; then autoheader${acvers} fi if test "$makefile" = "yes"; then #add --include-deps if you want to bootstrap with any other compiler than gcc #automake${amvers} --add-missing --copy --include-deps automake${amvers} --foreign --add-missing --copy fi # Remove cruft that we no longer want rm -Rf autom4te.cache ================================================ FILE: bserver.pl ================================================ #!/usr/bin/env perl # this is PoC for the Mikrotik bandwith server. On mikorotik use # /tool bandwidth-test duration=10s protocol=tcp tcp-connection-count=1 # to connect to it use IO::Socket::INET; use Digest::MD5 qw(md5 md5_hex md5_base64); use Data::Dumper; # auto-flush on socket $| = 1; my $require_auth=0; my $udp_port_offset=256; # creating a listening socket my $socket = new IO::Socket::INET ( LocalHost => '0.0.0.0', LocalPort => '2000', Proto => 'tcp', Listen => 5, Reuse => 1 ); die "cannot create socket $!\n" unless $socket; print "server waiting for client connection on port 2000\n"; while(1) { # waiting for a new client connection my $client_socket = $socket->accept(); # get information about a newly connected client my $client_address = $client_socket->peerhost(); my $client_port = $client_socket->peerport(); print "connection from $client_address:$client_port\n"; print "sending hello\n"; # write response data to the connected client $data = pack 'H*', '01000000'; $client_socket->send($data); print "reading reply\n"; $client_socket->recv($data, 1024); print "data is ".unpack('H*', "$data")."\n"; my $action=unpackCmd($data); print(Dumper($action)); # send auth requested command if ($require_auth) { $client_socket->send(pack 'H*', '02000000'); $digest=pack 'H*', '00000000000000000000000000000000'; # print "digest md5=".md5_hex($digest)." expected reply is: ".md5_hex(md5($digest))."\n"; $client_socket->send($digest); $client_socket->recv($data, 1024); print "client auth data is ".unpack('H*', "$data")."\n"; # $client_socket->send(pack 'H*', '00000000'); # auth failed $client_socket->send(pack 'H*', '01000000'); # auth accepted } if ($action->{proto} eq 'TCP') { # send data while (1) { $client_socket->send(pack 'H*', '00' x $action->{tx_size}); print "."; } # notify client that response has been sent shutdown($client_socket, 1); } else { print "sending hello\n"; # write response data to the connected client $data = pack 'H*', '01000000'; $client_socket->send($data); my $locudpport=2001; $data= pack('n', $locudpport); print "sending local port: " . unpack('H*', $data) . "\n"; $client_socket->send($data); #while(defined($client_socket->recv($data, 32768))) { # print "client recv data is ".unpack('H*', "$data")."\n"; #} #exit(0); my $tx_socket = new IO::Socket::INET ( PeerHost => "$client_address", PeerPort => $locudpport+$udp_port_offset, LocalHost => '0.0.0.0', LocalPort => $locudpport, Proto => 'udp', ); die "cannot create socket $!\n" unless $tx_socket; #sleep(10); my $seq=1; while (1) { $tx_socket->send(pack 'NNH*', 0, $seq, '00' x ($action->{tx_size}-8-28)); $seq++; print "."; } $tx_socket->close(); } } $socket->close(); sub unpackCmd { my $data=shift; my @cmdlist=unpack('CCCCvvNN', $data); my $cmd={ proto => $cmdlist[0] ? 'TCP' : 'UDP', direction => ($cmdlist[1]==1) ? 'RX' : (($cmdlist[1]==2) ? 'TX' : 'TXRX'), random => ($cmdlist[2]==0), tcp_conn_count => ($cmdlist[3]==0) ? 1 : $cmdlist[3], tx_size => $cmdlist[4], unknown => $cmdlist[5], remote_tx_speed => $cmdlist[6], local_tx_speed => $cmdlist[7], }; #print "data is ".unpack('H*', "$data")."\n"; return($cmd); } ================================================ FILE: btest.c ================================================ /* Program to emulate the Mikrotik Bandwidth test protocol */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "timing_mach.h" #include "utils.h" #include "md5.h" #define BTEST_PORT 2000 #define BTEST_PORT_CLIENT_OFFSET 256 #define CMD_PROTO_UDP 0 #define CMD_PROTO_TCP 1 #define CMD_DIR_RX 0x01 #define CMD_DIR_TX 0x02 #define CMD_RANDOM 0 unsigned char helloStr[] = {0x01, 0x00, 0x00, 0x00}; unsigned char noAuthResp[] = {0x01, 0x00, 0x00, 0x00}; unsigned char needAuthResp[] = {0x02, 0x00, 0x00, 0x00}; unsigned char failedAuthResp[] = {0x00, 0x00, 0x00, 0x00}; // unsigned char cmdStr[16]; unsigned int udpport = BTEST_PORT; struct cmdStruct { int proto; int direction; int random; int tcp_conn_count; unsigned int tx_size; unsigned int client_buf_size; unsigned long remote_tx_speed; unsigned long local_tx_speed; }; struct statStruct { unsigned long seq; unsigned char unknown[3]; unsigned long recvBytes; unsigned long maxInterval; // In us, Not sent over the wire unsigned long minInterval; // In us, Not sent over the wire signed long lostPackets; // Not sent over the wire }; void usage(); void usage_long(); int server(); int client(); int server_conn(int cmdsock, char *); struct cmdStruct unpackCmdStr(unsigned char *); void packCmdStr(struct cmdStruct *, unsigned char *); struct statStruct unpackStatStr(unsigned char *); void packStatStr(struct statStruct *, unsigned char *); void printStatStruct(char *, struct statStruct *); int test_udp(struct cmdStruct, int, char *); int test_tcp(struct cmdStruct, int); void timespec_diff(struct timespec *, struct timespec *, struct timespec *); void timespec_add(struct timespec *, struct timespec *); int timespec_cmp(struct timespec *t1, struct timespec *t2); void timespec_dump(char *, struct timespec *); void dumpBuffer(const char *msg, unsigned char *buffer, int len); void packShortLE(unsigned char *, unsigned int); void packLongLE(unsigned char *, unsigned long); void packLongBE(unsigned char *, unsigned long); void unpackShortLE(unsigned char *, unsigned int *); void unpackLongLE(unsigned char *, unsigned long *); void unpackLongBE(unsigned char *, unsigned long *); void calc_interval(struct timespec *, unsigned long, unsigned int); unsigned char *calc_md5auth(unsigned char *nonce, char *passwd); char *opt_bandwidth = NULL; int opt_udpmode = 0; int opt_server = 0; int opt_interval = 0; int opt_nat = 0; int opt_transmit = 0; int opt_receive = 0; int opt_display = 0; char *opt_connect = NULL; char *opt_authuser = NULL; char *opt_authpass = NULL; double new_tx_speed; // Used to command the sending thread to change speed int tx_speed_changed = 0; int main(int argc, char **argv) { int opt; static struct option long_options[] = { {"udp", no_argument, &opt_udpmode, 1}, {"transmit", no_argument, &opt_transmit, 1}, {"receive", no_argument, &opt_receive, 1}, {"server", no_argument, &opt_server, 1}, {"nat", no_argument, &opt_nat, 1}, {"display", no_argument, &opt_display, 1}, {"help", no_argument, 0, 'h'}, {"client", required_argument, 0, 'c'}, {"interval", required_argument, 0, 'i'}, {"bandwidth", required_argument, 0, 'b'}, {"authuser", required_argument, 0, 'a'}, {"authpass", required_argument, 0, 'p'}, {0, 0, 0, 0}}; int option_index = 0; if (argc < 2) { usage(); exit(1); } while ((opt = getopt_long(argc, argv, "utrsnhdc:i:b:a:p:", long_options, &option_index)) != -1) { switch (opt) { case 'u': opt_udpmode = 1; break; case 't': opt_transmit = 1; break; case 'r': opt_receive = 1; break; case 's': opt_server = 1; break; case 'n': opt_nat = 1; break; case 'd': opt_display = 1; break; case 'c': opt_connect = strdup(optarg); break; case 'i': opt_interval = atoi(optarg); break; case 'b': opt_bandwidth = strdup(optarg); break; case 'a': opt_authuser = strdup(optarg); break; case 'p': opt_authpass = strdup(optarg); break; case 'h': usage_long(); exit(1); default: usage(); exit(EXIT_FAILURE); } } if (opt_server) { server(); } else { if (!opt_transmit && !opt_receive) { printf("You must specify transmit(-t) or receive(-r)\n"); exit(EXIT_FAILURE); } client(); } } void usage() { const char usage_shortstr[] = "Usage: btest [-s|-r -c host] [options]\n" "Try `btest --help' for more information.\n"; printf(usage_shortstr); } void usage_long() { const char usage_longstr[] = "Usage: btest [-s|-c host] [options]\n" " btest [-h|--help]\n\n" "Server or Client:\n" " -i, --interval # seconds between periodic bandwidth reports\n" " -h, --help show this message and quit\n" " -a, --authuser provide username for authentication\n" " -p, --authpass provide password for authentication\n" "Server specific:\n" " -s, --server run in server mode\n" "Client specific:\n" " -c, --client run in client mode, connecting to \n" " -t, --transmit transmit data\n" " -r, --receive receive data\n" " -u, --udp use UDP\n" " -b, --bandwidth #[KMG][/#] target bandwidth in bits/sec (0 for unlimited)\n" " (default %d Mbit/sec for UDP, unlimited for TCP)\n" " (optional slash and packet count for burst mode)\n" ; printf("%s", usage_longstr); } int client() { int cmdsock; struct cmdStruct cmd; unsigned char cmdStr[16]; struct sockaddr_in serverAddr; struct hostent *he; char *remoteIP; char bwmult; int ret; cmd.proto = CMD_PROTO_UDP; cmd.direction = opt_transmit ? CMD_DIR_RX : 0; cmd.direction += opt_receive ? CMD_DIR_TX : 0; cmd.random = 0; cmd.tcp_conn_count = 0; cmd.tx_size = 1500; cmd.client_buf_size = 0; if (opt_bandwidth) { if ((ret = sscanf(opt_bandwidth, "%lu%c", &cmd.remote_tx_speed, &bwmult)) < 1) { fprintf(stderr, "Cannot parse bandwidth string\n"); return (-1); } if (ret == 2) { /* Apply multiplier */ if (bwmult == 'k' || bwmult == 'K') { cmd.remote_tx_speed *= 1000; } else if (bwmult == 'm' || bwmult == 'M') { cmd.remote_tx_speed *= 1000000; } else { fprintf(stderr, "Cannot parse bandwidth string\n"); } } } else { cmd.remote_tx_speed = 0; // Unlimited } cmd.local_tx_speed = cmd.remote_tx_speed; packCmdStr(&cmd, cmdStr); if ((he = gethostbyname(opt_connect)) == NULL) { // get the host info perror("gethostbyname"); exit(1); } // Return the first one; bcopy((char *)he->h_addr, (char *)&serverAddr.sin_addr.s_addr, he->h_length); remoteIP = strdup(inet_ntoa(serverAddr.sin_addr)); cmdsock = socket(PF_INET, SOCK_STREAM, 0); int enable = 1; if (setsockopt(cmdsock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { perror("setsockopt(SO_REUSEADDR) failed"); } serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(BTEST_PORT); memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); if (connect(cmdsock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { perror("connect"); exit(1); } /* Look for hello string */ if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr)) { fprintf(stderr, "Remote did not return any hello response\n"); return (-1); } if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0) { fprintf(stderr, "Remote did not return correct hello response\n"); dumpBuffer("Response: ", helloStr, sizeof(helloStr)); return (-1); } send(cmdsock, cmdStr, sizeof(cmdStr), 0); /* Look for hello string */ if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr)) { fprintf(stderr, "Remote did not return any auth response\n"); return (-1); } if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0) { if (memcmp(helloStr, needAuthResp, sizeof(needAuthResp)) == 0) { /* Fetch the 16 random bytes */ unsigned char nonce[16]; char user[32]; unsigned char *md5hash; if (recv(cmdsock, nonce, sizeof(nonce), 0) < sizeof(nonce)) { fprintf(stderr, "Remote did not send auth nonce\n"); return (-1); } // dumpBuffer("Nonce: ", nonce, sizeof(nonce)); memset(user, '\0', sizeof(user)); strncpy(user, opt_authuser, sizeof(user)); md5hash = calc_md5auth(nonce, opt_authpass); // dumpBuffer("MD5: ", md5hash, 16); send(cmdsock, md5hash, 16, 0); send(cmdsock, user, sizeof(user), 0); /* Look for hello string */ if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr)) { fprintf(stderr, "Remote did not return auth response\n"); return (-1); } if (memcmp(helloStr, failedAuthResp, sizeof(failedAuthResp)) == 0) { fprintf(stderr, "Remote authentication failed\n"); // dumpBuffer("Response: ", helloStr,sizeof(helloStr)); return (-1); } if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0) { fprintf(stderr, "Remote did not return correct hello response\n"); dumpBuffer("Response: ", helloStr, sizeof(helloStr)); return (-1); } } else { fprintf(stderr, "Remote did not return correct hello response\n"); dumpBuffer("Response: ", helloStr, sizeof(helloStr)); return (-1); } } if (cmd.proto == CMD_PROTO_UDP) { test_udp(cmd, cmdsock, remoteIP); } else { test_tcp(cmd, cmdsock); } return 0; } int server() { int controlSocket; struct sockaddr_in serverAddr; struct sockaddr_in clientAddr; socklen_t addr_size; int newSocket; printf("Running in server mode\n"); controlSocket = socket(PF_INET, SOCK_STREAM, 0); int enable = 1; if (setsockopt(controlSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { perror("setsockopt(SO_REUSEADDR) failed"); } serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(BTEST_PORT); serverAddr.sin_addr.s_addr = INADDR_ANY; memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); bind(controlSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); if (listen(controlSocket, 5) == 0) printf("Listening\n"); else printf("Error\n"); addr_size = sizeof(clientAddr); signal(SIGCHLD, SIG_IGN); while (1) { newSocket = accept(controlSocket, (struct sockaddr *)&clientAddr, &addr_size); /* fork a child process to handle the new connection */ if (!fork()) { server_conn(newSocket, strdup(inet_ntoa(clientAddr.sin_addr))); close(newSocket); printf("Complete\n"); exit(0); } else { /*if parent, close the socket and go back to listening new requests*/ close(newSocket); } } return 0; } int server_conn(int cmdsock, char *remoteIP) { int nBytes = 1; struct cmdStruct cmd; unsigned char cmdStr[16]; uint32_t hexValue; int flag = 1; setsockopt(cmdsock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)); /* Send hello message */ send(cmdsock, helloStr, sizeof(helloStr), 0); /* Recieve command */ nBytes = recv(cmdsock, cmdStr, sizeof(cmdStr), 0); if (nBytes < sizeof(cmdStr)) { return (-1); } cmd = unpackCmdStr(cmdStr); printf("proto=%d\n", cmd.proto); printf("direction=%d\n", cmd.direction); printf("random=%d\n", cmd.random); printf("tcp_conn_count=%d\n", cmd.tcp_conn_count); printf("tx_size=%d\n", cmd.tx_size); printf("client_buf_size=%d\n", cmd.client_buf_size); printf("remote_tx_speed=%lu\n", cmd.remote_tx_speed); printf("local_tx_speed=%lu\n", cmd.local_tx_speed); printf("remoteIP=%s\n", remoteIP); /* auth logic here */ int isauthrequired = isStringNotEmpty(opt_authuser) || isStringNotEmpty(opt_authpass); printf("is auth required: %d\n", isauthrequired); if (isauthrequired) { /* Send auth request */ hexValue = htonl(0x02000000); send(cmdsock, &hexValue, sizeof(hexValue), 0); unsigned char nonce[16]; /* Generate random nonce */ generateRandomNonce(nonce); char recuser[32]; char recdigest[32]; unsigned char *md5hash; send(cmdsock, nonce, sizeof(nonce), 0); md5hash = calc_md5auth(nonce, opt_authpass); unsigned char authstr[32]; nBytes = recv(cmdsock, authstr, sizeof(authstr), 0); if (nBytes < sizeof(authstr)) { return (-1); } unsigned char username[AUTHSTR_SIZE - 16]; char receivedhash[2 * (size_t)16 + 1]; char serverdigest[2 * (size_t)16 + 1]; memcpy(username, authstr + 16, sizeof(username)); getHexRepresentation(authstr, (size_t)16, receivedhash); getHexRepresentation(md5hash, (size_t)16, serverdigest); // dumpBuffer("> auth Response: ", authstr, sizeof(authstr)); printf("auth received from %s: user(%s) - pass_digest(%s)\n", remoteIP, username, receivedhash); printf("server have user(%s) - pass_digest(%s)\n", opt_authuser, serverdigest); if (isauth(opt_authuser, username, receivedhash, serverdigest)) { hexValue = htonl(0x01000000); send(cmdsock, &hexValue, sizeof(hexValue), 0); } else { hexValue = htonl(0x00000000); send(cmdsock, &hexValue, sizeof(hexValue), 0); return (-1); } } /* Send all OK message */ send(cmdsock, helloStr, sizeof(helloStr), 0); if (cmd.proto == CMD_PROTO_UDP) { if (test_udp(cmd, cmdsock, remoteIP) != -1) { return (-1); } } else { if (test_tcp(cmd, cmdsock) == -1) { return (-1); } } /* loop while connection is live */ return 0; } void packShortLE(unsigned char *buf, unsigned int res) { int i; for (i = 0; i < 2; i++) { buf[i] = res & 0xff; res >>= 8; } } void packLongLE(unsigned char *buf, unsigned long res) { int i; for (i = 0; i < 4; i++) { buf[i] = res & 0xff; res >>= 8; } } void packLongBE(unsigned char *buf, unsigned long res) { int i; for (i = 3; i >= 0; i--) { buf[i] = res & 0xff; res >>= 8; } } void unpackShortLE(unsigned char *buf, unsigned int *pres) { int i; *pres = 0; for (i = 2; i >= 0; i--) { *pres <<= 8; *pres += buf[i]; } } void unpackLongLE(unsigned char *buf, unsigned long *pres) { int i; *pres = 0; for (i = 3; i >= 0; i--) { *pres <<= 8; *pres += buf[i]; } } void unpackLongBE(unsigned char *buf, unsigned long *pres) { int i; *pres = 0; for (i = 0; i < 4; i++) { *pres <<= 8; *pres += buf[i]; } } struct cmdStruct unpackCmdStr(unsigned char *cmdStr) { struct cmdStruct cmd; dumpBuffer("Cmd buffer: ", cmdStr, 16); cmd.proto = cmdStr[0]; cmd.direction = cmdStr[1]; cmd.random = cmdStr[2]; cmd.tcp_conn_count = cmdStr[3]; unpackShortLE(&cmdStr[4], &cmd.tx_size); /* Assume little endian */ unpackShortLE(&cmdStr[6], &cmd.client_buf_size); unpackLongLE(&cmdStr[8], &cmd.remote_tx_speed); unpackLongLE(&cmdStr[12], &cmd.local_tx_speed); return (cmd); } void packCmdStr(struct cmdStruct *pcmd, unsigned char *buf) { buf[0] = (pcmd->proto == CMD_PROTO_TCP) ? CMD_PROTO_TCP : CMD_PROTO_UDP; buf[1] = pcmd->direction & 0x03; buf[2] = pcmd->random ? 1 : 0; buf[3] = pcmd->tcp_conn_count; /* Little endian */ packShortLE(&buf[4], pcmd->tx_size); packShortLE(&buf[6], pcmd->client_buf_size); packLongLE(&buf[8], pcmd->remote_tx_speed); packLongLE(&buf[12], pcmd->local_tx_speed); // dumpBuffer("Packed Buffer: ", buf, 16); return; } struct statStruct unpackStatStr(unsigned char *buf) { struct statStruct stat; // dumpBuffer("Stat buffer: ", buf, 12); unpackLongBE(&buf[1], &stat.seq); memcpy(stat.unknown, buf + 5, 3); unpackLongLE(&buf[8], &stat.recvBytes); /* These three are not used */ stat.maxInterval = 0; stat.minInterval = 0; stat.lostPackets = 0; return (stat); } void printStatStruct(char *msg, struct statStruct *pstat) { /* Work out the bit rate - assume status every second */ double bitRate; char bitRateStr[20]; bitRate = pstat->recvBytes * 8; if (bitRate > 10000000) { sprintf(bitRateStr, "%.1fMb/s", bitRate / 1000000); } else { sprintf(bitRateStr, "%.1fkb/s", bitRate / 1000); } printf("%sSeq: %lu, Rate: %s", msg, pstat->seq, bitRateStr); if (pstat->maxInterval > 0) { printf(", Lost: %ld, Min: %.4lfms, Max: %.4lfms, Diff %0.4lfms\n", pstat->lostPackets, ((double)pstat->minInterval) / 1000, ((double)pstat->maxInterval) / 1000, ((double)pstat->maxInterval) / 1000 - ((double)pstat->minInterval) / 1000); } else { printf("\n"); } } void packStatStr(struct statStruct *pstat, unsigned char *buf) { buf[0] = 0x07; packLongBE(&buf[1], pstat->seq); buf[5] = 0x00; buf[6] = 0x00; buf[7] = 0x00; packLongLE(&buf[8], pstat->recvBytes); // dumpBuffer("Stat buffer: ", buf, 12); return; } int udpSocket; void *test_udp_tx(void *arg) { struct cmdStruct *pcmd; unsigned char *buf; int seq = 1; int i; struct timespec interval; /* Interval between packets in nano seconds */ struct timespec nextPacketTime; struct timespec now; int tx_speed_variable = 0; unsigned long tx_speed; // printf("Calling test_udp_tx()\n"); // sleep(1); pcmd = (struct cmdStruct *)arg; // printf("Calling test_udp_tx(%d)\n", pcmd->tx_size); if (opt_server) { tx_speed = pcmd->remote_tx_speed; } else { tx_speed = pcmd->local_tx_speed; } printf("Tx speed: %lu\n", tx_speed); if (tx_speed == 0) { tx_speed_variable = 1; tx_speed = 1000000; } new_tx_speed = tx_speed; calc_interval(&interval, tx_speed, pcmd->tx_size); timespec_dump("Interval: ", &interval); buf = (unsigned char *)malloc(pcmd->tx_size - 28); // printf("Calling test_udp_tx(more)\n"); bzero(buf, pcmd->tx_size - 28); /* Get current time and add the interval to it */ clock_gettime(CLOCK_MONOTONIC, &nextPacketTime); timespec_dump("gettime: ", &nextPacketTime); while (1) { if (tx_speed_variable && tx_speed_changed) { tx_speed_changed = 0; tx_speed = new_tx_speed; calc_interval(&interval, tx_speed, pcmd->tx_size); // printf("New Tx speed: %lf\n", new_tx_speed); } nextPacketTime.tv_sec += interval.tv_sec; nextPacketTime.tv_nsec += interval.tv_nsec; if (nextPacketTime.tv_nsec >= 1000000000) { nextPacketTime.tv_sec += nextPacketTime.tv_nsec / 1000000000; nextPacketTime.tv_nsec %= 1000000000; } int tmp = seq++; // printf("seq=%d\n", seq); /* Put in sequence number */ for (i = 3; i >= 0; i--) { buf[i] = tmp % 256; tmp = tmp >> 8; } // printf("Sleep until %lu:%lu\n", nextPacketTime.tv_sec, nextPacketTime.tv_nsec); // timespec_dump("sleep until: ", &nextPacketTime); clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec <= nextPacketTime.tv_sec && now.tv_nsec < nextPacketTime.tv_nsec) { #ifdef __MACH__ clock_nanosleep_abstime(&nextPacketTime); #else if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &nextPacketTime, NULL) < 0) { perror("clock_nanosleep: "); } #endif } send(udpSocket, buf, pcmd->tx_size - 28, 0); /* if (send(udpSocket, buf, pcmd->tx_size-28,0)<0) { perror("send udp: "); exit(-1); } */ } } struct statStruct recvStats; void *test_udp_rx(void *arg) { struct cmdStruct *pcmd; unsigned char *buf; int nBytes; struct timespec last; struct timespec now; struct timespec interval; unsigned long intervalUs; unsigned long lastSeq = 0; unsigned long thisSeq = 0; recvStats.recvBytes = 0; recvStats.maxInterval = 0; recvStats.minInterval = 0; recvStats.lostPackets = 0; pcmd = (struct cmdStruct *)arg; // printf("Calling test_udp_rx(tx_size=%d)\n", pcmd->tx_size); buf = (unsigned char *)malloc(pcmd->tx_size); last.tv_sec = 0; last.tv_nsec = 0; while (1) { if ((nBytes = recv(udpSocket, buf, pcmd->tx_size, 0)) < 0) { /* Ignore connection refused - the other end probably wasn't ready */ if (errno != ECONNREFUSED) { perror("test_udp_rx: recv udp: "); exit(-1); } } if (nBytes > 0) { clock_gettime(CLOCK_MONOTONIC, &now); thisSeq = buf[0]; thisSeq = thisSeq * 256 + buf[1]; thisSeq = thisSeq * 256 + buf[2]; thisSeq = thisSeq * 256 + buf[3]; if (lastSeq > 0) { /* Ignore the first one - we often lose packets initially */ /* if (thisSeq-lastSeq-1 != 0) { printf("loss: thisSeq=%lu, lastSeq=%lu\n", thisSeq, lastSeq); } */ recvStats.lostPackets += thisSeq - lastSeq - 1; } if (last.tv_sec != 0 && last.tv_nsec != 0) { /* Work out time difference */ timespec_diff(&last, &now, &interval); intervalUs = interval.tv_nsec / 1000; intervalUs += interval.tv_sec * 1000000; /* Divide by the difference in the sequence ** number so that we don't get a bump in the ** interval due to a bunch of missed packets */ intervalUs /= thisSeq - lastSeq; if (recvStats.maxInterval == 0) { /* First result */ // printf("First: %lu\n", intervalUs); recvStats.maxInterval = intervalUs; recvStats.minInterval = intervalUs; } else { if (intervalUs > recvStats.maxInterval) { recvStats.maxInterval = intervalUs; } if (intervalUs < recvStats.minInterval) { recvStats.minInterval = intervalUs; } } } last = now; recvStats.recvBytes += nBytes + 28; lastSeq = thisSeq; } } } int test_udp(struct cmdStruct cmd, int cmdsock, char *remoteIP) { unsigned char socknumbuf[2]; pthread_t pth_tx; pthread_t pth_rx; struct sockaddr_in serverAddr, clientAddr; socklen_t addr_size; int nBytes; unsigned char buffer[1024]; struct statStruct remoteStats; struct timespec interval; /* Interval between status messages */ struct timespec nextStatusTime; struct timespec timeout; struct timespec now; // printf("Calling test_udp()\n"); if (opt_server) { /* Send server socket number */ udpport++; socknumbuf[0] = udpport / 256; socknumbuf[1] = udpport % 256; send(cmdsock, socknumbuf, sizeof(socknumbuf), 0); } else { if (recv(cmdsock, socknumbuf, sizeof(socknumbuf), 0) < sizeof(socknumbuf)) { fprintf(stderr, "Did not recieve remote port number\n"); return (-1); } // dumpBuffer("Socket number buffer: ", socknumbuf, 2); udpport = (socknumbuf[0] << 8) + socknumbuf[1]; } // printf("Calling test_udp(udpport=%d)\n", udpport); addr_size = sizeof(clientAddr); /* Create a UDP socket to transmit/recieve the data */ udpSocket = socket(PF_INET, SOCK_DGRAM, 0); serverAddr.sin_family = AF_INET; if (opt_server) { serverAddr.sin_port = htons(udpport); } else { serverAddr.sin_port = htons(udpport + BTEST_PORT_CLIENT_OFFSET); } serverAddr.sin_addr.s_addr = INADDR_ANY; memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); bind(udpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); clientAddr.sin_family = AF_INET; if (opt_server) { clientAddr.sin_port = htons(udpport + BTEST_PORT_CLIENT_OFFSET); } else { clientAddr.sin_port = htons(udpport); } clientAddr.sin_addr.s_addr = inet_addr(remoteIP); memset(clientAddr.sin_zero, '\0', sizeof clientAddr.sin_zero); /* Connect it to the remote end */ connect(udpSocket, (struct sockaddr *)&clientAddr, sizeof(clientAddr)); /* We have to swap the command round between server and client */ if (((cmd.direction & CMD_DIR_TX) && opt_server) || ((cmd.direction & CMD_DIR_RX) && !opt_server)) { pthread_create(&pth_tx, NULL, test_udp_tx, (void *)&cmd); } if (((cmd.direction & CMD_DIR_RX) && opt_server) || ((cmd.direction & CMD_DIR_TX) && !opt_server)) { if (opt_nat) { /* Send a zero length packet to open any firewall */ send(udpSocket, buffer, 0, 0); } pthread_create(&pth_rx, NULL, test_udp_rx, (void *)&cmd); } /* Interval between status messages */ interval.tv_nsec = 0; interval.tv_sec = 1; /* Get current time and add the interval to it */ clock_gettime(CLOCK_MONOTONIC, &nextStatusTime); timespec_add(&nextStatusTime, &interval); recvStats.seq = 0; while (1) { int ready; fd_set readfds; clock_gettime(CLOCK_MONOTONIC, &now); timespec_diff(&now, &nextStatusTime, &timeout); FD_ZERO(&readfds); FD_SET(cmdsock, &readfds); ready = pselect(cmdsock + 1, &readfds, NULL, NULL, &timeout, NULL); if (ready) { nBytes = recv(cmdsock, buffer, 1024, 0); if (nBytes <= 0) { exit(0); } remoteStats = unpackStatStr(buffer); if (((cmd.direction & CMD_DIR_TX) && opt_server) || ((cmd.direction & CMD_DIR_RX) && !opt_server)) { /* Only print this if we are transmitting */ if (opt_display) { double bitRate = remoteStats.recvBytes * 8; if (bitRate > 10000000) { printf("%.1f Mb/s\n", bitRate / 1000000); } else { printf("%.1f kb/s\n", bitRate / 1000); } } else { printStatStruct("Remote: ", &remoteStats); } fflush(stdout); /* Set the outgoing speed to be twice the rate reported */ new_tx_speed = remoteStats.recvBytes * 8; new_tx_speed *= 1.5; tx_speed_changed = 1; } } clock_gettime(CLOCK_MONOTONIC, &now); if (timespec_cmp(&now, &nextStatusTime) > 0) { recvStats.seq++; packStatStr(&recvStats, buffer); if (((cmd.direction & CMD_DIR_RX) && opt_server) || ((cmd.direction & CMD_DIR_TX) && !opt_server)) { /* Only print this if we are recieving */ if (opt_display) { double bitRate = recvStats.recvBytes * 8; if (bitRate > 10000000) { printf("%.1f Mb/s\n", bitRate / 1000000); } else { printf("%.1f kb/s\n", bitRate / 1000); } } else { printStatStruct("Local : ", &recvStats); } fflush(stdout); } send(cmdsock, buffer, 12, 0); timespec_add(&nextStatusTime, &interval); recvStats.recvBytes = 0; recvStats.maxInterval = 0; recvStats.minInterval = 0; recvStats.lostPackets = 0; } } } int tcpSocket; void *test_tcp_tx(void *arg) { struct cmdStruct *pcmd; unsigned char *buf; // printf("Calling test_tcp_tx()\n"); sleep(1); pcmd = (struct cmdStruct *)arg; buf = (unsigned char *)malloc(pcmd->tx_size); bzero(buf, pcmd->tx_size); buf[0] = 0x07; buf[4] = 0x01; while (send(tcpSocket, buf, pcmd->tx_size, 0) > 0) ; return NULL; } int test_tcp(struct cmdStruct cmd, int cmdsock) { pthread_t pth_tx; // pthread_t pth_rx; int nBytes, i; unsigned char buffer[1024]; // printf("Calling test_tcp()\n"); tcpSocket = cmdsock; if (cmd.direction == CMD_DIR_TX) { pthread_create(&pth_tx, NULL, test_tcp_tx, (void *)&cmd); } printf("Listening on TCP cmdsock\n"); while ((nBytes = recv(cmdsock, buffer, 1024, 0))) { for (i = 0; i < nBytes - 1; i++) { printf("%02x", buffer[i]); } printf("\n"); } return 0; } /* Calculate the difference between two timespec's */ void timespec_diff(struct timespec *start, struct timespec *stop, struct timespec *result) { if ((stop->tv_nsec - start->tv_nsec) < 0) { result->tv_sec = stop->tv_sec - start->tv_sec - 1; result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000; } else { result->tv_sec = stop->tv_sec - start->tv_sec; result->tv_nsec = stop->tv_nsec - start->tv_nsec; } return; } /* Add timespec t2 onto timespec t1 onto an existing one */ void timespec_add(struct timespec *t1, struct timespec *t2) { t1->tv_sec += t2->tv_sec; t1->tv_nsec += t2->tv_nsec; if (t1->tv_nsec >= 1000000000) { t1->tv_sec += t1->tv_nsec / 1000000000; t1->tv_nsec %= 1000000000; } } /* Return -1, 0, or +1 if t1 less than, equal to or greater than t2 */ int timespec_cmp(struct timespec *t1, struct timespec *t2) { if (t1->tv_sec < t2->tv_sec) { return (-1); } else if (t1->tv_sec > t2->tv_sec) { return (1); } else { if (t1->tv_nsec < t2->tv_nsec) { return (-1); } else if (t1->tv_nsec > t2->tv_nsec) { return (1); } else { return (0); } } } void timespec_dump(char *msg, struct timespec *t1) { printf("%s %lu:%lu\n", msg, t1->tv_sec, t1->tv_nsec); } void dumpBuffer(const char *msg, unsigned char *buffer, int len) { int i; printf("%s", msg); for (i = 0; i < len; i++) { printf("%02x", buffer[i]); } printf("\n"); } void calc_interval(struct timespec *ts, unsigned long tx_speed, unsigned int tx_size) { if (tx_speed > 0) { // pthread_getcpuclockid(pthread_self(), &clock_id); double interval_nsec; // We use a double so we don't overflow the various ints interval_nsec = 1000000000; interval_nsec *= tx_size * 8; interval_nsec /= tx_speed; ts->tv_sec = 0; ts->tv_nsec = interval_nsec; /* Duplicate bug? in MT where anything less than 2 packets per second gets converted to 1 packet second */ if (ts->tv_nsec > 500000000) { ts->tv_nsec = 0; ts->tv_sec = 1; } } else { ts->tv_nsec = 0; ts->tv_sec = 0; } } unsigned char * calc_md5auth(unsigned char *nonce, char *passwd) { MD5_CTX ctx; static unsigned char hash[16]; MD5_Init(&ctx); MD5_Update(&ctx, (void *)passwd, strlen(passwd)); MD5_Update(&ctx, (void *)nonce, 16); MD5_Final(hash, &ctx); MD5_Init(&ctx); MD5_Update(&ctx, (void *)passwd, strlen(passwd)); MD5_Update(&ctx, (void *)hash, 16); MD5_Final(hash, &ctx); return (hash); } ================================================ FILE: build.sh ================================================ gcc -o btest *.c -lpthread && chmod +x btest ================================================ FILE: configure.ac ================================================ AC_INIT([btest], [1.0], [bug-report@address]) AM_INIT_AUTOMAKE AC_ARG_ENABLE(debugging, [ --disable-debugging disable debugging code], ac_cv_debugging=$enableval, ac_cv_debugging=yes) AC_PROG_CC dnl Checks for header files. AC_HEADER_STDC AC_HEADER_SYS_WAIT AC_CHECK_HEADERS() AC_TYPE_UINT8_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT ================================================ FILE: docker-compose.yml ================================================ version: '3' services: btest-service: build: context: . dockerfile: Dockerfile container_name: btest_tool image: btest_tools:latest environment: - USERNAME=_________ # username here ... - PASSWORD=_________ # password here ... ================================================ FILE: install-sh ================================================ #!/bin/sh # install - install a program, script, or datafile scriptversion=2011-11-20.07; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the # following copyright and license. # # Copyright (C) 1994 X Consortium # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of the X Consortium shall not # be used in advertising or otherwise to promote the sale, use or other deal- # ings in this Software without prior written authorization from the X Consor- # tium. # # # FSF changes to this file are in the public domain. # # Calling this script install-sh is preferred over install.sh, to prevent # 'make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. nl=' ' IFS=" "" $nl" # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit=${DOITPROG-} if test -z "$doit"; then doit_exec=exec else doit_exec=$doit fi # Put in absolute file names if you don't have them in your path; # or use environment vars. chgrpprog=${CHGRPPROG-chgrp} chmodprog=${CHMODPROG-chmod} chownprog=${CHOWNPROG-chown} cmpprog=${CMPPROG-cmp} cpprog=${CPPROG-cp} mkdirprog=${MKDIRPROG-mkdir} mvprog=${MVPROG-mv} rmprog=${RMPROG-rm} stripprog=${STRIPPROG-strip} posix_glob='?' initialize_posix_glob=' test "$posix_glob" != "?" || { if (set -f) 2>/dev/null; then posix_glob= else posix_glob=: fi } ' posix_mkdir= # Desired mode of installed file. mode=0755 chgrpcmd= chmodcmd=$chmodprog chowncmd= mvcmd=$mvprog rmcmd="$rmprog -f" stripcmd= src= dst= dir_arg= dst_arg= copy_on_change=false no_target_directory= usage="\ Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE or: $0 [OPTION]... SRCFILES... DIRECTORY or: $0 [OPTION]... -t DIRECTORY SRCFILES... or: $0 [OPTION]... -d DIRECTORIES... In the 1st form, copy SRCFILE to DSTFILE. In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. In the 4th, create DIRECTORIES. Options: --help display this help and exit. --version display version info and exit. -c (ignored) -C install only if different (preserve the last data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. -s $stripprog installed files. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG " while test $# -ne 0; do case $1 in -c) ;; -C) copy_on_change=true;; -d) dir_arg=true;; -g) chgrpcmd="$chgrpprog $2" shift;; --help) echo "$usage"; exit $?;; -m) mode=$2 case $mode in *' '* | *' '* | *' '* | *'*'* | *'?'* | *'['*) echo "$0: invalid mode: $mode" >&2 exit 1;; esac shift;; -o) chowncmd="$chownprog $2" shift;; -s) stripcmd=$stripprog;; -t) dst_arg=$2 # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac shift;; -T) no_target_directory=true;; --version) echo "$0 $scriptversion"; exit $?;; --) shift break;; -*) echo "$0: invalid option: $1" >&2 exit 1;; *) break;; esac shift done if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then # When -d is used, all remaining arguments are directories to create. # When -t is used, the destination is already specified. # Otherwise, the last argument is the destination. Remove it from $@. for arg do if test -n "$dst_arg"; then # $@ is not empty: it contains at least $arg. set fnord "$@" "$dst_arg" shift # fnord fi shift # arg dst_arg=$arg # Protect names problematic for 'test' and other utilities. case $dst_arg in -* | [=\(\)!]) dst_arg=./$dst_arg;; esac done fi if test $# -eq 0; then if test -z "$dir_arg"; then echo "$0: no input file specified." >&2 exit 1 fi # It's OK to call 'install-sh -d' without argument. # This can happen when creating conditional directories. exit 0 fi if test -z "$dir_arg"; then do_exit='(exit $ret); exit $ret' trap "ret=129; $do_exit" 1 trap "ret=130; $do_exit" 2 trap "ret=141; $do_exit" 13 trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. case $mode in # Optimize common cases. *644) cp_umask=133;; *755) cp_umask=22;; *[0-7]) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw='% 200' fi cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; *) if test -z "$stripcmd"; then u_plus_rw= else u_plus_rw=,u+rw fi cp_umask=$mode$u_plus_rw;; esac fi for src do # Protect names problematic for 'test' and other utilities. case $src in -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then dst=$src dstdir=$dst test -d "$dstdir" dstdir_status=$? else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if test ! -f "$src" && test ! -d "$src"; then echo "$0: $src does not exist." >&2 exit 1 fi if test -z "$dst_arg"; then echo "$0: no destination specified." >&2 exit 1 fi dst=$dst_arg # If destination is a directory, append the input filename; won't work # if double slashes aren't ignored. if test -d "$dst"; then if test -n "$no_target_directory"; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst dst=$dstdir/`basename "$src"` dstdir_status=0 else # Prefer dirname, but fall back on a substitute if dirname fails. dstdir=` (dirname "$dst") 2>/dev/null || expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$dst" : 'X\(//\)[^/]' \| \ X"$dst" : 'X\(//\)$' \| \ X"$dst" : 'X\(/\)' \| . 2>/dev/null || echo X"$dst" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q' ` test -d "$dstdir" dstdir_status=$? fi fi obsolete_mkdir_used=false if test $dstdir_status != 0; then case $posix_mkdir in '') # Create intermediate dirs using mode 755 as modified by the umask. # This is like FreeBSD 'install' as of 1997-10-28. umask=`umask` case $stripcmd.$umask in # Optimize common cases. *[2367][2367]) mkdir_umask=$umask;; .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; *[0-7]) mkdir_umask=`expr $umask + 22 \ - $umask % 100 % 40 + $umask % 20 \ - $umask % 10 % 4 + $umask % 2 `;; *) mkdir_umask=$umask,go-w;; esac # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then mkdir_mode=-m$mode else mkdir_mode= fi posix_mkdir=false case $umask in *[123567][0-7][0-7]) # POSIX mkdir -p sets u+wx bits regardless of umask, which # is incompatible with FreeBSD 'install' when (umask & 300) != 0. ;; *) tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 if (umask $mkdir_umask && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 then if test -z "$dir_arg" || { # Check for POSIX incompatibilities with -m. # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or # other-writable bit of parent directory when it shouldn't. # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. ls_ld_tmpdir=`ls -ld "$tmpdir"` case $ls_ld_tmpdir in d????-?r-*) different_mode=700;; d????-?--*) different_mode=755;; *) false;; esac && $mkdirprog -m$different_mode -p -- "$tmpdir" && { ls_ld_tmpdir_1=`ls -ld "$tmpdir"` test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" } } then posix_mkdir=: fi rmdir "$tmpdir/d" "$tmpdir" else # Remove any dirs left behind by ancient mkdir implementations. rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null fi trap '' 0;; esac;; esac if $posix_mkdir && ( umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" ) then : else # The umask is ridiculous, or mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. case $dstdir in /*) prefix='/';; [-=\(\)!]*) prefix='./';; *) prefix='';; esac eval "$initialize_posix_glob" oIFS=$IFS IFS=/ $posix_glob set -f set fnord $dstdir shift $posix_glob set +f IFS=$oIFS prefixes= for d do test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then prefixes= else if $posix_mkdir; then (umask=$mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 else case $prefix in *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; *) qprefix=$prefix;; esac prefixes="$prefixes '$qprefix'" fi fi prefix=$prefix/ done if test -n "$prefixes"; then # Don't fail if two instances are running concurrently. (umask $mkdir_umask && eval "\$doit_exec \$mkdirprog $prefixes") || test -d "$dstdir" || exit 1 obsolete_mkdir_used=true fi fi fi if test -n "$dir_arg"; then { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 else # Make a couple of temp file names in the proper directory. dsttmp=$dstdir/_inst.$$_ rmtmp=$dstdir/_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 # Copy the file name to the temp name. (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && # and set any options; do chmod last to preserve setuid bits. # # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $cpprog $src $dsttmp" command. # { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && # If -C, don't bother to copy if it wouldn't change the file. if $copy_on_change && old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && eval "$initialize_posix_glob" && $posix_glob set -f && set X $old && old=:$2:$4:$5:$6 && set X $new && new=:$2:$4:$5:$6 && $posix_glob set +f && test "$old" = "$new" && $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 then rm -f "$dsttmp" else # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || # The rename failed, perhaps because mv can't rename something else # to itself, or perhaps because mv is so ancient that it does not # support -f. { # Now remove or move aside any old file at destination location. # We try this two ways since rm can't unlink itself on some # systems and the destination file might be busy for other # reasons. In this case, the final cleanup might fail but the new # file should still install successfully. { test ! -f "$dst" || $doit $rmcmd -f "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 } } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dst" } fi || exit 1 trap '' 0 fi done # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC" # time-stamp-end: "; # UTC" # End: ================================================ FILE: md5.c ================================================ /* * This is an OpenSSL-compatible implementation of the RSA Data Security, * Inc. MD5 Message-Digest Algorithm. * * Written by Solar Designer in 2001, and placed * in the public domain. There's absolutely no warranty. * * This differs from Colin Plumb's older public domain implementation in * that no 32-bit integer data type is required, there's no compile-time * endianness configuration, and the function prototypes match OpenSSL's. * The primary goals are portability and ease of use. * * This implementation is meant to be fast, but not as fast as possible. * Some known optimizations are not included to reduce source code size * and avoid compile-time configuration. */ #include #include "md5.h" /* * The basic MD5 functions. * * F is optimized compared to its RFC 1321 definition just like in Colin * Plumb's implementation. */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) #define H(x, y, z) ((x) ^ (y) ^ (z)) #define I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ #define STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); /* * SET reads 4 input bytes in little-endian byte order and stores them * in a properly aligned word in host byte order. * * The check for little-endian architectures which tolerate unaligned * memory accesses is just an optimization. Nothing will break if it * doesn't work. */ #if defined(__i386__) || defined(__vax__) # define SET(n) (*(MD5_u32plus *)&ptr[(n) * 4]) # define GET(n) SET(n) #else # define SET(n) \ (ctx->block[(n)] = \ (MD5_u32plus)ptr[(n) * 4] | \ ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) # define GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update * the bit counters. There're no alignment requirements. */ static void *body(MD5_CTX *ctx, void *data, unsigned long size) { unsigned char *ptr; MD5_u32plus a, b, c, d; MD5_u32plus saved_a, saved_b, saved_c, saved_d; ptr = data; a = ctx->a; b = ctx->b; c = ctx->c; d = ctx->d; do { saved_a = a; saved_b = b; saved_c = c; saved_d = d; /* Round 1 */ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) STEP(F, c, d, a, b, SET(2), 0x242070db, 17) STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) /* Round 2 */ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) STEP(G, d, a, b, c, GET(10), 0x02441453, 9) STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) /* Round 3 */ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) /* Round 4 */ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; c += saved_c; d += saved_d; ptr += MD5_BLOCK_SZ; } while (size -= MD5_BLOCK_SZ); ctx->a = a; ctx->b = b; ctx->c = c; ctx->d = d; return ptr; } void MD5_Init(MD5_CTX *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; ctx->d = 0x10325476; ctx->lo = 0; ctx->hi = 0; } void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size) { MD5_u32plus saved_lo; unsigned long used, free; saved_lo = ctx->lo; if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) ctx->hi++; ctx->hi += size >> 29; used = saved_lo & 0x3f; if (used) { free = MD5_BLOCK_SZ - used; if (size < free) { memcpy(&ctx->buffer[used], data, size); return; } memcpy(&ctx->buffer[used], data, free); data = (unsigned char *)data + free; size -= free; body(ctx, ctx->buffer, MD5_BLOCK_SZ); } if (size >= MD5_BLOCK_SZ) { data = body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } void MD5_Final(unsigned char *result, MD5_CTX *ctx) { unsigned long used, free; used = ctx->lo & 0x3f; ctx->buffer[used++] = 0x80; free = MD5_BLOCK_SZ - used; if (free < 8) { memset(&ctx->buffer[used], 0, free); body(ctx, ctx->buffer, MD5_BLOCK_SZ); used = 0; free = MD5_BLOCK_SZ; } memset(&ctx->buffer[used], 0, free - 8); ctx->lo <<= 3; ctx->buffer[56] = ctx->lo; ctx->buffer[57] = ctx->lo >> 8; ctx->buffer[58] = ctx->lo >> 16; ctx->buffer[59] = ctx->lo >> 24; ctx->buffer[60] = ctx->hi; ctx->buffer[61] = ctx->hi >> 8; ctx->buffer[62] = ctx->hi >> 16; ctx->buffer[63] = ctx->hi >> 24; body(ctx, ctx->buffer, MD5_BLOCK_SZ); result[0] = ctx->a; result[1] = ctx->a >> 8; result[2] = ctx->a >> 16; result[3] = ctx->a >> 24; result[4] = ctx->b; result[5] = ctx->b >> 8; result[6] = ctx->b >> 16; result[7] = ctx->b >> 24; result[8] = ctx->c; result[9] = ctx->c >> 8; result[10] = ctx->c >> 16; result[11] = ctx->c >> 24; result[12] = ctx->d; result[13] = ctx->d >> 8; result[14] = ctx->d >> 16; result[15] = ctx->d >> 24; memset(ctx, 0, sizeof(*ctx)); } ================================================ FILE: md5.h ================================================ /* * This is an OpenSSL-compatible implementation of the RSA Data Security, * Inc. MD5 Message-Digest Algorithm. * * Written by Solar Designer in 2001, and placed * in the public domain. See md5.c for more information. */ #ifndef __MD5_H__ #define __MD5_H__ #define MD5_DIGEST_SZ 16 #define MD5_BLOCK_SZ 64 /* Any 32-bit or wider unsigned integer data type will do */ typedef unsigned long MD5_u32plus; typedef struct { MD5_u32plus lo, hi; MD5_u32plus a, b, c, d; unsigned char buffer[MD5_BLOCK_SZ]; MD5_u32plus block[MD5_DIGEST_SZ]; } MD5_CTX; extern void MD5_Init(MD5_CTX *ctx); extern void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size); extern void MD5_Final(unsigned char *result, MD5_CTX *ctx); #endif /* __MD5_H__ */ ================================================ FILE: timing_mach.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include "timing_mach.h" /* inline functions - maintain ANSI C compatibility */ #ifdef TIMING_C99 /* *** */ /* C99 */ extern double timespec2secd(const struct timespec *ts_in); extern void secd2timespec(struct timespec *ts_out, const double sec_d); extern void timespec_monodiff_lmr(struct timespec *ts_out, const struct timespec *ts_in); extern void timespec_monodiff_rml(struct timespec *ts_out, const struct timespec *ts_in); extern void timespec_monoadd(struct timespec *ts_out, const struct timespec *ts_in); #endif #ifdef __MACH__ /* ******** */ /* __MACH__ */ #include #include #include /* timing struct for osx */ static struct TimingMach { mach_timebase_info_data_t timebase; clock_serv_t cclock; } timing_mach_g; /* mach clock port */ extern mach_port_t clock_port; int timing_mach_init (void) { int retval = mach_timebase_info(&timing_mach_g.timebase); if (retval != 0) return retval; retval = host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &timing_mach_g.cclock); return retval; } int clock_gettime(clockid_t id, struct timespec *tspec) { mach_timespec_t mts; int retval = 0; if (id == CLOCK_REALTIME) { retval = clock_get_time(timing_mach_g.cclock, &mts); if (retval != 0) return retval; tspec->tv_sec = mts.tv_sec; tspec->tv_nsec = mts.tv_nsec; } else if (id == CLOCK_MONOTONIC) { retval = clock_get_time(clock_port, &mts); if (retval != 0) return retval; tspec->tv_sec = mts.tv_sec; tspec->tv_nsec = mts.tv_nsec; } else { /* only CLOCK_MONOTOIC and CLOCK_REALTIME clocks supported */ return -1; } return 0; } int clock_nanosleep_abstime(const struct timespec *req) { struct timespec ts_delta; int retval = clock_gettime(CLOCK_MONOTONIC, &ts_delta); if (retval != 0) return retval; timespec_monodiff_rml (&ts_delta, req); /* mach does not properly return remainder from nanosleep */ retval = nanosleep(&ts_delta, NULL); return retval; } /* __MACH__ */ /* ******** */ #endif int itimer_start (struct timespec *ts_target, const struct timespec *ts_step) { int retval = clock_gettime(CLOCK_MONOTONIC, ts_target); if (retval != 0) return retval; /* add step size to current monotonic time */ timespec_monoadd(ts_target, ts_step); return retval; } int itimer_step (struct timespec *ts_target, const struct timespec *ts_step) { int retval = clock_nanosleep_abstime(ts_target); if (retval != 0) return retval; /* move target along */ timespec_monoadd(ts_target, ts_step); return retval; } ================================================ FILE: timing_mach.h ================================================ #ifndef TIMING_MACH_H #define TIMING_MACH_H /* ************* */ /* TIMING_MACH_H */ /* C99 check */ #if defined(__STDC__) # if defined(__STDC_VERSION__) # if (__STDC_VERSION__ >= 199901L) # define TIMING_C99 # endif # endif #endif #include #define TIMING_GIGA (1000000000) #define TIMING_NANO (1e-9) /* inline functions - maintain ANSI C compatibility */ #ifndef TIMING_C99 /* this is a bad hack that makes the functions static in a header file. Compiler warnings about unused functions will plague anyone using this code with ANSI C. */ #define inline static #endif /* timespec to double */ inline double timespec2secd(const struct timespec *ts_in) { return ((double) ts_in->tv_sec) + ((double) ts_in->tv_nsec ) * TIMING_NANO; } /* double sec to timespec */ inline void secd2timespec(struct timespec *ts_out, const double sec_d) { ts_out->tv_sec = (time_t) (sec_d); ts_out->tv_nsec = (long) ((sec_d - (double) ts_out->tv_sec) * TIMING_GIGA); } /* timespec difference (monotonic) left - right */ inline void timespec_monodiff_lmr(struct timespec *ts_out, const struct timespec *ts_in) { /* out = out - in, where out > in */ ts_out->tv_sec = ts_out->tv_sec - ts_in->tv_sec; ts_out->tv_nsec = ts_out->tv_nsec - ts_in->tv_nsec; if (ts_out->tv_nsec < 0) { ts_out->tv_sec = ts_out->tv_sec - 1; ts_out->tv_nsec = ts_out->tv_nsec + TIMING_GIGA; } } /* timespec difference (monotonic) right - left */ inline void timespec_monodiff_rml(struct timespec *ts_out, const struct timespec *ts_in) { /* out = in - out, where in > out */ ts_out->tv_sec = ts_in->tv_sec - ts_out->tv_sec; ts_out->tv_nsec = ts_in->tv_nsec - ts_out->tv_nsec; if (ts_out->tv_nsec < 0) { ts_out->tv_sec = ts_out->tv_sec - 1; ts_out->tv_nsec = ts_out->tv_nsec + TIMING_GIGA; } } /* timespec addition (monotonic) */ inline void timespec_monoadd(struct timespec *ts_out, const struct timespec *ts_in) { /* out = in + out */ ts_out->tv_sec = ts_out->tv_sec + ts_in->tv_sec; ts_out->tv_nsec = ts_out->tv_nsec + ts_in->tv_nsec; if (ts_out->tv_nsec >= TIMING_GIGA) { ts_out->tv_sec = ts_out->tv_sec + 1; ts_out->tv_nsec = ts_out->tv_nsec - TIMING_GIGA; } } #ifndef TIMING_C99 #undef inline #endif #ifdef __MACH__ /* ******** */ /* __MACH__ */ /* only CLOCK_REALTIME and CLOCK_MONOTONIC are emulated */ #ifndef CLOCK_REALTIME # define CLOCK_REALTIME 0 #endif #ifndef CLOCK_MONOTONIC # define CLOCK_MONOTONIC 1 #endif /* typdef POSIX clockid_t */ //typedef int clockid_t; /* initialize mach timing */ int timing_mach_init (void); /* clock_gettime - emulate POSIX */ int clock_gettime(const clockid_t id, struct timespec *tspec); /* clock_nanosleep for CLOCK_MONOTONIC and TIMER_ABSTIME */ int clock_nanosleep_abstime(const struct timespec *req); /* __MACH__ */ /* ******** */ #else /* ***** */ /* POSIX */ /* clock_nanosleep for CLOCK_MONOTONIC and TIMER_ABSTIME */ # define clock_nanosleep_abstime(req) \ clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, (req), NULL) /* POSIX */ /* ***** */ #endif /* timer functions that make use of clock_nanosleep_abstime For POSIX systems, it is recommended to use POSIX timers and signals. For Mac OSX (mach), there are no POSIX timers so these functions are very helpful. */ /* Sets absolute time ts_target to ts_step after current time */ int itimer_start (struct timespec *ts_target, const struct timespec *ts_step); /* Nanosleeps to ts_target then adds ts_step to ts_target for next iteration */ int itimer_step (struct timespec *ts_target, const struct timespec *ts_step); /* TIMING_MACH_H */ /* ************* */ #endif ================================================ FILE: utils.c ================================================ #include #include #include #include #include "utils.h" /** * generateRandomNonce - Generates a random nonce. * @nonce: The array to store the generated nonce. */ void generateRandomNonce(unsigned char nonce[16]) { srand((unsigned int)time(NULL)); for (int i = 0; i < 16; ++i) { nonce[i] = (unsigned char)rand(); } } /** * getHexRepresentation - Converts a buffer to its hexadecimal representation. * @buffer: Pointer to the input buffer. * @size: Size of the input buffer. * @hexString: Pointer to the output string for the hexadecimal representation. * * This function converts the content of the input buffer to its hexadecimal * representation and stores it in the output string. The resulting string is * null-terminated. */ void getHexRepresentation(const unsigned char *buffer, size_t size, char *hexString) { for (size_t i = 0; i < size; ++i) { sprintf(hexString + i * 2, "%02x", buffer[i]); } } /** * isStringNotEmpty - Checks if a C string is not empty. * @str: Pointer to the C string to be checked. * * This function determines whether the given C string is not empty. * It returns 1 if the string is not empty and not NULL, and 0 otherwise. * * Returns: 1 if the string is not empty, 0 otherwise. */ int isStringNotEmpty(const char *str) { return (str != NULL && strlen(str) > 0); } /** * isauth - Check authentication based on user and hash. * @opt_authuser: User-provided authentication username. * @username: Server's expected username for comparison. * @receivedhash: User-provided received hash for comparison. * @serverdigest: Server's expected hash for comparison. * * It compares opt_authuser with username and receivedhash with serverdigest. * Returns: 1 if both username and hash match, 0 otherwise. */ int isauth(const char *opt_authuser, const unsigned char *username, const char *receivedhash, const char *serverdigest) { // Check if opt_authuser is not empty and equal to username int userMatch = (opt_authuser != NULL && *opt_authuser != '\0' && strncmp(opt_authuser, (const char *)username, USERNAME_SIZE) == 0); // Check if receivedhash is not empty and equal to serverdigest int hashMatch = (receivedhash != NULL && *receivedhash != '\0' && strcmp(receivedhash, serverdigest) == 0); // Return 1 if both conditions are true, 0 otherwise return (userMatch && hashMatch); } ================================================ FILE: utils.h ================================================ #ifndef UTILS_H #define UTILS_H #define USERNAME_SIZE 16 #define HASH_SIZE 32 #define AUTHSTR_SIZE 32 // function to generate random Nonce to send to client for md5 hash void generateRandomNonce(unsigned char nonce[16]); // This function converts the content of the input buffer to its hexadecimal void getHexRepresentation(const unsigned char *buffer, size_t size, char *hexString); // Checks if a C string is not empty. int isStringNotEmpty(const char *str); // This function checks authentication based on the provided username and hash. int isauth(const char *opt_authuser, const unsigned char *username, const char *receivedhash, const char *serverdigest); #endif