├── .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 /.dockerignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samm-git/btest-opensource/5040a01267c3578d97bb14fa09ccf54de0f179bc/.dockerignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | btest 2 | rust_btest-server/** 3 | .vscode/** -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a minimal Alpine Linux base image with build tools 2 | FROM alpine:latest 3 | 4 | # Set the working directory inside the container 5 | WORKDIR /app 6 | # Install necessary build tools 7 | RUN apk --no-cache add build-base 8 | 9 | # Copy the build.sh script and btest source code into the container 10 | COPY build.sh . 11 | COPY . /app/ 12 | # Run the build.sh script to compile btest 13 | RUN chmod +x build.sh && ./build.sh 14 | COPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscaled /usr/local/bin/tailscaled 15 | COPY --from=docker.io/tailscale/tailscale:stable /usr/local/bin/tailscale /usr/local/bin/tailscale 16 | # Define environment variables 17 | ENV USERNAME admin 18 | ENV PASSWORD admin 19 | RUN [ -n "$AUTHKEY" ] && [ ! -z "$AUTHKEY" ] && tailscale up --authkey $AUTHKEY || { echo "AuthKey is not set or empty. Exiting."; exit 1; } 20 | 21 | # Set the entry point for the container 22 | ENTRYPOINT ["./btest"] 23 | 24 | # Specify the default command to run when the container starts 25 | CMD ["-a", "${USERNAME}", "-p", "${PASSWORD}", "-s"] 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Alex Samorukov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = btest 2 | btest_SOURCES = btest.c md5.c timing_mach.c 3 | AM_LDFLAGS = -lpthread 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mikrotik bandwith test protocol description 2 | ## about 3 | 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. 4 | 5 | Protocol itsef seems to be not complicated, so it should not be easy to create 3rd party client for it. 6 | 7 | ## Protocol description 8 | 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. 9 | 10 | ``` 11 | > bserver: hello 12 | 01:00:00:00 13 | > client: Start test cmd, depending on the client settings: 14 | 01:01:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (transmit) 15 | 01:01:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (transmit, random data) 16 | 00:01:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (transmit, UDP) 17 | 01:02:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (receive) 18 | 01:02:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (receive, random data) 19 | 00:02:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: default (1500)) 20 | 00:02:01:00:d0:07:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: 2000) 21 | 00:02:01:00:00:fa:00:00:00:00:00:00:00:00:00:00 (receive, UDP, remote-udp-tx-size: 64000) 22 | 01:02:01:00:00:80:00:00:01:00:00:00:00:00:00:00 (receive, remote-tx-speed=1) 23 | 01:02:01:00:00:80:00:00:ff:ff:ff:ff:00:00:00:00 (receive, remote-tx-speed=4294967295) 24 | 01:02:01:00:00:80:00:00:00:00:00:00:01:00:00:00 (receive, local-tx-speed=1) 25 | 01:02:01:00:00:80:00:00:00:00:00:00:ff:ff:ff:ff (receive, local-tx-speed=4294967295) 26 | 01:02:01:0a:00:80:00:00:00:00:00:00:00:00:00:00 (receive, tcp-connection-count=10) 27 | 01:03:01:00:00:80:00:00:00:00:00:00:00:00:00:00 (both) 28 | 01:03:00:00:00:80:00:00:00:00:00:00:00:00:00:00 (both, random data) 29 | 00:03:01:00:dc:05:00:00:00:00:00:00:00:00:00:00 (both, UDP) 30 | > bserver: Start test confirm (auth is disabled on server): 31 | 01:00:00:00 (tcp-connection-count=1 on a client) 32 | 01:bc:04:00 (tcp-connection-count>1 on a client) 33 | > bserver: auth requested, with 16 challenge bytes (3 random packets provided) 34 | 02:00:00:00:90:67:3f:0f:5c:c7:4e:17:a0:e0:9e:1c:b9:ee:3b:0c 35 | 02:00:00:00:17:e7:ee:84:83:cc:15:53:e8:fa:9c:0d:ad:ac:b8:e1 36 | 02:00:00:00:ee:d1:19:b9:d3:f2:df:6d:04:46:da:25:55:44:49:81 37 | > client: auth reply (3 random packets, userid test) 38 | 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 39 | 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 40 | 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 41 | > bserver: packet data follows 42 | 00:00:00:00........ 43 | ``` 44 | 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. 45 | 2. Client sends 16 bytes command started with 01 (TCP) or 00 (UDP) command to the server. Some of the bytes guess: 46 | - **cmd[0]**: protocol, 01: TCP, 00: UDP 47 | - **cmd[1]**: direction, 01 - transmit, 02 - receive, 03 - both 48 | - **cmd[2]**: use random data, 00 - use random, 01: use \00 character 49 | - **cmd[3]**: tcp-connection-count, 0 if tcp-connection-count=1, number if more 50 | - **cmd[4:5]**: remote-udp-tx-size (dc:05) on UDP, 00:80 on TCP, UINT16 - Little Endian 51 | - **cmd[6:7]**: client buffer size. Maximum value for TCP is 65535 (FF:FF). 52 | - **cmd[8:11]**: remote-tx-speed, 0: unlimimted, 1-4294967295: value, UINT32 - Little Endian 53 | - **cmd[12:15]**: local-tx-speed, 0: unlimimted, 1-4294967295: value, UINT32 - Little Endian 54 | 3. If server authentication is disabled it sends 01:00:00:00 and starts to transmit/recieve data. 55 | 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. 56 | 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. 57 | 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. 58 | 4. If auth failed server sends back `00000000` (client shows "authentication failed"), if succeed - `01000000` packet and test is starting. 59 | 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. 60 | 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: 61 | - **stat[0]** is 07 (message id) 62 | - **stat[4-7]** number or time from start in seconds, sends one per second, UINT32 - Little Endian 63 | - **stat[8-11]** number of bytes transferred per sequence, UINT32 - Little Endian 64 | 65 | ## Old authentication methoud 66 | Sample of the challenge-response for the "test" password: 67 | ``` 68 | ad32d6f94d28161625f2f390bb895637 69 | 3c968565bc0314f281a6da1571cf7255 70 | ``` 71 | 72 | Hashing alghoritm was found with implementation of the testing server and help from the **Chupaka** on a Mikrotik forum. It is 73 | md5('password' + md5('password' + hash)). This is an example on the Perl 74 | language, which using challenge from the example and returns same hash: 75 | 76 | ```perl 77 | use Digest::MD5 qw(md5 md5_hex); 78 | 79 | my $salt='ad32d6f94d28161625f2f390bb895637'; 80 | my $pass='test'; 81 | print md5_hex($pass.md5($pass.pack( 'H*', $salt)))."\n"; 82 | ``` 83 | Script should return `3c968565bc0314f281a6da1571cf7255` and its matching captured traffic. 84 | -------------------------------------------------------------------------------- /bclient.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # this is PoC for the Mikrotik bandwith server. On mikorotik use 4 | # /tool bandwidth-test duration=10s protocol=tcp tcp-connection-count=1 5 | # to connect to it 6 | 7 | use IO::Socket::INET; 8 | use Digest::MD5 qw(md5 md5_hex md5_base64); 9 | use Data::Dumper; 10 | 11 | # auto-flush on socket 12 | $| = 1; 13 | 14 | my $udp_port_offset=256; 15 | my $require_auth=0; 16 | 17 | # creating a listening socket 18 | my $socket = new IO::Socket::INET ( 19 | PeerHost => '192.168.10.1', 20 | PeerPort => '2000', 21 | Proto => 'tcp', 22 | ); 23 | die "cannot create socket $!\n" unless $socket; 24 | print "connecting to server port 2000\n"; 25 | 26 | $socket->recv($data, 1024); 27 | print "client hello data is ".unpack('H*', "$data")."\n"; 28 | $data=pack('CCCCvvNN', 29 | 1, # UDP 30 | 2, # TX 31 | 1, # Not random 32 | 0, # TCP Count 33 | 1500, # TX Size 34 | 0, # Unknown 35 | 0, # Unlimited 36 | 0, # Unlimited 37 | ); 38 | print "client command is ".unpack('H*', "$data")."\n"; 39 | 40 | $socket->send($data); 41 | 42 | while(defined($socket->recv($data, 32768))) { 43 | print "client recv data is ".unpack('H*', "$data")."\n"; 44 | } 45 | exit(0); 46 | 47 | while(1) 48 | { 49 | # waiting for a new client connection 50 | my $client_socket = $socket->accept(); 51 | 52 | # get information about a newly connected client 53 | my $client_address = $client_socket->peerhost(); 54 | my $client_port = $client_socket->peerport(); 55 | print "connection from $client_address:$client_port\n"; 56 | print "sending hello\n"; 57 | # write response data to the connected client 58 | $data = pack 'H*', '01000000'; 59 | $client_socket->send($data); 60 | print "reading reply\n"; 61 | $client_socket->recv($data, 1024); 62 | my $action=unpackCmd($data); 63 | print(Dumper($action)); 64 | # send auth requested command 65 | if ($require_auth) { 66 | $client_socket->send(pack 'H*', '02000000'); 67 | $digest=pack 'H*', '00000000000000000000000000000000'; 68 | # print "digest md5=".md5_hex($digest)." expected reply is: ".md5_hex(md5($digest))."\n"; 69 | $client_socket->send($digest); 70 | $client_socket->recv($data, 1024); 71 | print "client auth data is ".unpack('H*', "$data")."\n"; 72 | # $client_socket->send(pack 'H*', '00000000'); # auth failed 73 | $client_socket->send(pack 'H*', '01000000'); # auth accepted 74 | } 75 | if ($action->{proto} eq 'TCP') { 76 | # send data 77 | while (1) { 78 | $client_socket->send(pack 'H*', '00' x $action->{tx_size}); 79 | print "."; 80 | } 81 | # notify client that response has been sent 82 | shutdown($client_socket, 1); 83 | } else { 84 | my $tx_socket = new IO::Socket::INET ( 85 | PeerAddr => "$client_address:2000", 86 | Proto => 'udp', 87 | ); 88 | die "cannot create socket $!\n" unless $tx_socket; 89 | sleep(10); 90 | #while (1) { 91 | # $tx_socket->send(pack 'H*', '00' x $action->{tx_size}); 92 | # print "."; 93 | #} 94 | $tx_socket->close(); 95 | } 96 | } 97 | $socket->close(); 98 | 99 | sub unpackCmd { 100 | my $data=shift; 101 | my @cmdlist=unpack('CCCCvvNN', $data); 102 | my $cmd={ 103 | proto => $cmdlist[0] ? 'TCP' : 'UDP', 104 | direction => ($cmdlist[1]==1) ? 'RX' : (($cmdlist[1]==2) ? 'TX' : 'TXRX'), 105 | random => ($cmdlist[2]==0), 106 | tcp_conn_count => ($cmdlist[3]==0) ? 1 : $cmdlist[3], 107 | tx_size => $cmdlist[4], 108 | unknown => $cmdlist[5], 109 | remote_tx_speed => $cmdlist[6], 110 | local_tx_speed => $cmdlist[7], 111 | }; 112 | #print "data is ".unpack('H*', "$data")."\n"; 113 | return($cmd); 114 | } 115 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # bootstrap — generic bootstrap/autogen.sh script for autotools projects 4 | # 5 | # Copyright © 2002—2015 Sam Hocevar 6 | # 7 | # This program is free software. It comes without any warranty, to 8 | # the extent permitted by applicable law. You can redistribute it 9 | # and/or modify it under the terms of the Do What the Fuck You Want 10 | # to Public License, Version 2, as published by the WTFPL Task Force. 11 | # See http://www.wtfpl.net/ for more details. 12 | # 13 | # The latest version of this script can be found at the following place: 14 | # http://caca.zoy.org/wiki/build 15 | 16 | # Die if an error occurs 17 | set -e 18 | 19 | # Guess whether we are using configure.ac or configure.in 20 | if test -f configure.ac; then 21 | conffile="configure.ac" 22 | elif test -f configure.in; then 23 | conffile="configure.in" 24 | else 25 | echo "$0: could not find configure.ac or configure.in" 26 | exit 1 27 | fi 28 | 29 | # Check for needed features 30 | auxdir="`sed -ne 's/^[ \t]*A._CONFIG_AUX_DIR *([[ ]*\([^] )]*\).*/\1/p' $conffile`" 31 | pkgconfig="`grep '^[ \t]*PKG_PROG_PKG_CONFIG' $conffile >/dev/null 2>&1 && echo yes || echo no`" 32 | libtool="`grep '^[ \t]*A._PROG_LIBTOOL' $conffile >/dev/null 2>&1 && echo yes || echo no`" 33 | header="`grep '^[ \t]*A._CONFIG_HEADER' $conffile >/dev/null 2>&1 && echo yes || echo no`" 34 | makefile="`[ -f Makefile.am ] && echo yes || echo no`" 35 | aclocalflags="`sed -ne 's/^[ \t]*ACLOCAL_AMFLAGS[ \t]*=//p' Makefile.am 2>/dev/null || :`" 36 | 37 | # Check for automake 38 | amvers="no" 39 | for v in "" "-1.15" "-1.14" "-1.13" "-1.12" "-1.11"; do 40 | if automake${v} --version > /dev/null 2>&1; then 41 | amvers=${v} 42 | break 43 | fi 44 | done 45 | 46 | if test "$amvers" = "no"; then 47 | echo "$0: automake not found" 48 | exit 1 49 | fi 50 | 51 | # Check for autoconf 52 | acvers="no" 53 | for v in "" "259" "253"; do 54 | if autoconf${v} --version >/dev/null 2>&1; then 55 | acvers="${v}" 56 | break 57 | fi 58 | done 59 | 60 | if test "$acvers" = "no"; then 61 | echo "$0: autoconf not found" 62 | exit 1 63 | fi 64 | 65 | # Check for libtool 66 | if test "$libtool" = "yes"; then 67 | libtoolize="no" 68 | if glibtoolize --version >/dev/null 2>&1; then 69 | libtoolize="glibtoolize" 70 | else 71 | for v in "16" "15" "" "14"; do 72 | if libtoolize${v} --version >/dev/null 2>&1; then 73 | libtoolize="libtoolize${v}" 74 | break 75 | fi 76 | done 77 | fi 78 | 79 | if test "$libtoolize" = "no"; then 80 | echo "$0: libtool not found" 81 | exit 1 82 | fi 83 | fi 84 | 85 | # Check for pkg-config 86 | if test "$pkgconfig" = "yes"; then 87 | if ! pkg-config --version >/dev/null 2>&1; then 88 | echo "$0: pkg-config not found" 89 | exit 1 90 | fi 91 | fi 92 | 93 | # Remove old cruft 94 | 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 95 | rm -Rf autom4te.cache 96 | if test -n "$auxdir"; then 97 | if test ! -d "$auxdir"; then 98 | mkdir "$auxdir" 99 | fi 100 | aclocalflags="-I $auxdir -I . ${aclocalflags}" 101 | fi 102 | 103 | # Honour M4PATH because sometimes M4 doesn't 104 | save_IFS=$IFS 105 | IFS=: 106 | tmp="$M4PATH" 107 | for x in $tmp; do 108 | if test -n "$x"; then 109 | aclocalflags="-I $x ${aclocalflags}" 110 | fi 111 | done 112 | IFS=$save_IFS 113 | 114 | # Explain what we are doing from now 115 | set -x 116 | 117 | # Bootstrap package 118 | if test "$libtool" = "yes"; then 119 | ${libtoolize} --copy --force 120 | if test -n "$auxdir" -a ! "$auxdir" = "." -a -f "ltmain.sh"; then 121 | echo "$0: working around a minor libtool issue" 122 | mv ltmain.sh "$auxdir/" 123 | fi 124 | fi 125 | 126 | aclocal${amvers} ${aclocalflags} 127 | autoconf${acvers} 128 | if test "$header" = "yes"; then 129 | autoheader${acvers} 130 | fi 131 | if test "$makefile" = "yes"; then 132 | #add --include-deps if you want to bootstrap with any other compiler than gcc 133 | #automake${amvers} --add-missing --copy --include-deps 134 | automake${amvers} --foreign --add-missing --copy 135 | fi 136 | 137 | # Remove cruft that we no longer want 138 | rm -Rf autom4te.cache 139 | 140 | -------------------------------------------------------------------------------- /bserver.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # this is PoC for the Mikrotik bandwith server. On mikorotik use 4 | # /tool bandwidth-test duration=10s protocol=tcp tcp-connection-count=1 5 | # to connect to it 6 | 7 | use IO::Socket::INET; 8 | use Digest::MD5 qw(md5 md5_hex md5_base64); 9 | use Data::Dumper; 10 | 11 | # auto-flush on socket 12 | $| = 1; 13 | 14 | my $require_auth=0; 15 | 16 | my $udp_port_offset=256; 17 | 18 | # creating a listening socket 19 | my $socket = new IO::Socket::INET ( 20 | LocalHost => '0.0.0.0', 21 | LocalPort => '2000', 22 | Proto => 'tcp', 23 | Listen => 5, 24 | Reuse => 1 25 | ); 26 | die "cannot create socket $!\n" unless $socket; 27 | print "server waiting for client connection on port 2000\n"; 28 | 29 | while(1) 30 | { 31 | # waiting for a new client connection 32 | my $client_socket = $socket->accept(); 33 | 34 | # get information about a newly connected client 35 | my $client_address = $client_socket->peerhost(); 36 | my $client_port = $client_socket->peerport(); 37 | print "connection from $client_address:$client_port\n"; 38 | print "sending hello\n"; 39 | # write response data to the connected client 40 | $data = pack 'H*', '01000000'; 41 | $client_socket->send($data); 42 | print "reading reply\n"; 43 | $client_socket->recv($data, 1024); 44 | print "data is ".unpack('H*', "$data")."\n"; 45 | my $action=unpackCmd($data); 46 | print(Dumper($action)); 47 | # send auth requested command 48 | if ($require_auth) { 49 | $client_socket->send(pack 'H*', '02000000'); 50 | $digest=pack 'H*', '00000000000000000000000000000000'; 51 | # print "digest md5=".md5_hex($digest)." expected reply is: ".md5_hex(md5($digest))."\n"; 52 | $client_socket->send($digest); 53 | $client_socket->recv($data, 1024); 54 | print "client auth data is ".unpack('H*', "$data")."\n"; 55 | # $client_socket->send(pack 'H*', '00000000'); # auth failed 56 | $client_socket->send(pack 'H*', '01000000'); # auth accepted 57 | } 58 | if ($action->{proto} eq 'TCP') { 59 | # send data 60 | while (1) { 61 | $client_socket->send(pack 'H*', '00' x $action->{tx_size}); 62 | print "."; 63 | } 64 | # notify client that response has been sent 65 | shutdown($client_socket, 1); 66 | } else { 67 | print "sending hello\n"; 68 | # write response data to the connected client 69 | $data = pack 'H*', '01000000'; 70 | $client_socket->send($data); 71 | my $locudpport=2001; 72 | $data= pack('n', $locudpport); 73 | print "sending local port: " . unpack('H*', $data) . "\n"; 74 | $client_socket->send($data); 75 | #while(defined($client_socket->recv($data, 32768))) { 76 | # print "client recv data is ".unpack('H*', "$data")."\n"; 77 | #} 78 | #exit(0); 79 | 80 | my $tx_socket = new IO::Socket::INET ( 81 | PeerHost => "$client_address", 82 | PeerPort => $locudpport+$udp_port_offset, 83 | LocalHost => '0.0.0.0', 84 | LocalPort => $locudpport, 85 | Proto => 'udp', 86 | ); 87 | die "cannot create socket $!\n" unless $tx_socket; 88 | #sleep(10); 89 | my $seq=1; 90 | while (1) { 91 | $tx_socket->send(pack 'NNH*', 0, $seq, '00' x ($action->{tx_size}-8-28)); 92 | $seq++; 93 | print "."; 94 | } 95 | $tx_socket->close(); 96 | } 97 | } 98 | $socket->close(); 99 | 100 | sub unpackCmd { 101 | my $data=shift; 102 | my @cmdlist=unpack('CCCCvvNN', $data); 103 | my $cmd={ 104 | proto => $cmdlist[0] ? 'TCP' : 'UDP', 105 | direction => ($cmdlist[1]==1) ? 'RX' : (($cmdlist[1]==2) ? 'TX' : 'TXRX'), 106 | random => ($cmdlist[2]==0), 107 | tcp_conn_count => ($cmdlist[3]==0) ? 1 : $cmdlist[3], 108 | tx_size => $cmdlist[4], 109 | unknown => $cmdlist[5], 110 | remote_tx_speed => $cmdlist[6], 111 | local_tx_speed => $cmdlist[7], 112 | }; 113 | #print "data is ".unpack('H*', "$data")."\n"; 114 | return($cmd); 115 | } 116 | -------------------------------------------------------------------------------- /btest.c: -------------------------------------------------------------------------------- 1 | /* Program to emulate the Mikrotik Bandwidth test protocol */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "timing_mach.h" 19 | #include "utils.h" 20 | #include "md5.h" 21 | #define BTEST_PORT 2000 22 | #define BTEST_PORT_CLIENT_OFFSET 256 23 | #define CMD_PROTO_UDP 0 24 | #define CMD_PROTO_TCP 1 25 | #define CMD_DIR_RX 0x01 26 | #define CMD_DIR_TX 0x02 27 | #define CMD_RANDOM 0 28 | 29 | unsigned char helloStr[] = {0x01, 0x00, 0x00, 0x00}; 30 | unsigned char noAuthResp[] = {0x01, 0x00, 0x00, 0x00}; 31 | unsigned char needAuthResp[] = {0x02, 0x00, 0x00, 0x00}; 32 | unsigned char failedAuthResp[] = {0x00, 0x00, 0x00, 0x00}; 33 | // unsigned char cmdStr[16]; 34 | unsigned int udpport = BTEST_PORT; 35 | 36 | struct cmdStruct 37 | { 38 | int proto; 39 | int direction; 40 | int random; 41 | int tcp_conn_count; 42 | unsigned int tx_size; 43 | unsigned int client_buf_size; 44 | unsigned long remote_tx_speed; 45 | unsigned long local_tx_speed; 46 | }; 47 | 48 | struct statStruct 49 | { 50 | unsigned long seq; 51 | unsigned char unknown[3]; 52 | unsigned long recvBytes; 53 | unsigned long maxInterval; // In us, Not sent over the wire 54 | unsigned long minInterval; // In us, Not sent over the wire 55 | signed long lostPackets; // Not sent over the wire 56 | }; 57 | 58 | void usage(); 59 | void usage_long(); 60 | int server(); 61 | int client(); 62 | int server_conn(int cmdsock, char *); 63 | struct cmdStruct unpackCmdStr(unsigned char *); 64 | void packCmdStr(struct cmdStruct *, unsigned char *); 65 | struct statStruct unpackStatStr(unsigned char *); 66 | void packStatStr(struct statStruct *, unsigned char *); 67 | void printStatStruct(char *, struct statStruct *); 68 | int test_udp(struct cmdStruct, int, char *); 69 | int test_tcp(struct cmdStruct, int); 70 | void timespec_diff(struct timespec *, struct timespec *, 71 | struct timespec *); 72 | void timespec_add(struct timespec *, struct timespec *); 73 | int timespec_cmp(struct timespec *t1, struct timespec *t2); 74 | void timespec_dump(char *, struct timespec *); 75 | void dumpBuffer(const char *msg, unsigned char *buffer, int len); 76 | void packShortLE(unsigned char *, unsigned int); 77 | void packLongLE(unsigned char *, unsigned long); 78 | void packLongBE(unsigned char *, unsigned long); 79 | void unpackShortLE(unsigned char *, unsigned int *); 80 | void unpackLongLE(unsigned char *, unsigned long *); 81 | void unpackLongBE(unsigned char *, unsigned long *); 82 | void calc_interval(struct timespec *, unsigned long, unsigned int); 83 | unsigned char *calc_md5auth(unsigned char *nonce, char *passwd); 84 | 85 | char *opt_bandwidth = NULL; 86 | int opt_udpmode = 0; 87 | int opt_server = 0; 88 | int opt_interval = 0; 89 | int opt_nat = 0; 90 | int opt_transmit = 0; 91 | int opt_receive = 0; 92 | int opt_display = 0; 93 | char *opt_connect = NULL; 94 | char *opt_authuser = NULL; 95 | char *opt_authpass = NULL; 96 | 97 | double new_tx_speed; // Used to command the sending thread to change speed 98 | int tx_speed_changed = 0; 99 | 100 | int main(int argc, char **argv) 101 | { 102 | int opt; 103 | static struct option long_options[] = 104 | { 105 | {"udp", no_argument, &opt_udpmode, 1}, 106 | {"transmit", no_argument, &opt_transmit, 1}, 107 | {"receive", no_argument, &opt_receive, 1}, 108 | {"server", no_argument, &opt_server, 1}, 109 | {"nat", no_argument, &opt_nat, 1}, 110 | {"display", no_argument, &opt_display, 1}, 111 | {"help", no_argument, 0, 'h'}, 112 | {"client", required_argument, 0, 'c'}, 113 | {"interval", required_argument, 0, 'i'}, 114 | {"bandwidth", required_argument, 0, 'b'}, 115 | {"authuser", required_argument, 0, 'a'}, 116 | {"authpass", required_argument, 0, 'p'}, 117 | {0, 0, 0, 0}}; 118 | int option_index = 0; 119 | 120 | if (argc < 2) 121 | { 122 | usage(); 123 | exit(1); 124 | } 125 | 126 | while ((opt = getopt_long(argc, argv, "utrsnhdc:i:b:a:p:", long_options, &option_index)) != -1) 127 | { 128 | switch (opt) 129 | { 130 | case 'u': 131 | opt_udpmode = 1; 132 | break; 133 | case 't': 134 | opt_transmit = 1; 135 | break; 136 | case 'r': 137 | opt_receive = 1; 138 | break; 139 | case 's': 140 | opt_server = 1; 141 | break; 142 | case 'n': 143 | opt_nat = 1; 144 | break; 145 | case 'd': 146 | opt_display = 1; 147 | break; 148 | case 'c': 149 | opt_connect = strdup(optarg); 150 | break; 151 | case 'i': 152 | opt_interval = atoi(optarg); 153 | break; 154 | case 'b': 155 | opt_bandwidth = strdup(optarg); 156 | break; 157 | case 'a': 158 | opt_authuser = strdup(optarg); 159 | break; 160 | case 'p': 161 | opt_authpass = strdup(optarg); 162 | break; 163 | case 'h': 164 | usage_long(); 165 | exit(1); 166 | default: 167 | usage(); 168 | exit(EXIT_FAILURE); 169 | } 170 | } 171 | 172 | if (opt_server) 173 | { 174 | server(); 175 | } 176 | else 177 | { 178 | if (!opt_transmit && !opt_receive) 179 | { 180 | printf("You must specify transmit(-t) or receive(-r)\n"); 181 | exit(EXIT_FAILURE); 182 | } 183 | client(); 184 | } 185 | } 186 | 187 | void usage() 188 | { 189 | const char usage_shortstr[] = "Usage: btest [-s|-r -c host] [options]\n" 190 | "Try `btest --help' for more information.\n"; 191 | printf(usage_shortstr); 192 | } 193 | 194 | void usage_long() 195 | { 196 | const char usage_longstr[] = "Usage: btest [-s|-c host] [options]\n" 197 | " btest [-h|--help]\n\n" 198 | "Server or Client:\n" 199 | " -i, --interval # seconds between periodic bandwidth reports\n" 200 | " -h, --help show this message and quit\n" 201 | " -a, --authuser provide username for authentication\n" 202 | " -p, --authpass provide password for authentication\n" 203 | "Server specific:\n" 204 | " -s, --server run in server mode\n" 205 | "Client specific:\n" 206 | " -c, --client run in client mode, connecting to \n" 207 | " -t, --transmit transmit data\n" 208 | " -r, --receive receive data\n" 209 | " -u, --udp use UDP\n" 210 | " -b, --bandwidth #[KMG][/#] target bandwidth in bits/sec (0 for unlimited)\n" 211 | " (default %d Mbit/sec for UDP, unlimited for TCP)\n" 212 | " (optional slash and packet count for burst mode)\n" 213 | 214 | ; 215 | printf("%s", usage_longstr); 216 | } 217 | 218 | int client() 219 | { 220 | int cmdsock; 221 | struct cmdStruct cmd; 222 | unsigned char cmdStr[16]; 223 | struct sockaddr_in serverAddr; 224 | struct hostent *he; 225 | char *remoteIP; 226 | char bwmult; 227 | int ret; 228 | 229 | cmd.proto = CMD_PROTO_UDP; 230 | cmd.direction = opt_transmit ? CMD_DIR_RX : 0; 231 | cmd.direction += opt_receive ? CMD_DIR_TX : 0; 232 | cmd.random = 0; 233 | cmd.tcp_conn_count = 0; 234 | cmd.tx_size = 1500; 235 | cmd.client_buf_size = 0; 236 | 237 | if (opt_bandwidth) 238 | { 239 | if ((ret = sscanf(opt_bandwidth, "%lu%c", &cmd.remote_tx_speed, &bwmult)) < 1) 240 | { 241 | fprintf(stderr, "Cannot parse bandwidth string\n"); 242 | return (-1); 243 | } 244 | if (ret == 2) 245 | { 246 | /* Apply multiplier */ 247 | if (bwmult == 'k' || bwmult == 'K') 248 | { 249 | cmd.remote_tx_speed *= 1000; 250 | } 251 | else if (bwmult == 'm' || bwmult == 'M') 252 | { 253 | cmd.remote_tx_speed *= 1000000; 254 | } 255 | else 256 | { 257 | fprintf(stderr, "Cannot parse bandwidth string\n"); 258 | } 259 | } 260 | } 261 | else 262 | { 263 | cmd.remote_tx_speed = 0; // Unlimited 264 | } 265 | cmd.local_tx_speed = cmd.remote_tx_speed; 266 | 267 | packCmdStr(&cmd, cmdStr); 268 | 269 | if ((he = gethostbyname(opt_connect)) == NULL) 270 | { 271 | // get the host info 272 | perror("gethostbyname"); 273 | exit(1); 274 | } 275 | 276 | // Return the first one; 277 | bcopy((char *)he->h_addr, (char *)&serverAddr.sin_addr.s_addr, he->h_length); 278 | 279 | remoteIP = strdup(inet_ntoa(serverAddr.sin_addr)); 280 | 281 | cmdsock = socket(PF_INET, SOCK_STREAM, 0); 282 | int enable = 1; 283 | if (setsockopt(cmdsock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) 284 | { 285 | perror("setsockopt(SO_REUSEADDR) failed"); 286 | } 287 | 288 | serverAddr.sin_family = AF_INET; 289 | serverAddr.sin_port = htons(BTEST_PORT); 290 | memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); 291 | 292 | if (connect(cmdsock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) 293 | { 294 | perror("connect"); 295 | exit(1); 296 | } 297 | 298 | /* Look for hello string */ 299 | if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr)) 300 | { 301 | fprintf(stderr, "Remote did not return any hello response\n"); 302 | return (-1); 303 | } 304 | 305 | if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0) 306 | { 307 | fprintf(stderr, "Remote did not return correct hello response\n"); 308 | dumpBuffer("Response: ", helloStr, sizeof(helloStr)); 309 | return (-1); 310 | } 311 | 312 | send(cmdsock, cmdStr, sizeof(cmdStr), 0); 313 | 314 | /* Look for hello string */ 315 | if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr)) 316 | { 317 | fprintf(stderr, "Remote did not return any auth response\n"); 318 | return (-1); 319 | } 320 | 321 | if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0) 322 | { 323 | if (memcmp(helloStr, needAuthResp, sizeof(needAuthResp)) == 0) 324 | { 325 | /* Fetch the 16 random bytes */ 326 | unsigned char nonce[16]; 327 | char user[32]; 328 | unsigned char *md5hash; 329 | if (recv(cmdsock, nonce, sizeof(nonce), 0) < sizeof(nonce)) 330 | { 331 | fprintf(stderr, "Remote did not send auth nonce\n"); 332 | return (-1); 333 | } 334 | // dumpBuffer("Nonce: ", nonce, sizeof(nonce)); 335 | memset(user, '\0', sizeof(user)); 336 | strncpy(user, opt_authuser, sizeof(user)); 337 | md5hash = calc_md5auth(nonce, opt_authpass); 338 | // dumpBuffer("MD5: ", md5hash, 16); 339 | send(cmdsock, md5hash, 16, 0); 340 | send(cmdsock, user, sizeof(user), 0); 341 | 342 | /* Look for hello string */ 343 | if (recv(cmdsock, helloStr, sizeof(helloStr), 0) < sizeof(helloStr)) 344 | { 345 | fprintf(stderr, "Remote did not return auth response\n"); 346 | return (-1); 347 | } 348 | 349 | if (memcmp(helloStr, failedAuthResp, sizeof(failedAuthResp)) == 0) 350 | { 351 | fprintf(stderr, "Remote authentication failed\n"); 352 | // dumpBuffer("Response: ", helloStr,sizeof(helloStr)); 353 | return (-1); 354 | } 355 | 356 | if (memcmp(helloStr, noAuthResp, sizeof(noAuthResp)) != 0) 357 | { 358 | fprintf(stderr, "Remote did not return correct hello response\n"); 359 | dumpBuffer("Response: ", helloStr, sizeof(helloStr)); 360 | return (-1); 361 | } 362 | } 363 | else 364 | { 365 | fprintf(stderr, "Remote did not return correct hello response\n"); 366 | dumpBuffer("Response: ", helloStr, sizeof(helloStr)); 367 | return (-1); 368 | } 369 | } 370 | 371 | if (cmd.proto == CMD_PROTO_UDP) 372 | { 373 | test_udp(cmd, cmdsock, remoteIP); 374 | } 375 | else 376 | { 377 | test_tcp(cmd, cmdsock); 378 | } 379 | return 0; 380 | } 381 | 382 | int server() 383 | { 384 | int controlSocket; 385 | struct sockaddr_in serverAddr; 386 | struct sockaddr_in clientAddr; 387 | socklen_t addr_size; 388 | int newSocket; 389 | 390 | printf("Running in server mode\n"); 391 | controlSocket = socket(PF_INET, SOCK_STREAM, 0); 392 | int enable = 1; 393 | if (setsockopt(controlSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) 394 | { 395 | perror("setsockopt(SO_REUSEADDR) failed"); 396 | } 397 | 398 | serverAddr.sin_family = AF_INET; 399 | serverAddr.sin_port = htons(BTEST_PORT); 400 | serverAddr.sin_addr.s_addr = INADDR_ANY; 401 | memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); 402 | 403 | bind(controlSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); 404 | 405 | if (listen(controlSocket, 5) == 0) 406 | printf("Listening\n"); 407 | else 408 | printf("Error\n"); 409 | 410 | addr_size = sizeof(clientAddr); 411 | signal(SIGCHLD, SIG_IGN); 412 | while (1) 413 | { 414 | newSocket = accept(controlSocket, (struct sockaddr *)&clientAddr, &addr_size); 415 | /* fork a child process to handle the new connection */ 416 | if (!fork()) 417 | { 418 | server_conn(newSocket, strdup(inet_ntoa(clientAddr.sin_addr))); 419 | close(newSocket); 420 | printf("Complete\n"); 421 | exit(0); 422 | } 423 | else 424 | { 425 | /*if parent, close the socket and go back to listening new requests*/ 426 | close(newSocket); 427 | } 428 | } 429 | return 0; 430 | } 431 | 432 | int server_conn(int cmdsock, char *remoteIP) 433 | { 434 | int nBytes = 1; 435 | struct cmdStruct cmd; 436 | unsigned char cmdStr[16]; 437 | uint32_t hexValue; 438 | int flag = 1; 439 | setsockopt(cmdsock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)); 440 | 441 | /* Send hello message */ 442 | send(cmdsock, helloStr, sizeof(helloStr), 0); 443 | 444 | /* Recieve command */ 445 | nBytes = recv(cmdsock, cmdStr, sizeof(cmdStr), 0); 446 | if (nBytes < sizeof(cmdStr)) 447 | { 448 | return (-1); 449 | } 450 | 451 | cmd = unpackCmdStr(cmdStr); 452 | printf("proto=%d\n", cmd.proto); 453 | printf("direction=%d\n", cmd.direction); 454 | printf("random=%d\n", cmd.random); 455 | printf("tcp_conn_count=%d\n", cmd.tcp_conn_count); 456 | printf("tx_size=%d\n", cmd.tx_size); 457 | printf("client_buf_size=%d\n", cmd.client_buf_size); 458 | printf("remote_tx_speed=%lu\n", cmd.remote_tx_speed); 459 | printf("local_tx_speed=%lu\n", cmd.local_tx_speed); 460 | printf("remoteIP=%s\n", remoteIP); 461 | /* auth logic here */ 462 | int isauthrequired = isStringNotEmpty(opt_authuser) || isStringNotEmpty(opt_authpass); 463 | printf("is auth required: %d\n", isauthrequired); 464 | if (isauthrequired) 465 | { 466 | /* Send auth request */ 467 | hexValue = htonl(0x02000000); 468 | send(cmdsock, &hexValue, sizeof(hexValue), 0); 469 | unsigned char nonce[16]; 470 | /* Generate random nonce */ 471 | generateRandomNonce(nonce); 472 | char recuser[32]; 473 | char recdigest[32]; 474 | unsigned char *md5hash; 475 | send(cmdsock, nonce, sizeof(nonce), 0); 476 | md5hash = calc_md5auth(nonce, opt_authpass); 477 | unsigned char authstr[32]; 478 | nBytes = recv(cmdsock, authstr, sizeof(authstr), 0); 479 | if (nBytes < sizeof(authstr)) 480 | { 481 | return (-1); 482 | } 483 | unsigned char username[AUTHSTR_SIZE - 16]; 484 | char receivedhash[2 * (size_t)16 + 1]; 485 | char serverdigest[2 * (size_t)16 + 1]; 486 | memcpy(username, authstr + 16, sizeof(username)); 487 | getHexRepresentation(authstr, (size_t)16, receivedhash); 488 | getHexRepresentation(md5hash, (size_t)16, serverdigest); 489 | // dumpBuffer("> auth Response: ", authstr, sizeof(authstr)); 490 | printf("auth received from %s: user(%s) - pass_digest(%s)\n", remoteIP, username, receivedhash); 491 | printf("server have user(%s) - pass_digest(%s)\n", opt_authuser, serverdigest); 492 | if (isauth(opt_authuser, username, receivedhash, serverdigest)) 493 | { 494 | hexValue = htonl(0x01000000); 495 | send(cmdsock, &hexValue, sizeof(hexValue), 0); 496 | } 497 | else 498 | { 499 | hexValue = htonl(0x00000000); 500 | send(cmdsock, &hexValue, sizeof(hexValue), 0); 501 | return (-1); 502 | } 503 | } 504 | /* Send all OK message */ 505 | send(cmdsock, helloStr, sizeof(helloStr), 0); 506 | 507 | if (cmd.proto == CMD_PROTO_UDP) 508 | { 509 | if (test_udp(cmd, cmdsock, remoteIP) != -1) 510 | { 511 | return (-1); 512 | } 513 | } 514 | else 515 | { 516 | if (test_tcp(cmd, cmdsock) == -1) 517 | { 518 | return (-1); 519 | } 520 | } 521 | /* loop while connection is live */ 522 | return 0; 523 | } 524 | 525 | void packShortLE(unsigned char *buf, unsigned int res) 526 | { 527 | int i; 528 | for (i = 0; i < 2; i++) 529 | { 530 | buf[i] = res & 0xff; 531 | res >>= 8; 532 | } 533 | } 534 | 535 | void packLongLE(unsigned char *buf, unsigned long res) 536 | { 537 | int i; 538 | for (i = 0; i < 4; i++) 539 | { 540 | buf[i] = res & 0xff; 541 | res >>= 8; 542 | } 543 | } 544 | 545 | void packLongBE(unsigned char *buf, unsigned long res) 546 | { 547 | int i; 548 | for (i = 3; i >= 0; i--) 549 | { 550 | buf[i] = res & 0xff; 551 | res >>= 8; 552 | } 553 | } 554 | 555 | void unpackShortLE(unsigned char *buf, unsigned int *pres) 556 | { 557 | int i; 558 | *pres = 0; 559 | for (i = 2; i >= 0; i--) 560 | { 561 | *pres <<= 8; 562 | *pres += buf[i]; 563 | } 564 | } 565 | 566 | void unpackLongLE(unsigned char *buf, unsigned long *pres) 567 | { 568 | int i; 569 | *pres = 0; 570 | for (i = 3; i >= 0; i--) 571 | { 572 | *pres <<= 8; 573 | *pres += buf[i]; 574 | } 575 | } 576 | 577 | void unpackLongBE(unsigned char *buf, unsigned long *pres) 578 | { 579 | int i; 580 | *pres = 0; 581 | for (i = 0; i < 4; i++) 582 | { 583 | *pres <<= 8; 584 | *pres += buf[i]; 585 | } 586 | } 587 | 588 | struct cmdStruct unpackCmdStr(unsigned char *cmdStr) 589 | { 590 | struct cmdStruct cmd; 591 | 592 | dumpBuffer("Cmd buffer: ", cmdStr, 16); 593 | 594 | cmd.proto = cmdStr[0]; 595 | cmd.direction = cmdStr[1]; 596 | cmd.random = cmdStr[2]; 597 | cmd.tcp_conn_count = cmdStr[3]; 598 | 599 | unpackShortLE(&cmdStr[4], &cmd.tx_size); 600 | 601 | /* Assume little endian */ 602 | unpackShortLE(&cmdStr[6], &cmd.client_buf_size); 603 | unpackLongLE(&cmdStr[8], &cmd.remote_tx_speed); 604 | unpackLongLE(&cmdStr[12], &cmd.local_tx_speed); 605 | 606 | return (cmd); 607 | } 608 | 609 | void packCmdStr(struct cmdStruct *pcmd, unsigned char *buf) 610 | { 611 | 612 | buf[0] = (pcmd->proto == CMD_PROTO_TCP) ? CMD_PROTO_TCP : CMD_PROTO_UDP; 613 | buf[1] = pcmd->direction & 0x03; 614 | buf[2] = pcmd->random ? 1 : 0; 615 | buf[3] = pcmd->tcp_conn_count; 616 | 617 | /* Little endian */ 618 | packShortLE(&buf[4], pcmd->tx_size); 619 | 620 | packShortLE(&buf[6], pcmd->client_buf_size); 621 | 622 | packLongLE(&buf[8], pcmd->remote_tx_speed); 623 | packLongLE(&buf[12], pcmd->local_tx_speed); 624 | 625 | // dumpBuffer("Packed Buffer: ", buf, 16); 626 | 627 | return; 628 | } 629 | 630 | struct statStruct unpackStatStr(unsigned char *buf) 631 | { 632 | struct statStruct stat; 633 | 634 | // dumpBuffer("Stat buffer: ", buf, 12); 635 | 636 | unpackLongBE(&buf[1], &stat.seq); 637 | 638 | memcpy(stat.unknown, buf + 5, 3); 639 | 640 | unpackLongLE(&buf[8], &stat.recvBytes); 641 | 642 | /* These three are not used */ 643 | stat.maxInterval = 0; 644 | stat.minInterval = 0; 645 | stat.lostPackets = 0; 646 | return (stat); 647 | } 648 | 649 | void printStatStruct(char *msg, struct statStruct *pstat) 650 | { 651 | /* Work out the bit rate - assume status every second */ 652 | double bitRate; 653 | char bitRateStr[20]; 654 | 655 | bitRate = pstat->recvBytes * 8; 656 | if (bitRate > 10000000) 657 | { 658 | sprintf(bitRateStr, "%.1fMb/s", bitRate / 1000000); 659 | } 660 | else 661 | { 662 | sprintf(bitRateStr, "%.1fkb/s", bitRate / 1000); 663 | } 664 | printf("%sSeq: %lu, Rate: %s", 665 | msg, 666 | pstat->seq, 667 | bitRateStr); 668 | if (pstat->maxInterval > 0) 669 | { 670 | printf(", Lost: %ld, Min: %.4lfms, Max: %.4lfms, Diff %0.4lfms\n", 671 | pstat->lostPackets, 672 | ((double)pstat->minInterval) / 1000, 673 | ((double)pstat->maxInterval) / 1000, 674 | ((double)pstat->maxInterval) / 1000 - ((double)pstat->minInterval) / 1000); 675 | } 676 | else 677 | { 678 | printf("\n"); 679 | } 680 | } 681 | 682 | void packStatStr(struct statStruct *pstat, unsigned char *buf) 683 | { 684 | 685 | buf[0] = 0x07; 686 | packLongBE(&buf[1], pstat->seq); 687 | buf[5] = 0x00; 688 | buf[6] = 0x00; 689 | buf[7] = 0x00; 690 | packLongLE(&buf[8], pstat->recvBytes); 691 | 692 | // dumpBuffer("Stat buffer: ", buf, 12); 693 | 694 | return; 695 | } 696 | 697 | int udpSocket; 698 | 699 | void *test_udp_tx(void *arg) 700 | { 701 | struct cmdStruct *pcmd; 702 | unsigned char *buf; 703 | int seq = 1; 704 | int i; 705 | struct timespec interval; /* Interval between packets in nano seconds */ 706 | struct timespec nextPacketTime; 707 | struct timespec now; 708 | int tx_speed_variable = 0; 709 | unsigned long tx_speed; 710 | 711 | // printf("Calling test_udp_tx()\n"); 712 | // sleep(1); 713 | pcmd = (struct cmdStruct *)arg; 714 | // printf("Calling test_udp_tx(%d)\n", pcmd->tx_size); 715 | if (opt_server) 716 | { 717 | tx_speed = pcmd->remote_tx_speed; 718 | } 719 | else 720 | { 721 | tx_speed = pcmd->local_tx_speed; 722 | } 723 | printf("Tx speed: %lu\n", tx_speed); 724 | if (tx_speed == 0) 725 | { 726 | tx_speed_variable = 1; 727 | tx_speed = 1000000; 728 | } 729 | new_tx_speed = tx_speed; 730 | 731 | calc_interval(&interval, tx_speed, pcmd->tx_size); 732 | timespec_dump("Interval: ", &interval); 733 | buf = (unsigned char *)malloc(pcmd->tx_size - 28); 734 | // printf("Calling test_udp_tx(more)\n"); 735 | bzero(buf, pcmd->tx_size - 28); 736 | 737 | /* Get current time and add the interval to it */ 738 | clock_gettime(CLOCK_MONOTONIC, &nextPacketTime); 739 | timespec_dump("gettime: ", &nextPacketTime); 740 | while (1) 741 | { 742 | if (tx_speed_variable && tx_speed_changed) 743 | { 744 | tx_speed_changed = 0; 745 | tx_speed = new_tx_speed; 746 | calc_interval(&interval, tx_speed, pcmd->tx_size); 747 | // printf("New Tx speed: %lf\n", new_tx_speed); 748 | } 749 | nextPacketTime.tv_sec += interval.tv_sec; 750 | nextPacketTime.tv_nsec += interval.tv_nsec; 751 | if (nextPacketTime.tv_nsec >= 1000000000) 752 | { 753 | nextPacketTime.tv_sec += nextPacketTime.tv_nsec / 1000000000; 754 | nextPacketTime.tv_nsec %= 1000000000; 755 | } 756 | int tmp = seq++; 757 | 758 | // printf("seq=%d\n", seq); 759 | /* Put in sequence number */ 760 | for (i = 3; i >= 0; i--) 761 | { 762 | buf[i] = tmp % 256; 763 | tmp = tmp >> 8; 764 | } 765 | // printf("Sleep until %lu:%lu\n", nextPacketTime.tv_sec, nextPacketTime.tv_nsec); 766 | // timespec_dump("sleep until: ", &nextPacketTime); 767 | clock_gettime(CLOCK_MONOTONIC, &now); 768 | if (now.tv_sec <= nextPacketTime.tv_sec && now.tv_nsec < nextPacketTime.tv_nsec) 769 | { 770 | #ifdef __MACH__ 771 | clock_nanosleep_abstime(&nextPacketTime); 772 | #else 773 | if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &nextPacketTime, NULL) < 0) 774 | { 775 | perror("clock_nanosleep: "); 776 | } 777 | #endif 778 | } 779 | 780 | send(udpSocket, buf, pcmd->tx_size - 28, 0); 781 | /* 782 | if (send(udpSocket, buf, pcmd->tx_size-28,0)<0) { 783 | perror("send udp: "); 784 | exit(-1); 785 | } 786 | */ 787 | } 788 | } 789 | 790 | struct statStruct recvStats; 791 | 792 | void *test_udp_rx(void *arg) 793 | { 794 | struct cmdStruct *pcmd; 795 | unsigned char *buf; 796 | int nBytes; 797 | struct timespec last; 798 | struct timespec now; 799 | struct timespec interval; 800 | unsigned long intervalUs; 801 | unsigned long lastSeq = 0; 802 | unsigned long thisSeq = 0; 803 | 804 | recvStats.recvBytes = 0; 805 | recvStats.maxInterval = 0; 806 | recvStats.minInterval = 0; 807 | recvStats.lostPackets = 0; 808 | pcmd = (struct cmdStruct *)arg; 809 | // printf("Calling test_udp_rx(tx_size=%d)\n", pcmd->tx_size); 810 | buf = (unsigned char *)malloc(pcmd->tx_size); 811 | last.tv_sec = 0; 812 | last.tv_nsec = 0; 813 | while (1) 814 | { 815 | if ((nBytes = recv(udpSocket, buf, pcmd->tx_size, 0)) < 0) 816 | { 817 | /* Ignore connection refused - the other end probably wasn't ready */ 818 | if (errno != ECONNREFUSED) 819 | { 820 | perror("test_udp_rx: recv udp: "); 821 | exit(-1); 822 | } 823 | } 824 | if (nBytes > 0) 825 | { 826 | clock_gettime(CLOCK_MONOTONIC, &now); 827 | 828 | thisSeq = buf[0]; 829 | thisSeq = thisSeq * 256 + buf[1]; 830 | thisSeq = thisSeq * 256 + buf[2]; 831 | thisSeq = thisSeq * 256 + buf[3]; 832 | if (lastSeq > 0) 833 | { 834 | /* Ignore the first one - we often lose packets initially */ 835 | /* 836 | if (thisSeq-lastSeq-1 != 0) { 837 | printf("loss: thisSeq=%lu, lastSeq=%lu\n", thisSeq, lastSeq); 838 | } 839 | */ 840 | recvStats.lostPackets += thisSeq - lastSeq - 1; 841 | } 842 | 843 | if (last.tv_sec != 0 && last.tv_nsec != 0) 844 | { 845 | /* Work out time difference */ 846 | timespec_diff(&last, &now, &interval); 847 | intervalUs = interval.tv_nsec / 1000; 848 | intervalUs += interval.tv_sec * 1000000; 849 | 850 | /* Divide by the difference in the sequence 851 | ** number so that we don't get a bump in the 852 | ** interval due to a bunch of missed packets 853 | */ 854 | intervalUs /= thisSeq - lastSeq; 855 | 856 | if (recvStats.maxInterval == 0) 857 | { 858 | /* First result */ 859 | // printf("First: %lu\n", intervalUs); 860 | recvStats.maxInterval = intervalUs; 861 | recvStats.minInterval = intervalUs; 862 | } 863 | else 864 | { 865 | if (intervalUs > recvStats.maxInterval) 866 | { 867 | recvStats.maxInterval = intervalUs; 868 | } 869 | if (intervalUs < recvStats.minInterval) 870 | { 871 | recvStats.minInterval = intervalUs; 872 | } 873 | } 874 | } 875 | last = now; 876 | recvStats.recvBytes += nBytes + 28; 877 | lastSeq = thisSeq; 878 | } 879 | } 880 | } 881 | 882 | int test_udp(struct cmdStruct cmd, int cmdsock, char *remoteIP) 883 | { 884 | unsigned char socknumbuf[2]; 885 | pthread_t pth_tx; 886 | pthread_t pth_rx; 887 | struct sockaddr_in serverAddr, clientAddr; 888 | socklen_t addr_size; 889 | int nBytes; 890 | unsigned char buffer[1024]; 891 | struct statStruct remoteStats; 892 | struct timespec interval; /* Interval between status messages */ 893 | struct timespec nextStatusTime; 894 | struct timespec timeout; 895 | struct timespec now; 896 | 897 | // printf("Calling test_udp()\n"); 898 | if (opt_server) 899 | { 900 | /* Send server socket number */ 901 | udpport++; 902 | socknumbuf[0] = udpport / 256; 903 | socknumbuf[1] = udpport % 256; 904 | send(cmdsock, socknumbuf, sizeof(socknumbuf), 0); 905 | } 906 | else 907 | { 908 | if (recv(cmdsock, socknumbuf, sizeof(socknumbuf), 0) < sizeof(socknumbuf)) 909 | { 910 | fprintf(stderr, "Did not recieve remote port number\n"); 911 | return (-1); 912 | } 913 | // dumpBuffer("Socket number buffer: ", socknumbuf, 2); 914 | udpport = (socknumbuf[0] << 8) + socknumbuf[1]; 915 | } 916 | // printf("Calling test_udp(udpport=%d)\n", udpport); 917 | 918 | addr_size = sizeof(clientAddr); 919 | 920 | /* Create a UDP socket to transmit/recieve the data */ 921 | udpSocket = socket(PF_INET, SOCK_DGRAM, 0); 922 | serverAddr.sin_family = AF_INET; 923 | if (opt_server) 924 | { 925 | serverAddr.sin_port = htons(udpport); 926 | } 927 | else 928 | { 929 | serverAddr.sin_port = htons(udpport + BTEST_PORT_CLIENT_OFFSET); 930 | } 931 | serverAddr.sin_addr.s_addr = INADDR_ANY; 932 | memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero); 933 | 934 | bind(udpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); 935 | 936 | clientAddr.sin_family = AF_INET; 937 | if (opt_server) 938 | { 939 | clientAddr.sin_port = htons(udpport + BTEST_PORT_CLIENT_OFFSET); 940 | } 941 | else 942 | { 943 | clientAddr.sin_port = htons(udpport); 944 | } 945 | clientAddr.sin_addr.s_addr = inet_addr(remoteIP); 946 | memset(clientAddr.sin_zero, '\0', sizeof clientAddr.sin_zero); 947 | 948 | /* Connect it to the remote end */ 949 | connect(udpSocket, (struct sockaddr *)&clientAddr, sizeof(clientAddr)); 950 | 951 | /* We have to swap the command round between server and client */ 952 | if (((cmd.direction & CMD_DIR_TX) && opt_server) || ((cmd.direction & CMD_DIR_RX) && !opt_server)) 953 | { 954 | pthread_create(&pth_tx, NULL, test_udp_tx, (void *)&cmd); 955 | } 956 | if (((cmd.direction & CMD_DIR_RX) && opt_server) || ((cmd.direction & CMD_DIR_TX) && !opt_server)) 957 | { 958 | if (opt_nat) 959 | { 960 | /* Send a zero length packet to open any firewall */ 961 | send(udpSocket, buffer, 0, 0); 962 | } 963 | pthread_create(&pth_rx, NULL, test_udp_rx, (void *)&cmd); 964 | } 965 | 966 | /* Interval between status messages */ 967 | interval.tv_nsec = 0; 968 | interval.tv_sec = 1; 969 | 970 | /* Get current time and add the interval to it */ 971 | clock_gettime(CLOCK_MONOTONIC, &nextStatusTime); 972 | timespec_add(&nextStatusTime, &interval); 973 | recvStats.seq = 0; 974 | while (1) 975 | { 976 | int ready; 977 | fd_set readfds; 978 | 979 | clock_gettime(CLOCK_MONOTONIC, &now); 980 | timespec_diff(&now, &nextStatusTime, &timeout); 981 | 982 | FD_ZERO(&readfds); 983 | FD_SET(cmdsock, &readfds); 984 | ready = pselect(cmdsock + 1, &readfds, NULL, NULL, &timeout, NULL); 985 | if (ready) 986 | { 987 | nBytes = recv(cmdsock, buffer, 1024, 0); 988 | if (nBytes <= 0) 989 | { 990 | exit(0); 991 | } 992 | remoteStats = unpackStatStr(buffer); 993 | if (((cmd.direction & CMD_DIR_TX) && opt_server) || ((cmd.direction & CMD_DIR_RX) && !opt_server)) 994 | { 995 | /* Only print this if we are transmitting */ 996 | if (opt_display) 997 | { 998 | double bitRate = remoteStats.recvBytes * 8; 999 | if (bitRate > 10000000) 1000 | { 1001 | printf("%.1f Mb/s\n", bitRate / 1000000); 1002 | } 1003 | else 1004 | { 1005 | printf("%.1f kb/s\n", bitRate / 1000); 1006 | } 1007 | } 1008 | else 1009 | { 1010 | printStatStruct("Remote: ", &remoteStats); 1011 | } 1012 | 1013 | fflush(stdout); 1014 | /* Set the outgoing speed to be twice the rate reported */ 1015 | new_tx_speed = remoteStats.recvBytes * 8; 1016 | new_tx_speed *= 1.5; 1017 | tx_speed_changed = 1; 1018 | } 1019 | } 1020 | 1021 | clock_gettime(CLOCK_MONOTONIC, &now); 1022 | if (timespec_cmp(&now, &nextStatusTime) > 0) 1023 | { 1024 | recvStats.seq++; 1025 | packStatStr(&recvStats, buffer); 1026 | if (((cmd.direction & CMD_DIR_RX) && opt_server) || ((cmd.direction & CMD_DIR_TX) && !opt_server)) 1027 | { 1028 | /* Only print this if we are recieving */ 1029 | if (opt_display) 1030 | { 1031 | double bitRate = recvStats.recvBytes * 8; 1032 | if (bitRate > 10000000) 1033 | { 1034 | printf("%.1f Mb/s\n", bitRate / 1000000); 1035 | } 1036 | else 1037 | { 1038 | printf("%.1f kb/s\n", bitRate / 1000); 1039 | } 1040 | } 1041 | else 1042 | { 1043 | printStatStruct("Local : ", &recvStats); 1044 | } 1045 | fflush(stdout); 1046 | } 1047 | 1048 | send(cmdsock, buffer, 12, 0); 1049 | timespec_add(&nextStatusTime, &interval); 1050 | recvStats.recvBytes = 0; 1051 | recvStats.maxInterval = 0; 1052 | recvStats.minInterval = 0; 1053 | recvStats.lostPackets = 0; 1054 | } 1055 | } 1056 | } 1057 | 1058 | int tcpSocket; 1059 | 1060 | void *test_tcp_tx(void *arg) 1061 | { 1062 | struct cmdStruct *pcmd; 1063 | unsigned char *buf; 1064 | 1065 | // printf("Calling test_tcp_tx()\n"); 1066 | sleep(1); 1067 | pcmd = (struct cmdStruct *)arg; 1068 | buf = (unsigned char *)malloc(pcmd->tx_size); 1069 | bzero(buf, pcmd->tx_size); 1070 | 1071 | buf[0] = 0x07; 1072 | buf[4] = 0x01; 1073 | 1074 | while (send(tcpSocket, buf, pcmd->tx_size, 0) > 0) 1075 | ; 1076 | return NULL; 1077 | } 1078 | 1079 | int test_tcp(struct cmdStruct cmd, int cmdsock) 1080 | { 1081 | pthread_t pth_tx; 1082 | // pthread_t pth_rx; 1083 | int nBytes, i; 1084 | unsigned char buffer[1024]; 1085 | 1086 | // printf("Calling test_tcp()\n"); 1087 | tcpSocket = cmdsock; 1088 | if (cmd.direction == CMD_DIR_TX) 1089 | { 1090 | pthread_create(&pth_tx, NULL, test_tcp_tx, (void *)&cmd); 1091 | } 1092 | 1093 | printf("Listening on TCP cmdsock\n"); 1094 | while ((nBytes = recv(cmdsock, buffer, 1024, 0))) 1095 | { 1096 | for (i = 0; i < nBytes - 1; i++) 1097 | { 1098 | printf("%02x", buffer[i]); 1099 | } 1100 | printf("\n"); 1101 | } 1102 | return 0; 1103 | } 1104 | 1105 | /* Calculate the difference between two timespec's */ 1106 | void timespec_diff(struct timespec *start, struct timespec *stop, 1107 | struct timespec *result) 1108 | { 1109 | if ((stop->tv_nsec - start->tv_nsec) < 0) 1110 | { 1111 | result->tv_sec = stop->tv_sec - start->tv_sec - 1; 1112 | result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000; 1113 | } 1114 | else 1115 | { 1116 | result->tv_sec = stop->tv_sec - start->tv_sec; 1117 | result->tv_nsec = stop->tv_nsec - start->tv_nsec; 1118 | } 1119 | 1120 | return; 1121 | } 1122 | 1123 | /* Add timespec t2 onto timespec t1 onto an existing one */ 1124 | void timespec_add(struct timespec *t1, struct timespec *t2) 1125 | { 1126 | t1->tv_sec += t2->tv_sec; 1127 | t1->tv_nsec += t2->tv_nsec; 1128 | if (t1->tv_nsec >= 1000000000) 1129 | { 1130 | t1->tv_sec += t1->tv_nsec / 1000000000; 1131 | t1->tv_nsec %= 1000000000; 1132 | } 1133 | } 1134 | 1135 | /* Return -1, 0, or +1 if t1 less than, equal to or greater than t2 */ 1136 | int timespec_cmp(struct timespec *t1, struct timespec *t2) 1137 | { 1138 | if (t1->tv_sec < t2->tv_sec) 1139 | { 1140 | return (-1); 1141 | } 1142 | else if (t1->tv_sec > t2->tv_sec) 1143 | { 1144 | return (1); 1145 | } 1146 | else 1147 | { 1148 | if (t1->tv_nsec < t2->tv_nsec) 1149 | { 1150 | return (-1); 1151 | } 1152 | else if (t1->tv_nsec > t2->tv_nsec) 1153 | { 1154 | return (1); 1155 | } 1156 | else 1157 | { 1158 | return (0); 1159 | } 1160 | } 1161 | } 1162 | 1163 | void timespec_dump(char *msg, struct timespec *t1) 1164 | { 1165 | printf("%s %lu:%lu\n", msg, t1->tv_sec, t1->tv_nsec); 1166 | } 1167 | 1168 | void dumpBuffer(const char *msg, unsigned char *buffer, int len) 1169 | { 1170 | int i; 1171 | printf("%s", msg); 1172 | for (i = 0; i < len; i++) 1173 | { 1174 | printf("%02x", buffer[i]); 1175 | } 1176 | printf("\n"); 1177 | } 1178 | 1179 | void calc_interval(struct timespec *ts, unsigned long tx_speed, unsigned int tx_size) 1180 | { 1181 | if (tx_speed > 0) 1182 | { 1183 | // pthread_getcpuclockid(pthread_self(), &clock_id); 1184 | double interval_nsec; // We use a double so we don't overflow the various ints 1185 | 1186 | interval_nsec = 1000000000; 1187 | interval_nsec *= tx_size * 8; 1188 | interval_nsec /= tx_speed; 1189 | 1190 | ts->tv_sec = 0; 1191 | ts->tv_nsec = interval_nsec; 1192 | 1193 | /* Duplicate bug? in MT where anything less than 2 packets per second gets converted to 1 packet second */ 1194 | if (ts->tv_nsec > 500000000) 1195 | { 1196 | ts->tv_nsec = 0; 1197 | ts->tv_sec = 1; 1198 | } 1199 | } 1200 | else 1201 | { 1202 | ts->tv_nsec = 0; 1203 | ts->tv_sec = 0; 1204 | } 1205 | } 1206 | 1207 | unsigned char * 1208 | calc_md5auth(unsigned char *nonce, char *passwd) 1209 | { 1210 | MD5_CTX ctx; 1211 | static unsigned char hash[16]; 1212 | 1213 | MD5_Init(&ctx); 1214 | MD5_Update(&ctx, (void *)passwd, strlen(passwd)); 1215 | MD5_Update(&ctx, (void *)nonce, 16); 1216 | MD5_Final(hash, &ctx); 1217 | 1218 | MD5_Init(&ctx); 1219 | MD5_Update(&ctx, (void *)passwd, strlen(passwd)); 1220 | MD5_Update(&ctx, (void *)hash, 16); 1221 | MD5_Final(hash, &ctx); 1222 | 1223 | return (hash); 1224 | } 1225 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | gcc -o btest *.c -lpthread && chmod +x btest -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([btest], [1.0], [bug-report@address]) 2 | AM_INIT_AUTOMAKE 3 | AC_ARG_ENABLE(debugging, [ --disable-debugging disable debugging code], ac_cv_debugging=$enableval, ac_cv_debugging=yes) 4 | AC_PROG_CC 5 | dnl Checks for header files. 6 | AC_HEADER_STDC 7 | AC_HEADER_SYS_WAIT 8 | AC_CHECK_HEADERS() 9 | AC_TYPE_UINT8_T 10 | AC_TYPE_UINT16_T 11 | AC_TYPE_UINT32_T 12 | AC_CONFIG_HEADERS([config.h]) 13 | AC_CONFIG_FILES([Makefile]) 14 | AC_OUTPUT 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | btest-service: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | container_name: btest_tool 9 | image: btest_tools:latest 10 | environment: 11 | - USERNAME=_________ # username here ... 12 | - PASSWORD=_________ # password here ... 13 | -------------------------------------------------------------------------------- /install-sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # install - install a program, script, or datafile 3 | 4 | scriptversion=2011-11-20.07; # UTC 5 | 6 | # This originates from X11R5 (mit/util/scripts/install.sh), which was 7 | # later released in X11R6 (xc/config/util/install.sh) with the 8 | # following copyright and license. 9 | # 10 | # Copyright (C) 1994 X Consortium 11 | # 12 | # Permission is hereby granted, free of charge, to any person obtaining a copy 13 | # of this software and associated documentation files (the "Software"), to 14 | # deal in the Software without restriction, including without limitation the 15 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 16 | # sell copies of the Software, and to permit persons to whom the Software is 17 | # furnished to do so, subject to the following conditions: 18 | # 19 | # The above copyright notice and this permission notice shall be included in 20 | # all copies or substantial portions of the Software. 21 | # 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | # X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 26 | # AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- 27 | # TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | # 29 | # Except as contained in this notice, the name of the X Consortium shall not 30 | # be used in advertising or otherwise to promote the sale, use or other deal- 31 | # ings in this Software without prior written authorization from the X Consor- 32 | # tium. 33 | # 34 | # 35 | # FSF changes to this file are in the public domain. 36 | # 37 | # Calling this script install-sh is preferred over install.sh, to prevent 38 | # 'make' implicit rules from creating a file called install from it 39 | # when there is no Makefile. 40 | # 41 | # This script is compatible with the BSD install script, but was written 42 | # from scratch. 43 | 44 | nl=' 45 | ' 46 | IFS=" "" $nl" 47 | 48 | # set DOITPROG to echo to test this script 49 | 50 | # Don't use :- since 4.3BSD and earlier shells don't like it. 51 | doit=${DOITPROG-} 52 | if test -z "$doit"; then 53 | doit_exec=exec 54 | else 55 | doit_exec=$doit 56 | fi 57 | 58 | # Put in absolute file names if you don't have them in your path; 59 | # or use environment vars. 60 | 61 | chgrpprog=${CHGRPPROG-chgrp} 62 | chmodprog=${CHMODPROG-chmod} 63 | chownprog=${CHOWNPROG-chown} 64 | cmpprog=${CMPPROG-cmp} 65 | cpprog=${CPPROG-cp} 66 | mkdirprog=${MKDIRPROG-mkdir} 67 | mvprog=${MVPROG-mv} 68 | rmprog=${RMPROG-rm} 69 | stripprog=${STRIPPROG-strip} 70 | 71 | posix_glob='?' 72 | initialize_posix_glob=' 73 | test "$posix_glob" != "?" || { 74 | if (set -f) 2>/dev/null; then 75 | posix_glob= 76 | else 77 | posix_glob=: 78 | fi 79 | } 80 | ' 81 | 82 | posix_mkdir= 83 | 84 | # Desired mode of installed file. 85 | mode=0755 86 | 87 | chgrpcmd= 88 | chmodcmd=$chmodprog 89 | chowncmd= 90 | mvcmd=$mvprog 91 | rmcmd="$rmprog -f" 92 | stripcmd= 93 | 94 | src= 95 | dst= 96 | dir_arg= 97 | dst_arg= 98 | 99 | copy_on_change=false 100 | no_target_directory= 101 | 102 | usage="\ 103 | Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE 104 | or: $0 [OPTION]... SRCFILES... DIRECTORY 105 | or: $0 [OPTION]... -t DIRECTORY SRCFILES... 106 | or: $0 [OPTION]... -d DIRECTORIES... 107 | 108 | In the 1st form, copy SRCFILE to DSTFILE. 109 | In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. 110 | In the 4th, create DIRECTORIES. 111 | 112 | Options: 113 | --help display this help and exit. 114 | --version display version info and exit. 115 | 116 | -c (ignored) 117 | -C install only if different (preserve the last data modification time) 118 | -d create directories instead of installing files. 119 | -g GROUP $chgrpprog installed files to GROUP. 120 | -m MODE $chmodprog installed files to MODE. 121 | -o USER $chownprog installed files to USER. 122 | -s $stripprog installed files. 123 | -t DIRECTORY install into DIRECTORY. 124 | -T report an error if DSTFILE is a directory. 125 | 126 | Environment variables override the default commands: 127 | CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG 128 | RMPROG STRIPPROG 129 | " 130 | 131 | while test $# -ne 0; do 132 | case $1 in 133 | -c) ;; 134 | 135 | -C) copy_on_change=true;; 136 | 137 | -d) dir_arg=true;; 138 | 139 | -g) chgrpcmd="$chgrpprog $2" 140 | shift;; 141 | 142 | --help) echo "$usage"; exit $?;; 143 | 144 | -m) mode=$2 145 | case $mode in 146 | *' '* | *' '* | *' 147 | '* | *'*'* | *'?'* | *'['*) 148 | echo "$0: invalid mode: $mode" >&2 149 | exit 1;; 150 | esac 151 | shift;; 152 | 153 | -o) chowncmd="$chownprog $2" 154 | shift;; 155 | 156 | -s) stripcmd=$stripprog;; 157 | 158 | -t) dst_arg=$2 159 | # Protect names problematic for 'test' and other utilities. 160 | case $dst_arg in 161 | -* | [=\(\)!]) dst_arg=./$dst_arg;; 162 | esac 163 | shift;; 164 | 165 | -T) no_target_directory=true;; 166 | 167 | --version) echo "$0 $scriptversion"; exit $?;; 168 | 169 | --) shift 170 | break;; 171 | 172 | -*) echo "$0: invalid option: $1" >&2 173 | exit 1;; 174 | 175 | *) break;; 176 | esac 177 | shift 178 | done 179 | 180 | if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then 181 | # When -d is used, all remaining arguments are directories to create. 182 | # When -t is used, the destination is already specified. 183 | # Otherwise, the last argument is the destination. Remove it from $@. 184 | for arg 185 | do 186 | if test -n "$dst_arg"; then 187 | # $@ is not empty: it contains at least $arg. 188 | set fnord "$@" "$dst_arg" 189 | shift # fnord 190 | fi 191 | shift # arg 192 | dst_arg=$arg 193 | # Protect names problematic for 'test' and other utilities. 194 | case $dst_arg in 195 | -* | [=\(\)!]) dst_arg=./$dst_arg;; 196 | esac 197 | done 198 | fi 199 | 200 | if test $# -eq 0; then 201 | if test -z "$dir_arg"; then 202 | echo "$0: no input file specified." >&2 203 | exit 1 204 | fi 205 | # It's OK to call 'install-sh -d' without argument. 206 | # This can happen when creating conditional directories. 207 | exit 0 208 | fi 209 | 210 | if test -z "$dir_arg"; then 211 | do_exit='(exit $ret); exit $ret' 212 | trap "ret=129; $do_exit" 1 213 | trap "ret=130; $do_exit" 2 214 | trap "ret=141; $do_exit" 13 215 | trap "ret=143; $do_exit" 15 216 | 217 | # Set umask so as not to create temps with too-generous modes. 218 | # However, 'strip' requires both read and write access to temps. 219 | case $mode in 220 | # Optimize common cases. 221 | *644) cp_umask=133;; 222 | *755) cp_umask=22;; 223 | 224 | *[0-7]) 225 | if test -z "$stripcmd"; then 226 | u_plus_rw= 227 | else 228 | u_plus_rw='% 200' 229 | fi 230 | cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; 231 | *) 232 | if test -z "$stripcmd"; then 233 | u_plus_rw= 234 | else 235 | u_plus_rw=,u+rw 236 | fi 237 | cp_umask=$mode$u_plus_rw;; 238 | esac 239 | fi 240 | 241 | for src 242 | do 243 | # Protect names problematic for 'test' and other utilities. 244 | case $src in 245 | -* | [=\(\)!]) src=./$src;; 246 | esac 247 | 248 | if test -n "$dir_arg"; then 249 | dst=$src 250 | dstdir=$dst 251 | test -d "$dstdir" 252 | dstdir_status=$? 253 | else 254 | 255 | # Waiting for this to be detected by the "$cpprog $src $dsttmp" command 256 | # might cause directories to be created, which would be especially bad 257 | # if $src (and thus $dsttmp) contains '*'. 258 | if test ! -f "$src" && test ! -d "$src"; then 259 | echo "$0: $src does not exist." >&2 260 | exit 1 261 | fi 262 | 263 | if test -z "$dst_arg"; then 264 | echo "$0: no destination specified." >&2 265 | exit 1 266 | fi 267 | dst=$dst_arg 268 | 269 | # If destination is a directory, append the input filename; won't work 270 | # if double slashes aren't ignored. 271 | if test -d "$dst"; then 272 | if test -n "$no_target_directory"; then 273 | echo "$0: $dst_arg: Is a directory" >&2 274 | exit 1 275 | fi 276 | dstdir=$dst 277 | dst=$dstdir/`basename "$src"` 278 | dstdir_status=0 279 | else 280 | # Prefer dirname, but fall back on a substitute if dirname fails. 281 | dstdir=` 282 | (dirname "$dst") 2>/dev/null || 283 | expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ 284 | X"$dst" : 'X\(//\)[^/]' \| \ 285 | X"$dst" : 'X\(//\)$' \| \ 286 | X"$dst" : 'X\(/\)' \| . 2>/dev/null || 287 | echo X"$dst" | 288 | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ 289 | s//\1/ 290 | q 291 | } 292 | /^X\(\/\/\)[^/].*/{ 293 | s//\1/ 294 | q 295 | } 296 | /^X\(\/\/\)$/{ 297 | s//\1/ 298 | q 299 | } 300 | /^X\(\/\).*/{ 301 | s//\1/ 302 | q 303 | } 304 | s/.*/./; q' 305 | ` 306 | 307 | test -d "$dstdir" 308 | dstdir_status=$? 309 | fi 310 | fi 311 | 312 | obsolete_mkdir_used=false 313 | 314 | if test $dstdir_status != 0; then 315 | case $posix_mkdir in 316 | '') 317 | # Create intermediate dirs using mode 755 as modified by the umask. 318 | # This is like FreeBSD 'install' as of 1997-10-28. 319 | umask=`umask` 320 | case $stripcmd.$umask in 321 | # Optimize common cases. 322 | *[2367][2367]) mkdir_umask=$umask;; 323 | .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; 324 | 325 | *[0-7]) 326 | mkdir_umask=`expr $umask + 22 \ 327 | - $umask % 100 % 40 + $umask % 20 \ 328 | - $umask % 10 % 4 + $umask % 2 329 | `;; 330 | *) mkdir_umask=$umask,go-w;; 331 | esac 332 | 333 | # With -d, create the new directory with the user-specified mode. 334 | # Otherwise, rely on $mkdir_umask. 335 | if test -n "$dir_arg"; then 336 | mkdir_mode=-m$mode 337 | else 338 | mkdir_mode= 339 | fi 340 | 341 | posix_mkdir=false 342 | case $umask in 343 | *[123567][0-7][0-7]) 344 | # POSIX mkdir -p sets u+wx bits regardless of umask, which 345 | # is incompatible with FreeBSD 'install' when (umask & 300) != 0. 346 | ;; 347 | *) 348 | tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ 349 | trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 350 | 351 | if (umask $mkdir_umask && 352 | exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 353 | then 354 | if test -z "$dir_arg" || { 355 | # Check for POSIX incompatibilities with -m. 356 | # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or 357 | # other-writable bit of parent directory when it shouldn't. 358 | # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. 359 | ls_ld_tmpdir=`ls -ld "$tmpdir"` 360 | case $ls_ld_tmpdir in 361 | d????-?r-*) different_mode=700;; 362 | d????-?--*) different_mode=755;; 363 | *) false;; 364 | esac && 365 | $mkdirprog -m$different_mode -p -- "$tmpdir" && { 366 | ls_ld_tmpdir_1=`ls -ld "$tmpdir"` 367 | test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" 368 | } 369 | } 370 | then posix_mkdir=: 371 | fi 372 | rmdir "$tmpdir/d" "$tmpdir" 373 | else 374 | # Remove any dirs left behind by ancient mkdir implementations. 375 | rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null 376 | fi 377 | trap '' 0;; 378 | esac;; 379 | esac 380 | 381 | if 382 | $posix_mkdir && ( 383 | umask $mkdir_umask && 384 | $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" 385 | ) 386 | then : 387 | else 388 | 389 | # The umask is ridiculous, or mkdir does not conform to POSIX, 390 | # or it failed possibly due to a race condition. Create the 391 | # directory the slow way, step by step, checking for races as we go. 392 | 393 | case $dstdir in 394 | /*) prefix='/';; 395 | [-=\(\)!]*) prefix='./';; 396 | *) prefix='';; 397 | esac 398 | 399 | eval "$initialize_posix_glob" 400 | 401 | oIFS=$IFS 402 | IFS=/ 403 | $posix_glob set -f 404 | set fnord $dstdir 405 | shift 406 | $posix_glob set +f 407 | IFS=$oIFS 408 | 409 | prefixes= 410 | 411 | for d 412 | do 413 | test X"$d" = X && continue 414 | 415 | prefix=$prefix$d 416 | if test -d "$prefix"; then 417 | prefixes= 418 | else 419 | if $posix_mkdir; then 420 | (umask=$mkdir_umask && 421 | $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break 422 | # Don't fail if two instances are running concurrently. 423 | test -d "$prefix" || exit 1 424 | else 425 | case $prefix in 426 | *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; 427 | *) qprefix=$prefix;; 428 | esac 429 | prefixes="$prefixes '$qprefix'" 430 | fi 431 | fi 432 | prefix=$prefix/ 433 | done 434 | 435 | if test -n "$prefixes"; then 436 | # Don't fail if two instances are running concurrently. 437 | (umask $mkdir_umask && 438 | eval "\$doit_exec \$mkdirprog $prefixes") || 439 | test -d "$dstdir" || exit 1 440 | obsolete_mkdir_used=true 441 | fi 442 | fi 443 | fi 444 | 445 | if test -n "$dir_arg"; then 446 | { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && 447 | { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && 448 | { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || 449 | test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 450 | else 451 | 452 | # Make a couple of temp file names in the proper directory. 453 | dsttmp=$dstdir/_inst.$$_ 454 | rmtmp=$dstdir/_rm.$$_ 455 | 456 | # Trap to clean up those temp files at exit. 457 | trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 458 | 459 | # Copy the file name to the temp name. 460 | (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && 461 | 462 | # and set any options; do chmod last to preserve setuid bits. 463 | # 464 | # If any of these fail, we abort the whole thing. If we want to 465 | # ignore errors from any of these, just make sure not to ignore 466 | # errors from the above "$doit $cpprog $src $dsttmp" command. 467 | # 468 | { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && 469 | { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && 470 | { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && 471 | { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && 472 | 473 | # If -C, don't bother to copy if it wouldn't change the file. 474 | if $copy_on_change && 475 | old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && 476 | new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && 477 | 478 | eval "$initialize_posix_glob" && 479 | $posix_glob set -f && 480 | set X $old && old=:$2:$4:$5:$6 && 481 | set X $new && new=:$2:$4:$5:$6 && 482 | $posix_glob set +f && 483 | 484 | test "$old" = "$new" && 485 | $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 486 | then 487 | rm -f "$dsttmp" 488 | else 489 | # Rename the file to the real destination. 490 | $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || 491 | 492 | # The rename failed, perhaps because mv can't rename something else 493 | # to itself, or perhaps because mv is so ancient that it does not 494 | # support -f. 495 | { 496 | # Now remove or move aside any old file at destination location. 497 | # We try this two ways since rm can't unlink itself on some 498 | # systems and the destination file might be busy for other 499 | # reasons. In this case, the final cleanup might fail but the new 500 | # file should still install successfully. 501 | { 502 | test ! -f "$dst" || 503 | $doit $rmcmd -f "$dst" 2>/dev/null || 504 | { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && 505 | { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } 506 | } || 507 | { echo "$0: cannot unlink or rename $dst" >&2 508 | (exit 1); exit 1 509 | } 510 | } && 511 | 512 | # Now rename the file to the real destination. 513 | $doit $mvcmd "$dsttmp" "$dst" 514 | } 515 | fi || exit 1 516 | 517 | trap '' 0 518 | fi 519 | done 520 | 521 | # Local variables: 522 | # eval: (add-hook 'write-file-hooks 'time-stamp) 523 | # time-stamp-start: "scriptversion=" 524 | # time-stamp-format: "%:y-%02m-%02d.%02H" 525 | # time-stamp-time-zone: "UTC" 526 | # time-stamp-end: "; # UTC" 527 | # End: 528 | -------------------------------------------------------------------------------- /md5.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an OpenSSL-compatible implementation of the RSA Data Security, 3 | * Inc. MD5 Message-Digest Algorithm. 4 | * 5 | * Written by Solar Designer in 2001, and placed 6 | * in the public domain. There's absolutely no warranty. 7 | * 8 | * This differs from Colin Plumb's older public domain implementation in 9 | * that no 32-bit integer data type is required, there's no compile-time 10 | * endianness configuration, and the function prototypes match OpenSSL's. 11 | * The primary goals are portability and ease of use. 12 | * 13 | * This implementation is meant to be fast, but not as fast as possible. 14 | * Some known optimizations are not included to reduce source code size 15 | * and avoid compile-time configuration. 16 | */ 17 | 18 | #include 19 | #include "md5.h" 20 | 21 | /* 22 | * The basic MD5 functions. 23 | * 24 | * F is optimized compared to its RFC 1321 definition just like in Colin 25 | * Plumb's implementation. 26 | */ 27 | #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) 28 | #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) 29 | #define H(x, y, z) ((x) ^ (y) ^ (z)) 30 | #define I(x, y, z) ((y) ^ ((x) | ~(z))) 31 | 32 | /* 33 | * The MD5 transformation for all four rounds. 34 | */ 35 | #define STEP(f, a, b, c, d, x, t, s) \ 36 | (a) += f((b), (c), (d)) + (x) + (t); \ 37 | (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ 38 | (a) += (b); 39 | 40 | /* 41 | * SET reads 4 input bytes in little-endian byte order and stores them 42 | * in a properly aligned word in host byte order. 43 | * 44 | * The check for little-endian architectures which tolerate unaligned 45 | * memory accesses is just an optimization. Nothing will break if it 46 | * doesn't work. 47 | */ 48 | #if defined(__i386__) || defined(__vax__) 49 | # define SET(n) (*(MD5_u32plus *)&ptr[(n) * 4]) 50 | # define GET(n) SET(n) 51 | #else 52 | # define SET(n) \ 53 | (ctx->block[(n)] = \ 54 | (MD5_u32plus)ptr[(n) * 4] | \ 55 | ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ 56 | ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ 57 | ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) 58 | # define GET(n) \ 59 | (ctx->block[(n)]) 60 | #endif 61 | 62 | /* 63 | * This processes one or more 64-byte data blocks, but does NOT update 64 | * the bit counters. There're no alignment requirements. 65 | */ 66 | static void *body(MD5_CTX *ctx, void *data, unsigned long size) 67 | { 68 | unsigned char *ptr; 69 | MD5_u32plus a, b, c, d; 70 | MD5_u32plus saved_a, saved_b, saved_c, saved_d; 71 | 72 | ptr = data; 73 | 74 | a = ctx->a; 75 | b = ctx->b; 76 | c = ctx->c; 77 | d = ctx->d; 78 | 79 | do { 80 | saved_a = a; 81 | saved_b = b; 82 | saved_c = c; 83 | saved_d = d; 84 | 85 | /* Round 1 */ 86 | STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) 87 | STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) 88 | STEP(F, c, d, a, b, SET(2), 0x242070db, 17) 89 | STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) 90 | STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) 91 | STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) 92 | STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) 93 | STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) 94 | STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) 95 | STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) 96 | STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) 97 | STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) 98 | STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) 99 | STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) 100 | STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) 101 | STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) 102 | 103 | /* Round 2 */ 104 | STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) 105 | STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) 106 | STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) 107 | STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) 108 | STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) 109 | STEP(G, d, a, b, c, GET(10), 0x02441453, 9) 110 | STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) 111 | STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) 112 | STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) 113 | STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) 114 | STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) 115 | STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) 116 | STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) 117 | STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) 118 | STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) 119 | STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) 120 | 121 | /* Round 3 */ 122 | STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) 123 | STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) 124 | STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) 125 | STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) 126 | STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) 127 | STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) 128 | STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) 129 | STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) 130 | STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) 131 | STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) 132 | STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) 133 | STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) 134 | STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) 135 | STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) 136 | STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) 137 | STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) 138 | 139 | /* Round 4 */ 140 | STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) 141 | STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) 142 | STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) 143 | STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) 144 | STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) 145 | STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) 146 | STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) 147 | STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) 148 | STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) 149 | STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) 150 | STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) 151 | STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) 152 | STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) 153 | STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) 154 | STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) 155 | STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) 156 | 157 | a += saved_a; 158 | b += saved_b; 159 | c += saved_c; 160 | d += saved_d; 161 | 162 | ptr += MD5_BLOCK_SZ; 163 | } while (size -= MD5_BLOCK_SZ); 164 | 165 | ctx->a = a; 166 | ctx->b = b; 167 | ctx->c = c; 168 | ctx->d = d; 169 | 170 | return ptr; 171 | } 172 | 173 | void MD5_Init(MD5_CTX *ctx) 174 | { 175 | ctx->a = 0x67452301; 176 | ctx->b = 0xefcdab89; 177 | ctx->c = 0x98badcfe; 178 | ctx->d = 0x10325476; 179 | 180 | ctx->lo = 0; 181 | ctx->hi = 0; 182 | } 183 | 184 | void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size) 185 | { 186 | MD5_u32plus saved_lo; 187 | unsigned long used, free; 188 | 189 | saved_lo = ctx->lo; 190 | if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) 191 | ctx->hi++; 192 | 193 | ctx->hi += size >> 29; 194 | 195 | used = saved_lo & 0x3f; 196 | 197 | if (used) 198 | { 199 | free = MD5_BLOCK_SZ - used; 200 | 201 | if (size < free) 202 | { 203 | memcpy(&ctx->buffer[used], data, size); 204 | return; 205 | } 206 | 207 | memcpy(&ctx->buffer[used], data, free); 208 | data = (unsigned char *)data + free; 209 | size -= free; 210 | body(ctx, ctx->buffer, MD5_BLOCK_SZ); 211 | } 212 | 213 | if (size >= MD5_BLOCK_SZ) 214 | { 215 | data = body(ctx, data, size & ~(unsigned long)0x3f); 216 | size &= 0x3f; 217 | } 218 | 219 | memcpy(ctx->buffer, data, size); 220 | } 221 | 222 | void MD5_Final(unsigned char *result, MD5_CTX *ctx) 223 | { 224 | unsigned long used, free; 225 | 226 | used = ctx->lo & 0x3f; 227 | 228 | ctx->buffer[used++] = 0x80; 229 | 230 | free = MD5_BLOCK_SZ - used; 231 | 232 | if (free < 8) 233 | { 234 | memset(&ctx->buffer[used], 0, free); 235 | body(ctx, ctx->buffer, MD5_BLOCK_SZ); 236 | used = 0; 237 | free = MD5_BLOCK_SZ; 238 | } 239 | 240 | memset(&ctx->buffer[used], 0, free - 8); 241 | 242 | ctx->lo <<= 3; 243 | ctx->buffer[56] = ctx->lo; 244 | ctx->buffer[57] = ctx->lo >> 8; 245 | ctx->buffer[58] = ctx->lo >> 16; 246 | ctx->buffer[59] = ctx->lo >> 24; 247 | ctx->buffer[60] = ctx->hi; 248 | ctx->buffer[61] = ctx->hi >> 8; 249 | ctx->buffer[62] = ctx->hi >> 16; 250 | ctx->buffer[63] = ctx->hi >> 24; 251 | 252 | body(ctx, ctx->buffer, MD5_BLOCK_SZ); 253 | 254 | result[0] = ctx->a; 255 | result[1] = ctx->a >> 8; 256 | result[2] = ctx->a >> 16; 257 | result[3] = ctx->a >> 24; 258 | result[4] = ctx->b; 259 | result[5] = ctx->b >> 8; 260 | result[6] = ctx->b >> 16; 261 | result[7] = ctx->b >> 24; 262 | result[8] = ctx->c; 263 | result[9] = ctx->c >> 8; 264 | result[10] = ctx->c >> 16; 265 | result[11] = ctx->c >> 24; 266 | result[12] = ctx->d; 267 | result[13] = ctx->d >> 8; 268 | result[14] = ctx->d >> 16; 269 | result[15] = ctx->d >> 24; 270 | 271 | memset(ctx, 0, sizeof(*ctx)); 272 | } 273 | -------------------------------------------------------------------------------- /md5.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an OpenSSL-compatible implementation of the RSA Data Security, 3 | * Inc. MD5 Message-Digest Algorithm. 4 | * 5 | * Written by Solar Designer in 2001, and placed 6 | * in the public domain. See md5.c for more information. 7 | */ 8 | 9 | #ifndef __MD5_H__ 10 | #define __MD5_H__ 11 | 12 | #define MD5_DIGEST_SZ 16 13 | #define MD5_BLOCK_SZ 64 14 | 15 | /* Any 32-bit or wider unsigned integer data type will do */ 16 | typedef unsigned long MD5_u32plus; 17 | 18 | typedef struct { 19 | MD5_u32plus lo, hi; 20 | MD5_u32plus a, b, c, d; 21 | unsigned char buffer[MD5_BLOCK_SZ]; 22 | MD5_u32plus block[MD5_DIGEST_SZ]; 23 | } MD5_CTX; 24 | 25 | extern void MD5_Init(MD5_CTX *ctx); 26 | extern void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size); 27 | extern void MD5_Final(unsigned char *result, MD5_CTX *ctx); 28 | 29 | #endif /* __MD5_H__ */ 30 | -------------------------------------------------------------------------------- /timing_mach.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200809L 2 | #include 3 | 4 | #include 5 | #include "timing_mach.h" 6 | 7 | /* inline functions - maintain ANSI C compatibility */ 8 | #ifdef TIMING_C99 9 | /* *** */ 10 | /* C99 */ 11 | 12 | extern double timespec2secd(const struct timespec *ts_in); 13 | extern void secd2timespec(struct timespec *ts_out, const double sec_d); 14 | extern void timespec_monodiff_lmr(struct timespec *ts_out, 15 | const struct timespec *ts_in); 16 | extern void timespec_monodiff_rml(struct timespec *ts_out, 17 | const struct timespec *ts_in); 18 | extern void timespec_monoadd(struct timespec *ts_out, 19 | const struct timespec *ts_in); 20 | 21 | #endif 22 | 23 | #ifdef __MACH__ 24 | /* ******** */ 25 | /* __MACH__ */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | /* timing struct for osx */ 32 | static struct TimingMach { 33 | mach_timebase_info_data_t timebase; 34 | clock_serv_t cclock; 35 | } timing_mach_g; 36 | 37 | /* mach clock port */ 38 | extern mach_port_t clock_port; 39 | 40 | int timing_mach_init (void) { 41 | int retval = mach_timebase_info(&timing_mach_g.timebase); 42 | if (retval != 0) return retval; 43 | retval = host_get_clock_service(mach_host_self(), 44 | CALENDAR_CLOCK, &timing_mach_g.cclock); 45 | return retval; 46 | } 47 | 48 | int clock_gettime(clockid_t id, struct timespec *tspec) { 49 | mach_timespec_t mts; 50 | int retval = 0; 51 | if (id == CLOCK_REALTIME) { 52 | retval = clock_get_time(timing_mach_g.cclock, &mts); 53 | if (retval != 0) return retval; 54 | tspec->tv_sec = mts.tv_sec; 55 | tspec->tv_nsec = mts.tv_nsec; 56 | } else if (id == CLOCK_MONOTONIC) { 57 | retval = clock_get_time(clock_port, &mts); 58 | if (retval != 0) return retval; 59 | tspec->tv_sec = mts.tv_sec; 60 | tspec->tv_nsec = mts.tv_nsec; 61 | } else { 62 | /* only CLOCK_MONOTOIC and CLOCK_REALTIME clocks supported */ 63 | return -1; 64 | } 65 | return 0; 66 | } 67 | 68 | int clock_nanosleep_abstime(const struct timespec *req) { 69 | struct timespec ts_delta; 70 | int retval = clock_gettime(CLOCK_MONOTONIC, &ts_delta); 71 | if (retval != 0) return retval; 72 | timespec_monodiff_rml (&ts_delta, req); 73 | /* mach does not properly return remainder from nanosleep */ 74 | retval = nanosleep(&ts_delta, NULL); 75 | return retval; 76 | } 77 | 78 | /* __MACH__ */ 79 | /* ******** */ 80 | #endif 81 | 82 | int itimer_start (struct timespec *ts_target, const struct timespec *ts_step) { 83 | int retval = clock_gettime(CLOCK_MONOTONIC, ts_target); 84 | if (retval != 0) return retval; 85 | /* add step size to current monotonic time */ 86 | timespec_monoadd(ts_target, ts_step); 87 | return retval; 88 | } 89 | 90 | int itimer_step (struct timespec *ts_target, const struct timespec *ts_step) { 91 | int retval = clock_nanosleep_abstime(ts_target); 92 | if (retval != 0) return retval; 93 | /* move target along */ 94 | timespec_monoadd(ts_target, ts_step); 95 | return retval; 96 | } 97 | 98 | -------------------------------------------------------------------------------- /timing_mach.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMING_MACH_H 2 | #define TIMING_MACH_H 3 | /* ************* */ 4 | /* TIMING_MACH_H */ 5 | 6 | /* C99 check */ 7 | #if defined(__STDC__) 8 | # if defined(__STDC_VERSION__) 9 | # if (__STDC_VERSION__ >= 199901L) 10 | # define TIMING_C99 11 | # endif 12 | # endif 13 | #endif 14 | 15 | #include 16 | 17 | #define TIMING_GIGA (1000000000) 18 | #define TIMING_NANO (1e-9) 19 | 20 | /* inline functions - maintain ANSI C compatibility */ 21 | #ifndef TIMING_C99 22 | /* this is a bad hack that makes the functions static in a header file. 23 | Compiler warnings about unused functions will plague anyone using this code with ANSI C. */ 24 | #define inline static 25 | #endif 26 | 27 | /* timespec to double */ 28 | inline double timespec2secd(const struct timespec *ts_in) { 29 | return ((double) ts_in->tv_sec) + ((double) ts_in->tv_nsec ) * TIMING_NANO; 30 | } 31 | 32 | /* double sec to timespec */ 33 | inline void secd2timespec(struct timespec *ts_out, const double sec_d) { 34 | ts_out->tv_sec = (time_t) (sec_d); 35 | ts_out->tv_nsec = (long) ((sec_d - (double) ts_out->tv_sec) * TIMING_GIGA); 36 | } 37 | 38 | /* timespec difference (monotonic) left - right */ 39 | inline void timespec_monodiff_lmr(struct timespec *ts_out, 40 | const struct timespec *ts_in) { 41 | /* out = out - in, 42 | where out > in 43 | */ 44 | ts_out->tv_sec = ts_out->tv_sec - ts_in->tv_sec; 45 | ts_out->tv_nsec = ts_out->tv_nsec - ts_in->tv_nsec; 46 | if (ts_out->tv_nsec < 0) { 47 | ts_out->tv_sec = ts_out->tv_sec - 1; 48 | ts_out->tv_nsec = ts_out->tv_nsec + TIMING_GIGA; 49 | } 50 | } 51 | 52 | /* timespec difference (monotonic) right - left */ 53 | inline void timespec_monodiff_rml(struct timespec *ts_out, 54 | const struct timespec *ts_in) { 55 | /* out = in - out, 56 | where in > out 57 | */ 58 | ts_out->tv_sec = ts_in->tv_sec - ts_out->tv_sec; 59 | ts_out->tv_nsec = ts_in->tv_nsec - ts_out->tv_nsec; 60 | if (ts_out->tv_nsec < 0) { 61 | ts_out->tv_sec = ts_out->tv_sec - 1; 62 | ts_out->tv_nsec = ts_out->tv_nsec + TIMING_GIGA; 63 | } 64 | } 65 | 66 | /* timespec addition (monotonic) */ 67 | inline void timespec_monoadd(struct timespec *ts_out, 68 | const struct timespec *ts_in) { 69 | /* out = in + out */ 70 | ts_out->tv_sec = ts_out->tv_sec + ts_in->tv_sec; 71 | ts_out->tv_nsec = ts_out->tv_nsec + ts_in->tv_nsec; 72 | if (ts_out->tv_nsec >= TIMING_GIGA) { 73 | ts_out->tv_sec = ts_out->tv_sec + 1; 74 | ts_out->tv_nsec = ts_out->tv_nsec - TIMING_GIGA; 75 | } 76 | } 77 | 78 | #ifndef TIMING_C99 79 | #undef inline 80 | #endif 81 | 82 | #ifdef __MACH__ 83 | /* ******** */ 84 | /* __MACH__ */ 85 | 86 | /* only CLOCK_REALTIME and CLOCK_MONOTONIC are emulated */ 87 | #ifndef CLOCK_REALTIME 88 | # define CLOCK_REALTIME 0 89 | #endif 90 | #ifndef CLOCK_MONOTONIC 91 | # define CLOCK_MONOTONIC 1 92 | #endif 93 | 94 | /* typdef POSIX clockid_t */ 95 | //typedef int clockid_t; 96 | 97 | /* initialize mach timing */ 98 | int timing_mach_init (void); 99 | 100 | /* clock_gettime - emulate POSIX */ 101 | int clock_gettime(const clockid_t id, struct timespec *tspec); 102 | 103 | /* clock_nanosleep for CLOCK_MONOTONIC and TIMER_ABSTIME */ 104 | int clock_nanosleep_abstime(const struct timespec *req); 105 | 106 | /* __MACH__ */ 107 | /* ******** */ 108 | #else 109 | /* ***** */ 110 | /* POSIX */ 111 | 112 | /* clock_nanosleep for CLOCK_MONOTONIC and TIMER_ABSTIME */ 113 | # define clock_nanosleep_abstime(req) \ 114 | clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, (req), NULL) 115 | 116 | /* POSIX */ 117 | /* ***** */ 118 | #endif 119 | 120 | /* timer functions that make use of clock_nanosleep_abstime 121 | For POSIX systems, it is recommended to use POSIX timers and signals. 122 | For Mac OSX (mach), there are no POSIX timers so these functions are very helpful. 123 | */ 124 | 125 | /* Sets absolute time ts_target to ts_step after current time */ 126 | int itimer_start (struct timespec *ts_target, const struct timespec *ts_step); 127 | 128 | /* Nanosleeps to ts_target then adds ts_step to ts_target for next iteration */ 129 | int itimer_step (struct timespec *ts_target, const struct timespec *ts_step); 130 | 131 | /* TIMING_MACH_H */ 132 | /* ************* */ 133 | #endif 134 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "utils.h" 6 | 7 | /** 8 | * generateRandomNonce - Generates a random nonce. 9 | * @nonce: The array to store the generated nonce. 10 | */ 11 | void generateRandomNonce(unsigned char nonce[16]) 12 | { 13 | srand((unsigned int)time(NULL)); 14 | for (int i = 0; i < 16; ++i) 15 | { 16 | nonce[i] = (unsigned char)rand(); 17 | } 18 | } 19 | /** 20 | * getHexRepresentation - Converts a buffer to its hexadecimal representation. 21 | * @buffer: Pointer to the input buffer. 22 | * @size: Size of the input buffer. 23 | * @hexString: Pointer to the output string for the hexadecimal representation. 24 | * 25 | * This function converts the content of the input buffer to its hexadecimal 26 | * representation and stores it in the output string. The resulting string is 27 | * null-terminated. 28 | */ 29 | void getHexRepresentation(const unsigned char *buffer, size_t size, char *hexString) 30 | { 31 | for (size_t i = 0; i < size; ++i) 32 | { 33 | sprintf(hexString + i * 2, "%02x", buffer[i]); 34 | } 35 | } 36 | /** 37 | * isStringNotEmpty - Checks if a C string is not empty. 38 | * @str: Pointer to the C string to be checked. 39 | * 40 | * This function determines whether the given C string is not empty. 41 | * It returns 1 if the string is not empty and not NULL, and 0 otherwise. 42 | * 43 | * Returns: 1 if the string is not empty, 0 otherwise. 44 | */ 45 | int isStringNotEmpty(const char *str) 46 | { 47 | return (str != NULL && strlen(str) > 0); 48 | } 49 | /** 50 | * isauth - Check authentication based on user and hash. 51 | * @opt_authuser: User-provided authentication username. 52 | * @username: Server's expected username for comparison. 53 | * @receivedhash: User-provided received hash for comparison. 54 | * @serverdigest: Server's expected hash for comparison. 55 | * 56 | * It compares opt_authuser with username and receivedhash with serverdigest. 57 | * Returns: 1 if both username and hash match, 0 otherwise. 58 | */ 59 | int isauth(const char *opt_authuser, const unsigned char *username, 60 | const char *receivedhash, const char *serverdigest) 61 | { 62 | // Check if opt_authuser is not empty and equal to username 63 | int userMatch = (opt_authuser != NULL && *opt_authuser != '\0' && 64 | strncmp(opt_authuser, (const char *)username, USERNAME_SIZE) == 0); 65 | 66 | // Check if receivedhash is not empty and equal to serverdigest 67 | int hashMatch = (receivedhash != NULL && *receivedhash != '\0' && 68 | strcmp(receivedhash, serverdigest) == 0); 69 | 70 | // Return 1 if both conditions are true, 0 otherwise 71 | return (userMatch && hashMatch); 72 | } -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | #define USERNAME_SIZE 16 4 | #define HASH_SIZE 32 5 | #define AUTHSTR_SIZE 32 6 | // function to generate random Nonce to send to client for md5 hash 7 | void generateRandomNonce(unsigned char nonce[16]); 8 | // This function converts the content of the input buffer to its hexadecimal 9 | void getHexRepresentation(const unsigned char *buffer, size_t size, char *hexString); 10 | // Checks if a C string is not empty. 11 | int isStringNotEmpty(const char *str); 12 | // This function checks authentication based on the provided username and hash. 13 | int isauth(const char *opt_authuser, const unsigned char *username, const char *receivedhash, const char *serverdigest); 14 | #endif --------------------------------------------------------------------------------