Golang IPv6 server - go

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)})
}

Related

get original dst failed in container

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.

Cannot forward the connection after reading connection size buffer

So I have port forwarding application using golang.
The system is simple , create listener on specific port then forward it into different IP with a same port
here is the code
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"time"
)
func main() {
tcpAddr, err: = net.ResolveTCPAddr("tcp", "127.0.0.1:2222")
if err != nil {
log.Fatalln(err)
}
//Create the TCP listener
tcpListener, err: = net.ListenTCP("tcp", tcpAddr)
if err != nil {
log.Fatalln(err)
}
conn, err: = tcpListener.Accept()
for {
go func() {
buffer: = make([] byte, 1024)
n,
_: = bufio.NewReader(conn).Read(buffer)
// Print buffer size
fmt.Println(len(buffer[: n]));
handleConnection(conn, "192.168.1.107", "2222")
}()
}
}
func forward(src, dest net.Conn) {
io.Copy(src, dest)
defer src.Close()
defer dest.Close()
}
func handleConnection(c net.Conn, ip string, port string) {
dialCheck: = net.Dialer {
Timeout: time.Duration(5 * time.Second)
}
remote,
err: = dialCheck.Dial("tcp", ip)
if err != nil {
defer c.Close()
} else {
go forward(c, remote)
go forward(remote, c)
}
}
But everytime the client wants to connect, the connection always timeout. Seems the bufreader not stopped after reading.

Why am i getting connection connection closed before server preface received in go?

I am trying to setup a rpc server and a proxy HTTP server over GRPC server on same port using grpc-gateway. Wierdly some times i am getting failed to receive server preface within timeout error randomly. Most of the times it happens on service restarts. It starts working and returns proper response after couple of retries. I am not sure what's happening. Can somebody help me out ? Here is the service startup snippet
func makeHttpServer(conn *grpc.ClientConn) *runtime.ServeMux {
router := runtime.NewServeMux()
if err := services.RegisterHealthServiceHandler(context.Background(), router, conn); err != nil {
log.Logger.Error("Failed to register gateway", zap.Error(err))
nricher
if err := services.RegisterConstraintsServiceHandler(context.Background(), router, conn); err != nil {
log.Logger.Error("Failed to register gateway", zap.Error(err))
}
return router
}
func makeGrpcServer(address string) (*grpc.ClientConn, *grpc.Server) {
grpcServer := grpc.NewServer()
services.RegisterHealthServiceServer(grpcServer, health.Svc{})
services.RegisterABCServer(grpcServer, ABC.Svc{})
conn, err := grpc.DialContext(
context.Background(),
address,
grpc.WithInsecure(),
)
if err != nil {
log.Logger.Error("Failed to dial server", zap.Error(err))
}
return conn, grpcServer
}
func httpGrpcRouter(grpcServer *grpc.Server, httpHandler *runtime.ServeMux, listener net.Listener) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 {
grpcServer.Serve(listener)
} else {
httpHandler.ServeHTTP(w, r)
}
})
}
func Start() error {
conf := config.Get()
address := fmt.Sprintf("%s:%d", conf.ServerHost, conf.ServerPort)
listener, err := net.Listen("tcp", address)
if err != nil {
log.Logger.Fatal("failed to listen: %v", zap.Error(err))
}
conn, grpcServer := makeGrpcServer(address)
router := makeHttpServer(conn)
log.Logger.Info("Starting server on address : " + address)
err = http.Serve(listener, httpGrpcRouter(grpcServer, router, listener))
return err
}
Try wrapping your router with h2c.NewHandler so the the http.Serve() call looks as follows:
err = http.Serve(listener, h2c.NewHandler(
httpGrpcRouter(grpcServer, router, listener),
&http2.Server{})
)

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))
}

How to set SetKeepAlivePeriod on a *tls.Conn

I want to increase the keep alive period of my TCP connection for both HTTP and HTTPS requests.
For HTTP requests this can be done like this:
package main
import (
"fmt"
"io"
"log"
"net"
"net/http"
"time"
)
func main() {
server := &http.Server{Addr: ":8080", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, World!")
})}
server.ConnState = func(conn net.Conn, state http.ConnState) {
if state == http.StateNew {
if err := conn.(*net.TCPConn).SetKeepAlivePeriod(1000 * time.Second); err != nil {
fmt.Println("Could not set keep alive period", err)
} else {
fmt.Println("update keep alive period")
}
}
}
log.Fatal(server.ListenAndServe())
}
For HTTPS requests this cannot be done via server.ConnState because the net.Conn that will be passed inside the function is a *tls.Conn. This connection does not expose a function like SetKeepAlivePeriod or gives access to the underlying *net.TCPConn.
func main() {
server := &http.Server{Addr: ":8080", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, World!")
})}
server.ConnState = func(conn net.Conn, state http.ConnState) {
if state == http.StateNew {
tlsConn := conn.(*tls.Conn)
// how to set SetKeepAlivePeriod
}
}
log.Fatal(server.ListenAndServeTLS("../example.crt", "../example.key"))
}
How can I set the keep alive period for tls connections?
There are (at least) two ways to do it:
Use a net.ListenConfig:
The net.ListenConfig object has a KeepAlive time.Duration field. When non-zero, this will be used to set the keep-alive on accepted connections (eg: for TCP on posix).
You can pass the listener to ServeTLS:
server := &http.Server{...}
lc := net.ListenConfig{KeepAlive: 1000 * time.Second}
ln, err := lc.Listen(context.Background(), "tcp", ":8080")
if err != nil {
panic(err)
}
defer ln.Close()
log.Fatal(server.ServeTLS(ln, "../example.crt", "../example.key"))
As mentioned, accepted TCP connections will automatically have keep-alive enabled and the period set to the specified value.
Use a tls.Config callback:
You can access the net.Conn underlying the tls.Conn by setting the tls.Config GetConfigForClient or GetCertificate callback.
It does not matter which one you're using as long as you return nil to make the TLS code fall back to the default behavior. The important part is to get access to the tls.ClientHelloInfo which has a .Conn field pointing to the underlying connection. This will the net.TCPConn.
setTCPKeepAlive := func(clientHello *tls.ClientHelloInfo) (*tls.Config, error) {
// Check that the underlying connection really is TCP.
if tcpConn, ok := clientHello.Conn.(*net.TCPConn); ok {
if err := tcpConn.SetKeepAlivePeriod(1000 * time.Second); err != nil {
fmt.Println("Could not set keep alive period", err)
} else {
fmt.Println("update keep alive period")
}
} else {
fmt.Println("TLS over non-TCP connection")
}
// Make sure to return nil, nil to let the caller fall back on the default behavior.
return nil, nil
}
tlsConfig := &tls.Config{
...
GetConfigForClient: setTCPKeepAlive,
...
}
server := &http.Server{
Addr: ":8080",
TLSConfig: tlsConfig,
}
server.ListenAndServeTLS("../example.crt", "../example.key")

Resources