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 dependecies: apt-get install libnet-openssh-perl libio-pty-perl
15 #use diagnostics; #uncomment this line for more details when encountering warnings
18 use Getopt::Long qw(:config no_ignore_case bundling);
23 my ($host, $help, $usage, $proxy_port, $login, $password, $kidpid, $ssh_subsocket, $simpleproxy,
24 $pid, $ssh_handle, $client, $server, $capabilities, $hello_message, $verbose);
28 "p|port=i"=>\$proxy_port,
29 "s|simpleproxy" =>\$simpleproxy,
30 "v|verbose" =>\$verbose,
31 "C|capabilities=s"=>\$capabilities
34 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]
36 Netconf SSH to TCP proxy to debug netconf exchanges.
37 It listens to connections in clear TCP to the given port. When a TCP connection demand is received,
38 it establishes a netconf SSH encrypted connection to the host in argument. Netconf rpcs and replies
39 are then proxified between both ends.
40 By default, exchanges are altered according to the rules specified inside this script and easily
41 modifiable. This behaviour can be disabled with the '-s' option.
42 For more convenience, the server hello handshake can also alternatively be replaced by the content
43 of an external file rather instead of writing specific rules.
47 -h or --help print this help
48 -p or --port use the given port number for listening TCP clients, default=9000
49 -s or --simpleproxy simple proxy mode, do not alter any exchanges
50 -v or --verbose display exchanges to STDOUT
51 -C or --capabilities do not relay the real server hello message to the client
52 but replace it by the one provided in the following file
67 ($host, $login, $password) = @ARGV;
69 #netconf default port is no 22 but 830
70 if ($host !~ /:[0-9]+$/) { $host.=':830'; }
72 if (!defined($proxy_port)) { $proxy_port = 9000; }
74 my $connection_string=$host;
76 $connection_string=$login.":".$password."@".$connection_string;
78 $connection_string=$login."@".$connection_string;
81 #retrieving hello custom file if any
82 if ((!defined ($simpleproxy))&&(defined ($capabilities))) {
83 open(CAPABILITIES,'<',$capabilities) or die ("can not open $capabilities") ;
84 while (<CAPABILITIES>) {
87 chop $hello_message; # removing EOF
88 $hello_message.="]]>]]>";
92 # the following regex are used to modify some part of the server messages relayed to the client
93 # you can adapt it to your needs, some examples have been commented.
95 # replace oo-device v1.2 by v1.2.1
96 # 'module=org-openroadm-device&revision=2016-10-14.*<\/capability>'=>'s/&revision=2016-10-14/&revision=2017-02-06/',
97 # '<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@',
98 # remove all deviations found
99 # '&deviations=.*<\/capability>'=>'s@&deviations=.*</capability>@</capability>@',
100 # add the ietf-netconf capability to the hello handshake - without it, ODL netconf mountpoints can not work
101 # '<\/capabilities>'=>'s@</capabilities>@\n<capability>urn:ietf:params:xml:ns:yang:ietf-netconf?module=ietf-netconf&revision=2011-06-01</capability>\n</capabilities>@',
102 # add the right notifications capabilities to the hello handshake + provide another solution for the ietf-netconf capability
103 '<\/capabilities>'=>'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>@'
106 if (defined ($simpleproxy)) { %regex_hash=(); }
108 my %compiled_regex_hash;
109 foreach my $keyword (keys %regex_hash){
110 eval ('$compiled_regex_hash{$keyword}= qr/'.$keyword.'/;');
113 $server = IO::Socket::INET->new( Proto => 'tcp',
114 LocalPort => $proxy_port,
117 die "can't setup server" unless $server;
118 print STDERR "[Proxy server $0 accepting clients: Ctrl-C to stop]\n";
121 while ($client = $server->accept()) {
122 $client->autoflush(1);
123 my $hostinfo = gethostbyaddr($client->peeraddr);
124 printf STDERR "[Incoming connection from %s]\n", $hostinfo->name || $client->peerhost;
127 print STDERR "[relaying to ".$connection_string."]\n";
129 $ssh_handle = Net::OpenSSH->new($connection_string,
130 master_opts => [-o => 'StrictHostKeyChecking=no'],
131 timeout => 500, kill_ssh_on_timeout => 500);
133 #netconf requires a specific socket
134 ($ssh_subsocket, $pid) = $ssh_handle->open2socket({ssh_opts => '-s'}, 'netconf');
135 die "can't establish connection: exiting\n" unless defined($ssh_subsocket);
137 print STDERR "[Connected]\n";
139 # split the program into two processes, identical twins
140 die "can't fork: $!" unless defined($kidpid = fork());
144 # the if{} block runs only in the parent process (server output relayed to the client)
147 # copy the socket to standard output
150 if (defined ($hello_message)) {
151 #retrieve the server hello but do not relay it
152 while (my $nread = sysread($ssh_subsocket,$buf,400)) {
153 $ssh_subsocket->flush();
154 if ($buf =~ /]]>]]>/) { last };
156 #send a custom hello message instead
157 print $client $hello_message;
158 if (defined($verbose)) { print STDOUT $hello_message; }
161 #while (<$ssh_subsocket>) {
162 #buffer seems not totally flushed when using the usual syntax above (nor when using autoflush)
163 while (my $nread = sysread($ssh_subsocket,$buf,400)) {
164 foreach my $keyword (keys %regex_hash){
165 if($buf =~ $compiled_regex_hash{$keyword}){
166 print STDERR 'found regex '.$keyword.": replacing '\n".$buf."\n' by '\n";
167 eval ('$buf =~ '.$regex_hash{$keyword}.';');
168 print STDERR $buf."\n'\n";
172 $ssh_subsocket->flush();
173 if (defined($verbose)) { print STDOUT $buf; }
177 kill("TERM", $kidpid); # send SIGTERM to child
179 # the else{} block runs only in the child process (client input relayed to the server)
182 $ssh_subsocket->autoflush(1);
183 sleep 1; # wait needed for ensuring STDOUT buffer is not melt
186 #while (defined (my $buf = <$client>)) {
187 #usual syntax above used in verbose mode results into flush problems
188 while (my $nread = sysread($client,$buf,400)) {
189 print $ssh_subsocket $buf;
191 if (defined($verbose)) { print STDOUT $buf; }
201 kill("TERM", $kidpid); # send SIGTERM to child