Sending GRPC communications over a specific port - go

I am running a GRPC server (Server A) listening on a specific port. I want to be able to send a communication to another server (Server B), and have Server B record the incoming address of Server A's connection so that it may later contact Server A.
On Server A, I listen on a port and create a context like such:
lis, err := net.Listen("tcp", "0.0.0.0:6000")
ctx, cancel := context.WithTimeout(context.Background(),
10000*time.Millisecond)
Then create a connection like so:
connection, err = grpc.DialContext(ctx, server2Address,
grpc.WithInsecure(), grpc.WithBlock())
Before finally sending a message to an endpoint on Server B, which attempts to read the IP address of Server A's incoming connection
info, _ := peer.FromContext(ctx)
fmt.Printf(info.Addr.String()) // Returns a random port, NOT 6000,
However, the resulting port printed by Server B is random, like 62056 as opposed to 6000 as intended. My assumption is that, on Server A, GRPC dials from a random port - is it possible to force GRPC to dial from port 6000 as opposed to a random port?

You can specify the source port like this:
cc, err := grpc.Dial("127.0.0.1:6001", grpc.WithInsecure(),
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
dst, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil, err
}
src := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 6000,
}
return net.DialTCP("tcp", src, dst)
}))
However if your server is listening on the same port this will result in an error:
panic: dial tcp 127.0.0.1:6000->127.0.0.1:6001: bind: address already in use
A different approach would be to pass the address as metadata. On the client you do:
ctx := context.Background()
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("address", "127.0.0.1:6000"))
res, err := grpc_health_v1.NewHealthClient(cc).Check(ctx, &grpc_health_v1.HealthCheckRequest{
Service: "test",
})
And on the server:
func (s *server) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
addr := md.Get("address")
// addr == "127.0.0.1:6000"
}
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING,
}, nil
}
And a third approach would be to use streaming.

Related

Error while creating smtp.NewClient using socks5 proxy but getting EOF error

I am attempting to create an SMTP server using the SOCKS5 proxy using the net library in golang
Trying to establish a connection to the address via proxy protocol
//addr : mx2.privateemail.com.:25
//proxyURI = "socks5://username:password#xx.yyy.zzz.aaa:port?timeout=10s"
func establishProxyConnection(addr, proxyURI string) (net.Conn, error) {
return socks.Dial(proxyURI)("tcp", addr)
}
Using the returned connection from step 1, trying to create SMTP connection.
//host: mx1.privateemail.com.
client, err := smtp.NewClient(conn, host)
Further debugging the NewClient method I am getting error EOF at text.ReadResponse(220)
// NewClient returns a new Client using an existing connection and host as a
// server name to be used when authenticating.
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
}
I am a novice to all networking concepts, please let me know if any more information needs to be added.
Thanks in advance!

Paho MQTT golang for multiple modules?

I am writing a microservice in golang for a mqtt module. This module will be used by different function at the same time. I am using Grpc as a transport layer.
I have made a connect function which is this..
func Connect() { //it would be Connect(payload1 struct,topic string)
deviceID := flag.String("device", "handler-1", "GCP Device-Id")
bridge := struct {
host *string
port *string
}{
flag.String("mqtt_host", "", "MQTT Bridge Host"),
flag.String("mqtt_port", "", "MQTT Bridge Port"),
}
projectID := flag.String("project", "", "GCP Project ID")
registryID := flag.String("registry", "", "Cloud IoT Registry ID (short form)")
region := flag.String("region", "", "GCP Region")
certsCA := flag.String("ca_certs", "", "Download https://pki.google.com/roots.pem")
privateKey := flag.String("private_key", "", "Path to private key file")
server := fmt.Sprintf("ssl://%v:%v", *bridge.host, *bridge.port)
topic := struct {
config string
telemetry string
}{
config: fmt.Sprintf("/devices/%v/config", *deviceID),
telemetry: fmt.Sprintf("/devices/%v/events/topic", *deviceID),
}
qos := flag.Int("qos", 0, "The QoS to subscribe to messages at")
clientid := fmt.Sprintf("projects/%v/locations/%v/registries/%v/devices/%v",
*projectID,
*region,
*registryID,
*deviceID,
)
log.Println("[main] Loading Google's roots")
certpool := x509.NewCertPool()
pemCerts, err := ioutil.ReadFile(*certsCA)
if err == nil {
certpool.AppendCertsFromPEM(pemCerts)
}
log.Println("[main] Creating TLS Config")
config := &tls.Config{
RootCAs: certpool,
ClientAuth: tls.NoClientCert,
ClientCAs: nil,
InsecureSkipVerify: true,
Certificates: []tls.Certificate{},
MinVersion: tls.VersionTLS12,
}
flag.Parse()
connOpts := MQTT.NewClientOptions().
AddBroker(server).
SetClientID(clientid).
SetAutoReconnect(true).
SetPingTimeout(10 * time.Second).
SetKeepAlive(10 * time.Second).
SetDefaultPublishHandler(onMessageReceived).
SetConnectionLostHandler(connLostHandler).
SetReconnectingHandler(reconnHandler).
SetTLSConfig(config)
connOpts.SetUsername("unused")
///JWT Generation Starts from Here
token := jwt.New(jwt.SigningMethodES256)
token.Claims = jwt.StandardClaims{
Audience: *projectID,
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
}
//Reading key file
log.Println("[main] Load Private Key")
keyBytes, err := ioutil.ReadFile(*privateKey)
if err != nil {
log.Fatal(err)
}
//Parsing key from file
log.Println("[main] Parse Private Key")
key, err := jwt.ParseECPrivateKeyFromPEM(keyBytes)
if err != nil {
log.Fatal(err)
}
//Signing JWT with private key
log.Println("[main] Sign String")
tokenString, err := token.SignedString(key)
if err != nil {
log.Fatal(err)
}
//JWT Generation Ends here
connOpts.SetPassword(tokenString)
connOpts.OnConnect = func(c MQTT.Client) {
if token := c.Subscribe(topic.config, byte(*qos), nil); token.Wait() && token.Error() != nil {
log.Fatal(token.Error())
}
}
client := MQTT.NewClient(connOpts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
fmt.Printf("Not Connected..Retrying... %s\n", server)
} else {
fmt.Printf("Connected to %s\n", server)
}
}
i am calling this function in go routine in my main.go
func main() {
fmt.Println("Server started at port 5005")
lis, err := net.Listen("tcp", "0.0.0.0:5005")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
//Creating keepAlive channel for mqttt subscribe
keepAlive := make(chan os.Signal)
defer close(keepAlive)
go func() {
//checking for internet connection
for !IsOnline() {
fmt.Println("No Internet Connection..Retrying")
//looking for internet connection after every 8 seconds
time.Sleep(8 * time.Second)
}
fmt.Println("Internet connected...connecting to mqtt broker")
repositories.Connect()
//looking for interupt(Ctrl+C)
value := <-keepAlive
//If Ctrl+C is pressed then exit the application
if value == os.Interrupt {
fmt.Printf("Exiting the application")
os.Exit(3)
}
}()
s := grpc.NewServer()
MqttRepository := repositories.MqttRepository()
// It creates a new gRPC server instance
rpc.NewMqttServer(s, MqttRepository)
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
func IsOnline() bool {
timeout := time.Duration(5000 * time.Millisecond)
client := http.Client{
Timeout: timeout,
}
//default url to check connection is http://google.com
_, err := client.Get("https://google.com")
if err != nil {
return false
}
return true
}
I am using the go routine in my main in order for the application to start on every startup.
Now I want to use this MQTT Connect function to publish the data from other different functions.
e.g. Function A can call it like Connect(payload1,topic1) and function B can call it like Connect(payload2,topic2) and then this function should handle the publishing the data into the cloud.
Should I just add the topic and payload in this Connect function and then call it from another function? or is there any possibility that I can return or export the client as a global and then use it in another function or go routine? I am sorry if my question sound very stupid .. I am not a golang expert..
Now I want to use this MQTT Connect function to publish the data from other different functions.
I suspect I may be misunderstanding what you are trying to do here but unless you have a specific reason for making multiple connections you are best to connect once and then use that single connection to publish multiple messages. There are a few issues with establishing a connection each time you send a message including:
Establishing the connection takes time and generates a bit of network traffic (TLS handshake etc).
There can only be one active connection for a given ClientID (if you establish a second connection the broker will close the previous connection).
The library will not automatically disconnect - you would need to call Disconnect after publishing.
Incoming messages are likely to be lost due to the connection being down (note that CleanSession defaults to true).
Should I just add the topic and payload in this Connect function and then call it from another function?
As mentioned above the preferred approach would be to connect once and then publish multiple messages over the one connection. The Client is designed to be thread safe so you can pass it around and call Publish from multiple go routines. You can also make use of AutoConnect option (which you are) if you want the library to manage the connection (there is also a SetConnectRetry function) but bear in mind that a QOS 0 message will not be retried if the link is down when you attempt to send it.
I would suggest that your connect function return the client (i.e. func Connect() mqtt.Client) and then use that client to publish messages (you can store it somewhere or just pass it around; I'd suggest adding it you your grpc server struct).
I guess it is possible that you may need to establish multiple connections if you need to connect with a specific clientid in order to send to the desired topic (but generally you would give your servers connection access to a wide range of topics). This would require some work to ensure you don't try to establish multiple connections with the same client id simultaneously and, depending upon your requirements, receiving incoming messages.
A few additional notes:
If you use AutoConnect and SetConnectRetry you can simplify your code code (and just use IsConnectionOpen() to check if the connection is up removing the need for IsOnline()).
The spec states that "The Server MUST allow ClientIds which are between 1 and 23 UTF-8 encoded bytes in length" - it looks like yours is longer than that (I have not used GCP and it may well support/require a longer client ID).
You should not need InsecureSkipVerify in production.

Go TCP Listener ACL

I realise I have a lot of invalid requests hitting my HTTP server but these are at TCP session and has not gone pass TLS handshake, hence no HTTP request yet. (I can't block it at HTTP layer).
I've written the below to filter malicious traffic at TCP Listener but I think its not optimum, the connection is first accepted and then closed, I like to completely not accept the connection if it comes from a known IP address. The next best is to do it at IPtables/NFtables, but I like to explore if I can filter remote addr prior to conn established at TCP Listener. I try looking at the NET package, and looks like its in the File Descriptor which isn't something easy to do.
func main() {
// listen for incoming connections.
l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
// Close the listener when application closes.
defer l.Close()
fmt.Println("listening on ", CONN_PORT)
for {
//listen for an incoming connection.
conn, err := l.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
fmt.Println(conn.RemoteAddr())
p := fmt.Sprintln(conn.RemoteAddr())
ip := strings.Split(p, ":")[0]
if ip == "127.0.0.1" {
conn.Close()
}
// Handle connections in a new goroutine.
go handleRequest(conn)
}
}

Given a TCP server, how to get the connection domain address

I have a simple TCP server and, when a client connects, I want to get the domain address used to connect:
package main
import (
"fmt"
"net"
"os"
)
const (
CONN_HOST = "localhost"
CONN_PORT = "3333"
CONN_TYPE = "tcp"
)
func main() {
// Listen for incoming connections.
l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
// Close the listener when the application closes.
defer l.Close()
fmt.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
for {
// Listen for an incoming connection.
conn, err := l.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
// Handle connections in a new goroutine.
go handleRequest(conn)
}
}
// Handles incoming requests.
func handleRequest(conn net.Conn) {
// Make a buffer to hold incoming data.
buf := make([]byte, 1024)
// Read the incoming connection into the buffer.
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading:", err.Error())
}
// Send a response back to person contacting us.
conn.Write([]byte("Message received."))
// Close the connection when you're done with it.
conn.Close()
}
I tried debugging the conn net.Conn param but I can't find any reference to the domain address. Tried with http://test.127.0.0.1.xip.io:3333/ and I'm interested in getting test.127.0.0.1.xip.io somehow. Any ideas?
What you are trying to do is not possible with plain TCP. TCP works on plain IP-Addresses without domains.
To explain what is going on:
When you are establishing a connection to, e.g. example.com, first of all a DNS Lookup for example.com is done. In this case, the DNS Lookup would result in 93.184.216.34. You can read more about DNS here.
A TCP Connection with 93.184.216.34 is established after that, the original domain name is not sent with the request.
Because you sometimes need the original name the user was trying to connect to, some protocols send the domain name after connecting. HTTP for example does this via the Host header.
Maybe you can do something like that and require to send the original host first through your TCP Connection!

Dynamic port forward TCP traffic over reverse SSH tunnel

I have an SSH tunnel which dial into the server endpoint and starts up a remote listener on a port 1080. My goal is to dynamically forward any TCP traffic from that port to the client side of the tunnel. While I am able to accept TCP stream I am not able to find a way to Dial out on the client side of the SSH tunnel. It looks like the dialer I am passing is the serverConn server SSH end of the tunnel. And when I am attempting to remote, err := dialer.DialTCP("tcp4", local.RemoteAddr().(*net.TCPAddr), addr) I get the traffic dial on the remote end. I want to find a way to dial out at the client side over a reverse tunnel.
I am not looking to forward specific ports to a specific destination on local, but looking to do more dynamic port forwarding similar to ssh -D option. I am managing destination of TCP traffic myself, this is not a concern.
I have tried to create ssh.NewClient from the passed serverConn but it needs to perform an SSH handshake, and Iam only receiving raw TCP on the port 1080 so it's not a valid SSH client. Thanks.
func main(){
type Dialer interface {
DialTCP(net string, laddr, raddr *net.TCPAddr) (net.Conn, error)
}
// SSH server connection
sshConfig := &ssh.ClientConfig{
User: "tester",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Connect to SSH remote server using serverEndpoint (port 22)
serverConn, err := ssh.Dial("tcp", serverEndpoint.String(), sshConfig)
if err != nil {
log.Fatalln(fmt.Printf("Dial INTO remote server error: %s", err))
}
// Listen on remote server port (port 1080)
listener, err := serverConn.Listen("tcp",
remoteEndpoint.String())
if err != nil {
log.Fatalln(fmt.Printf("Listen open port ON remote server error: %s", err))
}
defer listener.Close()
acceptSLoop(listener, serverConn)
}
func acceptSLoop(listener net.Listener, sshClient *ssh.Client) {
fmt.Printf("Listener: %s\n", listener.Addr().String())
defer listener.Close()
for {
clientConn, err := listener.Accept()
fmt.Printf("local addr %s\n", clientConn.LocalAddr())
if err != nil {
log.Fatal(err)
}
fmt.Printf("New connection found on %s\n", listener.Addr().String())
listenSConnection(clientConn, sshClient, config)
}
}
func listenSConnection(SClientConn net.Conn, sshClient
*ssh.Client) {
// In a regular SSH I need to do
// sshConn, chans, reqs, err :=
ssh.NewServerConn(SClientConn, // config will be
passed in ..)
//
// But since it's TCP handoff I am passing socket data
directly
handleSConn(SClientConn, sshClient)
}
func handleSConn(local net.Conn, dialer Dialer) {
defer local.Close()
// Both of those establish socket from "remote" ssh side (Server) not "local" ssh side (client)
remote, err := dialer.DialTCP("tcp4", local.RemoteAddr().(*net.TCPAddr), addr)
//remote, err := net.Dial("tcp4", addr.String())
// transfer bytes from local SOCKS to remote desired endpoint over SSH
transfer(local, remote)
}
// in - local SOCKS conn, out - remote desired endpoint
func transfer(in, out net.Conn) {
//.... working
}

Resources