goexpect, fastest way to check if telnet is open - go

I'm using goexpect for connection to multiple wi-fi access points.
For some of them I need to use telnet, and SSH for others.
So, I need fastest way to check if telnet is open for some IP.
Code now looks like
e, _, err := expect.Spawn(fmt.Sprintf("telnet %s", ip), -1)
res, _, err := e.Expect(userRE, timeout) // we expect user prompt
if err != nil {
// if timeout, pass control to code block which handle SSH connection
}
I suppose there is a better and faster way to tell if telnet is open.
Any suggestions?

This worked for me after getting the github.com/reiver/go-telnet package:
package main
import (
"fmt"
"github.com/reiver/go-telnet"
)
func main() {
address := "127.0.0.1:8080"
_, err := telnet.DialTo(address)
if err != nil {
fmt.Println(err)
fmt.Println("Telnet closed")
} else {
fmt.Println("Telnet open")
}
}

Related

How to send multiple commands in one sesssion but save outputs separately

My code is supposed to SSH to a remote-host (let’s say Routers) and run multiple commands on the remote-host and return the outputs.
The code attached is simplified and has three parts:
Main function: Reads list of commands and then by using the ExecCommands function dials/ssh to a remote-host to execute the commands.
ExecCommands function takes the remote-host IP, list of commands and SSH ClientConfig that is used for SSH. Then it dials to the IP and run the commands one-by-one. At the end, returns the output of all commands in only one string
InsecureClientConfig function that actually doesn’t do much except creating a SSH ClientConfig which is used for ExecCommands function
This program works well when I just want to apply some commands or config and save the wholes result. I mean ExecCommands takes the bunch of commands, push all of them to the remote-host and returns (or saves) the whole output of applied commands in one string as output.
Problem:
I cannot process the output of each command individually. For example, assume that I apply CMD1, CMD2, CMD3, … to the remote-host#1 by using ExecCommands function. Since it gives me back the whole output in one string, it is hard to find which output belongs to which CMD
Goal:
Modify or re-design ExecCommands function to the way that it provides separate output for each command it applies. It means if for remote-host#1 it applies 10 commands, I should have 10 separate strings as output.
Conditions/Restrictions:
I can not create any extra session for commands and must apply all commands in the first SSH session I created, i.e. cannot create multiple Sessions and use Run, Shell, Output, Start function in SSH package
No re-authentication is allowed. For example, I have only a single one-time-password that can be used for all remote-hosts.
Remote hosts don't support "echo" like commands similar to what you have in Linux
The remote-hosts dont’s support any type of APIs
Points:
Main focus is the function ExecCommands. I put a simplified version of the whole code to give an idea
I am using stdout, err := session.StdoutPipe() to run multiple commands which means -as pipe - it's Reader only is possible to be read when the job is done.
An option is to use Session.Stdout and Session.Stdin inside of the for loop in ExecCommands function. Tried but was not successful.
Code:
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"time"
"golang.org/x/crypto/ssh"
)
func main() {
// List of the commands should be sent to the devices
listCMDs := []string{
"set cli op-command-xml-output on",
"test routing fib-lookup virtual-router default ip 1.1.1.1",
"test routing fib-lookup virtual-router default ip 2.2.2.2",
"show interface ethernet1/1",
"show interface ethernet1/2",
"test security-policy-match protocol 6 source 1.1.1.1 destination 2.2.2.2 destination-port 443 from ZONE1 to ZONE2",
"test security-policy-match protocol 6 source 10.0.0.1 destination 10.0.2.1 destination-port 443 from ZONE1 to ZONE2",
"exit",
}
sshconfig := InsecureClientConfig("admin", "admin")
s, err := ExecCommands("192.168.1.250", listCMDs, sshconfig)
fmt.Println(s, err)
}
// ExecCommands ...
func ExecCommands(ipAddr string, commands []string, sshconfig *ssh.ClientConfig) (string, error) {
// Gets IP, credentials and config/commands, SSH Config (Timeout, Ciphers, ...) and returns
// output of the device as "string" and an error. If error == nil, means program was able to SSH with no issue
// Creating outerr as Output Error.
outerr := errors.New("nil")
outerr = nil
// Creating Output as String
var outputStr string
// Dial to the remote-host
client, err := ssh.Dial("tcp", ipAddr+":22", sshconfig)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Create sesssion
session, err := client.NewSession()
if err != nil {
log.Fatal(err)
}
defer session.Close()
// StdinPipee() returns a pipe that will be connected to the remote command's standard input when the command starts.
// StdoutPipe() returns a pipe that will be connected to the remote command's standard output when the command starts.
stdin, err := session.StdinPipe()
if err != nil {
log.Fatal(err)
}
stdout, err := session.StdoutPipe()
if err != nil {
log.Fatal(err)
}
// Start remote shell
err = session.Shell()
if err != nil {
log.Fatal(err)
}
// Send the commands to the remotehost one by one.
for _, cmd := range commands {
_, err := stdin.Write([]byte(cmd + "\n"))
if err != nil {
log.Fatal(err)
}
}
// Wait for session to finish
err = session.Wait()
if err != nil {
log.Fatal(err)
}
strByte, _ := ioutil.ReadAll(stdout)
outputStr = string(strByte)
return outputStr, outerr
}
// InsecureClientConfig ...
func InsecureClientConfig(userStr, passStr string) *ssh.ClientConfig {
SSHconfig := &ssh.ClientConfig{
User: userStr,
Timeout: 5 * time.Second,
Auth: []ssh.AuthMethod{ssh.Password(passStr)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Config: ssh.Config{
Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-cbc", "aes192-cbc",
"aes256-cbc", "3des-cbc", "des-cbc"},
KeyExchanges: []string{"diffie-hellman-group1-sha1",
"diffie-hellman-group-exchange-sha1",
"diffie-hellman-group14-sha1"},
},
}
return SSHconfig
}
This works properly:
package main
import (
"bufio"
"errors"
"fmt"
"log"
"time"
"golang.org/x/crypto/ssh"
)
func main() {
// List of the commands should be sent to the devices
listCMDs := []string{
"set cli op-command-xml-output on\n",
"test routing fib-lookup virtual-router default ip 1.1.1.1\n",
"test routing fib-lookup virtual-router default ip 2.2.2.2\n",
"show interface ethernet1/1\n",
"show interface ethernet1/2\n",
"test security-policy-match protocol 6 source 1.1.1.1 destination 2.2.2.2 destination-port 443 from ZONE1 to ZONE2\n",
"test security-policy-match protocol 6 source 10.0.0.1 destination 10.0.2.1 destination-port 443 from ZONE1 to ZONE2\n",
"exit",
}
sshconfig := InsecureClientConfig("admin", "Ghazanfar1!")
s, _ := ExecCommands("192.168.1.249", listCMDs, sshconfig)
for _, item := range s {
fmt.Println(item)
fmt.Println("-------------------------------")
}
}
// ExecCommands ...
func ExecCommands(ipAddr string, commands []string, sshconfig *ssh.ClientConfig) ([]string, error) {
// Gets IP, credentials and config/commands, SSH Config (Timeout, Ciphers, ...) and returns
// output of the device as "string" and an error. If error == nil, means program was able to SSH with no issue
// Creating outerr as Output Error.
outerr := errors.New("nil")
outerr = nil
// Creating Output as String
var outputStr []string
var strTmp string
// Dial to the remote-host
client, err := ssh.Dial("tcp", ipAddr+":22", sshconfig)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Create sesssion
session, err := client.NewSession()
if err != nil {
log.Fatal(err)
}
defer session.Close()
// StdinPipee() returns a pipe that will be connected to the remote command's standard input when the command starts.
// StdoutPipe() returns a pipe that will be connected to the remote command's standard output when the command starts.
stdin, err := session.StdinPipe()
if err != nil {
log.Fatal(err)
}
stdout, err := session.StdoutPipe()
if err != nil {
log.Fatal(err)
}
// Start remote shell
err = session.Shell()
if err != nil {
log.Fatal(err)
}
stdinLines := make(chan string)
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
stdinLines <- scanner.Text()
}
if err := scanner.Err(); err != nil {
log.Printf("scanner failed: %v", err)
}
close(stdinLines)
}()
// Send the commands to the remotehost one by one.
for i, cmd := range commands {
_, err := stdin.Write([]byte(cmd + "\n"))
if err != nil {
log.Fatal(err)
}
if i == len(commands)-1 {
_ = stdin.Close() // send eof
}
// wait for command to complete
// we'll assume the moment we've gone 1 secs w/o any output that our command is done
timer := time.NewTimer(0)
InputLoop:
for {
timer.Reset(time.Second)
select {
case line, ok := <-stdinLines:
if !ok {
log.Println("Finished processing")
break InputLoop
}
strTmp += line
strTmp += "\n"
case <-timer.C:
break InputLoop
}
}
outputStr = append(outputStr, strTmp)
//log.Printf("Finished processing %v\n", cmd)
strTmp = ""
}
// Wait for session to finish
err = session.Wait()
if err != nil {
log.Fatal(err)
}
return outputStr, outerr
}
// InsecureClientConfig ...
func InsecureClientConfig(userStr, passStr string) *ssh.ClientConfig {
SSHconfig := &ssh.ClientConfig{
User: userStr,
Timeout: 5 * time.Second,
Auth: []ssh.AuthMethod{ssh.Password(passStr)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Config: ssh.Config{
Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-cbc", "aes192-cbc",
"aes256-cbc", "3des-cbc", "des-cbc"},
KeyExchanges: []string{"diffie-hellman-group1-sha1",
"diffie-hellman-group-exchange-sha1",
"diffie-hellman-group14-sha1"},
},
}
return SSHconfig
}
Since you have limited number of commands to run on special hardwares and you know the pattern of the each command's output, you may use strings.Split or regexp to split the output.
And if you do not have echo command, but know any command with fast response with unique output pattern, then you may replace it with echo command in the following example (number 2).
Since a session only accepts one call to Run, Start, Shell, Output, or CombinedOutput, and you do not want to start a new session per command:
The key is to use a strings.Builder and empty it using sb.Reset() befor sending the command, and using io.Copy to copy concurrently the session's stdout into strings.Builder (assuming you do not need session's stderr):
sb := new(strings.Builder)
go io.Copy(sb, stdout)
This works if you know how much to wait for each command (tested):
sb := new(strings.Builder)
go io.Copy(sb, stdout)
commands := []string{"uname -a", "sleep 1", "pwd", "whoami", "exit"}
wait := []time.Duration{10, 1200, 20, 10, 10} // * time.Millisecond
ans := []string{}
time.Sleep(10 * time.Millisecond) // wait for the ssh greetings
// Send the commands to the remotehost one by one.
for i, cmd := range commands {
sb.Reset()
fmt.Println("*** command:\t", cmd)
_, err := stdin.Write([]byte(cmd + "\n"))
if err != nil {
log.Fatal(err)
}
time.Sleep(wait[i] * time.Millisecond) // wait for the command to finish
s := sb.String()
fmt.Println("*** response:\t", s)
ans = append(ans, s)
}
Using string delimiter and strings.Split (Note: You may replace echo with any fast command with known output pattern):
sb := new(strings.Builder)
go io.Copy(sb, stdout)
commands := []string{"uname -a", "sleep 1", "pwd", "whoami"}
delim := "********--------========12345678"
for _, cmd := range commands {
_, err = stdin.Write([]byte("echo " + delim + "\n"))
if err != nil {
log.Fatal(err)
}
_, err := stdin.Write([]byte(cmd + "\n"))
if err != nil {
log.Fatal(err)
}
}
_, err = stdin.Write([]byte("exit\n"))
if err != nil {
log.Fatal(err)
}
err = session.Wait() // Wait for session to exit
if err != nil {
log.Fatal(err)
}
ans := strings.Split(sb.String(), delim)
ans = ans[1:] // remove ssh greetings
Check this out: https://github.com/yahoo/vssh
You can set sessions to how many commands you need to run concurrently then send each command to remote host through run method and get the result individually!

Can`t figure out how to use buffers with binary web socket

everyone!
I'm trying to get my go code work with openstack serial console. It`s exposed via web socket. And i have problems with it.
I found gorrilla websocket lib (which is great) and took this example as a reference
With a few tweaks, now i have a code like this:
package main
import (
"log"
"net/url"
"os"
"os/signal"
"time"
"net/http"
"github.com/gorilla/websocket"
)
func main() {
DialSettings := &websocket.Dialer {
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
Subprotocols: []string{"binary",},
ReadBufferSize: 4096,
WriteBufferSize: 4096,
}
log.SetFlags(0)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u, _ := url.Parse("ws://172.17.0.64:6083/?token=d1763f2b-3466-424c-aece-6aeea2a733d5") //websocket url as it outputs from 'nova get-serial-console test' cmd
log.Printf("connecting to %s", u.String())
c, _, err := DialSettings.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("%s", message)
}
}()
c.WriteMessage(websocket.TextMessage, []byte("\n")) //just to force output to console
for {
select {
case <-done:
return
case <-interrupt:
log.Println("interrupt")
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
And i get output like this:
connecting to ws://172.17.0.64:6083/?token=d1763f2b-3466-424c-aece-6aeea2a733d5
CentOS Linux 7
(C
ore)
K
erne
l
3.10.0-862.el7.x86_64
o
n an
x
86_64
centos
-test login:
Total mess...
I think it's because i recieve just a chunks of bytes with no way to delimit them. I need some buffer to store them and when do something like bufio.ReadLine. But i'm not most experienced go programmer, and i run out of ideas how to do this. At the end i just need strings to work with.
The log package writes each log message on a separate line. If the log message does not end with a newline, then the log package will add one.
These extra newlines are garbling the output. To fix the output, replace the call to log.Printf("%s", message) with a function that does not add newlines to the output. Here are some options:
Write the message to stderr (same destination as default log package config):
os.Stderr.Write(message)
Write the message to stdout (a more conventional location to write program output):
os.Stdout.Write(message)

How can I keep reading using net Conn Read method

I'm creating a simple chat server as a personal project to learn net package and some concurrency in go. My 1st idea is to make the server print whatever is send using nc command echo -n "hello" | nc -w1 -4 localhost 2016 -p 61865. However after the 1st read my code ignores the subsequent messages.
func (s *Server) messageReader(conn net.Conn) {
defer conn.Close()
buffer := make([]byte, 1024)
for {
//read buff
blen, err := conn.Read(buffer)
if err != nil {
log.Fatal(err)
}
message := string(buffer[:blen])
if message == "/quit" {
fmt.Println("quit command received. Bye.")
return
}
if blen > 0 {
fmt.Println(message)
buffer = buffer[:0]
}
}
}
// Run Start up the server. Manages join and leave chat
func (s *Server) Run() {
// Listen on port TCP 2016
listener, err := net.Listen("tcp", ":2016")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
for {
//wait for connection
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
go s.messageReader(conn)
}
}
If I send a new message from a new client it prints without problems but if I send another one it does nothing. What am I missing do I need to reset the Conn or close it and spawn a new one?
After printing your message, you slice buffer down to zero length. You can't read any data into a zero-length slice. There's no reason to re-slice your read buffer at all.
You also need to handle the read bytes before checking for errors, as io.EOF can be returned on a successful read.
You shouldn't use log.Fatal in the server's read loop, as that calls os.Exit
A working messageReader body might look like:
defer conn.Close()
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
message := string(buffer[:n])
if message == "/quit" {
fmt.Println("quit command received. Bye.")
return
}
if n > 0 {
fmt.Println(message)
}
if err != nil {
log.Println(err)
return
}
}
You should note though that because you're not using any sort of framing protocol here, you can't guarantee that each conn.Read returns a complete or single message. You need to have some sort of higher-level protocol to delimit messages in your stream.

Continuously check if tcp port is in use

I'm running a bash command to start up a server in the background : "./starServer &" However, my server takes a few seconds to start up. I'm wondering what I can do to continuously check the port that it's running on to ensure it's up before I actually move on and do other things. I couldn't find anything in the golang api that helped with this. Any help is appreciated!
c := exec.Command("/bin/sh", "-c", command)
err := c.Start()
if err != nil {
log.Fatalf("error: %v", err)
}
l, err1 := net.Listen("tcp", ":" + port)
You could connect to the port using net.DialTimeout or net.Dial, and if successful, immediately close it. You can do this in a loop until successful.
for {
conn, err := net.DialTimeout("tcp", net.JoinHostPort("", port), timeout)
if conn != nil {
conn.Close()
break
}
}
A simple tiny library (I wrote) for a similar purpose might also be of interest: portping.

SSH: is it possible to get STDERR from the session with pseudo-terminal?

This question is about golang.org/x/crypto/ssh package and maybe pseudo-terminal behaviour.
The code
Here is the demo code. You can run it on your local machine just change credentials to access SSH.
package main
import (
"bufio"
"fmt"
"golang.org/x/crypto/ssh"
"io"
)
func main() {
var pipe io.Reader
whichPipe := "error" // error or out
address := "192.168.1.62:22"
username := "username"
password := "password"
sshConfig := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{ssh.Password(password)},
}
connection, err := ssh.Dial("tcp", address, sshConfig)
if err != nil {
panic(err)
}
session, err := connection.NewSession()
if err != nil {
panic(err)
}
modes := ssh.TerminalModes{
ssh.ECHO: 0,
ssh.ECHOCTL: 0,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
if err := session.RequestPty("xterm", 80, 0, modes); err != nil {
session.Close()
panic(err)
}
switch whichPipe {
case "error":
pipe, _ = session.StderrPipe()
case "out":
pipe, _ = session.StdoutPipe()
}
err = session.Run("whoami23")
scanner := bufio.NewScanner(pipe)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
Actual result
Empty line
Expected result
bash: whoami23: command not found
Current "solution"
To get expected result you have two options:
Change whichPipe value to out. Yes, all errors going to stdout in case if you use tty.
Remove session.RequestPty. But in my case, I need to run sudo commands which require tty (servers are out of my control so I can't disable this requirement).
I use third way. I check err from err = session.Run("whoami23") and if it's not nil I mark content of session.StdoutPipe() as STDERR one.
But this method has limits. For example, if I run something like sudo sh -c 'uname -r; whoami23;' the whole result will be marked as error while uname -r returns output to STDOUT.
The question
While the behaviour looks logical to me (all that SSH client sees from pty is output without differentiations) I'm still not sure if I may miss something and there is a trick that allows to split these outputs.

Resources