I am trying to connect two peers behind same NAT using UDP hole punching but whenever 1 client send UDP packet to other client, it receives error:
read udp4 192.168.0.xx:xx->192.168.0.xx:xx: recvfrom: connection refused
I followed implementation from here.
Some background about my code:
server.go
init UDP server
start listening for packets on given IP:PORT (user input)
a. receive packet of type new_connection for new client (does not exist already), store client_name, IP:PORT mapping in a variable and send connection_success in response.
b. receive packet of type connection_request from client A requesting from B's details, send mapping_response packet to client A with B's details and send client B peer_request packet with A's details.
code for server.go:
package main
import (
"net"
"fmt"
"strings"
"os"
)
var mapping map[string]*net.UDPAddr
func initServer(addr string) (*net.UDPConn, error) {
s, err := net.ResolveUDPAddr("udp4", addr)
if err != nil {
fmt.Println(err.Error())
return nil, err
}
c, err := net.ListenUDP("udp4", s)
return c, nil
}
func main() {
port := ":" + os.Args[1]
mapping = make(map[string]*net.UDPAddr)
c, err := initServer(port)
defer c.Close()
if err != nil {
return
}
listen(c)
}
func listen(c *net.UDPConn) {
buffer := make([]byte, 1024)
for {
n, addr, err := c.ReadFromUDP(buffer)
if err != nil {
fmt.Println(err.Error())
} else {
s := strings.Split(string(buffer[0:n]), " ")
packetType, packetData := s[0], s[1]
go handle(c, packetType, packetData, addr)
}
}
}
func addMapping(c *net.UDPConn, data string, addr *net.UDPAddr) {
if _, ok := mapping[data]; !ok {
mapping[data] = addr
fmt.Println(mapping)
data := generateData("connection_success", "hello")
sendResponse(c, data, addr)
}
}
func makeConnection(c *net.UDPConn, name string, addr1 *net.UDPAddr) {
addr2 := getAddress(name)
var data string
data = generateData("mapping_response", addr2.String())
sendResponse(c, data, addr1)
data = generateData("peer_request", addr1.String())
sendResponse(c, data, addr2)
}
func handle(c *net.UDPConn, packetType string, packetData string, addr *net.UDPAddr) {
switch packetType {
case "new_connection":
addMapping(c, packetData, addr)
case "connection_request":
makeConnection(c, packetData, addr)
default:
break
}
}
func getAddress(name string) *net.UDPAddr {
return mapping[name]
}
func generateData(packetType string, packetData string) string {
return fmt.Sprintf("%s %s", packetType, packetData)
}
func sendResponse(c *net.UDPConn, data string, addr *net.UDPAddr) {
fmt.Println(data, addr)
_, err := c.WriteToUDP([]byte(data), addr)
if err != nil {
fmt.Println(err.Error())
}
}
client.go
init UDP client for server
get client_name as input (user input)
send packet of type new_connection to server every 10s.
a. receive packet of type connection_success, ask user whom to connect with (client B) and send connection_request packet to server asking B's details.
b. receive packet of type mapping_response, try to connect to client B by using sending establish_connection packet to client B every 3s.
c. receive packet of type peer_request, try same thing as 3b from client B side.
code for client.go:
package main
import (
"fmt"
"bufio"
"os"
"net"
"time"
"strings"
)
func initClient(addr string) (*net.UDPConn, error) {
s, err := net.ResolveUDPAddr("udp4", addr)
c, err := net.DialUDP("udp4", nil, s)
if err != nil {
fmt.Println(err.Error())
return nil, err
}
return c, nil
}
func main() {
serverAddress := os.Args[1]
c, err := initClient(serverAddress)
if err != nil {
return
}
defer c.Close()
name := getInput()
go addMappingToServer(c, name)
listen(c)
}
func addMappingToServer(c *net.UDPConn, name string) {
for {
data := generateData("new_connection", name)
sendPacket(c, data)
time.Sleep(10*time.Second)
}
}
func listen(c *net.UDPConn) {
buffer := make([]byte, 1024)
for {
n, addr, err := c.ReadFromUDP(buffer)
if err != nil {
fmt.Println(err.Error())
} else {
s := strings.Split(string(buffer[0:n]), " ")
packetType, packetData := s[0], s[1]
go handle(c, packetType, packetData, addr)
}
}
}
func handle(c *net.UDPConn, packetType string, packetData string, addr *net.UDPAddr) {
fmt.Println(packetType, packetData)
switch packetType {
case "connection_success":
getPeerMapping(c)
case "peer_request":
establishConnectionToPeer(packetData)
case "mapping_response":
establishConnectionToPeer(packetData)
default:
break
}
}
func generateData(packetType string, packetData string) string {
return fmt.Sprintf("%s %s", packetType, packetData)
}
func sendPacket(c *net.UDPConn, data string) {
_, err := c.Write([]byte(data))
if err != nil {
fmt.Println(err.Error())
}
}
func sendPacketToAddr(c *net.UDPConn, data string, addr *net.UDPAddr) {
_, err := c.WriteToUDP([]byte(data), addr)
if err != nil {
fmt.Println(err.Error())
}
}
func getInput() string {
reader := bufio.NewReader(os.Stdin)
fmt.Print(">> ")
text, _ := reader.ReadString('\n')
return text[0:len(text)-1]
}
func getPeerMapping(c *net.UDPConn) {
peer := getInput()
data := generateData("connection_request", peer)
sendPacket(c, data)
}
func establishConnectionToPeer(peerAddr string) {
c, err := initClient(peerAddr)
if err != nil {
fmt.Println(err.Error())
return
}
defer c.Close()
buffer := make([]byte, 1024)
for {
data := generateData("establish_connection", "hello")
sendPacket(c, data)
n, addr, err := c.ReadFromUDP(buffer)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("packet recieved from peer data : %s, addr : %#v", string(buffer[0:n]), addr)
break
}
time.Sleep(3*time.Second)
}
}
The problem that I am facing is in line
n, addr, err := c.ReadFromUDP(buffer)
inside establishConnectionToPeer function and I am getting error I mentioned above (connection refused).
Scenario
Client A and server are on 1 host and client B on another. Both hosts are behind same NAT.
Sample output:
for server:
map[abc:192.168.0.78:45999 xyz:192.168.0.10:44207]
meaning client abc's IP:PORT is 192.168.0.78:45999 and for xyz, it is 192.168.0.10:44207
for client abc:
read udp4 192.168.0.78:40934->192.168.0.10:44207: recvfrom: connection refused
for client xyz:
read udp4 192.168.0.10:49708->192.168.0.78:45999: recvfrom: connection refused
Problem is clear: both clients opened another port (different from what they opened to communicate from server)
What is wrong with the client code above, and what can I do to resolve it? It would be great if someone can provide (or modify above) code piece to make it start working.
I assume it's not the case that my router does not support UDP hole punching because I can use skype using it (and I read from here that skype uses hole punching).
Related
Go version: go1.14.4 darwin/amd64
TCP server
package main
import (
"fmt"
"io"
"net"
"time"
)
const (
maxBufLen int = 4 * 1024
)
type Server struct {
}
func (s *Server) Start() {
listener, err := net.Listen("tcp", "localhost:9001")
if err != nil {
panic(err)
}
var connections []net.Conn
defer func() {
for _, conn := range connections {
// ignore
_ = conn.Close()
}
}()
// loop...
for {
connected, err := listener.Accept()
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Temporary() {
// temporary err will bi ignored
continue
} else {
fmt.Println(err)
return
}
}
go handleConn(connected, true)
connections = append(connections, connected)
}
}
func handleConn(conn net.Conn, server bool) {
buf := make([]byte, maxBufLen)
for {
// read
setTimeout(conn)
_, err := conn.Read(buf)
if err != nil {
if ne, ok := err.(net.Error); ok && (ne.Timeout() || ne.Temporary()) {
fmt.Println("need continue...")
continue
}
if err == io.EOF {
fmt.Println("EOF")
break
}
// other...
panic(err)
}
// handle recv msg.
s := string(buf)
if server {
//fmt.Println("server recv req ", s)
} else {
fmt.Println("client recv resp ", s)
}
if server {
output := "hi " + s
ob := []byte(output)
_, err := conn.Write(ob)
if err != nil {
fmt.Println(err)
break
}
}
}
}
func setTimeout(conn net.Conn) {
setErr := conn.SetReadDeadline(time.Now().Add(20 * time.Second))
if setErr != nil {
panic(setErr)
}
}
TCP client
package main
import (
"net"
"time"
)
type Client struct {
Exit chan struct{}
}
func (c *Client) Start() {
conn, err := net.Dial("tcp", "localhost:9001")
if err != nil {
panic(err)
}
defer conn.Close()
go handleWrite(conn)
go handleConn(conn, false)
<-c.Exit
}
func handleWrite(conn net.Conn) {
for {
input := "carryxyh"
_, err := conn.Write([]byte(input))
if err != nil {
panic(err)
}
<-time.After(100 * time.Second)
}
}
Main function
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
type Starter interface {
Start()
}
func main() {
var s Server
var c Client
go s.Start()
go c.Start()
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
c.Exit <- struct{}{}
fmt.Println("exit")
}
Running package main will print :
client recv resp hi carryxyh
client recv resp carryxyh
Printed twice. But from the program point of view, the server only writes information to the client once, and the content of the information should be hi carryxyh. But the client printed carryxyh in addition to hi carryxyh, which made me very confused.
During the investigation, I accidentally modified a line of code, and the program immediately returned to normal:
modeifycode
As shown above, I modified the server response information: output := "hi "+ s => output := "hi ", at this time the program only prints client recv resp hi.
This makes me completely confused, can anyone help me solve this problem? It would be better if there is a troubleshooting idea.
enter image description here
这里你忽略了读取的字节长度,可能返回0字节,你又把buf的内容 又回写给client了.
_, err := conn.Read(buf) 改成 bytesRead, err := conn.Read(buf) ;
if bytesRead <= 0 { // 没有buf 可以读取 }
--
eg:
if bytesRead == 0 && err == nil {
err = io.EOF
log.Errorf("[network] ReadOnce maybe always return (0, nil) and causes dead loop, Connection = %d, Local Address = %+v, Remote Address = %+v",
c.id, c.rawConnection.LocalAddr(), c.RemoteAddr())
}
server.go
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
_ "net/http/pprof"
"sync"
"syscall"
)
type ConnSet struct {
data map[int]net.Conn
mutex sync.Mutex
}
func (m *ConnSet) Update(id int, conn net.Conn) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if _, ok := m.data[id]; ok {
fmt.Printf("add: key %d existed \n", id)
return fmt.Errorf("add: key %d existed \n", id)
}
m.data[id] = conn
return nil
}
var connSet = &ConnSet{
data: make(map[int]net.Conn),
}
func main() {
setLimit()
ln, err := net.Listen("tcp", ":12345")
if err != nil {
panic(err)
}
go func() {
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatalf("pprof failed: %v", err)
}
}()
var connections []net.Conn
defer func() {
for _, conn := range connections {
conn.Close()
}
}()
for {
conn, e := ln.Accept()
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
log.Printf("accept temp err: %v", ne)
continue
}
log.Printf("accept err: %v", e)
return
}
port := conn.RemoteAddr().(*net.TCPAddr).Port
connSet.Update(port, conn)
go handleConn(conn)
connections = append(connections, conn)
if len(connections)%100 == 0 {
log.Printf("total number of connections: %v", len(connections))
}
}
}
func handleConn(conn net.Conn) {
io.Copy(ioutil.Discard, conn)
}
func setLimit() {
var rLimit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
panic(err)
}
rLimit.Cur = rLimit.Max
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
panic(err)
}
log.Printf("set cur limit: %d", rLimit.Cur)
}
client.go
package main
import (
"bytes"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"strconv"
"sync"
"syscall"
"time"
)
var portFlag = flag.Int("port", 12345, "port")
type ConnSet struct {
data map[int]net.Conn
mutex sync.Mutex
}
func (m *ConnSet) Update(id int, conn net.Conn) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if _, ok := m.data[id]; ok {
fmt.Printf("add: key %d existed \n", id)
return fmt.Errorf("add: key %d existed \n", id)
}
m.data[id] = conn
return nil
}
var connSet = &ConnSet{
data: make(map[int]net.Conn),
}
func echoClient() {
addr := fmt.Sprintf("127.0.0.1:%d", *portFlag)
dialer := net.Dialer{}
conn, err := dialer.Dial("tcp", addr)
if err != nil {
fmt.Println("ERROR", err)
os.Exit(1)
}
port := conn.LocalAddr().(*net.TCPAddr).Port
connSet.Update(port, conn)
defer conn.Close()
for i := 0; i < 10; i++ {
s := fmt.Sprintf("%s", strconv.Itoa(i))
_, err := conn.Write([]byte(s))
if err != nil {
log.Println("write error: ", err)
}
b := make([]byte, 1024)
_, err = conn.Read(b)
switch err {
case nil:
if string(bytes.Trim(b, "\x00")) != s {
log.Printf("resp req not equal, req: %d, res: %s", i, string(bytes.Trim(b, "\x00")))
}
case io.EOF:
fmt.Println("eof")
break
default:
fmt.Println("ERROR", err)
break
}
}
time.Sleep(time.Hour)
if err := conn.Close(); err != nil {
log.Printf("client conn close err: %s", err)
}
}
func main() {
flag.Parse()
setLimit()
before := time.Now()
var wg sync.WaitGroup
for i := 0; i < 20000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
echoClient()
}()
}
wg.Wait()
fmt.Println(time.Now().Sub(before))
}
func setLimit() {
var rLimit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
panic(err)
}
rLimit.Cur = rLimit.Max
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit); err != nil {
panic(err)
}
log.Printf("set cur limit: %d", rLimit.Cur)
}
running command
go run server.go
---
go run client.go
server running screenshot
The client simultaneously initiates 20,000 connections to the server, and the server accepted two remotePort connections that are exactly the same (in a extremely short period of time).
I try to use tcpconn.py from bcc (patched from tcpconnect.py by add skc_num(aka: local_port))
tcpaccept.py
tracing the connection, and also finds that the remote port is duplicated on the server side when there is no duplicate on the client side
In my understanding, the 5-tuple of the socket will not be duplicated, Why the server accepted two sockets with exactly the same remote port?
My test environment:
Fedora 31, kernel version 5.3.15 x86_64
and
Ubuntu 18.04.3 LTS, kernel version 4.19.1 x86_64
go version go1.13.5 linux/amd64
wireshark:
server TCP Keep-Alive to both ACK & PSH+ACK
server TCP Keep-Alive to PSH+ACK only
Connection is added to the map data map[int]net.Conn when it is established, but when connection is closed it is not removed from the map. So if connection gets closed its port become free and could be reused by an Operation System for next connection. That's a reason why you can see duplicate ports.
Try to remove port from map when they are get closed.
I am trying to learn Golang (and net pkg). I have written a simple client
and server. Client sends two messages in sequence and reads server msg.
Server reads two messages from a client and sends one message - this hand
shake is hanging. Could you suggest what I am missing? Thanks.
When client combine two messages into one and sends it (so the server reads
only once), this does not hang.
/* ======= Server start ==============================================*/
package main
import (
"fmt"
"net"
"os"
)
func processClients(cfd net.Conn) {
clientMsg := make([]byte, 128)
_, err := cfd.Read(clientMsg) // read client msg
if err != nil {
return
}
clientID := make([]byte, 10)
_, err = cfd.Read(clientID) // read client id
if err != nil {
return
}
clientMsg = append(clientMsg, clientID...) // TBD: handle len boundaries
fmt.Println("Server received msg:", string(clientMsg))
_, err = cfd.Write(clientMsg[0:len(clientMsg)]) // ack client msg
if err != nil {
return
}
}
func initServer() {
lfd, err := net.Listen("unix", "/tmp/unix.socket")
if err != nil {
fmt.Println("net.Listen error", err)
return
}
defer os.Remove("/tmp/unix.socket")
fmt.Println("Listening on /tmp/unix.socket")
for {
fd, err := lfd.Accept()
if err != nil {
fmt.Println("Accept() error", fd)
break
}
go processClients(fd)
}
}
func main() {
initServer()
}
/* ======= Server end ===============================================*/
/* ======= Client Start ===============================================*/
package main
import (
"fmt"
"net"
"sync"
)
var wg sync.WaitGroup
var clientID int
func sendReq() {
fd, err := net.Dial("unix", "/tmp/unix.socket")
if err != nil {
return
}
defer fd.Close()
clientID++
id := fmt.Sprintf("%v", clientID)
_, err = fd.Write([]byte(id)) //send client id
if err != nil {
return
}
_, err = fd.Write([]byte("Hello there, I am client")) // send client msg
if err != nil {
return
}
serverMsg := make([]byte, 128)
_, err = fd.Read(serverMsg) // Read server msg
if err != nil {
return
}
fmt.Println(string(serverMsg))
wg.Done()
}
func main() {
wg.Add(5)
go sendReq()
go sendReq()
go sendReq()
go sendReq()
go sendReq()
wg.Wait()
}
/* ======= Client end ===============================================*/
I'm new to Go and evaluating it for a project.
I'm trying to write a custom handler to serve files with net/http.
I can't use the default http.FileServer() handler because I need to have access to the underlying socket (the internal net.Conn) so I can perform some informational platform specific "syscall" calls on it (mainly TCP_INFO).
More precisly: I need to access the underlying socket of the http.ResponseWriter in the handler function:
func myHandler(w http.ResponseWriter, r *http.Request) {
...
// I need the net.Conn of w
...
}
used in
http.HandleFunc("/", myHandler)
Is there a way to this. I looked at how websocket.Upgrade does this but it uses Hijack() which is 'too much' because then I have to code 'speaking http' over the raw tcp socket I get. I just want a reference to the socket and not taking over completely.
After Issue #30694 is completed, it looks like Go 1.13 will probably support storing the net.Conn in the Request Context, which makes this fairly clean and simple:
package main
import (
"net/http"
"context"
"net"
"log"
)
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 main() {
http.HandleFunc("/", myHandler)
server := http.Server{
Addr: ":8080",
ConnContext: SaveConnInContext,
}
server.ListenAndServe()
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
...
}
Until then ... For a server listening on a TCP port, net.Conn.RemoteAddr().String() is unique for each connection and is available to the http.Handler as r.RemoteAddr, so it can be used as a key to a global map of Conns:
package main
import (
"net/http"
"net"
"fmt"
"log"
)
var conns = make(map[string]net.Conn)
func ConnStateEvent(conn net.Conn, event http.ConnState) {
if event == http.StateActive {
conns[conn.RemoteAddr().String()] = conn
} else if event == http.StateHijacked || event == http.StateClosed {
delete(conns, conn.RemoteAddr().String())
}
}
func GetConn(r *http.Request) (net.Conn) {
return conns[r.RemoteAddr]
}
func main() {
http.HandleFunc("/", myHandler)
server := http.Server{
Addr: ":8080",
ConnState: ConnStateEvent,
}
server.ListenAndServe()
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
...
}
For a server listening on a UNIX socket, net.Conn.RemoteAddr().String() is always "#", so the above doesn't work. To make this work, we can override net.Listener.Accept(), and use that to override net.Conn.RemoteAddr().String() so that it returns a unique string for each connection:
package main
import (
"net/http"
"net"
"os"
"golang.org/x/sys/unix"
"fmt"
"log"
)
func main() {
http.HandleFunc("/", myHandler)
listenPath := "/var/run/go_server.sock"
l, err := NewUnixListener(listenPath)
if err != nil {
log.Fatal(err)
}
defer os.Remove(listenPath)
server := http.Server{
ConnState: ConnStateEvent,
}
server.Serve(NewConnSaveListener(l))
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
if unixConn, isUnix := conn.(*net.UnixConn); isUnix {
f, _ := unixConn.File()
pcred, _ := unix.GetsockoptUcred(int(f.Fd()), unix.SOL_SOCKET, unix.SO_PEERCRED)
f.Close()
log.Printf("Remote UID: %d", pcred.Uid)
}
}
var conns = make(map[string]net.Conn)
type connSaveListener struct {
net.Listener
}
func NewConnSaveListener(wrap net.Listener) (net.Listener) {
return connSaveListener{wrap}
}
func (self connSaveListener) Accept() (net.Conn, error) {
conn, err := self.Listener.Accept()
ptrStr := fmt.Sprintf("%d", &conn)
conns[ptrStr] = conn
return remoteAddrPtrConn{conn, ptrStr}, err
}
func GetConn(r *http.Request) (net.Conn) {
return conns[r.RemoteAddr]
}
func ConnStateEvent(conn net.Conn, event http.ConnState) {
if event == http.StateHijacked || event == http.StateClosed {
delete(conns, conn.RemoteAddr().String())
}
}
type remoteAddrPtrConn struct {
net.Conn
ptrStr string
}
func (self remoteAddrPtrConn) RemoteAddr() (net.Addr) {
return remoteAddrPtr{self.ptrStr}
}
type remoteAddrPtr struct {
ptrStr string
}
func (remoteAddrPtr) Network() (string) {
return ""
}
func (self remoteAddrPtr) String() (string) {
return self.ptrStr
}
func NewUnixListener(path string) (net.Listener, error) {
if err := unix.Unlink(path); err != nil && !os.IsNotExist(err) {
return nil, err
}
mask := unix.Umask(0777)
defer unix.Umask(mask)
l, err := net.Listen("unix", path)
if err != nil {
return nil, err
}
if err := os.Chmod(path, 0660); err != nil {
l.Close()
return nil, err
}
return l, nil
}
Note that although in current implementation http.ResponseWriter is a *http.response (note the lowercase!) which holds the connection, the field is unexported and you can't access it.
Instead take a look at the Server.ConnState hook: you can "register" a function which will be called when the connection state changes, see http.ConnState for details. For example you will get the net.Conn even before the request enters the handler (http.StateNew and http.StateActive states).
You can install a connection state listener by creating a custom Server like this:
func main() {
http.HandleFunc("/", myHandler)
s := &http.Server{
Addr: ":8081",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
ConnState: ConnStateListener,
}
panic(s.ListenAndServe())
}
func ConnStateListener(c net.Conn, cs http.ConnState) {
fmt.Printf("CONN STATE: %v, %v\n", cs, c)
}
This way you will have exactly the desired net.Conn even before (and also during and after) invoking the handler. The downside is that it is not "paired" with the ResponseWriter, you have to do that manually if you need that.
You can use an HttpHijacker to take over the TCP connection from the ResponseWriter. Once you've done that you're free to use the socket to do whatever you want.
See http://golang.org/pkg/net/http/#Hijacker, which also contains a good example.
This can be done with reflection. it's a bit "dirty" but it works:
package main
import "net/http"
import "fmt"
import "runtime"
import "reflect"
func myHandler(w http.ResponseWriter, r *http.Request) {
ptrVal := reflect.ValueOf(w)
val := reflect.Indirect(ptrVal)
// w is a "http.response" struct from which we get the 'conn' field
valconn := val.FieldByName("conn")
val1 := reflect.Indirect(valconn)
// which is a http.conn from which we get the 'rwc' field
ptrRwc := val1.FieldByName("rwc").Elem()
rwc := reflect.Indirect(ptrRwc)
// which is net.TCPConn from which we get the embedded conn
val1conn := rwc.FieldByName("conn")
val2 := reflect.Indirect(val1conn)
// which is a net.conn from which we get the 'fd' field
fdmember := val2.FieldByName("fd")
val3 := reflect.Indirect(fdmember)
// which is a netFD from which we get the 'sysfd' field
netFdPtr := val3.FieldByName("sysfd")
fmt.Printf("netFDPtr= %v\n", netFdPtr)
// which is the system socket (type is plateform specific - Int for linux)
if runtime.GOOS == "linux" {
fd := int(netFdPtr.Int())
fmt.Printf("fd = %v\n", fd)
// fd is the socket - we can call unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(fd),....) on it for instance
}
fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", myHandler)
err := http.ListenAndServe(":8081", nil)
fmt.Println(err.Error())
}
Ideally the library should be augmented with a method to get the underlying net.Conn
Expanding on KGJV's answer, a working solution using reflection to maintain a map of connections indexed by net.Conn instance memory addresses.
Instances of net.Conn can be looked up by pointer, and pointers derived using reflection against http.Response.
It's a bit nasty, but given you can't access unpublished fields with reflection it's the only way I could see of doing it.
// Connection array indexed by connection address
var conns = make(map[uintptr]net.Conn)
var connMutex = sync.Mutex{}
// writerToConnPrt converts an http.ResponseWriter to a pointer for indexing
func writerToConnPtr(w http.ResponseWriter) uintptr {
ptrVal := reflect.ValueOf(w)
val := reflect.Indirect(ptrVal)
// http.conn
valconn := val.FieldByName("conn")
val1 := reflect.Indirect(valconn)
// net.TCPConn
ptrRwc := val1.FieldByName("rwc").Elem()
rwc := reflect.Indirect(ptrRwc)
// net.Conn
val1conn := rwc.FieldByName("conn")
val2 := reflect.Indirect(val1conn)
return val2.Addr().Pointer()
}
// connToPtr converts a net.Conn into a pointer for indexing
func connToPtr(c net.Conn) uintptr {
ptrVal := reflect.ValueOf(c)
return ptrVal.Pointer()
}
// ConnStateListener bound to server and maintains a list of connections by pointer
func ConnStateListener(c net.Conn, cs http.ConnState) {
connPtr := connToPtr(c)
connMutex.Lock()
defer connMutex.Unlock()
switch cs {
case http.StateNew:
log.Printf("CONN Opened: 0x%x\n", connPtr)
conns[connPtr] = c
case http.StateClosed:
log.Printf("CONN Closed: 0x%x\n", connPtr)
delete(conns, connPtr)
}
}
func HandleRequest(w http.ResponseWriter, r *http.Request) {
connPtr := writerToConnPtr(w)
connMutex.Lock()
defer connMutex.Unlock()
// Requests can access connections by pointer from the responseWriter object
conn, ok := conns[connPtr]
if !ok {
log.Printf("error: no matching connection found")
return
}
// Do something with connection here...
}
// Bind with http.Server.ConnState = ConnStateListener
It looks like you cannot "pair" a socket (or net.Conn) to either http.Request or http.ResponseWriter.
But you can implement your own Listener:
package main
import (
"fmt"
"net"
"net/http"
"time"
"log"
)
func main() {
// init http server
m := &MyHandler{}
s := &http.Server{
Handler: m,
}
// create custom listener
nl, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
l := &MyListener{nl}
// serve through custom listener
err = s.Serve(l)
if err != nil {
log.Fatal(err)
}
}
// net.Conn
type MyConn struct {
nc net.Conn
}
func (c MyConn) Read(b []byte) (n int, err error) {
return c.nc.Read(b)
}
func (c MyConn) Write(b []byte) (n int, err error) {
return c.nc.Write(b)
}
func (c MyConn) Close() error {
return c.nc.Close()
}
func (c MyConn) LocalAddr() net.Addr {
return c.nc.LocalAddr()
}
func (c MyConn) RemoteAddr() net.Addr {
return c.nc.RemoteAddr()
}
func (c MyConn) SetDeadline(t time.Time) error {
return c.nc.SetDeadline(t)
}
func (c MyConn) SetReadDeadline(t time.Time) error {
return c.nc.SetReadDeadline(t)
}
func (c MyConn) SetWriteDeadline(t time.Time) error {
return c.nc.SetWriteDeadline(t)
}
// net.Listener
type MyListener struct {
nl net.Listener
}
func (l MyListener) Accept() (c net.Conn, err error) {
nc, err := l.nl.Accept()
if err != nil {
return nil, err
}
return MyConn{nc}, nil
}
func (l MyListener) Close() error {
return l.nl.Close()
}
func (l MyListener) Addr() net.Addr {
return l.nl.Addr()
}
// http.Handler
type MyHandler struct {
// ...
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
I'm looking for a good solution for a client/server communication with UDP sockets in Go language.
The examples I found on the Internet show me how to send data to the server, but they do not teach how to send them back to the client.
To demonstrate, my program does the following:
My client program creates a socket on the 4444 port, like this:
con, err := net.Dial("udp", "127.0.0.1:4444")
I sent a string and the local address to the server, so it could print the string and send an OK message. I am using gob for this:
enc := gob.NewEncoder(con)
enc.Encode(Data{"test", con.LocalAddr().String()})
My Data struct looks like this:
type Data struct{
Msg string
Addr string
}
My server listens to the 4444 port and decodes the Gob correctly, but how can I send the OK message back? I'm using the client address to do so (on the server .go file):
con, err := net.Dial("udp", data.Addr)
Then, I get an error code:
write udp 127.0.0.1:35290: connection refused
When the client tries to connect to the Server's port 4444, the client creates a port with a random number (in this case, 35290) so they can communicate. I know I shouldn't be passing the client's address to the server, but conn.RemoteAddress() does not work. A solution that discovers the client's address would be most appreciated.
Obs.: I know there is ReadFromUDP, so I can read the package. Should I read it, discover the client's address, and send the data to Gob so it can decode it?
Check the below samples for client/server communication over UDP. The sendResponse routine is for sending response back to client.
udpclient.go
package main
import (
"fmt"
"net"
"bufio"
)
func main() {
p := make([]byte, 2048)
conn, err := net.Dial("udp", "127.0.0.1:1234")
if err != nil {
fmt.Printf("Some error %v", err)
return
}
fmt.Fprintf(conn, "Hi UDP Server, How are you doing?")
_, err = bufio.NewReader(conn).Read(p)
if err == nil {
fmt.Printf("%s\n", p)
} else {
fmt.Printf("Some error %v\n", err)
}
conn.Close()
}
udpserver.go
package main
import (
"fmt"
"net"
)
func sendResponse(conn *net.UDPConn, addr *net.UDPAddr) {
_,err := conn.WriteToUDP([]byte("From server: Hello I got your message "), addr)
if err != nil {
fmt.Printf("Couldn't send response %v", err)
}
}
func main() {
p := make([]byte, 2048)
addr := net.UDPAddr{
Port: 1234,
IP: net.ParseIP("127.0.0.1"),
}
ser, err := net.ListenUDP("udp", &addr)
if err != nil {
fmt.Printf("Some error %v\n", err)
return
}
for {
_,remoteaddr,err := ser.ReadFromUDP(p)
fmt.Printf("Read a message from %v %s \n", remoteaddr, p)
if err != nil {
fmt.Printf("Some error %v", err)
continue
}
go sendResponse(ser, remoteaddr)
}
}
hello_echo.go
package main
import (
"bufio"
"fmt"
"net"
"time"
)
const proto, addr = "udp", ":8888"
func main() {
go func() {
conn, _ := net.ListenPacket(proto, addr)
buf := make([]byte, 1024)
n, dst, _ := conn.ReadFrom(buf)
fmt.Println("serv recv", string(buf[:n]))
conn.WriteTo(buf, dst)
}()
time.Sleep(1 * time.Second)
conn, _ := net.Dial(proto, addr)
conn.Write([]byte("hello\n"))
buf, _, _ := bufio.NewReader(conn).ReadLine()
fmt.Println("clnt recv", string(buf))
}
The gist is here:
https://gist.github.com/winlinvip/e8665ba888e2fd489ccd5a449fadfa73
server.go
/*
Usage:
go run server.go
See https://gist.github.com/winlinvip/e8665ba888e2fd489ccd5a449fadfa73
See https://stackoverflow.com/a/70576851/17679565
See https://github.com/ossrs/srs/issues/2843
*/
package main
import (
"fmt"
"net"
"os"
"strconv"
)
func main() {
serverPort := 8000
if len(os.Args) > 1 {
if v,err := strconv.Atoi(os.Args[1]); err != nil {
fmt.Printf("Invalid port %v, err %v", os.Args[1], err)
os.Exit(-1)
} else {
serverPort = v
}
}
addr := net.UDPAddr{
Port: serverPort,
IP: net.ParseIP("0.0.0.0"),
}
server, err := net.ListenUDP("udp", &addr)
if err != nil {
fmt.Printf("Listen err %v\n", err)
os.Exit(-1)
}
fmt.Printf("Listen at %v\n", addr.String())
for {
p := make([]byte, 1024)
nn, raddr, err := server.ReadFromUDP(p)
if err != nil {
fmt.Printf("Read err %v", err)
continue
}
msg := p[:nn]
fmt.Printf("Received %v %s\n", raddr, msg)
go func(conn *net.UDPConn, raddr *net.UDPAddr, msg []byte) {
_, err := conn.WriteToUDP([]byte(fmt.Sprintf("Pong: %s", msg)), raddr)
if err != nil {
fmt.Printf("Response err %v", err)
}
}(server, raddr, msg)
}
}
client.go
/*
Usage:
go run client.go
go run client.go 101.201.77.240
See https://gist.github.com/winlinvip/e8665ba888e2fd489ccd5a449fadfa73
See https://stackoverflow.com/a/70576851/17679565
See https://github.com/ossrs/srs/issues/2843
*/
package main
import (
"fmt"
"net"
"os"
"strings"
)
func main() {
serverEP := "127.0.0.1"
if len(os.Args) > 1 {
serverEP = os.Args[1]
}
if !strings.Contains(serverEP, ":") {
serverEP = fmt.Sprintf("%v:8000", serverEP)
}
conn, err := net.Dial("udp", serverEP)
if err != nil {
fmt.Printf("Dial err %v", err)
os.Exit(-1)
}
defer conn.Close()
msg := "Hello, UDP server"
fmt.Printf("Ping: %v\n", msg)
if _, err = conn.Write([]byte(msg)); err != nil {
fmt.Printf("Write err %v", err)
os.Exit(-1)
}
p := make([]byte, 1024)
nn, err := conn.Read(p)
if err != nil {
fmt.Printf("Read err %v\n", err)
os.Exit(-1)
}
fmt.Printf("%v\n", string(p[:nn]))
}