├── LICENSE.md ├── README.md ├── go.mod ├── go.sum ├── main.go ├── main_test.go └── proxy.conf /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Commercial Operators Open Source License 2 | ## COOL 3 | 4 | ### Version 1.000.000 5 | 6 | 7 | ### Preamble 8 | 9 | In the spirit of innovation, collaboration, and the enduring belief in the 10 | freedom to use, study, share, and improve software, we introduce the 11 | Commercial Operators Open Source License (COOL). This license is crafted with 12 | the hope of fostering a vibrant ecosystem where commercial entities and 13 | individual contributors alike can contribute to a flourishing open source 14 | community. 15 | 16 | 17 | ### Definitions 18 | 19 | "The License" refers to the terms and conditions for the use, copying, 20 | distribution, and modification set forth in this document. 21 | 22 | "The Software" refers to the covered work governed by this License. 23 | 24 | "You" refers to any individual or legal entity exercising permissions granted 25 | by this License. 26 | 27 | 28 | ### Grant of Rights 29 | 30 | With the goal of fostering an open and collaborative world, the authors of 31 | the Software hereby grant you a worldwide, royalty-free, non-exclusive, 32 | perpetual license, subject to the terms of this License, to: 33 | 34 | a) Use, copy, modify, merge, publish, distribute, sublicense, and/or sell 35 | copies of the Software. 36 | 37 | b) Permit persons to whom the Software is furnished to do likewise, subject 38 | to the following conditions. 39 | 40 | 41 | ### Conditions 42 | 43 | Your exercise of the granted rights is conditioned upon compliance with the 44 | following: 45 | 46 | a) Preservation of copyright notices, this License, and the disclaimer 47 | provided, within all copies of the Software or substantial portions thereof. 48 | 49 | b) Inclusion of a clear attribution to the original authors, preserving all 50 | notices of copyright, patent, or trademark rights, specifically including 51 | author information and a direct website link as specified by the authors. 52 | 53 | c) Ensuring that any modifications, including derivative works, are offered 54 | under the same terms of this License, without additional restrictions. 55 | 56 | d) Acknowledgment that the use of the Software's name or contributors' names 57 | for endorsement of derived products without prior written consent is not 58 | permitted. 59 | 60 | 61 | ### Disclaimer of Warranty 62 | 63 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 64 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 65 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 66 | AUTHORS, COPYRIGHT HOLDERS OR THEIR CONSTITUENTS BE LIABLE FOR ANY CLAIM, 67 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 68 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 69 | OR OTHER DEALINGS IN THE SOFTWARE. 70 | 71 | 72 | ### Termination 73 | 74 | If you violate any term of this License, your rights under this License will 75 | terminate automatically. Upon termination, all rights granted to you under 76 | this License cease, and you must cease all use, distribution, and development 77 | of the Software. 78 | 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # delorean 2 | 3 | **delorean**, a reverse IPv4 to IPv6 TLS SNI and HTTP proxy written in GoLang, takes you back to the future if you're stuck on the old IPv4 internet. 4 | 5 | ## Features 6 | 7 | - Multi-port 8 | - Signal handling for graceful shutdown 9 | - LRU cache for DNS lookups with a configurable TTL (Time-To-Live) 10 | - Supports both TLS and HTTP connections to extract the hostname 11 | - Backend connection based on IPv6 addresses with a specific prefix 12 | - Concurrency and multithreading to handle multiple clients efficiently 13 | - Lazy DNS cache reloading to minimize lookup delays 14 | - Extensive unit tests to ensure reliability and performance 15 | - Simple and readable codebase (like all our repos) 16 | 17 | ## Use case 18 | 19 | We use this in production on the [IPv6.rs](https://ipv6.rs) network, proxying thousands of websites. We open sourced this as part of our commitment to transparency. You should know how your packets are being handled. 20 | 21 | ## Test results 22 | 23 | These tests were performed in a Virtual Machine running on a 4 year old laptop. 24 | 25 | ``` 26 | $ go test -v main.go main_test.go 27 | === RUN TestGetNameAndBufferFromTLSConnection 28 | 2024/05/20 19:38:34 Packet Data: 16030300720100006e0303b6b26afb555e03d565a36af05ea5430293b959a754c3dd78575834c582fd53d1000004000100ff010000410000000e000c00000f7777772e6578616d706c652e636f6d000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101 29 | --- PASS: TestGetNameAndBufferFromTLSConnection (0.00s) 30 | === RUN TestGetNameAndBufferFromHTTPConnection 31 | --- PASS: TestGetNameAndBufferFromHTTPConnection (0.00s) 32 | === RUN TestStressServer 33 | main_test.go:207: Total failed connections: 0 out of 1000 attempts 34 | --- PASS: TestStressServer (1.04s) 35 | === RUN TestMemoryUsage 36 | 2024/05/20 19:38:35 Packet Data: 16030300720100006e0303b6b26afb555e03d565a36af05ea5430293b959a754c3dd78575834c582fd53d1000004000100ff010000410000000e000c00000f7777772e6578616d706c652e636f6d000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101 37 | 2024/05/20 19:38:35 Memory before: Alloc = 890480 TotalAlloc = 890480 Sys = 12801288 NumGC = 0 38 | 2024/05/20 19:38:35 Memory after: Alloc = 2600808 TotalAlloc = 21688720 Sys = 13194504 NumGC = 7 39 | --- PASS: TestMemoryUsage (0.01s) 40 | PASS 41 | ok command-line-arguments 1.050s 42 | ``` 43 | 44 | ``` 45 | $ free -h 46 | total used free shared buff/cache available 47 | Mem: 15Gi 1.2Gi 8.5Gi 335Mi 5.9Gi 13Gi 48 | Swap: 2.0Gi 0B 2.0Gi 49 | ``` 50 | 51 | ``` 52 | $ nproc 53 | 4 54 | ``` 55 | 56 | ## License 57 | 58 | Copyright (c) 2024 [IPv6rs Limited ](https://ipv6.rs) 59 | 60 | All Rights Reserved. 61 | 62 | COOL License. 63 | 64 | 65 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/ipv6rslimited/lrucache v1.0.0 7 | github.com/ipv6rslimited/peter v1.0.4 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ipv6rslimited/lrucache v1.0.0 h1:Ns9vpipQLPd3E9MHIjkNa4RFoo4/v/Jtr2s5SkWJVnE= 2 | github.com/ipv6rslimited/lrucache v1.0.0/go.mod h1:Xt4GFTJyEZ19s9uPKevgwTtuAW2vMfLyulX/AEDyKe4= 3 | github.com/ipv6rslimited/peter v1.0.4 h1:36MoZUIUFC0MMIeQwq8nK7BbK53tOf8+t0ALnm8hpUE= 4 | github.com/ipv6rslimited/peter v1.0.4/go.mod h1:Oap/pZXH8U9gJJY117QYXSuKjH5i8ENDWXL2W6VbzGo= 5 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | ** 3 | ** delorean 4 | ** A reverse IPv4 to IPv6 TLS SNI and HTTP proxy written in GoLang 5 | ** 6 | ** Distributed under the COOL License. 7 | ** 8 | ** Copyright (c) 2024 IPv6.rs 9 | ** All Rights Reserved 10 | ** 11 | */ 12 | 13 | package main 14 | 15 | import ( 16 | "bufio" 17 | "encoding/binary" 18 | "encoding/json" 19 | "fmt" 20 | "io" 21 | "log" 22 | "net" 23 | "os" 24 | "os/signal" 25 | "strings" 26 | "sync" 27 | "syscall" 28 | "time" 29 | "github.com/ipv6rslimited/lrucache" 30 | "github.com/ipv6rslimited/peter" 31 | ) 32 | 33 | type Config struct { 34 | Ports []int `json:"ports"` 35 | IP string `json:"ip"` 36 | TTL int `json:"ttl"` 37 | Prefix string `json:"prefix"` 38 | } 39 | 40 | type CacheEntry struct { 41 | Address string 42 | Timestamp time.Time 43 | } 44 | 45 | var ( 46 | config Config 47 | dnsCache = lrucache.NewLRUCache(4096) 48 | servers []net.Listener 49 | wg sync.WaitGroup 50 | logger *log.Logger 51 | shutdown chan struct{} 52 | maxBufferedDataSize = 16384 53 | maxConnectTime = 30 54 | ) 55 | 56 | type nullWriter struct{} 57 | 58 | 59 | func main() { 60 | enableLogging := false 61 | setLogger(enableLogging) 62 | 63 | logger.Println("Starting main function") 64 | loadConfig() 65 | handleSignals() 66 | 67 | startAllServers() 68 | 69 | wg.Wait() 70 | } 71 | 72 | func startAllServers() { 73 | logger.Println("Starting all servers") 74 | shutdown = make(chan struct{}) 75 | for _, port := range config.Ports { 76 | wg.Add(1) 77 | go func(port int) { 78 | defer wg.Done() 79 | startServer(port) 80 | }(port) 81 | } 82 | } 83 | 84 | func handleSignals() { 85 | logger.Println("Setting up signal handling") 86 | signalChan := make(chan os.Signal, 1) 87 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 88 | 89 | go func() { 90 | for sig := range signalChan { 91 | switch sig { 92 | case syscall.SIGINT, syscall.SIGTERM: 93 | logger.Println("Received SIGINT or SIGTERM signal, shutting down servers") 94 | stopServers() 95 | os.Exit(0) 96 | } 97 | } 98 | }() 99 | } 100 | 101 | func stopServers() { 102 | logger.Println("Stopping servers") 103 | close(shutdown) 104 | 105 | for _, server := range servers { 106 | logger.Printf("Closing server on %s", server.Addr()) 107 | server.Close() 108 | } 109 | servers = nil 110 | 111 | time.Sleep(2 * time.Second) 112 | 113 | wg.Wait() 114 | 115 | logger.Println("All servers stopped") 116 | } 117 | 118 | func startServer(port int) { 119 | listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.IP, port)) 120 | if err != nil { 121 | logger.Printf("Failed to start server on port %d: %v", port, err) 122 | return 123 | } 124 | logger.Printf("Server started on %s:%d", config.IP, port) 125 | 126 | servers = append(servers, listener) 127 | 128 | wg.Add(1) 129 | defer wg.Done() 130 | 131 | for { 132 | client, err := listener.Accept() 133 | if err != nil { 134 | select { 135 | case <-shutdown: 136 | logger.Printf("Shutting down server on port %d", port) 137 | return 138 | default: 139 | continue 140 | } 141 | } 142 | logger.Printf("Accepted connection from %s", client.RemoteAddr()) 143 | go handleConnection(client) 144 | } 145 | } 146 | 147 | func (nw nullWriter) Write(p []byte) (n int, err error) { 148 | return len(p), nil 149 | } 150 | 151 | func setLogger(enable bool) { 152 | if enable { 153 | logger = log.New(os.Stdout, "", log.LstdFlags) 154 | } else { 155 | logger = log.New(nullWriter{}, "", log.LstdFlags) 156 | } 157 | } 158 | 159 | func loadConfig() { 160 | logger.Println("Loading configuration") 161 | file, err := os.Open("proxy.conf") 162 | if err != nil { 163 | logger.Fatalf("Failed to open config file: %v", err) 164 | } 165 | defer file.Close() 166 | 167 | decoder := json.NewDecoder(file) 168 | if err := decoder.Decode(&config); err != nil { 169 | logger.Fatalf("Failed to decode config file: %v", err) 170 | } 171 | 172 | if len(config.Ports) == 0 || config.IP == "" || config.TTL == 0 || config.Prefix == "" { 173 | logger.Fatalf("Invalid configuration: %+v", config) 174 | } 175 | 176 | logger.Printf("Config loaded: %+v", config) 177 | } 178 | 179 | func handleConnection(client net.Conn) { 180 | defer client.Close() 181 | 182 | logger.Printf("Handling connection from %s", client.RemoteAddr()) 183 | client.SetDeadline(time.Now().Add(time.Duration(maxConnectTime) * time.Second)) 184 | 185 | reader := bufio.NewReader(client) 186 | initialBytes, err := reader.Peek(5) 187 | if err != nil { 188 | logger.Printf("Failed to peek initial bytes: %v", err) 189 | return 190 | } 191 | 192 | var name string 193 | var bufferedData []byte 194 | 195 | if isTLS(initialBytes) { 196 | logger.Println("Connection is TLS") 197 | name, bufferedData, err = getNameAndBufferFromTLSConnection(reader) 198 | } else { 199 | logger.Println("Connection is HTTP") 200 | name, bufferedData, err = getNameAndBufferFromHTTPConnection(reader) 201 | } 202 | 203 | if err != nil || name == "" { 204 | logger.Printf("Failed to get name from connection: %v", err) 205 | return 206 | } 207 | 208 | logger.Printf("Resolved name: %s", name) 209 | 210 | address, err := lookupWithCache(name) 211 | if err != nil { 212 | logger.Printf("Failed to lookup address: %v", err) 213 | return 214 | } 215 | 216 | logger.Printf("Resolved address: %s", address) 217 | 218 | backend, err := net.DialTimeout("tcp", net.JoinHostPort(address, fmt.Sprint(client.LocalAddr().(*net.TCPAddr).Port)), time.Duration(maxConnectTime)*time.Second) 219 | if err != nil { 220 | logger.Printf("Failed to connect to backend: %v", err) 221 | return 222 | } 223 | defer backend.Close() 224 | 225 | client.SetDeadline(time.Time{}) 226 | backend.SetDeadline(time.Time{}) 227 | 228 | logger.Printf("Writing buffer: %x", bufferedData) 229 | _, err = backend.Write(bufferedData) 230 | if err != nil { 231 | logger.Printf("Failed to write buffered data to backend: %v", err) 232 | return 233 | } 234 | 235 | logger.Println("Starting data piping between client and backend") 236 | piper := peter.NewPeter(client, backend) 237 | piper.Start() 238 | defer logger.Println("Ending data piping between client and backend") 239 | } 240 | 241 | func getNameAndBufferFromHTTPConnection(reader *bufio.Reader) (string, []byte, error) { 242 | logger.Println("Extracting name from HTTP connection") 243 | bufferedData := make([]byte, 0, maxBufferedDataSize) 244 | var host string 245 | 246 | for { 247 | line, err := reader.ReadString('\n') 248 | if err != nil { 249 | if err == io.EOF { 250 | break 251 | } 252 | return "", nil, fmt.Errorf("failed to read line: %w", err) 253 | } 254 | 255 | bufferedData = append(bufferedData, []byte(line)...) 256 | if len(bufferedData) > maxBufferedDataSize { 257 | return "", nil, fmt.Errorf("buffered data exceeds maximum size") 258 | } 259 | 260 | line = strings.TrimRight(line, "\r\n") 261 | 262 | if strings.HasPrefix(line, "Host: ") { 263 | host = strings.TrimSpace(line[6:]) 264 | logger.Printf("Extracted host from HTTP: %s", host) 265 | } 266 | 267 | if len(line) == 0 { 268 | break 269 | } 270 | } 271 | 272 | if host == "" { 273 | return "", nil, fmt.Errorf("host header not found") 274 | } 275 | 276 | remainingData := make([]byte, reader.Buffered()) 277 | n, err := reader.Read(remainingData) 278 | if err != nil && err != io.EOF { 279 | return "", nil, fmt.Errorf("failed to read remaining data: %w", err) 280 | } 281 | 282 | if len(bufferedData)+n > maxBufferedDataSize { 283 | return "", nil, fmt.Errorf("remaining data exceeds maximum buffer size") 284 | } 285 | bufferedData = append(bufferedData, remainingData[:n]...) 286 | 287 | return host, bufferedData, nil 288 | } 289 | 290 | func getNameAndBufferFromTLSConnection(reader *bufio.Reader) (string, []byte, error) { 291 | logger.Println("Extracting name from TLS connection") 292 | 293 | bufferedData := make([]byte, 0, maxBufferedDataSize) 294 | 295 | initialBytes := make([]byte, 43) 296 | _, err := io.ReadFull(reader, initialBytes) 297 | if err != nil { 298 | return "", nil, fmt.Errorf("failed to read initial bytes: %w", err) 299 | } 300 | bufferedData = append(bufferedData, initialBytes...) 301 | 302 | sessionIDLength, err := reader.ReadByte() 303 | if err != nil { 304 | return "", nil, fmt.Errorf("failed to read session ID length: %w", err) 305 | } 306 | bufferedData = append(bufferedData, sessionIDLength) 307 | 308 | sessionID := make([]byte, sessionIDLength) 309 | if len(bufferedData)+len(sessionID) > maxBufferedDataSize { 310 | return "", nil, fmt.Errorf("buffered data exceeds maximum size") 311 | } 312 | _, err = io.ReadFull(reader, sessionID) 313 | if err != nil { 314 | return "", nil, fmt.Errorf("failed to read session ID: %w", err) 315 | } 316 | bufferedData = append(bufferedData, sessionID...) 317 | 318 | cipherSuitesLengthBytes := make([]byte, 2) 319 | _, err = io.ReadFull(reader, cipherSuitesLengthBytes) 320 | if err != nil { 321 | return "", nil, fmt.Errorf("failed to read cipher suites length: %w", err) 322 | } 323 | bufferedData = append(bufferedData, cipherSuitesLengthBytes...) 324 | cipherSuitesLength := binary.BigEndian.Uint16(cipherSuitesLengthBytes) 325 | 326 | cipherSuites := make([]byte, cipherSuitesLength) 327 | if len(bufferedData)+len(cipherSuites) > maxBufferedDataSize { 328 | return "", nil, fmt.Errorf("buffered data exceeds maximum size") 329 | } 330 | _, err = io.ReadFull(reader, cipherSuites) 331 | if err != nil { 332 | return "", nil, fmt.Errorf("failed to read cipher suites: %w", err) 333 | } 334 | bufferedData = append(bufferedData, cipherSuites...) 335 | 336 | compressionMethodsLength, err := reader.ReadByte() 337 | if err != nil { 338 | return "", nil, fmt.Errorf("failed to read compression methods length: %w", err) 339 | } 340 | bufferedData = append(bufferedData, compressionMethodsLength) 341 | 342 | compressionMethods := make([]byte, compressionMethodsLength) 343 | if len(bufferedData)+len(compressionMethods) > maxBufferedDataSize { 344 | return "", nil, fmt.Errorf("buffered data exceeds maximum size") 345 | } 346 | _, err = io.ReadFull(reader, compressionMethods) 347 | if err != nil { 348 | return "", nil, fmt.Errorf("failed to read compression methods: %w", err) 349 | } 350 | bufferedData = append(bufferedData, compressionMethods...) 351 | 352 | extensionsLengthBytes := make([]byte, 2) 353 | _, err = io.ReadFull(reader, extensionsLengthBytes) 354 | if err != nil { 355 | return "", nil, fmt.Errorf("failed to read extensions length: %w", err) 356 | } 357 | bufferedData = append(bufferedData, extensionsLengthBytes...) 358 | extensionsLength := binary.BigEndian.Uint16(extensionsLengthBytes) 359 | extensionsEndIndex := int(extensionsLength) 360 | 361 | for extensionsEndIndex > 0 { 362 | extensionTypeBytes := make([]byte, 2) 363 | _, err = io.ReadFull(reader, extensionTypeBytes) 364 | if err != nil { 365 | return "", nil, fmt.Errorf("failed to read extension type: %w", err) 366 | } 367 | bufferedData = append(bufferedData, extensionTypeBytes...) 368 | extensionType := binary.BigEndian.Uint16(extensionTypeBytes) 369 | 370 | extensionLengthBytes := make([]byte, 2) 371 | _, err = io.ReadFull(reader, extensionLengthBytes) 372 | if err != nil { 373 | return "", nil, fmt.Errorf("failed to read extension length: %w", err) 374 | } 375 | bufferedData = append(bufferedData, extensionLengthBytes...) 376 | extensionLength := binary.BigEndian.Uint16(extensionLengthBytes) 377 | extensionsEndIndex -= 4 + int(extensionLength) 378 | 379 | if extensionType == 0x0000 { 380 | serverNameListLengthBytes := make([]byte, 2) 381 | _, err = io.ReadFull(reader, serverNameListLengthBytes) 382 | if err != nil { 383 | return "", nil, fmt.Errorf("failed to read server name list length: %w", err) 384 | } 385 | bufferedData = append(bufferedData, serverNameListLengthBytes...) 386 | 387 | serverNameType, err := reader.ReadByte() 388 | if err != nil { 389 | return "", nil, fmt.Errorf("failed to read server name type: %w", err) 390 | } 391 | bufferedData = append(bufferedData, serverNameType) 392 | if serverNameType != 0 { 393 | break 394 | } 395 | 396 | serverNameLengthBytes := make([]byte, 2) 397 | _, err = io.ReadFull(reader, serverNameLengthBytes) 398 | if err != nil { 399 | return "", nil, fmt.Errorf("failed to read server name length: %w", err) 400 | } 401 | bufferedData = append(bufferedData, serverNameLengthBytes...) 402 | serverNameLength := binary.BigEndian.Uint16(serverNameLengthBytes) 403 | 404 | serverNameBytes := make([]byte, serverNameLength) 405 | if len(bufferedData)+len(serverNameBytes) > maxBufferedDataSize { 406 | return "", nil, fmt.Errorf("buffered data exceeds maximum size") 407 | } 408 | _, err = io.ReadFull(reader, serverNameBytes) 409 | if err != nil { 410 | return "", nil, fmt.Errorf("failed to read server name: %w", err) 411 | } 412 | bufferedData = append(bufferedData, serverNameBytes...) 413 | 414 | serverName := string(serverNameBytes) 415 | logger.Printf("Extracted server name from TLS: %s", serverName) 416 | 417 | remainingData := make([]byte, reader.Buffered()) 418 | n, err := reader.Read(remainingData) 419 | if err != nil && err != io.EOF { 420 | return "", nil, fmt.Errorf("failed to read remaining data: %w", err) 421 | } 422 | if len(bufferedData)+n > maxBufferedDataSize { 423 | return "", nil, fmt.Errorf("remaining data exceeds maximum buffer size") 424 | } 425 | bufferedData = append(bufferedData, remainingData[:n]...) 426 | 427 | return serverName, bufferedData, nil 428 | } else { 429 | extensionData := make([]byte, extensionLength) 430 | if len(bufferedData)+len(extensionData) > maxBufferedDataSize { 431 | return "", nil, fmt.Errorf("buffered data exceeds maximum size") 432 | } 433 | _, err = io.ReadFull(reader, extensionData) 434 | if err != nil { 435 | return "", nil, fmt.Errorf("failed to skip extension: %w", err) 436 | } 437 | bufferedData = append(bufferedData, extensionData...) 438 | } 439 | } 440 | return "", nil, fmt.Errorf("SNI extension not found") 441 | } 442 | 443 | func lookupWithCache(hostname string) (string, error) { 444 | logger.Printf("Looking up hostname: %s", hostname) 445 | if entry, found := dnsCache.Get(hostname); found { 446 | cacheEntry := entry.(CacheEntry) 447 | if time.Since(cacheEntry.Timestamp) < time.Duration(config.TTL)*time.Second { 448 | logger.Printf("Cache hit for hostname: %s, address: %s", hostname, cacheEntry.Address) 449 | return cacheEntry.Address, nil 450 | } 451 | go lookupRaw(hostname) 452 | logger.Printf("Cache stale for hostname: %s, using old address while refreshing", hostname) 453 | return cacheEntry.Address, nil 454 | } 455 | logger.Printf("Cache miss for hostname: %s", hostname) 456 | return lookupRaw(hostname) 457 | } 458 | 459 | func lookupRaw(hostname string) (string, error) { 460 | logger.Printf("Performing raw lookup for hostname: %s", hostname) 461 | addresses, err := net.LookupIP(hostname) 462 | if err != nil { 463 | logger.Printf("Raw lookup failed for hostname: %s, error: %v", hostname, err) 464 | return "", err 465 | } 466 | 467 | var ipv6rsAddress string 468 | for _, addr := range addresses { 469 | if addr.To4() == nil && addr.To16() != nil && strings.HasPrefix(addr.String(), config.Prefix) { 470 | ipv6rsAddress = addr.String() 471 | break 472 | } 473 | } 474 | 475 | if ipv6rsAddress == "" { 476 | logger.Printf("No IPv6rs addresses found for hostname: %s", hostname) 477 | return "", fmt.Errorf("no IPv6rs addresses found") 478 | } 479 | 480 | dnsCache.Put(hostname, CacheEntry{Address: ipv6rsAddress, Timestamp: time.Now()}) 481 | logger.Printf("Raw lookup succeeded for hostname: %s, address: %s", hostname, ipv6rsAddress) 482 | return ipv6rsAddress, nil 483 | } 484 | 485 | func isTLS(data []byte) bool { 486 | return len(data) > 3 && data[0] == 0x16 && data[1] == 0x03 && data[2] >= 0x01 487 | } 488 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | ** 3 | ** delorean tests 4 | ** Tests for delorean 5 | ** 6 | ** Distributed under the COOL License. 7 | ** 8 | ** Copyright (c) 2024 IPv6.rs 9 | ** All Rights Reserved 10 | ** 11 | */ 12 | 13 | package main 14 | 15 | import ( 16 | "bufio" 17 | "bytes" 18 | "encoding/binary" 19 | "log" 20 | "net" 21 | "runtime" 22 | "sync" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | func init() { 28 | logger = log.New(nullWriter{}, "", log.LstdFlags) 29 | } 30 | 31 | func createFakeClientHelloPacket(serverName string) []byte { 32 | var packet bytes.Buffer 33 | 34 | packet.WriteByte(0x16) 35 | packet.WriteByte(0x03) 36 | packet.WriteByte(0x03) 37 | packet.Write([]byte{0x00, 0x00}) 38 | 39 | var handshake bytes.Buffer 40 | handshake.WriteByte(0x01) 41 | handshake.Write([]byte{0x00, 0x00, 0x00}) 42 | handshake.Write([]byte{0x03, 0x03}) 43 | handshake.Write([]byte{ 44 | 0xb6, 0xb2, 0x6a, 0xfb, 0x55, 0x5e, 0x03, 0xd5, 45 | 0x65, 0xa3, 0x6a, 0xf0, 0x5e, 0xa5, 0x43, 0x02, 46 | 0x93, 0xb9, 0x59, 0xa7, 0x54, 0xc3, 0xdd, 0x78, 47 | 0x57, 0x58, 0x34, 0xc5, 0x82, 0xfd, 0x53, 0xd1, 48 | }) 49 | handshake.WriteByte(0x00) 50 | handshake.Write([]byte{0x00, 0x04}) 51 | handshake.Write([]byte{ 52 | 0x00, 0x01, 53 | 0x00, 0xff, 54 | }) 55 | 56 | handshake.WriteByte(0x01) 57 | handshake.WriteByte(0x00) 58 | 59 | var extensions bytes.Buffer 60 | extensions.Write([]byte{0x00, 0x00}) 61 | var sni bytes.Buffer 62 | sni.Write([]byte{0x00, 0x0c}) 63 | sni.WriteByte(0x00) 64 | sniName := []byte(serverName) 65 | binary.Write(&sni, binary.BigEndian, uint16(len(sniName))) 66 | sni.Write(sniName) 67 | sniBytes := sni.Bytes() 68 | extensions.Write([]byte{0x00, 0x0e}) 69 | extensions.Write(sniBytes) 70 | 71 | extensions.Write([]byte{ 72 | 0x00, 0x0d, 73 | 0x00, 0x20, 74 | 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 75 | 0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 76 | 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 77 | 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 78 | }) 79 | 80 | extensions.Write([]byte{ 81 | 0x00, 0x0f, 82 | 0x00, 0x01, 83 | 0x01, 84 | }) 85 | 86 | extensionsBytes := extensions.Bytes() 87 | handshake.Write([]byte{0x00, 0x00}) 88 | extensionLengthFieldIndex := handshake.Len() - 2 89 | handshake.Write(extensionsBytes) 90 | binary.BigEndian.PutUint16(handshake.Bytes()[extensionLengthFieldIndex:], uint16(len(extensionsBytes))) 91 | 92 | handshakeBytes := handshake.Bytes() 93 | handshakeLength := len(handshakeBytes) - 4 94 | handshakeBytes[1] = byte(handshakeLength >> 16) 95 | handshakeBytes[2] = byte(handshakeLength >> 8) 96 | handshakeBytes[3] = byte(handshakeLength) 97 | 98 | packet.Write(handshakeBytes) 99 | 100 | recordLayerLength := len(packet.Bytes()) - 5 101 | packetBytes := packet.Bytes() 102 | binary.BigEndian.PutUint16(packetBytes[3:], uint16(recordLayerLength)) 103 | 104 | log.Printf("Packet Data: %x\n", packet.Bytes()) 105 | return packet.Bytes() 106 | } 107 | 108 | func TestGetNameAndBufferFromTLSConnection(t *testing.T) { 109 | serverName := "www.example.com" 110 | tlsData := createFakeClientHelloPacket(serverName) 111 | 112 | reader := bufio.NewReaderSize(bytes.NewReader(tlsData), 4096) 113 | name, _, err := getNameAndBufferFromTLSConnection(reader) 114 | if err != nil { 115 | t.Fatalf("Expected no error, got %v", err) 116 | } 117 | 118 | if name != serverName { 119 | t.Fatalf("Expected %s, got %s", serverName, name) 120 | } 121 | } 122 | 123 | func TestGetNameAndBufferFromHTTPConnection(t *testing.T) { 124 | httpData := []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") 125 | reader := bufio.NewReader(bytes.NewReader(httpData)) 126 | 127 | name, _, err := getNameAndBufferFromHTTPConnection(reader) 128 | if err != nil { 129 | t.Fatalf("Expected no error, got %v", err) 130 | } 131 | 132 | expectedName := "example.com" 133 | if name != expectedName { 134 | t.Fatalf("Expected %s, got %s", expectedName, name) 135 | } 136 | } 137 | 138 | func BenchmarkGetNameAndBufferFromTLSConnection(b *testing.B) { 139 | serverName := "www.example.com" 140 | tlsData := createFakeClientHelloPacket(serverName) 141 | 142 | for i := 0; i < b.N; i++ { 143 | reader := bufio.NewReaderSize(bytes.NewReader(tlsData), 4096) 144 | _, _, err := getNameAndBufferFromTLSConnection(reader) 145 | if err != nil { 146 | b.Fatalf("Expected no error, got %v", err) 147 | } 148 | } 149 | } 150 | 151 | func BenchmarkGetNameAndBufferFromHTTPConnection(b *testing.B) { 152 | httpData := []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") 153 | 154 | for i := 0; i < b.N; i++ { 155 | reader := bufio.NewReader(bytes.NewReader(httpData)) 156 | _, _, err := getNameAndBufferFromHTTPConnection(reader) 157 | if err != nil { 158 | b.Fatalf("Expected no error, got %v", err) 159 | } 160 | } 161 | } 162 | 163 | func StressTestServer(t *testing.T, serverFunc func(stop chan struct{})) { 164 | var wg sync.WaitGroup 165 | stop := make(chan struct{}) 166 | serverReady := make(chan struct{}) 167 | var failedConnections int 168 | var failedConnectionsMutex sync.Mutex 169 | 170 | wg.Add(1) 171 | go func() { 172 | defer wg.Done() 173 | serverFunc(stop) 174 | close(serverReady) 175 | }() 176 | 177 | time.Sleep(1 * time.Second) 178 | 179 | clientCount := 1000 180 | for i := 0; i < clientCount; i++ { 181 | wg.Add(1) 182 | go func(clientID int) { 183 | defer wg.Done() 184 | for { 185 | select { 186 | case <-stop: 187 | return 188 | default: 189 | conn, err := net.Dial("tcp", "127.0.0.1:8080") 190 | if err != nil { 191 | t.Logf("Client %d: Failed to connect: %v", clientID, err) 192 | failedConnectionsMutex.Lock() 193 | failedConnections++ 194 | failedConnectionsMutex.Unlock() 195 | return 196 | } 197 | conn.Close() 198 | } 199 | } 200 | }(i) 201 | } 202 | 203 | close(stop) 204 | <-serverReady 205 | wg.Wait() 206 | 207 | t.Logf("Total failed connections: %d out of %d attempts", failedConnections, clientCount) 208 | } 209 | 210 | func TestStressServer(t *testing.T) { 211 | StressTestServer(t, func(stop chan struct{}) { 212 | listener, err := net.Listen("tcp", "127.0.0.1:8080") 213 | if err != nil { 214 | log.Fatalf("Failed to start server: %v", err) 215 | } 216 | defer listener.Close() 217 | 218 | var wg sync.WaitGroup 219 | 220 | for { 221 | select { 222 | case <-stop: 223 | listener.Close() 224 | wg.Wait() 225 | return 226 | default: 227 | conn, err := listener.Accept() 228 | if err != nil { 229 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() { 230 | continue 231 | } 232 | log.Printf("Failed to accept connection: %v", err) 233 | continue 234 | } 235 | 236 | wg.Add(1) 237 | go func() { 238 | defer wg.Done() 239 | defer conn.Close() 240 | handleConnection(conn) 241 | }() 242 | } 243 | } 244 | }) 245 | } 246 | 247 | func TestMemoryUsage(t *testing.T) { 248 | var m runtime.MemStats 249 | 250 | serverName := "www.example.com" 251 | tlsData := createFakeClientHelloPacket(serverName) 252 | 253 | runtime.ReadMemStats(&m) 254 | log.Printf("Memory before: Alloc = %v TotalAlloc = %v Sys = %v NumGC = %v", m.Alloc, m.TotalAlloc, m.Sys, m.NumGC) 255 | 256 | for i := 0; i < 1000; i++ { 257 | reader := bufio.NewReaderSize(bytes.NewReader(tlsData), 4096) 258 | _, _, err := getNameAndBufferFromTLSConnection(reader) 259 | if err != nil { 260 | t.Fatalf("Expected no error, got %v", err) 261 | } 262 | } 263 | 264 | runtime.ReadMemStats(&m) 265 | log.Printf("Memory after: Alloc = %v TotalAlloc = %v Sys = %v NumGC = %v", m.Alloc, m.TotalAlloc, m.Sys, m.NumGC) 266 | } 267 | -------------------------------------------------------------------------------- /proxy.conf: -------------------------------------------------------------------------------- 1 | { 2 | "TTL": 300000, 3 | "prefix": "2607:", 4 | "ip": "0.0.0.0", 5 | "ports": [8000, 8080] 6 | } 7 | --------------------------------------------------------------------------------