// +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 . * */ package server import ( "context" "net" "syscall" "unsafe" "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" ) // ServerBPFEnabled indicates if BPF functionality is enabled. func ServerBPFEnabled() bool { return true } // 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) { p, err := support.ServerTacticsParametersCache.Get(NewGeoIPData()) if err != nil { return false, "", nil, errors.Trace(err) } if p.IsNil() { // No tactics are configured; BPF is disabled. 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( p.Float(parameters.BPFServerTCPProbability)) { return false, "", nil, nil } ok, name, rawInstructions := p.BPFProgram(parameters.BPFServerTCPProgram) return ok, name, rawInstructions, nil }