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

x/sys/windows: Add WSALookupService #54232

Closed
PumpkinSeed opened this issue Aug 3, 2022 · 20 comments
Closed

x/sys/windows: Add WSALookupService #54232

PumpkinSeed opened this issue Aug 3, 2022 · 20 comments
Assignees
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. Proposal Proposal-Accepted
Milestone

Comments

@PumpkinSeed
Copy link

Brief overview

Adding the following structs and functions:

Implementation proposal

I started to wrap up a possible implementation. I wrote everything in C first and after I implemented the same functionality and structs in Go.

Exact functionality and structs

  • structs - this implements the WSAQUERYSET and structs belongs to it. (Proofs can be found below)
  • lookup functions - these are the wrappers of the lookup functions.

Testing environment

  • test implementation - this creates a Scan function which using the implemented functionality
  • go test - this is a go test for the test implementation
  • main - a main package to build and run the implementation

Proofs of memory layout

There are proofs, that the WSAQUERYSET and other functions memory layout and data alignment are the same as the syscall can accept it.

  • types test - these are the test files which prints the memory layout and data alignment
  • memory layout - this file contains the comparison between C and Go memory layout and data alignment.

C implementation

  • file - this is the c implementation and investigation of the functionality implemented above

Current issue what I run into

The implementation works fine, prints the device what I expect, but at the and of the Scan function in the discover.go I just get something like a panic, which says runtime: unknown pc 0x0. I couldn't solve this issue yet. The detailed view shows that it's printing the devices and it's MAC addresses and the debug lines as well, and right after dies. It seems like the closing } wants to free some memory which doesn't exists or I don't know.

@gopherbot gopherbot added this to the Proposal milestone Aug 3, 2022
@ianlancetaylor ianlancetaylor added this to Incoming in Proposals (old) Aug 3, 2022
@rsc
Copy link
Contributor

rsc commented Aug 3, 2022

What is the API being suggested for x/sys/windows? If it is a direct port of structs and functions from Windows APIs, we typically fast-path it through the proposal process.

Looking at the linked PRs, I see some extra methods

func (w WSAQUERYSET) ServiceInstanceNameToString() string {
	return WcharToString(w.ServiceInstanceName)
}

func (w WSAQUERYSET) CommentToString() string {
	return WcharToString(w.Comment)
}

func (w WSAQUERYSET) ContextToString() string {
	return WcharToString(w.Context)
}

func (w WSAQUERYSET) QueryStringToString() string {
	return WcharToString(w.QueryString)
}

and a new function WcharToString as well as a Wchar type. Are those being suggested for x/sys/windows too? Those would require more discussion. If we can add just the struct and the direct DLL methods, that would simplify the process.

@rsc
Copy link
Contributor

rsc commented Aug 3, 2022

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@rsc rsc moved this from Incoming to Active in Proposals (old) Aug 3, 2022
@PumpkinSeed
Copy link
Author

@rsc I'm not forcing those, if there is a solution which works I'm good with it. Also if you agree on the proposal, I'm happy to create a change request on Gerrit.

On the other had, I more care about the runtime: unknown pc 0x0, because I don't know whether it's something what the I need to fix in the future change request, or why it happens.

@PumpkinSeed
Copy link
Author

PumpkinSeed commented Aug 9, 2022

I try to figure out the reason of the runtime: unknown pc 0x0. One of possible issues, that I'm creating the WSAQUERYSET here. Then pass it as a pointer to the WSALookupServiceNext, so it's still has a chance of stack allocation, because the struct itself is a fix length struct, and I'm passing down to the stackframe, so based on the escape rules it shouldn't be heap allocated. So at this point we still have a stack allocated variable. It's creating a raw memory pointer and send it to the syscall as uintptr. The syscall doing a heap allocation of the variable without the participation of the runtime, because it's raw memory. After these the runtime still thinks that the querySet variable is stack allocated here and want to free that memory space, and throws a panic, of runtime: unknown pc 0x0.

What can be a solution for this?

@PumpkinSeed
Copy link
Author

Also I fount out, that the length of WSAQUERYSET is 120. I need to pass it that size into the WSALookupServiceNext, which overwrites that (since that parameter of the syscall is an in, out) value to 1024. If I read the raw memory of uintptr of the 4th paramter of the syscall and convert it to a [1024]byte, then the first 120 byte is the representation of the WSAQUERYSET where the Buffer points to an other allocation just like the ServiceInstanceName. From the 120th byte it's start to represent the ServiceInstanceName on a []uint16 representation. So probably this is an allocation what the Go has issues about, because it's not existed before the syscall.

@PumpkinSeed
Copy link
Author

PumpkinSeed commented Aug 9, 2022

I could have fix it, so I have a properly working example of my proposal.

  • Removed the Wchar type, and I use *uint16 directly.
  • Added initializeWSAQUERYSET so this will initialize pointer in the WSAQUERYSET so the syscall doesn't have to. This solves the runtime: unknown pc 0x0 issue. Also this make sense to implement on the sys/windows side, because otherwise gives a headache to the developer on the implementation side.
  • Changed the function signature of WSALookupServiceNext. This is because if it calls the initializeWSAQUERYSET inside the function it can return the WSAQUERYSET, so the developer does not have to deal with the proper initialization of the WSAQUERYSET struct.

From my side the proposal is completed so if you decides on that I can have a PR on Gerrit.

Note: These changes are located on the fix-runtime-issue branch.

@rsc
Copy link
Contributor

rsc commented Aug 10, 2022

@PumpkinSeed can you please post the API changes here as a self-contained comment? Thanks.

/cc @alexbrainman

@PumpkinSeed
Copy link
Author

@rsc Yes, those are located here (these are actually used in production in our windows app).

// https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsalookupservicebeginw dwControlFlags
const (
	LUP_DEEP                = 0x0001
	LUP_CONTAINERS          = 0x0002
	LUP_NOCONTAINERS        = 0x0004
	LUP_NEAREST             = 0x0008
	LUP_RETURN_NAME         = 0x0010
	LUP_RETURN_TYPE         = 0x0020
	LUP_RETURN_VERSION      = 0x0040
	LUP_RETURN_COMMENT      = 0x0080
	LUP_RETURN_ADDR         = 0x0100
	LUP_RETURN_BLOB         = 0x0200
	LUP_RETURN_ALIASES      = 0x0400
	LUP_RETURN_QUERY_STRING = 0x0800
	LUP_RETURN_ALL          = 0x0FF0
	LUP_RES_SERVICE         = 0x8000

	LUP_FLUSHCACHE    = 0x1000
	LUP_FLUSHPREVIOUS = 0x2000

	LUP_NON_AUTHORITATIVE      = 0x4000
	LUP_SECURE                 = 0x8000
	LUP_RETURN_PREFERRED_NAMES = 0x10000
	LUP_DNS_ONLY               = 0x20000

	LUP_ADDRCONFIG           = 0x100000
	LUP_DUAL_ADDR            = 0x200000
	LUP_FILESERVER           = 0x400000
	LUP_DISABLE_IDN_ENCODING = 0x00800000
	LUP_API_ANSI             = 0x01000000

	LUP_RESOLUTION_HANDLE = 0x80000000
)

const socket_error = uintptr(^uint32(0))

const errnoERROR_IO_PENDING = 997

var (
	errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
	errERROR_EINVAL     error = syscall.EINVAL
)

var (
	modws2_32                 = windows.NewLazySystemDLL("ws2_32.dll")
	procWSALookupServiceBegin = modws2_32.NewProc("WSALookupServiceBeginW")
	procWSALookupServiceNext  = modws2_32.NewProc("WSALookupServiceNextW")
	procWSALookupServiceEnd   = modws2_32.NewProc("WSALookupServiceEnd")
)

// https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-wsaquerysetw
type WSAQUERYSET struct {
	Size                uint32
	ServiceInstanceName *uint16
	ServiceClassId      *windows.GUID
	Version             *WSAVersion
	Comment             *uint16
	NameSpace           uint32
	NSProviderId        *windows.GUID
	Context             *uint16
	NumberOfProtocols   uint32
	AfpProtocols        *AFProtocols
	QueryString         *uint16
	NumberOfCsAddrs     uint32
	SaBuffer            *AddrInfo
	OutputFlags         uint32
	Blob                *BLOB
}

// ----
// This section are helper methods, because these fields are raw pointers.
func (w WSAQUERYSET) ServiceInstanceNameToString() string {
	return RawPointerToString(w.ServiceInstanceName)
}

func (w WSAQUERYSET) CommentToString() string {
	return RawPointerToString(w.Comment)
}

func (w WSAQUERYSET) ContextToString() string {
	return RawPointerToString(w.Context)
}

func (w WSAQUERYSET) QueryStringToString() string {
	return RawPointerToString(w.QueryString)
}
// ----

// https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-wsaversion
type WSAVersion struct {
	Version                  uint32 // DWORD
	EnumerationOfComparision int32  // WSAEcomparator enum
}

// https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-afprotocols
type AFProtocols struct {
	AddressFamily int32
	Protocol      int32
}

// https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2
type Sockaddr struct {
	Family uint16
	Data   [14]byte
}

// https://docs.microsoft.com/en-us/windows/win32/api/Ws2def/ns-ws2def-socket_address
type SocketAddress struct {
	Sockaddr       *Sockaddr
	SockaddrLength int
}

// https://docs.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-csaddr_info
type AddrInfo struct {
	LocalAddr  SocketAddress
	RemoteAddr SocketAddress
	SocketType int32
	Protocol   int32
}

// https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-blob
type BLOB struct {
	Size     uint32
	BlobData *byte // TODO how to represent a block of data in Go?
}

// this can be unexported
func RawPointerToString(w *uint16) string {
	if w != nil {
		us := make([]uint16, 0, 256)
		for p := uintptr(unsafe.Pointer(w)); ; p += 2 {
			u := *(*uint16)(unsafe.Pointer(p))
			if u == 0 {
				return string(utf16.Decode(us))
			}
			us = append(us, u)
		}
	}
	return ""
}

func WSALookupServiceBegin(querySet *WSAQUERYSET, flags uint32, handle *windows.Handle) error {
	var qs = unsafe.Pointer(querySet)

	r, _, errNo := syscall.SyscallN(procWSALookupServiceBegin.Addr(), uintptr(qs), uintptr(flags), uintptr(unsafe.Pointer(handle)))
	if r == socket_error {
		return errnoErr(errNo)
	}

	return nil
}

func WSALookupServiceNext(handle windows.Handle, flags uint32, size *int32) (WSAQUERYSET, error) {
	var data = initializeWSAQUERYSET()
	r, _, errNo := syscall.SyscallN(procWSALookupServiceNext.Addr(), uintptr(handle), uintptr(flags), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(data)))
	if r == socket_error {
		return WSAQUERYSET{}, errnoErr(errNo)
	}

	var ret WSAQUERYSET
	if data != nil {
		ret = *data
	}

	return ret, nil
}

func WSALookupServiceEnd(handle windows.Handle) error {
	r, _, errNo := syscall.SyscallN(procWSALookupServiceEnd.Addr(), uintptr(handle))
	if r == socket_error {
		return errnoErr(errNo)
	}
	return nil
}

// This one is better to have in here, because makes it easier to use the WSALookupServiceNext without runtime panic (see above)
func initializeWSAQUERYSET() *WSAQUERYSET {
	var serviceInstanceName = new(uint16)
	var serviceClassId = new(windows.GUID)
	var version = new(WSAVersion)
	var nsProviderId = new(windows.GUID)
	var comment = new(uint16)
	var context = new(uint16)
	var afpProtocols = new(AFProtocols)
	var queryString = new(uint16)
	var saBuffer = &AddrInfo{
		LocalAddr: SocketAddress{
			Sockaddr: &Sockaddr{},
		},
		RemoteAddr: SocketAddress{
			Sockaddr: &Sockaddr{},
		},
	}
	var blob = &BLOB{
		BlobData: new(byte),
	}
	return &WSAQUERYSET{
		ServiceInstanceName: serviceInstanceName,
		ServiceClassId:      serviceClassId,
		Version:             version,
		NSProviderId:        nsProviderId,
		Comment:             comment,
		Context:             context,
		AfpProtocols:        afpProtocols,
		QueryString:         queryString,
		SaBuffer:            saBuffer,
		Blob:                blob,
	}
}

// I'm not sure this needs in here, probably already exists
func errnoErr(e syscall.Errno) error {
	switch e {
	case 0:
		return errERROR_EINVAL
	case errnoERROR_IO_PENDING:
		return errERROR_IO_PENDING
	}
	return e
}

@alexbrainman
Copy link
Member

Adding the following structs and functions:

If you just want to add these (and correspondent consts) to golang.org/x/sys/windows package, go ahead. I usually review such additions.

I expected you aware that Go already has code to access DnsQuery Windows API.

Alex

@gopherbot
Copy link

Change https://go.dev/cl/422997 mentions this issue: windows: Add WSALookupService syscall wrappers

@PumpkinSeed
Copy link
Author

@alexbrainman I added the changes. One thing which is detailed above (also commented in the CL), that it requires the initializeWSAQUERYSET because it returns a pointer and this "force" a heap allocation. Since the WSALookupServiceNext modifies that struct while it's unsafe.Pointer it throws a runtime panic otherwise. The initializeWSAQUERYSET initialize the struct fields and force that heap allocation so the runtime will be aware of the changes of memory while it's unsafe.Pointer.

@rsc
Copy link
Contributor

rsc commented Aug 17, 2022

CL 422997 has just data structures. If that's the proposal, that's fine.

RawPointerToString and the helper methods seem out of place here. The rest of the package doesn't provide that by convention. Perhaps it should, but if so that should be a separate proposal and we should apply the change consistently across the whole package, not just in this one API.

If we can drop RawPointerToString and the helpers, then I think this is a clear accept.

@aclements
Copy link
Member

The package is not consistent about string helpers. For example DrvInfoData has helpers to convert *uint16 fields into string that look exactly like this. Not very many types have them, but a few do.

@PumpkinSeed
Copy link
Author

@rsc I didn't add them, because I didn't feel an extreme necessity of that. I can create a new proposal right after it's getting merged, but for now I would be very happy to have it in.

@aclements I think once we have this in, I can create another proposal which can have a talk about this issue. I think many of these wrappers in Windows ecosystem requires this *uint16 to string solution.

@alexbrainman
Copy link
Member

@alexbrainman I added the changes. One thing which is detailed above (also commented in the CL), that it requires the initializeWSAQUERYSET

Let's discuss details on

https://go.dev/cl/422997

Alex

@rsc
Copy link
Contributor

rsc commented Aug 31, 2022

The current CL 422997 is just adding data structures and functions from the DLL itself, which is completely fine. Will mark this accepted assuming the CL stays that way.

@PumpkinSeed
Copy link
Author

The current CL 422997 is just adding data structures and functions from the DLL itself, which is completely fine. Will mark this accepted assuming the CL stays that way.

Currently test running into a runtime panic. This is because the corresponding buffer isn't initialized properly, I investigated the source of the issue, see above. Since the CL does not contain that changes it will throw the runtime panic. Alex commented that he can look into to problem.

Also I think it would be nice to have a wrapper function which makes the WSALookupServiceNext's usage easier, because currently it is a pain to use just like having the DLL functions. What do you think about? Can I open an other proposal for that?

Also I saw that the generation of the DLL functions using deprecated functions. This is something what I can work on? Is there an issue for that or can I create one?

@rsc
Copy link
Contributor

rsc commented Sep 7, 2022

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— rsc for the proposal review group

@rsc rsc changed the title proposal: x/sys/windows: Add WSALookupService x/sys/windows: Add WSALookupService Sep 7, 2022
@rsc rsc modified the milestones: Proposal, Backlog Sep 7, 2022
@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Sep 7, 2022
@PumpkinSeed
Copy link
Author

Do you have any update on this one? It's pending on the CL side.

@gopherbot
Copy link

Change https://go.dev/cl/461296 mentions this issue: windows: Add WSALookupService syscall wrappers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. Proposal Proposal-Accepted
Projects
No open projects
Development

No branches or pull requests

6 participants