Golang模拟用户连接SQL Server时触发Socket异常(WSAEPROVIDERFAILEDINIT)
问题分析与解决方案
核心问题
错误WSAEPROVIDERFAILEDINIT(10106)的本质是模拟用户后的线程无法初始化Winsock服务提供者。Winsock的WSAStartup是线程关联操作,线程安全上下文切换(模拟用户)后,系统无法为新上下文加载Winsock所需的服务组件,导致套接字创建失败。
解决步骤
提前初始化Winsock
在调用ImpersonateLoggedOnUser之前,先在当前线程完成Winsock初始化。因为Winsock初始化绑定线程上下文,模拟后再执行初始化会因权限/上下文不匹配失败。调整LogonUser登录类型
原代码使用的LOGON32_LOGON_NETWORK_CLEARTEXT属于受限登录令牌,权限不足以完成网络套接字初始化。建议替换为LOGON32_LOGON_INTERACTIVE或LOGON32_LOGON_BATCH,这类令牌拥有更完整的网络操作权限。规范资源清理顺序
确保WSACleanup在RevertToSelf之后调用,且与WSAStartup在同一线程执行,避免上下文混乱。
修改后的代码示例
package main import ( "database/sql" "fmt" "log" "runtime" "sync" "syscall" "unsafe" _ "github.com/microsoft/go-mssqldb" "golang.org/x/sys/windows" ) const ( user = "username" pass = "password" domain = "domain" ) var ( advapi32 = syscall.NewLazyDLL("advapi32.dll") ws2_32 = syscall.NewLazyDLL("ws2_32.dll") logonProc = advapi32.NewProc("LogonUserW") impersonateProc = advapi32.NewProc("ImpersonateLoggedOnUser") revertSelfProc = advapi32.NewProc("RevertToSelf") wsaStartupProc = ws2_32.NewProc("WSAStartup") wsacleanupProc = ws2_32.NewProc("WSACleanup") ) // WSAData 定义Winsock初始化结构体 type WSAData struct { Version uint16 HighVersion uint16 Description [257]uint16 SystemStatus [129]uint16 MaxSockets uint16 MaxUdpDg uint16 VendorInfo uintptr } func main() { log.SetFlags(0) var wg sync.WaitGroup wg.Add(1) go func(user, pass string) { defer wg.Done() runtime.LockOSThread() defer runtime.UnlockOSThread() log.Println("In a goroutine") // 1. 先初始化Winsock,在模拟用户之前 var wsaData WSAData res, _, err := wsaStartupProc.Call(uintptr(0x202), uintptr(unsafe.Pointer(&wsaData))) if res != 0 { log.Fatalf("WSAStartup failed: %v", err) } defer func() { // 3. 先恢复身份,再清理Winsock mustRevertToSelf() _, _, _ = wsacleanupProc.Call() }() // 2. 使用交互式登录类型替代NETWORK_CLEARTEXT token, err := logonUser(user, pass, domain) if err != nil { fmt.Println(err) return } defer closeHandle(token) u, _ := token.GetTokenUser() log.Println(u) r, _ := token.IsRestricted() log.Printf("token is restricted: %t", r) e := token.IsElevated() log.Printf("token is elevated: %t", e) err = impersonateUser(token) if err != nil { log.Println(err) return } connectToDB() log.Println("Impersonated") }(user, pass) wg.Wait() log.Println("OK") } func logonUser(user, pass, domain string) (windows.Token, error) { const ( LOGON32_LOGON_INTERACTIVE = uintptr(2) LOGON32_PROVIDER_DEFAULT = uintptr(0) ) pUsername, _ := windows.UTF16PtrFromString(user) pDomain, _ := windows.UTF16PtrFromString(domain) pPassword, _ := windows.UTF16PtrFromString(pass) hToken := uintptr(0) res, _, err := logonProc.Call( uintptr(unsafe.Pointer(pUsername)), uintptr(unsafe.Pointer(pDomain)), uintptr(unsafe.Pointer(pPassword)), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, uintptr(unsafe.Pointer(&hToken)), ) if res != 0 { return windows.Token(hToken), nil } return 0, err } func impersonateUser(token windows.Token) error { res, _, err := impersonateProc.Call(uintptr(token)) if res == 0 { return err } return nil } func revertToSelf() error { rc, _, ec := syscall.SyscallN(revertSelfProc.Addr(), 0, 0, 0, 0) if rc == 0 { return ec } return nil } func mustRevertToSelf() { err := revertToSelf() if err != nil { panic(err) } } func closeHandle(token windows.Token) { err := token.Close() if err != nil { panic(err) } } func connectToDB() { // 集成身份验证时无需显式指定User ID和Password,自动使用模拟身份 connectionStr := "Data Source=<host>;Initial Catalog=<Database>;Integrated Security=true;Encrypt=False;TrustServerCertificate=True" db, err := sql.Open("mssql", connectionStr) if err != nil { fmt.Println(err) return } defer db.Close() err = db.Ping() if err != nil { log.Println(err.Error()) return } log.Println("Database connection successful") }
额外说明
- 集成身份验证模式下,连接字符串无需添加
User ID和Password,驱动会自动使用当前模拟的用户身份。 - 确保模拟用户拥有目标SQL Server的登录权限及对应数据库的访问权限。
- 若必须使用
NETWORK_CLEARTEXT登录类型,需在本地安全策略中给该用户授予“作为操作系统的一部分操作”权限,但此权限过大,不推荐使用。
内容的提问来源于stack exchange,提问作者rajat
相关产品推荐
相关产品推荐

