How to properly loop through buffered channel in this case? - go

I am trying to play around with go to make some kind of port scanner using the stdlib. This is more of an exercise than anything else, so please don't comment on the logic involved.
Looking at the following code:
package main
import (
"flag"
"fmt"
"net"
"time"
"strings"
"strconv"
"log"
"sync"
)
var commonPorts = map[int]string {
21: "ftp",
22: "sftp",
80: "http",
110: "pop3",
143: "imap",
443: "https",
631: "ipp",
993: "imaps",
995: "pop3s",
}
type OP struct {
mu sync.Mutex
ports []string
}
func (o *OP) SafeAdd(port string) {
o.mu.Lock()
defer o.mu.Unlock()
o.ports = append(o.ports, port)
}
func worker(host string, port int) string {
address := fmt.Sprintf("%s:%d", host, port)
conn, err := net.DialTimeout("tcp", address, time.Second * 3)
if err != nil {
return ""; // is offline, cannot connect
}
conn.Close()
stringI := strconv.Itoa(port)
if name, ok := commonPorts[port]; ok {
stringI += fmt.Sprintf("(%s)", name)
}
return stringI
}
func processWithChannels(host string) <-chan string{
openPort := make(chan string, 1000)
var wg sync.WaitGroup
for i := 1; i <= 65535; i++ {
wg.Add(1)
go func(openPort chan string, host string, i int) {
defer wg.Done()
port := worker(host, i)
if port != "" {
openPort <- port
}
}(openPort, host, i)
}
wg.Wait()
close(openPort)
return openPort
}
func main() {
var host = flag.String("host", "127.0.0.1", "please insert the host")
var pType = flag.Int("type", 2, "please insert the type")
flag.Parse()
fmt.Printf("Scanning: %s...\n", *host)
if _, err := net.LookupHost(*host); err != nil {
log.Fatal(err)
}
openPorts := &OP{ports: []string{}};
if *pType == 1 {
ports := processWithChannels(*host);
for port := range ports {
openPorts.SafeAdd(port)
}
} else {
var wg sync.WaitGroup
for i := 1; i <= 65535; i++ {
wg.Add(1)
go func(o *OP, host string, i int){
defer wg.Done()
if port := worker(host, i); port != "" {
o.SafeAdd(port)
}
}(openPorts, *host, i)
}
wg.Wait()
}
if len(openPorts.ports) > 0 {
fmt.Printf("Following ports are opened: %s\n", strings.Join(openPorts.ports, ", "))
} else {
fmt.Printf("No open port on the host: %s!\n", *host)
}
}
There are two ways of starting a scan, either by using a buffered channel or by using sync.GroupWait and bail out once all the scans are done.
It seems to me that in this case, using sync.GroupWait makes more sense than using a buffered channel and loop through it till it's empty. However, using a buffered channel here, i don't see a way to detect that there's nothing else on the channel and that i should bail out from the for loop, except by using another sync.WaitGroup block.
I think my question is, in case i want to use the buffered channel solution only, how do i implement it properly so that i know when the processing is done so that i can proceed with the rest of the code? (don't suggest timeouts please).
Here's also a small benchmark with the two types, in case anyone interested:
MacBook-Pro:PortScanner c$ time ./PortScanner -host yahoo.com -type 1
Scanning: yahoo.com...
Following ports are opened: 80(http), 143(imap), 110(pop3), 995(pop3s), 993(imaps)
real 0m4.620s
user 0m1.193s
sys 0m1.284s
MacBook-Pro:PortScanner c$ time ./PortScanner -host yahoo.com -type 2
Scanning: yahoo.com...
Following ports are opened: 110(pop3), 80(http), 143(imap), 995(pop3s), 993(imaps)
real 0m4.055s
user 0m1.051s
sys 0m0.946s

The call to processWithChannels will hang if you need to put more than 1000 items into the channel. If you're going to use a buffered channel to hold all values until processing, there has to be enough capacity to accept all values.
If you are going to collect all values into a single slice, then there's no reason to use a channel, and your second solution is just fine.
If you want to "stream" the ports back as soon as possible, then you need something in between the two solutions
ports := make(chan string)
var wg sync.WaitGroup
for i := 1; i <= 65535; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
if port := worker(*host, i); port != "" {
ports <- port
}
}(i)
}
go func() {
wg.Wait()
close(ports)
}()
for port := range ports {
fmt.Println("PORT:", port)
}
This however is likely to run into problems, like missing open ports when you dial all 65535 ports at the same time. Here is one possible pattern to use a pool of workers to dial concurrently:
ports := make(chan string)
toScan := make(chan int)
var wg sync.WaitGroup
// make 100 workers for dialing
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for p := range toScan {
ports <- worker(*host, p)
}
}()
}
// close our receiving ports channel once all workers are done
go func() {
wg.Wait()
close(ports)
}()
// feed the ports to the worker pool
go func() {
for i := 1; i <= 65535; i++ {
toScan <- i
}
// signal the workers to stop
close(toScan)
}()
for port := range ports {
if port != "" {
fmt.Println("PORT:", port)
}
}

Related

Goroutine is blocked in execution [duplicate]

This question already has answers here:
Deadlock after attempting to print values of channel using 'range'
(3 answers)
Deadlock in the program when using for range on buffered channel
(1 answer)
Waiting for a WaitGroup and ranging over a channel results in a "fatal error: all goroutines are asleep - deadlock!"
(2 answers)
go routine for range over channels
(5 answers)
golang concurrency sync issue
(1 answer)
Closed 26 days ago.
I try to work on a piece of code with goroutine to scan open port on a subnet.
Here is the code :
package main
import (
"fmt"
"log"
"net"
"time"
)
func is_445_open(ip string, ch chan string){
connexion, err := net.DialTimeout("tcp", ip + ":445", 3*time.Second )
if err != nil {
return
}
defer connexion.Close()
ch <- ip
}
func ip_suivante(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
func main() {
ch := make(chan string)
ipv4Addr, ipv4Net, err := net.ParseCIDR("200.31.0.0/24")
if err != nil {
log.Fatal(err)
}
for ipv4Addr := ipv4Addr.Mask(ipv4Net.Mask); ipv4Net.Contains(ipv4Addr); ip_suivante(ipv4Addr) {
//fmt.Println(ipv4Addr.String())
go is_445_open(ipv4Addr.String(), ch)
}
for v := range ch{
fmt.Println(v)
}
fmt.Println("Done")
}
I use ParseCIDR to loop over every IP of the network range then i connect to the port 445 to test if it is open.
To speed up the process, i want to use goroutine and write the ip where 445 is open to a channel.
Then loop over it to print them on the console.
When i use the loop to print every IP on my console the program never end.
It print the ip correctly but never the "done" at the end.
I have tried to close the channel before looping but when i do, no more data is printed and the program only show "done".
Do you have any tips ?
EDIT :
Finally figured out. You MUST put the code which manage the waitgroup and the channel closing into another goroutine to make it work.
Cannot undestand why.
But here is the code modified accordingly :
package main
import (
"fmt"
"log"
"net"
"time"
"sync"
)
func is_445_open(ip string, ch chan string, wg *sync.WaitGroup){
connexion, err := net.DialTimeout("tcp", ip + ":445", 3*time.Second )
if err != nil {
wg.Done()
return
}
defer connexion.Close()
ch <- ip
wg.Done()
}
func ip_suivante(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
func main() {
wg := &sync.WaitGroup{}
ch := make(chan string)
ipv4Addr, ipv4Net, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
log.Fatal(err)
}
for ipv4Addr := ipv4Addr.Mask(ipv4Net.Mask); ipv4Net.Contains(ipv4Addr); ip_suivante(ipv4Addr) {
//fmt.Println(ipv4Addr.String())
wg.Add(1)
go is_445_open(ipv4Addr.String(), ch, wg)
}
go func(cha chan string, wg *sync.WaitGroup){
wg.Wait()
close(cha)
}(ch,wg)
for v := range ch{
fmt.Println(v)
}
fmt.Println("Done")
}

Waitgroups and synchronization not working in Go

I've been experimenting with goroutines and channels, and I wanted to test the WaitGroup feature. Here I'm trying to execute an HTTP flood job, where the parent thread spawns a lot of goroutines which will make infinite requests, unless receiving a stop message:
func (hf *HTTPFlood) Run() {
childrenStop := make(chan int, hf.ConcurrentCalls)
stop := false
totalRequests := 0
requestsChan := make(chan int)
totalErrors := 0
errorsChan := make(chan int)
var wg sync.WaitGroup
for i := 0; i < hf.ConcurrentCalls; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-childrenStop:
fmt.Printf("stop child\n")
return
default:
_, err := Request(hf.Victim.String())
requestsChan <- 1
if err != nil {
errorsChan <- 1
}
}
}
}()
}
timeout := time.NewTimer(time.Duration(MaximumJobTime) * time.Second)
for !stop {
select {
case req := <- requestsChan:
totalReq += req
case err := <- errorsChan:
totalErrors += err
case <- timeout.C:
fmt.Printf("%s timed up\n", hf.Victim.String())
for i := 0; i < hf.ConcurrentCalls; i++ {
childrenStop <- 1
}
close(childrenStop)
stop = true
break
}
}
fmt.Printf("waiting\n")
wg.Wait()
fmt.Printf("after wait\n")
close(requestsChan)
close(errorsChan)
fmt.Printf("end\n")
}
Once timeout is fired, the parent thread successfully exits the loop and reaches the Wait instruction, but even though the stopChildren channel is filled, the child goroutines seem to never receive messages on the stopChildren channel.
What am I missing?
EDIT:
So the issue obviously was how the channels and its sends/receives were managed.
First of all the childrenStop channel was closed before all childs had received the message. The channel should be closed after the Wait
On the other hand, since no reads were done neither on requestsChan nor errorsChan once the parent thread sends the stop signal, most of the childs stayed blocked sending on these two channels. I tried to keep reading in the parent thread, outside the loop just before the Wait but that didn't work so I switched the implementation to Atomic counters which seem to be a more suitable way to manage this specific use case.
func (hf *HTTPFlood) Run() {
childrenStop := make(chan int, hf.ConcurrentCalls)
var totalReq uint64
var totalErrors uint64
var wg sync.WaitGroup
for i := 0; i < hf.ConcurrentCalls; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-childrenStop:
fmt.Printf("stop child\n")
return
default:
_, err := Request(hf.Victim.String())
atomic.AddUint64(&totalReq, 1)
if err != nil {
atomic.AddUint64(&totalErrors, 1)
}
}
}
}()
}
timeout := time.NewTimer(time.Duration(MaximumJobTime) * time.Second)
<- timeout.C
fmt.Printf("%s timed up\n", hf.Victim.String())
for i := 0; i < hf.ConcurrentCalls; i++ {
childrenStop <- 1
}
fmt.Printf("waiting\n")
wg.Wait()
fmt.Printf("after wait\n")
close(childrenStop)
fmt.Printf("end\n")
}
Your go routines can be is blocked at requestsChan <- 1.
case <- timeout.C:
fmt.Printf("%s timed up\n", hf.Victim.String())
for i := 0; i < hf.ConcurrentCalls; i++ {
childrenStop <- 1
}
close(childrenStop)
stop = true
break
Here you are sending a number to childrenStop and expect the go routines to receive it. But while you are sending the childrenStop signal, your routines could have sent something on requestsChan. But as you break from the loop after sending the close signals, there's no one listening on requestsChan to receive.
You can confirm this by printing something just before and after requestsChan <- 1 to confirm the behaviour.
A channel will block when you send something on it while no one is receiving on the other end
Here is a possible modification.
package main
import (
"fmt"
"time"
)
func main() {
requestsChan := make(chan int)
done := make(chan chan bool)
for i := 0; i < 5; i++ {
go func(it int) {
for {
select {
case c := <-done:
c <- true
return
default:
requestsChan <- it
}
}
}(i)
}
max := time.NewTimer(1 * time.Millisecond)
allChildrenDone := make(chan bool)
childrenDone := 0
childDone := make(chan bool)
go func() {
for {
select {
case i := <-requestsChan:
fmt.Printf("received %d;", i)
case <-max.C:
fmt.Println("\nTimeup")
for i := 0; i < 5; i++ {
go func() {
done <- childDone
fmt.Println("sent done")
}()
}
case <-childDone:
childrenDone++
fmt.Println("child done ", childrenDone)
if childrenDone == 5 {
allChildrenDone <- true
return
}
}
}
}()
fmt.Println("Waiting")
<-allChildrenDone
}
Thing to note here is that am sending the close signal in go routines so that the loop can continue while i wait for all the children to have cleanly exit.
Please watch this talk by Rob Pike which covers these details clearly.
[Edit]: The previous code would have resulted in a running routine after exiting.

Why my Golang pressure measuring tool and Apache Batch generate different number of ESTABLISHED links?

Apache Batch is a popular pressure measuring tool.
I write a tool with the same function in golang myself. Then I make some tests.
I find it works well.
But accidentally, I find a difference from my tool and Apache Batch:
When checking ESTABLISHED link number by the cmd netstat -na|grep ESTABLISHED|wc -l:
Use cmd ab -n 128000 -c 128 http://127.0.0.1:8000/, the cmd above returns a number near 128.
But use my own tool, the cmd returns near 256 (when set concurrency 128).
Why my own tool have twice the expected number of concurrencies?
My code:
func request(cli *http.Client, uri string) {
resp, err := cli.Get(uri)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
panic("return" + strconv.Itoa(resp.StatusCode))
}
}
func workRoutine(ch chan string, wg *sync.WaitGroup) {
cli := &http.Client{}
for uri := range ch {
request(cli, uri)
}
wg.Done()
}
func main() {
rnum := 128
tnum := 128000
url := "http://127.0.0.1:8000/"
ch := make(chan string)
var wg sync.WaitGroup
go func() {
for i := 0; i < tnum; i++ {
ch <- url
}
close(ch)
}()
wg.Add(rnum)
for i := 0; i < rnum; i++ {
go workRoutine(ch, &wg)
}
stime := time.Now()
wg.Wait()
dtime := time.Now().Sub(stime)
fmt.Println("Timecost", dtime)
fmt.Println("Throughputs", float64(tnum)/dtime.Seconds())
}

How do I select on os.Stdin and http in Go?

Say I want to accept an animal. The user can either set the type of animal at the What type of animal? prompt on a terminal, or she can go to http://localhost:1234/animal?type=kitten
Whichever she does, the terminal will read What type of animal? kitten (assuming she chose a kitten) and the program will then prompt the user on the terminal (and only the terminal) What is the kitten's name?
My thought was to use channels to go routines, but since both go routines will be stuck in a function call (Scan() for the terminal, ListenAndServe() for http) then I'm not clear how to stop the go routine that is still in a function call once the input is received. The normal method of selecting on channels won't work.
In C I'd just do a select(2) on the two relevant file descriptors (fd 0 and the fd from listen()) but I don't think that's how to do it in Go.
Note: I answered this down below. If you're down voting the question this I'd be curious to know why as I would have found the answer I came up with useful. If you're down voting the answer, I'd really be interested in knowing why. If it's non-idiomatic Go or if it has other issues I'm missing, I'd really like to fix them.
OK, I figured out a solution for what I was trying to do.
This might not be the most Go idiomatic way though. If the channels took a struct with input source and the string I could probably toss the select and the webinput channel.
package main
import (
"bufio"
"context"
"fmt"
"io"
"net/http"
"os"
"time"
)
func getHTTPAnimal(input chan string) *http.Server {
srv := &http.Server{Addr: ":1234"}
http.HandleFunc("/animal", func(w http.ResponseWriter, r *http.Request) {
animal, ok := r.URL.Query()["type"]
if !ok || len(animal[0]) <= 0 {
io.WriteString(w, "Animal not understood.\n")
return
}
io.WriteString(w, "Animal understood.\n")
input <- string(animal[0])
})
go func() {
if err := srv.ListenAndServe(); err != nil {
if err.Error() != "http: Server closed" {
fmt.Printf("getHTTPAnimal error with ListenAndServe: %s", err)
}
}
}()
return srv
}
func getTerminalInput(input chan string) {
scanner := bufio.NewScanner(os.Stdin)
for {
scanner.Scan()
input <- scanner.Text()
}
}
func main() {
input := make(chan string)
webinput := make(chan string)
srv := getHTTPAnimal(webinput)
fmt.Print("Enter animal type: ")
go getTerminalInput(input)
var animal string
select {
case ta := <-input:
animal = ta
case webta := <-webinput:
animal = webta
fmt.Printf("%s\n", animal)
}
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
srv.Shutdown(ctx)
close(webinput)
fmt.Printf("Enter animal name: ")
name := <-input
fmt.Printf("Congrats on getting %s the half-%s\n", name, animal)
}
You can stop goroutine with close statement.
Example:
package main
import "sync"
func main() {
var wg sync.WaitGroup
wg.Add(1)
ch := make(chan int)
go func() {
for {
foo, ok := <- ch
if !ok {
println("done")
wg.Done()
return
}
println(foo)
}
}()
ch <- 1
ch <- 2
ch <- 3
close(ch)
wg.Wait()
}
There is couple of ways to stop goroutine:
1) by closing the read channel:
msgCh := make(chan string)
go func(){
for msg, ok := range msgCh {
if !ok {
return
}
}
}()
msgCh <- "String"
close(msgCh)
2) passing some sort of kill switch
msgCh := make(chan string)
killSwitch := make(chan struct{})
go func(){
for {
select{
case msg <- msgCh:
log.Println(msg)
case <-killSwitch:
return
}
}
}
msgCh <- "String"
close(killSwitch)
With approach NR2 you are avoiding writing to closed chan that would end up as panic. Also close signal is populated as fanout pattern (all places where that chan is used will receive that signal)

Capture Output and Errors of Goroutine Using Channels

I have a for-loop that calls a function runCommand() which runs a remote command on a switch and prints the output. The function is called in a goroutine on each iteration and I am using a sync.Waitgroup to synchronize the goroutines. Now, I need a way to capture the output and any errors of my runCommand() function into a channel. I have read many articles and watched a lot of videos on using channels with goroutines, but this is the first time I have ever written a concurrent application and I can't seem to wrap my head around the idea.
Basically, my program takes in a list of hostnames from the command line then asynchronously connects to each host, runs a configuration command on it, and prints the output. It is ok for my program to continue configuring the remaining hosts if one has an error.
How would I idiomatically send the output or error(s) of each call to runCommand() to a channel then receive the output or error(s) for printing?
Here is my code:
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
"os"
"time"
"sync"
)
func main() {
hosts := os.Args[1:]
clientConf := configureClient("user", "password")
var wg sync.WaitGroup
for _, host := range hosts {
wg.Add(1)
go runCommand(host, &clientConf, &wg)
}
wg.Wait()
fmt.Println("Configuration complete!")
}
// Run a remote command
func runCommand(host string, config *ssh.ClientConfig, wg *sync.WaitGroup) {
defer wg.Done()
// Connect to the client
client, err := ssh.Dial("tcp", host+":22", config)
if err != nil {
fmt.Println(err)
return
}
defer client.Close()
// Create a session
session, err := client.NewSession()
if err != nil {
fmt.Println(err)
return
}
defer session.Close()
// Get the session output
output, err := session.Output("show lldp ne")
if err != nil {
fmt.Println(err)
return
}
fmt.Print(string(output))
fmt.Printf("Connection to %s closed.\n", host)
}
// Set up client configuration
func configureClient(user, password string) ssh.ClientConfig {
var sshConf ssh.Config
sshConf.SetDefaults()
// Append supported ciphers
sshConf.Ciphers = append(sshConf.Ciphers, "aes128-cbc", "aes256-cbc", "3des-cbc", "des-cbc", "aes192-cbc")
// Create client config
clientConf := &ssh.ClientConfig{
Config: sshConf,
User: user,
Auth: []ssh.AuthMethod{ssh.Password(password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: time.Second * 5,
}
return *clientConf
}
EDIT: I got rid of the Waitgroup, as suggested, and now I need to keep track of which output belongs to which host by printing the hostname before printing its output and printing a Connection to <host> closed. message when the gorouttine completes. For example:
$ go run main.go host1[,host2[,...]]
Connecting to <host1>
[Output]
...
[Error]
Connection to <host1> closed.
Connecting to <host2>
...
Connection to <host2> closed.
Configuration complete!
I know the above won't necessarily process host1 and host2 in order, But I need to print the correct host value for the connecting and closing messages before and after the output/error(s), respectively. I tried defering printing the closing message in the runCommand() function, but the message is printed out before the output/error(s). And printing the closing message in the for-loop after each goroutine call doesn't work as expected either.
Updated code:
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
"os"
"time"
)
type CmdResult struct {
Host string
Output string
Err error
}
func main() {
start := time.Now()
hosts := os.Args[1:]
clientConf := configureClient("user", "password")
results := make(chan CmdResult)
for _, host := range hosts {
go runCommand(host, &clientConf, results)
}
for i := 0; i < len(hosts); i++ {
output := <- results
fmt.Println(output.Host)
if output.Output != "" {
fmt.Printf("%s\n", output.Output)
}
if output.Err != nil {
fmt.Printf("Error: %v\n", output.Err)
}
}
fmt.Printf("Configuration complete! [%s]\n", time.Since(start).String())
}
// Run a remote command
func runCommand(host string, config *ssh.ClientConfig, ch chan CmdResult) {
// This is printing before the output/error(s).
// Does the same when moved to the bottom of this function.
defer fmt.Printf("Connection to %s closed.\n", host)
// Connect to the client
client, err := ssh.Dial("tcp", host+":22", config)
if err != nil {
ch <- CmdResult{host, "", err}
return
}
defer client.Close()
// Create a session
session, err := client.NewSession()
if err != nil {
ch <- CmdResult{host, "", err}
return
}
defer session.Close()
// Get the session output
output, err := session.Output("show lldp ne")
if err != nil {
ch <- CmdResult{host, "", err}
return
}
ch <- CmdResult{host, string(output), nil}
}
// Set up client configuration
func configureClient(user, password string) ssh.ClientConfig {
var sshConf ssh.Config
sshConf.SetDefaults()
// Append supported ciphers
sshConf.Ciphers = append(sshConf.Ciphers, "aes128-cbc", "aes256-cbc", "3des-cbc", "des-cbc", "aes192-cbc")
// Create client config
clientConf := &ssh.ClientConfig{
Config: sshConf,
User: user,
Auth: []ssh.AuthMethod{ssh.Password(password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: time.Second * 5,
}
return *clientConf
}
If you use an unbuffered channel, you actually don't need the sync.WaitGroup, because you can call the receive operator on the channel once for every goroutine that will send on the channel. Each receive operation will block until a send statement is ready, resulting in the same behavior as a WaitGroup.
To make this happen, change runCommand to execute a send statement exactly once before the function exits, under all conditions.
First, create a type to send over the channel:
type CommandResult struct {
Output string
Err error
}
And edit your main() {...} to execute a receive operation on the channel the same number of times as the number of goroutines that will send to the channel:
func main() {
ch := make(chan CommandResult) // initialize an unbuffered channel
// rest of your setup
for _, host := range hosts {
go runCommand(host, &clientConf, ch) // pass in the channel
}
for x := 0; x < len(hosts); x++ {
fmt.Println(<-ch) // this will block until one is ready to send
}
And edit your runCommand function to accept the channel, remove references to WaitGroup, and execute the send exactly once under all conditions:
func runCommand(host string, config *ssh.ClientConfig, ch chan CommandResult) {
// do stuff that generates output, err; then when ready to exit function:
ch <- CommandResult{output, err}
}
EDIT: Question updated with stdout message order requirements
I'd like to get nicely formatted output that ignores the order of events
In this case, remove all print messages from runCommand, you're going to put all output into the element you're passing on the channel so it can be grouped together. Edit the CommandResult type to contain additional fields you want to organize, such as:
type CommandResult struct {
Host string
Output string
Err error
}
If you don't need to sort your results, you can just move on to printing the data received, e.g.
for x := 0; x < len(hosts); x++ {
r := <-ch
fmt.Printf("Host: %s----\nOutput: %s\n", r.Host, r.Output)
if r.Err != nil {
fmt.Printf("Error: %s\n", r.Err)
}
}
If you do need to sort your results, then in your main goroutine, add the elements received on the channel to a slice:
...
results := make([]CommandResult, 0, len(hosts))
for x := 0; x < len(hosts); x++ {
results = append(results, <-ch) // this will block until one is ready to send
}
Then you can use the sort package in the Go standard library to sort your results for printing. For example, you could sort them alphabetically by host. Or you could put the results into a map with host string as the key instead of a slice to allow you to print in the order of the original host list.

Resources