Re: [go-nuts] Is it possible to access sysfd in the net package

943 views
Skip to first unread message

Dave Cheney

unread,
Apr 9, 2013, 3:03:46 AM4/9/13
to ry...@rchapman.org, golang-nuts
You will find sysfd here

TCPConn.conn.fd.sysfd

The reason why you cannot access it is the networking package requires
that it controls the blocking mode of any fds it owns. If you were
able to get access to sysfd, you could change the blocking mode
yourself which would cause havock with the poller.

Having said that, if you do want to try, have a look in the
go.net/ipv4 package for a hint on how to access that field via
reflection. Be forewarned that the internals of the net package can
(and have) change.

Dave

On Tue, Apr 9, 2013 at 3:18 PM, <ry...@rchapman.org> wrote:
> I recently wrote a golang program that relied on Linux netfilter to
> determine the true destination IP address, which does so by a system call:
>
> getsockopt(fd, IPPROTO_IP, SO_ORIGINAL_DST, &buf, [16])
>
> However, when using go to set up a listener, I could not find a way to get
> the file descriptor (sysfd) from the netFD structure (defined in
> pkg/net/fd.go). When AcceptTCP() returns, a call to getsockopt() is used
> (part of the Linux iptables REDIRECT feature) to find the real destination
> ip address. I ended up having to modify the golang source to expose sysfd
> like so:
>
> --- tcpsock_posix.go.old 2013-04-08 03:08:51.114516592 +0000
> +++ tcpsock_posix.go 2013-04-08 03:09:11.546250633 +0000
> @@ -60,10 +60,11 @@
> // connections.
> type TCPConn struct {
> conn
> + SysFD int
> }
>
> func newTCPConn(fd *netFD) *TCPConn {
> - c := &TCPConn{conn{fd}}
> + c := &TCPConn{conn{fd}, fd.sysfd}
> c.SetNoDelay(true)
> return c
> }
>
>
> So the question is: how could I have accessed that file descriptor without
> changing the go source? If the answer is, "it isn't possible," then would
> the go authors entertain a patch?
>
> Ryan Chapman
> Bozeman, MT
>
>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Andy Balholm

unread,
Apr 9, 2013, 10:48:03 AM4/9/13
to golan...@googlegroups.com, ry...@rchapman.org
Here is what I do:

package main

import (
"errors"
"net"
"strconv"
"syscall"
"unsafe"
)

type sockaddr struct {
family uint16
data   [14]byte
}

const SO_ORIGINAL_DST = 80

// realServerAddress returns an intercepted connection's original destination.
func realServerAddress(conn net.Conn) (string, error) {
tcpConn, ok := conn.(*net.TCPConn)
if !ok {
return "", errors.New("not a TCPConn")
}

file, err := tcpConn.File()
if err != nil {
return "", err
}
defer file.Close()
fd := file.Fd()

var addr sockaddr
size := uint32(unsafe.Sizeof(addr))
err = getsockopt(int(fd), syscall.SOL_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), &size)
if err != nil {
return "", err
}

var ip net.IP
switch addr.family {
case syscall.AF_INET:
ip = addr.data[2:6]
default:
return "", errors.New("unrecognized address family")
}

port := int(addr.data[0])<<8 + int(addr.data[1])

return net.JoinHostPort(ip.String(), strconv.Itoa(port)), nil
}

Mikio Hara

unread,
Apr 9, 2013, 9:05:18 PM4/9/13
to Andy Balholm, golang-nuts, ry...@rchapman.org
On Tue, Apr 9, 2013 at 11:48 PM, Andy Balholm <andyb...@gmail.com> wrote:

> file, err := tcpConn.File()

Please be informed that File method of net.Conn will break net package
internet I/O, for example when you run <http://play.golang.org/p/uJo0nDaqDk>
with -file flag, timer wheel inside net package never fires.

<http://play.golang.org/p/7FJ2tE_2XQ> would be a safety solution for that.
Message has been deleted

ry...@rchapman.org

unread,
Sep 16, 2013, 3:50:02 AM9/16/13
to golan...@googlegroups.com
Just realized I forgot to report back what I found.. in case someone else runs into a similar situation.

Thanks Andy and Mikio, I ended up using the File() method, and GetsockoptIPv6Mreq(), which should work for IPv4.  Just now getting around to IPv6, so I think I'll be looking for another sys call in the near future that can return the v6 address and port number.

func getOriginalDst(clientConn *net.TCPConn) (ipv4 string, port uint16, newTCPConn *net.TCPConn) {
    srcipport := fmt.Sprintf("%v", clientConn.RemoteAddr())
    newTCPConn = nil
    // net.TCPConn.File() will cause the receiver's (clientConn) socket to be placed in blocking mode.
    // The workaround is to take the File returned by .File(), do getsockopt() to get the original
    // destination, then create a new *net.TCPConn by calling net.Conn.FileConn(). The new TCPConn
    // will be in non-blocking mode. What a pain.
    clientConnFile, err := clientConn.File()
    if err != nil {
        log.Infof("GETORIGINALDST|%v->?->FAILEDTOBEDETERMINED|ERR: could not get a copy of the client connection's file object", srcipport)
        return
    } else {
        clientConn.Close()
    }

    // Get original destination
    // this is the only syscall in the Golang libs that I can find that returns 16 bytes
    // Example result: &{Multiaddr:[2 0 31 144 206 190 36 45 0 0 0 0 0 0 0 0] Interface:0}
    // port starts at the 3rd byte and is 2 bytes long (31 144 = port 8080)
    // IPv4 address starts at the 5th byte, 4 bytes long (206 190 36 45)
    addr, err := syscall.GetsockoptIPv6Mreq(int(clientConnFile.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
    log.Debugf("getOriginalDst(): SO_ORIGINAL_DST=%+v\n", addr)
    if err != nil {
        log.Infof("GETORIGINALDST|%v->?->FAILEDTOBEDETERMINED|getsocketopt(SO_ORIGINAL_DST) failed: %v", srcipport, err)
        return
    }
    newConn, err := net.FileConn(clientConnFile)
    if err != nil {
        log.Infof("GETORIGINALDST|%v->?->%v|ERR: could not create a FileConn fron clientConnFile=%+v: %v", srcipport, addr, clientConnFile, err)
        return
    }
    if _, ok := newConn.(*net.TCPConn); ok {
        newTCPConn = newConn.(*net.TCPConn)
        clientConnFile.Close()
    } else {
        log.Infof("GETORIGINALDST|%v->?->%v|ERR: newConn is not a *net.TCPConn, instead it is: %T (%v)", srcipport, addr, newConn, newConn)
        return
    }

    ipv4 = itod(uint(addr.Multiaddr[4])) + "." +
           itod(uint(addr.Multiaddr[5])) + "." +
           itod(uint(addr.Multiaddr[6])) + "." +
           itod(uint(addr.Multiaddr[7]))
    port = uint16(addr.Multiaddr[2]) << 8 + uint16(addr.Multiaddr[3])

    return
}

est

unread,
Feb 26, 2014, 8:13:18 PM2/26/14
to golan...@googlegroups.com, ry...@rchapman.org
In case anybody else want to grab the god damn fd to use on setsockopt with a normal golang net Conn

Dave Cheney

unread,
Feb 27, 2014, 2:45:34 AM2/27/14
to golan...@googlegroups.com, ry...@rchapman.org
No need to be passive aggressive about it. Which socket option were you after ?

est

unread,
Feb 27, 2014, 4:01:44 AM2/27/14
to golan...@googlegroups.com
I am writing a UDP server trying to grab client's TTL.

From the manpage I've read I need to 

syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVTTL, 1)

and then recvmsg for the oob data.


Reply all
Reply to author
Forward
0 new messages