Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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がゼロの場合、ソケットは即座にクローズされ、未送信データは破棄されます。

SetsockoptGetsockopt

  • 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)では、setsockoptgetsockopt関数が提供されており、SO_LINGERオプションを設定する際にはLINGER構造体を使用します。このLINGER構造体は、GoのLinger構造体とは異なるフィールドの型(u_shortなど)を持つ場合があります。

unsafe.Pointerunsafe.Sizeof

Go言語のunsafeパッケージは、型安全性をバイパスしてメモリを直接操作するための機能を提供します。

  • unsafe.Pointer: 任意の型のポインタを任意の型のポインタに変換することを可能にします。これにより、異なる型のデータを同じメモリ領域として解釈することができます。
  • unsafe.Sizeof: 式の評価結果のサイズ(バイト単位)を返します。これは、構造体のサイズを動的に取得する際に使用されます。

これらの機能は、Goの型システムでは表現できない低レベルなメモリ操作(例えば、C言語の構造体とGoの構造体の間でデータをやり取りする場合など)を行う際に必要となりますが、誤用するとメモリ破壊や未定義動作を引き起こす可能性があるため、慎重に使用する必要があります。

技術的詳細

このコミットの主要な技術的変更点は、Windows環境におけるSO_LINGERオプションの正確な実装と、Getsockopt関数の追加です。

  1. Linger構造体の問題とsysLingerの導入: Goのsyscallパッケージには既にLingerという構造体が存在していましたが、これはWindows APIのLINGER構造体とフィールドの型が異なっていました。具体的には、Windows APIのLINGER構造体のl_onoffl_lingeru_short型(符号なし16ビット整数)ですが、GoのLinger構造体はint32型でした。この型不一致のため、GoのLinger構造体を直接Setsockoptに渡すと、メモリレイアウトの不整合により予期せぬ動作を引き起こす可能性がありました。 この問題を解決するため、コミットでは新たにsysLingerという構造体が導入されました。

    type sysLinger struct {
    	Onoff  uint16
    	Linger uint16
    }
    

    このsysLinger構造体は、Windows APIのLINGER構造体と完全に一致するuint16型のフィールドを持ちます。

  2. 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.Pointerunsafe.Sizeofの使用です。

    • (*byte)(unsafe.Pointer(&sys)): sysLinger構造体のアドレスを*byte型にキャストしています。これは、Setsockopt関数がオプション値へのポインタを*byte型で受け取るためです。unsafe.Pointerを使用することで、型安全性を一時的に無効にしてポインタの型変換を行っています。
    • int32(unsafe.Sizeof(sys)): sysLinger構造体のサイズをバイト単位で取得し、それをint32型にキャストしてSetsockoptに渡しています。これにより、システムコールが正しいサイズのデータを参照できるようになります。
  3. Getsockopt関数の追加: Windows環境でソケットオプションを取得するためのGetsockopt関数がsyscallパッケージに追加されました。これは、ws2_32.dllgetsockopt関数を呼び出すためのラッパーです。

    //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.gozsyscall_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.Pointeroptvaloptlenのポインタを渡すために使用されています。戻り値r1-1の場合、エラーが発生したと判断し、e1(システムコールからのエラーコード)またはEINVALを返します。

  4. GetCurrentProcessIdの移動: GetCurrentProcessId関数は、zsyscall_windows_386.gozsyscall_windows_amd64.go内で、procGetCurrentProcessIdの定義と関数の実装が移動されました。これは機能的な変更ではなく、コードの整理と一貫性のためのものです。

これらの変更により、GoプログラムはWindows上でSO_LINGERオプションを正確に設定できるようになり、またソケットオプションの取得も可能になりました。

コアとなるコードの変更箇所

このコミットでは、主に以下の4つのファイルが変更されています。

  1. 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
    
  2. 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     }
    
  3. 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
    -}
    
  4. 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_onoffl_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上でソケットオプションを安全かつ正確に取得できるようになります。

関連リンク

参考にした情報源リンク