1# Copyright 2016-2026 The OpenSSL Project Authors. All Rights Reserved. 2# 3# Licensed under the Apache License 2.0 (the "License"). You may not use 4# this file except in compliance with the License. You can obtain a copy 5# in the file LICENSE in the source distribution or at 6# https://www.openssl.org/source/license.html 7 8use strict; 9use POSIX ":sys_wait_h"; 10 11package TLSProxy::Proxy; 12 13use File::Spec; 14use IO::Socket; 15use IO::Select; 16use TLSProxy::Record; 17use TLSProxy::Message; 18use TLSProxy::ClientHello; 19use TLSProxy::ServerHello; 20use TLSProxy::HelloVerifyRequest; 21use TLSProxy::EncryptedExtensions; 22use TLSProxy::Certificate; 23use TLSProxy::CertificateRequest; 24use TLSProxy::CertificateVerify; 25use TLSProxy::ServerKeyExchange; 26use TLSProxy::NewSessionTicket; 27use TLSProxy::NextProto; 28 29my $have_IPv6; 30my $useINET6; 31my $IP_factory; 32 33BEGIN 34{ 35 # IO::Socket::IP is on the core module list, IO::Socket::INET6 isn't. 36 # However, IO::Socket::INET6 is older and is said to be more widely 37 # deployed for the moment, and may have less bugs, so we try the latter 38 # first, then fall back on the core modules. Worst case scenario, we 39 # fall back to IO::Socket::INET, only supports IPv4. 40 eval { 41 require IO::Socket::INET6; 42 my $s = IO::Socket::INET6->new( 43 LocalAddr => "::1", 44 LocalPort => 0, 45 Listen=>1, 46 ); 47 $s or die "\n"; 48 $s->close(); 49 }; 50 if ($@ eq "") { 51 $IP_factory = sub { IO::Socket::INET6->new(Domain => AF_INET6, @_); }; 52 $have_IPv6 = 1; 53 $useINET6 = 1; 54 } else { 55 eval { 56 require IO::Socket::IP; 57 my $s = IO::Socket::IP->new( 58 LocalAddr => "::1", 59 LocalPort => 0, 60 Listen=>1, 61 ); 62 $s or die "\n"; 63 $s->close(); 64 }; 65 if ($@ eq "") { 66 $IP_factory = sub { IO::Socket::IP->new(@_); }; 67 $have_IPv6 = 1; 68 $useINET6 = 0; 69 } else { 70 $IP_factory = sub { IO::Socket::INET->new(@_); }; 71 $have_IPv6 = 0; 72 $useINET6 = 0; 73 } 74 } 75} 76 77my $is_tls13 = 0; 78my $ciphersuite = undef; 79 80sub new { 81 my $class = shift; 82 my ($filter, 83 $execute, 84 $cert, 85 $debug) = @_; 86 return init($class, $filter, $execute, $cert, $debug, 0); 87} 88 89sub new_dtls { 90 my $class = shift; 91 my ($filter, 92 $execute, 93 $cert, 94 $debug) = @_; 95 return init($class, $filter, $execute, $cert, $debug, 1); 96} 97 98sub init 99{ 100 my $useSockInet = 0; 101 eval { 102 require IO::Socket::IP; 103 my $s = IO::Socket::IP->new( 104 LocalAddr => "::1", 105 LocalPort => 0, 106 Listen=>1, 107 ); 108 $s or die "\n"; 109 $s->close(); 110 }; 111 if ($@ eq "") { 112 require IO::Socket::IP; 113 } else { 114 $useSockInet = 1; 115 } 116 117 my $class = shift; 118 my ($filter, 119 $execute, 120 $cert, 121 $debug, 122 $isdtls) = @_; 123 124 my $test_client_port; 125 126 # Sometimes, our random selection of client ports gets unlucky 127 # And we randomly select a port that's already in use. This causes 128 # this test to fail, so lets harden ourselves against that by doing 129 # a test bind to the randomly selected port, and only continue once we 130 # find a port that's available. 131 my $test_client_addr = $have_IPv6 ? "[::1]" : "127.0.0.1"; 132 my $found_port = 0; 133 for (my $i = 0; $i <= 10; $i++) { 134 $test_client_port = 49152 + int(rand(65535 - 49152)); 135 my $test_sock; 136 if ($useINET6 == 0) { 137 if ($useSockInet == 0) { 138 $test_sock = IO::Socket::IP->new(LocalPort => $test_client_port, 139 LocalAddr => $test_client_addr); 140 } else { 141 $test_sock = IO::Socket::INET->new(LocalAddr => $test_client_addr, 142 LocalPort => $test_client_port); 143 } 144 } else { 145 $test_sock = IO::Socket::INET6->new(LocalAddr => $test_client_addr, 146 LocalPort => $test_client_port, 147 Domain => AF_INET6); 148 } 149 if ($test_sock) { 150 $found_port = 1; 151 $test_sock->close(); 152 print "Found available client port ${test_client_port}\n"; 153 last; 154 } 155 print "Port ${test_client_port} in use - $@\n"; 156 } 157 158 if ($found_port == 0) { 159 die "Unable to find usable port for TLSProxy"; 160 } 161 162 my $self = { 163 #Public read/write 164 proxy_addr => $test_client_addr, 165 client_addr => $test_client_addr, 166 filter => $filter, 167 serverflags => "", 168 clientflags => "", 169 serverconnects => 1, 170 reneg => 0, 171 sessionfile => undef, 172 173 #Public read 174 isdtls => $isdtls, 175 proxy_port => 0, 176 client_port => $test_client_port, 177 server_port => 0, 178 serverpid => 0, 179 clientpid => 0, 180 clientexit => 0, 181 execute => $execute, 182 cert => $cert, 183 debug => $debug, 184 cipherc => "", 185 ciphersuitesc => "", 186 ciphers => "AES128-SHA", 187 ciphersuitess => "TLS_AES_128_GCM_SHA256", 188 flight => -1, 189 direction => -1, 190 partial => ["", ""], 191 record_list => [], 192 message_list => [], 193 }; 194 195 return bless $self, $class; 196} 197 198sub DESTROY 199{ 200 my $self = shift; 201 202 $self->{proxy_sock}->close() if $self->{proxy_sock}; 203} 204 205sub clearClient 206{ 207 my $self = shift; 208 209 $self->{cipherc} = ""; 210 $self->{ciphersuitec} = ""; 211 $self->{flight} = -1; 212 $self->{direction} = -1; 213 $self->{partial} = ["", ""]; 214 $self->{record_list} = []; 215 $self->{message_list} = []; 216 $self->{clientflags} = ""; 217 $self->{sessionfile} = undef; 218 $self->{clientpid} = 0; 219 $self->{clientexit} = 0; 220 $is_tls13 = 0; 221 $ciphersuite = undef; 222 223 TLSProxy::Message->clear(); 224 TLSProxy::Record->clear(); 225} 226 227sub clear 228{ 229 my $self = shift; 230 231 $self->clearClient; 232 $self->{ciphers} = "AES128-SHA"; 233 $self->{ciphersuitess} = "TLS_AES_128_GCM_SHA256"; 234 $self->{serverflags} = ""; 235 $self->{serverconnects} = 1; 236 $self->{serverpid} = 0; 237 $self->{reneg} = 0; 238} 239 240sub restart 241{ 242 my $self = shift; 243 244 $self->clear; 245 $self->start; 246} 247 248sub clientrestart 249{ 250 my $self = shift; 251 252 $self->clear; 253 $self->clientstart; 254} 255 256sub connect_to_server 257{ 258 my $self = shift; 259 my $servaddr = $self->{server_addr}; 260 261 $servaddr =~ s/[\[\]]//g; # Remove [ and ] 262 263 my $sock = $IP_factory->(PeerAddr => $servaddr, 264 PeerPort => $self->{server_port}, 265 Proto => $self->{isdtls} ? 'udp' : 'tcp'); 266 if (!defined($sock)) { 267 my $err = $!; 268 kill(3, $self->{real_serverpid}); 269 die "unable to connect: $err\n"; 270 } 271 272 $self->{server_sock} = $sock; 273} 274 275sub start 276{ 277 my ($self) = shift; 278 my $pid; 279 280 # Create the Proxy socket 281 my $proxaddr = $self->{proxy_addr}; 282 $proxaddr =~ s/[\[\]]//g; # Remove [ and ] 283 my $clientaddr = $self->{client_addr}; 284 $clientaddr =~ s/[\[\]]//g; # Remove [ and ] 285 286 my @proxyargs; 287 288 if ($self->{isdtls}) { 289 @proxyargs = ( 290 LocalHost => $proxaddr, 291 LocalPort => 0, 292 PeerHost => $clientaddr, 293 PeerPort => $self->{client_port}, 294 Proto => "udp", 295 ); 296 } else { 297 @proxyargs = ( 298 LocalHost => $proxaddr, 299 LocalPort => 0, 300 Proto => "tcp", 301 Listen => SOMAXCONN, 302 ); 303 } 304 305 if (my $sock = $IP_factory->(@proxyargs)) { 306 $self->{proxy_sock} = $sock; 307 $self->{proxy_port} = $sock->sockport(); 308 $self->{proxy_addr} = $sock->sockhost(); 309 $self->{proxy_addr} =~ s/(.*:.*)/[$1]/; 310 print "Proxy started on port ", 311 "$self->{proxy_addr}:$self->{proxy_port}\n"; 312 # use same address for s_server 313 $self->{server_addr} = $self->{proxy_addr}; 314 } else { 315 warn "Failed creating proxy socket (".$proxaddr.",0): $!\n"; 316 } 317 318 if ($self->{proxy_sock} == 0) { 319 return 0; 320 } 321 322 my $execcmd = $self->execute 323 ." s_server -no_comp -engine ossltest -state" 324 #In TLSv1.3 we issue two session tickets. The default session id 325 #callback gets confused because the ossltest engine causes the same 326 #session id to be created twice due to the changed random number 327 #generation. Using "-ext_cache" replaces the default callback with a 328 #different one that doesn't get confused. 329 ." -ext_cache" 330 ." -accept $self->{server_addr}:0" 331 ." -cert ".$self->cert." -cert2 ".$self->cert 332 ." -naccept ".$self->serverconnects; 333 if ($self->{isdtls}) { 334 $execcmd .= " -dtls -max_protocol DTLSv1.2" 335 # TLSProxy does not support message fragmentation. So 336 # set a high mtu and fingers crossed. 337 ." -mtu 1500"; 338 } else { 339 $execcmd .= " -rev -max_protocol TLSv1.3"; 340 } 341 if ($self->ciphers ne "") { 342 $execcmd .= " -cipher ".$self->ciphers; 343 } 344 if ($self->ciphersuitess ne "") { 345 $execcmd .= " -ciphersuites ".$self->ciphersuitess; 346 } 347 if ($self->serverflags ne "") { 348 $execcmd .= " ".$self->serverflags; 349 } 350 if ($self->debug) { 351 print STDERR "Server command: $execcmd\n"; 352 } 353 354 open(my $savedin, "<&STDIN"); 355 356 # Temporarily replace STDIN so that sink process can inherit it... 357 open(STDIN, "$^X -e 'sleep(10)' |") if $self->{isdtls}; 358 $pid = open(STDIN, "$execcmd 2>&1 |") or die "Failed to $execcmd: $!\n"; 359 $self->{real_serverpid} = $pid; 360 361 # Process the output from s_server until we find the ACCEPT line, which 362 # tells us what the accepting address and port are. 363 while (<>) { 364 print; 365 s/\R$//; # Better chomp 366 next unless (/^ACCEPT\s.*:(\d+)$/); 367 $self->{server_port} = $1; 368 last; 369 } 370 371 if ($self->{server_port} == 0) { 372 # This actually means that s_server exited, because otherwise 373 # we would still searching for ACCEPT... 374 waitpid($pid, 0); 375 die "no ACCEPT detected in '$execcmd' output: $?\n"; 376 } 377 378 # Just make sure everything else is simply printed [as separate lines]. 379 # The sub process simply inherits our STD* and will keep consuming 380 # server's output and printing it as long as there is anything there, 381 # out of our way. 382 my $error; 383 $pid = undef; 384 if (eval { require Win32::Process; 1; }) { 385 if (Win32::Process::Create(my $h, $^X, "perl -ne print", 0, 0, ".")) { 386 $pid = $h->GetProcessID(); 387 $self->{proc_handle} = $h; # hold handle till next round [or exit] 388 } else { 389 $error = Win32::FormatMessage(Win32::GetLastError()); 390 } 391 } else { 392 if (defined($pid = fork)) { 393 $pid or exec("$^X -ne print") or exit($!); 394 } else { 395 $error = $!; 396 } 397 } 398 399 # Change back to original stdin 400 open(STDIN, "<&", $savedin); 401 close($savedin); 402 403 if (!defined($pid)) { 404 kill(3, $self->{real_serverpid}); 405 die "Failed to capture s_server's output: $error\n"; 406 } 407 408 $self->{serverpid} = $pid; 409 410 print STDERR "Server responds on ", 411 "$self->{server_addr}:$self->{server_port}\n"; 412 413 # Connect right away... 414 $self->connect_to_server(); 415 416 return $self->clientstart; 417} 418 419sub clientstart 420{ 421 my ($self) = shift; 422 423 my $success = 1; 424 425 if ($self->execute) { 426 my $pid; 427 my $execcmd = $self->execute 428 ." s_client -engine ossltest" 429 ." -connect $self->{proxy_addr}:$self->{proxy_port}"; 430 if ($self->{isdtls}) { 431 $execcmd .= " -dtls -max_protocol DTLSv1.2" 432 # TLSProxy does not support message fragmentation. So 433 # set a high mtu and fingers crossed. 434 ." -mtu 1500" 435 # UDP has no "accept" for sockets which means we need to 436 # know were to send data back to. 437 ." -bind $self->{client_addr}:$self->{client_port}"; 438 } else { 439 $execcmd .= " -max_protocol TLSv1.3"; 440 } 441 if ($self->cipherc ne "") { 442 $execcmd .= " -cipher ".$self->cipherc; 443 } 444 if ($self->ciphersuitesc ne "") { 445 $execcmd .= " -ciphersuites ".$self->ciphersuitesc; 446 } 447 if ($self->clientflags ne "") { 448 $execcmd .= " ".$self->clientflags; 449 } 450 if ($self->clientflags !~ m/-(no)?servername/) { 451 $execcmd .= " -servername localhost"; 452 } 453 if (defined $self->sessionfile) { 454 $execcmd .= " -ign_eof"; 455 } 456 if ($self->debug) { 457 print STDERR "Client command: $execcmd\n"; 458 } 459 460 open(my $savedout, ">&STDOUT"); 461 # If we open pipe with new descriptor, attempt to close it, 462 # explicitly or implicitly, would incur waitpid and effectively 463 # dead-lock... 464 if (!($pid = open(STDOUT, "| $execcmd"))) { 465 my $err = $!; 466 kill(3, $self->{real_serverpid}); 467 die "Failed to $execcmd: $err\n"; 468 } 469 $self->{clientpid} = $pid; 470 471 # queue [magic] input 472 print $self->reneg ? "R" : "test"; 473 474 # this closes client's stdin without waiting for its pid 475 open(STDOUT, ">&", $savedout); 476 close($savedout); 477 } 478 479 # Wait for incoming connection from client 480 my $fdset = IO::Select->new($self->{proxy_sock}); 481 if (!$fdset->can_read(60)) { 482 kill(3, $self->{real_serverpid}); 483 die "s_client didn't try to connect\n"; 484 } 485 486 my $client_sock; 487 if($self->{isdtls}) { 488 $client_sock = $self->{proxy_sock} 489 } elsif (!($client_sock = $self->{proxy_sock}->accept())) { 490 warn "Failed accepting incoming connection: $!\n"; 491 return 0; 492 } 493 494 print "Connection opened\n"; 495 496 my $server_sock = $self->{server_sock}; 497 my $indata; 498 499 #Wait for either the server socket or the client socket to become readable 500 $fdset = IO::Select->new($server_sock, $client_sock); 501 my @ready; 502 my $ctr = 0; 503 local $SIG{PIPE} = "IGNORE"; 504 $self->{saw_session_ticket} = undef; 505 while($fdset->count && $ctr < 10) { 506 if (defined($self->{sessionfile})) { 507 # s_client got -ign_eof and won't be exiting voluntarily, so we 508 # look for data *and* session ticket... 509 last if TLSProxy::Message->success() 510 && $self->{saw_session_ticket}; 511 } 512 if (!(@ready = $fdset->can_read(1))) { 513 last if TLSProxy::Message->success() 514 && $self->{saw_session_ticket}; 515 516 $ctr++; 517 next; 518 } 519 foreach my $hand (@ready) { 520 if ($hand == $server_sock) { 521 if ($server_sock->sysread($indata, 16384)) { 522 if ($indata = $self->process_packet(1, $indata)) { 523 $client_sock->syswrite($indata) or goto END; 524 } 525 $ctr = 0; 526 } else { 527 $fdset->remove($server_sock); 528 $client_sock->shutdown(SHUT_WR); 529 } 530 } elsif ($hand == $client_sock) { 531 if ($client_sock->sysread($indata, 16384)) { 532 if ($indata = $self->process_packet(0, $indata)) { 533 $server_sock->syswrite($indata) or goto END; 534 } 535 $ctr = 0; 536 } else { 537 $fdset->remove($client_sock); 538 $server_sock->shutdown(SHUT_WR); 539 } 540 } else { 541 kill(3, $self->{real_serverpid}); 542 die "Unexpected handle"; 543 } 544 } 545 } 546 547 if ($ctr >= 10) { 548 kill(3, $self->{real_serverpid}); 549 print "No progress made\n"; 550 $success = 0; 551 } 552 553 END: 554 print "Connection closed\n"; 555 if($server_sock) { 556 $server_sock->close(); 557 $self->{server_sock} = undef; 558 } 559 if($client_sock) { 560 #Closing this also kills the child process 561 $client_sock->close(); 562 } 563 564 my $pid; 565 if (--$self->{serverconnects} == 0) { 566 $pid = $self->{serverpid}; 567 print "Waiting for 'perl -ne print' process to close: $pid...\n"; 568 $pid = waitpid($pid, 0); 569 if ($pid > 0) { 570 die "exit code $? from 'perl -ne print' process\n" if $? != 0; 571 } elsif ($pid == 0) { 572 kill(3, $self->{real_serverpid}); 573 die "lost control over $self->{serverpid}?"; 574 } 575 $pid = $self->{real_serverpid}; 576 print "Waiting for s_server process to close: $pid...\n"; 577 # it's done already, just collect the exit code [and reap]... 578 waitpid($pid, 0); 579 die "exit code $? from s_server process\n" if $? != 0; 580 } else { 581 # It's a bit counter-intuitive spot to make next connection to 582 # the s_server. Rationale is that established connection works 583 # as synchronization point, in sense that this way we know that 584 # s_server is actually done with current session... 585 $self->connect_to_server(); 586 } 587 $pid = $self->{clientpid}; 588 print "Waiting for s_client process to close: $pid...\n"; 589 waitpid($pid, 0); 590 $self->{clientexit} = $?; 591 592 return $success; 593} 594 595sub process_packet 596{ 597 my ($self, $server, $packet) = @_; 598 my $len_real; 599 my $decrypt_len; 600 my $data; 601 my $recnum; 602 603 if ($server) { 604 print "Received server packet\n"; 605 } else { 606 print "Received client packet\n"; 607 } 608 609 if ($self->{direction} != $server) { 610 $self->{flight} = $self->{flight} + 1; 611 $self->{direction} = $server; 612 } 613 614 print "Packet length = ".length($packet)."\n"; 615 print "Processing flight ".$self->flight."\n"; 616 617 #Return contains the list of record found in the packet followed by the 618 #list of messages in those records and any partial message 619 my @ret = TLSProxy::Record->get_records($server, $self->flight, 620 $self->{partial}[$server].$packet, 621 $self->{isdtls}); 622 623 $self->{partial}[$server] = $ret[2]; 624 push @{$self->{record_list}}, @{$ret[0]}; 625 push @{$self->{message_list}}, @{$ret[1]}; 626 627 print "\n"; 628 629 if (scalar(@{$ret[0]}) == 0 or length($ret[2]) != 0) { 630 return ""; 631 } 632 633 #Finished parsing. Call user provided filter here 634 if (defined $self->filter) { 635 $self->filter->($self); 636 } 637 638 #Take a note on NewSessionTicket 639 foreach my $message (reverse @{$self->{message_list}}) { 640 if ($message->{mt} == TLSProxy::Message::MT_NEW_SESSION_TICKET) { 641 $self->{saw_session_ticket} = 1; 642 last; 643 } 644 } 645 646 #Reconstruct the packet 647 $packet = ""; 648 foreach my $record (@{$self->record_list}) { 649 $packet .= $record->reconstruct_record($server); 650 } 651 652 print "Forwarded packet length = ".length($packet)."\n\n"; 653 654 return $packet; 655} 656 657#Read accessors 658sub execute 659{ 660 my $self = shift; 661 return $self->{execute}; 662} 663sub cert 664{ 665 my $self = shift; 666 return $self->{cert}; 667} 668sub debug 669{ 670 my $self = shift; 671 return $self->{debug}; 672} 673sub flight 674{ 675 my $self = shift; 676 return $self->{flight}; 677} 678sub record_list 679{ 680 my $self = shift; 681 return $self->{record_list}; 682} 683sub success 684{ 685 my $self = shift; 686 return $self->{success}; 687} 688sub end 689{ 690 my $self = shift; 691 return $self->{end}; 692} 693sub supports_IPv6 694{ 695 my $self = shift; 696 return $have_IPv6; 697} 698sub proxy_addr 699{ 700 my $self = shift; 701 return $self->{proxy_addr}; 702} 703sub proxy_port 704{ 705 my $self = shift; 706 return $self->{proxy_port}; 707} 708sub server_addr 709{ 710 my $self = shift; 711 return $self->{server_addr}; 712} 713sub server_port 714{ 715 my $self = shift; 716 return $self->{server_port}; 717} 718sub serverpid 719{ 720 my $self = shift; 721 return $self->{serverpid}; 722} 723sub clientpid 724{ 725 my $self = shift; 726 return $self->{clientpid}; 727} 728sub clientexit 729{ 730 my $self = shift; 731 return $self->{clientexit}; 732} 733 734#Read/write accessors 735sub filter 736{ 737 my $self = shift; 738 if (@_) { 739 $self->{filter} = shift; 740 } 741 return $self->{filter}; 742} 743sub cipherc 744{ 745 my $self = shift; 746 if (@_) { 747 $self->{cipherc} = shift; 748 } 749 return $self->{cipherc}; 750} 751sub ciphersuitesc 752{ 753 my $self = shift; 754 if (@_) { 755 $self->{ciphersuitesc} = shift; 756 } 757 return $self->{ciphersuitesc}; 758} 759sub ciphers 760{ 761 my $self = shift; 762 if (@_) { 763 $self->{ciphers} = shift; 764 } 765 return $self->{ciphers}; 766} 767sub ciphersuitess 768{ 769 my $self = shift; 770 if (@_) { 771 $self->{ciphersuitess} = shift; 772 } 773 return $self->{ciphersuitess}; 774} 775sub serverflags 776{ 777 my $self = shift; 778 if (@_) { 779 $self->{serverflags} = shift; 780 } 781 return $self->{serverflags}; 782} 783sub clientflags 784{ 785 my $self = shift; 786 if (@_) { 787 $self->{clientflags} = shift; 788 } 789 return $self->{clientflags}; 790} 791sub serverconnects 792{ 793 my $self = shift; 794 if (@_) { 795 $self->{serverconnects} = shift; 796 } 797 return $self->{serverconnects}; 798} 799# This is a bit ugly because the caller is responsible for keeping the records 800# in sync with the updated message list; simply updating the message list isn't 801# sufficient to get the proxy to forward the new message. 802# But it does the trick for the one test (test_sslsessiontick) that needs it. 803sub message_list 804{ 805 my $self = shift; 806 if (@_) { 807 $self->{message_list} = shift; 808 } 809 return $self->{message_list}; 810} 811 812sub fill_known_data 813{ 814 my $length = shift; 815 my $ret = ""; 816 for (my $i = 0; $i < $length; $i++) { 817 $ret .= chr($i); 818 } 819 return $ret; 820} 821 822sub is_tls13 823{ 824 my $class = shift; 825 if (@_) { 826 $is_tls13 = shift; 827 } 828 return $is_tls13; 829} 830 831sub reneg 832{ 833 my $self = shift; 834 if (@_) { 835 $self->{reneg} = shift; 836 } 837 return $self->{reneg}; 838} 839 840#Setting a sessionfile means that the client will not close until the given 841#file exists. This is useful in TLSv1.3 where otherwise s_client will close 842#immediately at the end of the handshake, but before the session has been 843#received from the server. A side effect of this is that s_client never sends 844#a close_notify, so instead we consider success to be when it sends application 845#data over the connection. 846sub sessionfile 847{ 848 my $self = shift; 849 if (@_) { 850 $self->{sessionfile} = shift; 851 TLSProxy::Message->successondata(1); 852 } 853 return $self->{sessionfile}; 854} 855 856sub ciphersuite 857{ 858 my $class = shift; 859 if (@_) { 860 $ciphersuite = shift; 861 } 862 return $ciphersuite; 863} 864 865sub isdtls 866{ 867 my $self = shift; 868 return $self->{isdtls}; #read-only 869} 870 8711; 872