get original dst failed in container - go

I am designing an http service and need to get the real destination IP. The network on the host looks like this(just like k8s kubeproxy doing):
traffic arrived host -> host ipvs -> docker bridge -> container
My service(golang) runs in container, but there is an IPVS load balancer that do dnat in front, so I need to get original dst in some way, I know I can use syscall.GetsockoptIPv6Mreq to get real dst, but it just work when service run in host,if run in container,the code failed with err:syscall.GetsockoptIPv6Mreq: no such file or directory
container use docker, run in bridge mod, this is my service code:
package main
import (
"context"
"fmt"
"net"
"net/http"
"syscall"
)
const SO_ORIGINAL_DST = 80
type contextKey struct {
key string
}
var ConnContextKey = &contextKey{"http-conn"}
func SaveConnInContext(ctx context.Context, c net.Conn) context.Context {
return context.WithValue(ctx, ConnContextKey, c)
}
func GetConn(r *http.Request) net.Conn {
return r.Context().Value(ConnContextKey).(net.Conn)
}
func getOriginalDst(conn net.Conn) (string, int, error) {
tc, ok := conn.(*net.TCPConn)
if !ok {
return "", 0, fmt.Errorf("redirect proxy only support tcp")
}
f, err := tc.File()
if err != nil {
return "", 0, fmt.Errorf("get conn file error, err: %s", err)
}
defer f.Close()
addr, err := syscall.GetsockoptIPv6Mreq(int(f.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
if err != nil {
return "", 0, fmt.Errorf("syscall.GetsockoptIPv6Mreq: %s", err)
}
p0 := int(addr.Multiaddr[2])
p1 := int(addr.Multiaddr[3])
port := p0*256 + p1
ips := addr.Multiaddr[4:8]
ip := fmt.Sprintf("%d.%d.%d.%d", ips[0], ips[1], ips[2], ips[3])
return ip, port, nil
}
func hello(w http.ResponseWriter, req *http.Request) {
conn := GetConn(req)
ip, port, err := getOriginalDst(conn)
if err != nil {
fmt.Fprintf(w, fmt.Sprintf("get original dst failed: %s", err))
return
}
msg := fmt.Sprintf("source addr: %s, server addr: %s, original dst: %s:%d \n",
req.RemoteAddr, req.Context().Value(http.LocalAddrContextKey), ip, port)
fmt.Fprintf(w, msg)
}
func version(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "v4")
}
func main() {
http.HandleFunc("/", version)
http.HandleFunc("/test", hello)
addr := "0.0.0.0:9090"
srv := http.Server{
ConnContext: SaveConnInContext,
}
//server.ListenAndServe()
ln, err := net.Listen("tcp4", addr)
if err != nil {
panic(err)
}
srv.Serve(ln)
}
if service run on the host it works ok:
sudo iptables -t nat -A OUTPUT -p tcp -m tcp --dport 8080 -j REDIRECT --to-ports 9090
curl localhost:8080/test
get expected answer
source addr: 127.0.0.1:21918, server addr: 127.0.0.1:9090, original dst: 127.0.0.1:9090
If the service run in container then the request will fail
➜ ~ curl 172.17.0.2:9090/test
get original dst failed: syscall.GetsockoptIPv6Mreq: no such file or directory
I have tried many ways. At first I thought it was a permission problem. Adding --privileged=true -u=root is still useless.
I expect the code can work and get original dst in container.

Related

Access multiple gRPC services each one running on separated port

I am running a grpc server listening on localhost:9000, with 2 separate grpc services:
AuthenticationServiceServer port:9001
JobsServiceServer port 9002
This works correctly when I am only running the Auth server, but when adding Jobs server I get:
"error": "rpc error: code = Unimplemented desc = unknown service pb.AuthService"
The server looks like this when working fine
var (
port int
authAddr string
)
func init() {
flag.IntVar(&port, "port", 9000, "api service port")
flag.StringVar(&authAddr, "auth_addr", "localhost:9001", "authenticaton service address")
flag.Parse()
}
func main() {
log.Print("starting main service")
conn, err := grpc.Dial(authAddr, grpc.WithInsecure())
if err != nil {
log.Panicln(err)
}
defer conn.Close()
authSvcClient := pb.NewAuthServiceClient(conn)
authHandlers := resthandlers.NewAuthHandlers(authSvcClient)
authRoutes := routes.NewAuthRoutes(authHandlers)
router := mux.NewRouter().StrictSlash(true)
routes.Install(router, authRoutes)
log.Printf("API service running on [::]:%d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), routes.WithCORS(router)))
}
On some other attempts I got this error.
"error": "rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp: missing address""\
here's the code which returned an error
var (
port int
authAddr string
jobAddr string
)
func init() {
flag.IntVar(&port, "port", 9000, "api service port")
flag.StringVar(&authAddr, "auth_addr", "localhost:9001", "authenticaton service address")
flag.StringVar(&jobAddr, "job_addr", "localhost:9002", "job service address")
flag.Parse()
}
func main() {
log.Print("starting main service")
conn, err := grpc.Dial(authAddr, grpc.WithInsecure())
if err != nil {
log.Panicln(err)
}
defer conn.Close()
jobConn, err := grpc.Dial(jobAddr, grpc.WithInsecure())
if err != nil {
log.Panicln(err)
}
defer jobConn.Close()
jobSvcClient := pb.NewJobPostingServiceClient(jobConn)
jobHandlers := resthandlers.NewJobPostingHandlers(jobSvcClient)
jobRoutes := routes.NewJobPostingRoutes(jobHandlers)
authSvcClient := pb.NewAuthServiceClient(conn)
authHandlers := resthandlers.NewAuthHandlers(authSvcClient)
authRoutes := routes.NewAuthRoutes(authHandlers)
router := mux.NewRouter().StrictSlash(true)
routes.Install(router, authRoutes)
routes.Install(router, jobRoutes)
log.Printf("API service running on [::]:%d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), routes.WithCORS(router)))
}

Freeing a port before running a server instant

I want to run a go server at linux based system, it happened in soe cases that i found the same port is busy with another app, so i want to kill the running process at that port, and run my server instead, so I wrote the below code:
func main() {
host := "127.0.0.1"
port := "8070"
server := http.Server{
Addr: "127.0.0.1:8070",
}
http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))
ln, err := net.Listen("tcp", ":"+port)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
// kill the running process at this port
_, err := exec.Command("fuser", "-k", "8070/tcp").Output()
if err != nil {
fmt.Printf("Failed to kill process at Port %q\n", port)
} else {
fmt.Printf("TCP Port %q is available\n", port)
server.ListenAndServe()
}
} else {
ln.Close()
server.ListenAndServe()
}
}
I was able to get the response TCP Port 8070 is available whihc means there was another running process and it had been killed, but my app is closed directly without running my server at the same port which had been already closed!
hajsf#AIS-DM-YOUSEF-L:~/myapp$ go run myapp
Can't listen on port "8070": listen tcp :8070: bind: address already in use
TCP Port "8070" is available
hajsf#AIS-DM-YOUSEF-L:~/myapp$
In the origional terminal (the old instance of the app0 I got;
hajsf#AIS-DM-YOUSEF-L:~/myapp$ go run myapp
signal: killed
hajsf#AIS-DM-YOUSEF-L:~/myapp$
As you can see in the response How to kill a process running on particular port in Linux?
Your command _, err := exec.Command("fuser", "-k", "8070/tcp").Output() kill the process but doesn't cleanup the resource ie: port listening.
The port is put into TIME_WAIT state after the parent process is killed.
And you need to wait some time your OS/Kernel cleanup the port/socket
A better alternative is to handle the kill sigint from fuser and do a graceful shutdown
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"time"
)
func runServer(host, port string) {
server := http.Server{
Addr: host + ":" + port,
}
http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))
go func() {
if err := server.ListenAndServe(); err != nil {
}
}()
// Setting up signal capturing
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
// Waiting for SIGINT (kill -2)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
// handle err
}
}
func main() {
host := "127.0.0.1"
port := "8070"
ln, err := net.Listen("tcp", host+":"+port)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
// kill the running process at this port
cmd := exec.Command("fuser", "-k", "-2", "8070/tcp")
fmt.Println("wait")
err = cmd.Run()
if err != nil {
fmt.Printf("Failed to kill process at Port %q\n", port)
} else {
fmt.Printf("TCP Port %q is available\n", port)
runServer(host, port)
}
} else {
ln.Close()
runServer(host, port)
}
}
worked well on CentOS 7, but same issue with a Ubuntu server. #MoiioM 's answer is fine. But if another app is not the golang app itself, here is another way: set a SO_REUSEADDR flag on the socket
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"syscall"
"golang.org/x/sys/unix"
)
func reusePort(network, address string, conn syscall.RawConn) error {
return conn.Control(func(descriptor uintptr) {
syscall.SetsockoptInt(int(descriptor), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
})
}
func main() {
port := "8070"
server := http.Server{}
http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))
ln, err := net.Listen("tcp", ":"+port)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
// kill the running process at this port
_, err := exec.Command("fuser", "-k", "8070/tcp").Output()
if err != nil {
fmt.Printf("Failed to kill process at Port %q\n", port)
} else {
fmt.Printf("TCP Port %q is available\n", port)
config := &net.ListenConfig{Control: reusePort}
listener, err := config.Listen(context.Background(), "tcp", ":"+port)
if err != nil {
panic(err)
}
if err := server.Serve(listener); err != nil {
panic(err)
}
}
} else {
if err := server.Serve(ln); err != nil {
panic(err)
}
}
}
I was able to solve it with panic ... recover as below:
package main
import (
"fmt"
"net"
"net/http"
"onsen/resources"
"onsen/routes"
"os"
"os/exec"
)
func main() {
http.Handle("/webUI/", http.StripPrefix("/webUI/", http.FileServer(http.FS(resources.WebUI))))
http.Handle("/www/", http.StripPrefix("/www/", http.FileServer(http.Dir("./www"))))
for key, value := range routes.Urls() {
http.HandleFunc(key, value)
}
runServer()
}
func runServer() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
runServer()
}
}()
host := "127.0.0.1"
port := "8070"
server := http.Server{
Addr: fmt.Sprintf("%v:%v", host, port),
}
ln, err := net.Listen("tcp", ":"+port)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't listen on port %q: %s \n", port, err)
// kill the running process at this port
_, err := exec.Command("fuser", "-k", "8070/tcp").Output()
if err != nil {
panic(fmt.Sprintf("Failed to kill process at Port %q\n", port))
} else {
fmt.Printf("TCP Port %q is available\n", port)
fmt.Println("server started...")
if err := server.Serve(ln); err != nil {
panic(err)
}
}
} else {
fmt.Println("server started...")
if err := server.Serve(ln); err != nil {
panic(err)
}
}
}
And got the output as below:

HTTP GET, from specific local IP, using alternate DNS

First, newbie to Go. Next, I am trying to run the following code below and experiencing this error
2021/06/16 18:24:15 Get "https://www.cnn.com": dial tcp: lookup www.cnn.com on 192.168.100.200:53: dial udp: address 192.168.100.65: mismatched local address type
exit status 1
My end goal is to use a predefined DNS server (alternate from the OS) and create a HTTP/S connection using a specific local IP address. I also suspect this could be accomplished with less code, so would love to understand this more from someone more familiar with Go.
package main
import (
"context"
"io/ioutil"
"log"
"net"
"net/http"
"time"
)
func main() {
q := net.ParseIP("192.168.100.65")
addr := &net.IPAddr{q,""}
var (
dnsResolverIP = "8.8.8.8:53"
dnsResolverProto = "udp"
dnsResolverTimeoutMs = 5000
)
dialer := &net.Dialer{
Resolver: &net.Resolver {
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer {
LocalAddr: addr,
Timeout: time.Duration(dnsResolverTimeoutMs) * time.Millisecond,
}
return d.DialContext(ctx, dnsResolverProto, dnsResolverIP)
},
},
}
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, addr)
}
http.DefaultTransport.(*http.Transport).DialContext = dialContext
httpClient := &http.Client{}
// Testing the new HTTP client with the custom DNS resolver.
resp, err := httpClient.Get("https://www.cnn.com")
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
log.Println(string(body))
}

Golang IPv6 server

How to create ipv6 server. ipv4 server looks like
package main
import (
"fmt"
"net/http"
)
func h(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Test")
}
func main() {
http.HandleFunc("/", h)
http.ListenAndServe(":80", nil)
}
but how to listen ipv6 in 80 port
It already is listening on ipv6 (as well as ipv4).
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
// If srv.Addr is blank, ":http" is used.
// ListenAndServe always returns a non-nil error.
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
If you want to only listen on ipv6, you can do
func ListenAndServe(addr string, handler Handler) error {
srv := &http.Server{Addr: addr, Handler: handler}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp6", addr) // <--- tcp6 here
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

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