forked from golang/go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Package socktest provides utilities for socket testing. This package allows test cases in the net package to simulate complicated network conditions such as that a destination address is resolvable/discoverable but is not routable/reachable at network layer. Those conditions are required for testing functionality of timeout, multiple address families. Change-Id: Idbe32bcc3319b41b0cecac3d058014a93e13288b Reviewed-on: https://go-review.googlesource.com/6090 Reviewed-by: Ian Lance Taylor <[email protected]>
- Loading branch information
Showing
10 changed files
with
623 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build !plan9 | ||
|
||
package socktest_test | ||
|
||
import ( | ||
"net/internal/socktest" | ||
"os" | ||
"syscall" | ||
"testing" | ||
) | ||
|
||
var sw socktest.Switch | ||
|
||
func TestMain(m *testing.M) { | ||
installTestHooks() | ||
|
||
st := m.Run() | ||
|
||
for s := range sw.Sockets() { | ||
closeFunc(s) | ||
} | ||
uninstallTestHooks() | ||
os.Exit(st) | ||
} | ||
|
||
func TestSocket(t *testing.T) { | ||
for _, f := range []socktest.Filter{ | ||
func(st *socktest.Status) (socktest.AfterFilter, error) { return nil, nil }, | ||
nil, | ||
} { | ||
sw.Set(socktest.FilterSocket, f) | ||
for _, family := range []int{syscall.AF_INET, syscall.AF_INET6} { | ||
socketFunc(family, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build !plan9,!windows | ||
|
||
package socktest_test | ||
|
||
import "syscall" | ||
|
||
var ( | ||
socketFunc func(int, int, int) (int, error) | ||
closeFunc func(int) error | ||
) | ||
|
||
func installTestHooks() { | ||
socketFunc = sw.Socket | ||
closeFunc = sw.Close | ||
} | ||
|
||
func uninstallTestHooks() { | ||
socketFunc = syscall.Socket | ||
closeFunc = syscall.Close | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package socktest_test | ||
|
||
import "syscall" | ||
|
||
var ( | ||
socketFunc func(int, int, int) (syscall.Handle, error) | ||
closeFunc func(syscall.Handle) error | ||
) | ||
|
||
func installTestHooks() { | ||
socketFunc = sw.Socket | ||
closeFunc = sw.Closesocket | ||
} | ||
|
||
func uninstallTestHooks() { | ||
socketFunc = syscall.Socket | ||
closeFunc = syscall.Closesocket | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package socktest provides utilities for socket testing. | ||
package socktest | ||
|
||
import "sync" | ||
|
||
func switchInit(sw *Switch) { | ||
sw.fltab = make(map[FilterType]Filter) | ||
sw.sotab = make(Sockets) | ||
sw.stats = make(stats) | ||
} | ||
|
||
// A Switch represents a callpath point switch for socket system | ||
// calls. | ||
type Switch struct { | ||
once sync.Once | ||
|
||
fmu sync.RWMutex | ||
fltab map[FilterType]Filter | ||
|
||
smu sync.RWMutex | ||
sotab Sockets | ||
stats stats | ||
} | ||
|
||
// Stats returns a list of per-cookie socket statistics. | ||
func (sw *Switch) Stats() []Stat { | ||
var st []Stat | ||
sw.smu.RLock() | ||
for _, s := range sw.stats { | ||
ns := *s | ||
st = append(st, ns) | ||
} | ||
sw.smu.RUnlock() | ||
return st | ||
} | ||
|
||
// Sockets returns mappings of socket descriptor to socket status. | ||
func (sw *Switch) Sockets() Sockets { | ||
sw.smu.RLock() | ||
tab := make(Sockets, len(sw.sotab)) | ||
for i, s := range sw.sotab { | ||
tab[i] = s | ||
} | ||
sw.smu.RUnlock() | ||
return tab | ||
} | ||
|
||
// A Cookie represents a 3-tuple of a socket; address family, socket | ||
// type and protocol number. | ||
type Cookie uint64 | ||
|
||
// Family returns an address family. | ||
func (c Cookie) Family() int { return int(c >> 48) } | ||
|
||
// Type returns a socket type. | ||
func (c Cookie) Type() int { return int(c << 16 >> 32) } | ||
|
||
// Protocol returns a protocol number. | ||
func (c Cookie) Protocol() int { return int(c & 0xff) } | ||
|
||
func cookie(family, sotype, proto int) Cookie { | ||
return Cookie(family)<<48 | Cookie(sotype)&0xffffffff<<16 | Cookie(proto)&0xff | ||
} | ||
|
||
// A Status represents the status of a socket. | ||
type Status struct { | ||
Cookie Cookie | ||
Err error // error status of socket system call | ||
SocketErr int // error status of socket by SO_ERROR | ||
} | ||
|
||
// A Stat represents a per-cookie socket statistics. | ||
type Stat struct { | ||
Family int // address family | ||
Type int // socket type | ||
Protocol int // protocol number | ||
|
||
Opened uint64 // number of sockets opened | ||
Accepted uint64 // number of sockets accepted | ||
Connected uint64 // number of sockets connected | ||
Closed uint64 // number of sockets closed | ||
} | ||
|
||
type stats map[Cookie]*Stat | ||
|
||
func (st stats) getLocked(c Cookie) *Stat { | ||
s, ok := st[c] | ||
if !ok { | ||
s = &Stat{Family: c.Family(), Type: c.Type(), Protocol: c.Protocol()} | ||
st[c] = s | ||
} | ||
return s | ||
} | ||
|
||
// A FilterType represents a filter type. | ||
type FilterType int | ||
|
||
const ( | ||
FilterSocket FilterType = iota // for Socket | ||
FilterAccept // for Accept or Accept4 | ||
FilterConnect // for Connect or ConnectEx | ||
FilterGetsockoptInt // for GetsockoptInt | ||
FilterClose // for Close or Closesocket | ||
) | ||
|
||
// A Filter represents a socket system call filter. | ||
// | ||
// It will only be executed before a system call for a socket that has | ||
// an entry in internal table. | ||
// If the filter returns a non-nil error, the execution of system call | ||
// will be canceled and the system call function returns the non-nil | ||
// error. | ||
// It can return a non-nil AfterFilter for filtering after the | ||
// execution of the system call. | ||
type Filter func(*Status) (AfterFilter, error) | ||
|
||
func (f Filter) apply(st *Status) (AfterFilter, error) { | ||
if f == nil { | ||
return nil, nil | ||
} | ||
return f(st) | ||
} | ||
|
||
// An AfterFilter represents a socket system call filter after an | ||
// execution of a system call. | ||
// | ||
// It will only be executed after a system call for a socket that has | ||
// an entry in internal table. | ||
// If the filter returns a non-nil error, the system call function | ||
// returns the non-nil error. | ||
type AfterFilter func(*Status) error | ||
|
||
func (f AfterFilter) apply(st *Status) error { | ||
if f == nil { | ||
return nil | ||
} | ||
return f(st) | ||
} | ||
|
||
// Set deploys the socket system call filter f for the filter type t. | ||
func (sw *Switch) Set(t FilterType, f Filter) { | ||
sw.once.Do(func() { switchInit(sw) }) | ||
sw.fmu.Lock() | ||
sw.fltab[t] = f | ||
sw.fmu.Unlock() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build plan9 | ||
|
||
package socktest | ||
|
||
// Sockets maps a socket descriptor to the status of socket. | ||
type Sockets map[int]Status |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris | ||
|
||
package socktest | ||
|
||
// Sockets maps a socket descriptor to the status of socket. | ||
type Sockets map[int]Status | ||
|
||
func (sw *Switch) sockso(s int) *Status { | ||
sw.smu.RLock() | ||
defer sw.smu.RUnlock() | ||
so, ok := sw.sotab[s] | ||
if !ok { | ||
return nil | ||
} | ||
return &so | ||
} | ||
|
||
// addLocked returns a new Status without locking. | ||
// sw.smu must be held before call. | ||
func (sw *Switch) addLocked(s, family, sotype, proto int) *Status { | ||
sw.once.Do(func() { switchInit(sw) }) | ||
so := Status{Cookie: cookie(family, sotype, proto)} | ||
sw.sotab[s] = so | ||
return &so | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package socktest | ||
|
||
import "syscall" | ||
|
||
// Sockets maps a socket descriptor to the status of socket. | ||
type Sockets map[syscall.Handle]Status | ||
|
||
func (sw *Switch) sockso(s syscall.Handle) *Status { | ||
sw.smu.RLock() | ||
defer sw.smu.RUnlock() | ||
so, ok := sw.sotab[s] | ||
if !ok { | ||
return nil | ||
} | ||
return &so | ||
} | ||
|
||
// addLocked returns a new Status without locking. | ||
// sw.smu must be held before call. | ||
func (sw *Switch) addLocked(s syscall.Handle, family, sotype, proto int) *Status { | ||
sw.once.Do(func() { switchInit(sw) }) | ||
so := Status{Cookie: cookie(family, sotype, proto)} | ||
sw.sotab[s] = so | ||
return &so | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// +build freebsd linux | ||
|
||
package socktest | ||
|
||
import "syscall" | ||
|
||
// Accept4 wraps syscall.Accept4. | ||
func (sw *Switch) Accept4(s, flags int) (ns int, sa syscall.Sockaddr, err error) { | ||
so := sw.sockso(s) | ||
if so == nil { | ||
return syscall.Accept4(s, flags) | ||
} | ||
sw.fmu.RLock() | ||
f, _ := sw.fltab[FilterAccept] | ||
sw.fmu.RUnlock() | ||
|
||
af, err := f.apply(so) | ||
if err != nil { | ||
return -1, nil, err | ||
} | ||
ns, sa, so.Err = syscall.Accept4(s, flags) | ||
if err = af.apply(so); err != nil { | ||
if so.Err == nil { | ||
syscall.Close(ns) | ||
} | ||
return -1, nil, err | ||
} | ||
|
||
if so.Err != nil { | ||
return -1, nil, so.Err | ||
} | ||
sw.smu.Lock() | ||
nso := sw.addLocked(ns, so.Cookie.Family(), so.Cookie.Type(), so.Cookie.Protocol()) | ||
sw.stats.getLocked(nso.Cookie).Accepted++ | ||
sw.smu.Unlock() | ||
return ns, sa, nil | ||
} |
Oops, something went wrong.