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