Procházet zdrojové kódy

Add support for attaching BPFs to sockets

Rod Hynes před 6 roky
rodič
revize
c16dbed337

+ 9 - 0
psiphon/TCPConn_bind.go

@@ -136,6 +136,15 @@ func tcpDial(ctx context.Context, addr string, config *DialConfig) (net.Conn, er
 
 		setAdditionalSocketOptions(socketFD)
 
+		if config.BPFProgramInstructions != nil {
+			err = setSocketBPF(config.BPFProgramInstructions, socketFD)
+			if err != nil {
+				syscall.Close(socketFD)
+				lastErr = errors.Trace(err)
+				continue
+			}
+		}
+
 		if config.DeviceBinder != nil {
 			_, err = config.DeviceBinder.BindToDevice(socketFD)
 			if err != nil {

+ 1 - 1
psiphon/UDPConn_bind.go

@@ -43,7 +43,7 @@ func newUDPConn(domain int, config *DialConfig) (net.PacketConn, error) {
 	setAdditionalSocketOptions(socketFD)
 
 	if config.DeviceBinder != nil {
-		err := bindToDeviceCallWrapper(config.DeviceBinder, socketFD)
+		_, err = config.DeviceBinder.BindToDevice(socketFD)
 		if err != nil {
 			syscall.Close(socketFD)
 			return nil, errors.Tracef("BindToDevice failed: %s", err)

+ 208 - 0
psiphon/common/parameters/bpf.go

@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2020, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package parameters
+
+import (
+	"encoding/json"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
+	"golang.org/x/net/bpf"
+)
+
+// BPFProgramSpec specifies a BPF program. The Name field is informational and
+// may be used for logging. The Instructions field is a list of values which
+// map to golang.org/x/net/bpf.Instruction and which can be marshaled.
+type BPFProgramSpec struct {
+	Name         string
+	Instructions []BPFInstructionSpec
+}
+
+// Validate validates a BPF program spec.
+func (s *BPFProgramSpec) Validate() error {
+	if s.Name == "" {
+		return errors.TraceNew("missing name")
+	}
+	if len(s.Instructions) < 1 {
+		return errors.TraceNew("missing instructions")
+	}
+	_, err := s.Assemble()
+	return errors.Trace(err)
+}
+
+// Assemble converts the Instructions to equivilent
+// golang.org/x/net/bpf.Instruction values and assembles these into raw
+// instructions suitable for attaching to a socket.
+func (s *BPFProgramSpec) Assemble() ([]bpf.RawInstruction, error) {
+
+	if len(s.Instructions) == 0 {
+		return nil, errors.TraceNew("empty program")
+	}
+
+	program := make([]bpf.Instruction, len(s.Instructions))
+	for i, instructionSpec := range s.Instructions {
+		instruction, err := instructionSpec.GetInstruction()
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		program[i] = instruction
+	}
+
+	raw, err := bpf.Assemble(program)
+	if err != nil {
+		return nil, errors.Trace(err)
+	}
+
+	return raw, nil
+}
+
+// BPFInstructionSpec represents a golang.org/x/net/bpf.Instruction and can be
+// marshaled.
+type BPFInstructionSpec struct {
+	Name        string
+	Instruction json.RawMessage
+}
+
+// GetInstruction coverts a BPFInstructionSpec to the equivilent
+// golang.org/x/net/bpf.Instruction.
+func (s *BPFInstructionSpec) GetInstruction() (bpf.Instruction, error) {
+	switch s.Name {
+	case "ALUOpConstant":
+		var instruction bpf.ALUOpConstant
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "ALUOpX":
+		var instruction bpf.ALUOpX
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "Jump":
+		var instruction bpf.Jump
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "JumpIf":
+		var instruction bpf.JumpIf
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "JumpIfX":
+		var instruction bpf.JumpIfX
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "LoadAbsolute":
+		var instruction bpf.LoadAbsolute
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "LoadConstant":
+		var instruction bpf.LoadConstant
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "LoadExtension":
+		var instruction bpf.LoadExtension
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "LoadIndirect":
+		var instruction bpf.LoadIndirect
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "LoadMemShift":
+		var instruction bpf.LoadMemShift
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "LoadScratch":
+		var instruction bpf.LoadScratch
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "NegateA":
+		var instruction bpf.NegateA
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "RetA":
+		var instruction bpf.RetA
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "RetConstant":
+		var instruction bpf.RetConstant
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "StoreScratch":
+		var instruction bpf.StoreScratch
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "TAX":
+		var instruction bpf.TAX
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	case "TXA":
+		var instruction bpf.TXA
+		err := json.Unmarshal(s.Instruction, &instruction)
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+		return instruction, nil
+	}
+
+	return nil, errors.Tracef("unknown bpf instruction: %s", s.Name)
+}

+ 36 - 0
psiphon/common/parameters/clientParameters.go

@@ -64,6 +64,7 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/obfuscator"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
+	"golang.org/x/net/bpf"
 )
 
 const (
@@ -201,6 +202,7 @@ const (
 	ReplayTargetUpstreamBytes                        = "ReplayTargetUpstreamBytes"
 	ReplayTargetDownstreamBytes                      = "ReplayTargetDownstreamBytes"
 	ReplayTargetTunnelDuration                       = "ReplayTargetTunnelDuration"
+	ReplayBPF                                        = "ReplayBPF"
 	ReplaySSH                                        = "ReplaySSH"
 	ReplayObfuscatorPadding                          = "ReplayObfuscatorPadding"
 	ReplayFragmentor                                 = "ReplayFragmentor"
@@ -226,6 +228,10 @@ const (
 	ServerEntryMinimumAgeForPruning                  = "ServerEntryMinimumAgeForPruning"
 	ApplicationParametersProbability                 = "ApplicationParametersProbability"
 	ApplicationParameters                            = "ApplicationParameters"
+	BPFServerTCPProgram                              = "BPFServerTCPProgram"
+	BPFServerTCPProbability                          = "BPFServerTCPProbability"
+	BPFClientTCPProgram                              = "BPFClientTCPProgram"
+	BPFClientTCPProbability                          = "BPFClientTCPProbability"
 )
 
 const (
@@ -440,6 +446,7 @@ var defaultClientParameters = map[string]struct {
 	ReplayTargetUpstreamBytes:              {value: 0, minimum: 0},
 	ReplayTargetDownstreamBytes:            {value: 0, minimum: 0},
 	ReplayTargetTunnelDuration:             {value: 1 * time.Second, minimum: time.Duration(0)},
+	ReplayBPF:                              {value: true},
 	ReplaySSH:                              {value: true},
 	ReplayObfuscatorPadding:                {value: true},
 	ReplayFragmentor:                       {value: true},
@@ -469,6 +476,11 @@ var defaultClientParameters = map[string]struct {
 
 	ApplicationParametersProbability: {value: 1.0, minimum: 0.0},
 	ApplicationParameters:            {value: KeyValues{}},
+
+	BPFServerTCPProgram:     {value: (*BPFProgramSpec)(nil)},
+	BPFServerTCPProbability: {value: 0.5, minimum: 0.0},
+	BPFClientTCPProgram:     {value: (*BPFProgramSpec)(nil)},
+	BPFClientTCPProbability: {value: 0.5, minimum: 0.0},
 }
 
 // IsServerSideOnly indicates if the parameter specified by name is used
@@ -663,6 +675,16 @@ func (p *ClientParameters) Set(
 					}
 					return nil, errors.Trace(err)
 				}
+			case *BPFProgramSpec:
+				if v != nil {
+					err := v.Validate()
+					if err != nil {
+						if skipOnError {
+							continue
+						}
+						return nil, errors.Trace(err)
+					}
+				}
 			}
 
 			// Enforce any minimums. Assumes defaultClientParameters[name]
@@ -1045,3 +1067,17 @@ func (p ClientParametersAccessor) KeyValues(name string) KeyValues {
 	p.snapshot.getValue(name, &value)
 	return value
 }
+
+// BPFProgram returns an assembled BPF program corresponding to a
+// BPFProgramSpec parameter value. Returns nil in the case of any empty
+// program.
+func (p ClientParametersAccessor) BPFProgram(name string) (bool, string, []bpf.RawInstruction) {
+	var value *BPFProgramSpec
+	p.snapshot.getValue(name, &value)
+	if value == nil {
+		return false, "", nil
+	}
+	// Validation checks that Assemble is successful.
+	rawInstructions, _ := value.Assemble()
+	return true, value.Name, rawInstructions
+}

+ 7 - 0
psiphon/common/parameters/clientParameters_test.go

@@ -108,6 +108,13 @@ func TestGetDefaultParameters(t *testing.T) {
 			if !reflect.DeepEqual(v, g) {
 				t.Fatalf("KeyValues returned %+v expected %+v", g, v)
 			}
+		case *BPFProgramSpec:
+			ok, name, rawInstructions := p.Get().BPFProgram(name)
+			if v != nil || ok || name != "" || rawInstructions != nil {
+				t.Fatalf(
+					"BPFProgramSpec returned %+v %+v %+v expected %+v",
+					ok, name, rawInstructions, v)
+			}
 		default:
 			t.Fatalf("Unhandled default type: %s", name)
 		}

+ 12 - 0
psiphon/common/protocol/protocol.go

@@ -126,6 +126,13 @@ var SupportedServerEntrySources = TunnelProtocols{
 	SERVER_ENTRY_SOURCE_EXCHANGED,
 }
 
+func TunnelProtocolUsesTCP(protocol string) bool {
+	// Limitation: Marionette network protocol depends on its format configuration.
+	return protocol != TUNNEL_PROTOCOL_QUIC_OBFUSCATED_SSH &&
+		protocol != TUNNEL_PROTOCOL_FRONTED_MEEK_QUIC_OBFUSCATED_SSH &&
+		protocol != TUNNEL_PROTOCOL_MARIONETTE_OBFUSCATED_SSH
+}
+
 func TunnelProtocolUsesSSH(protocol string) bool {
 	return true
 }
@@ -389,3 +396,8 @@ func DeriveSSHServerVersionPRNGSeed(obfuscatedKey string) (*prng.Seed, error) {
 	seed := prng.Seed(sha256.Sum256([]byte(obfuscatedKey)))
 	return prng.NewSaltedSeed(&seed, "ssh-server-version")
 }
+
+func DeriveBPFServerProgramPRNGSeed(obfuscatedKey string) (*prng.Seed, error) {
+	seed := prng.Seed(sha256.Sum256([]byte(obfuscatedKey)))
+	return prng.NewSaltedSeed(&seed, "bpf-server-program")
+}

+ 7 - 3
psiphon/common/tactics/tactics.go

@@ -618,7 +618,7 @@ func (server *Server) GetTacticsPayload(
 	// includeServerSideOnly is false: server-side only parameters are not
 	// used by the client, so including them wastes space and unnecessarily
 	// exposes the values.
-	tactics, err := server.getTactics(false, geoIPData, apiParams)
+	tactics, err := server.GetTactics(false, geoIPData, apiParams)
 	if err != nil {
 		return nil, errors.Trace(err)
 	}
@@ -668,7 +668,11 @@ func (server *Server) GetTacticsPayload(
 	return payload, nil
 }
 
-func (server *Server) getTactics(
+// GetTactics assembles and returns tactics data for a client with the
+// specified GeoIP, API parameter, and speed test attributes.
+//
+// The tactics return value may be nil.
+func (server *Server) GetTactics(
 	includeServerSideOnly bool,
 	geoIPData common.GeoIPData,
 	apiParams common.APIParameters) (*Tactics, error) {
@@ -1111,7 +1115,7 @@ func (listener *Listener) Accept() (net.Conn, error) {
 
 	geoIPData := listener.geoIPLookup(common.IPAddressFromAddr(conn.RemoteAddr()))
 
-	tactics, err := listener.server.getTactics(true, geoIPData, make(common.APIParameters))
+	tactics, err := listener.server.GetTactics(true, geoIPData, make(common.APIParameters))
 	if err != nil {
 		listener.server.logger.WithTraceFields(
 			common.LogFields{"error": err}).Warning("failed to get tactics for connection")

+ 2 - 7
psiphon/common/tapdance/tapdance.go

@@ -57,7 +57,7 @@ type Listener struct {
 	net.Listener
 }
 
-// Listen creates a new Tapdance listener.
+// Listen creates a new Tapdance listener on top of an existing TCP listener.
 //
 // The Tapdance station will send the original client address via the HAProxy
 // proxy protocol v1, https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt.
@@ -65,12 +65,7 @@ type Listener struct {
 // RemoteAddr. RemoteAddr _must_ be called non-concurrently before calling Read
 // on accepted conns as the HAProxy proxy protocol header reading logic sets
 // SetReadDeadline and performs a Read.
-func Listen(address string) (*Listener, error) {
-
-	tcpListener, err := net.Listen("tcp", address)
-	if err != nil {
-		return nil, errors.Trace(err)
-	}
+func Listen(tcpListener net.Listener) (*Listener, error) {
 
 	// Setting a timeout ensures that reading the proxy protocol
 	// header completes or times out and RemoteAddr will not block. See:

+ 1 - 1
psiphon/common/tapdance/tapdance_disabled.go

@@ -40,7 +40,7 @@ type Listener struct {
 }
 
 // Listen creates a new Tapdance listener.
-func Listen(_ string) (*Listener, error) {
+func Listen(_ net.Listener) (*Listener, error) {
 	return nil, errors.TraceNew("operation is not enabled")
 }
 

+ 21 - 0
psiphon/dialParameters.go

@@ -39,6 +39,7 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/values"
 	utls "github.com/refraction-networking/utls"
 	regen "github.com/zach-klippenstein/goregen"
+	"golang.org/x/net/bpf"
 )
 
 // DialParameters represents a selected protocol and all the related selected
@@ -75,6 +76,9 @@ type DialParameters struct {
 	UpstreamProxyType              string   `json:"-"`
 	UpstreamProxyCustomHeaderNames []string `json:"-"`
 
+	BPFProgramName         string
+	BPFProgramInstructions []bpf.RawInstruction
+
 	SelectedSSHClientVersion bool
 	SSHClientVersion         string
 	SSHKEXSeed               *prng.Seed
@@ -149,6 +153,7 @@ func MakeDialParameters(
 	p := config.GetClientParameters().Get()
 
 	ttl := p.Duration(parameters.ReplayDialParametersTTL)
+	replayBPF := p.Bool(parameters.ReplayBPF)
 	replaySSH := p.Bool(parameters.ReplaySSH)
 	replayObfuscatorPadding := p.Bool(parameters.ReplayObfuscatorPadding)
 	replayFragmentor := p.Bool(parameters.ReplayFragmentor)
@@ -318,6 +323,21 @@ func MakeDialParameters(
 		dialParams.TunnelProtocol = selectedProtocol
 	}
 
+	if (!isReplay || !replayBPF) &&
+		supportsBPF() &&
+		protocol.TunnelProtocolUsesTCP(dialParams.TunnelProtocol) {
+
+		if p.WeightedCoinFlip(parameters.BPFClientTCPProbability) {
+			dialParams.BPFProgramName = ""
+			dialParams.BPFProgramInstructions = nil
+			ok, name, rawInstructions := p.BPFProgram(parameters.BPFClientTCPProgram)
+			if ok {
+				dialParams.BPFProgramName = name
+				dialParams.BPFProgramInstructions = rawInstructions
+			}
+		}
+	}
+
 	if !isReplay || !replaySSH {
 		dialParams.SelectedSSHClientVersion = true
 		dialParams.SSHClientVersion = values.GetSSHClientVersion()
@@ -603,6 +623,7 @@ func MakeDialParameters(
 		DiagnosticID:                  serverEntry.GetDiagnosticID(),
 		UpstreamProxyURL:              config.UpstreamProxyURL,
 		CustomHeaders:                 dialCustomHeaders,
+		BPFProgramInstructions:        dialParams.BPFProgramInstructions,
 		DeviceBinder:                  config.deviceBinder,
 		DnsServerGetter:               config.DnsServerGetter,
 		IPv6Synthesizer:               config.IPv6Synthesizer,

+ 5 - 0
psiphon/net.go

@@ -38,6 +38,7 @@ import (
 	"github.com/Psiphon-Labs/dns"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/fragmentor"
+	"golang.org/x/net/bpf"
 )
 
 const DNS_PORT = 53
@@ -67,6 +68,10 @@ type DialConfig struct {
 	// upstream proxy when specified by UpstreamProxyURL.
 	CustomHeaders http.Header
 
+	// BPFProgramInstructions specifies a BPF program to attach to the dial
+	// socket before connecting.
+	BPFProgramInstructions []bpf.RawInstruction
+
 	// BindToDevice parameters are used to exclude connections and
 	// associated DNS requests from VPN routing.
 	// When DeviceBinder is set, any underlying socket is

+ 51 - 0
psiphon/net_android_linux.go

@@ -0,0 +1,51 @@
+// +build android linux
+
+/*
+ * Copyright (c) 2020, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package psiphon
+
+import (
+	"unsafe"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
+	"golang.org/x/net/bpf"
+	"golang.org/x/sys/unix"
+)
+
+func supportsBPF() bool {
+	return true
+}
+
+func setSocketBPF(BPFProgramInstructions []bpf.RawInstruction, socketFD int) error {
+
+	// Tactics parameters validation ensures BPFProgramInstructions has len >= 1.
+	err := unix.SetsockoptSockFprog(
+		socketFD,
+		unix.SOL_SOCKET,
+		unix.SO_ATTACH_FILTER,
+		&unix.SockFprog{
+			Len:    uint16(len(BPFProgramInstructions)),
+			Filter: (*unix.SockFilter)(unsafe.Pointer(&BPFProgramInstructions[0])),
+		})
+	return errors.Trace(err)
+}
+
+func setAdditionalSocketOptions(_ int) {
+}

+ 11 - 5
psiphon/net_darwin.go

@@ -21,13 +21,19 @@ package psiphon
 
 import (
 	"syscall"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
+	"golang.org/x/net/bpf"
 )
 
-func setAdditionalSocketOptions(socketFd int) {
-	syscall.SetsockoptInt(socketFd, syscall.SOL_SOCKET, syscall.SO_NOSIGPIPE, 1)
+func supportsBPF() bool {
+	return false
 }
 
-func bindToDeviceCallWrapper(deviceBinder DeviceBinder, socketFD int) error {
-	_, err := deviceBinder.BindToDevice(socketFD)
-	return err
+func setSocketBPF(_ []bpf.RawInstruction, _ int) error {
+	return errors.TraceNew("BPF not supported")
+}
+
+func setAdditionalSocketOptions(socketFd int) {
+	syscall.SetsockoptInt(socketFd, syscall.SOL_SOCKET, syscall.SO_NOSIGPIPE, 1)
 }

+ 13 - 5
psiphon/net_other.go

@@ -1,4 +1,4 @@
-// +build !darwin,!windows
+// +build !darwin,!android,!linux
 
 /*
  * Copyright (c) 2017, Psiphon Inc.
@@ -21,10 +21,18 @@
 
 package psiphon
 
-func setAdditionalSocketOptions(_ int) {
+import (
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
+	"golang.org/x/net/bpf"
+)
+
+func supportsBPF() bool {
+	return false
 }
 
-func bindToDeviceCallWrapper(deviceBinder DeviceBinder, socketFD int) error {
-	_, err := deviceBinder.BindToDevice(socketFD)
-	return err
+func setSocketBPF(_ []bpf.RawInstruction, _ int) error {
+	return errors.TraceNew("BPF not supported")
+}
+
+func setAdditionalSocketOptions(_ int) {
 }

+ 4 - 0
psiphon/notice.go

@@ -441,6 +441,10 @@ func noticeWithDialParameters(noticeType string, dialParams *DialParameters) {
 
 	if GetEmitNetworkParameters() {
 
+		if dialParams.BPFProgramName != "" {
+			args = append(args, "client_bpf", dialParams.BPFProgramName)
+		}
+
 		if dialParams.SelectedSSHClientVersion {
 			args = append(args, "SSHClientVersion", dialParams.SSHClientVersion)
 		}

+ 1 - 0
psiphon/server/api.go

@@ -745,6 +745,7 @@ var baseRequestParams = []requestParamSpec{
 	{"meek_limit_request", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"meek_tls_padding", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"network_latency_multiplier", isFloatString, requestParamOptional | requestParamLogStringAsFloat},
+	{"client_bpf", isAnyString, requestParamOptional},
 }
 
 func validateRequestParams(

+ 132 - 0
psiphon/server/bpf.go

@@ -0,0 +1,132 @@
+// +build linux
+
+/*
+ * Copyright (c) 2020, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package server
+
+import (
+	"context"
+	"net"
+	"syscall"
+	"unsafe"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
+	"golang.org/x/net/bpf"
+	"golang.org/x/sys/unix"
+)
+
+// newTCPListenerWithBPF creates a TCP net.Listener, optionally attaching
+// the BPF program specified by the tactics parameter BPFServerTCPProgram.
+func newTCPListenerWithBPF(
+	support *SupportServices,
+	localAddress string) (net.Listener, string, error) {
+
+	// Limitations:
+	// - BPFServerTCPProgram must be set unconditionally as neither client GeoIP
+	//   nor API parameters are checked before the BPF is attached.
+	// - Currently, lhe listener BPF is not reset upon tactics hot reload.
+
+	havePBFProgram, programName, rawInstructions, err := getBPFProgram(support)
+	if err != nil {
+		log.WithTraceFields(
+			LogFields{"error": err}).Warning("failed to get BPF program for listener")
+		// If tactics is somehow misconfigured, keep running.
+	}
+
+	listenConfig := &net.ListenConfig{}
+
+	if havePBFProgram {
+
+		programName = programName
+
+		// Tactics parameters validation ensures BPFProgramInstructions has len >= 1.
+		listenConfig.Control = func(network, address string, c syscall.RawConn) error {
+			var setSockOptError error
+			err := c.Control(func(fd uintptr) {
+				setSockOptError = unix.SetsockoptSockFprog(
+					int(fd),
+					unix.SOL_SOCKET,
+					unix.SO_ATTACH_FILTER,
+					&unix.SockFprog{
+						Len:    uint16(len(rawInstructions)),
+						Filter: (*unix.SockFilter)(unsafe.Pointer(&rawInstructions[0])),
+					})
+			})
+			if err == nil {
+				err = setSockOptError
+			}
+			return errors.Trace(err)
+		}
+	}
+
+	listener, err := listenConfig.Listen(context.Background(), "tcp", localAddress)
+
+	return listener, programName, errors.Trace(err)
+}
+
+func getBPFProgram(support *SupportServices) (bool, string, []bpf.RawInstruction, error) {
+
+	tactics, err := support.TacticsServer.GetTactics(
+		true, common.GeoIPData(NewGeoIPData()), make(common.APIParameters))
+	if err != nil {
+		return false, "", nil, errors.Trace(err)
+	}
+
+	if tactics != nil {
+		// This server isn't configured with tactics.
+		return false, "", nil, nil
+	}
+
+	seed, err := protocol.DeriveBPFServerProgramPRNGSeed(support.Config.ObfuscatedSSHKey)
+	if err != nil {
+		return false, "", nil, errors.Trace(err)
+	}
+
+	PRNG := prng.NewPRNGWithSeed(seed)
+
+	if !PRNG.FlipWeightedCoin(tactics.Probability) {
+		// Skip tactics with the configured probability.
+		return false, "", nil, nil
+	}
+
+	clientParameters, err := parameters.NewClientParameters(nil)
+	if err != nil {
+		return false, "", nil, nil
+	}
+
+	_, err = clientParameters.Set("", false, tactics.Parameters)
+	if err != nil {
+		return false, "", nil, nil
+	}
+
+	p := clientParameters.Get()
+
+	if !PRNG.FlipWeightedCoin(
+		p.Float(parameters.BPFServerTCPProbability)) {
+		return false, "", nil, nil
+	}
+
+	ok, name, rawInstructions := p.BPFProgram(parameters.BPFServerTCPProgram)
+	return ok, name, rawInstructions, nil
+}

+ 9 - 7
psiphon/net_windows.go → psiphon/server/bpf_unsupported.go

@@ -1,5 +1,7 @@
+// +build !linux
+
 /*
- * Copyright (c) 2018, Psiphon Inc.
+ * Copyright (c) 2020, Psiphon Inc.
  * All rights reserved.
  *
  * This program is free software: you can redistribute it and/or modify
@@ -17,17 +19,17 @@
  *
  */
 
-package psiphon
+package server
 
 import (
-	"syscall"
+	"net"
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 )
 
-func setAdditionalSocketOptions(socketFd syscall.Handle) {
-}
+func newTCPListenerWithBPF(
+	_ *SupportServices, localAddress string) (net.Listener, string, error) {
 
-func bindToDeviceCallWrapper(deviceBinder DeviceBinder, socketFD syscall.Handle) error {
-	return errors.TraceNew("DeviceBinder with syscall.Handle not supported")
+	listener, err := net.Listen("tcp", localAddress)
+	return listener, "", errors.Trace(err)
 }

+ 2 - 2
psiphon/server/meek.go

@@ -585,7 +585,7 @@ func (server *MeekServer) getSessionOrEndpoint(
 		return "", nil, "", "", errors.Trace(err)
 	}
 
-	// Handle endpoints before enforcing the GetEstablishTunnels check.
+	// Handle endpoints before enforcing CheckEstablishTunnels.
 	// Currently, endpoints are tactics requests, and we allow these to be
 	// handled by servers which would otherwise reject new tunnels.
 
@@ -597,7 +597,7 @@ func (server *MeekServer) getSessionOrEndpoint(
 	// will not succeed, so creating a meek session just wastes resources.
 
 	if server.support.TunnelServer != nil &&
-		!server.support.TunnelServer.GetEstablishTunnels() {
+		!server.support.TunnelServer.CheckEstablishTunnels() {
 		return "", nil, "", "", errors.TraceNew("not establishing tunnels")
 	}
 

+ 3 - 1
psiphon/server/services.go

@@ -335,7 +335,9 @@ func logServerLoad(server *TunnelServer) {
 
 	serverLoad["event_name"] = "server_load"
 
-	serverLoad["establish_tunnels"] = server.GetEstablishTunnels()
+	establishTunnels, establishLimitedCount := server.GetEstablishTunnelsMetrics()
+	serverLoad["establish_tunnels"] = establishTunnels
+	serverLoad["establish_tunnels_limited_count"] = establishLimitedCount
 
 	for protocol, stats := range protocolStats {
 		serverLoad[protocol] = stats

+ 72 - 55
psiphon/server/tunnelServer.go

@@ -87,6 +87,14 @@ type TunnelServer struct {
 	sshServer         *sshServer
 }
 
+type sshListener struct {
+	net.Listener
+	localAddress   string
+	tunnelProtocol string
+	port           int
+	BPFProgramName string
+}
+
 // NewTunnelServer initializes a new tunnel server.
 func NewTunnelServer(
 	support *SupportServices,
@@ -127,12 +135,6 @@ func NewTunnelServer(
 // comment in sshClient.stop(). TODO: fully synchronized shutdown.
 func (server *TunnelServer) Run() error {
 
-	type sshListener struct {
-		net.Listener
-		localAddress   string
-		tunnelProtocol string
-	}
-
 	// TODO: should TunnelServer hold its own support pointer?
 	support := server.sshServer.support
 
@@ -147,6 +149,7 @@ func (server *TunnelServer) Run() error {
 			"%s:%d", support.Config.ServerIPAddress, listenPort)
 
 		var listener net.Listener
+		var BPFProgramName string
 		var err error
 
 		if protocol.TunnelProtocolUsesFrontedMeekQUIC(tunnelProtocol) {
@@ -169,13 +172,13 @@ func (server *TunnelServer) Run() error {
 				support.Config.ServerIPAddress,
 				support.Config.MarionetteFormat)
 
-		} else if protocol.TunnelProtocolUsesTapdance(tunnelProtocol) {
-
-			listener, err = tapdance.Listen(localAddress)
-
 		} else {
 
-			listener, err = net.Listen("tcp", localAddress)
+			listener, BPFProgramName, err = newTCPListenerWithBPF(support, localAddress)
+
+			if protocol.TunnelProtocolUsesTapdance(tunnelProtocol) {
+				listener, err = tapdance.Listen(listener)
+			}
 		}
 
 		if err != nil {
@@ -197,6 +200,7 @@ func (server *TunnelServer) Run() error {
 			LogFields{
 				"localAddress":   localAddress,
 				"tunnelProtocol": tunnelProtocol,
+				"BPFProgramName": BPFProgramName,
 			}).Info("listening")
 
 		listeners = append(
@@ -204,7 +208,9 @@ func (server *TunnelServer) Run() error {
 			&sshListener{
 				Listener:       tacticsListener,
 				localAddress:   localAddress,
+				port:           listenPort,
 				tunnelProtocol: tunnelProtocol,
+				BPFProgramName: BPFProgramName,
 			})
 	}
 
@@ -220,9 +226,8 @@ func (server *TunnelServer) Run() error {
 				}).Info("running")
 
 			server.sshServer.runListener(
-				listener.Listener,
-				server.listenerError,
-				listener.tunnelProtocol)
+				listener,
+				server.listenerError)
 
 			log.WithTraceFields(
 				LogFields{
@@ -328,9 +333,17 @@ func (server *TunnelServer) SetEstablishTunnels(establish bool) {
 	server.sshServer.setEstablishTunnels(establish)
 }
 
-// GetEstablishTunnels returns whether new tunnels may be established or not.
-func (server *TunnelServer) GetEstablishTunnels() bool {
-	return server.sshServer.getEstablishTunnels()
+// CheckEstablishTunnels returns whether new tunnels may be established or
+// not, and increments a metrics counter when establishment is disallowed.
+func (server *TunnelServer) CheckEstablishTunnels() bool {
+	return server.sshServer.checkEstablishTunnels()
+}
+
+// GetEstablishTunnelsMetrics returns whether tunnel establishment is
+// currently allowed and the number of tunnels rejected since due to not
+// establishing since the last GetEstablishTunnelsMetrics call.
+func (server *TunnelServer) GetEstablishTunnelsMetrics() (bool, int64) {
+	return server.sshServer.getEstablishTunnelsMetrics()
 }
 
 type sshServer struct {
@@ -339,6 +352,7 @@ type sshServer struct {
 	// (https://golang.org/pkg/sync/atomic/#pkg-note-BUG)
 	lastAuthLog                  int64
 	authFailedCount              int64
+	establishLimitedCount        int64
 	support                      *SupportServices
 	establishTunnels             int32
 	concurrentSSHHandshakes      semaphore.Semaphore
@@ -407,7 +421,7 @@ func (sshServer *sshServer) setEstablishTunnels(establish bool) {
 	// Do nothing when the setting is already correct. This avoids
 	// spurious log messages when setEstablishTunnels is called
 	// periodically with the same setting.
-	if establish == sshServer.getEstablishTunnels() {
+	if establish == (atomic.LoadInt32(&sshServer.establishTunnels) == 1) {
 		return
 	}
 
@@ -421,19 +435,23 @@ func (sshServer *sshServer) setEstablishTunnels(establish bool) {
 		LogFields{"establish": establish}).Info("establishing tunnels")
 }
 
-func (sshServer *sshServer) getEstablishTunnels() bool {
-	return atomic.LoadInt32(&sshServer.establishTunnels) == 1
+func (sshServer *sshServer) checkEstablishTunnels() bool {
+	establishTunnels := atomic.LoadInt32(&sshServer.establishTunnels) == 1
+	if !establishTunnels {
+		atomic.AddInt64(&sshServer.establishLimitedCount, 1)
+	}
+	return establishTunnels
+}
+
+func (sshServer *sshServer) getEstablishTunnelsMetrics() (bool, int64) {
+	return atomic.LoadInt32(&sshServer.establishTunnels) == 1,
+		atomic.SwapInt64(&sshServer.establishLimitedCount, 0)
 }
 
 // runListener is intended to run an a goroutine; it blocks
 // running a particular listener. If an unrecoverable error
 // occurs, it will send the error to the listenerError channel.
-func (sshServer *sshServer) runListener(
-	listener net.Listener,
-	listenerError chan<- error,
-	listenerTunnelProtocol string) {
-
-	listenerPort := common.PortFromAddr(listener.Addr())
+func (sshServer *sshServer) runListener(sshListener *sshListener, listenerError chan<- error) {
 
 	runningProtocols := make([]string, 0)
 	for tunnelProtocol := range sshServer.support.Config.TunnelProtocolPorts {
@@ -446,7 +464,7 @@ func (sshServer *sshServer) runListener(
 		// listeners in all cases (e.g., meek) since SSH tunnels can
 		// span multiple TCP connections.
 
-		if !sshServer.getEstablishTunnels() {
+		if !sshServer.checkEstablishTunnels() {
 			log.WithTrace().Debug("not establishing tunnels")
 			clientConn.Close()
 			return
@@ -458,7 +476,7 @@ func (sshServer *sshServer) runListener(
 		// don't use any client-declared value. Only use the client's
 		// value, if present, in special cases where the listening port
 		// cannot distinguish the protocol.
-		tunnelProtocol := listenerTunnelProtocol
+		tunnelProtocol := sshListener.tunnelProtocol
 		if clientTunnelProtocol != "" {
 
 			if !common.Contains(runningProtocols, clientTunnelProtocol) {
@@ -476,11 +494,11 @@ func (sshServer *sshServer) runListener(
 			}
 		}
 
-		// listenerTunnelProtocol indictes the tunnel protocol run by the listener.
-		// For direct protocols, this is also the client tunnel protocol. For
-		// fronted protocols, the client may use a different protocol to connect to
-		// the front and then only the front-to-Psiphon server will use the listener
-		// protocol.
+		// sshListener.tunnelProtocol indictes the tunnel protocol run by the
+		// listener. For direct protocols, this is also the client tunnel protocol.
+		// For fronted protocols, the client may use a different protocol to connect
+		// to the front and then only the front-to-Psiphon server will use the
+		// listener protocol.
 		//
 		// A fronted meek client, for example, reports its first hop protocol in
 		// protocol.MeekCookieData.ClientTunnelProtocol. Most metrics record this
@@ -494,8 +512,7 @@ func (sshServer *sshServer) runListener(
 		// client may dial a different port for its first hop.
 
 		// Process each client connection concurrently.
-		go sshServer.handleClient(
-			listenerTunnelProtocol, listenerPort, tunnelProtocol, clientConn)
+		go sshServer.handleClient(sshListener, tunnelProtocol, clientConn)
 	}
 
 	// Note: when exiting due to a unrecoverable error, be sure
@@ -503,17 +520,17 @@ func (sshServer *sshServer) runListener(
 	// TunnelServer.Run will properly shut down instead of remaining
 	// running.
 
-	if protocol.TunnelProtocolUsesMeekHTTP(listenerTunnelProtocol) ||
-		protocol.TunnelProtocolUsesMeekHTTPS(listenerTunnelProtocol) {
+	if protocol.TunnelProtocolUsesMeekHTTP(sshListener.tunnelProtocol) ||
+		protocol.TunnelProtocolUsesMeekHTTPS(sshListener.tunnelProtocol) {
 
 		meekServer, err := NewMeekServer(
 			sshServer.support,
-			listener,
-			listenerTunnelProtocol,
-			listenerPort,
-			protocol.TunnelProtocolUsesMeekHTTPS(listenerTunnelProtocol),
-			protocol.TunnelProtocolUsesFrontedMeek(listenerTunnelProtocol),
-			protocol.TunnelProtocolUsesObfuscatedSessionTickets(listenerTunnelProtocol),
+			sshListener.Listener,
+			sshListener.tunnelProtocol,
+			sshListener.port,
+			protocol.TunnelProtocolUsesMeekHTTPS(sshListener.tunnelProtocol),
+			protocol.TunnelProtocolUsesFrontedMeek(sshListener.tunnelProtocol),
+			protocol.TunnelProtocolUsesObfuscatedSessionTickets(sshListener.tunnelProtocol),
 			handleClient,
 			sshServer.shutdownBroadcast)
 
@@ -532,7 +549,7 @@ func (sshServer *sshServer) runListener(
 	} else {
 
 		for {
-			conn, err := listener.Accept()
+			conn, err := sshListener.Listener.Accept()
 
 			select {
 			case <-sshServer.shutdownBroadcast:
@@ -955,8 +972,7 @@ func (sshServer *sshServer) stopClients() {
 }
 
 func (sshServer *sshServer) handleClient(
-	listenerTunnelProtocol string, listenerPort int,
-	tunnelProtocol string, clientConn net.Conn) {
+	sshListener *sshListener, tunnelProtocol string, clientConn net.Conn) {
 
 	// Calling clientConn.RemoteAddr at this point, before any Read calls,
 	// satisfies the constraint documented in tapdance.Listen.
@@ -1013,7 +1029,10 @@ func (sshServer *sshServer) handleClient(
 	}
 
 	sshClient := newSshClient(
-		sshServer, listenerTunnelProtocol, listenerPort, tunnelProtocol, geoIPData)
+		sshServer,
+		sshListener,
+		tunnelProtocol,
+		geoIPData)
 
 	// sshClient.run _must_ call onSSHHandshakeFinished to release the semaphore:
 	// in any error case; or, as soon as the SSH handshake phase has successfully
@@ -1052,8 +1071,7 @@ func (sshServer *sshServer) monitorPortForwardDialError(err error) {
 type sshClient struct {
 	sync.Mutex
 	sshServer                            *sshServer
-	listenerTunnelProtocol               string
-	listenerPort                         int
+	sshListener                          *sshListener
 	tunnelProtocol                       string
 	sshConn                              ssh.Conn
 	activityConn                         *common.ActivityMonitoredConn
@@ -1125,8 +1143,7 @@ type handshakeState struct {
 
 func newSshClient(
 	sshServer *sshServer,
-	listenerTunnelProtocol string,
-	listenerPort int,
+	sshListener *sshListener,
 	tunnelProtocol string,
 	geoIPData GeoIPData) *sshClient {
 
@@ -1138,8 +1155,7 @@ func newSshClient(
 
 	client := &sshClient{
 		sshServer:              sshServer,
-		listenerTunnelProtocol: listenerTunnelProtocol,
-		listenerPort:           listenerPort,
+		sshListener:            sshListener,
 		tunnelProtocol:         tunnelProtocol,
 		geoIPData:              geoIPData,
 		isFirstTunnelInSession: true,
@@ -1270,8 +1286,8 @@ func (sshClient *sshClient) run(
 				func(clientIP string, logFields common.LogFields) {
 					logIrregularTunnel(
 						sshClient.sshServer.support,
-						sshClient.listenerTunnelProtocol,
-						sshClient.listenerPort,
+						sshClient.sshListener.tunnelProtocol,
+						sshClient.sshListener.port,
 						clientIP,
 						LogFields(logFields))
 				})
@@ -2128,6 +2144,7 @@ func (sshClient *sshClient) logTunnel(additionalMetrics []LogFields) {
 	// unconditionally, overwriting any value from handshake.
 	logFields["relay_protocol"] = sshClient.tunnelProtocol
 
+	logFields["server_bpf"] = sshClient.sshListener.BPFProgramName
 	logFields["session_id"] = sshClient.sessionID
 	logFields["handshake_completed"] = sshClient.handshakeState.completed
 	logFields["start_time"] = sshClient.activityConn.GetStartTime()

+ 4 - 0
psiphon/serverApi.go

@@ -795,6 +795,10 @@ func getBaseAPIParameters(
 		params["device_region"] = config.DeviceRegion
 	}
 
+	if dialParams.BPFProgramName != "" {
+		params["client_bpf"] = dialParams.BPFProgramName
+	}
+
 	if dialParams.SelectedSSHClientVersion {
 		params["ssh_client_version"] = dialParams.SSHClientVersion
 	}

+ 41 - 0
vendor/golang.org/x/net/bpf/asm.go

@@ -0,0 +1,41 @@
+// Copyright 2016 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 bpf
+
+import "fmt"
+
+// Assemble converts insts into raw instructions suitable for loading
+// into a BPF virtual machine.
+//
+// Currently, no optimization is attempted, the assembled program flow
+// is exactly as provided.
+func Assemble(insts []Instruction) ([]RawInstruction, error) {
+	ret := make([]RawInstruction, len(insts))
+	var err error
+	for i, inst := range insts {
+		ret[i], err = inst.Assemble()
+		if err != nil {
+			return nil, fmt.Errorf("assembling instruction %d: %s", i+1, err)
+		}
+	}
+	return ret, nil
+}
+
+// Disassemble attempts to parse raw back into
+// Instructions. Unrecognized RawInstructions are assumed to be an
+// extension not implemented by this package, and are passed through
+// unchanged to the output. The allDecoded value reports whether insts
+// contains no RawInstructions.
+func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool) {
+	insts = make([]Instruction, len(raw))
+	allDecoded = true
+	for i, r := range raw {
+		insts[i] = r.Disassemble()
+		if _, ok := insts[i].(RawInstruction); ok {
+			allDecoded = false
+		}
+	}
+	return insts, allDecoded
+}

+ 222 - 0
vendor/golang.org/x/net/bpf/constants.go

@@ -0,0 +1,222 @@
+// Copyright 2016 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 bpf
+
+// A Register is a register of the BPF virtual machine.
+type Register uint16
+
+const (
+	// RegA is the accumulator register. RegA is always the
+	// destination register of ALU operations.
+	RegA Register = iota
+	// RegX is the indirection register, used by LoadIndirect
+	// operations.
+	RegX
+)
+
+// An ALUOp is an arithmetic or logic operation.
+type ALUOp uint16
+
+// ALU binary operation types.
+const (
+	ALUOpAdd ALUOp = iota << 4
+	ALUOpSub
+	ALUOpMul
+	ALUOpDiv
+	ALUOpOr
+	ALUOpAnd
+	ALUOpShiftLeft
+	ALUOpShiftRight
+	aluOpNeg // Not exported because it's the only unary ALU operation, and gets its own instruction type.
+	ALUOpMod
+	ALUOpXor
+)
+
+// A JumpTest is a comparison operator used in conditional jumps.
+type JumpTest uint16
+
+// Supported operators for conditional jumps.
+// K can be RegX for JumpIfX
+const (
+	// K == A
+	JumpEqual JumpTest = iota
+	// K != A
+	JumpNotEqual
+	// K > A
+	JumpGreaterThan
+	// K < A
+	JumpLessThan
+	// K >= A
+	JumpGreaterOrEqual
+	// K <= A
+	JumpLessOrEqual
+	// K & A != 0
+	JumpBitsSet
+	// K & A == 0
+	JumpBitsNotSet
+)
+
+// An Extension is a function call provided by the kernel that
+// performs advanced operations that are expensive or impossible
+// within the BPF virtual machine.
+//
+// Extensions are only implemented by the Linux kernel.
+//
+// TODO: should we prune this list? Some of these extensions seem
+// either broken or near-impossible to use correctly, whereas other
+// (len, random, ifindex) are quite useful.
+type Extension int
+
+// Extension functions available in the Linux kernel.
+const (
+	// extOffset is the negative maximum number of instructions used
+	// to load instructions by overloading the K argument.
+	extOffset = -0x1000
+	// ExtLen returns the length of the packet.
+	ExtLen Extension = 1
+	// ExtProto returns the packet's L3 protocol type.
+	ExtProto Extension = 0
+	// ExtType returns the packet's type (skb->pkt_type in the kernel)
+	//
+	// TODO: better documentation. How nice an API do we want to
+	// provide for these esoteric extensions?
+	ExtType Extension = 4
+	// ExtPayloadOffset returns the offset of the packet payload, or
+	// the first protocol header that the kernel does not know how to
+	// parse.
+	ExtPayloadOffset Extension = 52
+	// ExtInterfaceIndex returns the index of the interface on which
+	// the packet was received.
+	ExtInterfaceIndex Extension = 8
+	// ExtNetlinkAttr returns the netlink attribute of type X at
+	// offset A.
+	ExtNetlinkAttr Extension = 12
+	// ExtNetlinkAttrNested returns the nested netlink attribute of
+	// type X at offset A.
+	ExtNetlinkAttrNested Extension = 16
+	// ExtMark returns the packet's mark value.
+	ExtMark Extension = 20
+	// ExtQueue returns the packet's assigned hardware queue.
+	ExtQueue Extension = 24
+	// ExtLinkLayerType returns the packet's hardware address type
+	// (e.g. Ethernet, Infiniband).
+	ExtLinkLayerType Extension = 28
+	// ExtRXHash returns the packets receive hash.
+	//
+	// TODO: figure out what this rxhash actually is.
+	ExtRXHash Extension = 32
+	// ExtCPUID returns the ID of the CPU processing the current
+	// packet.
+	ExtCPUID Extension = 36
+	// ExtVLANTag returns the packet's VLAN tag.
+	ExtVLANTag Extension = 44
+	// ExtVLANTagPresent returns non-zero if the packet has a VLAN
+	// tag.
+	//
+	// TODO: I think this might be a lie: it reads bit 0x1000 of the
+	// VLAN header, which changed meaning in recent revisions of the
+	// spec - this extension may now return meaningless information.
+	ExtVLANTagPresent Extension = 48
+	// ExtVLANProto returns 0x8100 if the frame has a VLAN header,
+	// 0x88a8 if the frame has a "Q-in-Q" double VLAN header, or some
+	// other value if no VLAN information is present.
+	ExtVLANProto Extension = 60
+	// ExtRand returns a uniformly random uint32.
+	ExtRand Extension = 56
+)
+
+// The following gives names to various bit patterns used in opcode construction.
+
+const (
+	opMaskCls uint16 = 0x7
+	// opClsLoad masks
+	opMaskLoadDest  = 0x01
+	opMaskLoadWidth = 0x18
+	opMaskLoadMode  = 0xe0
+	// opClsALU & opClsJump
+	opMaskOperand  = 0x08
+	opMaskOperator = 0xf0
+)
+
+const (
+	// +---------------+-----------------+---+---+---+
+	// | AddrMode (3b) | LoadWidth (2b)  | 0 | 0 | 0 |
+	// +---------------+-----------------+---+---+---+
+	opClsLoadA uint16 = iota
+	// +---------------+-----------------+---+---+---+
+	// | AddrMode (3b) | LoadWidth (2b)  | 0 | 0 | 1 |
+	// +---------------+-----------------+---+---+---+
+	opClsLoadX
+	// +---+---+---+---+---+---+---+---+
+	// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
+	// +---+---+---+---+---+---+---+---+
+	opClsStoreA
+	// +---+---+---+---+---+---+---+---+
+	// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
+	// +---+---+---+---+---+---+---+---+
+	opClsStoreX
+	// +---------------+-----------------+---+---+---+
+	// | Operator (4b) | OperandSrc (1b) | 1 | 0 | 0 |
+	// +---------------+-----------------+---+---+---+
+	opClsALU
+	// +-----------------------------+---+---+---+---+
+	// |      TestOperator (4b)      | 0 | 1 | 0 | 1 |
+	// +-----------------------------+---+---+---+---+
+	opClsJump
+	// +---+-------------------------+---+---+---+---+
+	// | 0 | 0 | 0 |   RetSrc (1b)   | 0 | 1 | 1 | 0 |
+	// +---+-------------------------+---+---+---+---+
+	opClsReturn
+	// +---+-------------------------+---+---+---+---+
+	// | 0 | 0 | 0 |  TXAorTAX (1b)  | 0 | 1 | 1 | 1 |
+	// +---+-------------------------+---+---+---+---+
+	opClsMisc
+)
+
+const (
+	opAddrModeImmediate uint16 = iota << 5
+	opAddrModeAbsolute
+	opAddrModeIndirect
+	opAddrModeScratch
+	opAddrModePacketLen // actually an extension, not an addressing mode.
+	opAddrModeMemShift
+)
+
+const (
+	opLoadWidth4 uint16 = iota << 3
+	opLoadWidth2
+	opLoadWidth1
+)
+
+// Operand for ALU and Jump instructions
+type opOperand uint16
+
+// Supported operand sources.
+const (
+	opOperandConstant opOperand = iota << 3
+	opOperandX
+)
+
+// An jumpOp is a conditional jump condition.
+type jumpOp uint16
+
+// Supported jump conditions.
+const (
+	opJumpAlways jumpOp = iota << 4
+	opJumpEqual
+	opJumpGT
+	opJumpGE
+	opJumpSet
+)
+
+const (
+	opRetSrcConstant uint16 = iota << 4
+	opRetSrcA
+)
+
+const (
+	opMiscTAX = 0x00
+	opMiscTXA = 0x80
+)

+ 82 - 0
vendor/golang.org/x/net/bpf/doc.go

@@ -0,0 +1,82 @@
+// Copyright 2016 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 bpf implements marshaling and unmarshaling of programs for the
+Berkeley Packet Filter virtual machine, and provides a Go implementation
+of the virtual machine.
+
+BPF's main use is to specify a packet filter for network taps, so that
+the kernel doesn't have to expensively copy every packet it sees to
+userspace. However, it's been repurposed to other areas where running
+user code in-kernel is needed. For example, Linux's seccomp uses BPF
+to apply security policies to system calls. For simplicity, this
+documentation refers only to packets, but other uses of BPF have their
+own data payloads.
+
+BPF programs run in a restricted virtual machine. It has almost no
+access to kernel functions, and while conditional branches are
+allowed, they can only jump forwards, to guarantee that there are no
+infinite loops.
+
+The virtual machine
+
+The BPF VM is an accumulator machine. Its main register, called
+register A, is an implicit source and destination in all arithmetic
+and logic operations. The machine also has 16 scratch registers for
+temporary storage, and an indirection register (register X) for
+indirect memory access. All registers are 32 bits wide.
+
+Each run of a BPF program is given one packet, which is placed in the
+VM's read-only "main memory". LoadAbsolute and LoadIndirect
+instructions can fetch up to 32 bits at a time into register A for
+examination.
+
+The goal of a BPF program is to produce and return a verdict (uint32),
+which tells the kernel what to do with the packet. In the context of
+packet filtering, the returned value is the number of bytes of the
+packet to forward to userspace, or 0 to ignore the packet. Other
+contexts like seccomp define their own return values.
+
+In order to simplify programs, attempts to read past the end of the
+packet terminate the program execution with a verdict of 0 (ignore
+packet). This means that the vast majority of BPF programs don't need
+to do any explicit bounds checking.
+
+In addition to the bytes of the packet, some BPF programs have access
+to extensions, which are essentially calls to kernel utility
+functions. Currently, the only extensions supported by this package
+are the Linux packet filter extensions.
+
+Examples
+
+This packet filter selects all ARP packets.
+
+	bpf.Assemble([]bpf.Instruction{
+		// Load "EtherType" field from the ethernet header.
+		bpf.LoadAbsolute{Off: 12, Size: 2},
+		// Skip over the next instruction if EtherType is not ARP.
+		bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1},
+		// Verdict is "send up to 4k of the packet to userspace."
+		bpf.RetConstant{Val: 4096},
+		// Verdict is "ignore packet."
+		bpf.RetConstant{Val: 0},
+	})
+
+This packet filter captures a random 1% sample of traffic.
+
+	bpf.Assemble([]bpf.Instruction{
+		// Get a 32-bit random number from the Linux kernel.
+		bpf.LoadExtension{Num: bpf.ExtRand},
+		// 1% dice roll?
+		bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2^32/100, SkipFalse: 1},
+		// Capture.
+		bpf.RetConstant{Val: 4096},
+		// Ignore.
+		bpf.RetConstant{Val: 0},
+	})
+
+*/
+package bpf // import "golang.org/x/net/bpf"

+ 726 - 0
vendor/golang.org/x/net/bpf/instructions.go

@@ -0,0 +1,726 @@
+// Copyright 2016 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 bpf
+
+import "fmt"
+
+// An Instruction is one instruction executed by the BPF virtual
+// machine.
+type Instruction interface {
+	// Assemble assembles the Instruction into a RawInstruction.
+	Assemble() (RawInstruction, error)
+}
+
+// A RawInstruction is a raw BPF virtual machine instruction.
+type RawInstruction struct {
+	// Operation to execute.
+	Op uint16
+	// For conditional jump instructions, the number of instructions
+	// to skip if the condition is true/false.
+	Jt uint8
+	Jf uint8
+	// Constant parameter. The meaning depends on the Op.
+	K uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil }
+
+// Disassemble parses ri into an Instruction and returns it. If ri is
+// not recognized by this package, ri itself is returned.
+func (ri RawInstruction) Disassemble() Instruction {
+	switch ri.Op & opMaskCls {
+	case opClsLoadA, opClsLoadX:
+		reg := Register(ri.Op & opMaskLoadDest)
+		sz := 0
+		switch ri.Op & opMaskLoadWidth {
+		case opLoadWidth4:
+			sz = 4
+		case opLoadWidth2:
+			sz = 2
+		case opLoadWidth1:
+			sz = 1
+		default:
+			return ri
+		}
+		switch ri.Op & opMaskLoadMode {
+		case opAddrModeImmediate:
+			if sz != 4 {
+				return ri
+			}
+			return LoadConstant{Dst: reg, Val: ri.K}
+		case opAddrModeScratch:
+			if sz != 4 || ri.K > 15 {
+				return ri
+			}
+			return LoadScratch{Dst: reg, N: int(ri.K)}
+		case opAddrModeAbsolute:
+			if ri.K > extOffset+0xffffffff {
+				return LoadExtension{Num: Extension(-extOffset + ri.K)}
+			}
+			return LoadAbsolute{Size: sz, Off: ri.K}
+		case opAddrModeIndirect:
+			return LoadIndirect{Size: sz, Off: ri.K}
+		case opAddrModePacketLen:
+			if sz != 4 {
+				return ri
+			}
+			return LoadExtension{Num: ExtLen}
+		case opAddrModeMemShift:
+			return LoadMemShift{Off: ri.K}
+		default:
+			return ri
+		}
+
+	case opClsStoreA:
+		if ri.Op != opClsStoreA || ri.K > 15 {
+			return ri
+		}
+		return StoreScratch{Src: RegA, N: int(ri.K)}
+
+	case opClsStoreX:
+		if ri.Op != opClsStoreX || ri.K > 15 {
+			return ri
+		}
+		return StoreScratch{Src: RegX, N: int(ri.K)}
+
+	case opClsALU:
+		switch op := ALUOp(ri.Op & opMaskOperator); op {
+		case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
+			switch operand := opOperand(ri.Op & opMaskOperand); operand {
+			case opOperandX:
+				return ALUOpX{Op: op}
+			case opOperandConstant:
+				return ALUOpConstant{Op: op, Val: ri.K}
+			default:
+				return ri
+			}
+		case aluOpNeg:
+			return NegateA{}
+		default:
+			return ri
+		}
+
+	case opClsJump:
+		switch op := jumpOp(ri.Op & opMaskOperator); op {
+		case opJumpAlways:
+			return Jump{Skip: ri.K}
+		case opJumpEqual, opJumpGT, opJumpGE, opJumpSet:
+			cond, skipTrue, skipFalse := jumpOpToTest(op, ri.Jt, ri.Jf)
+			switch operand := opOperand(ri.Op & opMaskOperand); operand {
+			case opOperandX:
+				return JumpIfX{Cond: cond, SkipTrue: skipTrue, SkipFalse: skipFalse}
+			case opOperandConstant:
+				return JumpIf{Cond: cond, Val: ri.K, SkipTrue: skipTrue, SkipFalse: skipFalse}
+			default:
+				return ri
+			}
+		default:
+			return ri
+		}
+
+	case opClsReturn:
+		switch ri.Op {
+		case opClsReturn | opRetSrcA:
+			return RetA{}
+		case opClsReturn | opRetSrcConstant:
+			return RetConstant{Val: ri.K}
+		default:
+			return ri
+		}
+
+	case opClsMisc:
+		switch ri.Op {
+		case opClsMisc | opMiscTAX:
+			return TAX{}
+		case opClsMisc | opMiscTXA:
+			return TXA{}
+		default:
+			return ri
+		}
+
+	default:
+		panic("unreachable") // switch is exhaustive on the bit pattern
+	}
+}
+
+func jumpOpToTest(op jumpOp, skipTrue uint8, skipFalse uint8) (JumpTest, uint8, uint8) {
+	var test JumpTest
+
+	// Decode "fake" jump conditions that don't appear in machine code
+	// Ensures the Assemble -> Disassemble stage recreates the same instructions
+	// See https://github.com/golang/go/issues/18470
+	if skipTrue == 0 {
+		switch op {
+		case opJumpEqual:
+			test = JumpNotEqual
+		case opJumpGT:
+			test = JumpLessOrEqual
+		case opJumpGE:
+			test = JumpLessThan
+		case opJumpSet:
+			test = JumpBitsNotSet
+		}
+
+		return test, skipFalse, 0
+	}
+
+	switch op {
+	case opJumpEqual:
+		test = JumpEqual
+	case opJumpGT:
+		test = JumpGreaterThan
+	case opJumpGE:
+		test = JumpGreaterOrEqual
+	case opJumpSet:
+		test = JumpBitsSet
+	}
+
+	return test, skipTrue, skipFalse
+}
+
+// LoadConstant loads Val into register Dst.
+type LoadConstant struct {
+	Dst Register
+	Val uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadConstant) Assemble() (RawInstruction, error) {
+	return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val)
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadConstant) String() string {
+	switch a.Dst {
+	case RegA:
+		return fmt.Sprintf("ld #%d", a.Val)
+	case RegX:
+		return fmt.Sprintf("ldx #%d", a.Val)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+// LoadScratch loads scratch[N] into register Dst.
+type LoadScratch struct {
+	Dst Register
+	N   int // 0-15
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadScratch) Assemble() (RawInstruction, error) {
+	if a.N < 0 || a.N > 15 {
+		return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
+	}
+	return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N))
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadScratch) String() string {
+	switch a.Dst {
+	case RegA:
+		return fmt.Sprintf("ld M[%d]", a.N)
+	case RegX:
+		return fmt.Sprintf("ldx M[%d]", a.N)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+// LoadAbsolute loads packet[Off:Off+Size] as an integer value into
+// register A.
+type LoadAbsolute struct {
+	Off  uint32
+	Size int // 1, 2 or 4
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadAbsolute) Assemble() (RawInstruction, error) {
+	return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off)
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadAbsolute) String() string {
+	switch a.Size {
+	case 1: // byte
+		return fmt.Sprintf("ldb [%d]", a.Off)
+	case 2: // half word
+		return fmt.Sprintf("ldh [%d]", a.Off)
+	case 4: // word
+		if a.Off > extOffset+0xffffffff {
+			return LoadExtension{Num: Extension(a.Off + 0x1000)}.String()
+		}
+		return fmt.Sprintf("ld [%d]", a.Off)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value
+// into register A.
+type LoadIndirect struct {
+	Off  uint32
+	Size int // 1, 2 or 4
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadIndirect) Assemble() (RawInstruction, error) {
+	return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off)
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadIndirect) String() string {
+	switch a.Size {
+	case 1: // byte
+		return fmt.Sprintf("ldb [x + %d]", a.Off)
+	case 2: // half word
+		return fmt.Sprintf("ldh [x + %d]", a.Off)
+	case 4: // word
+		return fmt.Sprintf("ld [x + %d]", a.Off)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+// LoadMemShift multiplies the first 4 bits of the byte at packet[Off]
+// by 4 and stores the result in register X.
+//
+// This instruction is mainly useful to load into X the length of an
+// IPv4 packet header in a single instruction, rather than have to do
+// the arithmetic on the header's first byte by hand.
+type LoadMemShift struct {
+	Off uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadMemShift) Assemble() (RawInstruction, error) {
+	return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off)
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadMemShift) String() string {
+	return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off)
+}
+
+// LoadExtension invokes a linux-specific extension and stores the
+// result in register A.
+type LoadExtension struct {
+	Num Extension
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadExtension) Assemble() (RawInstruction, error) {
+	if a.Num == ExtLen {
+		return assembleLoad(RegA, 4, opAddrModePacketLen, 0)
+	}
+	return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num))
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadExtension) String() string {
+	switch a.Num {
+	case ExtLen:
+		return "ld #len"
+	case ExtProto:
+		return "ld #proto"
+	case ExtType:
+		return "ld #type"
+	case ExtPayloadOffset:
+		return "ld #poff"
+	case ExtInterfaceIndex:
+		return "ld #ifidx"
+	case ExtNetlinkAttr:
+		return "ld #nla"
+	case ExtNetlinkAttrNested:
+		return "ld #nlan"
+	case ExtMark:
+		return "ld #mark"
+	case ExtQueue:
+		return "ld #queue"
+	case ExtLinkLayerType:
+		return "ld #hatype"
+	case ExtRXHash:
+		return "ld #rxhash"
+	case ExtCPUID:
+		return "ld #cpu"
+	case ExtVLANTag:
+		return "ld #vlan_tci"
+	case ExtVLANTagPresent:
+		return "ld #vlan_avail"
+	case ExtVLANProto:
+		return "ld #vlan_tpid"
+	case ExtRand:
+		return "ld #rand"
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+// StoreScratch stores register Src into scratch[N].
+type StoreScratch struct {
+	Src Register
+	N   int // 0-15
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a StoreScratch) Assemble() (RawInstruction, error) {
+	if a.N < 0 || a.N > 15 {
+		return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
+	}
+	var op uint16
+	switch a.Src {
+	case RegA:
+		op = opClsStoreA
+	case RegX:
+		op = opClsStoreX
+	default:
+		return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src)
+	}
+
+	return RawInstruction{
+		Op: op,
+		K:  uint32(a.N),
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a StoreScratch) String() string {
+	switch a.Src {
+	case RegA:
+		return fmt.Sprintf("st M[%d]", a.N)
+	case RegX:
+		return fmt.Sprintf("stx M[%d]", a.N)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+// ALUOpConstant executes A = A <Op> Val.
+type ALUOpConstant struct {
+	Op  ALUOp
+	Val uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a ALUOpConstant) Assemble() (RawInstruction, error) {
+	return RawInstruction{
+		Op: opClsALU | uint16(opOperandConstant) | uint16(a.Op),
+		K:  a.Val,
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a ALUOpConstant) String() string {
+	switch a.Op {
+	case ALUOpAdd:
+		return fmt.Sprintf("add #%d", a.Val)
+	case ALUOpSub:
+		return fmt.Sprintf("sub #%d", a.Val)
+	case ALUOpMul:
+		return fmt.Sprintf("mul #%d", a.Val)
+	case ALUOpDiv:
+		return fmt.Sprintf("div #%d", a.Val)
+	case ALUOpMod:
+		return fmt.Sprintf("mod #%d", a.Val)
+	case ALUOpAnd:
+		return fmt.Sprintf("and #%d", a.Val)
+	case ALUOpOr:
+		return fmt.Sprintf("or #%d", a.Val)
+	case ALUOpXor:
+		return fmt.Sprintf("xor #%d", a.Val)
+	case ALUOpShiftLeft:
+		return fmt.Sprintf("lsh #%d", a.Val)
+	case ALUOpShiftRight:
+		return fmt.Sprintf("rsh #%d", a.Val)
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+// ALUOpX executes A = A <Op> X
+type ALUOpX struct {
+	Op ALUOp
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a ALUOpX) Assemble() (RawInstruction, error) {
+	return RawInstruction{
+		Op: opClsALU | uint16(opOperandX) | uint16(a.Op),
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a ALUOpX) String() string {
+	switch a.Op {
+	case ALUOpAdd:
+		return "add x"
+	case ALUOpSub:
+		return "sub x"
+	case ALUOpMul:
+		return "mul x"
+	case ALUOpDiv:
+		return "div x"
+	case ALUOpMod:
+		return "mod x"
+	case ALUOpAnd:
+		return "and x"
+	case ALUOpOr:
+		return "or x"
+	case ALUOpXor:
+		return "xor x"
+	case ALUOpShiftLeft:
+		return "lsh x"
+	case ALUOpShiftRight:
+		return "rsh x"
+	default:
+		return fmt.Sprintf("unknown instruction: %#v", a)
+	}
+}
+
+// NegateA executes A = -A.
+type NegateA struct{}
+
+// Assemble implements the Instruction Assemble method.
+func (a NegateA) Assemble() (RawInstruction, error) {
+	return RawInstruction{
+		Op: opClsALU | uint16(aluOpNeg),
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a NegateA) String() string {
+	return fmt.Sprintf("neg")
+}
+
+// Jump skips the following Skip instructions in the program.
+type Jump struct {
+	Skip uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a Jump) Assemble() (RawInstruction, error) {
+	return RawInstruction{
+		Op: opClsJump | uint16(opJumpAlways),
+		K:  a.Skip,
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a Jump) String() string {
+	return fmt.Sprintf("ja %d", a.Skip)
+}
+
+// JumpIf skips the following Skip instructions in the program if A
+// <Cond> Val is true.
+type JumpIf struct {
+	Cond      JumpTest
+	Val       uint32
+	SkipTrue  uint8
+	SkipFalse uint8
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a JumpIf) Assemble() (RawInstruction, error) {
+	return jumpToRaw(a.Cond, opOperandConstant, a.Val, a.SkipTrue, a.SkipFalse)
+}
+
+// String returns the instruction in assembler notation.
+func (a JumpIf) String() string {
+	return jumpToString(a.Cond, fmt.Sprintf("#%d", a.Val), a.SkipTrue, a.SkipFalse)
+}
+
+// JumpIfX skips the following Skip instructions in the program if A
+// <Cond> X is true.
+type JumpIfX struct {
+	Cond      JumpTest
+	SkipTrue  uint8
+	SkipFalse uint8
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a JumpIfX) Assemble() (RawInstruction, error) {
+	return jumpToRaw(a.Cond, opOperandX, 0, a.SkipTrue, a.SkipFalse)
+}
+
+// String returns the instruction in assembler notation.
+func (a JumpIfX) String() string {
+	return jumpToString(a.Cond, "x", a.SkipTrue, a.SkipFalse)
+}
+
+// jumpToRaw assembles a jump instruction into a RawInstruction
+func jumpToRaw(test JumpTest, operand opOperand, k uint32, skipTrue, skipFalse uint8) (RawInstruction, error) {
+	var (
+		cond jumpOp
+		flip bool
+	)
+	switch test {
+	case JumpEqual:
+		cond = opJumpEqual
+	case JumpNotEqual:
+		cond, flip = opJumpEqual, true
+	case JumpGreaterThan:
+		cond = opJumpGT
+	case JumpLessThan:
+		cond, flip = opJumpGE, true
+	case JumpGreaterOrEqual:
+		cond = opJumpGE
+	case JumpLessOrEqual:
+		cond, flip = opJumpGT, true
+	case JumpBitsSet:
+		cond = opJumpSet
+	case JumpBitsNotSet:
+		cond, flip = opJumpSet, true
+	default:
+		return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", test)
+	}
+	jt, jf := skipTrue, skipFalse
+	if flip {
+		jt, jf = jf, jt
+	}
+	return RawInstruction{
+		Op: opClsJump | uint16(cond) | uint16(operand),
+		Jt: jt,
+		Jf: jf,
+		K:  k,
+	}, nil
+}
+
+// jumpToString converts a jump instruction to assembler notation
+func jumpToString(cond JumpTest, operand string, skipTrue, skipFalse uint8) string {
+	switch cond {
+	// K == A
+	case JumpEqual:
+		return conditionalJump(operand, skipTrue, skipFalse, "jeq", "jneq")
+	// K != A
+	case JumpNotEqual:
+		return fmt.Sprintf("jneq %s,%d", operand, skipTrue)
+	// K > A
+	case JumpGreaterThan:
+		return conditionalJump(operand, skipTrue, skipFalse, "jgt", "jle")
+	// K < A
+	case JumpLessThan:
+		return fmt.Sprintf("jlt %s,%d", operand, skipTrue)
+	// K >= A
+	case JumpGreaterOrEqual:
+		return conditionalJump(operand, skipTrue, skipFalse, "jge", "jlt")
+	// K <= A
+	case JumpLessOrEqual:
+		return fmt.Sprintf("jle %s,%d", operand, skipTrue)
+	// K & A != 0
+	case JumpBitsSet:
+		if skipFalse > 0 {
+			return fmt.Sprintf("jset %s,%d,%d", operand, skipTrue, skipFalse)
+		}
+		return fmt.Sprintf("jset %s,%d", operand, skipTrue)
+	// K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips
+	case JumpBitsNotSet:
+		return jumpToString(JumpBitsSet, operand, skipFalse, skipTrue)
+	default:
+		return fmt.Sprintf("unknown JumpTest %#v", cond)
+	}
+}
+
+func conditionalJump(operand string, skipTrue, skipFalse uint8, positiveJump, negativeJump string) string {
+	if skipTrue > 0 {
+		if skipFalse > 0 {
+			return fmt.Sprintf("%s %s,%d,%d", positiveJump, operand, skipTrue, skipFalse)
+		}
+		return fmt.Sprintf("%s %s,%d", positiveJump, operand, skipTrue)
+	}
+	return fmt.Sprintf("%s %s,%d", negativeJump, operand, skipFalse)
+}
+
+// RetA exits the BPF program, returning the value of register A.
+type RetA struct{}
+
+// Assemble implements the Instruction Assemble method.
+func (a RetA) Assemble() (RawInstruction, error) {
+	return RawInstruction{
+		Op: opClsReturn | opRetSrcA,
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a RetA) String() string {
+	return fmt.Sprintf("ret a")
+}
+
+// RetConstant exits the BPF program, returning a constant value.
+type RetConstant struct {
+	Val uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a RetConstant) Assemble() (RawInstruction, error) {
+	return RawInstruction{
+		Op: opClsReturn | opRetSrcConstant,
+		K:  a.Val,
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a RetConstant) String() string {
+	return fmt.Sprintf("ret #%d", a.Val)
+}
+
+// TXA copies the value of register X to register A.
+type TXA struct{}
+
+// Assemble implements the Instruction Assemble method.
+func (a TXA) Assemble() (RawInstruction, error) {
+	return RawInstruction{
+		Op: opClsMisc | opMiscTXA,
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a TXA) String() string {
+	return fmt.Sprintf("txa")
+}
+
+// TAX copies the value of register A to register X.
+type TAX struct{}
+
+// Assemble implements the Instruction Assemble method.
+func (a TAX) Assemble() (RawInstruction, error) {
+	return RawInstruction{
+		Op: opClsMisc | opMiscTAX,
+	}, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a TAX) String() string {
+	return fmt.Sprintf("tax")
+}
+
+func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) {
+	var (
+		cls uint16
+		sz  uint16
+	)
+	switch dst {
+	case RegA:
+		cls = opClsLoadA
+	case RegX:
+		cls = opClsLoadX
+	default:
+		return RawInstruction{}, fmt.Errorf("invalid target register %v", dst)
+	}
+	switch loadSize {
+	case 1:
+		sz = opLoadWidth1
+	case 2:
+		sz = opLoadWidth2
+	case 4:
+		sz = opLoadWidth4
+	default:
+		return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz)
+	}
+	return RawInstruction{
+		Op: cls | sz | mode,
+		K:  k,
+	}, nil
+}

+ 10 - 0
vendor/golang.org/x/net/bpf/setter.go

@@ -0,0 +1,10 @@
+// Copyright 2017 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 bpf
+
+// A Setter is a type which can attach a compiled BPF filter to itself.
+type Setter interface {
+	SetBPF(filter []RawInstruction) error
+}

+ 150 - 0
vendor/golang.org/x/net/bpf/vm.go

@@ -0,0 +1,150 @@
+// Copyright 2016 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 bpf
+
+import (
+	"errors"
+	"fmt"
+)
+
+// A VM is an emulated BPF virtual machine.
+type VM struct {
+	filter []Instruction
+}
+
+// NewVM returns a new VM using the input BPF program.
+func NewVM(filter []Instruction) (*VM, error) {
+	if len(filter) == 0 {
+		return nil, errors.New("one or more Instructions must be specified")
+	}
+
+	for i, ins := range filter {
+		check := len(filter) - (i + 1)
+		switch ins := ins.(type) {
+		// Check for out-of-bounds jumps in instructions
+		case Jump:
+			if check <= int(ins.Skip) {
+				return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
+			}
+		case JumpIf:
+			if check <= int(ins.SkipTrue) {
+				return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
+			}
+			if check <= int(ins.SkipFalse) {
+				return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
+			}
+		case JumpIfX:
+			if check <= int(ins.SkipTrue) {
+				return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
+			}
+			if check <= int(ins.SkipFalse) {
+				return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
+			}
+		// Check for division or modulus by zero
+		case ALUOpConstant:
+			if ins.Val != 0 {
+				break
+			}
+
+			switch ins.Op {
+			case ALUOpDiv, ALUOpMod:
+				return nil, errors.New("cannot divide by zero using ALUOpConstant")
+			}
+		// Check for unknown extensions
+		case LoadExtension:
+			switch ins.Num {
+			case ExtLen:
+			default:
+				return nil, fmt.Errorf("extension %d not implemented", ins.Num)
+			}
+		}
+	}
+
+	// Make sure last instruction is a return instruction
+	switch filter[len(filter)-1].(type) {
+	case RetA, RetConstant:
+	default:
+		return nil, errors.New("BPF program must end with RetA or RetConstant")
+	}
+
+	// Though our VM works using disassembled instructions, we
+	// attempt to assemble the input filter anyway to ensure it is compatible
+	// with an operating system VM.
+	_, err := Assemble(filter)
+
+	return &VM{
+		filter: filter,
+	}, err
+}
+
+// Run runs the VM's BPF program against the input bytes.
+// Run returns the number of bytes accepted by the BPF program, and any errors
+// which occurred while processing the program.
+func (v *VM) Run(in []byte) (int, error) {
+	var (
+		// Registers of the virtual machine
+		regA       uint32
+		regX       uint32
+		regScratch [16]uint32
+
+		// OK is true if the program should continue processing the next
+		// instruction, or false if not, causing the loop to break
+		ok = true
+	)
+
+	// TODO(mdlayher): implement:
+	// - NegateA:
+	//   - would require a change from uint32 registers to int32
+	//     registers
+
+	// TODO(mdlayher): add interop tests that check signedness of ALU
+	// operations against kernel implementation, and make sure Go
+	// implementation matches behavior
+
+	for i := 0; i < len(v.filter) && ok; i++ {
+		ins := v.filter[i]
+
+		switch ins := ins.(type) {
+		case ALUOpConstant:
+			regA = aluOpConstant(ins, regA)
+		case ALUOpX:
+			regA, ok = aluOpX(ins, regA, regX)
+		case Jump:
+			i += int(ins.Skip)
+		case JumpIf:
+			jump := jumpIf(ins, regA)
+			i += jump
+		case JumpIfX:
+			jump := jumpIfX(ins, regA, regX)
+			i += jump
+		case LoadAbsolute:
+			regA, ok = loadAbsolute(ins, in)
+		case LoadConstant:
+			regA, regX = loadConstant(ins, regA, regX)
+		case LoadExtension:
+			regA = loadExtension(ins, in)
+		case LoadIndirect:
+			regA, ok = loadIndirect(ins, in, regX)
+		case LoadMemShift:
+			regX, ok = loadMemShift(ins, in)
+		case LoadScratch:
+			regA, regX = loadScratch(ins, regScratch, regA, regX)
+		case RetA:
+			return int(regA), nil
+		case RetConstant:
+			return int(ins.Val), nil
+		case StoreScratch:
+			regScratch = storeScratch(ins, regScratch, regA, regX)
+		case TAX:
+			regX = regA
+		case TXA:
+			regA = regX
+		default:
+			return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
+		}
+	}
+
+	return 0, nil
+}

+ 182 - 0
vendor/golang.org/x/net/bpf/vm_instructions.go

@@ -0,0 +1,182 @@
+// Copyright 2016 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 bpf
+
+import (
+	"encoding/binary"
+	"fmt"
+)
+
+func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 {
+	return aluOpCommon(ins.Op, regA, ins.Val)
+}
+
+func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) {
+	// Guard against division or modulus by zero by terminating
+	// the program, as the OS BPF VM does
+	if regX == 0 {
+		switch ins.Op {
+		case ALUOpDiv, ALUOpMod:
+			return 0, false
+		}
+	}
+
+	return aluOpCommon(ins.Op, regA, regX), true
+}
+
+func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 {
+	switch op {
+	case ALUOpAdd:
+		return regA + value
+	case ALUOpSub:
+		return regA - value
+	case ALUOpMul:
+		return regA * value
+	case ALUOpDiv:
+		// Division by zero not permitted by NewVM and aluOpX checks
+		return regA / value
+	case ALUOpOr:
+		return regA | value
+	case ALUOpAnd:
+		return regA & value
+	case ALUOpShiftLeft:
+		return regA << value
+	case ALUOpShiftRight:
+		return regA >> value
+	case ALUOpMod:
+		// Modulus by zero not permitted by NewVM and aluOpX checks
+		return regA % value
+	case ALUOpXor:
+		return regA ^ value
+	default:
+		return regA
+	}
+}
+
+func jumpIf(ins JumpIf, regA uint32) int {
+	return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, ins.Val)
+}
+
+func jumpIfX(ins JumpIfX, regA uint32, regX uint32) int {
+	return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, regX)
+}
+
+func jumpIfCommon(cond JumpTest, skipTrue, skipFalse uint8, regA uint32, value uint32) int {
+	var ok bool
+
+	switch cond {
+	case JumpEqual:
+		ok = regA == value
+	case JumpNotEqual:
+		ok = regA != value
+	case JumpGreaterThan:
+		ok = regA > value
+	case JumpLessThan:
+		ok = regA < value
+	case JumpGreaterOrEqual:
+		ok = regA >= value
+	case JumpLessOrEqual:
+		ok = regA <= value
+	case JumpBitsSet:
+		ok = (regA & value) != 0
+	case JumpBitsNotSet:
+		ok = (regA & value) == 0
+	}
+
+	if ok {
+		return int(skipTrue)
+	}
+
+	return int(skipFalse)
+}
+
+func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
+	offset := int(ins.Off)
+	size := int(ins.Size)
+
+	return loadCommon(in, offset, size)
+}
+
+func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) {
+	switch ins.Dst {
+	case RegA:
+		regA = ins.Val
+	case RegX:
+		regX = ins.Val
+	}
+
+	return regA, regX
+}
+
+func loadExtension(ins LoadExtension, in []byte) uint32 {
+	switch ins.Num {
+	case ExtLen:
+		return uint32(len(in))
+	default:
+		panic(fmt.Sprintf("unimplemented extension: %d", ins.Num))
+	}
+}
+
+func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) {
+	offset := int(ins.Off) + int(regX)
+	size := int(ins.Size)
+
+	return loadCommon(in, offset, size)
+}
+
+func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) {
+	offset := int(ins.Off)
+
+	// Size of LoadMemShift is always 1 byte
+	if !inBounds(len(in), offset, 1) {
+		return 0, false
+	}
+
+	// Mask off high 4 bits and multiply low 4 bits by 4
+	return uint32(in[offset]&0x0f) * 4, true
+}
+
+func inBounds(inLen int, offset int, size int) bool {
+	return offset+size <= inLen
+}
+
+func loadCommon(in []byte, offset int, size int) (uint32, bool) {
+	if !inBounds(len(in), offset, size) {
+		return 0, false
+	}
+
+	switch size {
+	case 1:
+		return uint32(in[offset]), true
+	case 2:
+		return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
+	case 4:
+		return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
+	default:
+		panic(fmt.Sprintf("invalid load size: %d", size))
+	}
+}
+
+func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) {
+	switch ins.Dst {
+	case RegA:
+		regA = regScratch[ins.N]
+	case RegX:
+		regX = regScratch[ins.N]
+	}
+
+	return regA, regX
+}
+
+func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 {
+	switch ins.Src {
+	case RegA:
+		regScratch[ins.N] = regA
+	case RegX:
+		regScratch[ins.N] = regX
+	}
+
+	return regScratch
+}

+ 6 - 0
vendor/vendor.json

@@ -661,6 +661,12 @@
 			"revision": "a1f597ede03a7bef967a422b5b3a5bd08805a01e",
 			"revisionTime": "2019-02-05T21:23:42Z"
 		},
+		{
+			"checksumSHA1": "4rZ8D9GLDvy+6S940nMs75DvyfA=",
+			"path": "golang.org/x/net/bpf",
+			"revision": "6afb5195e5aab057fda82e27171243402346b0ad",
+			"revisionTime": "2020-01-14T15:53:21Z"
+		},
 		{
 			"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
 			"path": "golang.org/x/net/context",