From owner-freebsd-net@FreeBSD.ORG Wed Aug 27 07:25:05 2014 Return-Path: Delivered-To: freebsd-net@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [8.8.178.115]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id E10FFDD3; Wed, 27 Aug 2014 07:25:04 +0000 (UTC) Received: from mx11.netapp.com (mx11.netapp.com [216.240.18.76]) (using TLSv1 with cipher RC4-SHA (128/128 bits)) (Client CN "mx11.netapp.com", Issuer "VeriSign Class 3 International Server CA - G3" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id AE1013D81; Wed, 27 Aug 2014 07:25:04 +0000 (UTC) X-IronPort-AV: E=Sophos;i="5.04,409,1406617200"; d="asc'?scan'208";a="142509389" Received: from vmwexceht03-prd.hq.netapp.com ([10.106.76.241]) by mx11-out.netapp.com with ESMTP; 27 Aug 2014 00:23:43 -0700 Received: from HIOEXCMBX08-PRD.hq.netapp.com (10.122.105.41) by vmwexceht03-prd.hq.netapp.com (10.106.76.241) with Microsoft SMTP Server (TLS) id 14.3.123.3; Wed, 27 Aug 2014 00:23:20 -0700 Received: from HIOEXCMBX07-PRD.hq.netapp.com (10.122.105.40) by hioexcmbx08-prd.hq.netapp.com (10.122.105.41) with Microsoft SMTP Server (TLS) id 15.0.913.22; Wed, 27 Aug 2014 00:23:19 -0700 Received: from HIOEXCMBX07-PRD.hq.netapp.com ([::1]) by hioexcmbx07-prd.hq.netapp.com ([fe80::55e3:a7dc:11bd:462%21]) with mapi id 15.00.0913.011; Wed, 27 Aug 2014 00:23:19 -0700 From: "Eggert, Lars" To: Adrian Chadd Subject: Re: Patches for RFC6937 and draft-ietf-tcpm-newcwv-00 Thread-Topic: Patches for RFC6937 and draft-ietf-tcpm-newcwv-00 Thread-Index: AQHPIYzc3HaTSw//+U6b0HMYTUDKSZt6PvOAgGprrICAAAMkgIAANjaAgADd6IA= Date: Wed, 27 Aug 2014 07:23:18 +0000 Message-ID: <76D986F7-72A8-4ABE-8731-064C6C77A56F@netapp.com> References: <259C9434-C6FE-42EA-823D-ECB024DBF3D7@netapp.com> <814E0886-1B6B-4316-8BAB-684DAFDE1983@netapp.com> <20140826145517.GD12732@gmail.com> In-Reply-To: Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: yes X-MS-TNEF-Correlator: x-mailer: Apple Mail (2.1878.6) x-originating-ip: [10.122.56.79] Content-Type: multipart/signed; boundary="Apple-Mail=_3746A6C5-7BF1-4796-97F0-9AC400CD96D2"; protocol="application/pgp-signature"; micalg=pgp-sha1 MIME-Version: 1.0 Cc: Tom Jones , FreeBSD Net X-BeenThere: freebsd-net@freebsd.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: Networking and TCP/IP with FreeBSD List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 27 Aug 2014 07:25:05 -0000 --Apple-Mail=_3746A6C5-7BF1-4796-97F0-9AC400CD96D2 Content-Type: multipart/mixed; boundary="Apple-Mail=_B62A7D06-AE4C-4A19-904D-CCD75DCF6A4A" --Apple-Mail=_B62A7D06-AE4C-4A19-904D-CCD75DCF6A4A Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=us-ascii It would be great if people could also review Aris' PRR patch - RFC6937 = has been out for a while. Lars --Apple-Mail=_B62A7D06-AE4C-4A19-904D-CCD75DCF6A4A Content-Disposition: attachment; filename=prr.patch Content-Type: application/octet-stream; name="prr.patch" Content-Transfer-Encoding: 7bit diff --git a/sys/netinet/tcp_input.c b/sys/netinet/tcp_input.c index 75609fd..70c29a8 100644 --- a/sys/netinet/tcp_input.c +++ b/sys/netinet/tcp_input.c @@ -145,6 +145,18 @@ SYSCTL_VNET_INT(_net_inet_tcp, OID_AUTO, drop_synfin, CTLFLAG_RW, &VNET_NAME(drop_synfin), 0, "Drop TCP packets with SYN+FIN set"); +VNET_DEFINE(int, tcp_do_prr_conservative) = 0; +#define V_tcp_do_prr_conservative VNET(tcp_do_prr_conservative) +SYSCTL_VNET_INT(_net_inet_tcp, OID_AUTO, do_prr_conservative, CTLFLAG_RW, + &VNET_NAME(tcp_do_prr_conservative), 0, + "Do conservative PRR"); + +VNET_DEFINE(int, tcp_do_prr) = 0; +#define V_tcp_do_prr VNET(tcp_do_prr) +SYSCTL_VNET_INT(_net_inet_tcp, OID_AUTO, do_prr, CTLFLAG_RW, + &VNET_NAME(tcp_do_prr), 0, + "Do the Proportional Rate Reduction Algorithm"); + VNET_DEFINE(int, tcp_do_rfc3042) = 1; #define V_tcp_do_rfc3042 VNET(tcp_do_rfc3042) SYSCTL_VNET_INT(_net_inet_tcp, OID_AUTO, rfc3042, CTLFLAG_RW, @@ -229,6 +241,7 @@ static void tcp_pulloutofband(struct socket *, struct tcphdr *, struct mbuf *, int); static void tcp_xmit_timer(struct tcpcb *, int); static void tcp_newreno_partial_ack(struct tcpcb *, struct tcphdr *); +static void tcp_prr_partial_ack(struct tcpcb *, struct tcphdr *); static void inline tcp_fields_to_host(struct tcphdr *); #ifdef TCP_SIGNATURE static void inline tcp_fields_to_net(struct tcphdr *); @@ -2460,7 +2473,50 @@ tcp_do_segment(struct mbuf *m, struct tcphdr *th, struct socket *so, else if (++tp->t_dupacks > tcprexmtthresh || IN_FASTRECOVERY(tp->t_flags)) { cc_ack_received(tp, th, CC_DUPACK); - if ((tp->t_flags & TF_SACK_PERMIT) && + if (V_tcp_do_prr && + IN_FASTRECOVERY(tp->t_flags) && + (tp->t_flags & TF_SACK_PERMIT)) { + long snd_cnt = 0, limit = 0, del_data = 0, pipe = 0; + /* + *In a duplicate ACK del_data is only the + *diff_in_sack. If no SACK is used del_data will be 0. + *Pipe is the amount of data we estimate to be + *in the network. + */ + del_data = tp->diff_in_sack; + pipe = (tp->snd_nxt - tp->snd_fack) + + tp->sackhint.sack_bytes_rexmit; + tp->prr_delivered += del_data; + if (pipe > tp->snd_ssthresh) + snd_cnt = (tp->prr_delivered * tp->snd_ssthresh / + tp->recover_fs) + 1 - tp->prr_out; + else { + if (V_tcp_do_prr_conservative) + limit = tp->prr_delivered - tp->prr_out; + else + if ((tp->prr_delivered - tp->prr_out) > del_data) + limit = tp->prr_delivered - tp->prr_out + + tp->t_maxseg; + else + limit = del_data + tp->t_maxseg; + if ((tp->snd_ssthresh - pipe) < limit) + snd_cnt = tp->snd_ssthresh - pipe; + else + snd_cnt = limit; + } + snd_cnt = (snd_cnt / tp->t_maxseg); + if (snd_cnt < 0) + snd_cnt = 0; + /* + * Send snd_cnt new data into the network in + * response to this ack.If there is gonna be a + * SACK retransmission, adjust snd_cwnd + * accordingly. + */ + tp->snd_cwnd = tp->snd_nxt - tp->sack_newdata + + tp->sackhint.sack_bytes_rexmit + (snd_cnt*tp->t_maxseg); + } + else if ((tp->t_flags & TF_SACK_PERMIT) && IN_FASTRECOVERY(tp->t_flags)) { int awnd; @@ -2495,12 +2551,18 @@ tcp_do_segment(struct mbuf *m, struct tcphdr *th, struct socket *so, tcp_seq onxt = tp->snd_nxt; /* - * If we're doing sack, check to - * see if we're already in sack + * If we're doing sack or prr, check to + * see if we're already in * recovery. If we're not doing sack, * check to see if we're in newreno * recovery. */ + if (V_tcp_do_prr) { + if (IN_FASTRECOVERY(tp->t_flags)) { + tp->t_dupacks = 0; + break; + } + } if (tp->t_flags & TF_SACK_PERMIT) { if (IN_FASTRECOVERY(tp->t_flags)) { tp->t_dupacks = 0; @@ -2518,6 +2580,15 @@ tcp_do_segment(struct mbuf *m, struct tcphdr *th, struct socket *so, cc_ack_received(tp, th, CC_DUPACK); tcp_timer_activate(tp, TT_REXMT, 0); tp->t_rtttime = 0; + if (V_tcp_do_prr) { + /* + * snd_ssthresh is already updated by cc_cong_signal. + */ + tp->prr_delivered = 0; + tp->prr_out = 0; + if(!(tp->recover_fs = tp->snd_nxt - tp->snd_una)) + tp->recover_fs = 1; + } if (tp->t_flags & TF_SACK_PERMIT) { TCPSTAT_INC( tcps_sack_recovery_episode); @@ -2614,7 +2685,9 @@ tcp_do_segment(struct mbuf *m, struct tcphdr *th, struct socket *so, */ if (IN_FASTRECOVERY(tp->t_flags)) { if (SEQ_LT(th->th_ack, tp->snd_recover)) { - if (tp->t_flags & TF_SACK_PERMIT) + if (V_tcp_do_prr && (tp->t_flags & TF_SACK_PERMIT)) + tcp_prr_partial_ack(tp, th); + else if (tp->t_flags & TF_SACK_PERMIT) tcp_sack_partialack(tp, th); else tcp_newreno_partial_ack(tp, th); @@ -3692,6 +3765,57 @@ tcp_mssopt(struct in_conninfo *inc) return (mss); } +static void +tcp_prr_partial_ack(struct tcpcb *tp, struct tcphdr *th) +{ + long snd_cnt = 0, limit = 0, del_data = 0, pipe = 0; + + INP_WLOCK_ASSERT(tp->t_inpcb); + + tcp_timer_activate(tp, TT_REXMT, 0); + tp->t_rtttime = 0; + /* + * Compute amount of data that this ACK is indicating (del_data) + * and an estimate of how many bytes are in the network. + */ + if (SEQ_GEQ(th->th_ack,tp->snd_una)) + del_data = BYTES_THIS_ACK(tp, th); + del_data += tp->diff_in_sack; + pipe = (tp->snd_nxt - tp->snd_fack) + tp->sackhint.sack_bytes_rexmit; + tp->prr_delivered += del_data; + /* + * Proportional Rate Reduction + */ + if (pipe > tp->snd_ssthresh) + snd_cnt = (tp->prr_delivered * tp->snd_ssthresh / tp->recover_fs) - + tp->prr_out; + else { + if (V_tcp_do_prr_conservative) + limit = tp->prr_delivered - tp->prr_out; + else + if ((tp->prr_delivered - tp->prr_out) > del_data) + limit = tp->prr_delivered - tp->prr_out + tp->t_maxseg; + else + limit = del_data + tp->t_maxseg; + if ((tp->snd_ssthresh - pipe) < limit) + snd_cnt = tp->snd_ssthresh - pipe; + else + snd_cnt = limit; + } + snd_cnt = (snd_cnt / tp->t_maxseg); + if (snd_cnt < 0) + snd_cnt = 0; + /* + * Send snd_cnt new data into the network + * in response to this ack. + * If there is gonna be a SACK retransmission, + * adjust snd_cwnd accordingly. + */ + tp->snd_cwnd = tp->snd_nxt - tp->sack_newdata + + tp->sackhint.sack_bytes_rexmit + (snd_cnt * tp->t_maxseg); + tp->t_flags |= TF_ACKNOW; + (void) tcp_output(tp); +} /* * On a partial ack arrives, force the retransmission of the diff --git a/sys/netinet/tcp_output.c b/sys/netinet/tcp_output.c index 00d5415..7b4936d 100644 --- a/sys/netinet/tcp_output.c +++ b/sys/netinet/tcp_output.c @@ -1194,6 +1194,8 @@ send: ((so->so_options & SO_DONTROUTE) ? IP_ROUTETOIF : 0), NULL, NULL, tp->t_inpcb); + if (V_tcp_do_prr && IN_FASTRECOVERY(tp->t_flags)) + tp->prr_out += len; if (error == EMSGSIZE && ro.ro_rt != NULL) mtu = ro.ro_rt->rt_rmx.rmx_mtu; RO_RTFREE(&ro); @@ -1232,6 +1234,8 @@ send: ((so->so_options & SO_DONTROUTE) ? IP_ROUTETOIF : 0), 0, tp->t_inpcb); + if (V_tcp_do_prr && IN_FASTRECOVERY(tp->t_flags)) + tp->prr_out += len; if (error == EMSGSIZE && ro.ro_rt != NULL) mtu = ro.ro_rt->rt_rmx.rmx_mtu; RO_RTFREE(&ro); @@ -1323,6 +1327,8 @@ timer: * XXX: It is a POLA question whether calling tcp_drop right * away would be the really correct behavior instead. */ + if (V_tcp_do_prr && IN_FASTRECOVERY(tp->t_flags)) + tp->prr_out -= len; if (((tp->t_flags & TF_FORCEDATA) == 0 || !tcp_timer_active(tp, TT_PERSIST)) && ((flags & TH_SYN) == 0) && diff --git a/sys/netinet/tcp_sack.c b/sys/netinet/tcp_sack.c index 440bd64..800df2f 100644 --- a/sys/netinet/tcp_sack.c +++ b/sys/netinet/tcp_sack.c @@ -348,9 +348,10 @@ tcp_sackhole_remove(struct tcpcb *tp, struct sackhole *hole) void tcp_sack_doack(struct tcpcb *tp, struct tcpopt *to, tcp_seq th_ack) { - struct sackhole *cur, *temp; + struct sackhole *cur, *temp, *temp1; struct sackblk sack, sack_blocks[TCP_MAX_SACK + 1], *sblkp; int i, j, num_sack_blks; + tcp_seq old = 0, new = 0; INP_WLOCK_ASSERT(tp->t_inpcb); @@ -382,13 +383,25 @@ tcp_sack_doack(struct tcpcb *tp, struct tcpopt *to, tcp_seq th_ack) sack_blocks[num_sack_blks++] = sack; } } + if (TAILQ_EMPTY(&tp->snd_holes)) + /* + * Empty scoreboard. Need to initialize snd_fack (it may be + * uninitialized or have a bogus value). Scoreboard holes + * (from the sack blocks received) are created later below + * (in the logic that adds holes to the tail of the + * scoreboard). + */ + tp->snd_fack = SEQ_MAX(tp->snd_una, th_ack); /* * Return if SND.UNA is not advanced and no valid SACK block is - * received. + * received.If no new valid SACK block the scoreboard remains + * the same, i.e. the difference is 0. */ - if (num_sack_blks == 0) + if (num_sack_blks == 0){ + if (V_tcp_do_prr) + tp->diff_in_sack = 0; return; - + } /* * Sort the SACK blocks so we can update the scoreboard with just one * pass. The overhead of sorting upto 4+1 elements is less than @@ -403,15 +416,14 @@ tcp_sack_doack(struct tcpcb *tp, struct tcpopt *to, tcp_seq th_ack) } } } - if (TAILQ_EMPTY(&tp->snd_holes)) - /* - * Empty scoreboard. Need to initialize snd_fack (it may be - * uninitialized or have a bogus value). Scoreboard holes - * (from the sack blocks received) are created later below - * (in the logic that adds holes to the tail of the - * scoreboard). - */ - tp->snd_fack = SEQ_MAX(tp->snd_una, th_ack); + if (V_tcp_do_prr) + if(!TAILQ_EMPTY(&tp->snd_holes)) + TAILQ_FOREACH(temp, &tp->snd_holes, scblink) { + if ((temp1 = TAILQ_NEXT(temp, scblink)) != NULL) + old += temp1->start - temp->end; + else if (SEQ_GT(tp->snd_fack, temp->end)) + old += tp->snd_fack - temp->end; + } /* * In the while-loop below, incoming SACK blocks (sack_blocks[]) and * SACK holes (snd_holes) are traversed from their tails with just @@ -540,6 +552,19 @@ tcp_sack_doack(struct tcpcb *tp, struct tcpopt *to, tcp_seq th_ack) else sblkp--; } + /* + * Calculate number of bytes in the scoreboard. + */ + if (V_tcp_do_prr) + if (!TAILQ_EMPTY(&tp->snd_holes)) + TAILQ_FOREACH(temp, &tp->snd_holes, scblink) { + if ((temp1 = TAILQ_NEXT(temp, scblink)) != NULL) + new += temp1->start - temp->end; + else if (SEQ_GT(tp->snd_fack, temp->end)) + new += tp->snd_fack - temp->end; + } + /* Change in the scoreboard in # of bytes */ + tp->diff_in_sack = new - old; } /* diff --git a/sys/netinet/tcp_subr.c b/sys/netinet/tcp_subr.c index 5d37b50..089d8c6 100644 --- a/sys/netinet/tcp_subr.c +++ b/sys/netinet/tcp_subr.c @@ -801,6 +801,7 @@ tcp_newtcpcb(struct inpcb *inp) tp->t_rxtcur = TCPTV_RTOBASE; tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT; tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT; + tp->diff_in_sack = 0; tp->t_rcvtime = ticks; /* * IPv4 TTL initialization is necessary for an IPv6 socket as well, diff --git a/sys/netinet/tcp_var.h b/sys/netinet/tcp_var.h index aaaa4a4..fe1507e 100644 --- a/sys/netinet/tcp_var.h +++ b/sys/netinet/tcp_var.h @@ -161,6 +161,11 @@ struct tcpcb { u_long t_rttupdated; /* number of times rtt sampled */ u_long max_sndwnd; /* largest window peer has offered */ + tcp_seq prr_delivered; /* Total bytes delivered during PRR recovery */ + tcp_seq prr_out; /* Total bytes sent during PRR recovery */ + tcp_seq recover_fs; /* FlightSize at the start of PRR recovery */ + tcp_seq diff_in_sack; /* (Signed) Difference of data in scoreboard due to the current ACK */ + int t_softerror; /* possible error not yet reported */ /* out-of-band data */ char t_oobflags; /* have some */ @@ -174,6 +179,7 @@ struct tcpcb { u_int32_t ts_offset; /* our timestamp offset */ tcp_seq last_ack_sent; + /* experimental */ u_long snd_cwnd_prev; /* cwnd prior to retransmit */ u_long snd_ssthresh_prev; /* ssthresh prior to retransmit */ @@ -627,8 +633,10 @@ VNET_DECLARE(int, tcp_abc_l_var); #define V_tcp_abc_l_var VNET(tcp_abc_l_var) VNET_DECLARE(int, tcp_do_sack); /* SACK enabled/disabled */ +VNET_DECLARE(int, tcp_do_prr); /* PRR enabled/disabled */ VNET_DECLARE(int, tcp_sc_rst_sock_fail); /* RST on sock alloc failure */ #define V_tcp_do_sack VNET(tcp_do_sack) +#define V_tcp_do_prr VNET(tcp_do_prr) #define V_tcp_sc_rst_sock_fail VNET(tcp_sc_rst_sock_fail) VNET_DECLARE(int, tcp_do_ecn); /* TCP ECN enabled/disabled */ --Apple-Mail=_B62A7D06-AE4C-4A19-904D-CCD75DCF6A4A Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=us-ascii On 2014-8-26, at 20:09, Adrian Chadd wrote: > Hi! >=20 > I'm going to merge Tom's work in a week unless someone gives me a > really good reason not to. >=20 > I think there's been enough work and discussion about it since the > first post from Lars in Feburary and enough review opportunity. >=20 >=20 > -a >=20 >=20 > On 26 August 2014 07:55, Tom Jones wrote: >> On Tue, Aug 26, 2014 at 02:43:49PM +0000, Eggert, Lars wrote: >>> Hi, >>>=20 >>> the newcwv patch is probably stale now with Tom Jones' recent patch = based on >>> a more up-to-date version of the Internet-Draft, but the PRR patch = should >>> still be useful? >>=20 >> My newcwv patch is much more up to date than Aris's, but it is = slightly >> different in implementation. I have had a few suggestions from = Adrian, but he >> couldn't comment on how it relates to the tcp internals. >>=20 >> There is a PR: = https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=3D191520 >>=20 >> The biggest difference in structure between mine and Aris's patch is = the use of >> tcp timers. It would be good to hear if my approach or Aris's is = prefered. >>=20 >>> On 2014-6-19, at 23:35, George Neville-Neil = wrote: >>>=20 >>>> On 4 Feb 2014, at 1:38, Eggert, Lars wrote: >>>>=20 >>>>> Hi, >>>>>=20 >>>>> below are two patches that implement RFC6937 ("Proportional Rate = Reduction for TCP") and draft-ietf-tcpm-newcwv-00 ("Updating TCP to = support Rate-Limited Traffic"). They were done by Aris = Angelogiannopoulos for his MS thesis, which is at = https://eggert.org/students/angelogiannopoulos-thesis.pdf. >>>>>=20 >>>>> The patches should apply to -CURRENT as of Sep 17, 2013. (Sorry = for the delay in sending them, we'd been trying to get some feedback = from committers first, without luck.) >>>>>=20 >>>>> Please note that newcwv is still a work in progress in the IETF, = and the patch has some limitations with regards to the "pipeACK Sampling = Period" mentioned in the Internet-Draft. Aris says this in his thesis = about what exactly he implemented: >>>>>=20 >>>>> "The second implementation choice, is in regards with the = measurement of pipeACK. This variable is the most important introduced = by the method and is used to compute the phase that the sender currently = lies in. In order to compute pipeACK the approach suggested by the = Internet Draft (ID) is followed [ncwv]. During initialization, pipeACK = is set to the maximum possible value. A helper variable prevHighACK is = introduced that is initialized to the initial sequence number (iss). = prevHighACK holds the value of the highest acknowledged byte so far. = pipeACK is measured once per RTT meaning that when an ACK covering = prevHighACK is received, pipeACK becomes the difference between the = current ACK and prevHighACK. This is called a pipeACK sample. A newer = version of the draft suggests that multiple pipeACK samples can be used = during the pipeACK sampling period." >>>>>=20 >>>>> Lars >>>>>=20 >>>>>=20 >>>>> [prr.patch] >>>>>=20 >>>>> [newcwv.patch] >>>>=20 >>>> Apologies for not looking at this as yet. It is now closer to the = top of my list. >>>>=20 >>>> Best, >>>> George >>>=20 >>=20 >>=20 >>=20 >> -- >> Tom >> @adventureloop >> adventurist.me >>=20 >> :wq >> _______________________________________________ >> freebsd-net@freebsd.org mailing list >> http://lists.freebsd.org/mailman/listinfo/freebsd-net >> To unsubscribe, send any mail to = "freebsd-net-unsubscribe@freebsd.org" --Apple-Mail=_B62A7D06-AE4C-4A19-904D-CCD75DCF6A4A-- --Apple-Mail=_3746A6C5-7BF1-4796-97F0-9AC400CD96D2 Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="signature.asc" Content-Type: application/pgp-signature; name="signature.asc" Content-Description: Message signed with OpenPGP using GPGMail -----BEGIN PGP SIGNATURE----- iQCVAwUBU/2HedZcnpRveo1xAQKWOQP8CAI7w0vJ9ttPJxLIGF9p77PsZgcoV8wU qbmH4RfkDYGzdHhEXqYQkIBYOCsrDVCGHYLjVzmBMRGbY5+s3Jz/mGI8m2IXh/n/ rs7kkp1AORY5BsBCHjObfXWGicNSw8btpQSDkoTPfrk4zTHtWQESwFAB/ud1M/31 V+USeVu/pV0= =eWB1 -----END PGP SIGNATURE----- --Apple-Mail=_3746A6C5-7BF1-4796-97F0-9AC400CD96D2--