Go の標準パッケージにないシステムコールを使う

# はじめに

Qiita 初投稿です。

Go の [golang.org/x/sys](https://godoc.org/golang.org/x/sys) には、OS のシステムコールの Wrapper 関数が定義されていますが、全てが網羅されているわけではありません。

例えば、windows package を見てみると、WSARecv() などはありますが、WSASocket() がない。

https://godoc.org/golang.org/x/sys/windows#WSAEnumProtocols

本項では、これを自前で定義する方法について述べます。

# 検証環境

- Windows 10
- go version go1.13.3 windows/amd64

# Syscall の構造

まず、`WSARecv()` がどのように定義されているか見ていきます。
WSARecv() の定義は `golang.org/x/sys/windows/zsyscall_windows.go` にあります。

```go
func WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) {
	r1, _, e1 := syscall.Syscall9(procWSARecv.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0)
	if r1 == socket_error {
		if e1 != 0 {
			err = errnoErr(e1)
		} else {
			err = syscall.EINVAL
		}
	}
	return
}
```

はい、謎の関数 `syscall.Syscall9()` が現れました。
意味不明すぎて考えることをやめたくなりますが、恐れることはありません。ファイル先頭に次のようにある通り、これは自動生成コードです。

```go
// Code generated by 'go generate'; DO NOT EDIT.
```

## 関数定義

同ディレクトリの `syscall_windows.go` に生成する関数の定義があります。

```go
//sys	WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecv
```

長いので細かく分けて見ていきます。

### //sys

system call のコード生成であることを表す。このコメントを、後述の `mkwinsyscall` が拾う。

### WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error)

定義する関数シグネチャ。WSABuf とか Overlapped とかの型は、windows package で定義されている。

### [failretval==socket_error]

system call が戻り値 socket_error を返したときにエラーとする。socket_error は windows package の package private な定数。

```go
const socket_error = uintptr(^uint32(0))
```

[MSDN](https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsarecv#return-value) より


> If no error occurs and the receive operation has completed immediately, WSARecv returns zero. In this case, the completion routine will have already been scheduled to be called once the calling thread is in the alertable state. Otherwise, a value of SOCKET_ERROR is returned


### = ws2_32.WSARecv

上述の関数を、ws2_32.dll 内の WSARecv 関数にマッピングする。

## コード生成

生成コマンド定義は `mksyscall.go` にあります。

```go
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go
```

`//go:generate go run` までは、[Go generate](https://golang.org/cmd/go/#hdr-Generate_Go_files_by_processing_source) の書式。

`golang.org/x/sys/windows/mkwinsyscall` が `zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go` から `//sys` の定義を拾い上げて、`zsyscall_windows.go` を生成しているようです。


# 自前で WSASocket() を定義してみる

仕組みはわかったので、自前で WSASocket() を定義してみます。

```go
import "golang.org/x/sys/windows"

const (
	wsaprotocol_len    = 255
	max_protocol_chain = 7
	invalid_socket     = ^windows.Handle(0)
)

type GROUP uint32

type GUID struct {
	Data1 uint32
	Data2 uint16
	Data3 uint16
	Data4 [8]byte
}

type WSAPROTOCOLCHAIN struct {
	ChainLen     int32
	ChainEntries [max_protocol_chain]uint32
}

type WSAPROTOCOL_INFO struct {
	ServiceFlags1     uint32
	ServiceFlags2     uint32
	ServiceFlags3     uint32
	ServiceFlags4     uint32
	ProviderFlags     uint32
	ProviderID        GUID
	CatalogEntryID    uint32
	ProtocolChain     WSAPROTOCOLCHAIN
	Version           int32
	AddressFamily     int32
	MaxSockAddr       int32
	MinSockAddr       int32
	SocketType        int32
	Protocol          int32
	ProtocolMaxOffset int32
	NetworkByteOrder  int32
	SecurityScheme    int32
	MessageSize       uint32
	ProviderReserved  uint32
	Protocols         [wsaprotocol_len + 1]uint16
}

//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zwinsys.go winsys.go
//sys WSASocket(af int32, tp int32, protocol int32, protocolInfo *WSAPROTOCOL_INFO, g GROUP, flags uint32) (socket windows.Handle, err error) [failretval==invalid_socket] = ws2_32.WSASocketW
```

なにやら構造体やら色々でてきましたが、これらは C の構造体やマクロ定義を移植したものです。(この辺の情報を一々調べて定義するのが面倒。。。)

ここで出てきた WORD, DWORD, int の対応は次の通り。

|C |Go |
|---|---|
|WORD|uint16|
|DWORD|uint32|
|int|int32|


## コード生成

`//go:generate` を記述した package のディレクトリで次のコマンドを実行すると、`zmysys.go` が生成されます。

```shell
$ go generate
```

# 実際に使ってみる

正常ケースとエラーケースを試してみます。
まずは正常ケース。

```go
sock, err := WSASocket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP, nil, 0, 0)
fmt.Println(sock, err)
// => 344 <nil>
```

正しくSOCKETが生成され、エラーも発生していないようです。

次にエラーケース。

```go
const badArg int32 = 9999
sock, err := WSASocket(badArg, badArg, badArg, nil, 0, 0)
fmt.Println(sock, err)
// => 18446744073709551615 The support for the specified socket type does not exist in this address family.
```

不正な値を与えたので、[WSAESOCKTNOSUPPORT](https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2) のエラーが返ってきました。
このメッセージは [FormatMessageW](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagew)で取得されたものです。

エラーコードは `syscall.Syscall<N>()` の３番目の戻り値として返ってきます。その実装は runtime のアセンブラで定義されていてコードを追いきれませんでしたが、`GetLastError()` の結果を返してきている模様。(たぶん[この辺](https://github.com/golang/go/blob/5ff38e476177ce9e67375bd010bea2e030f2fe19/src/runtime/sys_windows_amd64.s#L66))
本来、`WSAXXX()` のエラーコードは `WSAGetLastError()` で取得するものですが、`WSAGetLastError()` は `GetLastError()` の Wrapper であるがため、問題なく動いているようです。

https://stackoverflow.com/questions/15586224/is-wsagetlasterror-just-an-alias-for-getlasterror


# まとめ

標準package で定義されていない system call も、`golang.org/x/sys/windows/mkwinsyscall` を使えば、比較的簡単に呼び出し可能。