github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/server/packetman.go (about) 1 /* 2 * Copyright (c) 2020, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package server 21 22 import ( 23 "net" 24 25 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 26 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/packetman" 27 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters" 28 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng" 29 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol" 30 ) 31 32 func makePacketManipulatorConfig( 33 support *SupportServices) (*packetman.Config, error) { 34 35 // Packet interception is configured for any tunnel protocol port that _may_ 36 // use packet manipulation. A future hot reload of tactics may apply specs to 37 // any of these protocols. 38 39 var ports []int 40 for tunnelProtocol, port := range support.Config.TunnelProtocolPorts { 41 if protocol.TunnelProtocolMayUseServerPacketManipulation(tunnelProtocol) { 42 ports = append(ports, port) 43 } 44 } 45 46 selectSpecName := func(protocolPort int, clientIP net.IP) (string, interface{}) { 47 48 specName, extraData, err := selectPacketManipulationSpec( 49 support, protocolPort, clientIP) 50 if err != nil { 51 log.WithTraceFields( 52 LogFields{"error": err}).Warning( 53 "failed to get tactics for packet manipulation") 54 return "", nil 55 } 56 57 return specName, extraData 58 } 59 60 specs, err := getPacketManipulationSpecs(support) 61 if err != nil { 62 return nil, errors.Trace(err) 63 } 64 65 return &packetman.Config{ 66 Logger: CommonLogger(log), 67 SudoNetworkConfigCommands: support.Config.PacketTunnelSudoNetworkConfigCommands, 68 QueueNumber: 1, 69 ProtocolPorts: ports, 70 Specs: specs, 71 SelectSpecName: selectSpecName, 72 }, nil 73 } 74 75 func getPacketManipulationSpecs(support *SupportServices) ([]*packetman.Spec, error) { 76 77 // By convention, parameters.ServerPacketManipulationSpecs should be in 78 // DefaultTactics, not FilteredTactics; and ServerTacticsParametersCache 79 // ignores Tactics.Probability. 80 81 p, err := support.ServerTacticsParametersCache.Get(NewGeoIPData()) 82 if err != nil { 83 return nil, errors.Trace(err) 84 } 85 86 if p.IsNil() { 87 // No tactics are configured; return an empty spec list. 88 return nil, nil 89 } 90 91 paramSpecs := p.PacketManipulationSpecs(parameters.ServerPacketManipulationSpecs) 92 93 specs := make([]*packetman.Spec, len(paramSpecs)) 94 for i, spec := range paramSpecs { 95 packetmanSpec := packetman.Spec(*spec) 96 specs[i] = &packetmanSpec 97 } 98 99 return specs, nil 100 } 101 102 func reloadPacketManipulationSpecs(support *SupportServices) error { 103 104 specs, err := getPacketManipulationSpecs(support) 105 if err != nil { 106 return errors.Trace(err) 107 } 108 109 err = support.PacketManipulator.SetSpecs(specs) 110 if err != nil { 111 return errors.Trace(err) 112 } 113 114 return nil 115 } 116 117 func selectPacketManipulationSpec( 118 support *SupportServices, 119 protocolPort int, 120 clientIP net.IP) (string, interface{}, error) { 121 122 // First check for replay, then check tactics. 123 124 // The intercepted packet source/protocol port is used to determine the 125 // tunnel protocol name, which is used to lookup first replay and then 126 // enabled packet manipulation specs in ServerProtocolPacketManipulations. 127 // 128 // This assumes that all TunnelProtocolMayUseServerPacketManipulation 129 // protocols run on distinct ports, which is true when all such protocols run 130 // over TCP. 131 132 targetTunnelProtocol := "" 133 for tunnelProtocol, port := range support.Config.TunnelProtocolPorts { 134 if port == protocolPort && protocol.TunnelProtocolMayUseServerPacketManipulation(tunnelProtocol) { 135 targetTunnelProtocol = tunnelProtocol 136 break 137 } 138 } 139 if targetTunnelProtocol == "" { 140 return "", nil, errors.Tracef( 141 "packet manipulation protocol port not found: %d", protocolPort) 142 } 143 144 geoIPData := support.GeoIPService.LookupIP(clientIP) 145 146 specName, doReplay := support.ReplayCache.GetReplayPacketManipulation( 147 targetTunnelProtocol, geoIPData) 148 149 // extraData records the is_server_replay metric. 150 extraData := doReplay 151 152 if doReplay { 153 return specName, extraData, nil 154 } 155 156 // GeoIP tactics filtering is applied when getting 157 // ServerPacketManipulationProbability and ServerProtocolPacketManipulations. 158 // 159 // When there are multiple enabled specs, one is selected at random. 160 // 161 // Specs under the key "All" apply to all protocols. Duplicate specs per 162 // entry are allowed, enabling weighted selection. If a spec appears in both 163 // "All" and a specific protocol, the duplicate(s) are retained. 164 165 p, err := support.ServerTacticsParametersCache.Get(geoIPData) 166 if err != nil { 167 return "", nil, errors.Trace(err) 168 } 169 170 if p.IsNil() { 171 // No tactics are configured; select no spec. 172 return "", extraData, nil 173 } 174 175 if !p.WeightedCoinFlip(parameters.ServerPacketManipulationProbability) { 176 return "", extraData, nil 177 } 178 179 protocolSpecs := p.ProtocolPacketManipulations( 180 parameters.ServerProtocolPacketManipulations) 181 182 // TODO: cache merged per-protocol + "All" lists? 183 specNames, ok := protocolSpecs[targetTunnelProtocol] 184 if !ok { 185 specNames = []string{} 186 } 187 188 allProtocolsSpecNames, ok := protocolSpecs[protocol.TUNNEL_PROTOCOLS_ALL] 189 if ok { 190 specNames = append(specNames, allProtocolsSpecNames...) 191 } 192 193 if len(specNames) < 1 { 194 // Tactics contains no candidate specs for this protocol. 195 return "", extraData, nil 196 } 197 198 return specNames[prng.Range(0, len(specNames)-1)], extraData, nil 199 }