OpenSSL状态机中可选消息的处理
openssl在實(shí)現(xiàn)ssl握手的時(shí)候是用狀態(tài)機(jī)實(shí)現(xiàn)的,實(shí)際上linux內(nèi)核的tcp協(xié)議也是狀態(tài)機(jī)實(shí)現(xiàn)的,原因就在于這些協(xié)議本身的握手規(guī)則就是狀態(tài)機(jī)(簡(jiǎn)直是廢話)。SSLv3握手規(guī)則要比tcp復(fù)雜的多,因此它的實(shí)現(xiàn)如果要既美觀又高效的話就一定需要很多技巧,造成這種結(jié)果的原因就在于ssl握手過(guò)程中存在很多的可選消息,而這些可選消息是否存在并不簡(jiǎn)單依賴與前一個(gè)或者前若干個(gè)消息的消息頭,而只有解析了前面的消息體內(nèi)容之后才能知道是否存在可選消息,在這個(gè)意義上可以說(shuō)客戶端的握手狀態(tài)機(jī)實(shí)現(xiàn)要么保存曾經(jīng)的消息遺留的狀態(tài),要么不保留狀態(tài),默認(rèn)按狀態(tài)機(jī)轉(zhuǎn)化順序處理任何消息(還好,狀態(tài)機(jī)有且只有一種順序),如果采用前一個(gè)方案的話,那么勢(shì)必要在握手規(guī)則和消息處理handler之間進(jìn)行耦合,如果那一天改變了一方的框架,就會(huì)導(dǎo)致另一方的改變,openssl的實(shí)現(xiàn)采用了后一種解決方案巧妙的解決了這個(gè)問(wèn)題,不管怎樣狀態(tài)機(jī)順序地在所有可能的狀態(tài)中轉(zhuǎn)變,每一個(gè)狀態(tài)中調(diào)用固定的處理函數(shù),然后在處理函數(shù)中采用了一個(gè)技巧:處理函數(shù)首先不去下面的BIO(一般是tcp套接字)中讀消息,而是先檢查一個(gè)標(biāo)志reuse,如果這個(gè)標(biāo)志置位了,那么就清除這個(gè)標(biāo)志,取上次讀到的消息后返回,在返回狀態(tài)機(jī)處理handler之后,首先根據(jù)得到消息的頭判斷其類型是否是狀態(tài)機(jī)處于該狀態(tài)所處理消息的對(duì)應(yīng)類型,如果不是,上述的reuse標(biāo)志重新置位,handler返回,狀態(tài)機(jī)推進(jìn)到下一個(gè)狀態(tài),從而在接下來(lái)的狀態(tài)處理當(dāng)中不再?gòu)奶捉幼肿x取消息。大致框架就是:
state_machine()
{
??? while (true) {
??? ??? switch (state)
??? ??? ??? case s1:
??? ??? ??? ??? handler1(param);
??? ??? ??? ??? state = s2;
??? ??? ??? ??? break;
??? ??? ??? case s2:
??? ??? ??? ??? handler2(param);
??? ??? ??? ??? state = s3;
??? ??? ??? ??? break;
??? ??? ??? case s3:
??? ??? ??? ??? handler3(param);
??? ??? ??? ??? state = s4;
??? ??? ??? ??? break;
??? ??? ??? ...
??? }
}
如果s2狀態(tài)所對(duì)應(yīng)的消息是可選的,但是s3是必須的狀態(tài),并且此次此時(shí)并不需要處理s2這個(gè)狀態(tài),但是handler2中確實(shí)要讀取消息,怎么辦呢?如果真的從套接字讀取消息的話,那么處理就會(huì)亂掉,handler2中讀取的是s3狀態(tài)所對(duì)應(yīng)的消息,那么怎么處理呢?openssl的方式是在case s2中調(diào)用handler2之中判斷一下當(dāng)前讀到消息的類型,如果不是對(duì)應(yīng)于s2的消息,那就說(shuō)明s2狀態(tài)所對(duì)應(yīng)的消息不存在,于是在case s2的handler中進(jìn)行完判斷之后,將一個(gè)reuse標(biāo)志設(shè)置上,于是在s3狀態(tài)中的s3處理中首先判斷是否有reuse標(biāo)志,如果有的話,那么就不再?gòu)奶捉幼肿x取消息,而是將在handler2中讀取的消息返回,并且清除reuse標(biāo)志,之后以此類推??偨Y(jié)一下就是只要在可選消息的handler處理之后,都要進(jìn)行類似s2處理的判斷,如果失敗就將reuse設(shè)置。
???? 但是這又引出了另外一個(gè)問(wèn)題,那就是在每個(gè)handler中都要判斷reuse標(biāo)志,這個(gè)工作實(shí)在繁重,狀態(tài)機(jī)處理是解放了,每個(gè)狀態(tài)的handler的任務(wù)卻更加繁重了,任何好的設(shè)計(jì)需要做到的就是盡量將繁雜的判斷放到穩(wěn)定的邏輯當(dāng)中,狀態(tài)機(jī)是不穩(wěn)定的,因?yàn)橛泻芏嗫蛇x狀態(tài),每個(gè)狀態(tài)的handler也不是穩(wěn)定的,因?yàn)槊總€(gè)狀態(tài)的處理太復(fù)雜了,隨著版本的更新,任何細(xì)節(jié)的改變都會(huì)導(dǎo)致大量的更改,于是openssl就把這個(gè)邏輯抽取出來(lái),專門(mén)放到SSL_METHOD的一個(gè)回調(diào)函數(shù)ssl3_get_message中,在調(diào)用完這個(gè)回調(diào)函數(shù)之后馬上檢查讀到的消息類型是否是我們本狀態(tài)要處理的類型,如果不是,那么馬上將reuse設(shè)置為1,我們看一下ssl_get_message中在真正在套接字上讀取消息之前的一個(gè)處理:
if (s->s3->tmp.reuse_message)
{
??? s->s3->tmp.reuse_message=0;
??? if ((mt >= 0) && (s->s3->tmp.message_type != mt)) {
??? ??? ...
??? }
??? *ok=1;
??? s->init_msg = s->init_buf->data + 4;
??? s->init_num = (int)s->s3->tmp.message_size;
??? return s->init_num;
}
以上代碼是s->method->ssl_get_message中首先要調(diào)用的,對(duì)應(yīng)上面代碼的另一部分在ssl3_connect中可選消息handler的調(diào)用之后,以ssl3_get_key_exchange為例:
int ssl3_get_key_exchange(SSL *s)
{
...
??? n=s->method->ssl_get_message(s,
??? ??? SSL3_ST_CR_KEY_EXCH_A,
??? ??? SSL3_ST_CR_KEY_EXCH_B,
??? ??? -1,
??? ??? s->max_cert_list,
??? ??? &ok);
...
??? if (s->s3->tmp.message_type != SSL3_MT_SERVER_KEY_EXCHANGE) {
??? ??? s->s3->tmp.reuse_message=1;
??? ??? return(1);
??? }
...
}
openssl的這種實(shí)現(xiàn)使得握手狀態(tài)機(jī)處理看起來(lái)十分清晰,所有的處理消息是否可選的邏輯都隱藏到了邏輯比較單一的回調(diào)函數(shù)中,然后在邏輯復(fù)雜的處理函數(shù)中簡(jiǎn)單判斷即可,這種分離比SSLv2的設(shè)計(jì)要更好,在SSLv2的狀態(tài)機(jī)實(shí)現(xiàn)中,每一個(gè)狀態(tài)處理完了之后就會(huì)判斷結(jié)果,然后根據(jù)結(jié)果去設(shè)置下一個(gè)狀態(tài),其實(shí)這種方式更像是一個(gè)狀態(tài)機(jī),SSLv3的實(shí)現(xiàn)反而不像狀態(tài)機(jī)了,一個(gè)一個(gè)順序的處理,沒(méi)有變數(shù),這種情況其實(shí)完全不用狀態(tài)機(jī)實(shí)現(xiàn),一個(gè)順序執(zhí)行的函數(shù)就可以了,連循環(huán)都不用,誰(shuí)知道以后的版本會(huì)怎樣呢。
轉(zhuǎn)載于:https://blog.51cto.com/dog250/1271994
總結(jié)
以上是生活随笔為你收集整理的OpenSSL状态机中可选消息的处理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Office无法打开超链接地址问题
- 下一篇: JDBC+Servlet+JSP整合开发