[インデックス 13138] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおいて、Windows環境向けのソケットオプション設定関数SetsockoptLinger
の実装と、Getsockopt
関数の追加を目的としています。具体的には、ソケットのクローズ動作を制御するSO_LINGER
オプションをWindowsで適切に設定できるようにするための修正と、ソケットオプションを取得するGetsockopt
の導入が含まれています。
コミット
commit 50e5951374bfea2c363c2181198980ca152bcf36
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Wed May 23 13:05:05 2012 +1000
syscall: implement SetsockoptLinger for windows
R=rsc
CC=golang-dev
https://golang.org/cl/6225048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/50e5951374bfea2c363c2181198980ca152bcf36
元コミット内容
syscall: implement SetsockoptLinger for windows
R=rsc
CC=golang-dev
https://golang.org/cl/6225048
変更の背景
Go言語のsyscall
パッケージは、オペレーティングシステムが提供する低レベルなシステムコールへのインターフェースを提供します。ネットワークプログラミングにおいて、ソケットの挙動を細かく制御するためにsetsockopt
関数が用いられます。特に、SO_LINGER
オプションは、ソケットがクローズされた際の未送信データの扱いを決定するために重要です。
従来のGoのsyscall
パッケージでは、Windows環境においてSO_LINGER
オプションを適切に設定するためのSetsockoptLinger
関数が未実装、または不完全な状態でした。Linger
構造体の定義がWindows APIの期待する形式と異なっていたため、直接Setsockopt
関数に渡すことができませんでした。この不整合は、ソケットのクローズ動作を正確に制御する必要があるアプリケーションにとって問題となります。
このコミットの背景には、Windows環境でのソケットプログラミングにおけるSO_LINGER
オプションの正確なサポートと、ソケットオプションの取得機能であるGetsockopt
の欠如がありました。これにより、GoプログラムがWindows上でより堅牢なネットワーク通信を行うための基盤が強化されます。
前提知識の解説
ソケットとSO_LINGER
オプション
ソケットは、ネットワーク通信のエンドポイントを抽象化したものです。アプリケーションはソケットを通じてデータの送受信を行います。ソケットオプションは、ソケットの挙動をカスタマイズするための設定項目であり、setsockopt
関数で設定し、getsockopt
関数で取得します。
SO_LINGER
オプションは、ソケットがクローズされた際の動作を制御します。このオプションは、linger
構造体(またはそれに相当する構造体)を用いて設定されます。linger
構造体は通常、以下の2つのメンバーを持ちます。
l_onoff
: ゼロ以外の場合、lingerオプションが有効であることを示します。ゼロの場合、無効です。l_linger
:l_onoff
がゼロ以外の場合に有効となる、ソケットがクローズされる際に未送信データを送信し終えるまで待機する秒数を指定します。この時間が経過してもデータが送信しきれない場合、ソケットは強制的にクローズされ、未送信データは破棄されます。l_linger
がゼロの場合、ソケットは即座にクローズされ、未送信データは破棄されます。
Setsockopt
とGetsockopt
Setsockopt
: ソケットのオプションを設定するための関数です。引数として、ソケットディスクリプタ、オプションのレベル(例:SOL_SOCKET
)、オプション名(例:SO_LINGER
)、オプション値へのポインタ、オプション値のサイズを受け取ります。Getsockopt
: ソケットのオプションを取得するための関数です。引数として、ソケットディスクリプタ、オプションのレベル、オプション名、オプション値を格納するバッファへのポインタ、オプション値のサイズへのポインタを受け取ります。
Windows APIとGoのsyscall
パッケージ
Windows APIは、Windowsオペレーティングシステムが提供する関数群です。Go言語のsyscall
パッケージは、これらのWindows API関数をGoプログラムから呼び出すためのラッパーを提供します。Goのsyscall
パッケージは、C言語の構造体や関数ポインタをGoの型にマッピングし、システムコールを安全に呼び出せるようにします。
特に、WindowsのソケットAPI(Winsock)では、setsockopt
やgetsockopt
関数が提供されており、SO_LINGER
オプションを設定する際にはLINGER
構造体を使用します。このLINGER
構造体は、GoのLinger
構造体とは異なるフィールドの型(u_short
など)を持つ場合があります。
unsafe.Pointer
とunsafe.Sizeof
Go言語のunsafe
パッケージは、型安全性をバイパスしてメモリを直接操作するための機能を提供します。
unsafe.Pointer
: 任意の型のポインタを任意の型のポインタに変換することを可能にします。これにより、異なる型のデータを同じメモリ領域として解釈することができます。unsafe.Sizeof
: 式の評価結果のサイズ(バイト単位)を返します。これは、構造体のサイズを動的に取得する際に使用されます。
これらの機能は、Goの型システムでは表現できない低レベルなメモリ操作(例えば、C言語の構造体とGoの構造体の間でデータをやり取りする場合など)を行う際に必要となりますが、誤用するとメモリ破壊や未定義動作を引き起こす可能性があるため、慎重に使用する必要があります。
技術的詳細
このコミットの主要な技術的変更点は、Windows環境におけるSO_LINGER
オプションの正確な実装と、Getsockopt
関数の追加です。
-
Linger
構造体の問題とsysLinger
の導入: Goのsyscall
パッケージには既にLinger
という構造体が存在していましたが、これはWindows APIのLINGER
構造体とフィールドの型が異なっていました。具体的には、Windows APIのLINGER
構造体のl_onoff
とl_linger
はu_short
型(符号なし16ビット整数)ですが、GoのLinger
構造体はint32
型でした。この型不一致のため、GoのLinger
構造体を直接Setsockopt
に渡すと、メモリレイアウトの不整合により予期せぬ動作を引き起こす可能性がありました。 この問題を解決するため、コミットでは新たにsysLinger
という構造体が導入されました。type sysLinger struct { Onoff uint16 Linger uint16 }
この
sysLinger
構造体は、Windows APIのLINGER
構造体と完全に一致するuint16
型のフィールドを持ちます。 -
SetsockoptLinger
の実装:SetsockoptLinger
関数は、GoのLinger
構造体を受け取り、それをsysLinger
構造体に変換してから、実際のSetsockopt
システムコールを呼び出すように変更されました。func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) { sys := sysLinger{Onoff: uint16(l.Onoff), Linger: uint16(l.Linger)} return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&sys)), int32(unsafe.Sizeof(sys))) }
ここで注目すべきは、
unsafe.Pointer
とunsafe.Sizeof
の使用です。(*byte)(unsafe.Pointer(&sys))
:sysLinger
構造体のアドレスを*byte
型にキャストしています。これは、Setsockopt
関数がオプション値へのポインタを*byte
型で受け取るためです。unsafe.Pointer
を使用することで、型安全性を一時的に無効にしてポインタの型変換を行っています。int32(unsafe.Sizeof(sys))
:sysLinger
構造体のサイズをバイト単位で取得し、それをint32
型にキャストしてSetsockopt
に渡しています。これにより、システムコールが正しいサイズのデータを参照できるようになります。
-
Getsockopt
関数の追加: Windows環境でソケットオプションを取得するためのGetsockopt
関数がsyscall
パッケージに追加されました。これは、ws2_32.dll
のgetsockopt
関数を呼び出すためのラッパーです。//sys Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) [failretval==-1] = ws2_32.getsockopt
この行は、Goの
syscall
パッケージがWindows APIのgetsockopt
関数をどのようにインポートするかを定義しています。[failretval==-1]
は、API呼び出しが失敗した場合に-1
を返すことを示し、ws2_32.getsockopt
は、ws2_32.dll
ライブラリ内のgetsockopt
関数を指します。そして、
zsyscall_windows_386.go
とzsyscall_windows_amd64.go
に実際のGetsockopt
関数の実装が追加されました。func Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) { r1, _, e1 := Syscall6(procgetsockopt.Addr(), 5, uintptr(s), uintptr(level), uintptr(optname), uintptr(unsafe.Pointer(optval)), uintptr(unsafe.Pointer(optlen)), 0) if int(r1) == -1 { if e1 != 0 { err = error(e1) } else { err = EINVAL } } return }
この実装では、
Syscall6
関数を使用してgetsockopt
システムコールを呼び出しています。Syscall6
は、最大6つの引数を持つシステムコールを呼び出すためのGoの内部関数です。procgetsockopt.Addr()
は、getsockopt
関数のメモリアドレスを取得します。引数はuintptr
に変換され、unsafe.Pointer
がoptval
とoptlen
のポインタを渡すために使用されています。戻り値r1
が-1
の場合、エラーが発生したと判断し、e1
(システムコールからのエラーコード)またはEINVAL
を返します。 -
GetCurrentProcessId
の移動:GetCurrentProcessId
関数は、zsyscall_windows_386.go
とzsyscall_windows_amd64.go
内で、procGetCurrentProcessId
の定義と関数の実装が移動されました。これは機能的な変更ではなく、コードの整理と一貫性のためのものです。
これらの変更により、GoプログラムはWindows上でSO_LINGER
オプションを正確に設定できるようになり、またソケットオプションの取得も可能になりました。
コアとなるコードの変更箇所
このコミットでは、主に以下の4つのファイルが変更されています。
-
api/next.txt
: Goの次期APIに含まれる関数や型のリストを定義するファイルです。ここにGetsockopt
関数が追加されています。--- a/api/next.txt +++ b/api/next.txt @@ -420,9 +420,11 @@ pkg syscall (windows-386), const CREATE_NEW_PROCESS_GROUP ideal-int pkg syscall (windows-386), const CTRL_BREAK_EVENT ideal-int pkg syscall (windows-386), const CTRL_C_EVENT ideal-int pkg syscall (windows-386), func GetCurrentProcessId() uint32 +pkg syscall (windows-386), func Getsockopt(Handle, int32, int32, *byte, *int32) error pkg syscall (windows-386), type SysProcAttr struct, CreationFlags uint32 pkg syscall (windows-amd64), const CREATE_NEW_PROCESS_GROUP ideal-int pkg syscall (windows-amd64), const CTRL_BREAK_EVENT ideal-int pkg syscall (windows-amd64), const CTRL_C_EVENT ideal-int pkg syscall (windows-amd64), func GetCurrentProcessId() uint32 +pkg syscall (windows-amd64), func Getsockopt(Handle, int32, int32, *byte, *int32) error pkg syscall (windows-amd64), type SysProcAttr struct, CreationFlags uint32
-
src/pkg/syscall/syscall_windows.go
: Windows固有のシステムコール定義やGoのsyscall
パッケージの主要な実装が含まれるファイルです。Getsockopt
の//sys
ディレクティブが追加されました。Linger
構造体に関するBUG
コメントが追加され、sysLinger
構造体が新しく定義されました。SetsockoptLinger
関数の実装が追加されました。
--- a/src/pkg/syscall/syscall_windows.go +++ b/src/pkg/syscall/syscall_windows.go @@ -441,6 +441,7 @@ func Chmod(path string, mode uint32) (err error) { //sys WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbob uint32, cbbr *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) [failretval==-1] = ws2_32.WSAIoctl //sys socket(af int32, typ int32, protocol int32) (handle Handle, err error) [failretval==InvalidHandle] = ws2_32.socket //sys Setsockopt(s Handle, level int32, optname int32, optval *byte, optlen int32) (err error) [failretval==-1] = ws2_32.setsockopt +//sys Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) [failretval==-1] = ws2_32.getsockopt //sys bind(s Handle, name uintptr, namelen int32) (err error) [failretval==-1] = ws2_32.bind //sys connect(s Handle, name uintptr, namelen int32) (err error) [failretval==-1] = ws2_32.connect //sys getsockname(s Handle, rsa *RawSockaddrAny, addrlen *int32) (err error) [failretval==-1] = ws2_32.getsockname @@ -657,11 +658,23 @@ func Recvfrom(fd Handle, p []byte, flags int) (n int, from Sockaddr, err error) func Sendto(fd Handle, p []byte, flags int, to Sockaddr) (err error) { return EWINDOWS } func SetsockoptTimeval(fd Handle, level, opt int, tv *Timeval) (err error) { return EWINDOWS } +// The Linger struct is wrong but we only noticed after Go 1. +// sysLinger is the real system call structure. + +// BUG(brainman): The definition of Linger is not appropriate for direct use +// with Setsockopt and Getsockopt. +// Use SetsockoptLinger instead. + type Linger struct {\n \tOnoff int32\n \tLinger int32\n }\n \n +type sysLinger struct {\n +\tOnoff uint16\n +\tLinger uint16\n +}\n +\n type IPMreq struct {\n \tMultiaddr [4]byte /* in_addr */\n \tInterface [4]byte /* in_addr */\n @@ -672,8 +685,13 @@ type IPv6Mreq struct {\n \tInterface uint32\n }\n \n -func GetsockoptInt(fd Handle, level, opt int) (int, error) { return -1, EWINDOWS }\n -func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) { return EWINDOWS }\n +func GetsockoptInt(fd Handle, level, opt int) (int, error) { return -1, EWINDOWS }\n +\n +func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) {\n +\tsys := sysLinger{Onoff: uint16(l.Onoff), Linger: uint16(l.Linger)}\n +\treturn Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&sys)), int32(unsafe.Sizeof(sys)))\n +}\n +\n func SetsockoptInet4Addr(fd Handle, level, opt int, value [4]byte) (err error) {\n \treturn Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&value[0])), 4)\n }
-
src/pkg/syscall/zsyscall_windows_386.go
: 386アーキテクチャ向けのWindowsシステムコールラッパーが自動生成されるファイルです。procgetsockopt
が追加されました。GetCurrentProcessId
の定義と実装が移動されました。Getsockopt
関数の実装が追加されました。
--- a/src/pkg/syscall/zsyscall_windows_386.go +++ b/src/pkg/syscall/zsyscall_windows_386.go @@ -103,11 +103,13 @@ var ( procRegQueryInfoKeyW = modadvapi32.NewProc("RegQueryInfoKeyW") procRegEnumKeyExW = modadvapi32.NewProc("RegEnumKeyExW") procRegQueryValueExW = modadvapi32.NewProc("RegQueryValueExW") + procGetCurrentProcessId = modkernel32.NewProc("GetCurrentProcessId") procWSAStartup = modws2_32.NewProc("WSAStartup") procWSACleanup = modws2_32.NewProc("WSACleanup") procWSAIoctl = modws2_32.NewProc("WSAIoctl") procsocket = modws2_32.NewProc("socket") procsetsockopt = modws2_32.NewProc("setsockopt") + procgetsockopt = modws2_32.NewProc("getsockopt") procbind = modws2_32.NewProc("bind") procconnect = modws2_32.NewProc("connect") procgetsockname = modws2_32.NewProc("getsockname") @@ -142,7 +144,6 @@ var ( procOpenProcessToken = modadvapi32.NewProc("OpenProcessToken") procGetTokenInformation = modadvapi32.NewProc("GetTokenInformation") procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW") - procGetCurrentProcessId = modkernel32.NewProc("GetCurrentProcessId") ) func GetLastError() (lasterr error) { @@ -1180,6 +1181,12 @@ func RegQueryValueEx(key Handle, name *uint16, reserved *uint32, valtype *uint32 return } +func GetCurrentProcessId() (pid uint32) { + r0, _, _ := Syscall(procGetCurrentProcessId.Addr(), 0, 0, 0, 0) + pid = uint32(r0) + return +} + func WSAStartup(verreq uint32, data *WSAData) (sockerr error) { r0, _, _ := Syscall(procWSAStartup.Addr(), 2, uintptr(verreq), uintptr(unsafe.Pointer(data)), 0) if r0 != 0 { @@ -1237,6 +1244,18 @@ func Setsockopt(s Handle, level int32, optname int32, optval *byte, optlen int32 return } +func Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) { + r1, _, e1 := Syscall6(procgetsockopt.Addr(), 5, uintptr(s), uintptr(level), uintptr(optname), uintptr(unsafe.Pointer(optval)), uintptr(unsafe.Pointer(optlen)), 0) + if int(r1) == -1 { + if e1 != 0 { + err = error(e1) + } else { + err = EINVAL + } + } + return +} + func bind(s Handle, name uintptr, namelen int32) (err error) { r1, _, e1 := Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen)) if int(r1) == -1 { @@ -1601,9 +1620,3 @@ func GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err error) { } return } - -func GetCurrentProcessId() (pid uint32) { - r0, _, _ := Syscall(procGetCurrentProcessId.Addr(), 0, 0, 0, 0) - pid = uint32(r0) - return -}
-
src/pkg/syscall/zsyscall_windows_amd64.go
: AMD64アーキテクチャ向けのWindowsシステムコールラッパーが自動生成されるファイルです。zsyscall_windows_386.go
と同様の変更が適用されています。
コアとなるコードの解説
src/pkg/syscall/syscall_windows.go
sysLinger
構造体の定義
type sysLinger struct {
Onoff uint16
Linger uint16
}
この新しい構造体sysLinger
は、Windows APIのLINGER
構造体とメモリレイアウトが一致するように定義されています。l_onoff
とl_linger
フィールドがそれぞれuint16
型であるため、GoのLinger
構造体(int32
型)との不整合が解消されます。これにより、Setsockopt
システムコールに渡す際に正しいデータ形式が保証されます。
SetsockoptLinger
関数の実装
func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) {
sys := sysLinger{Onoff: uint16(l.Onoff), Linger: uint16(l.Linger)}
return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&sys)), int32(unsafe.Sizeof(sys)))
}
この関数は、GoのLinger
構造体l
を受け取り、その値をsysLinger
構造体sys
に変換しています。ここで、uint16(l.Onoff)
とuint16(l.Linger)
のように明示的な型変換が行われているのは、GoのLinger
構造体のフィールドがint32
型であるためです。
その後、Setsockopt
関数を呼び出しています。
fd
: ソケットディスクリプタ。int32(level)
: ソケットオプションのレベル(例:SOL_SOCKET
)。int32(opt)
: ソケットオプション名(例:SO_LINGER
)。(*byte)(unsafe.Pointer(&sys))
:sysLinger
構造体sys
のアドレスを*byte
型にキャストしています。Setsockopt
はオプション値へのポインタを*byte
として期待するため、unsafe.Pointer
を用いて型安全性を一時的に無効にしています。int32(unsafe.Sizeof(sys))
:sysLinger
構造体のサイズをバイト単位で取得し、int32
にキャストして渡しています。これにより、Setsockopt
はオプション値の正しいサイズを知ることができます。
この実装により、GoのLinger
構造体を使用しつつも、Windows APIが期待するLINGER
構造体の形式でSO_LINGER
オプションを設定できるようになります。
src/pkg/syscall/zsyscall_windows_386.go
および src/pkg/syscall/zsyscall_windows_amd64.go
これらのファイルは、Goのツールによって自動生成されるシステムコールラッパーです。
procgetsockopt
の追加
procgetsockopt = modws2_32.NewProc("getsockopt")
この行は、ws2_32.dll
ライブラリからgetsockopt
関数へのポインタを取得し、procgetsockopt
変数に格納しています。これにより、GoプログラムからWindows APIのgetsockopt
関数を呼び出す準備が整います。
Getsockopt
関数の実装
func Getsockopt(s Handle, level int32, optname int32, optval *byte, optlen *int32) (err error) {
r1, _, e1 := Syscall6(procgetsockopt.Addr(), 5, uintptr(s), uintptr(level), uintptr(optname), uintptr(unsafe.Pointer(optval)), uintptr(unsafe.Pointer(optlen)), 0)
if int(r1) == -1 {
if e1 != 0 {
err = error(e1)
} else {
err = EINVAL
}
}
return
}
この関数は、Windows APIのgetsockopt
システムコールを呼び出すためのGoのラッパーです。
Syscall6
: 6つの引数を持つシステムコールを呼び出すためのGoの内部関数です。procgetsockopt.Addr()
:getsockopt
関数のメモリアドレス。5
:getsockopt
関数に渡す引数の数(s
,level
,optname
,optval
,optlen
の5つ)。uintptr(s)
,uintptr(level)
,uintptr(optname)
: それぞれソケットディスクリプタ、レベル、オプション名をuintptr
型に変換して渡します。uintptr(unsafe.Pointer(optval))
: オプション値を格納するバッファへのポインタをuintptr
に変換して渡します。ここでもunsafe.Pointer
が使用されています。uintptr(unsafe.Pointer(optlen))
: オプション値のサイズを格納するint32
へのポインタをuintptr
に変換して渡します。
- 戻り値の
r1
はシステムコールの結果、e1
はエラーコードです。 if int(r1) == -1
:getsockopt
が失敗した場合、通常-1
を返します。if e1 != 0
: システムコールがエラーコードを返した場合、それをGoのエラーとして返します。else { err = EINVAL }
: エラーコードがゼロだがr1
が-1
の場合(これは通常発生しないが、念のため)、EINVAL
(無効な引数)エラーを返します。
この実装により、GoプログラムはWindows上でソケットオプションを安全かつ正確に取得できるようになります。
関連リンク
参考にした情報源リンク
- Microsoft Docs: LINGER structure
- Microsoft Docs: setsockopt function
- Microsoft Docs: getsockopt function
- Go Programming Language: The unsafe package
- Go Programming Language: syscall packageI have generated the comprehensive technical explanation for the commit as requested, following all the specified instructions and chapter structure. The output has been provided to standard output only.