2 ##############################################################################
3 #Copyright (c) 2017 Orange, Inc. and others. All rights reserved.
5 # This program and the accompanying materials are made available under the
6 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
7 # and is available at http://www.eclipse.org/legal/epl-v10.html
8 ##############################################################################
10 # debian dependencies: apt-get install libnet-openssh-perl libio-pty-perl
16 #use diagnostics; #uncomment this line for more details when encountering warnings
19 use Getopt::Long qw(:config no_ignore_case bundling);
25 $host, $help, $usage, $proxy_port,
26 $login, $password, $kidpid, $ssh_subsocket,
27 $simpleproxy, $pid, $ssh_handle, $client,
28 $server, $capabilities, $hello_message, $verbose
33 "p|port=i" => \$proxy_port,
34 "s|simpleproxy" => \$simpleproxy,
35 "v|verbose" => \$verbose,
36 "C|capabilities=s" => \$capabilities
39 USAGE: netconf_TCP_SSH_hijackproxy.pl [-h|--help] [-p|--port <port_number>] [-s|--simpleproxy] [-v|--verbose] [-C|--capabilities <custom_hello_file.xml>] <[login[:password]@]host[:port]> [login] [password]
41 Netconf SSH to TCP proxy to debug netconf exchanges.
42 It listens to connections in clear TCP to the given port. When a TCP connection demand is received,
43 it establishes a netconf SSH encrypted connection to the host in argument. Netconf rpcs and replies
44 are then proxified between both ends.
45 By default, exchanges are altered according to the rules specified inside this script and easily
46 modifiable. This behaviour can be disabled with the '-s' option.
47 For more convenience, the server hello handshake can also alternatively be replaced by the content
48 of an external file instead of writing specific rules.
52 -h or --help print this help
53 -p or --port use the given port number for listening TCP clients, default=9000
54 -s or --simpleproxy simple proxy mode, do not alter any exchanges
55 -v or --verbose display exchanges to STDOUT
56 -C or --capabilities do not relay the real server hello message to the client
57 but replace it by the one provided in the following file
67 unless ( @ARGV >= 1 ) {
72 ( $host, $login, $password ) = @ARGV;
74 #netconf default port is no 22 but 830
75 if ( $host !~ /:[0-9]+$/ ) { $host .= ':830'; }
77 if ( !defined($proxy_port) ) { $proxy_port = 9000; }
79 my $connection_string = $host;
81 $connection_string = $login . ":" . $password . "@" . $connection_string;
84 $connection_string = $login . "@" . $connection_string;
87 #retrieving hello custom file if any
88 if ( ( !defined($simpleproxy) ) && ( defined($capabilities) ) ) {
89 open( CAPABILITIES, '<', $capabilities )
90 or die("can not open $capabilities");
91 while (<CAPABILITIES>) {
94 chop $hello_message; # removing EOF
95 $hello_message .= "]]>]]>";
99 # the following regex are used to modify some part of the server messages relayed to the client
100 # you can adapt it to your needs, some examples have been commented.
103 # replace oo-device v1.2 by v1.2.1
104 # 'module=org-openroadm-device&revision=2016-10-14.*<\/capability>'=>'s/&revision=2016-10-14/&revision=2017-02-06/',
105 # '<schema><identifier>org-openroadm-device<\/identifier><version>2016-10-14'=>'s@<schema><identifier>org-openroadm-device</identifier><version>2016-10-14@<schema><identifier>org-openroadm-device</identifier><version>2017-02-06@',
106 # remove all deviations found
107 # '&deviations=.*<\/capability>'=>'s@&deviations=.*</capability>@</capability>@',
108 # add the ietf-netconf capability to the hello handshake - without it, ODL netconf mountpoints can not work
109 # '<\/capabilities>'=>'s@</capabilities>@\n<capability>urn:ietf:params:xml:ns:yang:ietf-netconf?module=ietf-netconf&revision=2011-06-01</capability>\n</capabilities>@',
110 # add the right notifications capabilities to the hello handshake + provide another solution for the ietf-netconf capability
111 '<\/capabilities>' =>
112 's@</capabilities>@\n<capability>urn:ietf:params:xml:ns:netmod:notification?module=nc-notifications&revision=2008-07-14</capability>\n<capability>urn:ietf:params:xml:ns:netconf:notification:1.0?module=notifications&revision=2008-07-14</capability>\n<capability>urn:ietf:params:xml:ns:netconf:base:1.0?module=ietf-netconf&revision=2011-06-01</capability>\n</capabilities>@'
115 if ( defined($simpleproxy) ) { %regex_hash = (); }
117 my %compiled_regex_hash;
118 foreach my $keyword ( keys %regex_hash ) {
119 eval( '$compiled_regex_hash{$keyword}= qr/' . $keyword . '/;' );
122 $server = IO::Socket::INET->new(
124 LocalPort => $proxy_port,
128 die "can't setup server" unless $server;
129 print STDERR "[Proxy server $0 accepting clients: Ctrl-C to stop]\n";
131 while ( $client = $server->accept() ) {
132 $client->autoflush(1);
133 my $hostinfo = gethostbyaddr( $client->peeraddr );
134 printf STDERR "[Incoming connection from %s]\n",
135 $hostinfo->name || $client->peerhost;
137 print STDERR "[relaying to " . $connection_string . "]\n";
139 $ssh_handle = Net::OpenSSH->new(
141 master_opts => [ -o => 'StrictHostKeyChecking=no' ],
143 kill_ssh_on_timeout => 500
146 #netconf requires a specific socket
147 ( $ssh_subsocket, $pid ) =
148 $ssh_handle->open2socket( { ssh_opts => '-s' }, 'netconf' );
149 die "can't establish connection: exiting\n" unless defined($ssh_subsocket);
151 print STDERR "[Connected]\n";
153 # split the program into two processes, identical twins
154 die "can't fork: $!" unless defined( $kidpid = fork() );
158 # the if{} block runs only in the parent process (server output relayed to the client)
161 # copy the socket to standard output
164 if ( defined($hello_message) ) {
166 #retrieve the server hello but do not relay it
167 while ( my $nread = sysread( $ssh_subsocket, $buf, 400 ) ) {
168 $ssh_subsocket->flush();
169 if ( $buf =~ /]]>]]>/ ) { last }
172 #send a custom hello message instead
173 print $client $hello_message;
174 if ( defined($verbose) ) { print STDOUT $hello_message; }
177 #while (<$ssh_subsocket>) {
178 #buffer seems not totally flushed when using the usual syntax above (nor when using autoflush)
179 while ( my $nread = sysread( $ssh_subsocket, $buf, 400 ) ) {
180 foreach my $keyword ( keys %regex_hash ) {
181 if ( $buf =~ $compiled_regex_hash{$keyword} ) {
182 print STDERR 'found regex '
187 eval( '$buf =~ ' . $regex_hash{$keyword} . ';' );
188 print STDERR $buf . "\n'\n";
192 $ssh_subsocket->flush();
193 if ( defined($verbose) ) { print STDOUT $buf; }
197 kill( "TERM", $kidpid ); # send SIGTERM to child
200 # the else{} block runs only in the child process (client input relayed to the server)
203 $ssh_subsocket->autoflush(1);
204 sleep 1; # wait needed for ensuring STDOUT buffer is not melt
207 #while (defined (my $buf = <$client>)) {
208 #usual syntax above used in verbose mode results into flush problems
209 while ( my $nread = sysread( $client, $buf, 400 ) ) {
210 print $ssh_subsocket $buf;
212 if ( defined($verbose) ) { print STDOUT $buf; }
223 kill( "TERM", $kidpid ); # send SIGTERM to child