github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/Server/main.go (about) 1 /* 2 * Copyright (c) 2016, 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 main 21 22 import ( 23 "encoding/json" 24 "flag" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "os" 29 "strconv" 30 "strings" 31 "syscall" 32 "time" 33 34 "github.com/Psiphon-Inc/rotate-safe-writer" 35 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" 36 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo" 37 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server" 38 "github.com/mitchellh/panicwrap" 39 ) 40 41 var loadedConfigJSON []byte 42 43 func main() { 44 45 var configFilename string 46 var generateServerIPaddress string 47 var generateServerNetworkInterface string 48 var generateProtocolPorts stringListFlag 49 var generateWebServerPort int 50 var generateLogFilename string 51 var generateTrafficRulesConfigFilename string 52 var generateOSLConfigFilename string 53 var generateTacticsConfigFilename string 54 var generateServerEntryFilename string 55 56 flag.StringVar( 57 &configFilename, 58 "config", 59 server.SERVER_CONFIG_FILENAME, 60 "run or generate with this config `filename`") 61 62 flag.StringVar( 63 &generateServerIPaddress, 64 "ipaddress", 65 server.DEFAULT_SERVER_IP_ADDRESS, 66 "generate with this server `IP address`") 67 68 flag.StringVar( 69 &generateServerNetworkInterface, 70 "interface", 71 "", 72 "generate with server IP address from this `network-interface`") 73 74 flag.Var( 75 &generateProtocolPorts, 76 "protocol", 77 "generate with `protocol:port`; flag may be repeated to enable multiple protocols") 78 79 flag.IntVar( 80 &generateWebServerPort, 81 "web", 82 0, 83 "generate with web server `port`; 0 for no web server") 84 85 flag.StringVar( 86 &generateLogFilename, 87 "logFilename", 88 "", 89 "set application log file name and path; blank for stderr") 90 91 flag.StringVar( 92 &generateTrafficRulesConfigFilename, 93 "trafficRules", 94 server.SERVER_TRAFFIC_RULES_CONFIG_FILENAME, 95 "generate with this traffic rules config `filename`") 96 97 flag.StringVar( 98 &generateOSLConfigFilename, 99 "osl", 100 server.SERVER_OSL_CONFIG_FILENAME, 101 "generate with this OSL config `filename`") 102 103 flag.StringVar( 104 &generateTacticsConfigFilename, 105 "tactics", 106 server.SERVER_TACTICS_CONFIG_FILENAME, 107 "generate with this tactics config `filename`") 108 109 flag.StringVar( 110 &generateServerEntryFilename, 111 "serverEntry", 112 server.SERVER_ENTRY_FILENAME, 113 "generate with this server entry `filename`") 114 115 flag.Usage = func() { 116 fmt.Fprintf(os.Stderr, 117 "Usage:\n\n"+ 118 "%s <flags> generate generates configuration files\n"+ 119 "%s <flags> run runs configured services\n\n", 120 os.Args[0], os.Args[0]) 121 flag.PrintDefaults() 122 } 123 124 flag.Parse() 125 126 args := flag.Args() 127 128 if len(args) < 1 { 129 flag.Usage() 130 os.Exit(1) 131 } else if args[0] == "generate" { 132 133 serverIPaddress := generateServerIPaddress 134 135 if generateServerNetworkInterface != "" { 136 // TODO: IPv6 support 137 serverIPv4Address, _, err := common.GetInterfaceIPAddresses(generateServerNetworkInterface) 138 if err == nil && serverIPv4Address == nil { 139 err = fmt.Errorf("no IPv4 address for interface %s", generateServerNetworkInterface) 140 } 141 if err != nil { 142 fmt.Printf("generate failed: %s\n", err) 143 os.Exit(1) 144 } 145 serverIPaddress = serverIPv4Address.String() 146 } 147 148 tunnelProtocolPorts := make(map[string]int) 149 150 for _, protocolPort := range generateProtocolPorts { 151 parts := strings.Split(protocolPort, ":") 152 if len(parts) == 2 { 153 port, err := strconv.Atoi(parts[1]) 154 if err != nil { 155 fmt.Printf("generate failed: %s\n", err) 156 os.Exit(1) 157 } 158 tunnelProtocolPorts[parts[0]] = port 159 } 160 } 161 162 configJSON, trafficRulesConfigJSON, OSLConfigJSON, 163 tacticsConfigJSON, encodedServerEntry, err := 164 server.GenerateConfig( 165 &server.GenerateConfigParams{ 166 LogFilename: generateLogFilename, 167 ServerIPAddress: serverIPaddress, 168 EnableSSHAPIRequests: true, 169 WebServerPort: generateWebServerPort, 170 TunnelProtocolPorts: tunnelProtocolPorts, 171 TrafficRulesConfigFilename: generateTrafficRulesConfigFilename, 172 OSLConfigFilename: generateOSLConfigFilename, 173 TacticsConfigFilename: generateTacticsConfigFilename, 174 }) 175 if err != nil { 176 fmt.Printf("generate failed: %s\n", err) 177 os.Exit(1) 178 } 179 180 err = ioutil.WriteFile(configFilename, configJSON, 0600) 181 if err != nil { 182 fmt.Printf("error writing configuration file: %s\n", err) 183 os.Exit(1) 184 } 185 186 err = ioutil.WriteFile(generateTrafficRulesConfigFilename, trafficRulesConfigJSON, 0600) 187 if err != nil { 188 fmt.Printf("error writing traffic rule config file: %s\n", err) 189 os.Exit(1) 190 } 191 192 err = ioutil.WriteFile(generateOSLConfigFilename, OSLConfigJSON, 0600) 193 if err != nil { 194 fmt.Printf("error writing OSL config file: %s\n", err) 195 os.Exit(1) 196 } 197 198 err = ioutil.WriteFile(generateTacticsConfigFilename, tacticsConfigJSON, 0600) 199 if err != nil { 200 fmt.Printf("error writing tactics config file: %s\n", err) 201 os.Exit(1) 202 } 203 204 err = ioutil.WriteFile(generateServerEntryFilename, encodedServerEntry, 0600) 205 if err != nil { 206 fmt.Printf("error writing server entry file: %s\n", err) 207 os.Exit(1) 208 } 209 210 } else if args[0] == "run" { 211 212 configJSON, err := ioutil.ReadFile(configFilename) 213 if err != nil { 214 fmt.Printf("error loading configuration file: %s\n", err) 215 os.Exit(1) 216 } 217 218 loadedConfigJSON = configJSON 219 220 // The initial call to panicwrap.Wrap will spawn a child process 221 // running the same program. 222 // 223 // The parent process waits for the child to terminate and 224 // panicHandler logs any panics from the child. 225 // 226 // The child will return immediately from Wrap without spawning 227 // and fall through to server.RunServices. 228 229 // Unhandled panic wrapper. Logs it, then re-executes the current executable 230 exitStatus, err := panicwrap.Wrap(&panicwrap.WrapConfig{ 231 Handler: panicHandler, 232 ForwardSignals: []os.Signal{os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTSTP, syscall.SIGCONT}, 233 }) 234 if err != nil { 235 fmt.Printf("failed to set up the panic wrapper: %s\n", err) 236 os.Exit(1) 237 } 238 239 // Note: panicwrap.Wrap documentation states that exitStatus == -1 240 // should be used to determine whether the process is the child. 241 // However, we have found that this exitStatus is returned even when 242 // the process is the parent. Likely due to panicwrap returning 243 // syscall.WaitStatus.ExitStatus() as the exitStatus, which _can_ be 244 // -1. Checking panicwrap.Wrapped(nil) is more reliable. 245 246 if !panicwrap.Wrapped(nil) { 247 os.Exit(exitStatus) 248 } 249 // Else, this is the child process. 250 251 err = server.RunServices(configJSON) 252 if err != nil { 253 fmt.Printf("run failed: %s\n", err) 254 os.Exit(1) 255 } 256 } 257 } 258 259 type stringListFlag []string 260 261 func (list *stringListFlag) String() string { 262 return strings.Join(*list, ", ") 263 } 264 265 func (list *stringListFlag) Set(flagValue string) error { 266 *list = append(*list, flagValue) 267 return nil 268 } 269 270 func panicHandler(output string) { 271 if len(loadedConfigJSON) > 0 { 272 config, err := server.LoadConfig([]byte(loadedConfigJSON)) 273 if err != nil { 274 fmt.Printf("error parsing configuration file: %s\n%s\n", err, output) 275 os.Exit(1) 276 } 277 278 logEvent := make(map[string]string) 279 logEvent["host_id"] = config.HostID 280 logEvent["build_rev"] = buildinfo.GetBuildInfo().BuildRev 281 logEvent["timestamp"] = time.Now().Format(time.RFC3339) 282 logEvent["event_name"] = "server_panic" 283 284 // ELK has a maximum field length of 32766 UTF8 bytes. Over that length, the 285 // log won't be delivered. Truncate the panic field, as it may be much longer. 286 maxLen := 32766 287 if len(output) > maxLen { 288 output = output[:maxLen] 289 } 290 291 logEvent["panic"] = output 292 293 // Logs are written to the configured file name. If no name is specified, logs are written to stderr 294 var jsonWriter io.Writer 295 if config.LogFilename != "" { 296 297 retries, create, mode := config.GetLogFileReopenConfig() 298 panicLog, err := rotate.NewRotatableFileWriter( 299 config.LogFilename, retries, create, mode) 300 if err != nil { 301 fmt.Printf("unable to set panic log output: %s\n%s\n", err, output) 302 os.Exit(1) 303 } 304 defer panicLog.Close() 305 306 jsonWriter = panicLog 307 } else { 308 jsonWriter = os.Stderr 309 } 310 311 enc := json.NewEncoder(jsonWriter) 312 err = enc.Encode(logEvent) 313 if err != nil { 314 fmt.Printf("unable to serialize panic message to JSON: %s\n%s\n", err, output) 315 os.Exit(1) 316 } 317 } else { 318 fmt.Printf("no configuration JSON was loaded, cannot continue\n%s\n", output) 319 os.Exit(1) 320 } 321 322 os.Exit(1) 323 }