This file is indexed.

/usr/share/perl5/Net/SIP/Leg.pm is in libnet-sip-perl 0.812-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
###########################################################################
# package Net::SIP::Leg
# a leg is a special kind of socket, which can send and receive SIP packets
# and manipulate transport relevant SIP header (Via,Record-Route)
###########################################################################

use strict;
use warnings;

package Net::SIP::Leg;
use Digest::MD5 'md5_hex';
use Socket;
use Net::SIP::Debug;
use Net::SIP::Util ':all';
use Net::SIP::SocketPool;
use Net::SIP::Packet;
use Net::SIP::Request;
use Net::SIP::Response;
use Errno qw(EHOSTUNREACH EINVAL);
use Hash::Util 'lock_ref_keys';
use Carp;

use fields qw(contact branch via proto src socketpool);

# sock: the socket for the leg
# src: hash addr,port,family where it receives data and sends data from
# proto: udp|tcp
# contact: to identify myself (default from addr:port)
# branch: base for branch-tag for via header
# via: precomputed part of via value

###########################################################################
# create a new leg
# Args: ($class,%args)
#   %args: hash, the following keys will be used and deleted from hash
#      proto: udp|tcp|tls. If not given will be determined from 'sock' or will
#        default to 'udp' or 'tls' (if 'tls' arg is used)
#      host,addr,port,family: source of outgoing and destination of
#        incoming data.
#        If IP address addr not given these values will be determined from
#        'sock'. Otherwise port will default to 5060 or 5061 (tls) and family
#        will be determined from addr syntax. host will default to addr
#      dst: destination for this leg in case a fixed destination is used
#        if not given 'sock' will be checked if connected
#      sock: socket which can just be used
#        if not given will create new socket based on proto, addr, port
#        if dst is given this new socket will be connected (udp only)
#      socketpool: socketpool which can just be used
#        if not given a new SocketPool object will be created based on the given
#        'sock' or the created socket (addr, port...). 'sock' and 'socketpool'
#        must not be given both.
#      tls: optional configuration parameters for IO::Socket::SSL. Implies
#        use of proto 'tls'.
#      contact: contact information
#        default will be based on addr and port
#      branch: branch informaton
#        default will be based on proto, addr, port
# Returns: $self - new leg object
###########################################################################
sub new {
    my ($class,%args) = @_;
    my $self = fields::new($class);

    my $proto = delete $args{proto};
    my $dst = delete $args{dst};
    my $tls = delete $args{tls};
    $proto ||= 'tls' if $tls;

    my ($sip_proto,$default_port) = $proto && $proto eq 'tls'
	? ('sips',5061) : ('sip',5060);

    my $family;
    my $host = delete $args{host};
    if (my $addr = delete $args{addr}) {
	my $port = delete $args{port};
	my $family = delete $args{family};
	if (!$family) {
	    ($addr,my $port_a, $family) = ip_string2parts($addr);
	    die "port given both as argument and contained in address"
		if $port && $port_a && $port != $port_a;
	    $port = $port_a if $port_a;
	}
	# port defined and 0 -> get port from system
	$port = $default_port if ! defined $port;
	$self->{src} = lock_ref_keys({
	    host   => $host || $addr,
	    addr   => $addr,
	    port   => $port,
	    family => $family
	});
    }

    if ($dst && !ref($dst)) {
	my ($ip,$port,$family) = ip_string2parts($dst);
	$family or die "destination must contain IP address";
	$dst = lock_ref_keys({
	    host   => $ip,
	    addr   => $ip,
	    port   => $port,
	    family => $family,
	});
    }

    my $sock = delete $args{sock};
    my $socketpool = delete $args{socketpool};
    die "only socketpool or sock should be given" if $sock && $socketpool;
    $sock ||= $socketpool && $socketpool->master;

    my $sockpeer = undef;
    if (!$sock) {
	# create new socket
	$proto ||= 'udp';
	my $src = $self->{src};
	if (!$src) {
	    # no src given, try to get useable soure from dst
	    die "neither source, destination nor socket given" if !$dst;
	    my $srcip = laddr4dst($dst->{addr}) or die
		"cannot find local IP when connecting to $dst->{addr}";
	    $src = $self->{src} = lock_ref_keys({
		host   => $host || $srcip,
		addr   => $srcip,
		port   => 0,
		family => $dst->{family},
	    });
	}

	croak("addr must be IP address") if ! ip_is_v46($src->{addr});

	my %sockargs = (
	    Proto     => $proto eq 'tls' ? 'tcp' : $proto,
	    Family    => $src->{family},
	    LocalAddr => $src->{addr},
	    Reuse     => 1, ReuseAddr => 1,
	);
	if ($proto eq 'tcp' or $proto eq 'tls') {
	    # with TCP we create a listening socket
	    $sockargs{Listen} = 100;
	} elsif ($dst) {
	    # with UDP we can create a connected socket if dst is given
	    $sockargs{PeerAddr} = $dst->{addr};
	    $sockargs{PeerPort} = $dst->{port};
	    $sockpeer = $dst;
	}

	# create a socket with the given local port
	# if no port is given try 5060,5062.. or let the system pick one
	for my $port ($src->{port}
	    ? $src->{port}
	    : ($default_port, 5062..5100, 0)) {
	    last if $sock = INETSOCK(%sockargs, LocalPort => $port);
	}

	$sock or die "failed to bind to " . ip_parts2string($src).": $!";
	$src->{port} ||= $sock->sockport;
	DEBUG(90,"created socket on ".ip_parts2string($src));

    } else {
	# get proto from socket
	$proto ||= $sock->socktype == SOCK_DGRAM ? 'udp':'tcp';

	# get src from socket
	if (!$self->{src}) {
	    my $saddr = getsockname($sock) or die
		"cannot get local name from provided socket: $!";
	    $self->{src} = ip_sockaddr2parts($saddr);
	    $self->{src}{host} = $host if $host;
	}
	if (!$dst and my $saddr = getpeername($sock)) {
	    # set dst from connected socket
	    $sockpeer = $dst = ip_sockaddr2parts($saddr);
	}
    }

    # create socketpool and add primary socket of leg to it if needed
    $self->{socketpool} = $socketpool ||= Net::SIP::SocketPool->new(
	$proto, $sock, $dst, $sockpeer, $tls);

    my $leg_addr = ip_parts2string({
	%{$self->{src}},
	use_host => 1, # prefer hostname
	default_port => $default_port,
    }, 1);  # use "[ipv6]" even if no port is given
    $self->{contact}  = delete $args{contact} || "$sip_proto:$leg_addr";

    $self->{branch} = 'z9hG4bK'. (
	delete $args{branch}
	|| md5_hex(@{$self->{src}}{qw(addr port)}, $proto)  # ip, port, proto
    );

    $self->{via} =  sprintf( "SIP/2.0/%s %s;branch=",
	uc($proto),$leg_addr );
    $self->{proto} = $proto;

    die "unhandled arguments: ".join(", ", keys %args) if %args;

    return $self;
}

###########################################################################
# do we need retransmits on this leg?
# Args: $self
# Returns: 1|0
#   1: need retransmits (UDP)
#   0: don't need retransmits (TCP, TLS)
###########################################################################
sub do_retransmits {
    my Net::SIP::Leg $self = shift;
    return $self->{proto} eq 'udp' ? 1 : 0;
}

###########################################################################
# prepare incoming packet for forwarding
# Args: ($self,$packet)
#   $packet: incoming Net::SIP::Packet, gets modified in-place
# Returns: undef | [code,text]
#   code: error code (can be empty if just drop packet on error)
#   text: error description (e.g max-forwards reached..)
###########################################################################
sub forward_incoming {
    my Net::SIP::Leg $self = shift;
    my ($packet) = @_;

    if ( $packet->is_response ) {
	# remove top via
	my $via;
	$packet->scan_header( via => [ sub {
	    my ($vref,$hdr) = @_;
	    if ( !$$vref ) {
		$$vref = $hdr->{value};
		$hdr->remove;
	    }
	}, \$via ]);

    } else {
	# Request

	# Max-Fowards
	my $maxf = $packet->get_header( 'max-forwards' );
	# we don't want to put somebody Max-Forwards: 7363535353 into the header
	# and then crafting a loop, so limit it to the default value
	$maxf = 70 if !$maxf || $maxf>70;
	$maxf--;
	if ( $maxf <= 0 ) {
	    # just drop
	    DEBUG( 10,'reached max-forwards. DROP' );
	    return [ undef,'max-forwards reached 0, dropping' ];
	}
	$packet->set_header( 'max-forwards',$maxf );

	# check if last hop was strict router
	# remove myself from route
	my $uri = $packet->uri;
	$uri = $1 if $uri =~m{^<(.*)>};
	($uri) = sip_hdrval2parts( route => $uri );
	my $remove_route;
	if ( $uri eq $self->{contact} ) {
	    # last router placed myself into URI -> strict router
	    # get original URI back from last Route-header
	    my @route = $packet->get_header( 'route' );
	    if ( !@route ) {
		# ooops, no route headers? -> DROP
		return [ '','request from strict router contained no route headers' ];
	    }
	    $remove_route = $#route;
	    $uri = $route[-1];
	    $uri = $1 if $uri =~m{^<(.*)>};
	    $packet->set_uri($uri);

	} else {
	    # last router was loose,remove top route if it is myself
	    my @route = $packet->get_header( 'route' );
	    if ( @route ) {
		my $route = $route[0];
		$route = $1 if $route =~m{^<(.*)>};
		($route) = sip_hdrval2parts( route => $route );
		if ( sip_uri_eq( $route,$self->{contact}) ) {
		    # top route was me
		    $remove_route = 0;
		}
	    }
	}
	if ( defined $remove_route ) {
	    $packet->scan_header( route => [ sub {
		my ($rr,$hdr) = @_;
		$hdr->remove if $$rr-- == 0;
	    }, \$remove_route]);
	}

	# Add Record-Route to request, except
	# to REGISTER (RFC3261, 10.2)
	$packet->insert_header( 'record-route', '<'.$self->{contact}.';lr>' )
	    if $packet->method ne 'REGISTER';
    }

    return;
}

###########################################################################
# prepare packet which gets forwarded through this leg
# packet was processed before by forward_incoming on (usually) another
# leg on the same dispatcher.
# Args: ($self,$packet,$incoming_leg)
#   $packet: outgoing Net::SIP::Packet, gets modified in-place
#   $incoming_leg: leg where packet came in
# Returns: undef | [code,text]
#   code: error code (can be empty if just drop packet on error)
#   text: error description (e.g max-forwards reached..)
###########################################################################
sub forward_outgoing {
    my Net::SIP::Leg $self = shift;
    my ($packet,$incoming_leg) = @_;

    if ( $packet->is_request ) {
	# check if myself is already in Via-path
	# in this case drop the packet, because a loop is detected
	if ( my @via = $packet->get_header( 'via' )) {
	    my $branch = $self->via_branch($packet,3);
	    foreach my $via ( @via ) {
		my (undef,$param) = sip_hdrval2parts( via => $via );
		# ignore via header w/o branch, although these don't conform to
		# RFC 3261, sect 8.1.1.7
		defined $param->{branch} or next;
		if ( substr( $param->{branch},0,length($branch) ) eq $branch ) {
		    DEBUG( 10,'loop detected because outgoing leg is in Via. DROP' );
		    return [ undef,'loop detected on outgoing leg, dropping' ];
		}
	    }
	}

	# Add Record-Route to request, except
	# to REGISTER (RFC3261, 10.2)
	# This is necessary, because these information are used in in new requests
	# from UAC to UAS, but also from UAS to UAC and UAS should talk to this leg
	# and not to the leg, where the request came in.
	# don't add if the upper record-route is already me, this is the case
	# when incoming and outgoing leg are the same
	if ( $packet->method ne 'REGISTER' ) {
	    my $rr;
	    unless ( (($rr) = $packet->get_header( 'record-route' ))
		and sip_uri_eq( $rr,$self->{contact} )) {
		$packet->insert_header( 'record-route', '<'.$self->{contact}.';lr>' )
	    }
	}

	# strip myself from route header, because I'm done
	if ( my @route = $packet->get_header( 'route' ) ) {
	    my $route = $route[0];
	    $route = $1 if $route =~m{^<(.*)>};
	    ($route) = sip_hdrval2parts( route => $route );
	    if ( sip_uri_eq( $route,$self->{contact} )) {
		# top route was me, remove it
		my $remove_route = 0;
		$packet->scan_header( route => [ sub {
		    my ($rr,$hdr) = @_;
		    $hdr->remove if $$rr-- == 0;
		}, \$remove_route]);
	    }
	}
    }
    return;
}


###########################################################################
# deliver packet through this leg to specified addr
# add local Via header to requests
# Args: ($self,$packet,$dst;$callback)
#   $packet: Net::SIP::Packet
#   $dst:    target for delivery as hash host,addr,port,family
#   $callback: optional callback, if an error occurred the callback will
#      be called with $! as argument. If no error occurred and the
#      proto is tcp the callback will be called with error=0 to show
#      that the packet was definitely delivered (and there's no need to retry)
###########################################################################
sub deliver {
    my Net::SIP::Leg $self = shift;
    my ($packet,$dst,$callback) = @_;

    my $isrq = $packet->is_request;
    if ( $isrq ) {
	# add via,
	# clone packet, because I don't want to change the original
	# one because it might be retried later
	# (could skip this for tcp?)
	$packet = $packet->clone;
	$self->add_via($packet);
    }

    # 2xx responses to INVITE requests and the request itself must have a
    # Contact, Allow and Supported header, 2xx Responses to OPTIONS need
    # Allow and Supported, 405 Responses should have Allow and Supported

    my ($need_contact,$need_allow,$need_supported);
    my $method = $packet->method;
    my $code = ! $isrq && $packet->code;
    if ( $method eq 'INVITE' and ( $isrq or $code =~m{^2} )) {
	$need_contact = $need_allow = $need_supported =1;
    } elsif ( !$isrq and (
	$code == 405 or
	( $method eq 'OPTIONS'  and $code =~m{^2} ))) {
	$need_allow = $need_supported =1;
    }
    if ( $need_contact && ! ( my @a = $packet->get_header( 'contact' ))) {
	# needs contact header, create from this leg and user part of from/to
	my ($user) = sip_hdrval2parts( $isrq
	    ? ( from => scalar($packet->get_header('from')) )
	    : ( to   => scalar($packet->get_header('to')) )
	);
	my ($proto,$addr) = $self->{contact} =~m{^(\w+):(?:.*\@)?(.*)$};
	my $contact = ( $user =~m{([^<>\@\s]+)\@} ? $1 : $user ).
	    "\@$addr";
	$contact = $proto.':'.$contact if $contact !~m{^\w+:};
	$packet->insert_header( contact => $contact );
    }
    if ( $need_allow && ! ( my @a = $packet->get_header( 'allow' ))) {
	# insert default methods
	$packet->insert_header( allow => 'INVITE, ACK, OPTIONS, CANCEL, BYE' );
    }
    if ( $need_supported && ! ( my @a = $packet->get_header( 'supported' ))) {
	# set as empty
	$packet->insert_header( supported => '' );
    }

    die "target protocol $dst->{proto} does not match leg $self->{proto}"
	if exists $dst->{proto} && $dst->{proto} ne $self->{proto};
    $dst->{port} ||= $self->{proto} eq 'tls' ? 5061 : 5060;

    $DEBUG && DEBUG( 2, "delivery with %s from %s to %s:\n%s",
	$self->{proto},
	ip_parts2string($self->{src}),
	ip_parts2string($dst),
	$packet->dump( Net::SIP::Debug->level -2 ) );

    return $self->sendto($packet,$dst,$callback);
}

###########################################################################
# send data to peer
# Args: ($self,$packet,$dst,$callback)
#   $packet: SIP packet object
#   $dst:   target as hash host,addr,port,family
#   $callback: callback for error|success, see method deliver
# Returns: $success
#   $success: true if no problems occurred while sending (this does not
#     mean that the packet was delivered reliable!)
###########################################################################
sub sendto {
    my Net::SIP::Leg $self = shift;
    my ($packet,$dst,$callback) = @_;

    $self->{socketpool}->sendto($packet,$dst,$callback)
	&& return 1;
    return;
}

###########################################################################
# Handle newly received packet.
# Currently just passes through the packet
# Args: ($self,$packet,$from)
#   $packet: packet object
#   $from: hash with proto,addr,port,family where the packet came from
# Returns: ($packet,$from)|()
#   $packet: packet object
#   $from: hash with proto,ip,port,family where the packet came from
###########################################################################
sub receive {
    my Net::SIP::Leg $self = shift;
    my ($packet,$from) = @_;

    $DEBUG && DEBUG( 2,"received packet on %s from %s:\n%s",
	sip_sockinfo2uri($self->{proto},@{$self->{src}}{qw(addr port family)}),
	sip_sockinfo2uri(@{$from}{qw(proto addr port family)}),
	$packet->dump( Net::SIP::Debug->level -2 )
    );
    return ($packet,$from);
}


###########################################################################
# check if the top via header matches the transport of this call through
# this leg. Used to strip Via header in response.
# Args: ($self,$packet)
#  $packet: Net::SIP::Packet (usually Net::SIP::Response)
# Returns: $bool
#  $bool: true if the packets via matches this leg, else false
###########################################################################
sub check_via {
    my ($self,$packet) = @_;
    my ($via) = $packet->get_header( 'via' );
    my ($data,$param) = sip_hdrval2parts( via => $via );
    my $cmp_branch = $self->via_branch($packet,2);
    return substr( $param->{branch},0,length($cmp_branch)) eq $cmp_branch;
}

###########################################################################
# add myself as Via header to packet
# Args: ($self,$packet)
#  $packet: Net::SIP::Packet (usually Net::SIP::Request)
# Returns: NONE
# modifies packet in-place
###########################################################################
sub add_via {
    my Net::SIP::Leg $self = shift;
    my $packet = shift;
    $packet->insert_header( via => $self->{via}.$self->via_branch($packet,3));
}

###########################################################################
# computes branch tag for via header
# Args: ($self,$packet,$level)
#  $packet: Net::SIP::Packet (usually Net::SIP::Request)
#  $level: level of detail: 1:leg, 2:call, 3:path
# Returns: $value
###########################################################################
sub via_branch {
    my Net::SIP::Leg $self = shift;
    my ($packet,$level) = @_;
    my $val = $self->{branch};
    $val .= substr( md5_hex( $packet->tid ),0,15 ) if $level>1;
    if ($level>2) {
	my @parts;
	# RT#120816 -  take only known constant values from proxy-authorization
	for(sort $packet->get_header('proxy-authorization')) {
	    my ($typ,$param) = sip_hdrval2parts('proxy-authorization' => $_);
	    push @parts,$typ;
	    for(qw(realm username domain qop algorithm)) {
		push @parts,"$_=$param->{$_}" if exists $param->{$_};
	    }
	}

	# RT#120816 - include only the branch from via header if possible
	if (my $via = ($packet->get_header('via'))[0]) {
	    my (undef,$param) = sip_hdrval2parts(via => $via);
	    push @parts, $param && $param->{branch} || $via;
	}

	push @parts,
	    ( sort $packet->get_header('proxy-require')),
	    $packet->get_header('route'),
	    $packet->get_header('from'),
	    ($packet->as_parts())[1]; # URI
	$val .= substr(md5_hex(@parts),0,15);
    }
    return $val;
}

###########################################################################
# check if the leg could deliver to the specified addr
# Args: ($self,($addr|%spec))
#  $addr: addr|proto:addr|addr:port|proto:addr:port
#  %spec: hash with keys addr,proto,port
# Returns: $bool
#  $bool: true if we can deliver to $ip with $proto
###########################################################################
sub can_deliver_to {
    my Net::SIP::Leg $self = shift;
    my %spec;
    if (@_>1) {
	%spec = @_;
    } else {
	@spec{ qw(proto host port family) } = sip_uri2sockinfo(shift());
	$spec{addr} = $spec{family} ? $spec{host} : undef;
    }

    # return false if proto or family don't match
    return if $spec{proto} && $spec{proto} ne $self->{proto};
    return if $spec{family} && $self->{src}
	&& $self->{src}{family} != $spec{family};

    # XXXXX dont know how to find out if I can deliver to this addr from this
    # leg without lookup up route
    # therefore just return true and if you have more than one leg you have
    # to figure out yourself where to send it
    return 1
}

###########################################################################
# check if this leg matches given criteria (used in Dispatcher)
# Args: ($self,$args)
#   $args: hash with any of 'addr', 'port', 'proto', 'sub'
# Returns: true if leg fits all args
###########################################################################
sub match {
    my Net::SIP::Leg $self = shift;
    my $args = shift;
    return if $args->{addr}
	&& $args->{addr} ne $self->{src}{addr}
	&& $args->{addr} ne $self->{src}{host};
    return if $args->{port}  && $args->{port}  != $self->{src}{port};
    return if $args->{proto} && $args->{proto} ne $self->{proto};
    return if $args->{sub}   && !invoke_callback($args->{sub},$self);
    return 1;
}

###########################################################################
# returns SocketPool object on Leg
# Args: $self
# Returns: $socketpool
###########################################################################
sub socketpool {
    my Net::SIP::Leg $self = shift;
    return $self->{socketpool};
}

###########################################################################
# local address of the leg
# Args: $self;$parts
#  $parts: number of parts to include
#     0 -> address only
#     1 -> address[:non_default_port]
#     2 -> host[:non_default_port]
# Returns: string
###########################################################################
sub laddr {
    my Net::SIP::Leg $self = shift;
    my $parts = shift;
    ! $parts and return $self->{src}{addr};
    return ip_parts2string({
	%{ $self->{src} },
	default_port => $self->{proto} eq 'tls' ? 5061 : 5060,
	$parts == 1 ? () :
	$parts == 2 ? (use_host => 1) :
	die "invalid parts specification $parts",
    });
}

###########################################################################
# some info about the Leg for debugging
# Args: $self
# Returns: string
###########################################################################
sub dump {
    my Net::SIP::Leg $self = shift;
    return ref($self)." $self->{proto}:"
	. ip_parts2string($self->{src});
}


###########################################################################
# returns key for leg
# Args: $self
# Returns: key (string)
###########################################################################
sub key {
    my Net::SIP::Leg $self = shift;
    return ref($self).' '.join(':',$self->{proto},
	@{$self->{src}}{qw(addr port)});
}

1;