Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: File method of Conn on Windows #10350

Open
vyrus001 opened this issue Apr 5, 2015 · 22 comments
Open

net: File method of Conn on Windows #10350

vyrus001 opened this issue Apr 5, 2015 · 22 comments

Comments

@vyrus001
Copy link

vyrus001 commented Apr 5, 2015

I am writing something that requires access to the file descriptor of a socket created by net.Dial (which returns a net.conn) and then recast as net.TCPConn so that I can use net.TCPConn.File().Fd().

However, (as the error message says), "dup is not supported on windows". for my purposes, I don't "need" the dup() and would love to settle for just getting the raw file descriptor, but sadly the code within conn.File() appears as follows.

https://golang.org/src/net/net.go?s=5510:5555#L195

func (c *conn) File() (f *os.File, err error) { return c.fd.dup() }

Is there any way I could ask for an "if windows then just return c.fd" option? Hopefully this request isn't a waste of time, I am far from an expert on go.

@mikioh mikioh changed the title net.Conn File Descriptor on Windows net: File method of Conn on Windows Apr 5, 2015
@mikioh
Copy link
Contributor

mikioh commented Apr 5, 2015

See #9503. We need a good compromise for both issues, and so need Windows experts.

@alexbrainman
Copy link
Member

I don't think you can have "if windows then just return c.fd" option, because it is not what (*conn).File documented to do. Why do you need access to Windows socket handle?

Alex

@mattn
Copy link
Member

mattn commented Apr 7, 2015

Excuse me for discuss, because we want to pass socket handle into external process for listening. For example, to implement graceful server, we often use fork/exec model.

@vyrus001
Copy link
Author

vyrus001 commented Apr 7, 2015

In my case, I am passing it to some c compiled with cgo for additional
functionality
On Apr 7, 2015 12:36 AM, "mattn" notifications@github.com wrote:

Excuse me for discuss, because we want to pass socket handle into external
process for listening. For example, to implement graceful server, we often
use fork/exec model.


Reply to this email directly or view it on GitHub
#10350 (comment).

@alexbrainman
Copy link
Member

@vyrus001 there is no way to access original socket. If you need that functionality, you have to make copy of main Go repo and make whatever changes you want to net package.

@mattn I am not sure what you require, but have you tried implementing windows version of (*netFD).dup? Just use syscall.DuplicateHandle. Would that work for you?

Alex

@mattn
Copy link
Member

mattn commented Apr 8, 2015

@alexbrainman

https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms724251%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396

You should not use DuplicateHandle to duplicate handles to the following objects:

  • I/O completion ports. No error is returned, but the duplicate handle cannot be used.
  • Sockets. No error is returned, but the duplicate handle may not be recognized by Winsock at the target process. Also, using DuplicateHandle interferes with internal reference counting on the underlying object. To duplicate a socket handle, use the WSADuplicateSocket function.

@alexbrainman
Copy link
Member

Fair enough. Use WSADuplicateSocket instead. Would that work for you?

Alex

@mattn
Copy link
Member

mattn commented Apr 9, 2015

Works fine. (But not enough to do that I want)

Look this: https://github.com/mattn/gospel/blob/master/example/echo-server.go

This is not possible to build currently. For building, it need https://codereview.appspot.com/177590043/

This is fork/exec model server. This can replace executable without stop server with SIGHUP. This package is passing socket handle into child process on windows.

@alexbrainman
Copy link
Member

@mattn your old CL needs to be moved into Gerrit and made work on tip before we can discuss it.

Alex

@mattn
Copy link
Member

mattn commented Apr 9, 2015

@rsc rsc removed the os-windows label Apr 10, 2015
@rsc rsc added this to the Unplanned milestone Apr 10, 2015
@rsc rsc added the OS-Windows label Apr 15, 2015
@gopherbot
Copy link

CL https://golang.org/cl/8683 mentions this issue.

mattn added a commit to mattn/go that referenced this issue Apr 24, 2018
…tConn, FileListener

DO NOT REVIEW

Fixes golang#9503.
Fixes golang#10350.

Change-Id: If0851cb3340281cc1ea523cd0ddf48fc7f87c775
@NuSkooler
Copy link

Gah, I just ran into this as well:
I actually need to do exactly what MSDN says not to do (DuplidateHandle) so I can pass the fd to an "dumb" process. Unfortunately WSADuplicateSocket requires both processes to be in cooperation in order to pass off a special structure. Basically, I want to do this: https://github.com/vrcsix/synchronet/blob/93b01c55b3102ebc3c4f4793c3a45b8c13d0dc2a/src/sbbs3/main.cpp#L3085

If there is no way to get a underlying socket fd, I'm probably at a dead end golang-wise :(

@mikioh
Copy link
Contributor

mikioh commented Dec 28, 2018

This issue might be unfixable because of the architectural differences between Unix-variants and Windows. One good news is that Go 1.12 supports #24331; you may use os.File.SyscallConn instead of File method of net.{TCP,UDP,IP}Conn.

@crvv
Copy link
Contributor

crvv commented Dec 28, 2018

The purpose of this issue is to get the handle of a *net.TCPConn.
I think this issue was already fixed by (*net.TCPConn).SyscallConn
https://golang.org/pkg/net/#TCPConn.SyscallConn

The FileConn and (*os.File).Fd methods have other side effects(dup, blocking mode), so I think it's hardly useful now.

@mikioh
Copy link
Contributor

mikioh commented Jan 17, 2019

@crvv,

The purpose of the File method is not only for tweaking/crafting the underlying socket but for passing a cloned descriptor pointing to the same underlying socket to external processes.

@NuSkooler
Copy link

I can see that it would not be fixable with the current API (due to dupe guarantees/etc.), but certainly would be nice and useful for many reasons. It's a bit of a shame that the API kind of tosses Windows out the the uh... window here.

@crvv
Copy link
Contributor

crvv commented Jan 17, 2019

@mikioh
The purpose of @vyrus001 is to pass the handle to C code.
That can be done via SyscallConn, like

package main

/*
#cgo LDFLAGS: -lWs2_32
#include <stdio.h>
#include <Winsock.h>

void get_listen_addr(SOCKET s) {
    struct sockaddr_in name;
    int namelen = sizeof(name);

    if (getsockname(s, (struct sockaddr*)&name, &namelen)) {
        printf("failed %d\n", WSAGetLastError());
        return;
    }
    printf("%hhu.%hhu.%hhu.%hhu:%hu\n",
        name.sin_addr.S_un.S_un_b.s_b1,
        name.sin_addr.S_un.S_un_b.s_b2,
        name.sin_addr.S_un.S_un_b.s_b3,
        name.sin_addr.S_un.S_un_b.s_b4,
        ntohs(name.sin_port));
}
*/
import "C"
import "net"

func main() {
        listener, err := net.Listen("tcp4", "127.0.0.88:0")
        if err != nil {
                panic(err)
        }
        defer listener.Close()
        rawConn, err := listener.(*net.TCPListener).SyscallConn()
        if err != nil {
                panic(err)
        }
        fdCh := make(chan uintptr, 1)
        err = rawConn.Control(func(fd uintptr) {
                fdCh <- fd
        })
        if err != nil {
                panic(err)
        }
        fd := <-fdCh
        // You can do anything with the fd now
        println(listener.Addr().String())
        C.get_listen_addr(C.SOCKET(fd))
}

The handle can also be passed to external processes with os.NewFile and os.ProcAttr.Files.
The document of syscall.RawConn says

The file descriptor fd is guaranteed to remain valid while f executes but not after f returns.

But this approach is not worse than (*os.File).Fd() method.

@diogin
Copy link
Contributor

diogin commented Aug 19, 2021

Any progress on this issue?
I've written a function to emulate the "socketpair" syscall under unix, which I'd like to use to communicate between parent process and child process through os.StartProcess(), unfortunately I got an error: "not supported in windows". And I found the (*net.TCPConn).File() method is not implemented under Windows.

func SocketPair() (pair [2]*os.File, err error) {
	door, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return
	}
	defer door.Close()
	this, err := net.Dial("tcp", door.Addr().String())
	if err != nil {
		return
	}
	defer this.Close()
	that, err := door.(*net.TCPListener).AcceptTCP()
	if err != nil {
		return
	}
	defer that.Close()

	pair[0], err = this.(*net.TCPConn).File()
	if err != nil {
		return
	}
	pair[1], err = that.File()
	if err != nil {
		pair[0].Close()
	}
	return
}

So is there any workaround?

@lllbbbyyy
Copy link

@mikioh The purpose of @vyrus001 is to pass the handle to C code. That can be done via SyscallConn, like

package main

/*
#cgo LDFLAGS: -lWs2_32
#include <stdio.h>
#include <Winsock.h>

void get_listen_addr(SOCKET s) {
    struct sockaddr_in name;
    int namelen = sizeof(name);

    if (getsockname(s, (struct sockaddr*)&name, &namelen)) {
        printf("failed %d\n", WSAGetLastError());
        return;
    }
    printf("%hhu.%hhu.%hhu.%hhu:%hu\n",
        name.sin_addr.S_un.S_un_b.s_b1,
        name.sin_addr.S_un.S_un_b.s_b2,
        name.sin_addr.S_un.S_un_b.s_b3,
        name.sin_addr.S_un.S_un_b.s_b4,
        ntohs(name.sin_port));
}
*/
import "C"
import "net"

func main() {
        listener, err := net.Listen("tcp4", "127.0.0.88:0")
        if err != nil {
                panic(err)
        }
        defer listener.Close()
        rawConn, err := listener.(*net.TCPListener).SyscallConn()
        if err != nil {
                panic(err)
        }
        fdCh := make(chan uintptr, 1)
        err = rawConn.Control(func(fd uintptr) {
                fdCh <- fd
        })
        if err != nil {
                panic(err)
        }
        fd := <-fdCh
        // You can do anything with the fd now
        println(listener.Addr().String())
        C.get_listen_addr(C.SOCKET(fd))
}

The handle can also be passed to external processes with os.NewFile and os.ProcAttr.Files. The document of syscall.RawConn says

The file descriptor fd is guaranteed to remain valid while f executes but not after f returns.

But this approach is not worse than (*os.File).Fd() method.

As the documentation says, when the f function returns, won't the obtained handle be invalidated?

@crvv
Copy link
Contributor

crvv commented Sep 9, 2022

No, the handle won't be invalidated.
The handle will be invalidated when it is closed. If Conn.Close is called on another goroutine, the socket won't be closed until the f function returns.
This is the meaning of the documentation and it is also the reason why Fd uses dup. So I said "is not worse than Fd method".

@crvv
Copy link
Contributor

crvv commented Sep 9, 2022

The problem here is not how to get the handle, which is solved by SyscallConn.
The problem is Close may not work correctly if the handle/fd is passed to user code.
The handle can also be closed by the garbage collector.

This is the same problem as the dangling pointer.
I don't think Go stdlib can solve this problem. It is the user's responsibility to write the correct code at this situation.

@lllbbbyyy
Copy link

No, the handle won't be invalidated. The handle will be invalidated when it is closed. If Conn.Close is called on another goroutine, the socket won't be closed until the f function returns. This is the meaning of the documentation and it is also the reason why Fd uses dup. So I said "is not worse than Fd method".

Thank you very much, I now use the go coroutine and chan to make f blocked, so that the handle is always valid. When the user code closes, f is returned through the synchronized chan, the code is similar to the following, I don't know if this is the best practice in this way?

func (s *struct) getFd() {
	fdCh := make(chan uintptr, 1)
	s.syncClose = make(chan int)
	go func() {
		s.rawConn.Control(func(fd uintptr) {
			fdCh <- fd
			<-s.syncClose
		})
	}()
	s.fd = <-fdCh
}
func (s *struct) close() error {
	s.syncClose <- 1
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants