Golang SSH Source Port - go

I have a requirement to use a static source port. We will do an IPTables redirect rule based on this source port. So, the static source port is used as an identifier as multiple connections are pending to the same destination port on the server. Think poor man's TCP mux a la iptables.
I have followed the Golang examples and cobbled some messy code together. I am not a programmer.
The ssh.dial function handles a lot, that becomes apparent once you use net.dial along with ssh.NewClientConn, ssh.NewClient and ssh.NewSession.
I see there is no ProxyCommand like in OpenSSH config options. I was using:
ssh -o ProxyCommand="ncat --source-port %h %p" ...
to achieve the requirement in a Bash script.
Additionally, I apologise for a loaded question but ncat et al. allow me to reuse the source port immediately.
Whereas Golang SSH leaves a TIME-WAIT 0 0 192.168.99.53:31337 192.168.99.7:22 for 60 seconds on Arch Linux.
Obviously, subsequent binds to said source port result in an error.
WRT the below code:
I have omitted the ExampleHostKeyCheck function
I know the sClient.Listen is a poor attempt at getting this work
The remote port forward does NOT appear on the server
I'm assuming a LOT more code is now required to handle the channels etc.?
The shell command does work, the file appears in /tmp/
package main
import (
"bytes"
"log"
"net"
"os"
"bufio"
"strings"
"path/filepath"
"golang.org/x/crypto/ssh"
)
func main() {
hostKey, err := ExampleHostKeyCheck()
conf := &ssh.ClientConfig{
User: "robert",
Auth: []ssh.AuthMethod{
ssh.Password("password"),
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
server, _ := net.ResolveTCPAddr("tcp", "centos7.ephemeric.local:22")
client, _ := net.ResolveTCPAddr("tcp", ":31337")
cc, err := net.DialTCP("tcp", client, server)
//cc, err := net.DialTCP("tcp", nil, server)
if err != nil {
log.Fatalf("%s", err)
}
defer cc.Close()
conn, chans, reqs, err := ssh.NewClientConn(cc, "", conf)
if err != nil {
log.Fatalf("%s", err)
}
defer conn.Close()
// https://stackoverflow.com/questions/35906991/go-x-crypto-ssh-how-to-establish-ssh-connection-to-private-instance-over-a-ba
sClient := ssh.NewClient(conn, chans, reqs)
if err != nil {
log.Fatalf("%s", err)
}
defer sClient.Close()
session, err := sClient.NewSession()
if err != nil {
log.Fatalf("%s", err)
}
defer session.Close()
sListen, err := sClient.Listen("tcp", "127.0.0.1:31337")
if err != nil {
log.Fatal("unable to register tcp forward: ", err)
}
defer sListen.Close()
var b bytes.Buffer // import "bytes"
session.Stdout = &b // get output
// You can also pass what gets input to the stdin, allowing you to pipe content from client to server session.Stdin = bytes.NewBufferString("MyInput").
err = session.Run("echo slobwashere >>/tmp/slobwashere; ls")
}
Thank you.

Related

How to extract the connected local ip address using http.Client in Go?

My PC has multiple IP addresses(ex: 10.1.1.20, 192.168.123.30, ...).
Can I extract the connected local ip address when connecting to remote server using http.Client?
If this is not possible with http.Client, is there any other possible way?
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://www.google.com", nil)
if err != nil {
panic(err)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// extract the local ip address???
// getsockname(?????)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Printf("StatusCode=%v\n", resp.StatusCode)
fmt.Printf("%v\n", string(data))
}
You can either:
loop through all network interfaces
or retrieve the preferred outbound ip address
But in both case, the fact that you are in the middle of using an http.Client and making a GET would not matter: you could get those IP addresses independently.
You can provide your own Transport implementation that extracts the outgoing local IP address right after establishing the TCP connection, e.g. like this:
client := &http.Client{
Transport: &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
conn, err := net.Dial(network, addr)
if err == nil {
localAddr := conn.LocalAddr().(*net.TCPAddr)
fmt.Println("LOCAL IP:", localAddr.IP)
}
return conn, err
},
},
}

How can I add a 10 second timeout with tls.Dial ? (There is no tls.DialTimeout to correspond to net.DialTimeout)

What is the best way to add a timeout when using tls.Dial in Go?
I see the net package has net.DialTimeout, but unfortunately, the tls package doesn't have a corresponding function.
I presume I should be using a context or Dialer to implement a timeout, but I'm not an expert in Go and I can't find any good examples.
(1) I found tls.DialWithDialer, but I'm not sure how to create a net.Dialer that is configured with a timeout.
func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error)
(2) I also found tls.DialContext, but I'm not sure how to use that to implement a timeout.
func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error)
(3) I presume it might be possible to establish an initial connection using net.DialTimeout and then upgrade the connection and continue with the TLS handshake, but I can't find any examples that show how to do that.
Any help or guidance would be appreciated.
Here is my simple program that connects to a list of servers and prints some info about the certificate chain. When a server is not responding, this program hangs for a long time. All I want to do is time out after 10 seconds.
package main
import (
"bufio"
"crypto/tls"
"fmt"
"os"
)
func main() {
port := "443"
conf := &tls.Config{
InsecureSkipVerify: true,
}
s := bufio.NewScanner(os.Stdin)
for s.Scan() {
host := s.Text()
conn, err := tls.Dial("tcp", host+":"+port, conf)
if err != nil {
fmt.Println("Host:", host, "Dial:", err)
continue
}
defer conn.Close()
certs := conn.ConnectionState().PeerCertificates
for _, cert := range certs {
fmt.Println("Host:", host, "Issuer:", cert.Issuer)
}
}
}
As you mention in your question there are a few options; using DialContext is a common technique:
package main
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"os"
"time"
)
func main() {
port := "443"
conf := &tls.Config{
InsecureSkipVerify: true,
}
s := bufio.NewScanner(os.Stdin)
for s.Scan() {
host := s.Text()
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
d := tls.Dialer{
Config: conf,
}
conn, err := d.DialContext(ctx,"tcp", host+":"+port)
cancel() // Ensure cancel is always called
if err != nil {
fmt.Println("Host:", host, "Dial:", err)
continue
}
// warning: using defer in a loop may not have the expected result
// the connection will remain open until the function exists
defer conn.Close()
tlsConn := conn.(*tls.Conn)
certs := tlsConn.ConnectionState().PeerCertificates
for _, cert := range certs {
fmt.Println("Host:", host, "Issuer:", cert.Issuer)
}
}
}
Using the above approach makes it relatively simple to allow users of your code to cancel the request (accept a context and use it where the above has context.Background()). If this is not important to you then using a Dialer with Timeout is simpler:
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 10 * time.Second}, "tcp", host+":"+port, conf)
if err != nil {
fmt.Println("Host:", host, "Dial:", err)
continue
}
certs := conn.ConnectionState().PeerCertificates
for _, cert := range certs {
fmt.Println("Host:", host, "Issuer:", cert.Issuer)
}
conn.Close()

Different behaviors of golang tcp socket on different OS

I want to simulate the http server with tcp socket written in Go. The program runs well on Linux or Mac, but has some problems on Windows. I have configured the built-in firewall to allow connections on the port. On Windows, when the browser requests the program, it shows me "This site can’t be reached. The connection was reset." Instead, it can response "Hello world" correctly on Linux or Mac. As follows.
// implement http server with tcp socket
package main
import (
"log"
"net"
"os"
)
var content = []byte(`HTTP/1.1 200 OK
Content-type: text/plain
Hello world!`)
func handleConn(conn net.Conn) {
conn.Write(content)
defer conn.Close()
}
func main() {
addr := "localhost:10000"
listener, err := net.Listen("tcp", addr)
checkErr(err)
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
continue
}
go handleConn(conn)
}
}
func checkErr(err error) {
if err != nil {
log.Fatal(err)
os.Exit(1)
}
}
I try to change some code to read all the bytes from the connection on Windows, after that, the program responses correctly.
func handleConn(conn net.Conn) {
var buf = make([]byte, 1024)
_, err := conn.Read(buf)
checkErr(err)
conn.Write(content)
defer conn.Close()
}
But I don't know why must I read all the bytes of connection on Windows.

SMTP connection read welcome message

I try to connect on smtp server and read welcome message. This is my code:
package main
import (
"fmt"
"net"
"time"
"net/smtp"
"bufio"
)
func main() {
// attempt a connection
conn, _ := net.DialTimeout("tcp", "88.198.24.108:25", 15 * time.Second)
buf := bufio.NewReader(conn)
bytes, _ := buf.ReadBytes('\n')
fmt.Printf("%s", bytes)
client, err := smtp.NewClient(conn, "88.198.24.108")
if err != nil {
fmt.Println("1>>", err)
return
}
client.Quit()
conn.Close()
}
Problem is after read welcome message stop running and wait to go in timeout, I want to read/print welcome message and continue.
220 example.me ESMTP Haraka/2.8.18 ready
1>> 421 timeout
An inspection of the standard library source indicates that smtp.NewClient() reads the SMTP banner from the remote host and throws it away.
func NewClient(conn net.Conn, host string) (*Client, error) {
text := textproto.NewConn(conn)
_, _, err := text.ReadResponse(220)
if err != nil {
text.Close()
return nil, err
}
c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
_, c.tls = conn.(*tls.Conn)
return c, nil
}
You want to read this banner and decide whether to send mail based on its contents.
Since you have already read the banner yourself, and presumably will make a decision on that, instead of calling smtp.NewClient() you should then implement the rest of NewClient() in your own code, possibly something like this:
client := &smtp.Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
_, client.tls = conn.(*tls.Conn)

Golang: Mixing Gin with an UDP server

I'm trying to use both a UDP server to listen continuously to datagrams and a http server, but the string "UDP server up and listening on port..." and command "server.Run()" are never reached.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net"
)
func handleUDPConnection(conn *net.UDPConn) {
buffer := make([]byte, 8096)
n, addr, err := conn.ReadFromUDP(buffer)
if err != nil {
log.Fatal(err)
} else {
fmt.Println("UDP client: ", addr)
fmt.Println("Received from UDP client: ", string(buffer[:n]))
}
}
func main() {
server := gin.Default()
host, port := "localhost", "41234"
udpAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%s", host, port))
if err != nil {
log.Fatal(err)
}
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
server.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
for {
handleUDPConnection(conn)
}
fmt.Sprintf("UDP server up and listening on port %s\n", port)
server.Run()
}
How can I make it work?
There is an infinite loop in your code.
for {
handleUDPConnection(conn)
}
This will repetedly call the handleUDPConnection function until the program exits without ever moving on to
fmt.Sprintf("UDP server up and listening on port %s\n", port)
server.Run()
Perhaps you want to deal with the connections in a go thread. This would be something more like this:
//define an exit variable
keepListening := true
//spawn a go routine (starts the function on another thread*)
go func() {
for keepListening {
handleUDPConnection(conn)
}
}()
//notify the user that the server is listening
fmt.Sprintf("UDP server up and listening on port %s\n", port)
//run the server (I assume this function call is blocking
server.Run()
//stop the go routine when the server is done running
keepListening = false
Hope this helps!
*a goroutine is not a thread. It can be useful/simple to think of it like that, but they are distinctly different. Here's an article explaining some of the differences and advantages.

Resources